Search code examples
djangodjango-modelssoftware-designmaintainability

Django - Where should I place calculation method to design a proper and maintainable project?


I have some classes like these;

class RawMaterial(models.Model):
    name = models.CharField(max_length=100)

class Product(models.Model):
    name = models.CharField(max_length=100)
    amount = models.IntegerField()
    raw_materials = models.ManyToManyField(RawMaterial, through='MaterialProduct', related_name='products')

class MaterialProduct(models.Model):
    raw_material = models.ForeignKey(RawMaterial, on_delete=models.CASCADE)
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    material_price = models.FloatField()
    material_rate = models.FloatField()

I want to write a method which name is calculate_total_price, My method will use Product's amount and MaterialProduct's material_price , material_rate. To design a proper/beautiful/maintainable project, where should I write my method? To models.py or views.py ?

Thanks in advance.


Solution

  • Following the approach fat models thin views I'd recommend you to put that calculation in the models.py.

    It could look like this:

    class MaterialProduct(models.Model):
        # attributes
    
        def calculate_total_price(self):
            # perform calculation with
            # self.product.amount
            # self.material_price
            # self.material_rate
            return result
    

    You can call this method also from your templates ({{ object.calculate_total_price }}) to display the total price.

    Now, if you need to call this method more than once, the question is arising: why do we run the method again, if the result isn't changing?

    Therefore I'd go one step further and make it a property:

    class MaterialProduct(models.Model):
        # attributes
        @property
        def total_price(self):
            # perform calculation
            return result
    

    or, as mentioned before, if you don't expect the total price changing every few seconds, maybe you'd like to go with a cached_property:

    from django.utils.functional import cached_property
    
    class MaterialProduct(models.Model):
        # attributes
        @cached_property
        def total_price(self):
            # perform calculation
            return result
    

    The total price is now available as any other field in the templates ({{ object.total_price }}). If you use the cached_property the calculation is going to be performed only once and the result will be cached. Calling the property again will retrieve the result from the cache and you can save a hit to the database and CPU processing time.