1
votes

I'm trying to get all the Students which are enrolled to a list of specific courses (Say Course 1 and Course 2).

class Student(models.Model):
    name = models.CharField(max_length=50, verbose_name="Student name")
    courses = models.ManyToManyField(Course, related_name="students", blank=True)

    def __str__(self):
        return self.name


class Course(models.Model):
    name = models.CharField(max_length=100, verbose_name="Course Name")

    def __str__(self):
        return self.name

If I do:

result = Student.objects.filter(courses__in=[1,2])

I get all the Students who are enrolled with either course 1 or course 2. I'm interested only in students who are enrolled in both courses.

The below code works but I think it's not the best way to accomplish this:

all_students = Student.objects.all()
stud_id = []
for student in all_students:
    course_list = list(student.courses.values_list('pk', flat=True))
    check = all(item in course_list for item in [1,2])
    if check:
        stud_id.append(student.id)
studs = Student.objects.filter(id__in=stud_id)

The idea is to not loop over every student to find if they're enrolled to the given set of courses.

2

2 Answers

1
votes

As @Ashley H. indicated, but instead:

result = Student.objects.filter(courses__id=1).filter(courses__id=2)

So if we have:

Joe Ben: Math, Computing, Machine Learning
Ray Youth: Math, Computing
Elli Vader: Computing

The result would be: Joe Ben, Ray Youth. I assume this is what is needed. Can you correct me if not?

0
votes

A couple of things.

(1) Your related_name for courses on your student model is a little confusing. The idea of the related name is to be able to do a 'backwards' lookup on the model the relationship is defined on from the model the replationship is not defined on. Here, you'd want to be able to start with your Course model and use that related_name to get a list of students in the course(s). So instead of related_name="course_name", I would recommend related_name="students".

(2) The __in suffix indeed matches to anything in the list. If you want both, you have to explicitly filter for both. So, your query would be:

result = Student.objects.filter(courses__id=1).filter(courses__id=2)

This should only return students who are in BOTH courses.