Thursday, February 22, 2024

Continuing with video contents and getting started with the frontend

With some basic course and lecture end-points created and tested, the next step is the actual video contents of the course along with other downloadable resources like .pdf, .docx, .zip and other files. Since the backend is also now around 1/4th done, it is time to slowly get started with the frontend as well. Rather than start with the frontend for the app directly, I am starting with the video player.

Instead of using the regular video player, I have decided to build a custom video player for a few reasons. First, and quite obviously, I would like to have more control over the videos rather than what comes with a default video player. Second, a year or so back, I was working on another interesting project with a friend that involved building an interactive video player. This was a video player that would take user inputs and play certain parts of the video accordingly. The application for this is primarily in advertising and marketing as the user can choose to watch only what they are interested in watching. With respect to online courses, I would like to use this interactivity in asking students if they would like to review certain concepts necessary for the particular lecture they are about to watch. This is due to the fact that not all students can invest time regularly, and if they are watching videos after several days, they might need a refresher.

This project was called the Accordion player, a name that my friend came up with. I have started hosting this project at this Github link:

https://github.com/shivkiyer/accordion-player

I would definitely like to build the app in React, and would like to also use Angular as it is something I have used in the past. So, there are currently two sub-projects in this repo - one with React and the other with Angular.

Since the app is in the nascent stage, another thing which I would like to go back and work on is documentation. This time, with the Django backend, I have done my best to make the code as modular as possible. This implies classes and functions that are as small as possible. This has the advantage that the single doc string at the beginning of the function or class is usually good enough and there doesn't have to be comments all over the code which can become a little bothersome.

However, with both Python and JavaScript, it might be worthwhile to write proper doc strings which can then be used to create nice understandable documentation of the code. So there might be a few commits only related to documentation over the next week.

Wednesday, February 7, 2024

Additions to the lecture API endpoints

With the basic CRUD for lecture endpoints done, I will need to plan some other details as well as the lecture contents and resources (or attachments). For now, a lecture must have a title which must be unique in a course, and an optional description. Another field that a lecture must have is whether a preview is enabled, and in that case anyone can view it for free. Also, if the lecture has video content, the duration of the video needs to be displayed even in the list view.

Next comes the sequence of lectures in a course. There has to be a sequence ID to indicate how the lectures are to appear, and an instructor can move lectures around and change the sequence. This sequence ID is also unique within a course, and must not be set directly by the instructor, but rather must be generated during the creation process. In the case of a single lecture creation, it might be fairly easy, as one only needs to check the largest sequence number in the course and add the new lecture. In the case of bulk lecture creation, it might need some kind of counter as the contents of a spreadsheet might be read. Finally, in the lecture list view, the lectures should appear in the ascending order of the sequence ID.

There has been no difference between the Lecture List view and the Detail view. However, these two should be different - the list view should only display the title, description and time duration of a lecture. The detail view should provide everything - details of the video content, resources for download etc.

Some changes such as the detail view can wait until the other models such as videos and downloads are ready. But the sequence ID can be handled right away. Along with creating the sequence ID and retrieving lectures according to sequence ID, an API call for changing the sequence ID of a lecture must also be created. So, if lecture 15 in a course needs to be inserted before lecture 9, the sequence ID of lecture 15 will become 9, and the sequence IDs of lectures 9 to 14 will increase by 1. And the reverse process if a lecture needs to be moved down the list.

Thursday, February 1, 2024

Starting with the lecture detail view

The list view where all the lectures of a course can be fetched is a more public view, as the list of lectures should be visible to the general public even without login and registration. The only constraint is that the course should be published. Unpublished courses should be visible only to admin and to instructors.

For this reason, the GET method handler in lecture view is:

def get(self, request, *args, **kwargs):
    try:
        self.authenticate(request)
    except Exception as e:
        pass
    if self.request.user is not None and self.request.user.is_staff:
        self.init_lecture()
    else:
        self.init_lecture(admin_only=False)
    if self.kwargs.get('id', None) is None:
        return self.list(request, *args, **kwargs)
    self.check_permissions(request)
    return self.retrieve(request, *args, **kwargs)

First I call the authenticate method which will insert the user object into the request body from the JWT in the header. But, I call it in a way that it does not throw an exception. If the user is a staff user, I extract the course even if it is unpublished. Otherwise, only published courses will be extracted. This is by modifying the init_lecture method to be:

def init_lecture(self, admin_only=True):
    '''
    Initialize lecture view
        - fetch course object
    '''
    course_slug = self.kwargs.get('slug', None)
    self.course = Course.objects.get_course_by_slug(
        course_slug,
        admin_only=admin_only
    )

Unless the method is called with admin_only=False, it will extract even unpublished courses. In the view method, this happens only when there are no credentials or when the user is not admin. If a course is unpublished, a normal user or anonymous user will get a 'Course not found' 404 error.

In the GET method handler, if an id is passed in the url, it will proceed to the detail view. Here, for now, I am only checking permissions in the sense that a user should be logged in. The method check_permissions is:

def check_permissions(self, request):
    if request.user is None:
        raise CustomAPIError(
            status_code=status.HTTP_403_FORBIDDEN,
            detail='Must be logged in to access a lecture'
    )

Later this will check for registration and payment. For example, it an instructor wishes to have a course completely paid, the check_permissions will ensure that the user has paid for the course. My plan is to keep a certain percentage of videos of a course to be free, and so this can check if a video can be watched for free or needs to be paid for. This of course also implies that the lectures need a sequence ID within a course, so that you can find out beyond which lecture, a user will need to pay for the course.

The next view will be the PATCH and DELETE view. The lecture model needs a few changes and so does the serializer, as the detail view will need more details including the video content and also other resources like .pdf attachments etc.