Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing multiple user types with Django 1.5

What is the recommended way to implement multiple user types using Django 1.5's new configurable user model functionality?

I would like to have two user types: private users and trade users, each with their own set of required fields.

There are two ways I can think to implement this:

1) Multi-table inheritance

class BaseUser(AbstractBaseUser):
  email = models.EmailField(max_length=254, unique=True)
  # ...


class PrivateUser(BaseUser):
  first_name = models.CharField(max_length=30)
  last_name = models.CharField(max_length=30)
  # ...


class TradeUser(BaseUser):
  company_name = models.CharField(max_length=100)
  # ...

Are there any problems with using multi-table inheritance in conjunction with the configurable user model?

2) Using a single model with a "type" attribute

class User(AbstractBaseUser):
  email = models.EmailField(max_length=254, unique=True)
  user_type = models.CharField(max_length=30, choices={
    'P': 'Private',
    'T': 'Trade',
  })
  first_name = models.CharField(max_length=30, blank=True)
  last_name = models.CharField(max_length=30, blank=True)
  company_name = models.CharField(max_length=100, blank=True)
  # ...

This method would require some conditional validation dependent upon user_type.

Which of these methods best suits my use case? Or perhaps there is a better way of achieving this?

Also, In case number 1, how can I filter my users?

Thanks.

like image 440
gjb Avatar asked Dec 04 '12 09:12

gjb


People also ask

Can Django handle multiple users?

No matter what strategy you pick, or what is your business model, always use one, and only one Django model to handle the authentication. You can still have multiple user types, but generally speaking it's a bad idea to store authentication information across multiple models/tables.

How do I create multiple roles in Django REST framework?

You need to create a group for each user role, and add needed permissions for each group. (Django has a default model permission, created automatically, look at the docs on the given links) or create the needed permission manually in the model definition.

How many authentication models should I use in Django?

1. No matter what strategy you pick, or what is your business model, always use one, and only one Django model to handle the authentication. You can still have multiple user types, but generally speaking it’s a bad idea to store authentication information across multiple models/tables.

How to differentiate between normal users and admin users in Django?

For that case, you can use the built-in is_staff flag to differentiate normal users from admin users. Actually the built-in User model has two similar fields, is_staff and is_superuser. Those flags are used in the Django Admin app, the is_staff flag designates if the user can log in the Django Admin pages.

How to create a many to many relationship in Django?

If your application handle many user types, and users can assume multiple roles, an option is to create an extra table and create a many to many relationship: class Role(models.Model): ''' The Role entries are managed by the system, automatically created via a Django data migration.

How do I handle multiple user types on my application?

If the users on your application can assume multiple roles at the same time (e.g. be a Student and Teacher), or your application will have only a few user types, you can control that information in the central User model and create flags like is_student and is_teacher: This is perhaps the easiest way to handle multiple user types.


4 Answers

Warning: Django 1.5 is very new and the people are still investigating its new features. So my answer is nothing more than my opinion, based on recent research to answer this question.

Both ways are valid ways to achieve the result, with its advantages and disadvantages.

Let's start with the:

Second option

  • Without nested models and not modular. AbstractBaseUser, as the name says, is an abstract model and does not have a specific table
  • Has unused fields
  • You need to check the user_type for any iteration with the model that uses the extra fields:

    def foo():
        if user.user_type == 'Private':
            # ...
        else:
            # ...
    

The resulting SQL would be approximately as follows:

CREATE TABLE "myapp_user" (
    "id" integer NOT NULL PRIMARY KEY,
    "password" varchar(128) NOT NULL,
    "last_login" datetime NOT NULL,
    "email" varchar(254) NOT NULL UNIQUE,
    "user_type" varchar(30) NOT NULL,
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(30) NOT NULL,
    "company_name" varchar(100) NOT NULL
);

First option

  • Nested models with logical separation of entities
  • Very lean
  • You must implement BaseUserManager for each child if you want to use create_user-like functions
  • You cannot access the subclasses with a simple BaseUser.objects.all()*

The resulting SQL would be approximately as follows:

CREATE TABLE "myapp_baseuser" (
    "id" integer NOT NULL PRIMARY KEY,
    "password" varchar(128) NOT NULL,
    "last_login" datetime NOT NULL,
    "email" varchar(254) NOT NULL UNIQUE
);

CREATE TABLE "myapp_privateuser" (
    "baseuser_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "myapp_baseuser" ("id"),
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(30) NOT NULL
);

CREATE TABLE "myapp_tradeuser" (
    "baseuser_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "myapp_baseuser" ("id"),
    "company_name" varchar(100) NOT NULL
);

* Imagine the following situation:

>>> BaseUser.objects.create_user('[email protected]', password='baseuser')
>>> PrivateUser.objects.create_user('[email protected]', password='privateuser', first_name='His', last_name='Name')
>>> TradeUser.objects.create_user('[email protected]', password='tradeuser', company_name='Tech Inc.')
>>> BaseUser.objects.all()
[<BaseUser: [email protected]>, <BaseUser: [email protected]>, <BaseUser: [email protected]>]
>>> PrivateUser.objects.all()
[<PrivateUser: [email protected]>]
>>> TradeUser.objects.all()
[<TradeUser: [email protected]>]

So, you cannot directly retrieve the subclasses instances by using BaseUser.objects.all(). There is an excellent blog post by Jeff explaining better how to accomplish "automatic downcast" from BaseUser to its childs.

That said, you should consider the advantages and disadvantages of each approach and their impact on your project. When the involved logic is small (as in the example described), both approaches are valid. But in a more complex scenario, an approach can be better than the other one. I would choose the multi model option because it is more extensible.

like image 59
borges Avatar answered Oct 24 '22 02:10

borges


Maybe you should consider AbstractUser?

like image 25
Robert Avatar answered Oct 24 '22 00:10

Robert


The new custom user model you can assign only one model to AUTH_USER_MODEL. With multi-table inheritance you have two models. So that is a problem.

In the case of a single user model that covers both user types you could abstract the conditional logic in the model methods. You can also use different managers for different user types based on how much they are different. This could also help you in being explicit when working with a specific user type.

Other option could be to store only most common attributes in a single user model and then attach specifics of two user types into their own tables which are linked to your main user table.

If both users have most of the things in common (in terms of data at least), I would keep them in one place. In the end, I would think about what is simpler and easier to maintain.

like image 2
maulik13 Avatar answered Oct 24 '22 02:10

maulik13


I'd use a single model with a "type" attribute. Here is why:

  • Only one table, only one model
  • If you want to convert from one type to another you just change attribute
  • Implement getters and setters for the fields that exists for one type and doesn't exists for other = much more simple to use.
like image 1
Vladimir Prudnikov Avatar answered Oct 24 '22 02:10

Vladimir Prudnikov