This is a problem I encounter quite often. Lets say I have three models:
class CourseType(models.Model):
name = models.CharField(max_length=50)
class CourseLevel(models.Model):
name = models.CharField(max_length=50)
course_type = models.ForeignKey(CourseType, on_delete=models.CASCADE)
class Course(models.Model):
name = models.CharField(max_length=50)
course_level = models.ForeignKey(CourseLevel, on_delete=models.CASCADE)
def __str__(self):
return "{}-{}: {}".format(self.course_level.course_type.name, self.course_level.name, self.name)
If I were to iterate over Course.objects.all()
and print the Course
objects, that would be a n+1 query. This is easily solved by using select_related
:
Course.objects.select_related('course_level', 'course_level__course_type')
The problem with this is I have to remember to use select_related
every time. Its also ugly, especially if you have another model in there, like CourseDetails
.
One solution to this is to use a custom manager:
class CourseManager(models.Manager):
def with_names(self):
return self.select_related('course_level', 'course_level__course_type')
I can then use Course.with_names.all()
and no longer need to add in the select_related
every time. This has a major limitation - I can only take advantage of the manager when directly using Course
. Very often I will iterate over Course
objects through foreign keys, and again I will need to remember to use select_related
and litter it all over my code. An example:
class Student(models.Model):
name = models.CharField(max_length=50)
class StudentCourseEnroll(models.Model):
student = models.ForeignKey(Student, on_delete=models.CASCADE)
course = models.ForeignKey(Course, on_delete=models.CASCADE)
Now if I want to print out all students and the courses they are enrolled in, I again must remember to use select_related
and its even uglier:
StudentCourseEnroll.objects.select_related('course__course_level__course_type', 'course__course_level')
I could create another manager for StudentCourseEnroll
but that seems to defeat the DRY principle - I would have multiple managers doing the same thing.
Is there a better way to solve this problem? Is there something I can do directly in the Course
model to avoid having to think about this?