2012-11-23 5 views
12

私はDjango 1.4を使用しています。異なるインラインの値を比較する検証ルールを設定したいと思います。djangoの従属インラインの検証

私は3つの単純なクラスmodels.pyで

があります。例えば、でrhumのボトルを購入することが可能であるので、

class ItemInline(admin.TabularInline): 
    model = Item 

class BuyerInline(admin.TabularInline): 
    model = Buyer 

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

:admin.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) 

を10 $と8 $のウォッカの1つ。マイクは15ドル、トムは3ドルを支払う。

目標は、ユーザーが一致しない合計を持つインスタンスを保存しないようにすることです。支払い済みのアイテムはアイテム費用の合計(つまり10 + 8 = 15 + 3)と同じでなければなりません。

私が試した:

  • をShopping.clean方法でValidationErrorをを上げます。しかしインラインはまだきれいに更新されていないので、合計が正しくない。
  • ShoppingAdmin.save_relatedメソッドのValidationErrorを上げる。しかしここでValidationErrorを上げると、変更ページにリダイレクトするのではなく、非常にユーザーフレンドリーでないエラーページが表示されます。

この問題の解決方法はありますか?クライアント側(javascript/ajax)の検証は最も簡単ですか?

+0

こんにちは、これについて何かお考えですか?私はまったく同じ問題に直面しています。私が考えることができる唯一の解決策は、インラインモデルのクリーンメソッドですが、これは大きなdbオーバーヘッドを生成します。 – ppetrid

+0

私は1つの解決策は、djangoの管理者の動作を編集することだと思います。 django/contrib/admin/options.py、add_viewメソッドの行を見てください。924 – Rems

答えて

23

インラインフォームセットをオーバーライドして、必要なものを実現できます。フォームセットのクリーンメソッドでは、 'instance'メンバーを通じてShoppingインスタンスにアクセスできます。したがって、Shoppingモデルを使用して、計算された合計を一時的に保存し、フォームセットを通信させることができます。 models.pyで:

admin.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 

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 

これは、あなたが(私の知る限り)それを行うことができ、それがあるべき場所すべてが置かれている唯一のクリーンな方法です。

EDIT: * if form.cleaned_data * checkフォームに空のインラインも含まれているため、チェックが追加されました。 これがどのように機能するのか教えてください!

EDIT2:コメントに正確に指摘されているように、削除しようとしているフォームのチェックを追加しました。これらの書式は計算に参加すべきではありません。

+0

恥ずかしいです。私はあなたの答えを投票できません;私は十分な評判がありません編集:NVMいくつかの評判のポイントが魔法のように現れました – Rems

+1

削除された行は無視する必要があります: '' if if form.cleaned_data.get( 'DELETE'):continue'' –

+1

これは素敵な戦略です、ありがとうございます。 d、エラーメッセージは発生しません。私のコードでは、メインモデルのフィールドと比較しているため、インラインformsetは1つしか定義されていません(上記の例では、 'BuyerInlineFormSet'では、if self.instance.amount!= total :raise ... '。' Shopping'インスタンスをamount> 0で保存し、 'Buyers'を追加しないと、フォームが有効であることがわかります(買い手の金額の合計が0)。 – jenniwren

-2

申し訳ありません。解決策があります。これは、djangoの管理者のコードを編集することです。 add_view(ライン924)とchange_view(ライン1012)の方法におけるジャンゴに/ contrib /管理/ options.pyで

、この部分をスポット:

 [...] 
     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 

ような何かを行うことができますこれは、適切な解決策tよりも多くのハックですハフ。改善提案?誰もこれはジャンゴの機能要求でなければならないと思いますか?

+0

ValidationErrorを発生させるのではなく、リストにエラーを手動で追加する必要があるため、実際にはハックが増えています。しかし、それはまだ動作します!私はこれが基本的にformset検証の問題だと思う。その意味では、カスタムFormSetクラスを作成し、適切なクリーンメソッドを実装し、インラインでデフォルトのformsetではなくそのクラスを使用することができます。ちょっと考えました.. – ppetrid

+0

1つのFormSetを手動で作成することをお勧めしますか?だから、基本的にはインラインがなくなり、手で関連するセーブを処理しなければならないし、「別のボタンを追加する」などもありません...インラインのすべてのパワーを失うだけです:( – Rems

+0

申し訳ありません、 – ppetrid

関連する問題