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 ``
`` 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()
+
Subject:
+
Message:
+
Sender:
+
Cc myself:
+
Priority:
+
+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()
+
+# 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()
+
First name:
+
Last name:
+
Birthday:
+>>> m = Musician(auto_id=False)
+>>> print m.as_ul()
+
First name:
+
Last name:
+
Birthday:
+
Instrument:
+
+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()
+
First name:
+
Last name:
+
Birthday:
+
Instrument:
+
Haircut type:
+
# Forms with prefixes #########################################################
Sometimes it's necessary to have multiple forms display on the same HTML page,