Search code examples
djangocelerycelerybeat

Best way to keep track of the sum of the field of multiple foreign keys [Django]


If you can think of a better way to phrase my question, by all means, please change it. Here's the situation I'm facing.

I have 2 models: Agent, Deal. Here's the simplified version of what they look like:

class Agent(models.Model):
    name = models.CharField(max_length=100)
    price_sum_of_all_deals = models.IntegerField()


class Deal(models.Model):
    agent = models.ForeignKey(Agent, on_delete=models.CASCADE)
    address = models.CharField(max_length=100)
    price = models.IntegerField()

I'm using celery beat to check an API to see if there are any new deals for each agent. With my current configuration, I am searching for a new deal from within a task method and if I find a deal, I add the price of that deal to the price_sum_of_all_deals field of the corresponding agent. The summary of the task looks likes this:

from celery import shared_task
from agents import models

@shared_task
def get_deals():
    agents = models.Agent.objects.all()
    for agent in agents:
        price, address = get_new_deal_from_api(agent.name)
        new_deal = models.Deal(agent=agent, address=address, price=price)
        new_deal.save()

        agent.price_sum_of_all_deals += price
        agent.save()

This, however, is not very intuitive and feels like an unnecessary abstraction. Is there a better way of calculating the price_sum_of_all_deals from within the model? What is the best practice here?

I'm relatively new to Django so if there's something glaring that I overlooked, I apologize.


Solution

  • I don't think that's the best way to process, because if one deal is deleted, how do you update price_sum_of_all_deals? You won't be 100% certain that this value is always accurate (thus, it is not acceptable).

    Here is one way to do it, without storing the sum value in an attribute of a model:

    • use your API as usual,
    • just create the Deal instance and save it,
    • to get the sum of all deals of an agent at anytime, you can use the following:
    Deal.objects.filter(agent=this_agent).aggregate(price_sum_of_all_deals=Sum('price'))
    

    The result of this line is a dict containing a price_sum_of_all_deals with the desired value.

    Source:

    I hope that is what you needed!