проверка формы django на основе значения поля - PullRequest
0 голосов
/ 18 января 2011
  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))

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

Кстати (моя функция new_client_view не очень СУХАЯ, я знаю. Любые рекомендации будут с благодарностью приняты)

Ответы [ 2 ]

8 голосов
/ 18 января 2011

Если вы хотите, чтобы поля ModelForm не проверялись, когда они пусты, установите пустое = True, null = True в определении модели.

Если вы не хотите менять модель, вы можете переопределить атрибут required в ModelForm (см. Ниже), но имейте в виду, что ваш метод form.save () может попытаться оставить нулевые значения, если БД ожидает ненулевые значения.

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 Вот совершенно другой подход, учитывая, что комментарии ОП, по-видимому, подразумевают, что валидация между формами может быть тем, к чему он стремится:

Если вы хотите, чтобы одна из ваших текущих форм вела себя определенным образом в зависимости от того, что находится в другой форме, вы говорите о перекрестной валидации, которая невозможна, так как методы очистки форм не всегда легко знать о каких-либо других. формы обрабатываются этим представлением.

Вместо этого я бы порекомендовал создать новый класс forms.Form, в котором есть все поля моделей, с которыми вы хотите работать, но который не является ModelForm. Затем вы можете добавить в эту форму метод save() (или process() или любой другой), который будет выполнять то фанданго, которое у вас есть на данный момент. Это также может сделать ваш код немного аккуратнее.

Вам нужно будет передать 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
0 голосов
/ 18 января 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, поэтому я не выдаю ошибку для пустого form), а также функцию pre-save в форме, которая принимает объекты user и client и помещает их в поля и проверяет наличие номера телефона перед сохранением.

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

ЭТО СУМАСШЕДШЕЕ !!!

Единственное, о чем я могу думать, это сделать phone_type и number в ClientPhone модели blank=True, null=True, чтобы у меня могли быть дополнительные поля телефона

ЭТО БЕЗУМНО КАК ХОРОШО !!!

Почему я должен нарушать целостность моей модели, чтобы иметь поле опции в одной из моих форм ???

EDIT:

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