2012-11-23 79 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