Code

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

File 00-newforms-fieldsets.2.diff, 45.2 KB (added by Petr Marhoun <petr.marhoun@…>, 6 years ago)
Line 
1=== added file 'django/newforms/metaclassing.py'
2--- django/newforms/metaclassing.py     1970-01-01 00:00:00 +0000
3+++ django/newforms/metaclassing.py     2008-03-05 22:04:42 +0000
4@@ -0,0 +1,61 @@
5+from django.core.exceptions import ImproperlyConfigured
6+from django.utils.datastructures import SortedDict
7+
8+from fields import Field
9+
10+def create_meta(cls):
11+    cls._meta = cls._options(getattr(cls, 'Meta', None))
12+
13+def create_declared_fields(cls):
14+    fields = []
15+    for name, attr in cls.__dict__.items():
16+        if isinstance(attr, Field):
17+            fields.append((name, attr))
18+            delattr(cls, name)
19+    fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
20+    cls._declared_fields = SortedDict(fields)
21+
22+def create_model_fields(cls, formfield_callback):
23+    fields = []
24+    if cls._meta.model:
25+        for dbfield in cls._meta.model._meta.fields + cls._meta.model._meta.many_to_many:
26+            if dbfield.editable:
27+                formfield = formfield_callback(dbfield)
28+                if formfield:
29+                    fields.append((dbfield.name, formfield))
30+    cls._model_fields = SortedDict(fields)
31+
32+def create_base_fields_pool_from_declared_fields(cls):
33+    fields = []
34+    for base in cls.__mro__[::-1]:
35+        try:
36+            fields += base._declared_fields.items()
37+        except AttributeError:
38+            pass
39+    cls._base_fields_pool = SortedDict(fields)
40+
41+def create_base_fields_pool_from_model_fields_and_declared_fields(cls):
42+    model_fields, declared_fields = [], []
43+    for base in cls.__mro__[::-1]:
44+        try:
45+            declared_fields += base._declared_fields.items()
46+            if base._meta.model:
47+                model_fields = base._model_fields.items()
48+        except AttributeError:
49+            pass
50+    cls._base_fields_pool = SortedDict(model_fields + declared_fields)
51+
52+def create_base_fields_from_base_fields_pool(cls):
53+    if (cls._meta.fieldsets is None) + (cls._meta.fields is None) + (cls._meta.exclude is None) < 2:
54+        raise ImproperlyConfigured("%s cannot have more than one option from fieldsets, fields and exclude." % cls.__name__)
55+    if cls._meta.fieldsets:
56+        names = []
57+        for fieldset in cls._meta.fieldsets:
58+            names.extend(fieldset['fields'])
59+    elif cls._meta.fields:
60+        names = cls._meta.fields
61+    elif cls._meta.exclude:
62+        names = [name for name in cls._base_fields_pool if name not in cls._meta.exclude]
63+    else:
64+        names = cls._base_fields_pool.keys()
65+    cls.base_fields = SortedDict([(name, cls._base_fields_pool[name]) for name in names])
66
67=== modified file 'django/newforms/extras/widgets.py'
68--- django/newforms/extras/widgets.py   2008-01-28 15:22:01 +0000
69+++ django/newforms/extras/widgets.py   2008-01-28 17:45:19 +0000
70@@ -21,9 +21,9 @@
71     day_field = '%s_day'
72     year_field = '%s_year'
73 
74-    def __init__(self, attrs=None, years=None):
75+    def __init__(self, attrs=None, row_attrs=None, years=None):
76         # years is an optional list/tuple of years to use in the "year" select box.
77-        self.attrs = attrs or {}
78+        super(SelectDateWidget, self).__init__(attrs, row_attrs)
79         if years:
80             self.years = years
81         else:
82
83=== modified file 'django/newforms/fields.py'
84--- django/newforms/fields.py   2008-01-28 15:22:01 +0000
85+++ django/newforms/fields.py   2008-01-28 17:45:19 +0000
86@@ -513,7 +513,6 @@
87             return value
88         if self.verify_exists:
89             import urllib2
90-            from django.conf import settings
91             headers = {
92                 "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
93                 "Accept-Language": "en-us,en;q=0.5",
94
95=== modified file 'django/newforms/forms.py'
96--- django/newforms/forms.py    2008-02-16 07:59:58 +0000
97+++ django/newforms/forms.py    2008-03-05 22:14:58 +0000
98@@ -4,14 +4,14 @@
99 
100 from copy import deepcopy
101 
102-from django.utils.datastructures import SortedDict
103-from django.utils.html import escape
104+from django.utils.html import conditional_escape
105 from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
106 from django.utils.safestring import mark_safe
107 
108-from fields import Field, FileField
109+from fields import FileField
110 from widgets import TextInput, Textarea
111 from util import flatatt, ErrorDict, ErrorList, ValidationError
112+import metaclassing
113 
114 __all__ = ('BaseForm', 'Form')
115 
116@@ -22,41 +22,20 @@
117     name = name[0].upper() + name[1:]
118     return name.replace('_', ' ')
119 
120-def get_declared_fields(bases, attrs, with_base_fields=True):
121-    """
122-    Create a list of form field instances from the passed in 'attrs', plus any
123-    similar fields on the base classes (in 'bases'). This is used by both the
124-    Form and ModelForm metclasses.
125-
126-    If 'with_base_fields' is True, all fields from the bases are used.
127-    Otherwise, only fields in the 'declared_fields' attribute on the bases are
128-    used. The distinction is useful in ModelForm subclassing.
129-    """
130-    fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
131-    fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
132-
133-    # If this class is subclassing another Form, add that Form's fields.
134-    # Note that we loop over the bases in *reverse*. This is necessary in
135-    # order to preserve the correct order of fields.
136-    if with_base_fields:
137-        for base in bases[::-1]:
138-            if hasattr(base, 'base_fields'):
139-                fields = base.base_fields.items() + fields
140-    else:
141-        for base in bases[::-1]:
142-            if hasattr(base, 'declared_fields'):
143-                fields = base.declared_fields.items() + fields
144-
145-    return SortedDict(fields)
146-
147-class DeclarativeFieldsMetaclass(type):
148-    """
149-    Metaclass that converts Field attributes to a dictionary called
150-    'base_fields', taking into account parent class 'base_fields' as well.
151-    """
152+class FormOptions(object):
153+    def __init__(self, options=None):
154+        self.fieldsets = getattr(options, 'fieldsets', None)
155+        self.fields = getattr(options, 'fields', None)
156+        self.exclude = getattr(options, 'exclude', None)
157+
158+class FormMetaclass(type):
159     def __new__(cls, name, bases, attrs):
160-        attrs['base_fields'] = get_declared_fields(bases, attrs)
161-        return type.__new__(cls, name, bases, attrs)
162+        new_class = type.__new__(cls, name, bases, attrs)
163+        metaclassing.create_meta(new_class)
164+        metaclassing.create_declared_fields(new_class)
165+        metaclassing.create_base_fields_pool_from_declared_fields(new_class)
166+        metaclassing.create_base_fields_from_base_fields_pool(new_class)
167+        return new_class
168 
169 class BaseForm(StrAndUnicode):
170     # This is the main implementation of all the Form logic. Note that this
171@@ -98,7 +77,7 @@
172         return BoundField(self, field, name)
173 
174     def _get_errors(self):
175-        "Returns an ErrorDict for the data provided for the form"
176+        "Returns an ErrorDict for the data provided for the form."
177         if self._errors is None:
178             self.full_clean()
179         return self._errors
180@@ -111,70 +90,169 @@
181         """
182         return self.is_bound and not bool(self.errors)
183 
184-    def add_prefix(self, field_name):
185+    def has_fieldsets(self):
186+        "Returns True if this form has fieldsets."
187+        return bool(self._meta.fieldsets)
188+
189+    def add_prefix(self, name):
190         """
191         Returns the field name with a prefix appended, if this Form has a
192         prefix set.
193 
194         Subclasses may wish to override.
195         """
196-        return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name
197-
198-    def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row):
199+        return self.prefix and ('%s-%s' % (self.prefix, name)) or name
200+
201+    def first_fieldset_attributes(self):
202+        "Returns attributes for first fieldset as HTML code."
203+        if self.has_fieldsets() and 'attrs' in self._meta.fieldsets[0]:
204+            return flatatt(self._meta.fieldsets[0]['attrs'])
205+        else:
206+            return u''
207+
208+    def first_fieldset_legend_tag(self):
209+        "Returns legend tag for first fieldset as HTML code."
210+        if self.has_fieldsets() and 'legend' in self._meta.fieldsets[0]:
211+            return mark_safe(u'<legend>%s</legend>' % conditional_escape(force_unicode(self._meta.fieldsets[0]['legend'])))
212+        else:
213+            return u''
214+
215+    def _label_tag_html_output(self, bf, label_tag_html):
216+        "Helper function for outputting HTML from a label. Used by _widget_html_output."
217+        label, label_id = bf.label, bf.label_id
218+        if self.label_suffix and label and label[-1] not in ':?.!':
219+            label += self.label_suffix
220+        if label and label_id:
221+            return label_tag_html % {
222+                'label': label,
223+                'id': label_id,
224+            }
225+        else:
226+            return label
227+
228+    def _help_text_html_output(self, bf, help_text_html):
229+        "Helper function for outputting HTML from a help text. Used by _widget_html_output."
230+        if bf.help_text:
231+            return help_text_html % {
232+                'help_text': bf.help_text,
233+            }
234+        else:
235+            return u''
236+
237+    def _row_html_output(self, bf, row_html, label_tag_html, help_text_html):
238+        "Helper function for outputting HTML from a widget. Used by _html_output."
239+        return row_html % {
240+            'rendered_widget': unicode(bf),
241+            'rendered_errors': unicode(bf.errors),
242+            'label_tag': self._label_tag_html_output(bf, label_tag_html),
243+            'help_text': self._help_text_html_output(bf, help_text_html),
244+            'attrs': bf.row_attrs,
245+        }
246+
247+    def _top_errors_html_output(self, top_errors, top_errors_html):
248+        "Helper function for outputting HTML from a top errors. Used by _html_output."
249+        return top_errors_html % {
250+            'top_errors': unicode(top_errors),
251+        }
252+
253+    def _fieldset_html_output(self, fields, fieldset, is_first, is_last, fieldset_start_html, fieldset_end_html, legend_tag_html):
254+        "Helper function for outputting HTML from a fieldset. Used by _html_output."
255+        output = []
256+        if not is_first:
257+            legend_tag = attrs = u''
258+            if 'legend' in fieldset:
259+                legend_tag = legend_tag_html % {
260+                    'legend': conditional_escape(force_unicode(fieldset['legend'])),
261+                }
262+            if 'attrs' in fieldset:
263+                attrs = flatatt(fieldset.get('attrs'))
264+            output.append(fieldset_start_html % {
265+                'legend_tag': legend_tag,
266+                'attrs': attrs,
267+            })
268+        for name in fieldset['fields']:
269+            output.append(fields[name])
270+        if not is_last:
271+            output.append(fieldset_end_html)
272+        return u'\n'.join(output)
273+
274+    def _hidden_fields_html_output(self, hidden_fields, hidden_fields_html):
275+        "Helper function for outputting HTML from a hidden fields. Used by _html_output."
276+        return hidden_fields_html % {
277+            'hidden_fields': u''.join(hidden_fields),
278+        }
279+
280+    def _html_output(self, row_html, label_tag_html, help_text_html, top_errors_html,
281+            fieldset_start_html, legend_tag_html, fieldset_end_html, hidden_fields_html):
282         "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
283-        top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
284-        output, hidden_fields = [], []
285+        output = []
286+        top_errors, hidden_fields, visible_fields = self.non_field_errors(), [], {}
287         for name, field in self.fields.items():
288             bf = BoundField(self, field, name)
289-            bf_errors = self.error_class([escape(error) for error in bf.errors]) # Escape and cache in local variable.
290             if bf.is_hidden:
291-                if bf_errors:
292-                    top_errors.extend(['(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
293+                if bf.errors:
294+                    top_errors.extend(['(Hidden field %s) %s' % (name, conditional_escape(force_unicode(e))) for e in bf.errors])
295                 hidden_fields.append(unicode(bf))
296             else:
297-                if errors_on_separate_row and bf_errors:
298-                    output.append(error_row % force_unicode(bf_errors))
299-                if bf.label:
300-                    label = escape(force_unicode(bf.label))
301-                    # Only add the suffix if the label does not end in
302-                    # punctuation.
303-                    if self.label_suffix:
304-                        if label[-1] not in ':?.!':
305-                            label += self.label_suffix
306-                    label = bf.label_tag(label) or ''
307-                else:
308-                    label = ''
309-                if field.help_text:
310-                    help_text = help_text_html % force_unicode(field.help_text)
311-                else:
312-                    help_text = u''
313-                output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text})
314+                visible_fields[name] = self._row_html_output(bf, row_html, label_tag_html, help_text_html)
315         if top_errors:
316-            output.insert(0, error_row % top_errors)
317-        if hidden_fields: # Insert any hidden fields in the last row.
318-            str_hidden = u''.join(hidden_fields)
319-            if output:
320-                last_row = output[-1]
321-                # Chop off the trailing row_ender (e.g. '</td></tr>') and
322-                # insert the hidden fields.
323-                output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
324-            else:
325-                # If there aren't any rows in the output, just append the
326-                # hidden fields.
327-                output.append(str_hidden)
328+            output.append(self._top_errors_html_output(top_errors, top_errors_html))
329+        if self.has_fieldsets():
330+            for i, fieldset in enumerate(self._meta.fieldsets):
331+                fields = dict((name, visible_fields[name]) for name in fieldset['fields'] if name in visible_fields)
332+                is_first = (i == 0)
333+                is_last = (i + 1 == len(self._meta.fieldsets))
334+                output.append(self._fieldset_html_output(fields, fieldset, is_first, is_last,
335+                    fieldset_start_html, fieldset_end_html, legend_tag_html))
336+        else:
337+            for name in self.fields:
338+                if name in visible_fields:
339+                    output.append(visible_fields[name])
340+        if hidden_fields:
341+            output.append(self._hidden_fields_html_output(hidden_fields, hidden_fields_html))
342         return mark_safe(u'\n'.join(output))
343 
344     def as_table(self):
345         "Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
346-        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)
347+        kwargs = {
348+            'row_html': u'<tr%(attrs)s><th>%(label_tag)s</th><td>%(rendered_errors)s%(rendered_widget)s%(help_text)s</td></tr>',
349+            'label_tag_html': u'<label for="%(id)s">%(label)s</label>',
350+            'help_text_html': u'<br />%(help_text)s',
351+            'top_errors_html': u'<tr><td colspan="2">%(top_errors)s</td></tr>',
352+            'fieldset_start_html': u'<fieldset%(attrs)s>\n%(legend_tag)s<table>',
353+            'fieldset_end_html': u'</table>\n</fieldset>',
354+            'legend_tag_html': u'<legend>%(legend)s</legend>\n',
355+            'hidden_fields_html': u'<tr><td colspan="2">%(hidden_fields)s</td></tr>',
356+        }
357+        return self._html_output(**kwargs)
358 
359     def as_ul(self):
360         "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
361-        return self._html_output(u'<li>%(errors)s%(label)s %(field)s%(help_text)s</li>', u'<li>%s</li>', '</li>', u' %s', False)
362+        kwargs = {
363+            'row_html': u'<li%(attrs)s>%(rendered_errors)s%(label_tag)s %(rendered_widget)s%(help_text)s</li>',
364+            'label_tag_html': u'<label for="%(id)s">%(label)s</label>',
365+            'help_text_html': u' %(help_text)s',
366+            'top_errors_html': u'<li>%(top_errors)s</li>',
367+            'fieldset_start_html': u'<fieldset%(attrs)s>\n%(legend_tag)s<ul>',
368+            'fieldset_end_html': u'</ul>\n</fieldset>',
369+            'legend_tag_html': u'<legend>%(legend)s</legend>\n',
370+            'hidden_fields_html': u'<li>%(hidden_fields)s</li>',
371+        }
372+        return self._html_output(**kwargs)
373 
374     def as_p(self):
375         "Returns this form rendered as HTML <p>s."
376-        return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'%s', '</p>', u' %s', True)
377+        kwargs = {
378+            'row_html': u'%(rendered_errors)s<p%(attrs)s>%(label_tag)s %(rendered_widget)s%(help_text)s</p>',
379+            'label_tag_html': u'<label for="%(id)s">%(label)s</label>',
380+            'help_text_html': u' %(help_text)s',
381+            'top_errors_html': u'%(top_errors)s',
382+            'fieldset_start_html': u'<fieldset%(attrs)s>\n%(legend_tag)s',
383+            'fieldset_end_html': u'</fieldset>',
384+            'legend_tag_html': u'<legend>%(legend)s</legend>\n',
385+            'hidden_fields_html': u'<p>%(hidden_fields)s</p>',
386+        }
387+        return self._html_output(**kwargs)
388 
389     def non_field_errors(self):
390         """
391@@ -209,13 +287,13 @@
392                     value = getattr(self, 'clean_%s' % name)()
393                     self.cleaned_data[name] = value
394             except ValidationError, e:
395-                self._errors[name] = e.messages
396+                self._errors[name] = self.error_class(e.messages)
397                 if name in self.cleaned_data:
398                     del self.cleaned_data[name]
399         try:
400             self.cleaned_data = self.clean()
401         except ValidationError, e:
402-            self._errors[NON_FIELD_ERRORS] = e.messages
403+            self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages)
404         if self._errors:
405             delattr(self, 'cleaned_data')
406 
407@@ -245,20 +323,17 @@
408     # fancy metaclass stuff purely for the semantic sugar -- it allows one
409     # to define a form using declarative syntax.
410     # BaseForm itself has no way of designating self.fields.
411-    __metaclass__ = DeclarativeFieldsMetaclass
412+    __metaclass__ = FormMetaclass
413+    _options = FormOptions
414 
415 class BoundField(StrAndUnicode):
416     "A Field plus data"
417     def __init__(self, form, field, name):
418         self.form = form
419         self.field = field
420+        self.widget = field.widget
421         self.name = name
422         self.html_name = form.add_prefix(name)
423-        if self.field.label is None:
424-            self.label = pretty_name(name)
425-        else:
426-            self.label = self.field.label
427-        self.help_text = field.help_text or ''
428 
429     def __unicode__(self):
430         """Renders this field as an HTML widget."""
431@@ -279,7 +354,7 @@
432         field's default widget will be used.
433         """
434         if not widget:
435-            widget = self.field.widget
436+            widget = self.widget
437         attrs = attrs or {}
438         auto_id = self.auto_id
439         if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
440@@ -312,9 +387,37 @@
441         """
442         Returns the data for this BoundField, or None if it wasn't given.
443         """
444-        return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
445+        return self.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
446     data = property(_data)
447 
448+    def _label(self):
449+        "Returns label for this field as safe HTML."
450+        if self.field.label is None:
451+            return pretty_name(self.name)
452+        else:
453+            return conditional_escape(force_unicode(self.field.label))
454+    label = property(_label)
455+
456+    def _label_id(self):
457+        "Returns label id for this field as safe HTML."
458+        id_ = self.widget.attrs.get('id') or self.auto_id
459+        if id_:
460+            return self.widget.id_for_label(id_)
461+    label_id = property(_label_id)
462+
463+    def _help_text(self):
464+        "Returns help text for this field as safe HTML."
465+        if self.field.help_text is None:
466+            return u''
467+        else:
468+            return force_unicode(self.field.help_text)
469+    help_text = property(_help_text)
470+
471+    def _row_attrs(self):
472+        "Returns row attributes for this field as safe HTML."
473+        return flatatt(self.widget.row_attrs)
474+    row_attrs = property(_row_attrs)
475+
476     def label_tag(self, contents=None, attrs=None):
477         """
478         Wraps the given contents in a <label>, if the field has an ID attribute.
479@@ -323,17 +426,15 @@
480 
481         If attrs are given, they're used as HTML attributes on the <label> tag.
482         """
483-        contents = contents or escape(self.label)
484-        widget = self.field.widget
485-        id_ = widget.attrs.get('id') or self.auto_id
486-        if id_:
487+        contents = contents or self.label
488+        if self.label_id:
489             attrs = attrs and flatatt(attrs) or ''
490-            contents = '<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, contents)
491+            contents = '<label for="%s"%s>%s</label>' % (self.label_id, attrs, contents)
492         return mark_safe(contents)
493 
494     def _is_hidden(self):
495         "Returns True if this BoundField's widget is hidden."
496-        return self.field.widget.is_hidden
497+        return self.widget.is_hidden
498     is_hidden = property(_is_hidden)
499 
500     def _auto_id(self):
501
502=== modified file 'django/newforms/models.py'
503--- django/newforms/models.py   2008-02-16 07:59:58 +0000
504+++ django/newforms/models.py   2008-03-05 12:15:56 +0000
505@@ -8,12 +8,12 @@
506 from django.utils.translation import ugettext_lazy as _
507 from django.utils.encoding import smart_unicode
508 from django.utils.datastructures import SortedDict
509-from django.core.exceptions import ImproperlyConfigured
510 
511 from util import ValidationError, ErrorList
512-from forms import BaseForm, get_declared_fields
513+from forms import FormOptions, FormMetaclass, BaseForm
514 from fields import Field, ChoiceField, EMPTY_VALUES
515 from widgets import Select, SelectMultiple, MultipleHiddenInput
516+import metaclassing
517 
518 __all__ = (
519     'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
520@@ -205,39 +205,19 @@
521             field_list.append((f.name, formfield))
522     return SortedDict(field_list)
523 
524-class ModelFormOptions(object):
525+class ModelFormOptions(FormOptions):
526     def __init__(self, options=None):
527+        super(ModelFormOptions, self).__init__(options)
528         self.model = getattr(options, 'model', None)
529-        self.fields = getattr(options, 'fields', None)
530-        self.exclude = getattr(options, 'exclude', None)
531-
532-
533-class ModelFormMetaclass(type):
534-    def __new__(cls, name, bases, attrs,
535-                formfield_callback=lambda f: f.formfield()):
536-        try:
537-            parents = [b for b in bases if issubclass(b, ModelForm)]
538-        except NameError:
539-            # We are defining ModelForm itself.
540-            parents = None
541-        if not parents:
542-            return super(ModelFormMetaclass, cls).__new__(cls, name, bases,
543-                    attrs)
544-
545+
546+class ModelFormMetaclass(FormMetaclass):
547+    def __new__(cls, name, bases, attrs, formfield_callback=lambda f: f.formfield()):
548         new_class = type.__new__(cls, name, bases, attrs)
549-        declared_fields = get_declared_fields(bases, attrs, False)
550-        opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))
551-        if opts.model:
552-            # If a model is defined, extract form fields from it.
553-            fields = fields_for_model(opts.model, opts.fields,
554-                                      opts.exclude, formfield_callback)
555-            # Override default model fields with any custom declared ones
556-            # (plus, include all the other declared fields).
557-            fields.update(declared_fields)
558-        else:
559-            fields = declared_fields
560-        new_class.declared_fields = declared_fields
561-        new_class.base_fields = fields
562+        metaclassing.create_meta(new_class)
563+        metaclassing.create_model_fields(new_class, formfield_callback)
564+        metaclassing.create_declared_fields(new_class)
565+        metaclassing.create_base_fields_pool_from_model_fields_and_declared_fields(new_class)
566+        metaclassing.create_base_fields_from_base_fields_pool(new_class)
567         return new_class
568 
569 class BaseModelForm(BaseForm):
570@@ -251,7 +231,7 @@
571             object_data = {}
572         else:
573             self.instance = instance
574-            object_data = model_to_dict(instance, opts.fields, opts.exclude)
575+            object_data = model_to_dict(instance, self.base_fields.keys())
576         # if initial was provided, it should override the values from instance
577         if initial is not None:
578             object_data.update(initial)
579@@ -269,10 +249,11 @@
580             fail_message = 'created'
581         else:
582             fail_message = 'changed'
583-        return save_instance(self, self.instance, self._meta.fields, fail_message, commit)
584+        return save_instance(self, self.instance, self.base_fields.keys(), fail_message, commit)
585 
586 class ModelForm(BaseModelForm):
587     __metaclass__ = ModelFormMetaclass
588+    _options = ModelFormOptions
589 
590 
591 # Fields #####################################################################
592
593=== modified file 'django/newforms/util.py'
594--- django/newforms/util.py     2008-01-28 15:22:01 +0000
595+++ django/newforms/util.py     2008-03-05 11:45:00 +0000
596@@ -1,6 +1,5 @@
597-from django.utils.html import escape
598+from django.utils.html import conditional_escape
599 from django.utils.encoding import smart_unicode, StrAndUnicode, force_unicode
600-from django.utils.functional import Promise
601 from django.utils.safestring import mark_safe
602 
603 def flatatt(attrs):
604@@ -10,7 +9,7 @@
605     XML-style pairs.  It is assumed that the keys do not need to be XML-escaped.
606     If the passed dictionary is empty, then return an empty string.
607     """
608-    return u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs.items()])
609+    return mark_safe(u''.join([u' %s="%s"' % (k, conditional_escape(v)) for k, v in attrs.items()]))
610 
611 class ErrorDict(dict, StrAndUnicode):
612     """
613@@ -24,7 +23,7 @@
614     def as_ul(self):
615         if not self: return u''
616         return mark_safe(u'<ul class="errorlist">%s</ul>'
617-                % ''.join([u'<li>%s%s</li>' % (k, force_unicode(v))
618+                % ''.join([u'<li>%s%s</li>' % (k, conditional_escape(force_unicode(v)))
619                     for k, v in self.items()]))
620 
621     def as_text(self):
622@@ -40,7 +39,7 @@
623     def as_ul(self):
624         if not self: return u''
625         return mark_safe(u'<ul class="errorlist">%s</ul>'
626-                % ''.join([u'<li>%s</li>' % force_unicode(e) for e in self]))
627+                % ''.join([u'<li>%s</li>' % conditional_escape(force_unicode(e)) for e in self]))
628 
629     def as_text(self):
630         if not self: return u''
631
632=== modified file 'django/newforms/widgets.py'
633--- django/newforms/widgets.py  2008-02-05 21:49:42 +0000
634+++ django/newforms/widgets.py  2008-03-05 22:15:45 +0000
635@@ -29,15 +29,20 @@
636     is_hidden = False          # Determines whether this corresponds to an <input type="hidden">.
637     needs_multipart_form = False # Determines does this widget need multipart-encrypted form
638 
639-    def __init__(self, attrs=None):
640+    def __init__(self, attrs=None, row_attrs=None):
641         if attrs is not None:
642             self.attrs = attrs.copy()
643         else:
644             self.attrs = {}
645+        if row_attrs is not None:
646+            self.row_attrs = row_attrs.copy()
647+        else:
648+            self.row_attrs = {}
649 
650     def __deepcopy__(self, memo):
651         obj = copy.copy(self)
652         obj.attrs = self.attrs.copy()
653+        obj.row_attrs = self.row_attrs.copy()
654         memo[id(self)] = obj
655         return obj
656 
657@@ -98,8 +103,8 @@
658 class PasswordInput(Input):
659     input_type = 'password'
660 
661-    def __init__(self, attrs=None, render_value=True):
662-        super(PasswordInput, self).__init__(attrs)
663+    def __init__(self, attrs=None, row_attrs=None, render_value=True):
664+        super(PasswordInput, self).__init__(attrs, row_attrs)
665         self.render_value = render_value
666 
667     def render(self, name, value, attrs=None):
668@@ -115,8 +120,8 @@
669     A widget that handles <input type="hidden"> for fields that have a list
670     of values.
671     """
672-    def __init__(self, attrs=None, choices=()):
673-        super(MultipleHiddenInput, self).__init__(attrs)
674+    def __init__(self, attrs=None, row_attrs=None, choices=()):
675+        super(MultipleHiddenInput, self).__init__(attrs, row_attrs)
676         # choices can be any iterable
677         self.choices = choices
678 
679@@ -144,11 +149,12 @@
680         return files.get(name, None)
681 
682 class Textarea(Widget):
683-    def __init__(self, attrs=None):
684+    def __init__(self, attrs=None, row_attrs=None):
685         # The 'rows' and 'cols' attributes are required for HTML correctness.
686-        self.attrs = {'cols': '40', 'rows': '10'}
687+        default_attrs = {'cols': '40', 'rows': '10'}
688         if attrs:
689-            self.attrs.update(attrs)
690+            default_attrs.update(attrs)
691+        super(Textarea, self).__init__(default_attrs, row_attrs)
692 
693     def render(self, name, value, attrs=None):
694         if value is None: value = ''
695@@ -161,8 +167,8 @@
696     input_type = 'text'
697     format = '%Y-%m-%d %H:%M:%S'     # '2006-10-25 14:30:59'
698 
699-    def __init__(self, attrs=None, format=None):
700-        super(DateTimeInput, self).__init__(attrs)
701+    def __init__(self, attrs=None, row_attrs=None, format=None):
702+        super(DateTimeInput, self).__init__(attrs, row_attrs)
703         if format:
704             self.format = format
705 
706@@ -174,8 +180,8 @@
707         return super(DateTimeInput, self).render(name, value, attrs)
708 
709 class CheckboxInput(Widget):
710-    def __init__(self, attrs=None, check_test=bool):
711-        super(CheckboxInput, self).__init__(attrs)
712+    def __init__(self, attrs=None, row_attrs=None, check_test=bool):
713+        super(CheckboxInput, self).__init__(attrs, row_attrs)
714         # check_test is a callable that takes a value and returns True
715         # if the checkbox should be checked for that value.
716         self.check_test = check_test
717@@ -201,8 +207,8 @@
718         return super(CheckboxInput, self).value_from_datadict(data, files, name)
719 
720 class Select(Widget):
721-    def __init__(self, attrs=None, choices=()):
722-        super(Select, self).__init__(attrs)
723+    def __init__(self, attrs=None, row_attrs=None, choices=()):
724+        super(Select, self).__init__(attrs, row_attrs)
725         # choices can be any iterable, but we may need to render this widget
726         # multiple times. Thus, collapse it into a list so it can be consumed
727         # more than once.
728@@ -227,9 +233,9 @@
729     """
730     A Select Widget intended to be used with NullBooleanField.
731     """
732-    def __init__(self, attrs=None):
733+    def __init__(self, attrs=None, row_attrs=None):
734         choices = ((u'1', ugettext('Unknown')), (u'2', ugettext('Yes')), (u'3', ugettext('No')))
735-        super(NullBooleanSelect, self).__init__(attrs, choices)
736+        super(NullBooleanSelect, self).__init__(attrs, row_attrs, choices)
737 
738     def render(self, name, value, attrs=None, choices=()):
739         try:
740@@ -242,12 +248,7 @@
741         value = data.get(name, None)
742         return {u'2': True, u'3': False, True: True, False: False}.get(value, None)
743 
744-class SelectMultiple(Widget):
745-    def __init__(self, attrs=None, choices=()):
746-        super(SelectMultiple, self).__init__(attrs)
747-        # choices can be any iterable
748-        self.choices = choices
749-
750+class SelectMultiple(Select):
751     def render(self, name, value, attrs=None, choices=()):
752         if value is None: value = []
753         final_attrs = self.build_attrs(attrs, name=name)
754@@ -406,9 +407,9 @@
755 
756     You'll probably want to use this class with MultiValueField.
757     """
758-    def __init__(self, widgets, attrs=None):
759+    def __init__(self, widgets, attrs=None, row_attrs=None):
760         self.widgets = [isinstance(w, type) and w() or w for w in widgets]
761-        super(MultiWidget, self).__init__(attrs)
762+        super(MultiWidget, self).__init__(attrs, row_attrs)
763 
764     def render(self, name, value, attrs=None):
765         # value is a list of values, each corresponding to a widget
766@@ -460,9 +461,9 @@
767     """
768     A Widget that splits datetime input into two <input type="text"> boxes.
769     """
770-    def __init__(self, attrs=None):
771+    def __init__(self, attrs=None, row_attrs=None):
772         widgets = (TextInput(attrs=attrs), TextInput(attrs=attrs))
773-        super(SplitDateTimeWidget, self).__init__(widgets, attrs)
774+        super(SplitDateTimeWidget, self).__init__(widgets, attrs, row_attrs)
775 
776     def decompress(self, value):
777         if value:
778
779=== modified file 'tests/modeltests/model_forms/models.py'
780--- tests/modeltests/model_forms/models.py      2008-02-16 07:59:58 +0000
781+++ tests/modeltests/model_forms/models.py      2008-03-05 22:10:25 +0000
782@@ -142,9 +142,18 @@
783 ...         model = Category
784 ...         fields = ['name', 'url']
785 ...         exclude = ['url']
786-
787+Traceback (most recent call last):
788+  File "/home/petr/django/local2/00-newforms-fieldsets/django/test/_doctest.py", line 1267, in __run
789+    compileflags, 1) in test.globs
790+  File "<doctest modeltests.model_forms.models.__test__.API_TESTS[12]>", line 1, in ?
791+    class CategoryForm(ModelForm):
792+  File "/home/petr/django/local2/00-newforms-fieldsets/django/newforms/models.py", line 220, in __new__
793+    metaclassing.create_base_fields_from_base_fields_pool(new_class)
794+  File "/home/petr/django/local2/00-newforms-fieldsets/django/newforms/metaclassing.py", line 50, in create_base_fields_from_base_fields_pool
795+    raise ImproperlyConfigured("%s cannot have more than one option from fieldsets, fields and exclude." % cls.__name__)
796+ImproperlyConfigured: CategoryForm cannot have more than one option from fieldsets, fields and exclude.
797 >>> CategoryForm.base_fields.keys()
798-['name']
799+['name', 'slug']
800 
801 Don't allow more than one 'model' definition in the inheritance hierarchy.
802 Technically, it would generate a valid form, but the fact that the resulting
803
804=== modified file 'tests/regressiontests/forms/extra.py'
805--- tests/regressiontests/forms/extra.py        2008-01-28 15:22:03 +0000
806+++ tests/regressiontests/forms/extra.py        2008-02-07 15:23:04 +0000
807@@ -372,10 +372,8 @@
808 >>> f = CommentForm(data, auto_id=False, error_class=DivErrorList)
809 >>> print f.as_p()
810 <p>Name: <input type="text" name="name" maxlength="50" /></p>
811-<div class="errorlist"><div class="error">Enter a valid e-mail address.</div></div>
812-<p>Email: <input type="text" name="email" value="invalid" /></p>
813-<div class="errorlist"><div class="error">This field is required.</div></div>
814-<p>Comment: <input type="text" name="comment" /></p>
815+<div class="errorlist"><div class="error">Enter a valid e-mail address.</div></div><p>Email: <input type="text" name="email" value="invalid" /></p>
816+<div class="errorlist"><div class="error">This field is required.</div></div><p>Comment: <input type="text" name="comment" /></p>
817 
818 #################################
819 # Test multipart-encoded form #
820
821=== modified file 'tests/regressiontests/forms/forms.py'
822--- tests/regressiontests/forms/forms.py        2008-01-28 15:22:03 +0000
823+++ tests/regressiontests/forms/forms.py        2008-03-05 21:12:22 +0000
824@@ -89,12 +89,9 @@
825 <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>
826 <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>
827 >>> print p.as_p()
828-<ul class="errorlist"><li>This field is required.</li></ul>
829-<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
830-<ul class="errorlist"><li>This field is required.</li></ul>
831-<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
832-<ul class="errorlist"><li>This field is required.</li></ul>
833-<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>
834+<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>
835+<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>
836+<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>
837 
838 If you don't pass any values to the Form's __init__(), or if you pass None,
839 the Form will be considered unbound and won't do any validation. Form.errors
840@@ -543,7 +540,8 @@
841 ...     composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')], widget=MultipleHiddenInput)
842 >>> f = SongFormHidden(MultiValueDict(dict(name=['Yesterday'], composers=['J', 'P'])), auto_id=False)
843 >>> print f.as_ul()
844-<li>Name: <input type="text" name="name" value="Yesterday" /><input type="hidden" name="composers" value="J" />
845+<li>Name: <input type="text" name="name" value="Yesterday" /></li>
846+<li><input type="hidden" name="composers" value="J" />
847 <input type="hidden" name="composers" value="P" /></li>
848 
849 When using CheckboxSelectMultiple, the framework expects a list of input and
850@@ -768,30 +766,36 @@
851 >>> print p
852 <tr><th>First name:</th><td><input type="text" name="first_name" /></td></tr>
853 <tr><th>Last name:</th><td><input type="text" name="last_name" /></td></tr>
854-<tr><th>Birthday:</th><td><input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></td></tr>
855+<tr><th>Birthday:</th><td><input type="text" name="birthday" /></td></tr>
856+<tr><td colspan="2"><input type="hidden" name="hidden_text" /></td></tr>
857 >>> print p.as_ul()
858 <li>First name: <input type="text" name="first_name" /></li>
859 <li>Last name: <input type="text" name="last_name" /></li>
860-<li>Birthday: <input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></li>
861+<li>Birthday: <input type="text" name="birthday" /></li>
862+<li><input type="hidden" name="hidden_text" /></li>
863 >>> print p.as_p()
864 <p>First name: <input type="text" name="first_name" /></p>
865 <p>Last name: <input type="text" name="last_name" /></p>
866-<p>Birthday: <input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></p>
867+<p>Birthday: <input type="text" name="birthday" /></p>
868+<p><input type="hidden" name="hidden_text" /></p>
869 
870 With auto_id set, a HiddenInput still gets an ID, but it doesn't get a label.
871 >>> p = Person(auto_id='id_%s')
872 >>> print p
873 <tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" id="id_first_name" /></td></tr>
874 <tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" id="id_last_name" /></td></tr>
875-<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>
876+<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" id="id_birthday" /></td></tr>
877+<tr><td colspan="2"><input type="hidden" name="hidden_text" id="id_hidden_text" /></td></tr>
878 >>> print p.as_ul()
879 <li><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li>
880 <li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
881-<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>
882+<li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></li>
883+<li><input type="hidden" name="hidden_text" id="id_hidden_text" /></li>
884 >>> print p.as_p()
885 <p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
886 <p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
887-<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>
888+<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>
889+<p><input type="hidden" name="hidden_text" id="id_hidden_text" /></p>
890 
891 If a field with a HiddenInput has errors, the as_table() and as_ul() output
892 will include the error message(s) with the text "(Hidden field [fieldname]) "
893@@ -802,17 +806,20 @@
894 <tr><td colspan="2"><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></td></tr>
895 <tr><th>First name:</th><td><input type="text" name="first_name" value="John" /></td></tr>
896 <tr><th>Last name:</th><td><input type="text" name="last_name" value="Lennon" /></td></tr>
897-<tr><th>Birthday:</th><td><input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></td></tr>
898+<tr><th>Birthday:</th><td><input type="text" name="birthday" value="1940-10-9" /></td></tr>
899+<tr><td colspan="2"><input type="hidden" name="hidden_text" /></td></tr>
900 >>> print p.as_ul()
901 <li><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></li>
902 <li>First name: <input type="text" name="first_name" value="John" /></li>
903 <li>Last name: <input type="text" name="last_name" value="Lennon" /></li>
904-<li>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></li>
905+<li>Birthday: <input type="text" name="birthday" value="1940-10-9" /></li>
906+<li><input type="hidden" name="hidden_text" /></li>
907 >>> print p.as_p()
908 <ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul>
909 <p>First name: <input type="text" name="first_name" value="John" /></p>
910 <p>Last name: <input type="text" name="last_name" value="Lennon" /></p>
911-<p>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></p>
912+<p>Birthday: <input type="text" name="birthday" value="1940-10-9" /></p>
913+<p><input type="hidden" name="hidden_text" /></p>
914 
915 A corner case: It's possible for a form to have only HiddenInputs.
916 >>> class TestForm(Form):
917@@ -820,11 +827,11 @@
918 ...     bar = CharField(widget=HiddenInput)
919 >>> p = TestForm(auto_id=False)
920 >>> print p.as_table()
921-<input type="hidden" name="foo" /><input type="hidden" name="bar" />
922+<tr><td colspan="2"><input type="hidden" name="foo" /><input type="hidden" name="bar" /></td></tr>
923 >>> print p.as_ul()
924-<input type="hidden" name="foo" /><input type="hidden" name="bar" />
925+<li><input type="hidden" name="foo" /><input type="hidden" name="bar" /></li>
926 >>> print p.as_p()
927-<input type="hidden" name="foo" /><input type="hidden" name="bar" />
928+<p><input type="hidden" name="foo" /><input type="hidden" name="bar" /></p>
929 
930 A Form's fields are displayed in the same order in which they were defined.
931 >>> class TestForm(Form):
932@@ -1175,7 +1182,8 @@
933 >>> p = UserRegistration(auto_id=False)
934 >>> print p.as_ul()
935 <li>Username: <input type="text" name="username" maxlength="10" /> e.g., user@example.com</li>
936-<li>Password: <input type="password" name="password" /><input type="hidden" name="next" value="/" /></li>
937+<li>Password: <input type="password" name="password" /></li>
938+<li><input type="hidden" name="next" value="/" /></li>
939 
940 Help text can include arbitrary Unicode characters.
941 >>> class UserRegistration(Form):
942@@ -1219,10 +1227,10 @@
943 ...     haircut_type = CharField()
944 >>> b = Beatle(auto_id=False)
945 >>> print b.as_ul()
946+<li>Instrument: <input type="text" name="instrument" /></li>
947 <li>First name: <input type="text" name="first_name" /></li>
948 <li>Last name: <input type="text" name="last_name" /></li>
949 <li>Birthday: <input type="text" name="birthday" /></li>
950-<li>Instrument: <input type="text" name="instrument" /></li>
951 <li>Haircut type: <input type="text" name="haircut_type" /></li>
952 
953 # Forms with prefixes #########################################################
954
955=== modified file 'tests/regressiontests/forms/regressions.py'
956--- tests/regressiontests/forms/regressions.py  2008-01-28 15:22:03 +0000
957+++ tests/regressiontests/forms/regressions.py  2008-02-07 15:32:45 +0000
958@@ -56,7 +56,7 @@
959 >>> activate('ru')
960 >>> f = SomeForm({})
961 >>> f.as_p()
962-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>'
963+u'<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>'
964 >>> deactivate()
965 
966 Deep copying translated text shouldn't raise an error
967