开发者

How do I get my Address ModelForm not to raise an exception when a field is left blank?

Here's the form:

class AddressForm(ModelForm):
    postal_code = CharField()
    city = CharField()
    province = CharField()
    country = CharField()

    def clean_postal_code(self):
        postal_code = self.cleaned_data['postal_code'].strip().upper()
        if not re.match(r'^[A-Z][0-9][A-Z]\s?[0-9][A-Z][0-9]$', postal_code):
            raise ValidationError('Invalid postal code.')
        return ' '.join((postal_code[:3], postal_code[-3:]))

    def clean(self):
        data = self.cleaned_data

        if 'country' in data:
            data['country'] = Country.objects.get_or_create(name=data['country'])[0]
            if 'province' in data:
                data['province'] = Province.objects.get_or_create(name=data['province'], country=data['country'])[0]
                if 'city' in data:
                    data['city'] = City.objects.get_or_create(name=data['city'], province=data['province'])[0]
                    if 'postal_code' in data:
                        data['postal_code'] = PostalCode.objects.get_or_create(code=data['postal_code'], city=data['city'])[0]

        return data

    开发者_StackOverflowclass Meta:
        model = Address

Everything is normalized into separate tables, but I don't want to use <select> boxes because there could be hundreds of thousands of entries. So instead, I'm using CharFields and then I'll use auto-complete with it.

Problem is if something like the country field is left blank, then that whole chunk of code inside clean() never gets ran, and then the fields never get converted to their appropriate types. Thus, I get an exception like this:

Cannot assign "u'A1B 2C3'": "Address.postal_code" must be a "PostalCode" instance.

I was hoping that since the clean() process technically didn't pass (country is a required field) it wouldn't check for that. Now I don't know how to avoid it.

Suggestions?


Wow! This is a surprisingly difficult problem. I must have hit 10 road blocks.

if you don't need unique together validation (which you're not doing in your modelform anyways): I think the easiest solution is to exclude your model fields, override the save, and manually apply the cleaned data to self.instance. Let the regular form validation do the cleaning.

skip_model_validation_fields =  \
    ('country','province','city','postal_code')

def save(self, *args, **kwargs):
    for field in self.skip_model_validation_fields:
        setattr(self.instance, field, self.cleaned_data.get(field))

    return super(MyForm, self).save(*args, **kwargs)

class Meta:
    model = Address
    exclude = self.skip_model_validation_fields

If you need unique_together you could manually run self.unique_together() but at that point you would also need to capture the error, re-display form, etc...

So maybe a new suggestion is to use forms instead of hackishly dealing with ModelForm. Sounds like the aid is no longer an aid!


The problem is that even if you raise forms.ValidationError inside clean() an instance is created and fields from cleaned_data are set to that instance regardless.

Moving your validation to per-field would work but will be a pain because your validation has a hierarchy and you'd need to define the hierarchy in each fields clean method.

PostalCode depends on City, City depends on Province... etc or else risk running into the same error

You could delete items directly from cleaned_data in your clean() function but that too will be a royal pain for the same reason above.

I learned a ton about ModelForms today...

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜