Sitemap
Rahber Abbas Abidi

Rahber Abbas Abidi

Backend Developer

Implementing Multiple User Types in Django

6 min readMar 23, 2023

--

Press enter or click to view image in full size

Django is a versatile framework for web development. But it has many back-draws.

  1. It still uses the Username, Password system instead of Email.
  2. Although it is simple to create a custom user, you cannot do it and implement the Email login method together so easily for multiple user types.

There are many tutorials out there that teach and show how to create a custom user model and other tutorials that show how to implement the Email login system. But what if we want to implement both of these together? When I had to do it myself it was a hellish experience. I spent 3 days trying and reading every possible tutorial out there on this topic, although i could implement these methods independently, I just could not get them to work together.
One of the reasons for this was i did not understand most of the code (which I still don’t!) and also i had only a grasp of the fundamental functionalities of Django. I did not understand how forms worked or what in the world **kwargs meant. This is true for most beginners. So I will be showing a fast and not-so-efficient way to implement both these functionalities in your project with just the basic concepts of django. You can just copy paste the code snippets and (like me) you don’t need to understand what it does, but still you can use and modify it to your wish.

Create the Models

So, Like you have two types of users in your Django project i.e. Candidate and Organization User .

The First user is Candidate User in which it register with

  1. Email
  2. Name
  3. Phone Number
  4. Password

And The Second user i.e. Organization User. It register with

  1. Email
  2. Organization name
  3. Person Name
  4. Password

So we need to understand how our models will work here

from django.db import models
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, PermissionsMixin
from django.utils.translation import gettext_lazy as

# Create your models here.
class UserManager(BaseUserManager):

def create_user(self, email, password=None):
"""
Creates and saves a User with the given email and password.
"""
if not email:
raise ValueError('Users must have an email address')

user = self.model(
email=self.normalize_email(email),
)
user.set_password(password)
user.save(using=self._db)

return user

def create_candiate_user(self, email, name, mobile, password=None, password2=None):
"""
Creates and saves a User with the given email and password.
"""
if not email:
raise ValueError('Users must have an email address')

user = self.model(
email=self.normalize_email(email),
name = name,
mobile = mobile
)

user.set_password(password)
user.save(using=self._db)
return user

def create_orginisation(self, email, name, organization_name, password=None, password2=None):
"""
Creates and saves a User with the given email and password.
"""
if not email:
raise ValueError('Users must have an email address')

user = self.model(
email=self.normalize_email(email),
name = name,
organization_name = organization_name
)

user.set_password(password)
user.save(using=self._db)
return user

def create_staffuser(self, email, password):
"""
Creates and saves a staff user with the given email and password.
"""
user = self.create_user(
email,
password=password,
)
user.staff = True
user.save(using=self._db)
return user

def create_superuser(self, email, password):
"""
Creates and saves a superuser with the given email and password.
"""
user = self.create_user(
email,
password=password,
)
user.staff = True
user.admin = True
user.save(using=self._db)
return user

class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(
verbose_name='email address',
max_length=255,
unique=True,
)
is_active = models.BooleanField(default=True)
staff = models.BooleanField(default=False) # a admin user; non super-user
admin = models.BooleanField(default=False) # a superuser

objects = UserManager()
# notice the absence of a "Password field", that is built in.

USERNAME_FIELD = 'email'
REQUIRED_FIELDS = [] # Email & Password are required by default.

def get_full_name(self):
# The user is identified by their email address
return self.email

def get_short_name(self):
# The user is identified by their email address
return self.email

def __str__(self):
return self.email

def has_perm(self, perm, obj=None):
"Does the user have a specific permission?"
# Simplest possible answer: Yes, always
return True

def has_module_perms(self, app_label):
"Does the user have permissions to view the app `app_label`?"
# Simplest possible answer: Yes, always
return True

@property
def is_staff(self):
"Is the user a member of staff?"
return self.staff

@property
def is_admin(self):
"Is the user a admin member?"
return self.admin


# Here we use Inheritance for creating the multi-users

class Organization(User):
user = models.OneToOneField(
User, on_delete=models.CASCADE,
parent_link=True,
related_name='organization',
)
organization_name = models.CharField(max_length=256, null=True, blank=True)
name = models.CharField(max_length=256, null=True, blank=True)


class Meta:
verbose_name = _('Organization')
verbose_name_plural = _('Organizations')

class Candidate(User):
user = models.OneToOneField(
User, on_delete=models.CASCADE,
parent_link=True,
related_name='candidate',
)
name = models.CharField(max_length=256, null=True, blank=True)
mobile = models.CharField(max_length=256, null=True, blank=True)

class Meta:
verbose_name = _('Candidate')
verbose_name_plural = _('Candidates')

So, In the above code we use Inheritance and inherite the User class into Canidate and Organization Class for creating the multiple users.

Get Rahber Abbas Abidi’s stories in your inbox

Join Medium for free to get updates from this writer.

We use BaseUserManager and AbstractBaseUser for override the User model for using email instead for username for registration and login.

So, Model is created now we need to update the settings.py file with AUTH_USER_MODEL = ‘accounts.User’.

Creating the Admin

In the admin.py file we need to create the classes and function that will help us to create the users.

from django import forms
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from .models import User, Candidate, Organization

class AddUserForm(forms.ModelForm):
"""
New User Form. Requires password confirmation.
"""
password1 = forms.CharField(
label='Password', widget=forms.PasswordInput
)
password2 = forms.CharField(
label='Confirm password', widget=forms.PasswordInput
)

class Meta:
model = User
fields = ('email','staff','is_active','admin',)

def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError("Passwords do not match")
return password2

def save(self, commit=True):
# Save the provided password in hashed format
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user

class UpdateUserForm(forms.ModelForm):
"""
Update User Form. Doesn't allow changing password in the Admin.
"""
password = ReadOnlyPasswordHashField()

class Meta:
model = User
fields = (
'email', 'password', 'is_active',
'staff','admin'
)

def clean_password(self):
return self.initial["password"]

class UserAdmin(BaseUserAdmin):
form = UpdateUserForm
add_form = AddUserForm

list_display = ('email','staff','is_active','admin','last_login')
list_filter = ('staff','is_active','admin',)
fieldsets = (
(None, {'fields': ('email', 'password')}),
# ('Personal info', {'fields': ('first_name', 'last_name', 'gender', 'role', 'resume', 'mobile', 'organizationName','organizationStrength', 'organizationType',)}),
('Permissions', {'fields': ('is_active', 'staff','admin')}),
)
add_fieldsets = (
(
None,
{
'classes': ('wide',),
'fields': (
'email', 'password1',
'password2'
)
}
),
)
search_fields = ()
ordering = ('email',)
filter_horizontal = ()

class AddCandidateForm(forms.ModelForm):
"""
New User Form. Requires password confirmation.
"""
password1 = forms.CharField(
label='Password', widget=forms.PasswordInput
)
password2 = forms.CharField(
label='Confirm password', widget=forms.PasswordInput
)

class Meta:
model = Candidate
fields = ('email','name','mobile')

def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError("Passwords do not match")
return password2

def save(self, commit=True):
# Save the provided password in hashed format
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user

class UpdateCandidateForm(forms.ModelForm):
"""
Update User Form. Doesn't allow changing password in the Admin.
"""
password = ReadOnlyPasswordHashField()

class Meta:
model = Candidate
fields = (
'email', 'password','name','mobile'
)

def clean_password(self):
# Password can't be changed in the admin
return self.initial["password"]

class CandidateAdmin(BaseUserAdmin):
form = UpdateCandidateForm
add_form = AddCandidateForm

list_display = ('email','name','mobile')
list_filter = ()
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Personal info', {'fields': ('name', 'mobile')}),
# ('Permissions', {'fields': ('is_active', 'staff','admin')}),
)
add_fieldsets = (
(
None,
{
'classes': ('wide',),
'fields': (
'email','name','mobile', 'password1',
'password2'
)
}
),
)
search_fields = ()
ordering = ('email','name')
filter_horizontal = ()

class AddOrganizationForm(forms.ModelForm):
"""
New User Form. Requires password confirmation.
"""
password1 = forms.CharField(
label='Password', widget=forms.PasswordInput
)
password2 = forms.CharField(
label='Confirm password', widget=forms.PasswordInput
)

class Meta:
model = Organization
fields = ('email','organization_name','name')

def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError("Passwords do not match")
return password2

def save(self, commit=True):
# Save the provided password in hashed format
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user

class UpdateOrganizationForm(forms.ModelForm):
"""
Update User Form. Doesn't allow changing password in the Admin.
"""
password = ReadOnlyPasswordHashField()

class Meta:
model = Organization
fields = (
'email', 'password','organization_name','name'
)

def clean_password(self):
# Password can't be changed in the admin
return self.initial["password"]

class OrganizationAdmin(BaseUserAdmin):
form = UpdateOrganizationForm
add_form = AddOrganizationForm

list_display = ('email','organization_name','name')
list_filter = ()
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Personal info', {'fields': ('organization_name', 'name')}),
# ('Permissions', {'fields': ('is_active', 'staff','admin')}),
)
add_fieldsets = (
(
None,
{
'classes': ('wide',),
'fields': (
'email','organization_name','name', 'password1',
'password2'
)
}
),
)
search_fields = ()
ordering = ('email','organization_name','name')
filter_horizontal = ()

admin.site.register(User,UserAdmin)
admin.site.register(Candidate,CandidateAdmin)
admin.site.register(Organization,OrganizationAdmin)

After than runs these code

python manage.py makemigrations

python manage.py migrate

python manage.py createsuperuser

python manage.py runsever

We have finally finished creating an Email based multi user system. There are obviously more efficient ways to handle this. But this simple trick and implementation can be used in other cases to save some mental breakdowns.

You can also check my Github Repository. Link

Happy Coding!

--

--