Different auth user models withoout doing multi-table inheritance

109 views
Skip to first unread message

Ankit Agrawal

unread,
Aug 10, 2015, 9:38:16 AM8/10/15
to Django users

I am working on a project which has two different sets of users - Customer and Merchant. Both of these users should be able to register and login to their respective profiles. The most obvious choice to implement this that came to my mind was to make two different models Customer and Merchant that inherit from a BaseUser model that will store the common fields i.e. Multi-table inheritance - https://docs.djangoproject.com/en/1.8/topics/db/models/#multi-table-inheritance


Quoting Two Scoops of Django -


At all costs, everyone should avoid multi-table inheritance (see warning above) since it adds both confusion and substantial overhead...
Adds substantial overhead since each query on a child table requires joins with all parent tables.

I would like to know if and why having an explicit OneToOneField is better than Multi-table inheritance. Also, are there any other better ways to model the above relationship?

Avraham Serour

unread,
Aug 10, 2015, 10:43:03 AM8/10/15
to django...@googlegroups.com

I personally like a profile model, but if you implement two you may have headaches when doing a reverse relation from user, you would need to check every time of the request.user is a customer or merchant.

In any case what is the difference between them anyway? Why can't a user be both?


--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/81954b62-2c89-404f-94a5-5f9a485c28c8%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Robin Lery

unread,
Aug 10, 2015, 12:24:39 PM8/10/15
to django...@googlegroups.com

You surely can use Choice field for either Customer or Merchant in your custom user class.

Carl Meyer

unread,
Aug 10, 2015, 12:47:13 PM8/10/15
to django...@googlegroups.com
Hello Ankit,

On 08/10/2015 09:38 AM, Ankit Agrawal wrote:
> I am working on a project which has two different sets of users -
> |Customer| and |Merchant|. Both of these users should be able to
> register and login to their respective profiles. The most obvious choice
> to implement this that came to my mind was to make two different models
> |Customer| and |Merchant| that inherit from a BaseUser model that will
> store the common fields i.e. Multi-table inheritance -
> https://docs.djangoproject.com/en/1.8/topics/db/models/#multi-table-inheritance
>
>
> Quoting |Two Scoops of Django| -
>
>
> |At all costs, everyone should avoid multi-table inheritance (see warning above) since it adds both confusion and substantial overhead...
> Adds substantial overhead since each query on a child table requires joins with all parent tables.
>
> |
>
> I would like to know if and why having an explicit |OneToOneField| is
> better than Multi-table inheritance.

In terms of the underlying database schema (and thus query efficiency),
a OneToOneField is exactly the same as MTI (MTI uses a OneToOneField
internally).

Personally, I agree with the _Two Scoops_ authors that the explicit
OneToOneField is preferable, because it means the ORM objects more
closely map to the actual database schema, making it easier to
understand what is actually happening at the database level. An explicit
OneToOneField also gives you more flexibility in several ways - you can
create / delete the base User instance and the linked CustomerProfile /
MerchantProfile instances separately from each other, and you can even
have a single User instance with both a CustomerProfile and a
MerchantProfile (though that may not be useful for your case).

> Also, are there any other better
> ways to model the above relationship?

That depends on how much different information you need to store about
customers vs merchants. Assuming it's significant, I think a common User
model with OneToOne-linked CustomerProfile and MerchantProfile is
probably best (I'm doing the same thing in my current project in a
similar situation). I also recommend having a `role` or similar field on
the User model, with 'merchant' and 'customer' as choices. This means
you can know what type of user a given user is while querying only the
User table (so you know which profile table to query for that user), and
it also means that you can have users with a known role who haven't
created their profile yet (this may or may not be useful for your
application, depending how/when profiles are created).

Carl

signature.asc

James Schneider

unread,
Aug 10, 2015, 12:48:25 PM8/10/15
to django...@googlegroups.com

If you want to avoid MT inheritance, look at inheriting from AbstractBaseUser using a custom abstract class where you can add your extra custom fields. Your two types of users (inheriting from your new abstract class) would then be standard, separate models.

-James

Ankit Agrawal

unread,
Aug 10, 2015, 1:30:02 PM8/10/15
to django...@googlegroups.com
@James: Even if I implement a Custom abstract class inheriting from AbstractBaseUser, the Custom Abstract Class would have a explicit OneToOneField to the AbstractBaseUser, which essentially would mean having overheads due to JOINs as in MTI.


Ankit Agrawal,
IIT Bombay.

--
You received this message because you are subscribed to a topic in the Google Groups "Django users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-users/aV_PTRRD__s/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-users...@googlegroups.com.

To post to this group, send email to django...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-users.

James Schneider

unread,
Aug 10, 2015, 1:50:43 PM8/10/15
to django...@googlegroups.com
That is an incorrect assumption. Abstract model classes do not generate any database tables, and are specifically designed for inheritance purposes (otherwise they are pretty much useless). It is impossible for a OneToOne field to exist that points to an abstract class.


If you need a similar example, look at how Django defines its own User class:


User inherits from AbstractUser (abstract class that adds username/password fields, etc.), which in turn inherits from AbstractBaseUser (abstract class that adds other methods for grabbing various attributes of a User object). There are no excessive joins created by queries against the User class because both AbstractUser and AbstractBaseUser are both...abstract. They don't exist in the DB. 

-James

Ankit Agrawal

unread,
Aug 10, 2015, 3:11:08 PM8/10/15
to Django users
Hi James,

     Correct me if I am wrong but if I understood you correctly, I should be able to implement it this way -

class User(AbstractBaseUser, PermissionsMixin):
    common_fields_go_her
= ...

    objects
= CustomUserManager()
   
class Meta:
       
abstract = True


class Customer(User):
    customer_specific_fields
= ...


class Merchant(User):
    merchant_speicifc_fields
= ...

In this case, what would be AUTH_USER_MODEL? If i am not wrong, an abstract base class cannot be an AUTH_USER_MODEL.

James Schneider

unread,
Aug 10, 2015, 5:01:11 PM8/10/15
to django...@googlegroups.com
On Mon, Aug 10, 2015 at 12:11 PM, Ankit Agrawal <aaaag...@gmail.com> wrote:
Hi James,

     Correct me if I am wrong but if I understood you correctly, I should be able to implement it this way -

class User(AbstractBaseUser, PermissionsMixin):
    common_fields_go_her
= ...

    objects
= CustomUserManager()
   
class Meta:
       
abstract = True


class Customer(User):
    customer_specific_fields
= ...


class Merchant(User):
    merchant_speicifc_fields
= ...



Yes this is exactly what I was thinking. You were asking about avoiding MTI, and this is how you would do it. 

 
In this case, what would be AUTH_USER_MODEL? If i am not wrong, an abstract base class cannot be an AUTH_USER_MODEL.


Ah, now we get back in to Carl's original answer and why having a single (probably custom) user class with a separate profile table makes sense. Obviously you can't point AUTH_USER_MODEL at the abstract class, leaving you with a choice about whether you want Customer's or Merchant's to be the target of AUTH_USER_MODEL. Not ideal either way. It was unclear to me whether or not you were going to use the default auth backends to log both types of users in, or if those were simply storage models, with the actual users who log in to the system in a separate model (which is where AUTH_USER_MODEL would be pointing).

You could have a single CustomUser class that has a static choices=((1, 'Customer'),(2,'Merchant')) for a particular field so that a DB hit is not involved whenever the user is pulled to make the determination as to the type of user. Not very flexible, but cheap to process.

If you are planning on having a ton of fields added to your CustomUser class, I would highly recommend you take the DB hit and create a separate set of related models to a single CustomUser (similar to the profile idea that Carl mentioned). That way you aren't returning a ton of data for every single request, which can get expensive in the long run, even with only using a single query. 

# models.py
class CustomUser(AbstractBaseUser, PermissionsMixin):
    # small number of fields needed for almost every single request
    # should rarely change structure via migrations

class CustomerData(models.Model):
    # OneToOne back to CustomerUser
    # Other fields that are not necessarily needed for every request, loaded on demand
    # may be often modified by migrations

class MerchantData(models.Model):
    # OneToOne back to CustomerUser
    # Other fields that are not necessarily needed for every request, loaded on demand
    # may be often modified by migrations

# settings.py
AUTH_USER_MODEL = 'myapp.models.CustomUser'


Note that you'll want your AUTH_USER_MODEL to change as little as possible in production. Errors in that model will often have a far-reaching effect. The two Data models will likely change more often, and will probably have less of an impact in the event of an issue with a migration.

As Carl mentioned the 2Scoops authors were not saying that OneToOne relationships were bad and to never use them, they just don't like the 'implied' OneToOne relationships Django creates when MTI is used. OneToOne fields are not something that should be made scary, rather, they should simply be made explicit in the code.

-James

Ankit Agrawal

unread,
Aug 10, 2015, 10:00:38 PM8/10/15
to Django users

Hi James, thanks for the elaborate answer. Not that explicit OneToOne relationships are bad but I was just trying to avoid the overhead due to the JOINs that comes with it.
-James

James Schneider

unread,
Aug 11, 2015, 1:03:05 PM8/11/15
to django...@googlegroups.com

Hi James, thanks for the elaborate answer. Not that explicit OneToOne relationships are bad but I was just trying to avoid the overhead due to the JOINs that comes with it.

Understood. Unless you specifically request the data either by accessing the .merchant_data FK attribute or use select_related(), you shouldn't see a JOIN. I stumbled across this video yesterday where Russell talks about the user model and having only minimal authentication data in the primary model vs. profile data in ancillary models, along with a bunch of other fun topics like tracking names and genders within the user models.


-James
Reply all
Reply to author
Forward
0 new messages