views:

463

answers:

3

Let's say that I have subclassed User model (CustomUser) properly (as explained here: http://scottbarnham.com/blog/2008/08/21/extending-the-django-user-model-with-inheritance/)

and installed the comments app.

to access the user of a comment in the template I write:

{{comment.user}} # which provides User, not my CustomUser

and therefore,

{{comment.user.CustomProperty}} #does not work.

How can I work around it?

+1  A: 

I don’t think there is a way around it, because as you said, comment.user is a User, not a CustomUser.

See http://docs.djangoproject.com/en/dev/topics/db/models/#proxy-models:

QUERYSETS STILL RETURN THE MODEL THAT WAS REQUESTED
There is no way to have Django return, say, a MyUser object whenever you query for User objects. A queryset for User objects will return those types of objects. The whole point of proxy objects is that code relying on the original User will use those and your own code can use the extensions you included (that no other code is relying on anyway). It is not a way to replace the User (or any other) model everywhere with something of your own creation.

Maybe in this case, the old way of using UserProfile would be a better choice?

Sorry if I’m not helping much.

Arnaud
I used the good old userprofile, but things became inelegant when userprofile had many to many relationships on itself (such as friends). look at this, is it not "lovely"?: friend = my_user.get_profile().followed_users[0].user -- As a result, I subclassed User; but now it seems some apps will be unavailable for me.
shanyu
I can certainly see how it could get quite messy pretty quickly!
Arnaud
+1  A: 

As you can see in the comments of that post, it is still controversially discussed, what is the best way.

I tried it by subclassing too, but I ran in many problems, while using profiles work perfectly for me.

class IRCUser(models.Model):
    user        = models.ForeignKey(User, unique=True)
    name        = models.CharField(max_length=100, blank= True, null = True )
    friends     = models.ManyToManyField("IRCUser", blank= True, null = True)
    dataRecieved= models.BooleanField(default=False)

creating an IRCUser works like this:

>>> IRCUser(user = User.objects.get(username='Kermit')).save()

EDIT: Why are user_profiles elegant:

Let's assume, we are writing a webapp, that will behave as a multi-protocol chat. The users can provide their accounts on ICQ, MSN, Jabber, FaceBook, Google Talk .....

We are free to create a custom user class by inheritance, that will hold all the additional informations.

class CustomUser(User):
    irc_username = models.CharField(blank=True, null=True)
    irc_password = models.PasswordField(blank=True, null=True)
    msn_username = models.CharField(blank=True, null=True)
    msn_password = models.PasswordField(blank=True, null=True)
    fb_username = models.CharField(blank=True, null=True)
    fb_password = models.PasswordField(blank=True, null=True)
    gt_username = models.CharField(blank=True, null=True)
    gt_password = models.PasswordField(blank=True, null=True)
    ....
    ....

this leads to

  • data-rows with a lot of zero-values
  • tricky what-if-then validation
  • the impossibility, to have more the one account with the same service

So now let's do it with user_profiles

class IRCProfile(models.Model):
    user = models.ForeignKey(User, unique=True, related_name='ircprofile')
    username = models.CharField()
    password = models.PasswordField()

class MSNProfile(models.Model):
    user = models.ForeignKey(User, unique=True, related_name='msnprofile')
    username = models.CharField()
    password = models.PasswordField()

class FBProfile(models.Model):
    user = models.ForeignKey(User, unique=True, related_name='fbprofile')
    username = models.CharField()
    password = models.PasswordField()

the result:

  • User_profiles can be created when needed
  • the db isn't flooded by zero-values
  • n profiles of same type can be assigned to one user
  • validation is easy

this may lead to a more cryptic syntax in the templates, but we are free to have some shortcuts in our views/template_tags or to use {% with ... %} to flavour it as we want.

vikingosegundo
As I commented on Arnaud's post, I dislike it when userprofile has many to many relationships on itself. a relationship between a user and user turns out one between userprofile and userprofile. not elegant IMO.
shanyu
see my edit for an example, why user_profiles can be *very* elegant
vikingosegundo
+2  A: 

The ForeignKey from comments.Comment is to django's built-in User object, so querying comment.user will give you the parent object (ie: the base User model). However, django inheritance does provide a way to get the subclass version from the superclass:

{{ comment.user.customeruser }}

Which would then allow you to do:

{{ comment.user.customeruser.customproperty }}

I happen to think this is a weakness in Django's inheritance implementation since it doesn't exactly mirror the behaviour you'd expect from object inheritance in Python generally, but at least there is a workaround. Since I'm hardly rushing out to submit a patch with my version of the right behaviour, I can't complain :-)


I agree with Carl Meyer's comment: it could expensive to automatically fetch the subclass without altering the parent model's db table, and returning the instance of the subclass from the parent class query would be inconsistent with Django's promise that a queryset returns the model on which the queryset was run.

I still find in practice, however, that Django's inheritance leads to some awkward extra steps on occasion. Having used Django since 0.91, and knowing that all the different strategies for resolving object-relational mapping issues have tradeoffs, I'm very happy to have inheritance in Django now, and feel that the current implementation is excellent... so I'd hate for my original answer to be construed as a slight against the project.

As such, I thought I would edit this answer to link to an answer Carl himself provided on a solution in cases where you don't know what type the subclass is: How do I access the child classes of an object in Django without knowing the name of the child class?. He offers advice there for using the ContentType framework. Still some indirection involved, but a good, generalizable, option in the toolkit.

Jarret Hardie
This is what I needed, thanks a lot!
shanyu
It's true that this doesn't "mirror the behaviour you'd expect from object inheritance in Python," but there are really good reasons it's implemented as it is, which I think you'd discover if you did try to write a patch :-) I assume your desired behavior would be have an object automatically be the "leaf" subclass; the problem is that for sizable inheritance trees that requires multiple expensive queries to figure out. Or you have to store type information on the parent model, but having to modify the parent model kills many use cases for inheritance.
Carl Meyer
Good extra information for the question, Carl. I added a link to one of your answers based on the ContentTypes framework from another question.
Jarret Hardie
Good response. I agree that Django's inheritance leads to "awkward extra steps," it's just that AFAICT that awkwardness is pretty much inherent to the problem domain, not specific to a particular implementation. Mapping object inheritance to a relational database is just messy no matter how you slice it.
Carl Meyer