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.

Monday, September 25, 2023

Launching the online course web application

 After making a few attempts at building web apps and abandoning them after a few months, I finally found a project that I feel I can effectively use. As the creator of online courses, their hosting and management is fairly critical to the Python Power Electronics project. The courses are currently hosted on Udemy which has done quite a good job in terms of providing instructors a nice interface for hosting courses. However, to begin with, a fairly significant share of the course revenue goes to Udemy. Moreover, with Udemy choosing a new pricing strategy that changes dynamically, my courses are unaffordable to most students unless they are on sale. For this reason, a very useful web app will be one that allows me to host courses on my own website while providing most of the facilities that Udemy does.

As always, this project will be completely open source as that is largely my philosophy behind software. I have been using open-source software for close to twenty years and for the past decade almost exclusively open-source software. The hope is that the code can be used by another instructor to host his or her courses online.

The backend for the app will be Django and Django Rest Framework (DRF). In terms of the database, I have not decided yet, but will probably be MySQL or Postgres. For the frontend, I would like to experiment. I have sworn by Angular for the past several years, but have been forced to use React for a few projects. I have always preferred Angular over React, but there are a few aspects to React that are interesting and might be worth digging deeper with a side-project. I learned Flutter for mobile app development a while back, but did nothing with it. This might be the time to start using Flutter, and if a React app is ready, some of the state management can be transferred to the Flutter app.

The last bits are testing, DevOps and security. All of these are domains where I have dabbled in over the past few years with many projects. I would like to use them extensively with this project. So, the hope is to test every bit of code. For the backend, I will be using Pytest, for React it will be Jest, for Angular it will be Jasmine. In terms of DevOps, all deployment will be done using Docker containers. But besides, I would also like to automate the deployment by using Jenkins. In terms of security, I will be using a number of tools. For Static Application Security Testing (SAST), I will be using add-ons with Visual Studio Code such as HCL AppScan which highlights security issues as files are saved. For Penetration Testing, I will be using OWASP ZAP Penetration tester and also some other tools as I learn more on web application security.

Since I do see myself using this web app, I hope I will be motivated to build it consistently over the next few months. I hope it will also be a good learning experience just like the circuit simulator was.