tags:

views:

109

answers:

2

Really confused about what's going on here. I have a class defined as follows:

class Profile(models.Model):
    user = models.OneToOneField(User)
    primary_phone = models.CharField(max_length=20)
    address = models.ForeignKey(Address)

    @property
    def primary_email(self): return self.user.email
    @primary_email.setter
    def primary_email(self, val): self.user.email = val

NB: user has an attribute email.

Now from the commandline, I'm trying this:

>>> u = User.objects.get(pk=1)
>>> u.email = 'xxx'
>>> u.profile.primary_email
u'yyy'

It spits out a different value? Specifically, the old value of u.email. What's going on? How is this possible? I basically just want to create an alias for email.


Some more info:

>>> id(u) == id(u.profile.user)
False
>>> u
<User: mark>
>>> u.profile.user
<User: mark>

They seem to be different copies of user, but they... what? Both start with the same values?

Doing this seems to commit the changes:

>>> u.profile.primary_email = 'yyy'
>>> u.profile.user.save()

But u.save() won't do the trick because u != u.profile.user for whatever reason. I guess that answers my question, but it's still kind of lame.

It is possible for those two to refer to the same object in Python, right? It was just a funny design decision in Django that's causing this?

+2  A: 

I'm not a Django user, but I'd guess it's because you didn't update the model after changing u.email. Try calling u.save() (or whatever the method happens to be called) before accessing the user's email through the profile.

You can use Django's signaling feature to build a workaround. Basically, update Profile.user when User sends post_save:

# models.py
...
def update_user(**kwargs):
    kwargs['instance'].profile.user = kwargs['instance']

models.signals.post_save.connect(update_user, sender=User)

You still need to call User.save for this to work, and clobbering Profile.user may have other side effects. There might be a more Django-y way; someone with more Django experience than I may yet post it. For example, it may be possible to hook the code that gets called the first time you access User.profile and set Profile.user to the parent user, instead of creating a new User.

Alternatively, whenever you get a user via Users.objects.get and reference Users.profile, replace the user object with the user property from User.profile.

outis
You are correct in guessing it's `u.save()`, but that doesn't work. And regardless, printing `u.email` without saving will print "xxx". I thought with the properties, it would refer to that exact same instance variable?
Mark
`u.profile.user` most likely refers to a wrapper around the DB object; `id(u) == id(u.profile.user)` will tell you a little more, as will examining the properties of the various objects.
outis
Oh. I didn't know about this `id` function. You're right, they're different.
Mark
Nice edit. That could work... this was really just a convenience property anyway. It's not a huge deal. Wanted it to be consistent, so that both the phone and email properties appear on the profile, instead of one here, and one there.
Mark
+2  A: 

Python properties per se do not work in django models because django's models do some magic to set instance attributes. Maybe this is having an effect.

blokeley