How to avoid n+1 queries in Django when you are not able to use select_related?

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?

Check out django-auto-prefetch which automatically uses prefetch_related() style queries on access.