Tuesday, September 26, 2023

Custom User model

With this blog post, I will get started with the code. The code for the project can be found in the repository:

 Github repo

Besides the usual Django project setup, for now the environment variables such as SECRET_KEY have been defined in a file env.py file which is importing in settings.py file. The variables that need to be defined are specified in env_example.py file. There are definitely more elegant ways to do this, but that can come later when I am about to deploy a first version.

Though I intend to only collect the email of a user as required info, I thought it best to define a custom user model rather than use the User model that is present in django.contrib.auth.models. This is done with:

class User(AbstractUser):
    '''User model for authentication and authorization'''

    def save(self, *args, **kwargs):
        '''Username validation during save to db'''
        try:
            validate_email(self.username)
        except:
            raise ValidationError('Username must be a valid email')
        else:
            super().save(*args, **kwargs)

    def clean_fields(self, exclude=None):
        '''Ensure that username is a valid email in Admin dashboard.'''
        try:
            validate_email(self.username)
        except:
            raise ValidationError('Username must be a valid email')

The custom User model being defined imports AbstractUser model from django.contrib.auth.models. This provides all the working features of the in-built User model without creating a database table. For now, all I wish to do is enforce the requirement that the username should be a valid email. For that, I overrode two methods – clean_fields and save. The clean_fields method works when a form is filled such as in the Admin dashboard. Since this will be a REST API, users will be created using User.objects.create() method, and this method does not call the clean_fields model method. So, it is necessary to override the save() model method as well. Having only the save() method and omitting the clean_fields() method is possible, but when using the Admin dashboard, this will result in an exception that breaks the server rather than a nice error message in the form field.

I wrote a test for this right away in the tests.py file:

def test_username_only_email():
    '''Test that the username can only be a valid email'''

    # Passing test with valid email
    user1 = User(username='someuser@domain.com')
    user1.set_password('somepasswordfortest')
    user1.save()
    users_in_db = User.objects.all().count()
    assert users_in_db == 1

    # Failing test with normal text instead of email
    with pytest.raises(Exception):
        user2 = User(username='someuser')
        user2.set_password('someotherpassword')
        user2.save()

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

The test creates a user with the username being a valid email and verifies that there is now one user created in the database. The test then tries to create a user with a username not being an email address and verifies that an exception is raised.

No comments:

Post a Comment