如何在使用内联时验证django-admin中两个模型的数据?

时间:2011-07-08 17:54:53

标签: django django-models django-admin

更新:直接读取django源代码我得到了一个未记录的缺失部分来解决我的问题。感谢布兰登通过给我一个缺失的部分解决了一半的问题。看看我自己的答案,看看我的解决方案(我不想在这里混合一些东西)。

我有以下(简化)模型:

Order(models.Model):
    status = models.CharField( max_length=25, choices=STATUS_CHOICES, default='PENDING')
    total = models.DecimalField( max_digits=22, decimal_places=2)

    def clean(self):
        if self.estatus == 'PAID' or self.estatus == 'SENT':
            if len(self.payment.all()) > 0:
                raise ValidationError("The status cannot be SENT or PAID if there is no payment for the order")

Payment(models.Model):
    amount = models.DecimalField( max_digits=22, decimal_places=2 )
    order  = models.ForeignKey(Order, related_name="payment")

    def clean(self):
        if self.amount < self.order.total or self.amount <= 0:
            ValidationError("The payment cannot be less than the order total")

在我的admin.py中我有:

class paymentInline(admin.StackedInline):
    model   = Payment
    max_num = 1

class OrderAdmin(admin.ModelAdmin):
    model   = Order
    inlines = [ paymentInline, ]

订单的清洁方法中的验证不起作用,因为验证发生时没有保存付款(显然它尚未保存到数据库中)。

付款内的验证工作正常(如果编辑或添加新付款)。

我想验证订单是否有付款,如果状态是'付款'或'发送',但因为我不能这样做的方式是干净的方法。

我的问题是,如何在订单表格的内联(付款)中访问用户输入的'payment.amount'值,以完成我的验证? (考虑我在订单模型的清洁方法中)

2 个答案:

答案 0 :(得分:3)

在阅读django源代码后,我发现BaseInlineFormSet的一个属性包含Inline的Parent实例,在我的例子中,正在编辑的Order实例。

布兰登给了我另一个重要的部分,迭代了BaseInlineFormSet的self.forms以获取每个实例(甚至没有保存或未清除或清空),在我的情况下,每个付款实例都被编辑。

这些是检查状态为“付款”或“发送”的订单是否有付款所需的两条信息。迭代formset的cleaning_data不会给订单数据(即不更改订单,只更改付款,或者不添加付款 - 和空付款 - 但更改订单),这是决定保存如果订单状态不同于'PAID'或'SENT',则建模,因此此方法之前被丢弃。

模型保持不变,我只修改了admin.py以添加下一个:

class PaymentInlineFormset(forms.models.BaseInlineFormSet):
    def clean(self):
        order = None
        payment = None

        if any(self.errors):
            return

        # django/forms/models.py Line # 672,674 .. class BaseInlineFormSet(BaseModelFormSet) . Using Django 1.3
        order = self.instance

        #There should be only one form in the paymentInline (by design), so  in the iteration below we end with a valid payment data.
        if len(self.forms) > 1:
            raise forms.ValidationError(u'Only one payment per order allowed.')
        for f in self.forms:
            payment = f.save(commit=False)

        if payment.amount == None:
            payment.amount = 0

        if order != None:
            if order.status in ['PAID', 'SENT'] and payment.amount <= 0:
                raise forms.ValidationError(u'The order with %s status must have an associated payment.'%order.status)

class pymentInline(admin.StackedInline):
    model   = Payment
    max_num = 1
    formset = PaymentInlineFormset

class OrderAdmin(admin.ModelAdmin):
    inlines = [ paymentInline, ]

admin.site.register(Order, OrderAdmin)
admin.site.register(Payment)

答案 1 :(得分:0)

听起来您只需要验证内联中至少有一个有效的formset ...您可以尝试使用此代码:http://wadofstuff.blogspot.com/2009/08/requiring-at-least-one-inline-formset.html

希望能让你前进。

[编辑]

我看了一下我为另一个应用程序编写的代码,这些代码基于相关模型上的属性对内联进行了自定义验证。当然,您可能需要进行一些调整,但请尝试一下。您还需要在管理模型中指定内联使用。

#I would put this in your app's admin.py
class PaymentInline(admin.TabularInline):
    model = Payment
    formset = PaymentInlineFormset

#I would put this in the app's forms.py
class PaymentInlineFormset(forms.models.BaseInlineFormSet):
        def clean(self):
            order = None
            valid_forms = 0

            for error in self.errors:
                if error:
                    return

            for cleaned_data in self.cleaned_data:
                amount = cleaned_data.get('amount', 0)
                if order == None:
                    order = cleaned_data.get('order')
                if amount > 0:
                    valid_forms += 1

            if order.status in ['PAID', 'SENT'] and len(valid_forms) > 0:
                raise forms.ValidationError(u'Your error message')