валидиране на django форма въз основа на това дали полето има стойност

  1. Когато изпратя празен формуляр с избрано phone_type (за formHomePhone), формулярът се връща без избрана стойност в phone_type, определяща This field is required

  2. Както можете да видите от изгледа, първият телефонен номер във формуляра е задължителен, но другите телефонни номера не са. Искам да ги обработя само ако има налична стойност. Въпреки че, когато щракна върху изпращане на празен формуляр, допълнителните полета за телефонен номер извеждат грешка от UKPhoneNumberField > Phone number must include an area code. Как мога да проверя само когато има номер в съответното поле?

Имам файл view.py като този

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))

forms.py като този:

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')

и models.py като този:

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 (моята функция new_client_view не е много СУХА, знам. Всякакви препоръки ще бъдат получени с благодарност)


person Robert Johnstone    schedule 18.01.2011    source източник


Отговори (2)


Ако искате да спрете валидирането на полетата на ModelForm, когато са празни, задайте blank=True, null=True в дефиницията на модела.

Ако не искате да промените модела, можете да замените required attr в ModelForm (вижте по-долу), но имайте предвид, че вашият метод form.save() може да се опита да остави нулеви стойности там, където DB очаква не- нули.

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

АКТУАЛИЗАЦИЯ Ето един напълно различен подход, като се има предвид, че коментарите на OP изглежда предполагат, че валидирането между формулярите може да е това, което той търси:

Ако искате един от вашите текущи формуляри да се държи по определен начин въз основа на това, което е в друг формуляр, вие говорите за кръстосано валидиране на формуляри, което не е възможно, тъй като методите за почистване на формуляри не могат лесно да разпознаят каквито и да било други формуляри, които се обработват от тази гледна точка.

Вместо това бих препоръчал да направите нов клас forms.Form, който има всички полета на моделите, с които искате да работите, но който не е ModelForm. След това можете да добавите save() (или process() или какъвто и да е) метод към този формуляр, който ще направи fandango, което в момента имате във вашия изглед. Това също може да направи кода ви малко по-изчистен.

Ще трябва да предадете request.user във формуляра като аргумент (kwarg е по-лесен за правилен), така че да можете да зададете подходящия потребител на моделите, които запазвате.

Но, разбира се, нека не забравяме за валидирането, което препоръчвам да направите в основния метод clean() на този формуляр, който ще има достъп до всички полета, които трябва да жонглирате.

И така, като бърз нетестван код:

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
person Steve Jalim    schedule 18.01.2011
comment
Бих искал да направя това на базата на формуляр, а не на база модел, ако е възможно - person Robert Johnstone; 18.01.2011
comment
вижте моя редактиран отговор по-горе. Освен това имам обяснение в задния си джоб за кръстосано валидиране, но не мога да разбера дали това е, което всъщност искате вместо това - person Steve Jalim; 18.01.2011
comment
Благодаря за функцията, но наистина се надявах на малко логика, която ще влезе в класа на изгледа › new_client_view, която ще извърши проверка САМО и ще запази телефонен номер, ако има нещо в полето number на първо място - person Robert Johnstone; 18.01.2011
comment
Валидирането на формуляра се извършва във формуляра, а не в изгледа. Актуализирах отговора си с друг, по-дълъг подход, който може или не може да искате да използвате - person Steve Jalim; 18.01.2011
comment
Благодаря, че отделихте време да обясните това. Просто съм изненадан, че не можете да имате if data in form: if from.is_valid: form.save() във функцията за изглед. Не можете да потвърдите данни, ако потребителят не ги е въвел! - person Robert Johnstone; 18.01.2011

Намерих отговор, който звучи нещо подобно (да, знам, че е глупаво!)

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))

Мисля, че трябва да презапиша функцията is_valid във формуляра, за да се уверя, че има данни в едно от полетата, преди да направя нещо като super.is_valid() (в противен случай връщане на true, за да не повдигам грешка за празен формуляр), а също и функция pre-save във формуляра, който взема обектите user и client и ги поставя в полетата и проверява дали има телефонен номер, преди да запази.

  1. Презапишете is_valid(), за да сте сигурни, че празните стойности могат да преминат
  2. Създайте pre-save() за зареждане в User и Client в модела и прекратете, ако можете, ако няма стойност за number

ТОВА Е ЛУДОСТ!!!

Единственото друго нещо, за което се сещам, е да направя phone_type и number в модела ClientPhone blank=True, null=True, за да мога да имам незадължителни телефонни полета

ТОВА СЪЩО Е ЛУДО!!!

Защо трябва да нарушавам целостта на модела си, само за да имам поле за опция в един от формулярите си???

РЕДАКТИРАНЕ:

view.py се оказа по-чист, когато направих следното с файла forms.py:

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()
person Robert Johnstone    schedule 18.01.2011