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 and related_name attributes from child models and migrations are running now but it still require the username instead of email.
解决方案
The errors in makemigrations, and later (after dropping PermissionsMixin) requiring username instead of email for authentication are hints that you have not set your custom model as the default user model to be used by the Django auth app. As a result, Django is using the auth.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 the auth.User being the default/active one used for authentication purposes.
In essence, edit your settings.py to add the following:
AUTH_USER_MODEL = '.User'
Now Django will use your customized User model instead of the one from auth app (the default) and the auth.User model will be dropped. The auth app uses the get_user_model (django.contrib.auth.get_user_model) function to get the currently active user model for the project which checks for settings.AUTH_USER_MODEL, and by default the rest of the system (e.g. the admin app) also checks for this setting to get the current user model. So as long as you're using the auth app the above should suffice.