Shack de Shnaek
Shack de Shnaek

Reputation: 21

How can I get the incoming value of a property that is calculated from related objects in a model's clean method

I have two models, an Invoice model and a LineItem model. The LineItem model looks like this:

class LineItem(models.Model):
    unit_price = models.DecimalField()
    quantity = models.IntegerField()
    invoice = models.ForeignKey(Invoice)

    @property
    def lineitem_total(self): return self.unit_price * self.quantity

The Invoice model also has a total property, which returns the sum of the total of all of the related line items.

Now, when the line items related to an invoice get updated, I need to validate if the total property on the Invoice exceeds a certain maximum value. However the clean() method on the Invoice fires before the related line items get updated, so it still returns the old value. I need the validation to happen on the model itself rather than a form.

Is there a way to validate the line items?

I've tried putting the validation in the Invoice model's clean() method, however the total property still returns the old value before the line items are updated.
I've also tried raising a ValidationError in the Invoice model's save() method, however that returns a 500 error.

Upvotes: 1

Views: 28

Answers (1)

ruddra
ruddra

Reputation: 52018

You can try like this in the LineItem model form (explanation in code comments):

class SomeFormItem(forms.ModelForm):
   ...
   def save(self, commit=False):
       instance = super().save(commit=False)
       total = instance.invoice.total  #  get total of line items (assuming used using reverse query)

       if self.instance.pk and ('quantity' in self.changed_data or 'unit_price' in self.changed_data):  # if we are editing already existing lineitem and total has been changed
           total += (self.cleaned_data['unit_price'] - self.data['unit_price']) * (self.cleaned_data['quantity'] - self.data['quantity'])  # self.cleaned_data contains new information and self.data contains old information and calculating the difference only
       else:
           total += self.cleaned_data['quantity'] * self.cleaned_data['unit_price']

       if total  > SOME_NUMBER:
            raise ValidationError()
       return super().save(commit=commit)

More information can be found in the documentation.

Upvotes: 1

Related Questions