Only validate certain fields if a BooleanField is set
Scenario: I'm building an order-form. Like every other order-form on the planet, it has separate invoicing shipping addresses. I've just added a "Use billing address" checkbox to let the user save time.
The problem is, the shipping fields are still there. They will fail validation if the user don't enter any shi开发者_如何学编程pping address data (like if they want to use the billing address).
What I think I'd like to do override the ModelForm validation for these duplicate fields. In there, if the box is checked (not sure how I get that data from within a validator), I return the billing version. If it's not checked, I pass it back to the original validation.
Sounds like a plan doesn't it? Well I fell at the first hurdle. My clean_functions
aren't working. Doesn't look like they're even being called.
Here's some code:
# shipping_street is a field in my Order Model
class OrderForm(ModelForm):
class Meta:
model = Order
def clean_shipping_street(self):
print "JUST GET ME SOME OUTPUT!!!"
raise forms.ValidationError('RAWRAWR')
Here's how I'm testing:
def checkout(request):
of = OrderForm()
if request.method == "POST":
of = OrderForm(request.POST)
print 'Form valid:', of.is_valid()
# ...
# return my HttpResponse with 'of' in the context.
I'm not sure if I was just being a clutz but the following worked (and answers my whole question):
class OrderForm(ModelForm): class Meta: model = Order
def clean_shipping_street(self):
print 'VALIDATING!!! YEY!'
if self.cleaned_data['ship_to_billing']:
return self.clean_billing_street()
return super(OrderForm, self).clean_shipping_street()
But if you think I'm going about this the wrong way, please let me know!
As Nick points out below, cleaned_data isn't filled in a guaranteed order, meaning ship_to_billing
might not exist when clean_shipping_street()
is called. The way around this is to call the clean_shipping_street()
method instead accessing cleaned_data
.
def clean_shipping_street(self):
print 'VALIDATING!!! YEY!'
if self.clean_ship_to_billing():
return self.clean_billing_street()
return super(OrderForm, self).clean_shipping_street()
If you weren't as lazy as I was when I wrote the code, you might want to avoid so many duplicate validations of the boolean field. This should be faster (provided the default field isn't run unless it's needed - not sure on that myself):
def clean_shipping_street(self):
print 'VALIDATING!!! YEY!'
if self.cleaned_data.get('ship_to_billing', self.clean_ship_to_billing):
return self.clean_billing_street()
return super(OrderForm, self).clean_shipping_street()
OR even better than that:
def clean_shipping_street(self):
if not self.cleaned_data.has_key['ship_to_billing']:
self.cleaned_data['ship_to_billing'] = self.clean_ship_to_billing()
if self.cleaned_data['ship_to_billing']:
return self.clean_billing_street()
return super(OrderForm, self).clean_shipping_street()
It's only slightly different but it should mean clean_ship_to_billing() gets called a lot less than my previous efforts. But seriously, I doubt you could even detect these "improvements" in a profiling session.
There were several problems with my last answer. Copied data wasn't rendered back into the form (might be something you want, I do) and it was a little unreliable.
Here is what I'm using now. Instead of adding dozens of clean_field_name()
definitions, I have one just on the BooleanField
:
def clean_ship_to_billing(self):
if self.cleaned_data.get('ship_to_billing', False):
data = self.data.copy()
for f in ['street', 'street_2', 'post_code', 'city', 'county', 'country', ]:
data['shipping_%s' % f] = data['billing_%s' % f]
self.data = data
If checked, it copies the raw data across for billing fields into shipping fields. It's important that the field is before the shipping fields in the model's (or form's) field order.
And I'm copying self.data because the POST data is immutable.
精彩评论