Coming back to this project after a month as I get back from the holiday season. I left off where I had written the endpoint and the tests for publishing and unpublishing a course. The next endpoint will be to register a logged in student for the course.
I created a new class for this as for now I can't see how this endpoint can be multiplexed with another one in the manner in which CRU for the basic course creation was done. This endpoint will add the logged in user to the list of students in the course and then return all the courses which the student has registered so far. Conceptually, a logged in user will visit a course page, go through the overview and maybe watch a few videos, and if interesting will click on the register button. If the registration is successful, the user will get back a list of all the courses he has registered for maybe with this latest course first on the list.
The class is:
class CourseRegisterView(UpdateAPIView, UserAuthentication): ''' Register a student for a course and return list of courses for the student. ''' serializer_class = CourseSerializer lookup_field = 'slug' user_model = User def get_queryset(self, *args, **kwargs): '''Return published courses''' return Course.objects.fetch_courses() def partial_update(self, request, *args, **kwargs): try: user = self.authenticate( request, check_admin=False ) if user is not None: course_obj = self.get_object() course_obj.add_students(user) return Response( data=CourseSerializer( user.course_set.all(), many=True ).data ) except CourseGenericError as e: return Response( data=str(e), status=status.HTTP_400_BAD_REQUEST ) except InvalidToken as e: return Response( data='Must be logged in to register for course', status=status.HTTP_403_FORBIDDEN ) except Exception as e: return Response( data=str(e), status=status.HTTP_400_BAD_REQUEST )
Some of it is a duplicate of the previous CourseView class. The first few properties of the class like serializer_class, lookup_field and user_model seem basic and so not sure if a base class can be created to not repeat these lines.
The get_queryset() method has been simplified to return only published courses as no one (admin or normal user) should be able to register for an unpublished course. The class uses the UpdateAPIView which is apt for editing a detail and provides a PUT and PATCH request. Theoretically, a register should be a POST, but then this would mean the extraction of the course object would need to be done manually. Since a user is being added to the "students" ManyToManyField, this can be interpreted as a PATCH request.
The UpdateAPIView handles a put or a patch request and provides an update() or partial_update method(). The UpdateModelMixin was used for the CourseView class as well. The logged in user is the user to be added to the students list. And so the view class inherits the UserAuthentication class which overloads the authenticate() method provided by JWTAuthentication.
To extract the course, I had initially overloaded the get_object() method. This get_object method is provided by GenericAPIView from which the UpdateAPIView inherits. This was my original overloaded method:
def get_object(self): try: return get_object_or_404( self.get_queryset(), slug=self.kwargs['slug'] ) except: raise Exception('Course not found')
Then I thought - what for? If the lookup_field has been specified as 'slug' and the get_queryset() method has been defined as all the published courses, the default get_object which filters the queryset with the value of the slug should be good enough. And so getting rid of this get_object() method makes no difference to the working of the code.
Trying to register a user for a course if the user has already been registered will through a 400 error. This can be displayed as a modal in the frontend. Will write tests for the this endpoint and check if any changes need to be made.
No comments:
Post a Comment