问题
When I submit an empty form with a
phone_type
selected (forformHomePhone
) the form returns its self without a value selected inphone_type
stipulatingThis field is required
As you can see from the view The first phone number in the form is required but the other phone numbers are not. I only want to process them if there is a value present. Though when I click submit on an empty form the additional phone number fields throw up an error from the
UKPhoneNumberField
>Phone number must include an area code.
How can I only validate when there is a number in the respected field?
I have a view.py file like this
def new_client_view(request):
if request.method == 'POST':
formDetails = ClientDetailsForm(request.POST)
formAddress = ClientAddressForm(request.POST)
formHomePhone = ClientPhoneForm(request.POST)
formWorkPhone = ClientOtherPhoneForm(request.POST)
formMobilePhone = ClientOtherPhoneForm(request.POST)
if formDetails.is_valid() and formAddress.is_valid() and formHomePhone.is_valid():
c = Client()
c.save()
fd = formDetails.save(commit=False)
fd.client = c
fd.created_by = request.user
fd.save()
fa = formAddress.save(commit=False)
fa.client = c
fa.created_by = request.user
fa.save()
fph = formHomePhone.save(commit=False)
fph.client = c
fph.created_by = request.user
fph.save()
if 'p2-number' in request.POST and request.POST['p2-number']:
fpw = formWorkPhone.save(commit=False)
fpw.client = c.id
fpw.created_by = request.user
if fpw.is_valid():
fpw.save()
if 'p3-number' in request.POST and request.POST['p3-number']:
fpm = formMobilePhone.save(commit=False)
fpm.client = c
fpm.created_by = request.user
if fpm.is_valid():
fpm.save()
return render_to_response('client/client.html', context_instance=RequestContext(request))
else:
return render_to_response('client/new_client.html', {'formDetails': formDetails, 'formAddress': formAddress, 'formHomePhone': formHomePhone, 'formWorkPhone': formWorkPhone, 'formMobilePhone': formMobilePhone}, context_instance=RequestContext(request))
else:
formAddress = ClientAddressForm()
formDetails = ClientDetailsForm()
formHomePhone = ClientPhoneForm(initial={'phone_type':'home'}, prefix="p1")
formWorkPhone = ClientPhoneForm(initial={'phone_type':'work'}, prefix="p2")
formMobilePhone = ClientPhoneForm(initial={'phone_type':'mobi'}, prefix="p3")
return render_to_response('client/new_client.html', {'formDetails': formDetails, 'formAddress': formAddress, 'formHomePhone': formHomePhone, 'formWorkPhone': formWorkPhone, 'formMobilePhone': formMobilePhone}, context_instance=RequestContext(request))
a forms.py like this:
class ClientDetailsForm(ModelForm):
class Meta:
model = ClientDetails
exclude = ('client', 'created', 'created_by')
class ClientAddressForm(ModelForm):
class Meta:
model = ClientAddress
exclude = ('client', 'created', 'created_by')
class ClientPhoneForm(ModelForm):
number = UKPhoneNumberField()
class Meta:
model = ClientPhone
exclude = ('client', 'created', 'created_by')
class ClientOtherPhoneForm(ModelForm):
number = UKPhoneNumberField(required=False)
class Meta:
model = ClientPhone
exclude = ('client', 'created', 'created_by')
and a models.py like this:
MARITAL_STATUS_CHOICES = (
...
)
NAME_TITLE_CHOICES = (
...
)
PHONE_CHOICES = (
('home', 'Home'),
('home2', 'Home 2'),
('mobi', 'Mobile'),
('mobi2', 'Mobile 2'),
('work', 'Work'),
('work2', 'Work 2'),
)
class Client(models.Model):
closed = models.DateTimeField(blank=True, null=True)
closed_by = models.ForeignKey(User, blank=True, null=True)
comment = models.TextField(blank=True, null=True)
def __unicode__(self):
return u'%s' % (self.id)
class ClientDetails(models.Model):
...
class ClientAddress(models.Model):
...
class ClientPhone(models.Model):
client = models.ForeignKey(Client, null=True)
created = models.DateTimeField(default=datetime.now)
created_by = models.ForeignKey(User, blank=True, null=True)
phone_type = models.CharField(max_length=5, choices=PHONE_CHOICES)
number = models.CharField(max_length=24)
BTW(my new_client_view
function is not very DRY I know. Any recommendations would be gratefully received)
回答1:
If you want to stop ModelForm fields being validated when they're empty, set blank=True, null=True in the model definition.
If you don't want to change the model, you can override the required
attr in the ModelForm (see below), but bear in mind then that your form.save() method may try to leave null values where the DB expects non-nulls.
class FooForm(forms.ModelForm):
class Meta:
#exclude etc as you wish
def __init__(self, *args, **kwargs):
#init the form as usual
super(FooForm, self).__init__(*args, **kwargs)
#then change the required status on the fields:
self.fields['baz'].required = False
UPDATE Here's a totally different approach, given that the OP's comments seem to imply that validation between forms may be what he's after:
If you want one of your current forms to behave a certain way based on what's in another form, you're talking cross-form validation, which isn't possible as the cleaning methods of forms are not easily aware of any other forms being handled by that view.
Instead, I'd recommend making a new forms.Form class that has all the fields of the models you want to deal with, but which is not a ModelForm. You can then add a save()
(or process()
or whatever) method to that Form that will do the fandango you currently have in your view. That may make your code slightly neater, too.
You'll need to pass in the request.user into the form as an argument (a kwarg is easier to get right) so that you can set the appropriate user on the models you're saving.
But of course, let's not forget about the validation, which I recommend you do in the main clean() method of that form, which will have access to all the fields you need to juggle.
So, as some quick untested code:
from django import forms
from django.forms.util import ErrorList
class ClientForm(forms.Form):
## define your fields here ##
home_phone = forms.CharField(required=False)
# etc etc
def __init__(self, *args, **kwargs):
"""
NB: in the view you'll need to call the form's constructor with
keyword arguments for client (the client whose details are being
edited) and user (ie: request.user)
"""
#before the form is initialised, grab the two extra objects we'll
#need in save()
self.client = kwargs.pop('client')
#NB:this will explode if there is no client kwarg present
self.user = kwargs.pop('user') #ditto, for a 'user' argument
#now carry on with the standard form initialisation
super(ClientForm, self).__init__(*args, **kwargs)
def clean(self):
#probe your cleaned data here and enforce any cross-field dependencies you wish
if self.cleaned_data.get('foo') and not self.cleaned_data.get('bar'):
self._errors['foo'] = ErrorList(["A Foo should only be present if there\'s not a Bar"])
# NB: only override a value in an _errors dict if you're sure there is
# nothing in there already (ie: the foo field has been cleaned and is
# present in cleaned_data)
# Otherwise, you should append() a string to the ErrorList in question
return self.cleaned_data
def save(self):
#what you have in the view is doing a lot of the legwork, so extend that
#code here to get or create the relevant ClientHomePhone etc models
#based on lookups related to self.client, which we set in __init__() above
#also, remember you'll have access to self.user which is the user who is
#creating/editing so you can set this for created_by
return self.client # or some other relevant entity, if you wish
回答2:
I found an answer that goes something like this (yes I know it's stupid!)
def new_client_view(request):
if request.method == 'POST':
formDetails = ClientDetailsForm(request.POST)
formAddress = ClientAddressForm(request.POST)
formHomePhone = ClientPhoneForm(request.POST)
if formDetails.is_valid() and formAddress.is_valid() and formHomePhone.is_valid():
c = Client()
c.save()
fd = formDetails.save(commit=False)
fd.client = c
fd.created_by = request.user
fd.save()
fa = formAddress.save(commit=False)
fa.client = c
fa.created_by = request.user
fa.save()
fph = formHomePhone.save(commit=False)
fph.client = c
fph.created_by = request.user
fph.save()
if 'p2-number' in request.POST and request.POST['p2-number']:
formWorkPhone = ClientOtherPhoneForm(request.POST)
fpw = formWorkPhone.save(commit=False)
fpw.client = c.id
fpw.created_by = request.user
# if the work number is valid
if fpw.is_valid():
# check the mobile number before you save the work number
if 'p3-number' in request.POST and request.POST['p3-number']:
formMobilePhone = ClientOtherPhoneForm(request.POST)
fpm = formMobilePhone.save(commit=False)
fpm.client = c
fpm.created_by = request.user
# if the mobile number is good then there is no more checking to do
if fpm.is_valid():
fpm.save()
# else load up the form again with the error for the mobile phone and the original
# data for the mobile phone and the work phone
else:
formWorkPhone = ClientOtherPhoneForm(request.POST)
return render_to_response('client/new_client.html', {'formDetails': formDetails, 'formAddress': formAddress, 'formHomePhone': formHomePhone, 'formWorkPhone': formWorkPhone, 'formMobilePhone': formMobilePhone}, context_instance=RequestContext(request))
# if the work number is valid and the mobile field wasn't used then just save the work number
fpw.save()
# if the work number is not valid then find out if a mobile number
# was entered and feed it back to the form
else:
if 'p3-number' in request.POST and request.POST['p3-number']:
formMobilePhone = ClientOtherPhoneForm(request.POST)
else:
formMobilePhone = ClientPhoneForm(initial={'phone_type':'mobi'}, prefix="p3")
return render_to_response('client/new_client.html', {'formDetails': formDetails, 'formAddress': formAddress, 'formHomePhone': formHomePhone, 'formWorkPhone': formWorkPhone, 'formMobilePhone': formMobilePhone}, context_instance=RequestContext(request))
return render_to_response('client/client.html', context_instance=RequestContext(request))
# if the form doesn't validate then you need to find out if the optional fields contain data,
# and if so load that data back into the form
else:
if 'p2-number' in request.POST and request.POST['p2-number']:
formWorkPhone = ClientOtherPhoneForm(request.POST, prefix="p2")
else:
formWorkPhone = ClientPhoneForm(initial={'phone_type':'work'}, prefix="p2")
if 'p3-number' in request.POST and request.POST['p3-number']:
formMobilePhone = ClientOtherPhoneForm(request.POST, prefix="p3")
else:
formMobilePhone = ClientPhoneForm(initial={'phone_type':'mobi'}, prefix="p3")
return render_to_response('client/new_client.html', {'formDetails': formDetails, 'formAddress': formAddress, 'formHomePhone': formHomePhone, 'formWorkPhone': formWorkPhone, 'formMobilePhone': formMobilePhone}, context_instance=RequestContext(request))
else:
formAddress = ClientAddressForm()
formDetails = ClientDetailsForm()
formHomePhone = ClientPhoneForm(initial={'phone_type':'home'}, prefix="p1")
formWorkPhone = ClientPhoneForm(initial={'phone_type':'work'}, prefix="p2")
formMobilePhone = ClientPhoneForm(initial={'phone_type':'mobi'}, prefix="p3")
return render_to_response('client/new_client.html', {'formDetails': formDetails, 'formAddress': formAddress, 'formHomePhone': formHomePhone, 'formWorkPhone': formWorkPhone, 'formMobilePhone': formMobilePhone}, context_instance=RequestContext(request))
I think I need to overwrite the is_valid
function on the form to make sure there is data in one of the fields before doing something like super.is_valid()
(else returning true so I don't raise an error for an empty form) and also a pre-save
function on the form that takes the user
and client
objects and puts them into the fields and checks there is a phone number before saving.
- Overwrite
is_valid()
to make sure blank values can get through - Create
pre-save()
to load inUser
andClient
into the model and abort if you can if there is no value fornumber
THIS IS CRAZY!!!
The only other thing I can think of is making phone_type
and number
in the ClientPhone
model blank=True, null=True
so that I can have optional phone fields
THATS CRAZY AS WELL!!!
Why should I have to break the integrity of my model just to have an option field in one of my forms???
EDIT:
view.py turned out cleaner when I did the following to the forms.py file:
class ClientDetailsForm(ModelForm):
class Meta:
model = ClientDetails
exclude = ('client', 'created', 'created_by')
def CustomSave(self,c,u):
t = self.save(commit=False)
t.client = c
t.created_by = u
t.first_name = self.cleaned_data['first_name'].capitalize()
t.middle_name = self.cleaned_data['first_name'].capitalize()
t.last_name = self.cleaned_data['first_name'].capitalize()
t.save()
class ClientAddressForm(ModelForm):
class Meta:
model = ClientAddress
exclude = ('client', 'created', 'created_by')
def CustomSave(self,c,u):
t = self.save(commit=False)
t.client = c
t.created_by = u
t.address_1 = self.cleaned_data['address_1'].capitalize()
t.address_2 = self.cleaned_data['address_2'].capitalize()
t.address_3 = self.cleaned_data['address_3'].capitalize()
t.address_4 = self.cleaned_data['address_4'].capitalize()
t.post_code = self.cleaned_data['post_code'].upper()
t.save()
class ClientPhoneForm(ModelForm):
number = UKPhoneNumberField()
class Meta:
model = ClientPhone
exclude = ('client', 'created', 'created_by')
def CustomSave(self,c,u):
t = self.save(commit=False)
t.client = c
t.created_by = u
t.save()
class ClientOtherPhoneForm(ModelForm):
number = UKPhoneNumberField(required=False)
class Meta:
model = ClientPhone
exclude = ('client', 'created', 'created_by')
def CustomSave(self,c,u):
t = self.save(commit=False)
t.client = c
t.created_by = u
t.save()
来源:https://stackoverflow.com/questions/4723315/django-form-validation-based-on-whether-field-has-value