Подтвердить что ты не робот

Проверка зависимых строк в django admin

Я использую 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)

Так, например, можно купить бутылку rhum на 10 $и одну водки при 8 $. Майк платит 15 $, а Том платит 3 $.

Цель состоит в том, чтобы запретить пользователю сохранять экземпляр с суммами, которые не совпадают: то, что было оплачено, должно быть таким же, как сумма стоимости предмета (например, 10 + 8 = 15 + 3).

Я пробовал:

  • повышение ValidationError в методе Shopping.clean. Но встроенные строки еще не обновлены в чистоте, поэтому суммы неверны.
  • повышение ValidationError в методе ShoppingAdmin.save_related. Но повышение ValidationError здесь дает очень недружественную страницу ошибок пользователя, а не перенаправление на страницу с хорошим сообщением об ошибке.

Есть ли какое-либо решение этой проблемы? Является ли проверка на стороне клиента (javascript/ajax) самой простой?

4b9b3361

Ответ 1

Вы можете переопределить набор встроенных форм для достижения желаемого. В чистом методе набора форм у вас есть доступ к вашему экземпляру 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

Это единственный чистый способ, которым вы можете это сделать (насколько мне известно), и все будет размещено там, где оно должно быть.

EDIT: Добавлена ​​проверка * if form.cleaned_data *, так как формы также содержат пустые строки. Пожалуйста, дайте мне знать, как это работает для вас!

EDIT2: Добавлена ​​проверка на удаление форм, как правильно указано в комментариях. Эти формы не должны участвовать в расчетах.

Ответ 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?