How to make one Model instance have the different values of same attribute for other Model's each instance where both Models have ManyToMany relation with each other?

Hi there,

Suppose I two models which have ManyToMany relation with each other. And suppose my models are below"

class Student(models.Model):
    name = models.CharField(max_length=254)
    courses = models.ManyToManyField(Course, required=False)
    finished = models.BooleanField(default=False)  # THIS KIND OF FUNCTIONALITY I WANT

class Course(models.Model):
    name = models.CharField(max_length=254)

Now what I explained above might seem a right solution, but look at this example:

john = Student.objects.create(name="John")
eng_course = Course.objects.create(name="English")
math_course = Course.objects.create(name="Mathematics")
cs_course = Course.objects.create(name="Computer Science")

john.courses.add(eng_course)
john.courses.add(math_course)
john.courses.add(cs_course)
john.save()

Now lets say john has finished eng_course, and I want the finished for eng_course to be True. So, if I do below:

john.finished = True
john.save()

Then this will make finished have True for all courses, but I just want it to have True for eng_course only.

So, I hope I tried my best to explain the problem. So, can anyone help me in implementing the functionality/behavior I am desiring?

Hey there!
For what you issued it looks like you want to specify custom attributes on a Many to Many relationship.

@leandrodesouzadev I read the link that you shared but I did not understand it. Can you provide me an example on the code that I shared in my question? Also let’s say we have intermediary Model Finished, how would I access the intermediary Model’s attributes?

Like if I want to see the finished status of john on eng_course, should I do it like this:

john_eng_course_finished = Finished.objects.create(student=john, course=eng_course, finished=False)
john.courses.filter(name="English").finished

or if I want to update the same, then should I do it like this:

john.courses.filter(name="English").finished = True
john.save()
eng_course.save()
john_eng_course_finished.save()

Kindly clear up all the things I stated above and if possible please explain me providing the example using the Models I wrote in my question.

When you have a Student and a Course, students Enroll on that course.
You can add a Enrollment table that would look like

class Enrollment(models.Model):
    student = models.ForeignKey("Student", on_delete=models.CASCADE)
    course = models.ForeignKey(Course, on_delete=models.CASCADE)
    finished = models.BooleanField(default=False)

class Student(models.Model):
    # other infos
    enrollments = models.ManyToManyField(Course, through=Enrollment)

1 Like

Then you can add your enrollments:

Enrollment.objects.create(course=course, student=student)

Or maybe using the enrollments set on the student.

student.enrollments.create(through_defaults={"course": course})

1 Like

That’s not necessary. Using the through attribute still allows all the normal many-to-many operations to be performed. You can still use operations like john.courses.add(eng_course). When you don’t need to reference the through table, you can ignore it.
(I would also recommend keeping the name courses as the reference to Course.)

1 Like

@leandrodesouzadev and @KenWhitesell thank you for explaining. I have one question which is still not answered. And that is how would I access the finished value for john on eng_course? Secondly, also tell me if we have to make the class Diagram or Database Design, then what relationship should be should between the two classes Student & Course and the other class Enrollment?

Ignore the many-to-many relationship for the moment.

Look at just the Student and Enrollment models.

What you have is a reverse foreign key relationship from Student to Enrollment.

If john is your Student, then john.enrollment_set is the related object manager created for that relationship, and john.enrollment_set.all() is the complete set of all Enrollment for john.

In this case, since you only want one instance of Enrollment, you can use get:
john.enrollment_set.get(course=eng_course) (You do want to ensure that in the Enrollment model, the fields student and course are defined as unique together.)

In all cases, a many-to-many relationship is modeled using a “join table” connecting two tables. This join table consists of two foreign keys, one to each of the two tables it is joining. This is a standard relational db technique.

The differences between using the through attribute and not using it is that by using it, you’re making that join table to be an explicit model and not something implicitly created by Django and otherwise hidden from view.

So, depending upon how your instructor would want to see it modelled, you can continue to show this as just a many-to-many relationship between the two with an annotation describing the additional fields.

Or, if you want to show the additional detail of that Enrollment model, the relationship is the same as any other ForeignKey relationship. You have a Many-To-One relationship from Enrollment to both Student and Course. (The “many” side of a many-to-one relationship is always the table with the Foreign Key.)

2 Likes

Thank you @KenWhitesell for explaining in detail.