django form validation based on whether field has value

浪尽此生 提交于 2019-12-08 12:18:30

问题


  1. When I submit an empty form with a phone_type selected (for formHomePhone) the form returns its self without a value selected in phone_type stipulating This field is required

  2. 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.

  1. Overwrite is_valid() to make sure blank values can get through
  2. Create pre-save() to load in User and Client into the model and abort if you can if there is no value for number

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!