Python - Create multiple user types and email as t

2020-08-01 03:06发布

问题:

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:

  1. Personal Account - below 18
  2. Personal Account - above 18
  3. Parent Account
  4. Coach Account
  5. 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.

回答1:

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.

from django.contrib.auth.models import AbstractUser

class CustomUser(AbstractUser):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4)
    points = models.IntegerField(default=0)

    def user_id(self):
        return self.id.__str__()



回答2:

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 = '<your_app>.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.