21

Django 1.4 を使用しており、異なるインラインの値を比較する検証ルールを設定したいと考えています。

私は3つの簡単なクラスを持っています

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 ドルで、ウォッカを 1 杯 8 ドルで購入することができます。マイクは 15 ドル、トムは 3 ドルを支払います。

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

私は試した:

  • Shopping.clean メソッドで ValidationError を発生させます。しかし、インラインはまだきれいに更新されていないため、合計は正しくありません
  • ShoppingAdmin.save_related メソッドで ValidationError を発生させます。しかし、ここで ValidationError を発生させると、適切なエラー メッセージとともに変更ページにリダイレクトされる代わりに、非常にユーザーフレンドリーでないエラー ページが表示されます。

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

4

2 に答える 2

41

インラインフォームセットをオーバーライドして、必要なものを実現できます。フォームセットの clean メソッドでは、「インスタンス」メンバーを介してショッピング インスタンスにアクセスできます。したがって、ショッピング モデルを使用して、計算された合計を一時的に保存し、フォームセットを通信させることができます。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:コメントで正しく指摘されているように、削除しようとしているフォームのチェックを追加しました。これらのフォームは計算に参加しないでください。

于 2012-12-25T13:40:20.143 に答える
-3

さて、解決策があります。これには、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の機能リクエストであるべきだと思う人はいますか?

于 2012-12-22T14:40:51.353 に答える