Validation of dependant inlines in django admin

后端 未结 2 1701
余生分开走
余生分开走 2020-12-09 09:42

I am using Django 1.4 and I want to set validation rules that compare values of different inlines.

I have three simple classes

In models.py:

         


        
相关标签:
2条回答
  • 2020-12-09 10:09

    You could override your Inline formset to achieve what you want. In the clean method of the formset you have access to your Shopping instance through the 'instance' member. Therefore you could use the Shopping model to store the calculated total temporarily and make your formsets communicate. In 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
    

    in 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
    

    This is the only clean way you can do it (to the best of my knowledge) and everything is placed where it should be.

    EDIT: Added the *if form.cleaned_data* check since forms contain empty inlines as well. Please let me know how this works for you!

    EDIT2: Added the check for forms about to be deleted, as correctly pointed out in the comments. These forms should not participate in the calculations.

    0 讨论(0)
  • 2020-12-09 10:12

    Alright I have a solution. It involves editing django admin's code.

    In django/contrib/admin/options.py, in the add_view (line 924) and change_view (line 1012) methods, spot this part:

            [...]
            if all_valid(formsets) and form_validated:
                self.save_model(request, new_object, form, True)
            [...]
    

    and replace it with

            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)
    

    Now in your ModelAdmin, you can do something like this

    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
    

    This is more a hack than a proper solution though. Any improvement suggestions? Does anyone think this should be a feature request on django?

    0 讨论(0)
提交回复
热议问题