Changeset 7115
- Timestamp:
- 02/14/08 11:38:05 (5 months ago)
- Files:
-
- django/trunk/django/newforms/forms.py (modified) (2 diffs)
- django/trunk/django/newforms/models.py (modified) (2 diffs)
- django/trunk/docs/modelforms.txt (modified) (2 diffs)
- django/trunk/tests/modeltests/model_forms/models.py (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
django/trunk/django/newforms/forms.py
r7112 r7115 23 23 return name.replace('_', ' ') 24 24 25 def get_declared_fields(bases, attrs): 25 def 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 """ 26 35 fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] 27 36 fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) … … 30 39 # Note that we loop over the bases in *reverse*. This is necessary in 31 40 # 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 35 49 36 50 return SortedDict(fields) django/trunk/django/newforms/models.py
r7112 r7115 226 226 227 227 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) 229 229 opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None)) 230 230 if opts.model: … … 237 237 else: 238 238 fields = declared_fields 239 new_class.declared_fields = declared_fields 239 240 new_class.base_fields = fields 240 241 # XXX: The following is a sanity check for the user to avoid242 # inadvertent attribute hiding.243 244 # Search base classes, but don't allow more than one Meta model245 # definition. The fields would be generated correctly, but the save246 # method won't deal with more than one object. Also, it wouldn't be247 # clear what to do with multiple fields and exclude lists.248 first = None249 current = opts.model250 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_model259 260 241 return new_class 261 242 django/trunk/docs/modelforms.txt
r7112 r7115 336 336 is some extra validation and cleaning for the ``pub_date`` field. 337 337 338 You can also subclass the parent's ``Meta`` inner class if you want to change 339 the ``Meta.fields`` or ``Meta.excludes`` lists:: 340 341 >>> class RestrictedArticleForm(EnhancedArticleForm): 342 ... class Meta(ArticleForm.Meta): 343 ... exclude = ['body'] 344 345 This adds in the extra method from the ``EnhancedArticleForm`` and modifies 346 the original ``ArticleForm.Meta`` to remove one field. 347 338 348 There are a couple of things to note, however. Most of these won't normally be 339 349 of concern unless you are trying to do something tricky with subclassing. 340 341 * All the fields from the parent classes will appear in the child342 ``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 is344 already part of the field list in the parent class.345 350 346 351 * Normal Python name resolution rules apply. If you have multiple base … … 352 357 both a ``ModelForm`` and a ``Form`` simultaneously. 353 358 354 Because of the "child inherits all fields from parents" behaviour, you355 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 inheritance357 to add "extra" non-model fields and methods to the final result. Whether you358 put the "extra" functions in the parent class or the child class will depend359 on how you intend to reuse them.360 django/trunk/tests/modeltests/model_forms/models.py
r7112 r7115 156 156 ... model = Category 157 157 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 162 OddForm is now an Article-related thing, because BadForm.Meta overrides 163 CategoryForm.Meta. 164 >>> OddForm.base_fields.keys() 165 ['headline', 'slug', 'pub_date', 'writer', 'article', 'status', 'categories'] 164 166 165 167 >>> class ArticleForm(ModelForm): 166 168 ... class Meta: 167 169 ... model = Article 170 171 First class with a Meta class wins. 168 172 169 173 >>> class BadForm(ArticleForm, CategoryForm): 170 174 ... 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'] 181 177 182 178 Subclassing without specifying a Meta on the class will use the parent's Meta … … 186 182 ... class Meta: 187 183 ... model = Category 188 ... exclude = ['url']189 184 >>> class SubCategoryForm(CategoryForm): 190 185 ... pass 186 >>> SubCategoryForm.base_fields.keys() 187 ['name', 'slug', 'url'] 188 189 We 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'] 191 199 192 200 >>> print SubCategoryForm() 193 201 <tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr> 194 202 <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> 195 204 196 205 # Old form_for_x tests #######################################################
