Ticket #6630: 00-forms-fieldsets.diff

File 00-forms-fieldsets.diff, 63.6 KB (added by Petr Marhoun <petr.marhoun@…>, 16 years ago)
  • django/forms/metaclassing.py

    === added file 'django/forms/metaclassing.py'
     
     1"""
     2Functions for form metaclasses. Their name describes their purpose.
     3"""
     4
     5from django.core.exceptions import ImproperlyConfigured
     6from django.utils.datastructures import SortedDict
     7
     8from fields import Field
     9from widgets import media_property
     10
     11def create_meta(cls, attrs):
     12    cls._meta = cls._options(getattr(cls, 'Meta', None))
     13
     14def create_declared_fields(cls, attrs):
     15    fields = []
     16    for name, possible_field in attrs.items():
     17        if isinstance(possible_field, Field):
     18            fields.append((name, possible_field))
     19            delattr(cls, name)
     20    fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
     21    cls.declared_fields = SortedDict(fields)
     22
     23def create_model_fields(cls, attrs):
     24    formfield_callback = attrs.pop('formfield_callback', lambda f: f.formfield())
     25    fields = []
     26    if cls._meta.model:
     27        for dbfield in cls._meta.model._meta.fields + cls._meta.model._meta.many_to_many:
     28            if dbfield.editable:
     29                formfield = formfield_callback(dbfield)
     30                if formfield:
     31                    fields.append((dbfield.name, formfield))
     32    cls.model_fields = SortedDict(fields)
     33
     34def create_base_fields_pool_from_declared_fields(cls, attrs):
     35    fields = []
     36    for base in cls.__mro__[::-1]:
     37        try:
     38            fields += base.declared_fields.items()
     39        except AttributeError:
     40            pass
     41    cls.base_fields_pool = SortedDict(fields)
     42
     43def create_base_fields_pool_from_model_fields_and_declared_fields(cls, attrs):
     44    model_fields, declared_fields = [], []
     45    for base in cls.__mro__[::-1]:
     46        try:
     47            declared_fields += base.declared_fields.items()
     48            if base._meta.model:
     49                model_fields = base.model_fields.items()
     50        except AttributeError:
     51            pass
     52    cls.base_fields_pool = SortedDict(model_fields + declared_fields)
     53
     54def create_base_fields_from_base_fields_pool(cls, attrs):
     55    if (cls._meta.fieldsets is None) + (cls._meta.fields is None) + (cls._meta.exclude is None) < 2:
     56        raise ImproperlyConfigured("%s cannot have more than one option from fieldsets, fields and exclude." % cls.__name__)
     57    if cls._meta.fieldsets:
     58        names = []
     59        for fieldset in cls._meta.fieldsets:
     60            names.extend(fieldset['fields'])
     61    elif cls._meta.fields:
     62        names = cls._meta.fields
     63    elif cls._meta.exclude:
     64        names = [name for name in cls.base_fields_pool if name not in cls._meta.exclude]
     65    else:
     66        names = cls.base_fields_pool.keys()
     67    cls.base_fields = SortedDict([(name, cls.base_fields_pool[name]) for name in names])
     68
     69def create_media(cls, attrs):
     70    if not 'media' in attrs:
     71        cls.media = media_property(cls)
  • django/contrib/admin/validation.py

    === modified file 'django/contrib/admin/validation.py'
     
    263263                % (cls.__name__, label, field, model.__name__))
    264264
    265265def _check_form_field_exists(cls, model, opts, label, field):
    266     if hasattr(cls.form, 'base_fields'):
     266    if hasattr(cls.form, 'base_fields') and cls.form.base_fields:
    267267        try:
    268268            cls.form.base_fields[field]
    269269        except KeyError:
  • django/contrib/auth/forms.py

    === modified file 'django/contrib/auth/forms.py'
     
    1919   
    2020    class Meta:
    2121        model = User
    22         fields = ("username",)
     22        fields = ("username", "password1", "password2")
    2323   
    2424    def clean_username(self):
    2525        username = self.cleaned_data["username"]
  • django/contrib/auth/tests/forms.py

    === modified file 'django/contrib/auth/tests/forms.py'
     
    4242>>> form.is_valid()
    4343False
    4444>>> form["password2"].errors
    45 [u"The two password fields didn't match."]
     45[u'The two password fields didn&#39;t match.']
    4646
    4747The success case.
    4848
     
    107107>>> form.is_valid()
    108108False
    109109>>> form["new_password2"].errors
    110 [u"The two password fields didn't match."]
     110[u'The two password fields didn&#39;t match.']
    111111
    112112The success case.
    113113
     
    145145>>> form.is_valid()
    146146False
    147147>>> form["new_password2"].errors
    148 [u"The two password fields didn't match."]
     148[u'The two password fields didn&#39;t match.']
    149149
    150150The success case.
    151151
  • django/contrib/auth/tests/views.py

    === modified file 'django/contrib/auth/tests/views.py'
     
    1313        response = self.client.get('/password_reset/')
    1414        self.assertEquals(response.status_code, 200)
    1515        response = self.client.post('/password_reset/', {'email': 'not_a_real_email@email.com'})
    16         self.assertContains(response, "That e-mail address doesn't have an associated user account")
     16        self.assertContains(response, "That e-mail address doesn&#39;t have an associated user account")
    1717        self.assertEquals(len(mail.outbox), 0)
    1818   
    1919    def test_email_found(self):
     
    8484        response = self.client.post(path, {'new_password1': 'anewpassword',
    8585                                           'new_password2':' x'})
    8686        self.assertEquals(response.status_code, 200)
    87         self.assert_("The two password fields didn't match" in response.content)
     87        self.assert_("The two password fields didn&#39;t match" in response.content)
    8888
  • django/forms/extras/widgets.py

    === modified file 'django/forms/extras/widgets.py'
     
    2424    day_field = '%s_day'
    2525    year_field = '%s_year'
    2626
    27     def __init__(self, attrs=None, years=None):
     27    def __init__(self, attrs=None, years=None, row_attrs=None):
    2828        # years is an optional list/tuple of years to use in the "year" select box.
    29         self.attrs = attrs or {}
     29        super(SelectDateWidget, self).__init__(attrs, row_attrs)
    3030        if years:
    3131            self.years = years
    3232        else:
  • django/forms/forms.py

    === modified file 'django/forms/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.html import conditional_escape
    98from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
    109from django.utils.safestring import mark_safe
    1110
    12 from fields import Field, FileField
    13 from widgets import Media, media_property, TextInput, Textarea
     11from fields import FileField
     12from widgets import Media, TextInput, Textarea
    1413from util import flatatt, ErrorDict, ErrorList, ValidationError
     14import metaclassing
    1515
    1616__all__ = ('BaseForm', 'Form')
    1717
     
    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     Also integrates any additional media definitions
    35     """
    36     fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
    37     fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
    38 
    39     # If this class is subclassing another Form, add that Form's fields.
    40     # Note that we loop over the bases in *reverse*. This is necessary in
    41     # order to preserve the correct order of fields.
    42     if with_base_fields:
    43         for base in bases[::-1]:
    44             if hasattr(base, 'base_fields'):
    45                 fields = base.base_fields.items() + fields
    46     else:
    47         for base in bases[::-1]:
    48             if hasattr(base, 'declared_fields'):
    49                 fields = base.declared_fields.items() + fields
    50 
    51     return SortedDict(fields)
    52 
    53 class DeclarativeFieldsMetaclass(type):
    54     """
    55     Metaclass that converts Field attributes to a dictionary called
    56     'base_fields', taking into account parent class 'base_fields' as well.
    57     """
     25class FormOptions(object):
     26    def __init__(self, options=None):
     27        self.fieldsets = getattr(options, 'fieldsets', None)
     28        self.fields = getattr(options, 'fields', None)
     29        self.exclude = getattr(options, 'exclude', None)
     30
     31class FormMetaclass(type):
    5832    def __new__(cls, name, bases, attrs):
    59         attrs['base_fields'] = get_declared_fields(bases, attrs)
    60         new_class = super(DeclarativeFieldsMetaclass,
    61                      cls).__new__(cls, name, bases, attrs)
    62         if 'media' not in attrs:
    63             new_class.media = media_property(new_class)
     33        new_class = type.__new__(cls, name, bases, attrs)
     34        metaclassing.create_meta(new_class, attrs)
     35        metaclassing.create_declared_fields(new_class, attrs)
     36        metaclassing.create_base_fields_pool_from_declared_fields(new_class, attrs)
     37        metaclassing.create_base_fields_from_base_fields_pool(new_class, attrs)
     38        metaclassing.create_media(new_class, attrs)
    6439        return new_class
    6540
    6641class BaseForm(StrAndUnicode):
     
    10681        return BoundField(self, field, name)
    10782
    10883    def _get_errors(self):
    109         "Returns an ErrorDict for the data provided for the form"
     84        "Returns an ErrorDict for the data provided for the form."
    11085        if self._errors is None:
    11186            self.full_clean()
    11287        return self._errors
     
    11994        """
    12095        return self.is_bound and not bool(self.errors)
    12196
    122     def add_prefix(self, field_name):
     97    def add_prefix(self, name):
    12398        """
    12499        Returns the field name with a prefix appended, if this Form has a
    125100        prefix set.
    126101
    127102        Subclasses may wish to override.
    128103        """
    129         return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name
    130 
    131     def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row):
     104        return self.prefix and ('%s-%s' % (self.prefix, name)) or name
     105
     106    def has_fieldsets(self):
     107        "Returns True if this form has fieldsets."
     108        return bool(self._meta.fieldsets)
     109
     110    def first_fieldset_attrs(self):
     111        "Returns attributes for first fieldset as HTML code."
     112        if self.has_fieldsets() and 'attrs' in self._meta.fieldsets[0]:
     113            return flatatt(self._meta.fieldsets[0]['attrs'])
     114        else:
     115            return u''
     116
     117    def first_fieldset_legend_tag(self):
     118        "Returns legend tag for first fieldset as HTML code."
     119        if self.has_fieldsets() and 'legend' in self._meta.fieldsets[0]:
     120            return mark_safe(u'<legend>%s</legend>' % conditional_escape(force_unicode(self._meta.fieldsets[0]['legend'])))
     121        else:
     122            return u''
     123
     124    def _label_tag_html_output(self, bf, label_tag_html):
     125        "Helper function for outputting HTML from a label. Used by _row_html_output."
     126        label, label_id = bf.label, bf.label_id
     127        if self.label_suffix and label and label[-1] not in ':?.!':
     128            label += self.label_suffix
     129        if label and label_id:
     130            return label_tag_html % {
     131                'label': label,
     132                'id': label_id,
     133            }
     134        else:
     135            return label
     136
     137    def _help_text_html_output(self, bf, help_text_html):
     138        "Helper function for outputting HTML from a help text. Used by _row_html_output."
     139        if bf.help_text:
     140            return help_text_html % {
     141                'help_text': bf.help_text,
     142            }
     143        else:
     144            return u''
     145
     146    def _row_html_output(self, bf, row_html, label_tag_html, help_text_html):
     147        "Helper function for outputting HTML from a widget. Used by _html_output."
     148        return row_html % {
     149            'rendered_widget': unicode(bf),
     150            'rendered_errors': unicode(bf.errors),
     151            'label_tag': self._label_tag_html_output(bf, label_tag_html),
     152            'help_text': self._help_text_html_output(bf, help_text_html),
     153            'attrs': bf.row_attrs,
     154        }
     155
     156    def _top_errors_html_output(self, top_errors, top_errors_html):
     157        "Helper function for outputting HTML from a top errors. Used by _html_output."
     158        return top_errors_html % {
     159            'top_errors': unicode(top_errors),
     160        }
     161
     162    def _fieldset_html_output(self, fields, fieldset, is_first, is_last, fieldset_start_html, fieldset_end_html, legend_tag_html):
     163        "Helper function for outputting HTML from a fieldset. Used by _html_output."
     164        output = []
     165        if not is_first:
     166            legend_tag = attrs = u''
     167            if 'legend' in fieldset:
     168                legend_tag = legend_tag_html % {
     169                    'legend': conditional_escape(force_unicode(fieldset['legend'])),
     170                }
     171            if 'attrs' in fieldset:
     172                attrs = flatatt(fieldset.get('attrs'))
     173            output.append(fieldset_start_html % {
     174                'legend_tag': legend_tag,
     175                'attrs': attrs,
     176            })
     177        for name in fieldset['fields']:
     178            output.append(fields[name])
     179        if not is_last:
     180            output.append(fieldset_end_html)
     181        return u'\n'.join(output)
     182
     183    def _hidden_fields_html_output(self, hidden_fields, hidden_fields_html):
     184        "Helper function for outputting HTML from a hidden fields. Used by _html_output."
     185        return hidden_fields_html % {
     186            'hidden_fields': u''.join(hidden_fields),
     187        }
     188
     189    def _html_output(self, row_html, label_tag_html, help_text_html, top_errors_html,
     190            fieldset_start_html, legend_tag_html, fieldset_end_html, hidden_fields_html):
    132191        "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
    133         top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
    134         output, hidden_fields = [], []
     192        output = []
     193        top_errors, hidden_fields, visible_fields = self.non_field_errors(), [], {}
    135194        for name, field in self.fields.items():
    136195            bf = BoundField(self, field, name)
    137             bf_errors = self.error_class([escape(error) for error in bf.errors]) # Escape and cache in local variable.
    138196            if bf.is_hidden:
    139                 if bf_errors:
    140                     top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
     197                if bf.errors:
     198                    top_errors.extend([u'(Hidden field %s) %s' % (name, conditional_escape(force_unicode(e))) for e in bf.errors])
    141199                hidden_fields.append(unicode(bf))
    142200            else:
    143                 if errors_on_separate_row and bf_errors:
    144                     output.append(error_row % force_unicode(bf_errors))
    145                 if bf.label:
    146                     label = escape(force_unicode(bf.label))
    147                     # Only add the suffix if the label does not end in
    148                     # punctuation.
    149                     if self.label_suffix:
    150                         if label[-1] not in ':?.!':
    151                             label += self.label_suffix
    152                     label = bf.label_tag(label) or ''
    153                 else:
    154                     label = ''
    155                 if field.help_text:
    156                     help_text = help_text_html % force_unicode(field.help_text)
    157                 else:
    158                     help_text = u''
    159                 output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text})
     201                visible_fields[name] = self._row_html_output(bf, row_html, label_tag_html, help_text_html)
    160202        if top_errors:
    161             output.insert(0, error_row % force_unicode(top_errors))
    162         if hidden_fields: # Insert any hidden fields in the last row.
    163             str_hidden = u''.join(hidden_fields)
    164             if output:
    165                 last_row = output[-1]
    166                 # Chop off the trailing row_ender (e.g. '</td></tr>') and
    167                 # insert the hidden fields.
    168                 output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
    169             else:
    170                 # If there aren't any rows in the output, just append the
    171                 # hidden fields.
    172                 output.append(str_hidden)
     203            output.append(self._top_errors_html_output(top_errors, top_errors_html))
     204        if self.has_fieldsets():
     205            for i, fieldset in enumerate(self._meta.fieldsets):
     206                fields = dict((name, visible_fields[name]) for name in fieldset['fields'] if name in visible_fields)
     207                is_first = (i == 0)
     208                is_last = (i + 1 == len(self._meta.fieldsets))
     209                output.append(self._fieldset_html_output(fields, fieldset, is_first, is_last,
     210                    fieldset_start_html, fieldset_end_html, legend_tag_html))
     211        else:
     212            for name in self.fields:
     213                if name in visible_fields:
     214                    output.append(visible_fields[name])
     215        if hidden_fields:
     216            output.append(self._hidden_fields_html_output(hidden_fields, hidden_fields_html))
    173217        return mark_safe(u'\n'.join(output))
    174218
    175219    def as_table(self):
    176220        "Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
    177         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)
     221        kwargs = {
     222            'row_html': u'<tr%(attrs)s><th>%(label_tag)s</th><td>%(rendered_errors)s%(rendered_widget)s%(help_text)s</td></tr>',
     223            'label_tag_html': u'<label for="%(id)s">%(label)s</label>',
     224            'help_text_html': u'<br />%(help_text)s',
     225            'top_errors_html': u'<tr><td colspan="2">%(top_errors)s</td></tr>',
     226            'fieldset_start_html': u'<fieldset%(attrs)s>\n%(legend_tag)s<table>',
     227            'fieldset_end_html': u'</table>\n</fieldset>',
     228            'legend_tag_html': u'<legend>%(legend)s</legend>\n',
     229            'hidden_fields_html': u'<tr class="hidden"><td colspan="2">%(hidden_fields)s</td></tr>',
     230        }
     231        return self._html_output(**kwargs)
    178232
    179233    def as_ul(self):
    180234        "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
    181         return self._html_output(u'<li>%(errors)s%(label)s %(field)s%(help_text)s</li>', u'<li>%s</li>', '</li>', u' %s', False)
     235        kwargs = {
     236            'row_html': u'<li%(attrs)s>%(rendered_errors)s%(label_tag)s %(rendered_widget)s%(help_text)s</li>',
     237            'label_tag_html': u'<label for="%(id)s">%(label)s</label>',
     238            'help_text_html': u' %(help_text)s',
     239            'top_errors_html': u'<li>%(top_errors)s</li>',
     240            'fieldset_start_html': u'<fieldset%(attrs)s>\n%(legend_tag)s<ul>',
     241            'fieldset_end_html': u'</ul>\n</fieldset>',
     242            'legend_tag_html': u'<legend>%(legend)s</legend>\n',
     243            'hidden_fields_html': u'<li class="hidden">%(hidden_fields)s</li>',
     244        }
     245        return self._html_output(**kwargs)
    182246
    183247    def as_p(self):
    184248        "Returns this form rendered as HTML <p>s."
    185         return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'%s', '</p>', u' %s', True)
     249        kwargs = {
     250            'row_html': u'%(rendered_errors)s<p%(attrs)s>%(label_tag)s %(rendered_widget)s%(help_text)s</p>',
     251            'label_tag_html': u'<label for="%(id)s">%(label)s</label>',
     252            'help_text_html': u' %(help_text)s',
     253            'top_errors_html': u'%(top_errors)s',
     254            'fieldset_start_html': u'<fieldset%(attrs)s>\n%(legend_tag)s',
     255            'fieldset_end_html': u'</fieldset>',
     256            'legend_tag_html': u'<legend>%(legend)s</legend>\n',
     257            'hidden_fields_html': u'<p class="hidden">%(hidden_fields)s</p>',
     258        }
     259        return self._html_output(**kwargs)
    186260
    187261    def non_field_errors(self):
    188262        """
     
    221295                    value = getattr(self, 'clean_%s' % name)()
    222296                    self.cleaned_data[name] = value
    223297            except ValidationError, e:
    224                 self._errors[name] = e.messages
     298                self._errors[name] = self.error_class(e.messages)
    225299                if name in self.cleaned_data:
    226300                    del self.cleaned_data[name]
    227301        try:
    228302            self.cleaned_data = self.clean()
    229303        except ValidationError, e:
    230             self._errors[NON_FIELD_ERRORS] = e.messages
     304            self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages)
    231305        if self._errors:
    232306            delattr(self, 'cleaned_data')
    233307
     
    291365    # fancy metaclass stuff purely for the semantic sugar -- it allows one
    292366    # to define a form using declarative syntax.
    293367    # BaseForm itself has no way of designating self.fields.
    294     __metaclass__ = DeclarativeFieldsMetaclass
     368    __metaclass__ = FormMetaclass
     369    _options = FormOptions
    295370
    296371class BoundField(StrAndUnicode):
    297372    "A Field plus data"
    298373    def __init__(self, form, field, name):
    299374        self.form = form
    300375        self.field = field
     376        self.widget = field.widget
     377        self.required = self.field.required
     378        self.is_hidden = self.widget.is_hidden
    301379        self.name = name
    302380        self.html_name = form.add_prefix(name)
    303         if self.field.label is None:
    304             self.label = pretty_name(name)
    305         else:
    306             self.label = self.field.label
    307         self.help_text = field.help_text or ''
    308381
    309382    def __unicode__(self):
    310383        """Renders this field as an HTML widget."""
     
    325398        field's default widget will be used.
    326399        """
    327400        if not widget:
    328             widget = self.field.widget
     401            widget = self.widget
    329402        attrs = attrs or {}
    330403        auto_id = self.auto_id
    331404        if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
     
    358431        """
    359432        Returns the data for this BoundField, or None if it wasn't given.
    360433        """
    361         return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
     434        return self.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
    362435    data = property(_data)
    363436
     437    def _label(self):
     438        "Returns label for this field as safe HTML."
     439        if self.field.label is None:
     440            return pretty_name(self.name)
     441        else:
     442            return conditional_escape(force_unicode(self.field.label))
     443    label = property(_label)
     444
     445    def _label_id(self):
     446        "Returns label id for this field as safe HTML."
     447        id_ = self.widget.attrs.get('id') or self.auto_id
     448        if id_:
     449            return self.widget.id_for_label(id_)
     450    label_id = property(_label_id)
     451
     452    def _help_text(self):
     453        "Returns help text for this field as safe HTML."
     454        if self.field.help_text is None:
     455            return u''
     456        else:
     457            return force_unicode(self.field.help_text)
     458    help_text = property(_help_text)
     459
     460    def _row_attrs(self):
     461        "Returns row attributes for this field as safe HTML."
     462        return flatatt(self.widget.row_attrs)
     463    row_attrs = property(_row_attrs)
     464
    364465    def label_tag(self, contents=None, attrs=None):
    365466        """
    366467        Wraps the given contents in a <label>, if the field has an ID attribute.
     
    369470
    370471        If attrs are given, they're used as HTML attributes on the <label> tag.
    371472        """
    372         contents = contents or escape(self.label)
    373         widget = self.field.widget
    374         id_ = widget.attrs.get('id') or self.auto_id
    375         if id_:
     473        contents = contents or self.label
     474        if self.label_id:
    376475            attrs = attrs and flatatt(attrs) or ''
    377             contents = '<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, contents)
     476            contents = '<label for="%s"%s>%s</label>' % (self.label_id, attrs, contents)
    378477        return mark_safe(contents)
    379478
    380     def _is_hidden(self):
    381         "Returns True if this BoundField's widget is hidden."
    382         return self.field.widget.is_hidden
    383     is_hidden = property(_is_hidden)
    384 
    385479    def _auto_id(self):
    386480        """
    387481        Calculates and returns the ID attribute for this BoundField, if the
  • django/forms/models.py

    === modified file 'django/forms/models.py'
     
    1010from django.utils.datastructures import SortedDict
    1111
    1212from util import ValidationError, ErrorList
    13 from forms import BaseForm, get_declared_fields
     13from forms import FormOptions, FormMetaclass, BaseForm
    1414from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES
    1515from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput
    16 from widgets import media_property
    1716from formsets import BaseFormSet, formset_factory, DELETION_FIELD_NAME
     17import metaclassing
    1818
    1919__all__ = (
    2020    'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
     
    206206            field_list.append((f.name, formfield))
    207207    return SortedDict(field_list)
    208208
    209 class ModelFormOptions(object):
     209class ModelFormOptions(FormOptions):
    210210    def __init__(self, options=None):
     211        super(ModelFormOptions, self).__init__(options)
    211212        self.model = getattr(options, 'model', None)
    212         self.fields = getattr(options, 'fields', None)
    213         self.exclude = getattr(options, 'exclude', None)
    214 
    215 
    216 class ModelFormMetaclass(type):
     213
     214class ModelFormMetaclass(FormMetaclass):
    217215    def __new__(cls, name, bases, attrs):
    218         formfield_callback = attrs.pop('formfield_callback',
    219                 lambda f: f.formfield())
    220         try:
    221             parents = [b for b in bases if issubclass(b, ModelForm)]
    222         except NameError:
    223             # We are defining ModelForm itself.
    224             parents = None
    225         new_class = super(ModelFormMetaclass, cls).__new__(cls, name, bases,
    226                 attrs)
    227         if not parents:
    228             return new_class
    229 
    230         if 'media' not in attrs:
    231             new_class.media = media_property(new_class)
    232         declared_fields = get_declared_fields(bases, attrs, False)
    233         opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))
    234         if opts.model:
    235             # If a model is defined, extract form fields from it.
    236             fields = fields_for_model(opts.model, opts.fields,
    237                                       opts.exclude, formfield_callback)
    238             # Override default model fields with any custom declared ones
    239             # (plus, include all the other declared fields).
    240             fields.update(declared_fields)
    241         else:
    242             fields = declared_fields
    243         new_class.declared_fields = declared_fields
    244         new_class.base_fields = fields
     216        new_class = type.__new__(cls, name, bases, attrs)
     217        metaclassing.create_meta(new_class, attrs)
     218        metaclassing.create_model_fields(new_class, attrs)
     219        metaclassing.create_declared_fields(new_class, attrs)
     220        metaclassing.create_base_fields_pool_from_model_fields_and_declared_fields(new_class, attrs)
     221        metaclassing.create_base_fields_from_base_fields_pool(new_class, attrs)
     222        metaclassing.create_media(new_class, attrs)
    245223        return new_class
    246224
    247225class BaseModelForm(BaseForm):
     
    255233            object_data = {}
    256234        else:
    257235            self.instance = instance
    258             object_data = model_to_dict(instance, opts.fields, opts.exclude)
     236            object_data = model_to_dict(instance, self.base_fields.keys())
    259237        # if initial was provided, it should override the values from instance
    260238        if initial is not None:
    261239            object_data.update(initial)
     
    274252            fail_message = 'created'
    275253        else:
    276254            fail_message = 'changed'
    277         return save_instance(self, self.instance, self._meta.fields, fail_message, commit)
     255        return save_instance(self, self.instance, self.base_fields.keys(), fail_message, commit)
    278256
    279257class ModelForm(BaseModelForm):
    280258    __metaclass__ = ModelFormMetaclass
     259    _options = ModelFormOptions
    281260
    282261def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
    283262                       formfield_callback=lambda f: f.formfield()):
  • django/forms/util.py

    === modified file 'django/forms/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
    33from django.utils.safestring import mark_safe
    44
     
    99    XML-style pairs.  It is assumed that the keys do not need to be XML-escaped.
    1010    If the passed dictionary is empty, then return an empty string.
    1111    """
    12     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()]))
    1313
    1414class ErrorDict(dict, StrAndUnicode):
    1515    """
     
    5555        a string) or a list of objects.
    5656        """
    5757        if isinstance(message, list):
    58             self.messages = ErrorList([smart_unicode(msg) for msg in message])
     58            self.messages = ErrorList([conditional_escape(smart_unicode(msg)) for msg in message])
    5959        else:
    60             message = smart_unicode(message)
     60            message = conditional_escape(smart_unicode(message))
    6161            self.messages = ErrorList([message])
    6262
    6363    def __str__(self):
  • django/forms/widgets.py

    === modified file 'django/forms/widgets.py'
     
    132132    is_hidden = False          # Determines whether this corresponds to an <input type="hidden">.
    133133    needs_multipart_form = False # Determines does this widget need multipart-encrypted form
    134134
    135     def __init__(self, attrs=None):
     135    def __init__(self, attrs=None, row_attrs=None):
    136136        if attrs is not None:
    137137            self.attrs = attrs.copy()
    138138        else:
    139139            self.attrs = {}
     140        if row_attrs is not None:
     141            self.row_attrs = row_attrs.copy()
     142        else:
     143            self.row_attrs = {}
    140144
    141145    def __deepcopy__(self, memo):
    142146        obj = copy.copy(self)
    143147        obj.attrs = self.attrs.copy()
     148        obj.row_attrs = self.row_attrs.copy()
    144149        memo[id(self)] = obj
    145150        return obj
    146151
     
    220225class PasswordInput(Input):
    221226    input_type = 'password'
    222227
    223     def __init__(self, attrs=None, render_value=True):
    224         super(PasswordInput, self).__init__(attrs)
     228    def __init__(self, attrs=None, render_value=True, row_attrs=None):
     229        super(PasswordInput, self).__init__(attrs, row_attrs)
    225230        self.render_value = render_value
    226231
    227232    def render(self, name, value, attrs=None):
     
    237242    A widget that handles <input type="hidden"> for fields that have a list
    238243    of values.
    239244    """
    240     def __init__(self, attrs=None, choices=()):
    241         super(MultipleHiddenInput, self).__init__(attrs)
     245    def __init__(self, attrs=None, choices=(), row_attrs=None):
     246        super(MultipleHiddenInput, self).__init__(attrs, row_attrs)
    242247        # choices can be any iterable
    243248        self.choices = choices
    244249
     
    271276        return True
    272277
    273278class Textarea(Widget):
    274     def __init__(self, attrs=None):
     279    def __init__(self, attrs=None, row_attrs=None):
    275280        # The 'rows' and 'cols' attributes are required for HTML correctness.
    276         self.attrs = {'cols': '40', 'rows': '10'}
     281        default_attrs = {'cols': '40', 'rows': '10'}
    277282        if attrs:
    278             self.attrs.update(attrs)
     283            default_attrs.update(attrs)
     284        super(Textarea, self).__init__(default_attrs, row_attrs)
    279285
    280286    def render(self, name, value, attrs=None):
    281287        if value is None: value = ''
     
    288294    input_type = 'text'
    289295    format = '%Y-%m-%d %H:%M:%S'     # '2006-10-25 14:30:59'
    290296
    291     def __init__(self, attrs=None, format=None):
    292         super(DateTimeInput, self).__init__(attrs)
     297    def __init__(self, attrs=None, format=None, row_attrs=None):
     298        super(DateTimeInput, self).__init__(attrs, row_attrs)
    293299        if format:
    294300            self.format = format
    295301
     
    302308        return super(DateTimeInput, self).render(name, value, attrs)
    303309
    304310class CheckboxInput(Widget):
    305     def __init__(self, attrs=None, check_test=bool):
    306         super(CheckboxInput, self).__init__(attrs)
     311    def __init__(self, attrs=None, check_test=bool, row_attrs=None):
     312        super(CheckboxInput, self).__init__(attrs, row_attrs)
    307313        # check_test is a callable that takes a value and returns True
    308314        # if the checkbox should be checked for that value.
    309315        self.check_test = check_test
     
    334340        return bool(initial) != bool(data)
    335341
    336342class Select(Widget):
    337     def __init__(self, attrs=None, choices=()):
    338         super(Select, self).__init__(attrs)
     343    def __init__(self, attrs=None, choices=(), row_attrs=None):
     344        super(Select, self).__init__(attrs, row_attrs)
    339345        # choices can be any iterable, but we may need to render this widget
    340346        # multiple times. Thus, collapse it into a list so it can be consumed
    341347        # more than once.
     
    375381    """
    376382    A Select Widget intended to be used with NullBooleanField.
    377383    """
    378     def __init__(self, attrs=None):
     384    def __init__(self, attrs=None, row_attrs=None):
    379385        choices = ((u'1', ugettext('Unknown')), (u'2', ugettext('Yes')), (u'3', ugettext('No')))
    380         super(NullBooleanSelect, self).__init__(attrs, choices)
     386        super(NullBooleanSelect, self).__init__(attrs, choices, row_attrs)
    381387
    382388    def render(self, name, value, attrs=None, choices=()):
    383389        try:
     
    570576
    571577    You'll probably want to use this class with MultiValueField.
    572578    """
    573     def __init__(self, widgets, attrs=None):
     579    def __init__(self, widgets, attrs=None, row_attrs=None):
    574580        self.widgets = [isinstance(w, type) and w() or w for w in widgets]
    575         super(MultiWidget, self).__init__(attrs)
     581        super(MultiWidget, self).__init__(attrs, row_attrs)
    576582
    577583    def render(self, name, value, attrs=None):
    578584        # value is a list of values, each corresponding to a widget
     
    642648    """
    643649    A Widget that splits datetime input into two <input type="text"> boxes.
    644650    """
    645     def __init__(self, attrs=None):
     651    def __init__(self, attrs=None, row_attrs=None):
    646652        widgets = (TextInput(attrs=attrs), TextInput(attrs=attrs))
    647         super(SplitDateTimeWidget, self).__init__(widgets, attrs)
     653        super(SplitDateTimeWidget, self).__init__(widgets, attrs, row_attrs)
    648654
    649655    def decompress(self, value):
    650656        if value:
  • tests/modeltests/model_forms/models.py

    === modified file 'tests/modeltests/model_forms/models.py'
     
    151151...         model = Category
    152152...         fields = ['name', 'url']
    153153...         exclude = ['url']
    154 
    155 >>> CategoryForm.base_fields.keys()
    156 ['name']
     154Traceback (most recent call last):
     155  File "/home/petr/django/local2/00-forms-fieldsets/django/test/_doctest.py", line 1267, in __run
     156    compileflags, 1) in test.globs
     157  File "<doctest modeltests.model_forms.models.__test__.API_TESTS[12]>", line 1, in ?
     158    class CategoryForm(ModelForm):
     159  File "/home/petr/django/local2/00-forms-fieldsets/django/forms/models.py", line 220, in __new__
     160    metaclassing.create_base_fields_from_base_fields_pool(new_class)
     161  File "/home/petr/django/local2/00-forms-fieldsets/django/forms/metaclassing.py", line 50, in create_base_fields_from_base_fields_pool
     162    raise ImproperlyConfigured("%s cannot have more than one option from fieldsets, fields and exclude." % cls.__name__)
     163ImproperlyConfigured: CategoryForm cannot have more than one option from fieldsets, fields and exclude.
    157164
    158165Don't allow more than one 'model' definition in the inheritance hierarchy.
    159166Technically, it would generate a valid form, but the fact that the resulting
  • tests/modeltests/model_formsets/models.py

    === modified file 'tests/modeltests/model_formsets/models.py'
     
    4646>>> formset = AuthorFormSet(queryset=qs)
    4747>>> for form in formset.forms:
    4848...     print form.as_p()
    49 <p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /><input type="hidden" name="form-0-id" id="id_form-0-id" /></p>
    50 <p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" maxlength="100" /><input type="hidden" name="form-1-id" id="id_form-1-id" /></p>
    51 <p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" maxlength="100" /><input type="hidden" name="form-2-id" id="id_form-2-id" /></p>
     49<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /></p>
     50<p class ="hidden"><input type="hidden" name="form-0-id" id="id_form-0-id" /></p>
     51<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" maxlength="100" /></p>
     52<p class ="hidden"><input type="hidden" name="form-1-id" id="id_form-1-id" /></p>
     53<p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" maxlength="100" /></p>
     54<p class ="hidden"><input type="hidden" name="form-2-id" id="id_form-2-id" /></p>
    5255
    5356>>> data = {
    5457...     'form-TOTAL_FORMS': '3', # the number of forms rendered
     
    8285>>> formset = AuthorFormSet(queryset=qs)
    8386>>> for form in formset.forms:
    8487...     print form.as_p()
    85 <p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Arthur Rimbaud" maxlength="100" /><input type="hidden" name="form-0-id" value="2" id="id_form-0-id" /></p>
    86 <p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" value="Charles Baudelaire" maxlength="100" /><input type="hidden" name="form-1-id" value="1" id="id_form-1-id" /></p>
    87 <p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" maxlength="100" /><input type="hidden" name="form-2-id" id="id_form-2-id" /></p>
     88<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Arthur Rimbaud" maxlength="100" /></p>
     89<p class ="hidden"><input type="hidden" name="form-0-id" value="2" id="id_form-0-id" /></p>
     90<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" value="Charles Baudelaire" maxlength="100" /></p>
     91<p class ="hidden"><input type="hidden" name="form-1-id" value="1" id="id_form-1-id" /></p>
     92<p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" maxlength="100" /></p>
     93<p class ="hidden"><input type="hidden" name="form-2-id" id="id_form-2-id" /></p>
    8894
    8995
    9096>>> data = {
     
    122128>>> for form in formset.forms:
    123129...     print form.as_p()
    124130<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Arthur Rimbaud" maxlength="100" /></p>
    125 <p><label for="id_form-0-DELETE">Delete:</label> <input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE" /><input type="hidden" name="form-0-id" value="2" id="id_form-0-id" /></p>
     131<p><label for="id_form-0-DELETE">Delete:</label> <input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE" /></p>
     132<p class ="hidden"><input type="hidden" name="form-0-id" value="2" id="id_form-0-id" /></p>
    126133<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" value="Charles Baudelaire" maxlength="100" /></p>
    127 <p><label for="id_form-1-DELETE">Delete:</label> <input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE" /><input type="hidden" name="form-1-id" value="1" id="id_form-1-id" /></p>
     134<p><label for="id_form-1-DELETE">Delete:</label> <input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE" /></p>
     135<p class ="hidden"><input type="hidden" name="form-1-id" value="1" id="id_form-1-id" /></p>
    128136<p><label for="id_form-2-name">Name:</label> <input id="id_form-2-name" type="text" name="form-2-name" value="Paul Verlaine" maxlength="100" /></p>
    129 <p><label for="id_form-2-DELETE">Delete:</label> <input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE" /><input type="hidden" name="form-2-id" value="3" id="id_form-2-id" /></p>
     137<p><label for="id_form-2-DELETE">Delete:</label> <input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE" /></p>
     138<p class ="hidden"><input type="hidden" name="form-2-id" value="3" id="id_form-2-id" /></p>
    130139<p><label for="id_form-3-name">Name:</label> <input id="id_form-3-name" type="text" name="form-3-name" maxlength="100" /></p>
    131 <p><label for="id_form-3-DELETE">Delete:</label> <input type="checkbox" name="form-3-DELETE" id="id_form-3-DELETE" /><input type="hidden" name="form-3-id" id="id_form-3-id" /></p>
     140<p><label for="id_form-3-DELETE">Delete:</label> <input type="checkbox" name="form-3-DELETE" id="id_form-3-DELETE" /></p>
     141<p class ="hidden"><input type="hidden" name="form-3-id" id="id_form-3-id" /></p>
    132142
    133143>>> data = {
    134144...     'form-TOTAL_FORMS': '4', # the number of forms rendered
     
    243253>>> formset = AuthorBooksFormSet(instance=author)
    244254>>> for form in formset.forms:
    245255...     print form.as_p()
    246 <p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" maxlength="100" /><input type="hidden" name="book_set-0-id" id="id_book_set-0-id" /></p>
    247 <p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p>
    248 <p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p>
     256<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" maxlength="100" /></p>
     257<p class ="hidden"><input type="hidden" name="book_set-0-id" id="id_book_set-0-id" /></p>
     258<p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /></p>
     259<p class ="hidden"><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p>
     260<p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /></p>
     261<p class ="hidden"><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p>
    249262
    250263>>> data = {
    251264...     'book_set-TOTAL_FORMS': '3', # the number of forms rendered
     
    277290>>> formset = AuthorBooksFormSet(instance=author)
    278291>>> for form in formset.forms:
    279292...     print form.as_p()
    280 <p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" value="Les Fleurs du Mal" maxlength="100" /><input type="hidden" name="book_set-0-id" value="1" id="id_book_set-0-id" /></p>
    281 <p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p>
    282 <p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p>
     293<p><label for="id_book_set-0-title">Title:</label> <input id="id_book_set-0-title" type="text" name="book_set-0-title" value="Les Fleurs du Mal" maxlength="100" /></p>
     294<p class ="hidden"><input type="hidden" name="book_set-0-id" value="1" id="id_book_set-0-id" /></p>
     295<p><label for="id_book_set-1-title">Title:</label> <input id="id_book_set-1-title" type="text" name="book_set-1-title" maxlength="100" /></p>
     296<p class ="hidden"><input type="hidden" name="book_set-1-id" id="id_book_set-1-id" /></p>
     297<p><label for="id_book_set-2-title">Title:</label> <input id="id_book_set-2-title" type="text" name="book_set-2-title" maxlength="100" /></p>
     298<p class ="hidden"><input type="hidden" name="book_set-2-id" id="id_book_set-2-id" /></p>
    283299
    284300>>> data = {
    285301...     'book_set-TOTAL_FORMS': '3', # the number of forms rendered
     
    331347>>> formset = AuthorBooksFormSet(prefix="test")
    332348>>> for form in formset.forms:
    333349...     print form.as_p()
    334 <p><label for="id_test-0-title">Title:</label> <input id="id_test-0-title" type="text" name="test-0-title" maxlength="100" /><input type="hidden" name="test-0-id" id="id_test-0-id" /></p>
    335 <p><label for="id_test-1-title">Title:</label> <input id="id_test-1-title" type="text" name="test-1-title" maxlength="100" /><input type="hidden" name="test-1-id" id="id_test-1-id" /></p>
     350<p><label for="id_test-0-title">Title:</label> <input id="id_test-0-title" type="text" name="test-0-title" maxlength="100" /></p>
     351<p class ="hidden"><input type="hidden" name="test-0-id" id="id_test-0-id" /></p>
     352<p><label for="id_test-1-title">Title:</label> <input id="id_test-1-title" type="text" name="test-1-title" maxlength="100" /></p>
     353<p class ="hidden"><input type="hidden" name="test-1-id" id="id_test-1-id" /></p>
    336354
    337355# Test a custom primary key ###################################################
    338356
  • tests/regressiontests/forms/extra.py

    === modified file 'tests/regressiontests/forms/extra.py'
     
    439439>>> f = CommentForm(data, auto_id=False, error_class=DivErrorList)
    440440>>> print f.as_p()
    441441<p>Name: <input type="text" name="name" maxlength="50" /></p>
    442 <div class="errorlist"><div class="error">Enter a valid e-mail address.</div></div>
    443 <p>Email: <input type="text" name="email" value="invalid" /></p>
    444 <div class="errorlist"><div class="error">This field is required.</div></div>
    445 <p>Comment: <input type="text" name="comment" /></p>
     442<div class="errorlist"><div class="error">Enter a valid e-mail address.</div></div><p>Email: <input type="text" name="email" value="invalid" /></p>
     443<div class="errorlist"><div class="error">This field is required.</div></div><p>Comment: <input type="text" name="comment" /></p>
    446444
    447445#################################
    448446# Test multipart-encoded form #
  • tests/regressiontests/forms/forms.py

    === modified file 'tests/regressiontests/forms/forms.py'
     
    9494<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>
    9595<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>
    9696>>> print p.as_p()
    97 <ul class="errorlist"><li>This field is required.</li></ul>
    98 <p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
    99 <ul class="errorlist"><li>This field is required.</li></ul>
    100 <p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
    101 <ul class="errorlist"><li>This field is required.</li></ul>
    102 <p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>
     97<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>
     98<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>
     99<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>
    103100
    104101If you don't pass any values to the Form's __init__(), or if you pass None,
    105102the Form will be considered unbound and won't do any validation. Form.errors
     
    563560...     composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')], widget=MultipleHiddenInput)
    564561>>> f = SongFormHidden(MultiValueDict(dict(name=['Yesterday'], composers=['J', 'P'])), auto_id=False)
    565562>>> print f.as_ul()
    566 <li>Name: <input type="text" name="name" value="Yesterday" /><input type="hidden" name="composers" value="J" />
     563<li>Name: <input type="text" name="name" value="Yesterday" /></li>
     564<li class ="hidden"><input type="hidden" name="composers" value="J" />
    567565<input type="hidden" name="composers" value="P" /></li>
    568566
    569567When using CheckboxSelectMultiple, the framework expects a list of input and
     
    808806>>> print p
    809807<tr><th>First name:</th><td><input type="text" name="first_name" /></td></tr>
    810808<tr><th>Last name:</th><td><input type="text" name="last_name" /></td></tr>
    811 <tr><th>Birthday:</th><td><input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></td></tr>
     809<tr><th>Birthday:</th><td><input type="text" name="birthday" /></td></tr>
     810<tr class="hidden"><td colspan="2"><input type="hidden" name="hidden_text" /></td></tr>
    812811>>> print p.as_ul()
    813812<li>First name: <input type="text" name="first_name" /></li>
    814813<li>Last name: <input type="text" name="last_name" /></li>
    815 <li>Birthday: <input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></li>
     814<li>Birthday: <input type="text" name="birthday" /></li>
     815<li class ="hidden"><input type="hidden" name="hidden_text" /></li>
    816816>>> print p.as_p()
    817817<p>First name: <input type="text" name="first_name" /></p>
    818818<p>Last name: <input type="text" name="last_name" /></p>
    819 <p>Birthday: <input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></p>
     819<p>Birthday: <input type="text" name="birthday" /></p>
     820<p class ="hidden"><input type="hidden" name="hidden_text" /></p>
    820821
    821822With auto_id set, a HiddenInput still gets an ID, but it doesn't get a label.
    822823>>> p = Person(auto_id='id_%s')
    823824>>> print p
    824825<tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" id="id_first_name" /></td></tr>
    825826<tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" id="id_last_name" /></td></tr>
    826 <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>
     827<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" id="id_birthday" /></td></tr>
     828<tr class="hidden"><td colspan="2"><input type="hidden" name="hidden_text" id="id_hidden_text" /></td></tr>
    827829>>> print p.as_ul()
    828830<li><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li>
    829831<li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
    830 <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>
     832<li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></li>
     833<li class ="hidden"><input type="hidden" name="hidden_text" id="id_hidden_text" /></li>
    831834>>> print p.as_p()
    832835<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
    833836<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
    834 <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>
     837<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>
     838<p class ="hidden"><input type="hidden" name="hidden_text" id="id_hidden_text" /></p>
    835839
    836840If a field with a HiddenInput has errors, the as_table() and as_ul() output
    837841will include the error message(s) with the text "(Hidden field [fieldname]) "
     
    842846<tr><td colspan="2"><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></td></tr>
    843847<tr><th>First name:</th><td><input type="text" name="first_name" value="John" /></td></tr>
    844848<tr><th>Last name:</th><td><input type="text" name="last_name" value="Lennon" /></td></tr>
    845 <tr><th>Birthday:</th><td><input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></td></tr>
     849<tr><th>Birthday:</th><td><input type="text" name="birthday" value="1940-10-9" /></td></tr>
     850<tr class="hidden"><td colspan="2"><input type="hidden" name="hidden_text" /></td></tr>
    846851>>> print p.as_ul()
    847852<li><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></li>
    848853<li>First name: <input type="text" name="first_name" value="John" /></li>
    849854<li>Last name: <input type="text" name="last_name" value="Lennon" /></li>
    850 <li>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></li>
     855<li>Birthday: <input type="text" name="birthday" value="1940-10-9" /></li>
     856<li class ="hidden"><input type="hidden" name="hidden_text" /></li>
    851857>>> print p.as_p()
    852858<ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul>
    853859<p>First name: <input type="text" name="first_name" value="John" /></p>
    854860<p>Last name: <input type="text" name="last_name" value="Lennon" /></p>
    855 <p>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></p>
     861<p>Birthday: <input type="text" name="birthday" value="1940-10-9" /></p>
     862<p class ="hidden"><input type="hidden" name="hidden_text" /></p>
    856863
    857864A corner case: It's possible for a form to have only HiddenInputs.
    858865>>> class TestForm(Form):
     
    860867...     bar = CharField(widget=HiddenInput)
    861868>>> p = TestForm(auto_id=False)
    862869>>> print p.as_table()
    863 <input type="hidden" name="foo" /><input type="hidden" name="bar" />
     870<tr class="hidden"><td colspan="2"><input type="hidden" name="foo" /><input type="hidden" name="bar" /></td></tr>
    864871>>> print p.as_ul()
    865 <input type="hidden" name="foo" /><input type="hidden" name="bar" />
     872<li class ="hidden"><input type="hidden" name="foo" /><input type="hidden" name="bar" /></li>
    866873>>> print p.as_p()
    867 <input type="hidden" name="foo" /><input type="hidden" name="bar" />
     874<p class ="hidden"><input type="hidden" name="foo" /><input type="hidden" name="bar" /></p>
    868875
    869876A Form's fields are displayed in the same order in which they were defined.
    870877>>> class TestForm(Form):
     
    12151222>>> p = UserRegistration(auto_id=False)
    12161223>>> print p.as_ul()
    12171224<li>Username: <input type="text" name="username" maxlength="10" /> e.g., user@example.com</li>
    1218 <li>Password: <input type="password" name="password" /><input type="hidden" name="next" value="/" /></li>
     1225<li>Password: <input type="password" name="password" /></li>
     1226<li class ="hidden"><input type="hidden" name="next" value="/" /></li>
    12191227
    12201228Help text can include arbitrary Unicode characters.
    12211229>>> class UserRegistration(Form):
     
    12591267...     haircut_type = CharField()
    12601268>>> b = Beatle(auto_id=False)
    12611269>>> print b.as_ul()
     1270<li>Instrument: <input type="text" name="instrument" /></li>
    12621271<li>First name: <input type="text" name="first_name" /></li>
    12631272<li>Last name: <input type="text" name="last_name" /></li>
    12641273<li>Birthday: <input type="text" name="birthday" /></li>
    1265 <li>Instrument: <input type="text" name="instrument" /></li>
    12661274<li>Haircut type: <input type="text" name="haircut_type" /></li>
    12671275
    12681276# Forms with prefixes #########################################################
  • tests/regressiontests/forms/formsets.py

    === modified file 'tests/regressiontests/forms/formsets.py'
     
    2020
    2121>>> formset = ChoiceFormSet(auto_id=False, prefix='choices')
    2222>>> print formset
    23 <input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" />
     23<tr class="hidden"><td colspan="2"><input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /></td></tr>
    2424<tr><th>Choice:</th><td><input type="text" name="choices-0-choice" /></td></tr>
    2525<tr><th>Votes:</th><td><input type="text" name="choices-0-votes" /></td></tr>
    2626
  • tests/regressiontests/forms/localflavor/ch.py

    === modified file 'tests/regressiontests/forms/localflavor/ch.py'
     
    4141>>> f.clean('C1234567<1')
    4242Traceback (most recent call last):
    4343...
    44 ValidationError: [u'Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.']
     44ValidationError: [u'Enter a valid Swiss identity or passport card number in X1234567&lt;0 or 1234567890 format.']
    4545>>> f.clean('2123456700')
    4646u'2123456700'
    4747>>> f.clean('2123456701')
    4848Traceback (most recent call last):
    4949...
    50 ValidationError: [u'Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.']
     50ValidationError: [u'Enter a valid Swiss identity or passport card number in X1234567&lt;0 or 1234567890 format.']
    5151
    5252# CHStateSelect #############################################################
    5353
  • 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 for="id_somechoice_0"><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label for="id_somechoice_1"><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label for="id_somechoice_2"><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 for="id_somechoice_0"><input type="radio" id="id_somechoice_0" value="\xc5" name="somechoice" /> En tied\xe4</label></li>\n<li><label for="id_somechoice_1"><input type="radio" id="id_somechoice_1" value="\xf8" name="somechoice" /> Mies</label></li>\n<li><label for="id_somechoice_2"><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
  • tests/regressiontests/forms/util.py

    === modified file 'tests/regressiontests/forms/util.py'
     
    77>>> from django.forms.util import *
    88>>> from django.utils.translation import ugettext_lazy
    99
     10# Escaping.
     11>>> from django.utils.html import escape
     12>>> from django.utils.html import conditional_escape
     13>>> script = "$('#example').html('<a href=\"http://www.example.com/\">example</a>');"
     14
    1015###########
    1116# flatatt #
    1217###########
     
    1924>>> flatatt({})
    2025u''
    2126
     27# Escaping.
     28
     29>>> flatatt({'onclick': script})
     30u' onclick="$(&#39;#example&#39;).html(&#39;&lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;&#39;);"'
     31>>> flatatt({'onclick': escape(script)})
     32u' onclick="$(&#39;#example&#39;).html(&#39;&lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;&#39;);"'
     33>>> flatatt({'onclick': conditional_escape(script)})
     34u' onclick="$(&#39;#example&#39;).html(&#39;&lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;&#39;);"'
     35
     36>>> conditional_escape(flatatt({'onclick': script}))
     37u' onclick="$(&#39;#example&#39;).html(&#39;&lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;&#39;);"'
     38>>> conditional_escape(flatatt({'onclick': escape(script)}))
     39u' onclick="$(&#39;#example&#39;).html(&#39;&lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;&#39;);"'
     40>>> conditional_escape(flatatt({'onclick': conditional_escape(script)}))
     41u' onclick="$(&#39;#example&#39;).html(&#39;&lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;&#39;);"'
     42
    2243###################
    2344# ValidationError #
    2445###################
     
    4970# Can take a non-string.
    5071>>> print ValidationError(VeryBadError()).messages
    5172<ul class="errorlist"><li>A very bad error.</li></ul>
     73
     74# Escaping.
     75
     76>>> print ValidationError(script).messages
     77<ul class="errorlist"><li>$(&#39;#example&#39;).html(&#39;&lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;&#39;);</li></ul>
     78>>> print ValidationError(escape(script)).messages
     79<ul class="errorlist"><li>$(&#39;#example&#39;).html(&#39;&lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;&#39;);</li></ul>
     80>>> print ValidationError(conditional_escape(script)).messages
     81<ul class="errorlist"><li>$(&#39;#example&#39;).html(&#39;&lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;&#39;);</li></ul>
     82>>> print ErrorDict({'example': ValidationError(script).messages})
     83<ul class="errorlist"><li>example<ul class="errorlist"><li>$(&#39;#example&#39;).html(&#39;&lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;&#39;);</li></ul></li></ul>
     84>>> print ErrorDict({'example': ValidationError(escape(script)).messages})
     85<ul class="errorlist"><li>example<ul class="errorlist"><li>$(&#39;#example&#39;).html(&#39;&lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;&#39;);</li></ul></li></ul>
     86>>> print ErrorDict({'example': ValidationError(conditional_escape(script)).messages})
     87<ul class="errorlist"><li>example<ul class="errorlist"><li>$(&#39;#example&#39;).html(&#39;&lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;&#39;);</li></ul></li></ul>
     88
     89>>> print conditional_escape(unicode(ValidationError(script).messages))
     90<ul class="errorlist"><li>$(&#39;#example&#39;).html(&#39;&lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;&#39;);</li></ul>
     91>>> print conditional_escape(unicode(ValidationError(escape(script)).messages))
     92<ul class="errorlist"><li>$(&#39;#example&#39;).html(&#39;&lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;&#39;);</li></ul>
     93>>> print conditional_escape(unicode(ValidationError(conditional_escape(script)).messages))
     94<ul class="errorlist"><li>$(&#39;#example&#39;).html(&#39;&lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;&#39;);</li></ul>
     95>>> print conditional_escape(unicode(ErrorDict({'example': ValidationError(script).messages})))
     96<ul class="errorlist"><li>example<ul class="errorlist"><li>$(&#39;#example&#39;).html(&#39;&lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;&#39;);</li></ul></li></ul>
     97>>> print conditional_escape(unicode(ErrorDict({'example': ValidationError(escape(script)).messages})))
     98<ul class="errorlist"><li>example<ul class="errorlist"><li>$(&#39;#example&#39;).html(&#39;&lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;&#39;);</li></ul></li></ul>
     99>>> print conditional_escape(unicode(ErrorDict({'example': ValidationError(conditional_escape(script)).messages})))
     100<ul class="errorlist"><li>example<ul class="errorlist"><li>$(&#39;#example&#39;).html(&#39;&lt;a href=&quot;http://www.example.com/&quot;&gt;example&lt;/a&gt;&#39;);</li></ul></li></ul>
     101
    52102"""
Back to Top