Updating parent and sibling child objects in save override

Hi, new Django user here hoping to get some pointers!

I’m trying to update sibling Stock objects as well as the parent Portfolio object when a user edits or creates a new Stock, but I haven’t been able to figure out how to make things work. So far, I’ve tried implementing this logic as part of the save override directly (as shown in the screenshot), as well as in the pre/post_save methods but I haven’t been able to figure it out.

class Portfolio(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    name = models.CharField(max_length=100)
    description = models.CharField(max_length=200,default=None)
    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)
    active = models.BooleanField(default=True)
    total_value = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)

    def get_stocks_children(self):
        return self.stock_set.all()
    
class Stock(models.Model):
    EQUITY_MASTER_LIST = utils.EQUITY_MASTER_LIST
    portfolio = models.ForeignKey(Portfolio, on_delete=models.CASCADE)
    symbol = models.CharField(blank=False, max_length=10, choices=EQUITY_MASTER_LIST, default=None)
    shares = models.DecimalField(blank=False, max_digits=12, decimal_places=4, default=1.0000)
    portfolio_percentage = models.DecimalField(max_digits=10, decimal_places=4, default=0.0000)
    purchase_date = models.DateField(null=True, blank=False, default=None)
    purchase_price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
    sell_stop_price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)
    
    def save(self, *args, **kwargs):
          self.update_stocks_and_portfolio()
          super().save(*args, **kwargs)
        
    def update_stocks_and_portfolio(self):
        #calc sell_stop_price; need to update for actual calc later
        self.sell_stop_price = self.purchase_price / 4 * 3
        
        #calc portfolio total value
        portfolio = self.portfolio
        stocks_in_portfolio = portfolio.get_stocks_children()
        value = 0

        for stock in stocks_in_portfolio:
            value += stock.shares * stock.purchase_price
        portfolio.total_value = value

        #calc percentage weight of stock in portfolio total value
        for stock in stocks_in_portfolio:
            percentage = stock.shares * stock.purchase_price / value
            stock.portfolio_percentage = percentage

The code does seem to work when I run it directly in the Django shell, but does not update the fields for portfolio_percentage (in the Stock model) or the total_value file (from the Portfolio model) when it runs as part of the save methods in the app itself.

Thanks in advance for any pointers on how to best accomplish this!

Please don’t post images of code, templates or error messages here. Copy / paste the code into the body of your post.

When posting code here, enclose the code between lines of three backtick - ` characters. This means you’ll have a line of ```, then the code, then another line of ```. This forces the forum software to keep your code properly formatted.

Thanks for the tip @KenWhitesell! I just went back in and corrected it.

If what you posted is an accurate copy, then your indentation on the save method is not correct. You have save at the same level as your class definition instead of indented within that class.

Now, assuming your code’s indentation is actually correct, I don’t know if update_stocks_and_portfolio is going to work the way you’re expecting it to work.

You’re iterating over stocks_in_portfolio and calculating a new value. However, the stocks_in_portfolio queryset is going to be the data that is in the database - but the new instance of Stock that is being saved and starting this process, has not yet been written to the database. So, the instance of Stock being used in that calculation is the old value, not the new one.

You’re correct; the indent is just messed up from when I copied/pasted the code over.

That makes sense regarding the timing of calculations against new vs old values. Approach-wise, if I moved the current approach into a post-save method, it should work then?

There is no “post_save” method. There’s a post_save signal, but I only recommend using signals when there’s no other option.

In this case, the solution is easier, and you have a couple ways to handle this.

  • You could modify your processing to save the instance of the model and then update the data, and save the model again.

  • You could modify your processing to exclude the current instance from the query and then add in the current instance to the calculation.

1 Like

That helps; I appreciate the quick replies @KenWhitesell!