I want to create multiple user types in Django. The user types are ‘Admin’, ‘Company’ and ‘Individual’. Should I use abstract models or proxy models for this requirement.
I have already done this using proxy models. How would it be done using abstract models instead? Is any one of them better than the other? If so, how?
Here is how I have implemented it using proxy models.
models.py
:
class User(AbstractBaseUser, PermissionsMixin):
id = models.AutoField(primary_key=True)
email = models.EmailField(max_length=150, unique=True, null=False, blank=False)
role = models.CharField(max_length=50, choices=Role.choices, null=False, blank=True)
is_staff = models.BooleanField(null=False, default=False)
is_active = models.BooleanField(null=False, default=True)
is_superuser = models.BooleanField(null=False, default=False)
objects = AccountManager()
USERNAME_FIELD = "email"
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
return self.is_staff
def has_module_perms(self, app_label):
return self.is_superuser
class Admin(User):
objects = AdminManager()
class Meta:
proxy = True
def custom_method_for_admin_only(self):
return something
class AdminProfile(models.Model):
admin = models.OneToOneField(
Admin, on_delete=models.CASCADE, related_name="admin_profile"
)
first_name = models.CharField(max_length=50, null=False, blank=False)
middle_name = models.CharField(max_length=50, null=True, blank=True)
last_name = models.CharField(max_length=50, null=False, blank=False)
def __str__(self):
return f"{self.first_name} {self.last_name}"
class Company(User):
objects = CompanyManager()
class Meta:
proxy = True
def custom_method_for_company_only(self):
return something
class CompanyProfile(models.Model):
company = models.OneToOneField(
Company, on_delete=models.CASCADE, related_name="company_profile"
)
name = models.CharField(max_length=50, null=False, blank=False)
is_verified = models.BooleanField(default=False, null=False, blank=True)
logo = models.ImageField(upload_to="images/", null=True, blank=True)
def __str__(self):
return self.name
class Individual(User):
objects = IndividualManager()
class Meta:
proxy = True
class IndividualProfile(models.Model):
individual = models.OneToOneField(
Individual, on_delete=models.CASCADE, related_name="individual_profile"
)
first_name = models.CharField(max_length=50, null=False, blank=False)
middle_name = models.CharField(max_length=50, null=True, blank=True)
last_name = models.CharField(max_length=50, null=False, blank=False)
def __str__(self):
return f"{self.first_name} {self.last_name}"
And the managers for the models would be something like this:
managers.py:
class Role(models.TextChoices):
ADMIN = "ADMIN", "Admin"
COMPANY = "COMPANY", "Company"
INDIVIDUAL = "INDIVIDUAL", "Individual"
class AccountManager(BaseUserManager):
def create_superuser(
self,
email,
password,
first_name=None,
middle_name=None,
last_name=None,
role=Role.ADMIN,
**other_fields
):
with transaction.atomic():
other_fields.setdefault("is_staff", True)
other_fields.setdefault("is_superuser", True)
other_fields.setdefault("is_active", True)
if not role == Role.ADMIN:
raise ValueError("Superuser must have a role of ADMIN.")
if not email:
raise ValueError("Superuser must have an email.")
if not first_name:
raise ValueError("Superuser must have a first name.")
if not last_name:
raise ValueError("Superuser must have a last name.")
if other_fields.get("is_staff") is not True:
raise ValueError("Superuser must be assigned to is_staff=True")
if other_fields.get("is_superuser") is not True:
raise ValueError("Superuser must be assigned to is_superuser=True")
email = self.normalize_email(email)
superuser = self.model(email=email, role=role, **other_fields)
superuser.set_password(password)
superuser.save()
admin_profile_model = apps.get_model("admin_app", "AdminProfile")
admin_profile_model.objects.create(
user=superuser,
first_name=first_name,
middle_name=middle_name,
last_name=last_name,
)
return superuser
def create(self, email, password, role=None, **other_fields):
if not role:
raise ValueError("A user must have a role.")
if not email:
raise ValueError("A user must have an email.")
email = self.normalize_email(email)
user = self.model(email=email, role=role, **other_fields)
user.set_password(password)
user.save()
return user
class AdminManager(AccountManager):
def create(
self, email, password, first_name, last_name, middle_name=None, **other_fields
):
admin_profile_model = apps.get_model("admin_app", "AdminProfile")
with transaction.atomic():
admin = super().create(
email=email, role=Role.ADMIN, password=password, **other_fields
)
admin_profile_model.objects.create(
admin=admin,
first_name=first_name,
middle_name=middle_name,
last_name=last_name,
)
return admin
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(role=Role.ADMIN)
class CompanyManager(AccountManager):
def create(self, email, password, name, **other_fields):
with transaction.atomic():
company_profile_model = apps.get_model("company", "CompanyProfile")
company = super().create(
email=email, role=Role.COMPANY, password=password, **other_fields
)
company_profile_model.objects.create(company=company, name=name)
return company
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(role=Role.COMPANY)
class IndividualManager(AccountManager):
def create(
self, email, password, first_name, last_name, middle_name=None, **other_fields
):
individual_profile_model = apps.get_model("account", "IndividualProfile")
with transaction.atomic():
user = super().create(
email=email, role=Role.INDIVIDUAL, password=password, **other_fields
)
individual_profile_model.objects.create(
user=user,
first_name=first_name,
middle_name=middle_name,
last_name=last_name,
)
return user
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(role=Role.INDIVIDUAL)