Creating relationship between Student and Class in Django

I am trying to create models where I can have relationship between Students and ClassName tables so that I can get all users using using ClassName.objects.get() and ClassName using Student.objects.get() method? I am completely stuck here. Should I add more fields to Student model?

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


# Create your models here.
class User(AbstractUser):
    pass


class ClassName(models.Model):
    grade = models.IntegerField()
    section = models.CharField(max_length=1)

    def __str__(self):
        return f"{self.grade} - {self.section}"


class Student(models.Model):
    first_name = models.CharField(max_length=124, null=False)
    middle_name = models.CharField(max_length=124, default='')
    last_name = models.CharField(max_length=124, null=False)
    name = models.CharField(max_length=124, default=f"{first_name} {middle_name} {last_name}")
    father_name = models.CharField(max_length=124, null=False)
    phone_number = models.CharField(max_length=20, null=False)
    date_of_birth = models.DateField()
    national_id= models.CharField(max_length=15, null=False)
    student_class = models.ForeignKey(ClassName, on_delete=models.DO_NOTHING)

    def __str__(self):
        return f"{self.first_name} {self.middle_name} {self.last_name}"

I am trying to find a method to get all users using using ClassName.objects.get() and ClassName using Student.objects.get() method?

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


# Create your models here.
class User(AbstractUser):
    pass


class ClassName(models.Model):
    grade = models.IntegerField()
    section = models.CharField(max_length=1)

    def __str__(self):
        return f"{self.grade} - {self.section}"


class Student(models.Model):
    first_name = models.CharField(max_length=124, null=False)
    middle_name = models.CharField(max_length=124, default='')
    last_name = models.CharField(max_length=124, null=False)
    name = models.CharField(max_length=124, default=f"{first_name} {middle_name} {last_name}")
    father_name = models.CharField(max_length=124, null=False)
    phone_number = models.CharField(max_length=20, null=False)
    date_of_birth = models.DateField()
    national_id= models.CharField(max_length=15, null=False)
    classname = models.ForeignKey(
          ClassName, 
          on_delete=models.CASCADE
  )

    def __str__(self):
        return f"{self.first_name} {self.middle_name} {self.last_name}"

So it looks like what you're trying to set up is a many-to-many (m2m) relationship between users and classes. That makes sense because a user is enrolled in multiple classes and each class is attended by multiple students.

Then you decided to create your own user models, this also makes sense as it opens the door to possible future changes to the codebase without having to perform risky manual operations on the DB.

You extended AbstractUser. Now spend a minute to go check in the Django codebase what attributes and methods does AbstractUser comes with: >> AbstractUser <<

You can see that it comes with username, first_name, last_name, email, is_staff, is_active, etc.. So this is all already built-in in Django, if you use this model as base (as you should) you don't need to re-declare all these attributes.

Now ask yourself: How many students each user can represent? Normally the answer to that should be "just 1", so this tells you that between user and student you probably want a one-to-one relationship.

So, to recap, you need a User model with a 1to1 relationship with a Student model, then you need a m2m relationship between Student and ClassName. Let's see how to implement it:

# User and Student
class User(AbstractUser):
    middle_name = models.CharField(max_length=124, default='')
    father_name = models.CharField(max_length=124, null=False)
    phone_number = models.CharField(max_length=20, null=False)
    date_of_birth = models.DateField()
    national_id= models.CharField(max_length=15, null=False)

class Student(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="student")

Now you probably noticed how some fields are missing from your original implementation and how I moved some others from Student to User.

I didn't include some of the fields that already exist in the AbstractUser model that you inherit when declaring User, because you already have access to those. So if for example you were to execute:

user = User.objects.get(id=1) 
print(user.first_name)

This would work fine.

The reason why i moved the Student fields into User is because those are attributes that belong to the User. If for example you wanted to store average_grade then it would squarely belong in the Student model. For example father_name probably belongs in the Student model. Let's say that later you want to add a Teacher model, I can't imagine it being mandatory for teachers to provide their father's name. But i'll let you decide that! For more information on this you can check out this answer i wrote sometime back about this specific model structure you're trying to implement.

Now let's look at the m2m relationship between Student and ClassName. For this i really recommend you to read some introductory material on databases and on database relationships. Many-to-many relationships are the most difficult to wrap your head around and you may need to spend sometime doing that before you proceed with your Django project.

You can check out this video for a summary of a lot of important database concepts. It has helped me in the past.

Here is how you would implement it:

class Student(models.Model)
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="student")
    class_names = models.ManyToManyField(ClassName, related_name="students")

class ClassName(models.Model):
    grade = models.IntegerField()
    section = models.CharField(max_length=1)

I recommend you to read the very good django docs on many-to-many relationships to form an idea of what you can do with them.

In simple terms though, this means that every Student can have many ClassName and every ClassNames can have many Students.

You can manipulate the relationship from both sides. For example:

class_name_1 = ClassName.objects.get(id=1)
class_name_2 = ClassName.objects.get(id=2)
user.class_names.add(class_name_1)
user.class_names.add(class_name_2)
print(user.class_names.all()). # will print: [class_name1, class_name2]

print(class_name_1.students.all()). # will print: [user, ]
Back to Top