2012-11-23 172 views
12

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

我有三个简单的类

在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美元和8美元的伏特加酒中的一种。迈克支付15美元,汤姆支付3美元。

目标是防止用户使用不匹配的和来保存实例:已支付的费用必须与物品费用总和相同(即10 + 8 = 15 + 3)。

我想:

  • 在Shopping.clean方法提高ValidationError。但内联没有更新,因此总和不正确
  • 在ShoppingAdmin.save_related方法中引发ValidationError。但是,在这里引发ValidationError会给用户提供一个不友好的错误页面,而不是将错误信息重定向到更改页面。

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

+0

你好,你有想过这个吗?我面对完全相同的问题。我能想到的唯一解决方案是内联模型的clean方法,但这会产生大的数据库开销。 – ppetrid

+0

我想一个解决方案是编辑django管理员的行为。看看django/contrib/admin/options.py,add_view方法行924 – Rems

回答

23

您可以重写您的内联窗体集以实现您想要的内容。在formset的干净方法中,您可以通过“实例”成员访问您的Shopping实例。因此,您可以使用购物模式临时存储计算得出的总数,并使您的表单集进行沟通。在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:添加了对表格即将被删除的检查,正如在评论中指出的那样。这些表格不应该参与计算。

+0

太棒了!这是一种耻辱,我不能投你的答案;我没有足够的声望编辑:NVM一些声望点神奇地出现了 – Rems

+1

它应该忽略被删除的行:''if form.cleaned_data.get('DELETE'):continue'' –

+1

这是一个可爱的策略,谢谢,但我有一个问题,因为当没有内联时,adde d,错误信息不会发生。在我的代码中,我只定义了一个内联formset,因为我将它与主模型中的字段进行比较(所以在上例中,在'BuyerInlineFormSet'中,我会使用比较'if self.instance.amount!= total :raise ......'当我保存数量大于0的'Shopping'实例并且不添加任何'Buyers'时,它告诉我表单是有效的,即使它不是(因为没有买方金额的总和是0) – jenniwren

-2

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

在Django /了contrib /管理/ 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的功能要求?

+0

这实际上更像是一种黑客行为,因为我们必须在列表中手动附加错误而不是引发ValidationError。但它仍然有效!我认为这基本上是一个formset验证的问题。从这个意义上讲,也许可以创建一个自定义的FormSet类,实现一个合适的干净的方法,并使用该类,而不是内联默认的formset。只是一个想法.. – ppetrid

+0

你建议手动创建一个FormSet?所以基本上没有更多的内联,你必须手动处理相关的保存,没有“添加另一个按钮”,等等......你只是松开了内联的所有权力:( – Rems

+0

对不起,也许我不清楚,我建议覆盖内联表单,最后我发布了一个单独的答案,因为我为自己的项目提出了一个解决方案。 – ppetrid