How to create custom users with different roles/types?

Hello Django professionals!

I am trying to create a base accounts app that i can reuse for different projects, so i don’t have to retype all accounts app and everything that goes with it, each time i create a new project. I am trying to escape from WordPress so it is a big challange, but i love the decision i made.

So, in the accounts app i have created a custom user.
Everything that i have learned so far, is working.

  • registration
  • login
  • logout
  • password change
  • password reset

Now i am trying to create types of users. As i understand, it is recommended that we use only one custom user and have different types of that user.

For us, those are:

  • Admin
  • Company owner
  • Company employees
  • Customers

Here is my accounts app models, for Custom User

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

class User(AbstractUser):
    # NEW FOR THE ROLES
    class Types(models.TextChoices):
        ADMIN = "ADMIN", "Admin"
        OWNER = "OWNER", "Owner"
        EMPLOYEE = "EMPLOYEE", "Employee"
        CUSTOMER = "CUSTOMER", "Customer"

    type = models.CharField(
        max_length=20, choices=Types.choices, default=Types.ADMIN
    )

    email = models.EmailField(unique=True, max_length=50)
    username = models.CharField(unique=True, max_length=20)

    note = models.TextField(max_length=200, null=True, blank=True)

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = ["username"]

    def __str__(self):
        return self.email

So i have defined a custom user, imported it into Settings.py and everything is working.

The problem occures when i want to create Types of users. I am having troubles finding the content online that i could learn from.

Okay so this is what i have tried:

# custom model managers for user roles

class OwnerManager(models.Manager):
    def get_queryset(self, *args, **kwargs):
        return super().get_queryset(*args, **kwargs).filter(type=User.Types.OWNER)

class Owner(User):
    objects = OwnerManager()

    class Meta:
        proxy = True

    def save(self, *args, **kwargs):
        if not self.pk:
            self.type = User.Types.OWNER
        return super().save(*args, **kwargs)

Then i went into views.py, created a sepparate view for registering the owner, it is this one:


def register_view_owner(request):
    if request.method == "POST":
        form = UserRegisterForm(request.POST)
        if form.is_valid():
            # save the user but deactivate him
            user = form.save(commit=False)
            user.type = User.Types.OWNER
            form.save()
            messages.success(request, "Owner is created. login now.")
            return redirect("accounts:login_view")
    else:
        form = UserRegisterForm()

    context = {
        "form": form,
    }
    return render(request, "accounts/register-owner.html", context)

After that, i have created a sepparate url register-owner and the html page for it.

When i submit the form on that page, i do get my owner created, because i used user.type = User.Types.OWNER in the view.

I did the same for employees and customers, everhing is working. I also created a dashboard page and put in:

{% if request.user.type == 'ADMIN' %}
You are admin
{% elif request.user.type == 'OWNER' %}
You are the owner, you can create new employees and edit orders.
{% elif request.user.type == 'CUSTOMER' %}
You are a customer, you can see your orders.
{% endif %}

I did that just to test, if those different types of users will see their corresponding info, and it works.

So now the problem.

How do i assign additional data for different types of users?

For this owner, we need company name, address, url…
I tried to create a Class OwnerProfile and tried a signal, but that is what i can’t find the answer for :confused:


@receiver(post_save, sender=Owner)
def create_user_profile(sender, instance, created, **kwargs):
    if created and instance.type == 'OWNER':
        OwnerProfile.objects.create(user=instance)

# Owner Profile for more data for the owner
class OwnerProfile(models.Model):
    # relation to Owner
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    owner_first_name = models.CharField(max_length=20)
    company_name = models.CharField(max_length=50, default='Adidas')
    company_description = models.TextField(blank=True, null=True)

    def __str__(self):
        return self.owner_first_name

That is that in a nutshell :smiley:

I hope somebody can help me, thank you and best regards from Serbia :slight_smile:

Actually, that’s not how I would suggest approaching this.

We have found that the easiest way to handle something like this is to use Django’s Group model as a “Role” being performed by a user. (Depending upon your specific requirements, it’s possible that, for example, an Employee might also be a Customer - or that, over time, a Customer might become an Employee.)

These roles then are assigned the appropriate Permissions for the functionality that those roles can perform.

Then, if there’s role-specific profile data needing to be stored, you could still have your profile models, each with a OneToOne relationship with User.

This all logically separates the authentication components of the website from the authorization components. Someone whose role changes is no longer required to register a new account. Or, someone performing two roles doesn’t need to maintain two accounts.

1 Like

Thank you. Can you please explain how to approach this like you suggested?

Can you be more specific regarding the details you’re looking for?

I can, but i don’t know how to start based on your advice.

Comparing to what i did and you recommended a different approach, i just wanted to ask, how would i go about it?

I would define that same custom user, but i don’t know how to continue in order to achieve what we need.

Essentially:

  • Read the docs and the sample code at Using the Django authentication system | Django documentation | Django and Customizing authentication in Django | Django documentation | Django. (You’re not “done” with this step until you understand what each line of code is doing in the examples. You don’t need to learn it all up front - you’ll probably be cycling through this material multiple times as you’re working your way through this.)
  • Define the models for the different profile models
  • Create the Groups
  • Assign the right permissions to the right Groups
    • For example, your AdminGroup would probably have (at a minimum) the permissions ‘accounts.add_adminprofile’, ‘accounts.change_adminprofile’, and ‘accounts.view_adminprofile’
  • Define your workflows for the management of these users and roles
    • Who is going to assign users to roles?
    • What are all the steps necessary for a new user to be registered?
  • Create your views
    • Define the urls
    • Create your forms and templates as necessary
1 Like

Thank you very much, i will do that.

Hi, i will give some feedback.
I have read through the docs, it is a bit hard to understand. I have made changes in the process, but i still have trouble assigning a user to a group during registration.

This is one role and profile i had, for the employee.


def register_employee(request):
    form = UserRegisterForm()
    if request.method == 'POST':
        form = UserRegisterForm(request.POST)
        if form.is_valid():
            user = form.save(commit=False)
            user.is_active = False
            user.role = User.EMPLOYEE
            group = Group.objects.get(name='Employees')
            request.user.groups.add(group)
            user.save()
            messages.success(request, 'Employee is created!')
            return redirect('accounts:login_view')
    
    context = {
        'form': form,
    }
    return render(request, 'accounts/register-employee.html', context)
    

I am doing something wrong, because i get this error

ValueError at /accounts/register-employee/

“<User: employeetest@emtest.com>” needs to have a value for field “id” before this many-to-many relationship can be used.

When i comment that out, it all works again, and the role is assigned, just the problem with putting the user in a group is still present.

Maybe you can give me an advice?

Thank you

p.s. users will NOT be able to switch roles.

You’re really close here.

The first key point to remember is that adding or removing group membership does not change the User object. Group membership is defined by a many-to-many relationship, which means the only table that is affected by this is the “through” table.

The second point is that using a Group relationship for roles means that you no longer need or use a “role” attribute for role.

The effect of this?

  • The line user.role = User.EMPLOYEE can be deleted, along with the related field. (Although, I don’t see a role attribute on User.

  • To perform the request.user.groups.add function, the user object must be saved - that’s specifically what the error message is telling you. You need to move the user.save() call to immediately after the user.is_active line.

Thank you very much! I was reading the docs and looking over a bunch of videos, so that is why i changed the code. If you can please see the bellow models, for this current version.

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

class User(AbstractUser):
    email = models.EmailField(unique=True, max_length=50)
    username = models.CharField(unique=True, max_length=20)

    note = models.TextField(max_length=200, null=True, blank=True)

    # ROLES
    OWNER = 1
    CUSTOMER = 2
    EMPLOYEE = 3
    ROLE_CHOICES = (
        (OWNER, "Owner"),
        (CUSTOMER, 'Customer'),
        (EMPLOYEE, 'Employee'),
    )
    role = models.PositiveSmallIntegerField(choices=ROLE_CHOICES, blank=True, null=True)


    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = ["username"]

    def __str__(self):
        return self.email


# CREATE USER PROFILE CONNECTED TO USER
class UserProfile(models.Model):
    # relation to user
    user = models.OneToOneField(User, on_delete=models.CASCADE, blank=True, null=True)
    #profile_image = models.ImageField(upload_to='', default='')
    adress = models.CharField(max_length=100, blank=True, null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    modified_at = models.DateTimeField(auto_now=True)
    
    def __str__(self):
        return self.user.email


############################################################################################################

# SIGNALS

from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    print(created)
    if created:
        UserProfile.objects.create(user=instance)
        print('user profile is created.')    
    else:
        try:
            profile = UserProfile.objects.get(user=instance)
            profile.save()
            print('user is updated')    
        except:
            # create user profile is it dosn't exist
            UserProfile.objects.create(user=instance)
            print('Profile did not exist, but i created one now.')

Then in views.py


# REGISTER OWNERS OF THE COMPANY
def register_owner(request):
    form = UserRegisterForm()
    if request.method == 'POST':
        form = UserRegisterForm(request.POST)
        if form.is_valid():
            owner = form.save(commit=False)
            owner.role = User.OWNER
            owner.is_active = False
            owner.save()
            group = Group.objects.get(name='Owners')
            group.user_set.add(owner)
            messages.success(request, 'You have successfully registered as an owner.')
            return redirect('accounts:login_view')
    
    context = {
        'form': form,
    }
    return render(request, 'accounts/register-owner.html', context)


# REGISTER COMPANY EMPLOYEES
def register_employee(request):
    form = UserRegisterForm()
    if request.method == 'POST':
        form = UserRegisterForm(request.POST)
        if form.is_valid():
            employee = form.save(commit=False)
            employee.role = User.EMPLOYEE
            employee.is_active = False
            employee.save()
            group = Group.objects.get(name='Employees') 
            group.user_set.add(employee)
            messages.success(request, 'Employee is created!')
            return redirect('accounts:login_view')
    
    context = {
        'form': form,
    }
    return render(request, 'accounts/register-employee.html', context)
    

Admin

User profiles

Also, when i click on employee or the owner user, i do see the group assigned to them

Now, i understand i can create permissions for each group and check views with those permissions.

But, i am having troubles understanding what if those different roles have different fields of data?
Especially when i add a CUSTOMER registration.

I tried to create a signal for create_owner_user_profile and put if created and instance.role == “OWNER”:
I did the same for Employees. But then in the admin, i see the same profile in both Owner profiles and Employee profiles. So i deleted them…and came back to the code i sent now.

I hope you can give me a few pointers, i feel i am close as you say, but i am just confused a lot :slight_smile:

Thank you!