Django

Code

Changeset 7115

Show
Ignore:
Timestamp:
02/14/08 11:38:05 (5 months ago)
Author:
mtredinnick
Message:

Modified [7112] to make things behave more in line with Python subclassing when subclassing ModelForms?.

Meta can now be subclassed and changes on the child model affect the fields
list. Also removed a block of error checking, since it's harder to mess up in
unexpected ways now (e.g. you can't change the model and get the entirely wrong
fields list), so it was a level of overkill.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/newforms/forms.py

    r7112 r7115  
    2323    return name.replace('_', ' ') 
    2424 
    25 def get_declared_fields(bases, attrs): 
     25def get_declared_fields(bases, attrs, with_base_fields=True): 
     26    """ 
     27    Create a list of form field instances from the passed in 'attrs', plus any 
     28    similar fields on the base classes (in 'bases'). This is used by both the 
     29    Form and ModelForm metclasses. 
     30 
     31    If 'with_base_fields' is True, all fields from the bases are used. 
     32    Otherwise, only fields in the 'declared_fields' attribute on the bases are 
     33    used. The distinction is useful in ModelForm subclassing. 
     34    """ 
    2635    fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] 
    2736    fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) 
     
    3039    # Note that we loop over the bases in *reverse*. This is necessary in 
    3140    # 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 
     41    if with_base_fields: 
     42        for base in bases[::-1]: 
     43            if hasattr(base, 'base_fields'): 
     44                fields = base.base_fields.items() + fields 
     45    else: 
     46        for base in bases[::-1]: 
     47            if hasattr(base, 'declared_fields'): 
     48                fields = base.declared_fields.items() + fields 
    3549 
    3650    return SortedDict(fields) 
  • django/trunk/django/newforms/models.py

    r7112 r7115  
    226226 
    227227        new_class = type.__new__(cls, name, bases, attrs) 
    228         declared_fields = get_declared_fields(bases, attrs
     228        declared_fields = get_declared_fields(bases, attrs, False
    229229        opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None)) 
    230230        if opts.model: 
     
    237237        else: 
    238238            fields = declared_fields 
     239        new_class.declared_fields = declared_fields 
    239240        new_class.base_fields = fields 
    240  
    241         # XXX: The following is a sanity check for the user to avoid 
    242         # inadvertent attribute hiding. 
    243  
    244         # Search base classes, but don't allow more than one Meta model 
    245         # definition. The fields would be generated correctly, but the save 
    246         # method won't deal with more than one object. Also, it wouldn't be 
    247         # clear what to do with multiple fields and exclude lists. 
    248         first = None 
    249         current = opts.model 
    250         for base in parents: 
    251             base_opts = getattr(base, '_meta', None) 
    252             base_model = getattr(base_opts, 'model', None) 
    253             if base_model: 
    254                 if current: 
    255                     if base_model is not current: 
    256                         raise ImproperlyConfigured("%s's base classes define more than one model." % name) 
    257                 else: 
    258                     current = base_model 
    259  
    260241        return new_class 
    261242 
  • django/trunk/docs/modelforms.txt

    r7112 r7115  
    336336is some extra validation and cleaning for the ``pub_date`` field. 
    337337 
     338You can also subclass the parent's ``Meta`` inner class if you want to change 
     339the ``Meta.fields`` or ``Meta.excludes`` lists:: 
     340 
     341    >>> class RestrictedArticleForm(EnhancedArticleForm): 
     342    ...     class Meta(ArticleForm.Meta): 
     343    ...         exclude = ['body'] 
     344 
     345This adds in the extra method from the ``EnhancedArticleForm`` and modifies 
     346the original ``ArticleForm.Meta`` to remove one field. 
     347 
    338348There are a couple of things to note, however. Most of these won't normally be 
    339349of concern unless you are trying to do something tricky with subclassing. 
    340  
    341  * All the fields from the parent classes will appear in the child 
    342    ``ModelForm``. This means you cannot change a parent's ``Meta.exclude`` 
    343    attribute, for example, and except it to have an effect, since the field is 
    344    already part of the field list in the parent class. 
    345350 
    346351 * Normal Python name resolution rules apply. If you have multiple base 
     
    352357   both a ``ModelForm`` and a ``Form`` simultaneously. 
    353358 
    354 Because of the "child inherits all fields from parents" behaviour, you 
    355 shouldn't try to declare model fields in multiple classes (parent and child). 
    356 Instead, declare all the model-related stuff in one class and use inheritance 
    357 to add "extra" non-model fields and methods to the final result. Whether you 
    358 put the "extra" functions in the parent class or the child class will depend 
    359 on how you intend to reuse them. 
    360  
  • django/trunk/tests/modeltests/model_forms/models.py

    r7112 r7115  
    156156...         model = Category 
    157157 
    158 >>> class BadForm(CategoryForm): 
    159 ...     class Meta: 
    160 ...         model = Article 
    161 Traceback (most recent call last): 
    162 ... 
    163 ImproperlyConfigured: BadForm's base classes define more than one model. 
     158>>> class OddForm(CategoryForm): 
     159...     class Meta: 
     160...         model = Article 
     161 
     162OddForm is now an Article-related thing, because BadForm.Meta overrides 
     163CategoryForm.Meta. 
     164>>> OddForm.base_fields.keys() 
     165['headline', 'slug', 'pub_date', 'writer', 'article', 'status', 'categories'] 
    164166 
    165167>>> class ArticleForm(ModelForm): 
    166168...     class Meta: 
    167169...         model = Article 
     170 
     171First class with a Meta class wins. 
    168172 
    169173>>> class BadForm(ArticleForm, CategoryForm): 
    170174...     pass 
    171 Traceback (most recent call last): 
    172 ... 
    173 ImproperlyConfigured: BadForm's base classes define more than one model. 
    174  
    175 This one is OK since the subclass specifies the same model as the parent. 
    176  
    177 >>> class SubCategoryForm(CategoryForm): 
    178 ...     class Meta: 
    179 ...         model = Category 
    180  
     175>>> OddForm.base_fields.keys() 
     176['headline', 'slug', 'pub_date', 'writer', 'article', 'status', 'categories'] 
    181177 
    182178Subclassing without specifying a Meta on the class will use the parent's Meta 
     
    186182...     class Meta: 
    187183...         model = Category 
    188 ...         exclude = ['url'] 
    189184>>> class SubCategoryForm(CategoryForm): 
    190185...     pass 
     186>>> SubCategoryForm.base_fields.keys() 
     187['name', 'slug', 'url'] 
     188 
     189We can also subclass the Meta inner class to change the fields list. 
     190 
     191>>> class CategoryForm(ModelForm): 
     192...     checkbox = forms.BooleanField() 
     193... 
     194...     class Meta: 
     195...         model = Category 
     196>>> class SubCategoryForm(CategoryForm): 
     197...     class Meta(CategoryForm.Meta): 
     198...         exclude = ['url'] 
    191199 
    192200>>> print SubCategoryForm() 
    193201<tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr> 
    194202<tr><th><label for="id_slug">Slug:</label></th><td><input id="id_slug" type="text" name="slug" maxlength="20" /></td></tr> 
     203<tr><th><label for="id_checkbox">Checkbox:</label></th><td><input type="checkbox" name="checkbox" id="id_checkbox" /></td></tr> 
    195204 
    196205# Old form_for_x tests #######################################################