Search code examples
djangodjango-rest-frameworkdjango-rest-auth

Django-rest-auth registration with both Email and username (Please read the explanation carefully)


WHAT I WANT To HAPPEN I am using Django rest auth, 1) I want the users to be able to signup/register asking for their Email, username, and password. 2) When the user wants to log in, the email is what will be asked.

WHAT TO NOTE 1) The email is important to me because it will be verified to know is the user is real 2) The Username is important to me because a unique username will be able to easily access the profile of the user with style, for example, www.website.com/. I want to be able to do stuff like that with the username that is why it is important

WHAT IS HAPPENING 1) I noticed I cannot use both email and username at the same time, or rather I do not know how to set the Authentication backend up effectively

MY CODE

settings.py

INSTALLED_APPS = [
    ...
    'rest_framework',
    'rest_framework.authtoken',
    'rest_auth',
    'rest_auth.registration',
    'allauth',
    'allauth.account',
    ...
    'accounts',
]

ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_USER_EMAIL_FIELD = 'email'
ACCOUNT_UNIQUE_EMAIL = True
ACCOUNT_LOGOUT_ON_GET = True

models.py

class UserManager(BaseUserManager):
    """
    The User Manager
    """
    def _create_user(self, email, fullname, 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)
        fullname = fullname
        user = self.model(
            email=email,
            fullname=fullname,
            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, fullname, password, **extra_fields):
        return self._create_user(email, fullname, password, False, False, **extra_fields)

    def create_superuser(self, email, fullname, password, **extra_fields):
        user = self._create_user(email, fullname, password, True, True, **extra_fields)
        user.save(using=self._db)
        return user


class User(AbstractBaseUser, PermissionsMixin):
    username = None
    email = models.EmailField(max_length=254, unique=True)
    fullname = models.CharField(max_length=250)
    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)
    slug = models.SlugField(max_length=255, unique=True, blank=True)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['fullname']

    objects = UserManager()

    def __str__(self):
        return self.email

I set the username to none because it doesn't work when I set it otherwise

serializer

class CustomRegisterSerializer(RegisterSerializer):
    '''
    a custom serializer that overides the default rest-auth, and for
    the user to register himself
    '''
    username = None
    email = serializers.EmailField(required=True)
    password1 = serializers.CharField(write_only=True)
    fullname = serializers.CharField(required=True)
    slug = serializers.SlugField(read_only=True)

    def get_cleaned_data(self):
        super(CustomRegisterSerializer, self).get_cleaned_data()

        return {
            'password1': self.validated_data.get('password1', ''),
            'email': self.validated_data.get('email', ''),
            'fullname': self.validated_data.get('fullname', ''),
        }

view

class CustomRegisterView(RegisterView):
    '''
    a custom register view that overrides the rest-auth's default 
    '''
    permission_classes = [AllowAny]
    queryset = User.objects.all()

please, this has been taking me a long time to fix, and I would appreciate it if someone can help me out. thank you


Solution

  • To solve exactly the problems you are pointing, an alternative solution is django-graphql-auth. It does not affect django-rest-framework, both can be used together.

    I want the users to be able to signup/register asking for their Email, username, and password.

    Registration is made by providing email, username, password1 and password2. But you can add more fields or change these on settings.

    When the user wants to log in, the email is what will be asked.

    Users can use email or username to login. But again, you can change it in settings and let only the email if you want.

    The email is important to me because it will be verified to know is the user is real.

    It does have the verification email flow, also have a secondary email verification.

    I noticed I cannot use both email and username at the same time

    In django-graphql-auth you can.

    Example

    After installation and following the setup guide, you can make a simple flow like this example.

    Registration

    The registration is made by simple sending a graphql mutation like this (if you don't know what is or how to use graphql, the quickstart guide teaches you):

    mutation {
      register(
        email:"[email protected]",
        username:"user_username",
        password1: "somerandompassword",
        password2:"somerandompassword"
      ) {
        success,
        errors,
        token,
        refreshToken
      }
    }
    

    Then, a model UserStatus is created, related to your user model:

    from django.contrib.auth import get_user_model
    
    u = get_user_model().objects.get(username="user_username")
    
    # check the user status
    u.verified # False
    u.archived # False
    u.secondary_email # "" 
    

    Verify account

    During the registration, an email was sent to the user, with a link containing the token.

    On you front end, you can use the token from the url to send other mutation:

    mutation {
      verifyAccount(
        token:"<TOKEN HERE>",
      ) {
        success, errors
      }
    }
    

    After a successful verification, the status of your user now is:

    u.verified # True
    

    Demo

    Here is a demo video from this flow:

    Demo Video

    (I'm the author.)