Reputation: 23
I want to learn what is the best approach to recalculate a model field value(total_cost
in my Product
model class) in all related models after updating a model. I wrote a piece of code and it works but it takes long time. How can I speed up my code? Here is model classes
#models.py
class Product(..):
total_cost = models.FloatField(...)
materials = models.ManyToManyField(Material, through="MaterialProduct")
def calculate_cost(self):
return round(calc_material_price(self.materialproduct_set.all()),2)
def calc_material_price(self, mylist):
res = 0
for m in mylist:
res += m.material.price * m.material_rate
return res
class Material(..):
price = models.FloatField(...)
class MaterialProduct(..):
product = models.ForeignKey(Product, ..)
material = models.ForeignKey(Material, ..)
material_rate = models.FloatField()
class UpdateProductMaterialSerializer(..):
#...
def update(self, instance, validated_data):
#in front-end total cost calculated and I assign it here directly
instance.total_cost = self.initial_data.get("total_cost")
instance.save()
mylist_data = self.initial_data.get("mylist")
material_list_for_update = []
for item in mylist_data:
material = item.get("material")
material_instance = Material.objects.get(pk=material["id"])
# I use this list for updating all Products
material_list_for_update.append(material_instance)
material_rate = item.get("material_rate")
mp = MaterialProduct.objects.filter(product=instance, material=material_instance).first()
mp.material_rate = material_rate
mp.save()# here my one product is updated successfully
# TO UPDATE ALL PRODUCTS I call below method
# But this takes 15 seconds
update_all_product_by_sent_materials(material_list_for_update)
return instance
def update_all_product_by_sent_materials(self, material_list):
for material in material_list:
for mp in MaterialProduct.objects.filter(material=material):
mp.material.total_cost = mp.material.calculate_cost()
mp.material.save()
total_cost
as property in my Product model class and use it. But I dont know how to approach this problem.Upvotes: 2
Views: 461
Reputation: 51998
Maybe I should use total_cost as property in my Product model class and use it.
I think its a good idea, and not to store this value in DB as well. But it will not reduce time to calculate the cost. Maybe you can use aggregation
to reduce the time like this:
from django.db.models import Sum, F
....
@property
def calculate_cost(self):
cost = self.materialproduct_set.aggregate(cost=Sum(F('material__price') * F('material_rate')))['cost']
return round(cost, 2)
Basically it will aggregate the value from DB so you don't have to make a loop and calculate manually. Also, it will reduce DB hits so performance should improve.
Improved answer based on this SO Answer
:
from django.db.models import F, Func, Sum
class Round(Func):
function = 'ROUND'
template='%(function)s(%(expressions)s, 2)'
# rest of the code
@property
def calculate_cost(self):
cost = self.materialproduct_set.aggregate(cost=Round(Sum(F('material__price') * F('material_rate'))))['cost']
return cost
Upvotes: 3