Django: Why create a OneToOne to UserProfile instead of subclassing auth.User?
Asked Answered
K

4

10

Note: If you are tempted to 'answer' this question by telling me that you don't like django.contrib.auth, please move on. That will not be helpful. I am well aware of the range and strength of opinions on this matter.

Now, the question:

The convention is to create a model, UserProfile, with a OneToOne to User.

In every way I can think of, a more efficient and effective approach is to subclass User to a class that one intends to use for every human in the system - a class called, say, Person(User).

I have not seen a coherent explanation of why the former is conventional and the latter is regarded as a hack. A while ago, I changed over to the OneToOne approach so as to gain the ability to use get_profile() and I have regretted it ever since. I'm thinking of switching back unless I can be made to understand the advantage of this approach.

Kuching answered 27/3, 2011 at 20:44 Comment(0)
D
4

You do realise, don't you, that model subclassing is implemented by means of a OneToOne relationship under the hood? In fact, as far as efficiency is concerned, I cannot see any difference at all between these two methods.

Subclassing of existing concrete models is, in my opinion, a nasty hack that should be avoided if at all possible. It involves hiding a database relationship so that it is unclear when extra db access is performed. It's much clearer to show the relationships explicitly, and access them explicitly where necessary.

Now, a third alternative which I do like is to create a completely new User model, along with a custom authentication backend that returns instances of the new model instead of the default one. Creating a backend only involves defining a couple of simple methods, so it's very easy to do.

Dragonfly answered 27/3, 2011 at 20:56 Comment(5)
Yes, of course I realize that subclassing creates an "implicit" OneToOne. In terms of clarity and efficiency, person.email is way clearer than userprofile.user.email. Can you flesh out what the problem is with subclassing of existing models? Your third alternative has me very interested. Is there a good document that I can read about this technique?Kuching
Also, on your second paragraph: Can't you make the same claim vis a vis hiding database hits with an ordinary OneToOne on a concrete model? Or are you saying that since you don't always need the PK of the parent, you can sometimes save the trip? If so, that's a fair enough point, but it doesn't really apply here, since we're always (at least, in every case I can think of) going to want to go to User anyway.Kuching
when I subclassed User, it worked for a while, but later when I created a manytomany "through" relationship, when the through object would try to access the FK to the subclassed User module, it would actually access the user model instead, which caused all sorts of problems & wasn't really correctible without going into django internals. This was django 1.3.1. I switched to just having profile objects which point to some user objects & it was solved.Dorkus
Is this post outdated? I don't get why it's a "nasty hack", especially as Django now has control over setting a custom User model in settings, and everything that wants a "user" should fetch it with get_user_model() (don't know when that stuff was added though)Crossbill
Subclassing a concrete model was and is still a nasty hack. Subclassing an abstract model, as you do when you subclass AbstractUser to create a custom User model, is perfectly fine, although it was not available when I wrote this post.Dragonfly
S
2

There's never really been a good explanation, at least from "official" sources as to why, in practice, subclassing User is less useful than having a UserProfile.

However, I have a couple of reasons, that came up after I had decided myself that subclassing User was "the way to go".

  • You need a custom authentication backend. This is not a big issue, but the less code you need to write, the better.
  • Other apps may be assuming that your User is a django.contrib.auth.models.User. Mostly this will be okay, unless that code is fetching User objects. Because we are a subclass, any code just using our User objects should be fine.
  • A User may only 'be' one sub-class at a time. For instance, if you had User subclasses of Student and Teacher, then at a given time, your User would only be able to be a Teacher or a Student. With UserProfiles, there could be both a Teacher and a Student profile attached to the same user at the same time.
  • Following on, converting from one sub-class to another is hard: especially if you have an instance of one sub-class already.

So, you may say, "my project will only ever have the one User subclass". That's what I thought. Now we have three, plus regular Users, and possibly a fourth. Requirements change, having to change heaps of code to deal with that is not much fun.

note: There has been quite a lot of discussion on django-developers recently about a better fix to the issues related to the contrib.auth User model.

Sapiential answered 10/7, 2012 at 3:51 Comment(1)
If any other apps assume django.contrib.auth.models.User, they should be rewritten to use get_user_model()Crossbill
B
0

Is it more efficient and effective to inherit the User model? I don't see why, but I'd like to read your arguments. IMNSHO, model inheritance has always been a pain.

Yet, this may not answer your question, but I'm quite satisfied with the solution proposed by Will Hardy in this snippet. By taking advantage of signals, it automatically creates a new user profile for every new user.

The link is unlikely to disappear, but here's my slightly different version of his code:

from django.contrib.auth.models import User
from django.db import models
from django.db.models.signals import post_save
from django.utils.translation import ugettext_lazy as _

class AuthUserProfileModelBase(models.base.ModelBase):
    # _prepare is not part of the public API and may change
    def _prepare(self):
        super(AuthUserProfileModelBase, self)._prepare()
        def on_save(sender, instance, created, **kwargs):
            if created:
                self.objects.create(user=instance)
        # Automatically link profile when a new user is created
        post_save.connect(on_save, sender=User, weak=False)

# Every profile model must inherit this class
class AuthUserProfileModel(models.Model):
    class Meta:
        abstract = True
    __metaclass__ = AuthUserProfileModelBase
    user = models.OneToOneField(User, db_column='auth_user_id',
        primary_key=True, parent_link=True)

# The actual profile model
class Profile(AuthUserProfileModel):
    class Meta:
        app_label = 'some_app_label'
        db_table = 'auth_user_profile'
        managed = True
    language = models.CharField(_('language'), max_length=5, default='en')

Of course, any credit goes to Will Hardy.

Blinking answered 27/3, 2011 at 21:37 Comment(10)
One of the main reasons I regard inheritance as favorable, at least in theory, is expressed succinctly by the P.S. in Will's snippet: "(PS: It would also be nice to have the resulting class proxy the User object's attributes like django's model inheritance does, while still automatically creating a UserProfile object when a User object is created :-)"Kuching
@Justin: I don't see it as a real advantage mostly because the proxy is already one property away - profile_instance.user.desired_attribute.Blinking
Welp, there are times when I don't know whether I have an AfricanSwallow or Swallow object, and I want to be able to access its coconuts either way. It strikes me an unpythonic to need to determine that in advance - that's the whole point of class inheritance to begin with.Kuching
(and btw, this discussion bumps squarely into a another question of mine, the answer to which will get you 50 points and a lot of love from me :-). It is here: #5348657)Kuching
@Justin: I don't see a connection. In fact, the pointed question seems fairly distant from what we're discussing here. When would that question arise within the scenario of your original question? Sorry, but I fail to realise.Blinking
When I have a parent object and I want to access a method of the child object. The status of the child object as a member of a subclass is significant because I may want to access fields (or, for that matter), methods of the parent.Kuching
@Justin: I understand that, but IMO it has no connection with your original question. And I believe you know that because you already has it as a different question. You'll face this same problem either way you solve this, via inheritance or not. I'd solve that by adding a profile_type property to AuthUserProfileModel. This way any raw query can also distinct between them. The difficulty here is that Django only allows you to configure a single profile class.Blinking
Why not DRY? AuthUserProfileModel is a base class, so you could put the shared properties/methods in there.Blinking
In the scenario, Where is the authoritative place where one looks to understand what type of profile this is? There are two places: 1) the inherent knowledge in the type ob object it is, and 2) in profile_type.Kuching
I agree with that point. Though the answer to your other question should solve this. In the mean time, we have to accept how Python works, or find that answer ourselves.Blinking
H
0
  1. If you are having just one type of user, like any social media site facebook or Instagram then go for inheritance.

  2. If your application has different type of users, like accounts, student, teacher, inventory manager then go for oneToone relationship.

Holifield answered 16/3, 2023 at 4:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.