Ticket #6337: ModelFormMetaclass-inheritance.diff

File ModelFormMetaclass-inheritance.diff, 6.9 KB (added by semenov, 7 years ago)
  • django/newforms/models.py

     
    1111from django.core.exceptions import ImproperlyConfigured
    1212
    1313from util import ValidationError, ErrorList
    14 from forms import BaseForm
     14from forms import BaseForm, get_declared_fields
    1515from fields import Field, ChoiceField, EMPTY_VALUES
    1616from widgets import Select, SelectMultiple, MultipleHiddenInput
    1717
     
    214214class ModelFormMetaclass(type):
    215215    def __new__(cls, name, bases, attrs,
    216216                formfield_callback=lambda f: f.formfield()):
    217         fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
    218         fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
    219217
    220         # If this class is subclassing another Form, add that Form's fields.
    221         # Note that we loop over the bases in *reverse*. This is necessary in
    222         # order to preserve the correct order of fields.
    223         for base in bases[::-1]:
    224             if hasattr(base, 'base_fields'):
    225                 fields = base.base_fields.items() + fields
    226         declared_fields = SortedDict(fields)
     218        fields = SortedDict()
    227219
    228         opts = ModelFormOptions(attrs.get('Meta', None))
    229         attrs['_meta'] = opts
    230 
    231         # Don't allow more than one Meta model defenition in bases. The fields
    232         # would be generated correctly, but the save method won't deal with
    233         # more than one object.
    234         base_models = []
    235         for base in bases:
    236             base_opts = getattr(base, '_meta', None)
    237             base_model = getattr(base_opts, 'model', None)
    238             if base_model is not None:
    239                 base_models.append(base_model)
    240         if len(base_models) > 1:
    241             raise ImproperlyConfigured("%s's base classes define more than one model." % name)
    242 
    243         # If a model is defined, extract form fields from it and add them to base_fields
    244         if attrs['_meta'].model is not None:
    245             # Don't allow a subclass to define a different Meta model than a
    246             # parent class has. Technically the right fields would be generated,
    247             # but the save method will not deal with more than one model.
     220        meta = attrs.get('Meta', None)
     221        if meta is not None:
     222            opts = ModelFormOptions(meta)
     223            # If a model is defined, extract form fields from it
     224            if opts.model is not None:
     225                fields = fields_for_model(opts.model, opts.fields,
     226                                          opts.exclude, formfield_callback)
     227        else:
     228            # No Meta options defined in the class. Search base classes, but
     229            # don't allow more than one Meta definition. The fields would be
     230            # generated correctly, but the save method won't deal with more
     231            # than one object. Also, it wouldn't be clear what to do with
     232            # multiple fields and exclude lists.
     233            opts = None
    248234            for base in bases:
    249235                base_opts = getattr(base, '_meta', None)
    250                 base_model = getattr(base_opts, 'model', None)
    251                 if base_model and base_model is not opts.model:
    252                     raise ImproperlyConfigured('%s defines a different model than its parent.' % name)
    253             model_fields = fields_for_model(opts.model, opts.fields,
    254                     opts.exclude, formfield_callback)
    255             # fields declared in base classes override fields from the model
    256             model_fields.update(declared_fields)
    257             attrs['base_fields'] = model_fields
    258         else:
    259             attrs['base_fields'] = declared_fields
     236                if base_opts is not None:
     237                    if opts is not None:
     238                        raise ImproperlyConfigured("%s's base classes define more than one Meta options." % name)
     239                    opts = base_opts
     240            if opts is None:
     241                # No Meta options defined neither in the class nor its base classes
     242                opts = ModelFormOptions()
     243
     244        attrs['_meta'] = opts
     245
     246        # Fields declared explicitely override fields from the model
     247        fields.update(get_declared_fields(bases, attrs))
     248        attrs['base_fields'] = fields
    260249        return type.__new__(cls, name, bases, attrs)
    261250
    262251class BaseModelForm(BaseForm):
  • django/newforms/forms.py

     
    2222    name = name[0].upper() + name[1:]
    2323    return name.replace('_', ' ')
    2424
     25def get_declared_fields(bases, attrs):
     26    fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
     27    fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
     28
     29    # If this class is subclassing another Form, add that Form's fields.
     30    # Note that we loop over the bases in *reverse*. This is necessary in
     31    # order to preserve the correct order of fields.
     32    for base in bases[::-1]:
     33        if hasattr(base, 'base_fields'):
     34            fields = base.base_fields.items() + fields
     35
     36    return SortedDict(fields)
     37
    2538class DeclarativeFieldsMetaclass(type):
    2639    """
    2740    Metaclass that converts Field attributes to a dictionary called
    2841    'base_fields', taking into account parent class 'base_fields' as well.
    2942    """
    3043    def __new__(cls, name, bases, attrs):
    31         fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
    32         fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
    33 
    34         # If this class is subclassing another Form, add that Form's fields.
    35         # Note that we loop over the bases in *reverse*. This is necessary in
    36         # order to preserve the correct order of fields.
    37         for base in bases[::-1]:
    38             if hasattr(base, 'base_fields'):
    39                 fields = base.base_fields.items() + fields
    40 
    41         attrs['base_fields'] = SortedDict(fields)
     44        attrs['base_fields'] = get_declared_fields(bases, attrs)
    4245        return type.__new__(cls, name, bases, attrs)
    4346
    4447class BaseForm(StrAndUnicode):
  • docs/modelforms.txt

     
    311311    ...
    312312    ...     class Meta:
    313313    ...         model = Article
     314
     315Form inheritance
     316----------------
     317As with the basic forms, you can extend and reuse forms by inheriting them.
     318The fields will be combined, but only the most recently declared Meta
     319definition will be used. If you do not include a Meta definition in a child
     320class, a Meta definition will be looked in its parent classes. Note that it is
     321not allowed to have more than one base class with the Meta defition.
Back to Top