【问题标题】:Django ModelChoiceField not raising ValidationError for invalid choiceDjango ModelChoiceField 没有为无效选择引发 ValidationError
【发布时间】:2013-02-02 11:35:50
【问题描述】:

我正在为 ModelForm 开发单元测试,当我传入一个在为字段定义的查询集中不可选择的值时,我发现表单上的 ModelChoiceField 没有引发 invalid_choice ValidationError。

我相信原因是 ModelChoiceField 的 to_python() 没有被首先调用,正如https://docs.djangoproject.com/en/1.4/ref/forms/validation/#form-and-field-validation 的文档所说的那样,或者 to_python() 在调用表单和模型的 clean 方法之前没有捕捉到 DoesNotExist 异常,后者其中导致以下未处理的异常:

# Test Results       
E
======================================================================
ERROR: test_clean (tickets.tests.PaymentFormTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/anand/Ubuntu One/paytickets/park_db/tickets/tests.py", line 187, in test_clean
    self.assertFalse(form.is_valid())
  File "/usr/local/lib/python2.7/dist-packages/django/forms/forms.py", line 124, in is_valid
    return self.is_bound and not bool(self.errors)
  File "/usr/local/lib/python2.7/dist-packages/django/forms/forms.py", line 115, in _get_errors
    self.full_clean()
  File "/usr/local/lib/python2.7/dist-packages/django/forms/forms.py", line 272, in full_clean
    self._post_clean()
  File "/usr/local/lib/python2.7/dist-packages/django/forms/models.py", line 332, in _post_clean
    self.instance.clean()
  File "/home/anand/Ubuntu One/paytickets/park_db/tickets/models.py", line 133, in clean
    fine = self.ticket.fine_amount
  File "/usr/local/lib/python2.7/dist-packages/django/db/models/fields/related.py", line 343, in __get__
    raise self.field.rel.to.DoesNotExist
DoesNotExist

现在,当我注释掉模型的 clean 方法时,表单的行为与预期一样,并引发了 invalid_choice 错误;这告诉我模型的 clean 方法在 to_python 方法之前被调用。

请帮助我了解这里发生了什么。我怎样才能使这个测试工作?我正在使用 Django 1.4.3。我的代码如下:

# models.py
class Payment(TimeStampedModel): # TimeStampedModel is an abstract base class

    user = models.ForeignKey(User, editable=False)
    ticket = models.ForeignKey('Ticket', to_field='number')
    payment_amount = models.DecimalField(max_digits=7, decimal_places=2)

    def clean(self):

        fine = self.ticket.fine_amount
        payment_sum = self.ticket.sum_payments()
        remaining = fine - payment_sum

        if self.payment_amount < 1:
            msg = 'The minimum payment is $1.'
            raise ValidationError(msg)            
        elif remaining == 0:
            msg = 'That ticket has already been fully paid.'
            raise ValidationError(msg)
        elif remaining < self.payment_amount:
            msg = ('You cannot pay more than the oustanding fine: $%0.2f' %
                   remaining)
            raise ValidationError(msg) 

    def __unicode__(self):
        return u'%s; %s; %s' % (self.created, self.ticket, self.payment_amount)


# forms.py
class PaymentForm(forms.ModelForm):

    class Meta:
        model = Payment

    def __init__(self, user=None, *args, **kwargs):
        super(PaymentForm, self).__init__(*args, **kwargs)

        # passing in user and performing query for ModelChoiceField options
        self._user = user                
        vehicles = self._user.vehicle_set.all().prefetch_related('ticket_set')

        ticket_list= []
        for i in vehicles:
            for j in i.ticket_set.all():
                ticket_list.append(j.pk)

        self.ticket_queryset = Ticket.objects.filter(pk__in=ticket_list)        
        self.fields['ticket'].queryset = self.ticket_queryset

    def save(self, commit=True):
        instance = super(PaymentForm, self).save(commit=False)
        instance.user = self._user
        if commit:
            instance.save()
        return instance


# tests.py
class PaymentFormTests(TestCase):

    fixtures = ['test_users.json', 'tickets_app.json']

    def test_clean(self):

        user = User.objects.get(username="test_external_active")

        form_data = {
            "ticket": 1000007, # this is not a valid ticket number
            "payment_amount": 1.00
            }

        form = PaymentForm(user=user, data=form_data)
        self.assertFalse(form.is_valid())

【问题讨论】:

    标签: django django-forms


    【解决方案1】:

    您需要将该要求添加到您的代码中。好好阅读docs on form and field validation

    您可以在很多地方添加验证(例如,在模型上、在表单中的模型字段上等)。我建议您按照here 的描述将您的验证添加到表单字段中。

    类似的东西-

    def clean_ticket(self):
        # get your valid ticket queryset
        if ticket not in valid_ticket_queryset:
            raise forms.ValidationError("You have added an invalid ticket!")
        return data
    

    【讨论】:

    • 所以我只是尝试了这个,它仍然给出了同样的错误。但是,再次阅读文档后,我认为问题在于表单和模型清理方法始终运行,即使验证错误是由字段级验证/清理触发的。所以我所做的是在我的模型级清理中捕获异常并为其引发验证错误。现在,如果用户在发布之前在客户端修改表单,这对于防止用户因 500 错误而被发送垃圾邮件非常有用。
    猜你喜欢
    • 2013-09-28
    • 1970-01-01
    • 2021-11-26
    • 2018-12-27
    • 1970-01-01
    • 2017-04-04
    • 1970-01-01
    • 2017-04-21
    • 1970-01-01
    相关资源
    最近更新 更多