Saturday, September 30, 2023

Confirming password while registering

To add the confirm_password field when registering a new user, the simplest way seems to be to define another serializer RegisterUserSerializer that inherits the UserSerializer:

class RegisterUserSerializer(UserSerializer):
    '''Serializer for registering a new user'''
    confirm_password = serializers.CharField(
        style={'input_type': 'password'},
        write_only=True
    )

    def validate(self, data):
        '''Validate that password and confirm_password are the same'''
        if not data['password'] == data['confirm_password']:
            raise serializers.ValidationError('Passwords are not matching.')
        return data

    class Meta(UserSerializer.Meta):
        model = User
        fields = ['username', 'password', 'confirm_password', 'is_active']

A new field confirm_password has been defined along with a validate method that performs object level checking on the serializer to ensure that the password and confirm_password field are identical. The view method will now use request.data to create a RegisterUserSerializer instance instead of UserSerializer. The only problem is that the error for passwords not matching is not very nice – “non_field_errors – Passwords not matching”. This is because errors found on the object using the validate method are sent out in the dictionary with non_field_errors key. This means that the error messages will need to be customized rather than processed in the view method.

class Meta:
    model = User
    fields = ['username', 'password', 'is_active']
    read_only_fields = ['is_active', ]
    extra_kwargs = {
        'username': {
            'error_messages':
            {
                'blank': 'The username field is required',
                'required': 'The username field is required'
            }
        }
    }

I included both blank and required error messages for the browser form as well as an API request sent from Postman. The password fields can have similar error messages in their serializer field definitions.

password = serializers.CharField(
    style={'input_type': 'password'},
    write_only=True,
    error_messages={
        'blank': 'The password field is required',
        'required': 'The password field is required'
    }
)

confirm_password = serializers.CharField(
    style={'input_type': 'password'},
    write_only=True,
    error_messages={
        'blank': 'The confirm password field is required',
        'required': 'The confirm password field is required'
    }
)

The tests for the API endpoint can be updated:

def test_register_new_user():
    '''Test API to register new user'''
    client = APIClient()

    # Should result in a user created in db
    api_response = client.post(
        '/user/register-user',
        {
            'username': 'someuser@domain.com',
            'password': 'somepass',
            'confirm_password': 'somepass'
        },
        format='json'
    )
    assert api_response.status_code == 201
    assert api_response.data['username'] == 'someuser@domain.com'
    assert hasattr(api_response.data, 'password') == False
    assert api_response.data['is_active'] == False

    users_in_db = User.objects.all().count()
    assert users_in_db == 1

    # Should fail model validation
    api_response = client.post(
        '/user/register-user',
        {
            'username': 'someuser',
            'password': 'somepass'
        },
        format='json'
    )
    assert api_response.status_code == 400

    # Should fail because of missing password field
    api_response = client.post(
        '/user/register-user',
        {
            'username': 'someuser@domain.com',
        },
        format='json'
    )
    assert api_response.status_code == 400

    # Should fail because of missing username field
    api_response = client.post(
        '/user/register-user',
        {
            'password': 'somepass',
        },
        format='json'
    )
    assert api_response.status_code == 400

    # Should fail become of missing username and password
    api_response = client.post(
        '/user/register-user',
        format='json'
    )
    assert api_response.status_code == 400

    # Should fail because of missing confirm_password field
    api_response = client.post(
        '/user/register-user',
        {
            'username': 'someuser@domain.com',
            'password': 'somepass',
        },
        format='json'
    )
    assert api_response.status_code == 400

    # Should fail because the passwords do not match
    api_response = client.post(
        '/user/register-user',
        {
            'username': 'someuser1@domain.com',
            'password': 'somepass',
            'confirm_password': 'somepass1'
        },
        format='json'
    )
    assert api_response.status_code == 400

    users_in_db = User.objects.all().count()
    assert users_in_db == 1


No comments:

Post a Comment