21

我正在使用 Django 1.4,我想设置比较不同内联值的验证规则。

我有三个简单的类

在模型.py 中:

class Shopping(models.Model):
    shop_name = models.CharField(max_length=200)

class Item(models.Model):
    item_name = models.CharField(max_length=200)
    cost = models.IntegerField()
    item_shop = models.ForeignKey(Shopping)

class Buyer(models.Model):
    buyer_name = models.CharField(max_length=200)
    amount = models.IntegerField()
    buyer_shop = models.ForeignKey(Shopping)

在 admin.py 中:

class ItemInline(admin.TabularInline):
    model = Item

class BuyerInline(admin.TabularInline):
    model = Buyer

class ShoppingAdmin(admin.ModelAdmin):
    inlines = (ItemInline, BuyerInline)

例如,可以以 10 美元的价格购买一瓶朗姆酒,以 8 美元的价格购买一瓶伏特加。迈克支付 15 美元,汤姆支付 3 美元。

目标是防止用户保存总和不匹配的实例:已支付的金额必须与项目成本的总和相同(即 10+8 = 15+3)。

我试过:

  • 在 Shopping.clean 方法中引发 ValidationError。但是内联还没有更新干净所以总和不正确
  • 在 ShoppingAdmin.save_related 方法中引发 ValidationError。但是在这里提高 ValidationError 会给出一个对用户非常不友好的错误页面,而不是重定向到带有漂亮错误消息的更改页面。

这个问题有什么解决办法吗?客户端(javascript/ajax)验证是最简单的吗?

4

2 回答 2

41

您可以覆盖您的内联表单集以实现您想要的。在表单集的 clean 方法中,您可以通过“实例”成员访问您的购物实例。因此,您可以使用 Shopping 模型临时存储计算的总数并让您的表单集进行通信。在模型.py 中:

class Shopping(models.Model):
   shop_name = models.CharField(max_length=200)

   def __init__(self, *args, **kwargs)
       super(Shopping, self).__init__(*args, **kwargs)
       self.__total__ = None

在 admin.py 中:

from django.forms.models import BaseInlineFormSet
class ItemInlineFormSet(BaseInlineFormSet):
   def clean(self):
      super(ItemInlineFormSet, self).clean()
      total = 0
      for form in self.forms:
         if not form.is_valid():
            return #other errors exist, so don't bother
         if form.cleaned_data and not form.cleaned_data.get('DELETE'):
            total += form.cleaned_data['cost']
      self.instance.__total__ = total


class BuyerInlineFormSet(BaseInlineFormSet):
   def clean(self):
      super(BuyerInlineFormSet, self).clean()
      total = 0
      for form in self.forms:
         if not form.is_valid():
            return #other errors exist, so don't bother
         if form.cleaned_data and not form.cleaned_data.get('DELETE'):
            total += form.cleaned_data['cost']

      #compare only if Item inline forms were clean as well
      if self.instance.__total__ is not None and self.instance.__total__ != total:
         raise ValidationError('Oops!')

class ItemInline(admin.TabularInline):
   model = Item
   formset = ItemInlineFormSet

class BuyerInline(admin.TabularInline):
   model = Buyer
   formset = BuyerInlineFormSet

这是您可以做到的唯一干净的方法(据我所知),并且所有东西都放置在应有的位置。

编辑:添加了 *if form.cleaned_data* 检查,因为表单也包含空内联。请让我知道这对您有用!

EDIT2:添加了对即将删除的表格的检查,正如评论中正确指出的那样。这些表格不应参与计算。

于 2012-12-25T13:40:20.143 回答
-3

好吧,我有一个解决方案。它涉及编辑 django admin 的代码。

在 django/contrib/admin/options.py 中,在 add_view(第 924 行)和 change_view(第 1012 行)方法中,找出这部分:

        [...]
        if all_valid(formsets) and form_validated:
            self.save_model(request, new_object, form, True)
        [...]

并将其替换为

        if not hasattr(self, 'clean_formsets') or self.clean_formsets(form, formsets):
            if all_valid(formsets) and form_validated:
                self.save_model(request, new_object, form, True)

现在在你的 ModelAdmin 中,你可以做这样的事情

class ShoppingAdmin(admin.ModelAdmin):
    inlines = (ItemInline, BuyerInline)
    def clean_formsets(self, form, formsets):
        items_total = 0
        buyers_total = 0
        for formset in formsets:
            if formset.is_valid():
                if issubclass(formset.model, Item):
                    items_total += formset.cleaned_data[0]['cost']
                if issubclass(formset.model, Buyer):
                    buyers_total += formset.cleaned_data[0]['amount']

        if items_total != buyers_total:
            # This is the most ugly part :(
            if not form._errors.has_key(forms.forms.NON_FIELD_ERRORS):
                form._errors[forms.forms.NON_FIELD_ERRORS] = []
            form._errors[forms.forms.NON_FIELD_ERRORS].append('The totals don\'t match!')
            return False
        return True

不过,这更像是一种技巧,而不是一个适当的解决方案。有什么改进建议吗?有人认为这应该是 django 上的功能请求吗?

于 2012-12-22T14:40:51.353 回答