Ticket #6630: 00-newforms-fieldsets.2.diff

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

    === added file 'django/newforms/metaclassing.py'
     
     1from django.core.exceptions import ImproperlyConfigured
     2from django.utils.datastructures import SortedDict
     3
     4from fields import Field
     5
     6def create_meta(cls):
     7    cls._meta = cls._options(getattr(cls, 'Meta', None))
     8
     9def create_declared_fields(cls):
     10    fields = []
     11    for name, attr in cls.__dict__.items():
     12        if isinstance(attr, Field):
     13            fields.append((name, attr))
     14            delattr(cls, name)
     15    fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
     16    cls._declared_fields = SortedDict(fields)
     17
     18def create_model_fields(cls, formfield_callback):
     19    fields = []
     20    if cls._meta.model:
     21        for dbfield in cls._meta.model._meta.fields + cls._meta.model._meta.many_to_many:
     22            if dbfield.editable:
     23                formfield = formfield_callback(dbfield)
     24                if formfield:
     25                    fields.append((dbfield.name, formfield))
     26    cls._model_fields = SortedDict(fields)
     27
     28def create_base_fields_pool_from_declared_fields(cls):
     29    fields = []
     30    for base in cls.__mro__[::-1]:
     31        try:
     32            fields += base._declared_fields.items()
     33        except AttributeError:
     34            pass
     35    cls._base_fields_pool = SortedDict(fields)
     36
     37def create_base_fields_pool_from_model_fields_and_declared_fields(cls):
     38    model_fields, declared_fields = [], []
     39    for base in cls.__mro__[::-1]:
     40        try:
     41            declared_fields += base._declared_fields.items()
     42            if base._meta.model:
     43                model_fields = base._model_fields.items()
     44        except AttributeError:
     45            pass
     46    cls._base_fields_pool = SortedDict(model_fields + declared_fields)
     47
     48def create_base_fields_from_base_fields_pool(cls):
     49    if (cls._meta.fieldsets is None) + (cls._meta.fields is None) + (cls._meta.exclude is None) < 2:
     50        raise ImproperlyConfigured("%s cannot have more than one option from fieldsets, fields and exclude." % cls.__name__)
     51    if cls._meta.fieldsets:
     52        names = []
     53        for fieldset in cls._meta.fieldsets:
     54            names.extend(fieldset['fields'])
     55    elif cls._meta.fields:
     56        names = cls._meta.fields
     57    elif cls._meta.exclude:
     58        names = [name for name in cls._base_fields_pool if name not in cls._meta.exclude]
     59    else:
     60        names = cls._base_fields_pool.keys()
     61    cls.base_fields = SortedDict([(name, cls._base_fields_pool[name]) for name in names])
  • 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.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
     11from fields import FileField
    1312from widgets import 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     """
    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(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):
    5732    def __new__(cls, name, bases, attrs):
    58         attrs['base_fields'] = get_declared_fields(bases, attrs)
    59         return type.__new__(cls, name, bases, attrs)
     33        new_class = type.__new__(cls, name, bases, attrs)
     34        metaclassing.create_meta(new_class)
     35        metaclassing.create_declared_fields(new_class)
     36        metaclassing.create_base_fields_pool_from_declared_fields(new_class)
     37        metaclassing.create_base_fields_from_base_fields_pool(new_class)
     38        return new_class
    6039
    6140class BaseForm(StrAndUnicode):
    6241    # This is the main implementation of all the Form logic. Note that this
     
    9877        return BoundField(self, field, name)
    9978
    10079    def _get_errors(self):
    101         "Returns an ErrorDict for the data provided for the form"
     80        "Returns an ErrorDict for the data provided for the form."
    10281        if self._errors is None:
    10382            self.full_clean()
    10483        return self._errors
     
    11190        """
    11291        return self.is_bound and not bool(self.errors)
    11392
    114     def add_prefix(self, field_name):
     93    def has_fieldsets(self):
     94        "Returns True if this form has fieldsets."
     95        return bool(self._meta.fieldsets)
     96
     97    def add_prefix(self, name):
    11598        """
    11699        Returns the field name with a prefix appended, if this Form has a
    117100        prefix set.
    118101
    119102        Subclasses may wish to override.
    120103        """
    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):
     104        return self.prefix and ('%s-%s' % (self.prefix, name)) or name
     105
     106    def first_fieldset_attributes(self):
     107        "Returns attributes for first fieldset as HTML code."
     108        if self.has_fieldsets() and 'attrs' in self._meta.fieldsets[0]:
     109            return flatatt(self._meta.fieldsets[0]['attrs'])
     110        else:
     111            return u''
     112
     113    def first_fieldset_legend_tag(self):
     114        "Returns legend tag for first fieldset as HTML code."
     115        if self.has_fieldsets() and 'legend' in self._meta.fieldsets[0]:
     116            return mark_safe(u'<legend>%s</legend>' % conditional_escape(force_unicode(self._meta.fieldsets[0]['legend'])))
     117        else:
     118            return u''
     119
     120    def _label_tag_html_output(self, bf, label_tag_html):
     121        "Helper function for outputting HTML from a label. Used by _widget_html_output."
     122        label, label_id = bf.label, bf.label_id
     123        if self.label_suffix and label and label[-1] not in ':?.!':
     124            label += self.label_suffix
     125        if label and label_id:
     126            return label_tag_html % {
     127                'label': label,
     128                'id': label_id,
     129            }
     130        else:
     131            return label
     132
     133    def _help_text_html_output(self, bf, help_text_html):
     134        "Helper function for outputting HTML from a help text. Used by _widget_html_output."
     135        if bf.help_text:
     136            return help_text_html % {
     137                'help_text': bf.help_text,
     138            }
     139        else:
     140            return u''
     141
     142    def _row_html_output(self, bf, row_html, label_tag_html, help_text_html):
     143        "Helper function for outputting HTML from a widget. Used by _html_output."
     144        return row_html % {
     145            'rendered_widget': unicode(bf),
     146            'rendered_errors': unicode(bf.errors),
     147            'label_tag': self._label_tag_html_output(bf, label_tag_html),
     148            'help_text': self._help_text_html_output(bf, help_text_html),
     149            'attrs': bf.row_attrs,
     150        }
     151
     152    def _top_errors_html_output(self, top_errors, top_errors_html):
     153        "Helper function for outputting HTML from a top errors. Used by _html_output."
     154        return top_errors_html % {
     155            'top_errors': unicode(top_errors),
     156        }
     157
     158    def _fieldset_html_output(self, fields, fieldset, is_first, is_last, fieldset_start_html, fieldset_end_html, legend_tag_html):
     159        "Helper function for outputting HTML from a fieldset. Used by _html_output."
     160        output = []
     161        if not is_first:
     162            legend_tag = attrs = u''
     163            if 'legend' in fieldset:
     164                legend_tag = legend_tag_html % {
     165                    'legend': conditional_escape(force_unicode(fieldset['legend'])),
     166                }
     167            if 'attrs' in fieldset:
     168                attrs = flatatt(fieldset.get('attrs'))
     169            output.append(fieldset_start_html % {
     170                'legend_tag': legend_tag,
     171                'attrs': attrs,
     172            })
     173        for name in fieldset['fields']:
     174            output.append(fields[name])
     175        if not is_last:
     176            output.append(fieldset_end_html)
     177        return u'\n'.join(output)
     178
     179    def _hidden_fields_html_output(self, hidden_fields, hidden_fields_html):
     180        "Helper function for outputting HTML from a hidden fields. Used by _html_output."
     181        return hidden_fields_html % {
     182            'hidden_fields': u''.join(hidden_fields),
     183        }
     184
     185    def _html_output(self, row_html, label_tag_html, help_text_html, top_errors_html,
     186            fieldset_start_html, legend_tag_html, fieldset_end_html, hidden_fields_html):
    124187        "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
    125         top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
    126         output, hidden_fields = [], []
     188        output = []
     189        top_errors, hidden_fields, visible_fields = self.non_field_errors(), [], {}
    127190        for name, field in self.fields.items():
    128191            bf = BoundField(self, field, name)
    129             bf_errors = self.error_class([escape(error) for error in bf.errors]) # Escape and cache in local variable.
    130192            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])
     193                if bf.errors:
     194                    top_errors.extend(['(Hidden field %s) %s' % (name, conditional_escape(force_unicode(e))) for e in bf.errors])
    133195                hidden_fields.append(unicode(bf))
    134196            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})
     197                visible_fields[name] = self._row_html_output(bf, row_html, label_tag_html, help_text_html)
    152198        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)
     199            output.append(self._top_errors_html_output(top_errors, top_errors_html))
     200        if self.has_fieldsets():
     201            for i, fieldset in enumerate(self._meta.fieldsets):
     202                fields = dict((name, visible_fields[name]) for name in fieldset['fields'] if name in visible_fields)
     203                is_first = (i == 0)
     204                is_last = (i + 1 == len(self._meta.fieldsets))
     205                output.append(self._fieldset_html_output(fields, fieldset, is_first, is_last,
     206                    fieldset_start_html, fieldset_end_html, legend_tag_html))
     207        else:
     208            for name in self.fields:
     209                if name in visible_fields:
     210                    output.append(visible_fields[name])
     211        if hidden_fields:
     212            output.append(self._hidden_fields_html_output(hidden_fields, hidden_fields_html))
    165213        return mark_safe(u'\n'.join(output))
    166214
    167215    def as_table(self):
    168216        "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)
     217        kwargs = {
     218            'row_html': u'<tr%(attrs)s><th>%(label_tag)s</th><td>%(rendered_errors)s%(rendered_widget)s%(help_text)s</td></tr>',
     219            'label_tag_html': u'<label for="%(id)s">%(label)s</label>',
     220            'help_text_html': u'<br />%(help_text)s',
     221            'top_errors_html': u'<tr><td colspan="2">%(top_errors)s</td></tr>',
     222            'fieldset_start_html': u'<fieldset%(attrs)s>\n%(legend_tag)s<table>',
     223            'fieldset_end_html': u'</table>\n</fieldset>',
     224            'legend_tag_html': u'<legend>%(legend)s</legend>\n',
     225            'hidden_fields_html': u'<tr><td colspan="2">%(hidden_fields)s</td></tr>',
     226        }
     227        return self._html_output(**kwargs)
    170228
    171229    def as_ul(self):
    172230        "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)
     231        kwargs = {
     232            'row_html': u'<li%(attrs)s>%(rendered_errors)s%(label_tag)s %(rendered_widget)s%(help_text)s</li>',
     233            'label_tag_html': u'<label for="%(id)s">%(label)s</label>',
     234            'help_text_html': u' %(help_text)s',
     235            'top_errors_html': u'<li>%(top_errors)s</li>',
     236            'fieldset_start_html': u'<fieldset%(attrs)s>\n%(legend_tag)s<ul>',
     237            'fieldset_end_html': u'</ul>\n</fieldset>',
     238            'legend_tag_html': u'<legend>%(legend)s</legend>\n',
     239            'hidden_fields_html': u'<li>%(hidden_fields)s</li>',
     240        }
     241        return self._html_output(**kwargs)
    174242
    175243    def as_p(self):
    176244        "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)
     245        kwargs = {
     246            'row_html': u'%(rendered_errors)s<p%(attrs)s>%(label_tag)s %(rendered_widget)s%(help_text)s</p>',
     247            'label_tag_html': u'<label for="%(id)s">%(label)s</label>',
     248            'help_text_html': u' %(help_text)s',
     249            'top_errors_html': u'%(top_errors)s',
     250            'fieldset_start_html': u'<fieldset%(attrs)s>\n%(legend_tag)s',
     251            'fieldset_end_html': u'</fieldset>',
     252            'legend_tag_html': u'<legend>%(legend)s</legend>\n',
     253            'hidden_fields_html': u'<p>%(hidden_fields)s</p>',
     254        }
     255        return self._html_output(**kwargs)
    178256
    179257    def non_field_errors(self):
    180258        """
     
    209287                    value = getattr(self, 'clean_%s' % name)()
    210288                    self.cleaned_data[name] = value
    211289            except ValidationError, e:
    212                 self._errors[name] = e.messages
     290                self._errors[name] = self.error_class(e.messages)
    213291                if name in self.cleaned_data:
    214292                    del self.cleaned_data[name]
    215293        try:
    216294            self.cleaned_data = self.clean()
    217295        except ValidationError, e:
    218             self._errors[NON_FIELD_ERRORS] = e.messages
     296            self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages)
    219297        if self._errors:
    220298            delattr(self, 'cleaned_data')
    221299
     
    245323    # fancy metaclass stuff purely for the semantic sugar -- it allows one
    246324    # to define a form using declarative syntax.
    247325    # BaseForm itself has no way of designating self.fields.
    248     __metaclass__ = DeclarativeFieldsMetaclass
     326    __metaclass__ = FormMetaclass
     327    _options = FormOptions
    249328
    250329class BoundField(StrAndUnicode):
    251330    "A Field plus data"
    252331    def __init__(self, form, field, name):
    253332        self.form = form
    254333        self.field = field
     334        self.widget = field.widget
    255335        self.name = name
    256336        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 ''
    262337
    263338    def __unicode__(self):
    264339        """Renders this field as an HTML widget."""
     
    279354        field's default widget will be used.
    280355        """
    281356        if not widget:
    282             widget = self.field.widget
     357            widget = self.widget
    283358        attrs = attrs or {}
    284359        auto_id = self.auto_id
    285360        if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
     
    312387        """
    313388        Returns the data for this BoundField, or None if it wasn't given.
    314389        """
    315         return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
     390        return self.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
    316391    data = property(_data)
    317392
     393    def _label(self):
     394        "Returns label for this field as safe HTML."
     395        if self.field.label is None:
     396            return pretty_name(self.name)
     397        else:
     398            return conditional_escape(force_unicode(self.field.label))
     399    label = property(_label)
     400
     401    def _label_id(self):
     402        "Returns label id for this field as safe HTML."
     403        id_ = self.widget.attrs.get('id') or self.auto_id
     404        if id_:
     405            return self.widget.id_for_label(id_)
     406    label_id = property(_label_id)
     407
     408    def _help_text(self):
     409        "Returns help text for this field as safe HTML."
     410        if self.field.help_text is None:
     411            return u''
     412        else:
     413            return force_unicode(self.field.help_text)
     414    help_text = property(_help_text)
     415
     416    def _row_attrs(self):
     417        "Returns row attributes for this field as safe HTML."
     418        return flatatt(self.widget.row_attrs)
     419    row_attrs = property(_row_attrs)
     420
    318421    def label_tag(self, contents=None, attrs=None):
    319422        """
    320423        Wraps the given contents in a <label>, if the field has an ID attribute.
     
    323426
    324427        If attrs are given, they're used as HTML attributes on the <label> tag.
    325428        """
    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_:
     429        contents = contents or self.label
     430        if self.label_id:
    330431            attrs = attrs and flatatt(attrs) or ''
    331             contents = '<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, contents)
     432            contents = '<label for="%s"%s>%s</label>' % (self.label_id, attrs, contents)
    332433        return mark_safe(contents)
    333434
    334435    def _is_hidden(self):
    335436        "Returns True if this BoundField's widget is hidden."
    336         return self.field.widget.is_hidden
     437        return self.widget.is_hidden
    337438    is_hidden = property(_is_hidden)
    338439
    339440    def _auto_id(self):
  • django/newforms/models.py

    === modified file 'django/newforms/models.py'
     
    88from django.utils.translation import ugettext_lazy as _
    99from django.utils.encoding import smart_unicode
    1010from django.utils.datastructures import SortedDict
    11 from django.core.exceptions import ImproperlyConfigured
    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
     16import metaclassing
    1717
    1818__all__ = (
    1919    'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
     
    205205            field_list.append((f.name, formfield))
    206206    return SortedDict(field_list)
    207207
    208 class ModelFormOptions(object):
     208class ModelFormOptions(FormOptions):
    209209    def __init__(self, options=None):
     210        super(ModelFormOptions, self).__init__(options)
    210211        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 
     212
     213class ModelFormMetaclass(FormMetaclass):
     214    def __new__(cls, name, bases, attrs, formfield_callback=lambda f: f.formfield()):
    227215        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
     216        metaclassing.create_meta(new_class)
     217        metaclassing.create_model_fields(new_class, formfield_callback)
     218        metaclassing.create_declared_fields(new_class)
     219        metaclassing.create_base_fields_pool_from_model_fields_and_declared_fields(new_class)
     220        metaclassing.create_base_fields_from_base_fields_pool(new_class)
    241221        return new_class
    242222
    243223class BaseModelForm(BaseForm):
     
    251231            object_data = {}
    252232        else:
    253233            self.instance = instance
    254             object_data = model_to_dict(instance, opts.fields, opts.exclude)
     234            object_data = model_to_dict(instance, self.base_fields.keys())
    255235        # if initial was provided, it should override the values from instance
    256236        if initial is not None:
    257237            object_data.update(initial)
     
    269249            fail_message = 'created'
    270250        else:
    271251            fail_message = 'changed'
    272         return save_instance(self, self.instance, self._meta.fields, fail_message, commit)
     252        return save_instance(self, self.instance, self.base_fields.keys(), fail_message, commit)
    273253
    274254class ModelForm(BaseModelForm):
    275255    __metaclass__ = ModelFormMetaclass
     256    _options = ModelFormOptions
    276257
    277258
    278259# 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
     
    98103class PasswordInput(Input):
    99104    input_type = 'password'
    100105
    101     def __init__(self, attrs=None, render_value=True):
    102         super(PasswordInput, self).__init__(attrs)
     106    def __init__(self, attrs=None, row_attrs=None, render_value=True):
     107        super(PasswordInput, self).__init__(attrs, row_attrs)
    103108        self.render_value = render_value
    104109
    105110    def render(self, name, value, attrs=None):
     
    115120    A widget that handles <input type="hidden"> for fields that have a list
    116121    of values.
    117122    """
    118     def __init__(self, attrs=None, choices=()):
    119         super(MultipleHiddenInput, self).__init__(attrs)
     123    def __init__(self, attrs=None, row_attrs=None, choices=()):
     124        super(MultipleHiddenInput, self).__init__(attrs, row_attrs)
    120125        # choices can be any iterable
    121126        self.choices = choices
    122127
     
    144149        return files.get(name, None)
    145150
    146151class Textarea(Widget):
    147     def __init__(self, attrs=None):
     152    def __init__(self, attrs=None, row_attrs=None):
    148153        # The 'rows' and 'cols' attributes are required for HTML correctness.
    149         self.attrs = {'cols': '40', 'rows': '10'}
     154        default_attrs = {'cols': '40', 'rows': '10'}
    150155        if attrs:
    151             self.attrs.update(attrs)
     156            default_attrs.update(attrs)
     157        super(Textarea, self).__init__(default_attrs, row_attrs)
    152158
    153159    def render(self, name, value, attrs=None):
    154160        if value is None: value = ''
     
    161167    input_type = 'text'
    162168    format = '%Y-%m-%d %H:%M:%S'     # '2006-10-25 14:30:59'
    163169
    164     def __init__(self, attrs=None, format=None):
    165         super(DateTimeInput, self).__init__(attrs)
     170    def __init__(self, attrs=None, row_attrs=None, format=None):
     171        super(DateTimeInput, self).__init__(attrs, row_attrs)
    166172        if format:
    167173            self.format = format
    168174
     
    174180        return super(DateTimeInput, self).render(name, value, attrs)
    175181
    176182class CheckboxInput(Widget):
    177     def __init__(self, attrs=None, check_test=bool):
    178         super(CheckboxInput, self).__init__(attrs)
     183    def __init__(self, attrs=None, row_attrs=None, check_test=bool):
     184        super(CheckboxInput, self).__init__(attrs, row_attrs)
    179185        # check_test is a callable that takes a value and returns True
    180186        # if the checkbox should be checked for that value.
    181187        self.check_test = check_test
     
    201207        return super(CheckboxInput, self).value_from_datadict(data, files, name)
    202208
    203209class Select(Widget):
    204     def __init__(self, attrs=None, choices=()):
    205         super(Select, self).__init__(attrs)
     210    def __init__(self, attrs=None, row_attrs=None, choices=()):
     211        super(Select, self).__init__(attrs, row_attrs)
    206212        # choices can be any iterable, but we may need to render this widget
    207213        # multiple times. Thus, collapse it into a list so it can be consumed
    208214        # more than once.
     
    227233    """
    228234    A Select Widget intended to be used with NullBooleanField.
    229235    """
    230     def __init__(self, attrs=None):
     236    def __init__(self, attrs=None, row_attrs=None):
    231237        choices = ((u'1', ugettext('Unknown')), (u'2', ugettext('Yes')), (u'3', ugettext('No')))
    232         super(NullBooleanSelect, self).__init__(attrs, choices)
     238        super(NullBooleanSelect, self).__init__(attrs, row_attrs, choices)
    233239
    234240    def render(self, name, value, attrs=None, choices=()):
    235241        try:
     
    242248        value = data.get(name, None)
    243249        return {u'2': True, u'3': False, True: True, False: False}.get(value, None)
    244250
    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 
     251class SelectMultiple(Select):
    251252    def render(self, name, value, attrs=None, choices=()):
    252253        if value is None: value = []
    253254        final_attrs = self.build_attrs(attrs, name=name)
     
    406407
    407408    You'll probably want to use this class with MultiValueField.
    408409    """
    409     def __init__(self, widgets, attrs=None):
     410    def __init__(self, widgets, attrs=None, row_attrs=None):
    410411        self.widgets = [isinstance(w, type) and w() or w for w in widgets]
    411         super(MultiWidget, self).__init__(attrs)
     412        super(MultiWidget, self).__init__(attrs, row_attrs)
    412413
    413414    def render(self, name, value, attrs=None):
    414415        # value is a list of values, each corresponding to a widget
     
    460461    """
    461462    A Widget that splits datetime input into two <input type="text"> boxes.
    462463    """
    463     def __init__(self, attrs=None):
     464    def __init__(self, attrs=None, row_attrs=None):
    464465        widgets = (TextInput(attrs=attrs), TextInput(attrs=attrs))
    465         super(SplitDateTimeWidget, self).__init__(widgets, attrs)
     466        super(SplitDateTimeWidget, self).__init__(widgets, attrs, row_attrs)
    466467
    467468    def decompress(self, value):
    468469        if value:
  • tests/modeltests/model_forms/models.py

    === modified file 'tests/modeltests/model_forms/models.py'
     
    142142...         model = Category
    143143...         fields = ['name', 'url']
    144144...         exclude = ['url']
    145 
     145Traceback (most recent call last):
     146  File "/home/petr/django/local2/00-newforms-fieldsets/django/test/_doctest.py", line 1267, in __run
     147    compileflags, 1) in test.globs
     148  File "<doctest modeltests.model_forms.models.__test__.API_TESTS[12]>", line 1, in ?
     149    class CategoryForm(ModelForm):
     150  File "/home/petr/django/local2/00-newforms-fieldsets/django/newforms/models.py", line 220, in __new__
     151    metaclassing.create_base_fields_from_base_fields_pool(new_class)
     152  File "/home/petr/django/local2/00-newforms-fieldsets/django/newforms/metaclassing.py", line 50, in create_base_fields_from_base_fields_pool
     153    raise ImproperlyConfigured("%s cannot have more than one option from fieldsets, fields and exclude." % cls.__name__)
     154ImproperlyConfigured: CategoryForm cannot have more than one option from fieldsets, fields and exclude.
    146155>>> CategoryForm.base_fields.keys()
    147 ['name']
     156['name', 'slug']
    148157
    149158Don't allow more than one 'model' definition in the inheritance hierarchy.
    150159Technically, it would generate a valid form, but the fact that the resulting
  • 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 #########################################################
  • 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