Custom validation in django newforms
Let's say that we have a form, with some form elements conditionally required. E.g., if you've picked a wg action in mail_type, then you must have picked a working group in group. Here's the form we're working with:
First, the simplest: list_to_close is required if mail_type is "closenon". So we make it required=False in the definition and add some field-specific validation in the field's clean function:
Note that the clean function has to return the cleaned value. In this case, we're not actually cleaning anything, just making our own 'conditionally required' validation.
When the action is an action for a working group, we first want to make the group list 'conditionally required', as above. However, we also want to check, if request is to close a list, that the list exists, or if the request is to create a list, that the list doesn't exist yet. So we fetch the count from the ImportedMailingList.objects, and depending on the action, we error if there are none or if there aren't none.
Once again, we have to end up returning the data from the clean function. (Note that in the real implementation, group is actually a ModelChoiceField, so using it in the filter provides the numeric ID, and using it in the ValidationError uses the str() representation.)
Since I'm just performing validation, none of my clean_ functions change the data that is returned, but that is quite reasonable - if you have a string that you want to trim leading and trailing spaces from, or do some other kind of normalization on, this is also a hook for that.
Note that in django SVN, the clean_data attribute has been renamed to cleaned_data, so any references have to be changed accordingly. If you're using any version past 0.96, be aware of this change.
class ListReqStep1(forms.Form):
DOMAIN_CHOICES = (
('ietf.org', 'ietf.org'),
('iab.org', 'iab.org'),
('irtf.org', 'irtf.org'),
)
mail_type = forms.ChoiceField(choices=(
('newwg', 'Create new WG email list at ietf.org'),
('movewg', 'Move existing WG email list to ietf.org'),
('closewg', 'Close existing WG email list at ietf.org'),
('newnon', 'Create new non-WG email list at selected domain above'),
('movenon', 'Move existing non-WG email list to selected domain above'),
('closenon', 'Close existing non-WG email list at selected domain above'),
), widget=forms.RadioSelect())
group = forms.ChoiceField(choices=..., required=False)
domain_name = forms.ChoiceField(choices=DOMAIN_CHOICES, required=False)
list_to_close = forms.ChoiceField(choices=..., required=False)
First, the simplest: list_to_close is required if mail_type is "closenon". So we make it required=False in the definition and add some field-specific validation in the field's clean function:
def clean_list_to_close(self):
if self.clean_data.get('mail_type', '') == 'closenon':
if not self.clean_data.get('list_to_close'):
raise forms.ValidationError, 'Please pick a list to close'
return self.clean_data['list_to_close']
Note that the clean function has to return the cleaned value. In this case, we're not actually cleaning anything, just making our own 'conditionally required' validation.
More complex validation
When the action is an action for a working group, we first want to make the group list 'conditionally required', as above. However, we also want to check, if request is to close a list, that the list exists, or if the request is to create a list, that the list doesn't exist yet. So we fetch the count from the ImportedMailingList.objects, and depending on the action, we error if there are none or if there aren't none.
def clean_group(self):
group = self.clean_data['group']
action = self.clean_data.get('mail_type', '')
if action.endswith('wg'):
if not self.clean_data.get('group'):
raise forms.ValidationError, 'Please pick a working group'
group_list_exists = ImportedMailingList.objects.filter(group_acronym=group).count()
if action.startswith('close'):
if group_list_exists == 0:
raise forms.ValidationError, 'The %s mailing list does not exist.' % group
else:
if group_list_exists:
raise forms.ValidationError, 'The %s mailing list already exists.' % group
return self.clean_data['group']
Once again, we have to end up returning the data from the clean function. (Note that in the real implementation, group is actually a ModelChoiceField, so using it in the filter provides the numeric ID, and using it in the ValidationError uses the str() representation.)
Since I'm just performing validation, none of my clean_ functions change the data that is returned, but that is quite reasonable - if you have a string that you want to trim leading and trailing spaces from, or do some other kind of normalization on, this is also a hook for that.
P.S.
Note that in django SVN, the clean_data attribute has been renamed to cleaned_data, so any references have to be changed accordingly. If you're using any version past 0.96, be aware of this change.

0 Comments:
Post a Comment
<< Home