diff options
| author | Adrian Holovaty <adrian@holovaty.com> | 2007-02-14 23:44:46 +0000 |
|---|---|---|
| committer | Adrian Holovaty <adrian@holovaty.com> | 2007-02-14 23:44:46 +0000 |
| commit | 05182053088d3c30fad3fe5cc81db7a12412ae2b (patch) | |
| tree | 65a1a38c7605bd3623aa17ad34b6086fd484626b | |
| parent | 4c4209b14449554382b541d709749340f9b1a7ae (diff) | |
Implemented subclassing Forms in newforms
git-svn-id: http://code.djangoproject.com/svn/django/trunk@4506 bcc190cf-cafb-0310-a4f2-bffc1f526a37
| -rw-r--r-- | django/newforms/forms.py | 13 | ||||
| -rw-r--r-- | docs/newforms.txt | 40 | ||||
| -rw-r--r-- | tests/regressiontests/forms/tests.py | 41 |
3 files changed, 93 insertions, 1 deletions
diff --git a/django/newforms/forms.py b/django/newforms/forms.py index 0b90b09e2c..1032d02e35 100644 --- a/django/newforms/forms.py +++ b/django/newforms/forms.py @@ -31,10 +31,21 @@ class SortedDictFromList(SortedDict): return SortedDictFromList([(k, copy.copy(v)) for k, v in self.items()]) class DeclarativeFieldsMetaclass(type): - "Metaclass that converts Field attributes to a dictionary called 'base_fields'." + """ + Metaclass that converts Field attributes to a dictionary called + 'base_fields', taking into account parent class 'base_fields' as well. + """ def __new__(cls, name, bases, attrs): fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) + + # If this class is subclassing another Form, add that Form's fields. + # Note that we loop over the bases in *reverse*. This is necessary in + # order to preserve the correct order of fields. + for base in bases[::-1]: + if hasattr(base, 'base_fields'): + fields = base.base_fields.items() + fields + attrs['base_fields'] = SortedDictFromList(fields) return type.__new__(cls, name, bases, attrs) diff --git a/docs/newforms.txt b/docs/newforms.txt index 063f686ed5..ff222516b3 100644 --- a/docs/newforms.txt +++ b/docs/newforms.txt @@ -571,6 +571,46 @@ is a list-like object that is displayed as an HTML ``<ul>`` when printed:: >>> str(f['subject'].errors) '' +Subclassing forms +----------------- + +If you subclass a custom ``Form`` class, the resulting ``Form`` class will +include all fields of the parent class(es), followed by the fields you define +in the subclass. + +In this example, ``ContactFormWithPriority`` contains all the fields from +``ContactForm``, plus an additional field, ``priority``. The ``ContactForm`` +fields are ordered first:: + + >>> class ContactFormWithPriority(ContactForm): + ... priority = forms.CharField() + >>> f = ContactFormWithPriority(auto_id=False) + >>> print f.as_ul() + <li>Subject: <input type="text" name="subject" maxlength="100" /></li> + <li>Message: <input type="text" name="message" /></li> + <li>Sender: <input type="text" name="sender" /></li> + <li>Cc myself: <input type="checkbox" name="cc_myself" /></li> + <li>Priority: <input type="text" name="priority" /></li> + +It's possible to subclass multiple forms, treating forms as "mix-ins." In this +example, ``BeatleForm`` subclasses both ``PersonForm`` and ``InstrumentForm`` +(in that order), and its field list includes the fields from the parent +classes:: + + >>> class PersonForm(Form): + ... first_name = CharField() + ... last_name = CharField() + >>> class InstrumentForm(Form): + ... instrument = CharField() + >>> class BeatleForm(PersonForm, InstrumentForm): + ... haircut_type = CharField() + >>> b = Beatle(auto_id=False) + >>> print b.as_ul() + <li>First name: <input type="text" name="first_name" /></li> + <li>Last name: <input type="text" name="last_name" /></li> + <li>Instrument: <input type="text" name="instrument" /></li> + <li>Haircut type: <input type="text" name="haircut_type" /></li> + Fields ====== diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index b96b62a221..db7beb96a5 100644 --- a/tests/regressiontests/forms/tests.py +++ b/tests/regressiontests/forms/tests.py @@ -2682,6 +2682,47 @@ purposes, though. <li>Username: <input type="text" name="username" maxlength="10" /> e.g., user@example.com</li> <li>Password: <input type="password" name="password" /><input type="hidden" name="next" value="/" /></li> +# Subclassing forms ########################################################### + +You can subclass a Form to add fields. The resulting form subclass will have +all of the fields of the parent Form, plus whichever fields you define in the +subclass. +>>> class Person(Form): +... first_name = CharField() +... last_name = CharField() +... birthday = DateField() +>>> class Musician(Person): +... instrument = CharField() +>>> p = Person(auto_id=False) +>>> print p.as_ul() +<li>First name: <input type="text" name="first_name" /></li> +<li>Last name: <input type="text" name="last_name" /></li> +<li>Birthday: <input type="text" name="birthday" /></li> +>>> m = Musician(auto_id=False) +>>> print m.as_ul() +<li>First name: <input type="text" name="first_name" /></li> +<li>Last name: <input type="text" name="last_name" /></li> +<li>Birthday: <input type="text" name="birthday" /></li> +<li>Instrument: <input type="text" name="instrument" /></li> + +Yes, you can subclass multiple forms. The fields are added in the order in +which the parent classes are listed. +>>> class Person(Form): +... first_name = CharField() +... last_name = CharField() +... birthday = DateField() +>>> class Instrument(Form): +... instrument = CharField() +>>> class Beatle(Person, Instrument): +... haircut_type = CharField() +>>> b = Beatle(auto_id=False) +>>> print b.as_ul() +<li>First name: <input type="text" name="first_name" /></li> +<li>Last name: <input type="text" name="last_name" /></li> +<li>Birthday: <input type="text" name="birthday" /></li> +<li>Instrument: <input type="text" name="instrument" /></li> +<li>Haircut type: <input type="text" name="haircut_type" /></li> + # Forms with prefixes ######################################################### Sometimes it's necessary to have multiple forms display on the same HTML page, |
