Django - Multiple User Profiles
Asked Answered
P

3

28

Initially, I started my UserProfile like this:

from django.db import models
from django.contrib.auth.models import User

class UserProfile(models.Model):
    user = models.OneToOneField(User)
    verified = models.BooleanField()
    mobile = models.CharField(max_length=32)

    def __unicode__(self):
        return self.user.email

Which works nicely along with AUTH_PROFILE_MODULE = 'accounts.UserProfile' set in settings.py.

However, I have two different kinds of users in my website, Individuals and Corporate, each having their own unique attributes. For instance, I would want my Individual users to have a single user only, hence having user = models.OneToOneField(User), and for Corporate I would want them to have multiple users related to the same profile, so I would have user = models.ForeignKey(User) instead.

So I thought about segregating the model into two different models, IndivProfile and CorpProfile, both inheriting from UserProfile while moving the model-specific attributes into the relevant sub-models. Seems like a good idea to me and would probably work, however I would not be able to specify AUTH_PROFILE_MODULE this way since I'm having two user profiles that would be different for different users.

I also thought about doing it the other way around, having UserProfile inherit from multiple classes (models), something like this:

class UserProfile(IndivProfile, CorpProfile):
    # some field

    def __unicode__(self):
        return self.user.email

This way I would set AUTH_PROFILE_MODULE = 'accounts.UserProfile' and solve its problem. But that doesn't look like it's going to work, since inheritance in python works from left to right and all the variables in IndivProfile will be dominant.

Sure I can always have one single model with IndivProfile and CorpProfile variables all mixed in together and then I would use the required ones where necessary. But that is just doesn't look clean to me, I would rather have them segregated and use the appropriate model in the appropriate place.

Any suggestions of a clean way of doing this?

Plead answered 10/3, 2012 at 20:58 Comment(6)
What about inheritance?Char
I could make UserProfile abstract and let IndivProfile and CorpProfile inherit from UserProfile. This is still not going to solve the issue with AUTH_PROFILE_MODULE. Which one is it going to point at?Plead
How about adding corporate + individual + account_type fields to one Model? I think it could be OK if you have only two types, but may be bad if you'll have more than 2 or those profiles are very different.Aliunde
That won't solve the issue of user being different for each type. Please read the post, I have already mentioned I can do it but I'm looking for a clean approach.Plead
A minor issue: if you have multiple users related to the same corporate profile, you can't model that with a ForeignKey field on the corporate profile.Statuary
What about point to abstract class and use inheritanceManager model utils?Char
P
9

I have done it this way.

PROFILE_TYPES = (
    (u'INDV', 'Individual'),
    (u'CORP', 'Corporate'),
)

# used just to define the relation between User and Profile
class UserProfile(models.Model):
    user = models.ForeignKey(User)
    profile = models.ForeignKey('Profile')
    type = models.CharField(choices=PROFILE_TYPES, max_length=16)

# common fields reside here
class Profile(models.Model):
    verified = models.BooleanField(default=False)

I ended up using an intermediate table to reflect the relation between two abstract models, User which is already defined in Django, and my Profile model. In case of having attributes that are not common, I will create a new model and relate it to Profile.

Plead answered 17/3, 2012 at 11:5 Comment(2)
How did you store the uncommon fields? Can you show the code for that?Otolith
Looking back almost 4 years later - is there anything you would have done different?Hypothermal
C
25

You can do this in following way. Have a profile which will contains common fields which are necessary in both profiles. And you have already done this by creating class UserProfile.

class UserProfile(models.Model):
    user = models.ForeignKey(User)
    # Some common fields here, which are shared among both corporate and individual profiles

class CorporateUser(models.Model):
    profile = models.ForeignKey(UserProfile)
    # Corporate fields here

    class Meta:
        db_table = 'corporate_user'

class IndividualUser(models.Model):
    profile = models.ForeignKey(UserProfile)
    # Individual user fields here

   class Meta:
        db_table = 'individual_user'

There is no rocket science involved here. Just have a keyword which will distinguish between corporate profile or individual profile. E.g. Consider that the user is signing up. Then have a field on form which will differentiate whether the user is signing up for corporate or not. And Use that keyword(request parameter) to save the user in respective model.

Then later on when ever you want to check that the profile of user is corporate or individual you can check it by writing a small function.

def is_corporate_profile(profile):
    try:
        profile.corporate_user
        return True
    except CorporateUser.DoesNotExist:
        return False

# If there is no corporate profile is associated with main profile then it will raise `DoesNotExist` exception and it means its individual profile
# You can use this function as a template function also to use in template
{% if profile|is_corporate_profile %}

Hope this will lead you some where. Thanks!

Cinerarium answered 11/3, 2012 at 19:35 Comment(7)
Is not preferable a OneToOne instead a ForeignKey?Char
Specifying Foreign key is also some what related to oneToOne relationship.Cinerarium
without OneToOne you should write: try: profile.corporate_user_set.all()[0]Char
To make that clear: OneToOneField is just a Django internal wrapper to ForeignKey(unique=True). So OTO is not just somewhat related, it is exactly the same as a ForeignKey.Electrolyte
@Electrolyte you are wrong on that! OneToOneField is not just a internal wrapper to ForeignKey(unique=True) which suggests they are equivalent... They are not! https://mcmap.net/q/73339/-onetoonefield-vs-foreignkey-in-djangoBonilla
except: It would be best to specify te exception type here. Even with small try/except blocks it's good to be specificSprag
@AamirAdnan: thanks :) I'm sure that change will save a lot of people from shooting themselves in the foot. Very nice answerSprag
P
9

I have done it this way.

PROFILE_TYPES = (
    (u'INDV', 'Individual'),
    (u'CORP', 'Corporate'),
)

# used just to define the relation between User and Profile
class UserProfile(models.Model):
    user = models.ForeignKey(User)
    profile = models.ForeignKey('Profile')
    type = models.CharField(choices=PROFILE_TYPES, max_length=16)

# common fields reside here
class Profile(models.Model):
    verified = models.BooleanField(default=False)

I ended up using an intermediate table to reflect the relation between two abstract models, User which is already defined in Django, and my Profile model. In case of having attributes that are not common, I will create a new model and relate it to Profile.

Plead answered 17/3, 2012 at 11:5 Comment(2)
How did you store the uncommon fields? Can you show the code for that?Otolith
Looking back almost 4 years later - is there anything you would have done different?Hypothermal
E
-1

Could be worth to try using a through field. The idea behind it is to use the UserProfile model as through model for the CorpProfile or IndivProfile models. That way it is being created as soon as a Corp or Indiv Profile is linked to a user:

from django.db import models
from django.contrib.auth.models import User

class UserProfile(models.Model):
    user = models.ForeignKey(User)
    profile = models.ForeignKey(Profile, related_name='special_profile')

class Profile(models.Model):
    common_property=something

class CorpProfile(Profile):
    user=models.ForeignKey(User, through=UserProfile)
    corp_property1=someproperty1
    corp_property2=someproperty2

class IndivProfile(Profile):
    user=models.ForeignKey(User, through=UserProfile, unique=true)
    indiv_property1=something
    indiv_property2=something

I think that way it should be possible to set AUTH_PROFILE_MODULE = 'accounts.UserProfile', and every time you create either a CorpProfile or a IndivProfile that is linked to a real user a unique UserProfile model is created. You can then access that with db queries or whatever you want.

I haven't tested this, so no guarantees. It may be a little bit hacky, but on the other side i find the idea quite appealing. :)

Electrolyte answered 11/3, 2012 at 16:59 Comment(4)
I have tried running this but it showed an invalid keyword argument 'through' error. I'm at work at the moment and can't get the exact wording.Plead
Oops, through works only for ManyToMany relations. Looks like another not so clean solution now. But i think Aamir already proposed a quite simple solution.Electrolyte
@marue, should this answer be completely removed? It is incorrect and seems confusing.Bellarmine
@BasicWolf It should be removed, yes.Electrolyte

© 2022 - 2024 — McMap. All rights reserved.