Some things about the form fields already can be modified via the Meta class of the ModelForm, for example the widgets used to render the form fields. Unfortunately, setting of custom error messages via the Meta class does not seem to be possible.
So I implemented a small change to the ModelForm class, which allows you to set arbitrary field attributes via the Meta class. For that, I introduced a new Meta class field, called "field_args". It is used like this (note that we are deriving from a new base class, called ExtendedMetaModelForm):
class AuthorForm(ExtendedMetaModelForm): class Meta: model = Author field_args = { "first_name" : { "error_messages" : { "required" : "Please let us know what to call you!" } }, "notes" : { "+error_messages" : { "required" : "Please also enter some notes.", "invalid" : "This is not a valid note." }, "widget" : forms.Textarea(attrs={'cols': 70, 'rows': 15}), } }
As you can see, field_args is a dictionary of dictionaries. Each dictionary within field_args specifies the field attributes you wish to set for a given field. As you can see, you are not limited to the error messages. For instance, the "notes" field here receives a custom widget. Please note that the Meta class already allows you to set custom widgets. This example here is is just to show that you are free to use field_args to set any field attribute.
Another interesting feature is illustrated with the error messages for the "notes" field. As you can see we have a plus sign at the start of "+error_messages". This merely is used as an indicator that we wish to merge the specified attributes to an already existing attribute, rather than fully replace it. This only is supported if both the newly defined value and the already existing value for the named field attribute are dictionary types. The advantage of using the "+" notation is that you do not have to fully specify all error messages - for example - if you only wish to customize a few of them. In our example above, the error messages for the first_name field are fully replaced, while for the notes field they are merely modified or appended to.
The new ExtendedMetaModelForm can be used anywhere you traditionally would have used forms.ModelForm. The source code for this class is as follows:
class ExtendedMetaModelForm(forms.ModelForm): """ Allow the setting of any field attributes via the Meta class. """ def __init__(self, *args, **kwargs): """ Iterate over fields, set attributes from Meta.field_args. """ super(ExtendedMetaModelForm, self).__init__(*args, **kwargs) if hasattr(self.Meta, "field_args"): # Look at the field_args Meta class attribute to get # any (additional) attributes we should set for a field. field_args = self.Meta.field_args # Iterate over all fields... for fname, field in self.fields.items(): # Check if we have something for that field in field_args fargs = field_args.get(fname) if fargs: # Iterate over all attributes for a field that we # have specified in field_args for attr_name, attr_val in fargs.items(): if attr_name.startswith("+"): merge_attempt = True attr_name = attr_name[1:] else: merge_attempt = False orig_attr_val = getattr(field, attr_name, None) if orig_attr_val and merge_attempt and \ type(orig_attr_val) == dict and \ type(attr_val) == dict: # Merge dictionaries together orig_attr_val.update(attr_val) else: # Replace existing attribute setattr(field, attr_name, attr_val)
I hope this little code snippet here helps you to make your work with Django's model forms easier.
I have added your solution for the non-inheritance of error_messages to the modelforms into the django bugtrack (https://code.djangoproject.com/ticket/13693)
ReplyDeleteThat works nice for me.
Thank you for your solution.