开发者

django - weird results (cached?) obtained while storing calculated values in fields at model level

Dear django gurus,

please grab my nose and stick it in where my silly mistake glows.

I was about to proceed some simple math operation based on existing field values and store it in a separate field (subtotal) of current instance.

My question is actually included in the very last comment of code.

class Element(models.Model):
    name = models.CharField(max_length=128)
    kids = models.ManyToManyField('self', null=True, blank=True, symmetrical=False)
    price = models.IntegerField('unit price', null=True, blank=True)
    amount = models.IntegerField('amount', null=True, blank=True)
    subtotal = models.IntegerField(null=True, blank=True)
    def counter(self):
        output = 0
        # Check if this is the lowest hierarchy level (where the intention is to 
        # store both values for price and amount) and proceed calculations 
        # based on input values. Also check if values are set to avoid operations
        # with incompatible types.
        # Else aggregate Sum() on subtotal values of all kids.
        if self.kids.count() == 0:
            if self.price == None or self.amount == None:
                output = 0
            else:
                output = self.price * self.amount
        else:
            output = self.kids.aggregate(Sum('subtotal'))['subtotal__sum']
        self.subtotal = output
    def __unicode__(self):
        return self.name

This is how my sample data look like (I am sorry if I am missing some convention of how to show it).

element.name = son
element.kids = # None
element.price = 100
element.amount = 1
element.subtotal = 100 # Calculates and stores correct value.

element.name = daughter
element.kids = # None
element.price = 200
element.amount = 5
element.subtotal = 1000 # Calculates and stores correct value.

element.name = father
element.kids = son, daughter
element.price = # None. Use this field for开发者_如何学编程 overriding the calculated
                # price at this level.
element.amount = # None.
element.subtotal = 25   # Calculates completely irrelevant value. 
                        # The result seems to be some previous state 
                        # (first maybe) of subtotal of one of the kids.
                        # This is where my cache part of question comes from.

While solving this simple task I have started with clean() class, but the results were calculated after second save only (maybe a good topic for another question). I switched then to custom method. But now after a night spent on this I would admiteddly use Mr. Owl's words to Winnie the Pooh: "You sir are stuck!". At this point I am using only native django admin forms. Any help will be most appreciated.


Without seeing the code you are using to invoke all these calculations, I can only guess what the problem might be. It may be helpful to show your view code (or other), which 'kicks off' the calculations of subtotal.

There are two potential issues I can see here.

The first is in your counter method. Are you saving the model instance after calculating the total?

def counter(self):
    output = 0
    if self.kids.count() == 0:
        if self.price == None or self.amount == None:
            output = 0
        else:
            output = self.price * self.amount
    else:
        output = self.kids.aggregate(Sum('subtotal'))['subtotal__sum']
    self.subtotal = output
    self.save() # save the calculation

Without saving the instance, when you query the children from the parent, you will get the current database value rather than the newly calculated value. This may or may not be an issue, depending on the code that calls counter to begin with.

The other potential issue I see here is cache invalidation. If a child updates their price or amount, is the parent notified so it can also update its subtotal? You may be able to override the save method to do your calculations at the last minute.

def save(self, *args, **kwargs):
    self.counter() # calculate subtotal
    super(Element, self).save(*args, **kwargs) # Call the "real" save() method.
    for parent in self.element_set.all(): # use a related name instead
        parent.save() # force recalculation of each parent

You'll note that this will only force the correct values of subtotal to be valid after saving only. Be aware that overriding the save method in this way is directly contradictory to my first solution of saving the instance when calculating counter. If you use both, together, you will get a StackOverflow as counter is calculated then saved, then during saving counter is calculated, which will trigger another save. Bad news.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜