making use of Django's built-in authentication

alex 13th May 2024 at 12:01pm

One of the advantages of starting with Django is wielding its out-of-the-box authentication system. I want students to access common resources, but also be entitled to their own materials, which should be out of bounds for every other user.

From the very beginning, my models inherited the default Django User class. I could have expanded it — merging Student and User — but I haven't given it much thought.

from django.contrib.auth.models import User
from django.db import models


class Student(models.Model):
    # the name generated in the TiddlyWiki platform
    student_code = models.CharField(max_length=20, default="CHANGEME")
    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)

    def __str__(self):
        return self.student_code


class Stint(models.Model):
    student_id = models.ForeignKey(Student, on_delete=models.CASCADE)
    stint_code = models.CharField(max_length=20, default="CHANGEME")
    pass

This allows for two different strategies in the backend:

@login_required
def protected(request):
    [...]

Simple, function-based views are nice, and I knew these before. Using a decorator, some views are protected from unauthenticated users — but this, by itself, does not prevent users from accessing each other's material!

But I came across class-based views (CBV), which are handled differently:

class StudentCodeRequiredMixin(LoginRequiredMixin):
    def dispatch(self, request, *args, **kwargs):
        # Call the superclass's dispatch method to ensure the user is logged in
        response = super().dispatch(request, *args, **kwargs)
        
        # Extract the student_code from the URL parameters
        student_code = kwargs.get('student_code')
        
        # Check if the logged-in user is associated with the student_code
        try:
            student = Student.objects.get(student_code=student_code)
            if student.user != request.user:
                return HttpResponseForbidden("You do not have permission to access this page.")
        except Student.DoesNotExist:
            return HttpResponseForbidden("Student not found.")
        
        return response


class StintFilesView(StudentCodeRequiredMixin, View):
    def get(self, request, student_code, stint_code):
        try:
            student = Student.objects.get(student_code=student_code)
            stint = Stint.objects.get(student_id=student, stint_code=stint_code)
            files_dict = get_dict_of_files_in_stint(student, stint)
            return JsonResponse({'stint': stint.stint_code, 'files': files_dict})
        except Student.DoesNotExist:
            return JsonResponse({'error': 'Student not found'}, status=404)
        except Stint.DoesNotExist:
            return JsonResponse({'error': 'Stint not found'}, status=404)

The example above uses some mixins to ensure not only user authentication, but that an user is not accessing other user's resources.