I'm working on a project using Python(3.7) and Django(2.2) in which I have to implement multiple types of users as:
- Personal Account - below 18
- Personal Account - above 18
- Parent Account
- Coach Account
- Admin
along with that, I also need to use email
as the username
field for login/authentication.
The strategy I'm trying to use is to build a custom base model as User
inherited from AbstractBaseUser
and also created a custom User Manager
to make the email
as username
but it's not working.
Here's my complete model code:
class UserManager(BaseUserManager):
def _create_user(self, email, password, is_staff, is_superuser, **extra_fields):
if not email:
raise ValueError('Users must have an email address')
now = timezone.now()
email = self.normalize_email(email)
user = self.model(
email=email,
is_staff=is_staff,
is_active=True,
is_superuser=is_superuser,
last_login=now,
date_joined=now,
**extra_fields
)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, email=None, password=None, **extra_fields):
return self._create_user(email, password, False, False, **extra_fields)
def create_superuser(self, email, password, **extra_fields):
user = self._create_user(email, password, True, True, **extra_fields)
user.save(using=self._db)
return user
def generate_cid():
customer_number = "".join([random.choice(string.digits) for i in range(10)])
return customer_number
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(max_length=255, unique=True)
is_personal_above_18 = models.BooleanField(default=False)
is_personal_below_18 = models.BooleanField(default=False)
is_parent = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
last_login = models.DateTimeField(null=True, blank=True)
date_joined = models.DateTimeField(auto_now_add=True)
USERNAME_FIELD = 'email'
EMAIL_FIELD = 'email'
REQUIRED_FIELDS = []
objects = UserManager()
def get_absolute_url(self):
return "/users/%i/" % self.pk
def get_email(self):
return self.email
class PersonalAccountAbove18(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE,
primary_key=True, related_name='profile')
customer_id = models.BigIntegerField(default=generate_cid)
class PersonalAccountBelow18(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE,
primary_key=True, related_name='profile')
customer_id = models.BigIntegerField(blank=False)
class ParentAccount(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE,
primary_key=True, related_name='profile')
customer_id = models.BigIntegerField(default=generate_cid)
I'm confused about my approach and even it's also return an error when I run makemigrations
as:
users.User.user_permissions: (fields.E304) Reverse accessor for 'User.user_permissions' clashes with reverse accessor for 'User.user_permissions'. HINT: Add or change a related_name argument to the definition for 'User.user_permissions' or 'User.user_permissions'.
Update: I removed the
PermissionMixin
andrelated_name
attributes from child models and migrations are running now but it still require theusername
instead of
For using email as login, I recommend the
django-allauth
package. It takes care of all the heavy lifting and you can use it to login with email instead of username. Will Vincent has a write up on this at:https://wsvincent.com/django-allauth-tutorial-custom-user-model/
Will also has a good write up on creating custom user models at:
https://wsvincent.com/django-custom-user-model-tutorial/
In short, he recommends subclassing
AbstractUser
. Here is an example from one of my projects where a user collects points throughout their journey on the site and I must record these points.The errors in
makemigrations
, and later (after droppingPermissionsMixin
) requiringusername
instead ofemail
for authentication are hints that you have not set your custom model as the default user model to be used by the Djangoauth
app. As a result, Django is using theauth.User
model as the default user model and your one being added to the project like any other model. So both of the user models exist simultaneously with theauth.User
being the default/active one used for authentication purposes.In essence, edit your
settings.py
to add the following:Now Django will use your customized
User
model instead of the one fromauth
app (the default) and theauth.User
model will be dropped. Theauth
app uses theget_user_model
(django.contrib.auth.get_user_model
) function to get the currently active user model for the project which checks forsettings.AUTH_USER_MODEL
, and by default the rest of the system (e.g. theadmin
app) also checks for this setting to get the current user model. So as long as you're using theauth
app the above should suffice.