Ticket #17301: ticket17301-version1.diff

File ticket17301-version1.diff, 33.2 KB (added by msiedlarek, 12 years ago)

Patch version 1

  • django/forms/forms.py

    diff --git a/django/forms/forms.py b/django/forms/forms.py
    index 1400be3..f6351c2 100644
    a b def get_declared_fields(bases, attrs, with_base_fields=True):  
    5454
    5555    return SortedDict(fields)
    5656
    57 class DeclarativeFieldsMetaclass(type):
    58     """
    59     Metaclass that converts Field attributes to a dictionary called
    60     'base_fields', taking into account parent class 'base_fields' as well.
    61     """
     57class BaseFormOptions(object):
     58    def __init__(self, options=None):
     59        self.fieldsets = getattr(options, 'fieldsets', None)
     60
     61class BaseFormMetaclass(type):
    6262    def __new__(cls, name, bases, attrs):
    63         attrs['base_fields'] = get_declared_fields(bases, attrs)
    64         new_class = super(DeclarativeFieldsMetaclass,
    65                      cls).__new__(cls, name, bases, attrs)
     63        try:
     64            parents = [b for b in bases if issubclass(b, BaseForm)]
     65        except NameError:
     66            # We are defining Form itself.
     67            parents = None
     68        new_class = super(BaseFormMetaclass, cls).__new__(cls, name, bases, attrs)
     69        if not parents:
     70            return new_class
    6671        if 'media' not in attrs:
    6772            new_class.media = media_property(new_class)
     73        new_class._meta = BaseFormOptions(getattr(new_class, 'Meta', None))
    6874        return new_class
    6975
     76class DeclarativeFieldsMetaclass(BaseFormMetaclass):
     77     """
     78     Metaclass that converts Field attributes to a dictionary called
     79     'base_fields', taking into account parent class 'base_fields' as well.
     80     """
     81     def __new__(cls, name, bases, attrs):
     82         attrs['base_fields'] = get_declared_fields(bases, attrs)
     83         return super(DeclarativeFieldsMetaclass, cls).__new__(cls, name, bases, attrs)
     84
    7085class BaseForm(StrAndUnicode):
    7186    # This is the main implementation of all the Form logic. Note that this
    7287    # class is different than Form. See the comments by the Form class for more
    class BaseForm(StrAndUnicode):  
    138153        """
    139154        return u'initial-%s' % self.add_prefix(field_name)
    140155
    141     def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row):
     156    def _html_output(self, fieldset_method, error_row, before_fieldset=u'', after_fieldset=u''):
    142157        "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
    143158        top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
    144         output, hidden_fields = [], []
    145 
    146         for name, field in self.fields.items():
    147             html_class_attr = ''
    148             bf = BoundField(self, field, name)
    149             bf_errors = self.error_class([conditional_escape(error) for error in bf.errors]) # Escape and cache in local variable.
    150             if bf.is_hidden:
    151                 if bf_errors:
    152                     top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
    153                 hidden_fields.append(unicode(bf))
    154             else:
    155                 # Create a 'class="..."' atribute if the row should have any
    156                 # CSS classes applied.
    157                 css_classes = bf.css_classes()
    158                 if css_classes:
    159                     html_class_attr = ' class="%s"' % css_classes
    160 
    161                 if errors_on_separate_row and bf_errors:
    162                     output.append(error_row % force_unicode(bf_errors))
    163 
    164                 if bf.label:
    165                     label = conditional_escape(force_unicode(bf.label))
    166                     # Only add the suffix if the label does not end in
    167                     # punctuation.
    168                     if self.label_suffix:
    169                         if label[-1] not in ':?.!':
    170                             label += self.label_suffix
    171                     label = bf.label_tag(label) or ''
    172                 else:
    173                     label = ''
    174 
    175                 if field.help_text:
    176                     help_text = help_text_html % force_unicode(field.help_text)
    177                 else:
    178                     help_text = u''
    179 
    180                 output.append(normal_row % {
    181                     'errors': force_unicode(bf_errors),
    182                     'label': force_unicode(label),
    183                     'field': unicode(bf),
    184                     'help_text': help_text,
    185                     'html_class_attr': html_class_attr
    186                 })
     159        output = []
     160
     161        for fieldset in self.fieldsets:
     162            fieldset_html = [getattr(fieldset, fieldset_method)()]
     163            if not fieldset.dummy:
     164                fieldset_html.insert(0, u'<fieldset>')
     165                fieldset_html.insert(1, before_fieldset)
     166                fieldset_html.append(after_fieldset)
     167                fieldset_html.append(u'</fieldset>')
     168                if fieldset.legend:
     169                    fieldset_html.insert(1, fieldset.legend_tag())
     170                if top_errors:
     171                    output.insert(0, force_unicode(top_errors))
     172            output.extend(fieldset_html)
    187173
    188174        if top_errors:
    189175            output.insert(0, error_row % force_unicode(top_errors))
    190176
    191         if hidden_fields: # Insert any hidden fields in the last row.
    192             str_hidden = u''.join(hidden_fields)
    193             if output:
    194                 last_row = output[-1]
    195                 # Chop off the trailing row_ender (e.g. '</td></tr>') and
    196                 # insert the hidden fields.
    197                 if not last_row.endswith(row_ender):
    198                     # This can happen in the as_p() case (and possibly others
    199                     # that users write): if there are only top errors, we may
    200                     # not be able to conscript the last row for our purposes,
    201                     # so insert a new, empty row.
    202                     last_row = (normal_row % {'errors': '', 'label': '',
    203                                               'field': '', 'help_text':'',
    204                                               'html_class_attr': html_class_attr})
    205                     output.append(last_row)
    206                 output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
    207             else:
    208                 # If there aren't any rows in the output, just append the
    209                 # hidden fields.
    210                 output.append(str_hidden)
    211177        return mark_safe(u'\n'.join(output))
    212178
    213179    def as_table(self):
    214180        "Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
    215181        return self._html_output(
    216             normal_row = u'<tr%(html_class_attr)s><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>',
     182            fieldset_method='as_table',
    217183            error_row = u'<tr><td colspan="2">%s</td></tr>',
    218             row_ender = u'</td></tr>',
    219             help_text_html = u'<br /><span class="helptext">%s</span>',
    220             errors_on_separate_row = False)
     184            before_fieldset=u'<table>',
     185            after_fieldset=u'</table>')
    221186
    222187    def as_ul(self):
    223188        "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
    224189        return self._html_output(
    225             normal_row = u'<li%(html_class_attr)s>%(errors)s%(label)s %(field)s%(help_text)s</li>',
     190            fieldset_method='as_ul',
    226191            error_row = u'<li>%s</li>',
    227             row_ender = '</li>',
    228             help_text_html = u' <span class="helptext">%s</span>',
    229             errors_on_separate_row = False)
     192            before_fieldset=u'<ul>',
     193            after_fieldset=u'</ul>')
    230194
    231195    def as_p(self):
    232196        "Returns this form rendered as HTML <p>s."
    233197        return self._html_output(
    234             normal_row = u'<p%(html_class_attr)s>%(label)s %(field)s%(help_text)s</p>',
    235             error_row = u'%s',
    236             row_ender = '</p>',
    237             help_text_html = u' <span class="helptext">%s</span>',
    238             errors_on_separate_row = True)
     198            fieldset_method='as_p',
     199            error_row = u'%s')
    239200
    240201    def non_field_errors(self):
    241202        """
    class BaseForm(StrAndUnicode):  
    380341        """
    381342        return [field for field in self if not field.is_hidden]
    382343
     344    def _fieldsets(self):
     345        """
     346        Returns a list of Fieldset objects for each fieldset
     347        defined in Form's Meta options. If no fieldsets were defined,
     348        returns a list containing single, 'dummy' Fieldset with
     349        all form fields.
     350        """
     351        if self._meta.fieldsets:
     352            return [Fieldset(self, legend, attrs.get('fields', tuple()))
     353                    for legend, attrs in self._meta.fieldsets]
     354        return [Fieldset(self, None, self.fields.keys(), dummy=True)]
     355    fieldsets = property(_fieldsets)
     356
    383357class Form(BaseForm):
    384358    "A collection of Fields, plus their associated data."
    385359    # This is a separate class from BaseForm in order to abstract the way
    class Form(BaseForm):  
    389363    # BaseForm itself has no way of designating self.fields.
    390364    __metaclass__ = DeclarativeFieldsMetaclass
    391365
     366class Fieldset(StrAndUnicode):
     367
     368    def __init__(self, form, legend, fields, dummy=False):
     369        """
     370        Arguments:
     371        form   -- form this fieldset belongs to
     372        legend -- fieldset's legend (used in <legend> tag)
     373        fields -- list containing names of fields in this fieldset
     374
     375        Keyword arguments:
     376        dummy  -- flag informing that the fieldset was created automatically
     377                  from all fields of form, because user has not defined
     378                  custom fieldsets
     379        """
     380        self.form = form
     381        self.legend = legend
     382        self.fields = fields
     383        self.dummy = dummy
     384
     385    def __unicode__(self):
     386        return self.as_table()
     387
     388    def __iter__(self):
     389        for name in self.fields:
     390            yield BoundField(self.form, self.form.fields[name], name)
     391
     392    def __getitem__(self, name):
     393        "Returns a BoundField with the given name."
     394        if not name in self.fields:
     395            raise KeyError('Key %r not found in Fieldset' % name)
     396        return self.form[name]
     397
     398    def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row):
     399        "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
     400        output, hidden_fields = [], []
     401        top_errors = self.form.error_class()
     402
     403        for name in self.fields:
     404            field = self.form.fields[name]
     405            html_class_attr = ''
     406            bf = BoundField(self.form, field, name)
     407            bf_errors = self.form.error_class([conditional_escape(error) for error in bf.errors]) # Escape and cache in local variable.
     408            if bf.is_hidden:
     409                if bf_errors:
     410                    top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
     411                hidden_fields.append(unicode(bf))
     412            else:
     413                # Create a 'class="..."' atribute if the row should have any
     414                # CSS classes applied.
     415                css_classes = bf.css_classes()
     416                if css_classes:
     417                    html_class_attr = ' class="%s"' % css_classes
     418
     419                if errors_on_separate_row and bf_errors:
     420                    output.append(error_row % force_unicode(bf_errors))
     421
     422                if bf.label:
     423                    label = conditional_escape(force_unicode(bf.label))
     424                    # Only add the suffix if the label does not end in
     425                    # punctuation.
     426                    if self.form.label_suffix:
     427                        if label[-1] not in ':?.!':
     428                            label += self.form.label_suffix
     429                    label = bf.label_tag(label) or ''
     430                else:
     431                    label = ''
     432
     433                if field.help_text:
     434                    help_text = help_text_html % force_unicode(field.help_text)
     435                else:
     436                    help_text = u''
     437
     438                output.append(normal_row % {
     439                    'errors': force_unicode(bf_errors),
     440                    'label': force_unicode(label),
     441                    'field': unicode(bf),
     442                    'help_text': help_text,
     443                    'html_class_attr': html_class_attr
     444                })
     445
     446        if top_errors:
     447            output.insert(0, error_row % force_unicode(top_errors))
     448
     449        if hidden_fields: # Insert any hidden fields in the last row.
     450            str_hidden = u''.join(hidden_fields)
     451            if output:
     452                last_row = output[-1]
     453                # Chop off the trailing row_ender (e.g. '</td></tr>') and
     454                # insert the hidden fields.
     455                if not last_row.endswith(row_ender):
     456                    # This can happen in the as_p() case (and possibly others
     457                    # that users write): if there are only top errors, we may
     458                    # not be able to conscript the last row for our purposes,
     459                    # so insert a new, empty row.
     460                    last_row = (normal_row % {'errors': '', 'label': '',
     461                                              'field': '', 'help_text':'',
     462                                              'html_class_attr': html_class_attr})
     463                    output.append(last_row)
     464                output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
     465            else:
     466                # If there aren't any rows in the output, just append the
     467                # hidden fields.
     468                output.append(str_hidden)
     469
     470        return mark_safe(u'\n'.join(output))
     471
     472    def as_table(self):
     473        "Returns this fieldset rendered as HTML <tr>s -- excluding the <table>, <fieldset> and <legend> tags."
     474        return self._html_output(
     475            normal_row = u'<tr%(html_class_attr)s><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>',
     476            error_row = u'<tr><td colspan="2">%s</td></tr>',
     477            row_ender = u'</td></tr>',
     478            help_text_html = u'<br /><span class="helptext">%s</span>',
     479            errors_on_separate_row = False)
     480
     481    def as_ul(self):
     482        "Returns this fieldset rendered as HTML <li>s -- excluding the <ul>, <fieldset> and <legend> tags."
     483        return self._html_output(
     484            normal_row = u'<li%(html_class_attr)s>%(errors)s%(label)s %(field)s%(help_text)s</li>',
     485            error_row = u'<li>%s</li>',
     486            row_ender = '</li>',
     487            help_text_html = u' <span class="helptext">%s</span>',
     488            errors_on_separate_row = False)
     489
     490    def as_p(self):
     491        "Returns this fieldset rendered as HTML <p>s -- excluding the <fieldset> and <legend> tags."
     492        return self._html_output(
     493            normal_row = u'<p%(html_class_attr)s>%(label)s %(field)s%(help_text)s</p>',
     494            error_row = u'%s',
     495            row_ender = '</p>',
     496            help_text_html = u' <span class="helptext">%s</span>',
     497            errors_on_separate_row = True)
     498
     499    def legend_tag(self, contents=None, attrs=None):
     500        """
     501        Wraps the given contents in a <legend>. Does not HTML-escape the contents.
     502        If contents aren't given, uses the fieldset's HTML-escaped legend.
     503
     504        If attrs are given, they're used as HTML attributes on the <legend> tag.
     505        """
     506        if contents is None and not self.legend is None:
     507            contents = conditional_escape(self.legend)
     508        attrs = attrs and flatatt(attrs) or ''
     509        if not contents is None:
     510            return mark_safe(u'<legend%s>%s</legend>' % (attrs, force_unicode(self.legend)))
     511        return None
     512
     513    def hidden_fields(self):
     514        """
     515        Returns a list of all the BoundField objects that are hidden fields.
     516        Useful for manual form layout in templates.
     517        """
     518        return [field for field in self if field.is_hidden]
     519
     520    def visible_fields(self):
     521        """
     522        Returns a list of BoundField objects that aren't hidden fields.
     523        The opposite of the hidden_fields() method.
     524        """
     525        return [field for field in self if not field.is_hidden]
     526
    392527class BoundField(StrAndUnicode):
    393528    "A Field plus data"
    394529    def __init__(self, form, field, name):
  • django/forms/models.py

    diff --git a/django/forms/models.py b/django/forms/models.py
    index b65f067..467d00e 100644
    a b from __future__ import absolute_import  
    88from django.core.exceptions import ValidationError, NON_FIELD_ERRORS, FieldError
    99from django.core.validators import EMPTY_VALUES
    1010from django.forms.fields import Field, ChoiceField
    11 from django.forms.forms import BaseForm, get_declared_fields
     11from django.forms.forms import (BaseForm, BaseFormOptions, BaseFormMetaclass,
     12    get_declared_fields)
    1213from django.forms.formsets import BaseFormSet, formset_factory
    1314from django.forms.util import ErrorList
    1415from django.forms.widgets import (SelectMultiple, HiddenInput,
    15     MultipleHiddenInput, media_property)
     16    MultipleHiddenInput)
    1617from django.utils.encoding import smart_unicode, force_unicode
    1718from django.utils.datastructures import SortedDict
    1819from django.utils.text import get_text_list, capfirst
    def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_c  
    175176        )
    176177    return field_dict
    177178
    178 class ModelFormOptions(object):
     179class ModelFormOptions(BaseFormOptions):
    179180    def __init__(self, options=None):
     181        super(ModelFormOptions, self).__init__(options)
    180182        self.model = getattr(options, 'model', None)
    181183        self.fields = getattr(options, 'fields', None)
    182184        self.exclude = getattr(options, 'exclude', None)
    183185        self.widgets = getattr(options, 'widgets', None)
    184186
    185 
    186 class ModelFormMetaclass(type):
     187class ModelFormMetaclass(BaseFormMetaclass):
    187188    def __new__(cls, name, bases, attrs):
    188189        formfield_callback = attrs.pop('formfield_callback', None)
    189190        try:
    190             parents = [b for b in bases if issubclass(b, ModelForm)]
     191            parents = [b for b in bases if issubclass(b, BaseModelForm)]
    191192        except NameError:
    192193            # We are defining ModelForm itself.
    193194            parents = None
    class ModelFormMetaclass(type):  
    196197                attrs)
    197198        if not parents:
    198199            return new_class
    199 
    200         if 'media' not in attrs:
    201             new_class.media = media_property(new_class)
     200        # Override BaseFormOptions with ModelFormOptions (which is actually
     201        # BaseFormOptions' subclass). This obviously causes BaseFormOptions.__init__()
     202        # being called twice through the form class definition, but it's a price we can
     203        # pay for the less redundant code.
    202204        opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))
    203205        if opts.model:
    204206            # If a model is defined, extract form fields from it.
    class BaseModelForm(BaseForm):  
    305307
    306308    def _post_clean(self):
    307309        opts = self._meta
    308         # Update the model instance with self.cleaned_data.
     310        # Update the model instance with self.cleaned_data.ModelForm
    309311        self.instance = construct_instance(self, self.instance, opts.fields, opts.exclude)
    310312
    311313        exclude = self._get_validation_exclusions()
  • docs/topics/forms/index.txt

    diff --git a/docs/topics/forms/index.txt b/docs/topics/forms/index.txt
    index 18e55f5..bd47732 100644
    a b tag::  
    388388If you find yourself doing this often, you might consider creating a custom
    389389:ref:`inclusion tag<howto-custom-template-tags-inclusion-tags>`.
    390390
     391Fieldsets
     392---------
     393
     394.. versionadded:: development
     395
     396Having a complex form you may want to organize its fields in logical groups.
     397In Django you can do that using fieldsets. Fieldsets allow you to iterate
     398through their fields and are autmatically rendered by form using ``<fieldset>``
     399HTML tag and its subtag ``<legend>``.
     400
     401
     402Fieldsets are defined using ``Meta`` options class. If you're familiar
     403with :attr:`~django.contrib.admin.ModelAdmin.fieldsets` from Django admin
     404options, you alreadyknow the syntax:
     405
     406.. code-block:: python
     407
     408    class PersonForm(forms.Form):
     409        home_phone = CharField()
     410        cell_phone = CharField()
     411        first_name = CharField()
     412        last_name = CharField()
     413
     414        class Meta:
     415            fieldsets = (
     416                (None, {
     417                    'fields': ('first_name', 'last_name',),
     418                }),
     419                ("Phone numbers", {
     420                    'fields': ('cell_phone', 'home_phone',),
     421                }),
     422            )
     423
     424Having above example form you may render it in a template just like a normal form::
     425
     426    <form action="" method="post">
     427        {{ form.as_table }}
     428        <input type="submit" value="Send" />
     429    </form>
     430
     431Except now instead of one ``<table>`` element, ``as_table`` method will
     432print two tables wrapped up in ``<fieldset>`` tags::
     433
     434    <form action="" method="post">
     435        <fieldset>
     436            <table>
     437                <tr>
     438                    <th><label for="id_first_name">First name:</label></th>
     439                    <td><input type="text" name="first_name" id="id_first_name" /></td>
     440                </tr>
     441                <tr>
     442                    <th><label for="id_last_name">Last name:</label></th>
     443                    <td><input type="text" name="last_name" id="id_last_name" /></td>
     444                </tr>
     445            </table>
     446        </fieldset>
     447        <fieldset>
     448            <legend>Phone numbers</legend>
     449            <table>
     450                <tr>
     451                    <th><label for="id_cell_phone">Cell phone:</label></th>
     452                    <td><input type="text" name="cell_phone" id="id_cell_phone" /></td>
     453                </tr>
     454                <tr>
     455                    <th><label for="id_home_phone">Home phone:</label></th>
     456                    <td><input type="text" name="home_phone" id="id_home_phone" /></td>
     457                </tr>
     458            </table>
     459        </fieldset>
     460        <input type="submit" value="Send" />
     461    </form>
     462
     463You can also customize your output looping through form's fieldsets and using
     464their methods -- ``as_table``, ``as_ul`` and ``as_p`` -- which behave just like
     465their equivalents from ``Form`` class and using a ``legend_tag`` method::
     466
     467    <form action="" method="post">
     468        {% for fieldset in form.fieldsets %}
     469            <fieldset>
     470                {{ fieldset.legend_tag }}
     471                <table>
     472                    {{ fieldset.as_table }}
     473                </table>
     474            </fieldset>
     475        {% endfor %}
     476        <input type="submit" value="Send" />
     477    </form>
     478
     479You can be even more specific and loop through all fields of all fieldsets::
     480
     481    <form action="" method="post">
     482        {% for fieldset in form.fieldsets %}
     483            <fieldset>
     484                {{ fieldset.legend_tag }}
     485                <ul>
     486                    {% for field in fieldset %}
     487                        <li>
     488                            {{ field.label_tag }}
     489                            {{ field }}
     490                        </li>
     491                    {% endfor %}
     492                </ul>
     493            </fieldset>
     494        {% endfor %}
     495        <input type="submit" value="Send" />
     496    </form>
     497
     498You can also loop though fieldset ``hidden_fields`` and ``visible_fields`` just
     499line in a form class.
     500
    391501Further topics
    392502==============
    393503
  • tests/regressiontests/forms/tests/__init__.py

    diff --git a/tests/regressiontests/forms/tests/__init__.py b/tests/regressiontests/forms/tests/__init__.py
    index 8e2150c..8c3d0fb 100644
    a b from .util import FormsUtilTestCase  
    1919from .validators import TestFieldWithValidators
    2020from .widgets import (FormsWidgetTestCase, FormsI18NWidgetsTestCase,
    2121    WidgetTests, ClearableFileInputTests)
     22from .fieldsets import FieldsetsTestCase
  • new file tests/regressiontests/forms/tests/fieldsets.py

    diff --git a/tests/regressiontests/forms/tests/fieldsets.py b/tests/regressiontests/forms/tests/fieldsets.py
    new file mode 100644
    index 0000000..79ccabb
    - +  
     1# -*- coding: utf-8 -*-
     2import datetime
     3
     4from django.core.files.uploadedfile import SimpleUploadedFile
     5from django.forms import *
     6from django import forms
     7from django.http import QueryDict
     8from django.template import Template, Context
     9from django.utils.datastructures import MultiValueDict, MergeDict
     10from django.utils.safestring import mark_safe
     11from django.utils.unittest import TestCase
     12
     13
     14class PersonWithoutFormfields(Form):
     15    first_name = CharField()
     16    last_name = CharField()
     17    birthday = DateField()
     18    band = CharField()
     19    secret = CharField(widget=HiddenInput)
     20
     21class Person(PersonWithoutFormfields):
     22    class Meta:
     23        fieldsets = (
     24            (None, {
     25                'fields': ('first_name', 'last_name', 'birthday'),
     26            }),
     27            ("Additional fields", {
     28                'fields': ('band', 'secret'),
     29            }),
     30        )
     31
     32class FieldsetsTestCase(TestCase):
     33
     34    some_data = {
     35        'first_name': u'John',
     36        'last_name': u'Lennon',
     37        'birthday': u'1940-10-9',
     38        'band': u'The Beatles',
     39        'secret': u'he didnt say',
     40    }
     41
     42    def test_simple_rendering(self):
     43        # Pass a dictionary to a Form's __init__().
     44        p = Person(self.some_data)
     45        # as_table
     46        self.assertEqual(str(p), """<fieldset>
     47<table>
     48<tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" value="John" id="id_first_name" /></td></tr>
     49<tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" value="Lennon" id="id_last_name" /></td></tr>
     50<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></td></tr>
     51</table>
     52</fieldset>
     53<fieldset>
     54<legend>Additional fields</legend>
     55<table>
     56<tr><th><label for="id_band">Band:</label></th><td><input type="text" name="band" value="The Beatles" id="id_band" /><input type="hidden" name="secret" value="he didnt say" id="id_secret" /></td></tr>
     57</table>
     58</fieldset>""")
     59        self.assertEqual(str(p), unicode(p))
     60        self.assertEqual(str(p), p.as_table())
     61        # as_ul
     62        self.assertEqual(p.as_ul(), """<fieldset>
     63<ul>
     64<li><label for="id_first_name">First name:</label> <input type="text" name="first_name" value="John" id="id_first_name" /></li>
     65<li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" value="Lennon" id="id_last_name" /></li>
     66<li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></li>
     67</ul>
     68</fieldset>
     69<fieldset>
     70<legend>Additional fields</legend>
     71<ul>
     72<li><label for="id_band">Band:</label> <input type="text" name="band" value="The Beatles" id="id_band" /><input type="hidden" name="secret" value="he didnt say" id="id_secret" /></li>
     73</ul>
     74</fieldset>""")
     75        # as_p
     76        self.assertEqual(p.as_p(), """<fieldset>
     77
     78<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" value="John" id="id_first_name" /></p>
     79<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" value="Lennon" id="id_last_name" /></p>
     80<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></p>
     81
     82</fieldset>
     83<fieldset>
     84<legend>Additional fields</legend>
     85
     86<p><label for="id_band">Band:</label> <input type="text" name="band" value="The Beatles" id="id_band" /><input type="hidden" name="secret" value="he didnt say" id="id_secret" /></p>
     87
     88</fieldset>""") # Additional blank lines are ok
     89
     90    def test_single_fieldset_rendering(self):
     91        # Pass a dictionary to a Form's __init__().
     92        p = Person(self.some_data)
     93        # as_table
     94        self.assertEqual(str(p.fieldsets[0]), """<tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" value="John" id="id_first_name" /></td></tr>
     95<tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" value="Lennon" id="id_last_name" /></td></tr>
     96<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></td></tr>""")
     97        self.assertEqual(str(p.fieldsets[0]), unicode(p.fieldsets[0]))
     98        self.assertEqual(str(p.fieldsets[0]), p.fieldsets[0].as_table())
     99        self.assertEqual(str(p.fieldsets[1]), """<tr><th><label for="id_band">Band:</label></th><td><input type="text" name="band" value="The Beatles" id="id_band" /><input type="hidden" name="secret" value="he didnt say" id="id_secret" /></td></tr>""")
     100        self.assertEqual(str(p.fieldsets[1]), p.fieldsets[1].as_table())
     101        # as_ul
     102        self.assertEqual(p.fieldsets[0].as_ul(), """<li><label for="id_first_name">First name:</label> <input type="text" name="first_name" value="John" id="id_first_name" /></li>
     103<li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" value="Lennon" id="id_last_name" /></li>
     104<li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></li>""")
     105        self.assertEqual(p.fieldsets[1].as_ul(), """<li><label for="id_band">Band:</label> <input type="text" name="band" value="The Beatles" id="id_band" /><input type="hidden" name="secret" value="he didnt say" id="id_secret" /></li>""")
     106        # as_p
     107        self.assertEqual(p.fieldsets[0].as_p(), """<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" value="John" id="id_first_name" /></p>
     108<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" value="Lennon" id="id_last_name" /></p>
     109<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></p>""")
     110        self.assertEqual(p.fieldsets[1].as_p(), """<p><label for="id_band">Band:</label> <input type="text" name="band" value="The Beatles" id="id_band" /><input type="hidden" name="secret" value="he didnt say" id="id_secret" /></p>""")
     111
     112    def test_fieldset_fields_iteration(self):
     113        # Pass a dictionary to a Form's __init__().
     114        p = Person(self.some_data)
     115        for fieldset in p.fieldsets:
     116            for field in fieldset:
     117                pass
     118        self.assertEqual(set([field.name for field in p.fieldsets[0].visible_fields()]), set(['first_name', 'last_name', 'birthday']))
     119        self.assertEqual(len(p.fieldsets[0].hidden_fields()), 0)
     120        self.assertEqual(set([field.name for field in p.fieldsets[1].visible_fields()]), set(['band']))
     121        self.assertEqual(set([field.name for field in p.fieldsets[1].hidden_fields()]), set(['secret']))
     122
     123    def test_legend_tag(self):
     124        # Pass a dictionary to a Form's __init__().
     125        p = Person(self.some_data)
     126        self.assertIsNone(p.fieldsets[0].legend_tag())
     127        self.assertEqual(p.fieldsets[1].legend_tag(), """<legend>Additional fields</legend>""")
     128
  • tests/regressiontests/forms/tests/forms.py

    diff --git a/tests/regressiontests/forms/tests/forms.py b/tests/regressiontests/forms/tests/forms.py
    index a37cc2e..8a9dc40 100644
    a b class FormsTestCase(TestCase):  
    17531753
    17541754        form = EventForm()
    17551755        self.assertEqual(form.as_ul(), u'<input type="hidden" name="happened_at_0" id="id_happened_at_0" /><input type="hidden" name="happened_at_1" id="id_happened_at_1" />')
     1756
     1757    def test_meta_options(self):
     1758        class MetaOptionsForm(Form):
     1759            class Meta:
     1760                fieldsets = 0xDEADBEEF
     1761                some_nonexising_option = True
     1762        class MetaOptionsDerivantForm(MetaOptionsForm):
     1763            pass
     1764        # Test classes
     1765        self.assertEqual(MetaOptionsForm._meta.fieldsets, 0xDEADBEEF)
     1766        self.assertEqual(MetaOptionsDerivantForm._meta.fieldsets, 0xDEADBEEF)
     1767        self.assertFalse(hasattr(MetaOptionsForm._meta, 'some_nonexising_option'))
     1768        self.assertFalse(hasattr(MetaOptionsDerivantForm._meta, 'some_nonexising_option'))
     1769        # Test instances
     1770        meta_options_form = MetaOptionsForm()
     1771        meta_options_derivant_form = MetaOptionsDerivantForm()
     1772        self.assertEqual(meta_options_form._meta.fieldsets, 0xDEADBEEF)
     1773        self.assertEqual(meta_options_derivant_form._meta.fieldsets, 0xDEADBEEF)
     1774        self.assertFalse(hasattr(meta_options_form._meta, 'some_nonexising_option'))
     1775        self.assertFalse(hasattr(meta_options_derivant_form._meta, 'some_nonexising_option'))
     1776
     1777    def test_meta_options_override(self):
     1778        class MetaOptionsForm(Form):
     1779            class Meta:
     1780                fieldsets = 0xDEADBEEF
     1781        class MetaOptionsDerivantForm(MetaOptionsForm):
     1782            class Meta:
     1783                fieldsets = 0xCAFEBABE
     1784        # Test classes
     1785        self.assertEqual(MetaOptionsForm._meta.fieldsets, 0xDEADBEEF)
     1786        self.assertEqual(MetaOptionsDerivantForm._meta.fieldsets, 0xCAFEBABE)
     1787        # Test instances
     1788        meta_options_form = MetaOptionsForm()
     1789        meta_options_derivant_form = MetaOptionsDerivantForm()
     1790        self.assertEqual(meta_options_form._meta.fieldsets, 0xDEADBEEF)
     1791        self.assertEqual(meta_options_derivant_form._meta.fieldsets, 0xCAFEBABE)
     1792
     1793    def test_meta_option_defaults(self):
     1794        class MetaOptionsForm(Form):
     1795            pass
     1796        # Test classes
     1797        self.assertIsNone(MetaOptionsForm._meta.fieldsets)
     1798        # Test instance
     1799        meta_options_form = MetaOptionsForm()
     1800        self.assertIsNone(meta_options_form._meta.fieldsets)
     1801
Back to Top