Thursday, August 11, 2022

[FIXED] How to create mathematics operations with decimal in Django - [<class 'decimal.InvalidOperation'>]

Issue

A very simple save method causes this error :

class Shift(models.Model):
    branch = models.ForeignKey(Branch, on_delete=models.CASCADE)
    employee = models.ForeignKey(Employee, on_delete=models.CASCADE)
    start_time = models.DateTimeField(auto_now_add=True)
    end_time = models.DateTimeField(null=True)
    closing_type_amount = models.DecimalField(max_digits=4, decimal_places=2, default=0)
    closing_actual_amount = models.DecimalField(max_digits=4, decimal_places=2, default=0)
    diff = models.DecimalField(max_digits=4, decimal_places=2, null=True)
    on_duty = models.BooleanField(default=True)

    def save(self, *args, **kwargs):
        self.diff = float(self.closing_type_amount) - float(self.closing_actual_amount)
        super(Shift, self).save(*args, **kwargs)

The cause of this error is the line self.diff = float(self.closing_type_amount) - float(self.closing_actual_amount) NOTE when give inputs more than 100 it fails ,, when a small numbers like 5, 4 , 6, 11 it works normally .


Solution

Because of the restrictions you have set. max_digits=4 and decimal_places=2 allows for a maximum number of 99.99 to be saved (decimal places are included in max digits).

See usage example here.

Since those are both required arguments you can set something high enough to work for most use cases. For example to store numbers up to approximately one billion with a resolution of 10 decimal places:

models.DecimalField(..., max_digits=19, decimal_places=10)

I would also suggest a bit of a change to your model because in my opinion saving the diff value is redundant since you already have the two values you calculate it from. Rather add a property to your model to calculate it on the fly:

class Shift(models.Model):
    branch = models.ForeignKey(Branch, on_delete=models.CASCADE)
    employee = models.ForeignKey(Employee, on_delete=models.CASCADE)
    start_time = models.DateTimeField(auto_now_add=True)
    end_time = models.DateTimeField(null=True)
    closing_type_amount = models.DecimalField(max_digits=4, decimal_places=2, default=0)
    closing_actual_amount = models.DecimalField(max_digits=4, decimal_places=2, default=0)
    on_duty = models.BooleanField(default=True)

    @property
    def diff(self):
        return float(self.closing_type_amount) - float(self.closing_actual_amount)

Also, if you are going to cast to float you might as well use FloatField instead of DecimalField.



Answered By - Toni Sredanović
Answer Checked By - Gilberto Lyons (PHPFixing Admin)

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.