My setup looks roughly like this:
# account/models.py
Account(models.Model):
""" The user profile """
user = models.OneToOneField("auth.User", primary_key=True, related_name="account")
balance = models.DecimalField(max_digits=32, decimal_places=2, default=0)
activation_key = models.CharField(max_length=32)
...
And I have a signal handler that looks like this:
def recalculate_credit(sender, instance=None, created=None, **kwargs):
print(instance.user.account.activation_key)
account = instance.user.get_profile()
print(account.activation_key)
account.calculate_balance()
account.save()
I’ve added the print statements there to highlight the problem. During my registration process, I set the activation_key property, save, and then this signal is fired later. The first print statement returns that activation key, the second returns ''.
How is this possible? Am I missing something in how .get_profile() works? The code above is actually removing the activation_key value from the user account. Is there some lazy loading going on, or object caching that I’m not aware of?
Yeah, so what’s going on here is that django doesn’t re-load the
.accountattr or the value of.get_profile()from the DB every time, it’a cached after the first access. To fix, either get a totally fresh copy from the DB withAccount.objects.get(id=user.account_id)when you need to guarantee that the object is fresh, or only access the relatedAccountobject through either the.get_profile()API or the.accountrelated descriptor – not both. As long as you access the object in the same way that you did before calling.save()on it, you shouldn’t have this issue.Conceptually, the underlying issue is that Django doesn’t do any identity mapping –
Foo.objects.get(id=1), run twice, will result in two separateFooobjects in memory. Their attributes won’t be shared, and updating one won’t change the other. It’s more appropriate to consider them “snapshots” of what that model’s DB row looked like when it was retrived from the DB. To find out what the DB value is right now, you have to run the query again.