Django管理中依赖内联线的验证

2024-05-16 14:20:17 发布

您现在位置:Python中文网/ 问答频道 /正文

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

我有三节简单的课

在models.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美元可以买到一瓶rhum,8美元可以买到一瓶伏特加。迈克付15美元,汤姆付3美元。

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

我试过:

  • 在Shopping.clean方法中引发ValidationError。但是内联线还没有完全更新,所以总和不正确
  • 在ShoppingAdmin.save_相关方法中引发ValidationError。但是在这里提升ValidationError会给用户一个非常不友好的错误页面,而不是重定向到带有错误消息的更改页面。

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


Tags: namepymodeladminmodelsshopitemlength
2条回答

好吧,我有个解决办法。它包括编辑django管理员的代码。

在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的一个功能请求吗?

你可以重写你的内联表单集来实现你想要的。在表单集的clean方法中,您可以通过“instance”成员访问购物实例。因此,您可以使用购物模型暂时存储计算的总数,并使您的表单集进行通信。在models.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:添加了对将要删除的表单的检查,如注释中正确指出的。这些表格不应参与计算。

相关问题 更多 >