Search code examples
pythondjangodjango-rest-auth

How to alter the LoginSerializer for one field for username/telephone/email?


In the custom LoginSerializer:

class LoginSerializer(serializers.Serializer):
    username = serializers.CharField(required=False, allow_blank=True)
    email = serializers.EmailField(required=False, allow_blank=True)
    password = serializers.CharField(style={'input_type': 'password'})

    def _validate_email(self, email, password):
        user = None

        if email and password:
            user = authenticate(email=email, password=password)
        else:
            msg = 'must input email and password'
            raise exceptions.ValidationError(msg)

        return user

    def _validate_username(self, username, password):
        user = None

        if username and password:
            user = authenticate(username=username, password=password)
        else:
            msg = 'must input username and password'
            raise exceptions.ValidationError(msg)

        return user

    def _validate_username_email(self, username, email, password):
        user = None

        if email and password:
            user = authenticate(email=email, password=password)
        elif username and password:
            user = authenticate(username=username, password=password)
        else:
            msg = 'must type in email and pwd or username and pwd'
            raise exceptions.ValidationError(msg)

        return user

    def validate(self, attrs):
        username = attrs.get('username')
        email = attrs.get('email')
        password = attrs.get('password')

        user = None

        if 'allauth' in settings.INSTALLED_APPS:
            from allauth.account import app_settings

            # Authentication through email
            if app_settings.AUTHENTICATION_METHOD == app_settings.AuthenticationMethod.EMAIL:
                user = self._validate_email(email, password)

            # Authentication through username
            if app_settings.AUTHENTICATION_METHOD == app_settings.AuthenticationMethod.USERNAME:
                user = self._validate_username(username, password)

            # Authentication through either username or email
            else:
                user = self._validate_username_email(username, email, password)

        else:
            # Authentication without using allauth
            if email:
                try:
                    username = User.objects.get(email__iexact=email).get_username()
                except User.DoesNotExist:
                    pass

            if username:
                user = self._validate_username_email(username, '', password)

        # Did we get back an active user?
        if user:
            if not user.is_active:
                msg = 'this user can not login'
                raise exceptions.ValidationError(msg)
        else:
            msg = '不能使用提供的信息登录'
            raise exceptions.ValidationError(msg)

        # If required, is the email verified?
        if 'rest_auth.registration' in settings.INSTALLED_APPS:
            from allauth.account import app_settings
            if app_settings.EMAIL_VERIFICATION == app_settings.EmailVerificationMethod.MANDATORY:
                email_address = user.emailaddress_set.get(email=user.email)
                if not email_address.verified:
                    raise serializers.ValidationError('email invalidate')

        return attrs

the login panel is this:


I want to optimize the login panel to two fields. one for username/telphone/email, the other for password.

But how to change the LoginSerializer?


Solution

  • You can easily replace those 3 fields (Email/Username/PhoneNumber), with one CharField and then try querying for all of the 3 fields to find a match, in the easiest case.

    Somewhat more elaborate is a way to distinguish between different login types by using simple RegExes or even simple checks, to reduce the query load on your database.

    (You might need to use some more complex checks to determine a phone login, depending on the type of your accepted phone numbers, e.g. +1 813 3181, +1 (317) 173-1375, ...)

    So all in all, something like the following would do:

    def validate(self, attrs):
        id_field = attrs.get('id_field')
    
        user = None
    
        if '@' in id_field:
            user = User.objects.filter(email__iexact=id_field).first()
        elif id_field.isdigit():
            user = User.objects.filter(phone_number=id_field).first()
        else:
            user = User.objects.filter(username__iexact=id_field).first()
    
        if not user:
            raise serializers.ValidationError("User does not exists")
    
        ...
    
        return attrs
    

    EDIT

    To check the password for the found user, you can use Django's check_password function

    Password validation would be something like:

    ...
    
    if not user.check_password(password):
        raise ValidationError('Invalid password')
    
    ...
    # Now on, user's password is validated.