Value of foreignkey variable not updated after save

Hi all

Going to be hard to try and explain this, but here goes:

I have a Model called Subscription and a Model Wheels.

class Subscription(models.Model):
  wheels = models.ManyToManyField(Wheels, blank=True)
  price = models.IntegerField(default=0)

class Wheels(models.Model):
(not actually empty of course)

That model Subscription has a ManyToMany relationship variable called wheels, which points to the Wheels Model. And a price variable that shows the cost of the number of wheels in the subscription.
Now I want to do a override save (or post/pre save, all three have the same behavior) where it calculates the wheels in the subscription * 10.

Save method below is part of Subscription

def save(self, *args, **kwargs):

        # Automatically calculate cost of subscription
        wheels_price = self.wheels.count() * 10
        self.price = wheels_price

        print(self.wheels.count())

        # Save to original Model
        super(Subscription, self).save(*args, **kwargs)

Now I save this using the admin GUI, clicking a ‘wheels’ and hitting save. I’ve did a count in the save as well, to see where it went wrong. Only if I save it twice, it will show the correct amount of wheels and calculate the price. There’s no change on the first save.

However, if I check the database after the first save, I do see that the Wheels foreignkey is populated, but the save says it is not.

{
        "amount_paid": 0,
        "price": 0,
        "wheels": [
            2
        ],
        "get_wheels_count": 1
}

Any clues?

Seems I haven’t searched hard enough. Stumbled upon a post on manytomany being only updated after the save of the original instance.

So I have used the m2m changed signal now to trigger a new function that follows up the variables and saves them if necessary.

@receiver(m2m_changed, sender=Subscription.wheels.through)
def m2m_save_subscription_wheels(sender, instance, *args, **kwargs):
    instance = calculate_price_subscription(instance)
    instance.save()

Unfortunately I’m now getting an error when creating a completely new object:

ValueError: "<Subscription: Subscription object (None)>" needs to have a value for field "id" before this many-to-many relationship can be used.

I forgot I had a pre-save on it as well, to modify the price if the amount has changed.

@receiver(pre_save, sender=Subscription)
def pre_save_subscription(sender, instance, *args, **kwargs):
    if instance.id is not None:
        instance = calculate_price_subscription(instance)

I had to add if instance.id is not None: to fix this, but it cannot be the right way to do it right?

So as a general rule, this basically isn’t something you want to do. If this is a case where price is always wheels.count()*10, you’re actually better off doing the calculation when needed rather than trying to maintain it in the database.

There are too many cases where these values can get out-of-sync to want to try and maintain it as a valid copy of data.

Hi @KenWhitesell

Thanks a lot for the quick response!
It’s not only Wheels*10, did that to keep the clutter down. There’s also discounts and other subscription elements that change the price around. I thought this was easiest as otherwise I had to query all elements to see what prices are higher / lower than a certain amount for instance.

I have now used the m2m changes and pre-save methods, is this still not done and are you suggesting that I’d take it to the frontend to calculate this?

Many thanks

The issue is one of data consistency. No matter what you do from within Django, there’s always at least one way that the data can be changed to make the values inconsistent.

What I am suggesting that you perform those calculations in the view that retrieves the data to be displayed.

I’m not suggesting you do the calculations in the front end - although you could do that, I’m not saying you should do that. (I’m not saying you shouldn’t, either - it’s a choice for you to make. My preference is to keep all those calculations in the back-end.)

(Now, if it’s really not practical to perform those operations in the view, then your next-best solution is to add a trigger to the database to update the data at that layer when a change is detected. But that would be a really unusual case. You can do a lot in a view before it becomes a problem.)