Use a single custom user model inheriting from AbstractBaseUser and add an is_agent boolean field to distinguish roles. This approach avoids database table conflicts inherent in abstract inheritance while allowing separate registration flows via distinct views. It ensures consistent password hashing and permission handling across all actors.
The Problem with Abstract Inheritance
A common misconception among Django developers is that creating separate user bases for different actor types, such as regular Users and Agents, requires multiple models extending AbstractBaseUser. Attempting to create two abstract classes that both inherit from AbstractBaseUser leads to immediate technical failures. As noted in previous drafts, abstract models cannot inherit from AbstractBaseUser because they lack database tables. The authentication backend expects a concrete table to store credentials, passwords, and permissions.
If you try to define two abstract models, neither will be migrated to the database. Even if you use proxy models or multi-table inheritance incorrectly, you risk duplicating password storage logic or breaking the check_password functionality. The correct architectural pattern is to have one unified user table that represents all authenticated entities, differentiated by role fields rather than separate tables.
Designing the Unified User Model
To implement this correctly, start by defining a single custom user model. This model must inherit from AbstractBaseUser and PermissionsMixin (or AbstractUser if you prefer default fields). Add specific fields that are relevant to your business logic, such as an is_agent boolean flag. This flag allows you to easily query agents versus regular users without joining tables.
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
from django.db import models
class CustomUserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
if not email:
raise ValueError('Email is required')
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password) # Securely hashes the password
user.save(using=self._db)
return user
def create_superuser(self, email, password=None, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
return self.create_user(email, password, **extra_fields)
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
is_agent = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
objects = CustomUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
def __str__(self):
return self.email
This structure ensures that every user, whether an agent or a customer, shares the same secure password hashing mechanism provided by set_password. There is no need to manually hash passwords using libraries like bcrypt or hashlib unless you are integrating with legacy systems. The PermissionsMixin handles the complex many-to-many relationships for groups and permissions automatically.
Separate Registration Views
While the database model is unified, the user experience should differentiate between registering as a User and registering as an Agent. You can achieve this by creating separate Django views and forms. For example, an AgentRegistrationView might require additional information like an employee ID or company affiliation, which are stored in a separate AgentProfile model linked via a OneToOneField.
Create a form specifically for agents that extends your base user creation logic. When the form is valid, create the CustomUser instance with is_agent=True, and then save the related profile data. This keeps the authentication core simple while allowing flexible data collection during sign-up. Regular users follow a simpler flow, perhaps only requiring email and password.
This separation of concerns is crucial for maintaining code clarity. Your authentication backend remains agnostic to the specific role, checking only for valid credentials and active status. Role-specific logic resides in the views and serializers, not in the authentication backbone itself.
Authentication Backend Configuration
Configure your AUTHENTICATION_BACKENDS in settings.py to point to your custom manager. Since there is only one user model, you do not need multiple backends unless you are integrating third-party OAuth providers. The standard email/password flow works seamlessly with the CustomUserManager.
Ensure that your login view checks the is_active status but does not block agents specifically unless they are inactive. You can add middleware to redirect agents to a specific dashboard upon login based on the is_agent flag. This provides the tailored experience mentioned in the original problem statement without complicating the user management schema.
For more details on managing project structures that support these custom models, see Support for Several Django Projects. Proper project layout helps keep your custom user models isolated and testable.
Maintaining Data Integrity
When querying users, always filter by is_agent when necessary. For instance, to get all agents, use CustomUser.objects.filter(is_agent=True). Avoid creating separate tables for agents because it complicates foreign key relationships and makes reporting difficult. A single table with role flags is easier to maintain and scales better as you add more actor types, such as admins or moderators.
Remember that AbstractBaseUser provides the check_password method, which is essential for verifying login attempts. Do not attempt to bypass this by storing plain text passwords or using weak hashing algorithms. The security of your application relies on the integrity of this single source of truth for user credentials.
Frequently Asked Questions
Can I use Proxy Models instead of a Boolean Flag?
Proxy models allow you to subclass a model for Python-level behavior without creating new database tables. However, they do not help you store different fields for agents versus users. If you need extra data for agents, you still need a separate model or fields on the main user model, making the boolean flag approach simpler for role differentiation.
How do I handle password resets for both types?
Django's built-in password reset framework works out of the box with your custom user model. Ensure your USERNAME_FIELD is set correctly (e.g., email) so that the reset token is sent to the right address. The system does not distinguish between users and agents during the reset process, which maintains consistency.
What if I need completely different login fields for agents?
If agents log in with an Employee ID instead of email, you must customize your authentication backend. Create a custom backend that accepts either email or employee ID. Query the CustomUser table for the matching identifier. This keeps the database unified while supporting diverse login methods.
Is it safe to share the same password hash algorithm?
Yes, it is highly recommended. Using the same PBKDF2 or Argon2 algorithm for all users ensures uniform security standards. Mixing algorithms can lead to vulnerabilities if one is weaker than the other. Consistency also simplifies auditing and compliance checks across your entire user base.
How do I prevent agents from accessing user-only views?
Use Django's @login_required decorator combined with custom checks. Create a mixin or decorator that verifies user.is_agent` is False before granting access. Alternatively, use URL patterns that redirect agents to their own dashboard. This enforces role-based access control at the view level.