Python 3 Django Rest Framework - 如何向此 M-1-M 模型结构添加自定义管理器?

Python 3 Django Rest Framework - how to add a custom manager to this M-1-M model structure?

我有这些型号:

一个学生属于一个组织

一个学生可以注册一门或多门课程

所以注册记录基本上由给定的课程和给定的学生组成

from django.db import models
from model_utils.models import TimeStampedModel

class Organisation(TimeStampedModel):
  objects = models.Manager()
  name = models.CharField(max_length=50)

  def __str__(self):
    return self.name

class Student(TimeStampedModel):
  objects = models.Manager()
  first_name = models.CharField(max_length=50)
  last_name = models.CharField(max_length=50)
  email = models.EmailField(unique=True)
  organisation = models.ForeignKey(to=Organisation, on_delete=models.SET_NULL, default=None, null=True)

  def __str__(self):
    return self.email

class Course(TimeStampedModel):
  objects = models.Manager()
  language = models.CharField(max_length=30)
  level = models.CharField(max_length=2)

  def __str__(self):
    return self.language + ' ' + self.level

  class Meta:
    unique_together = ("language", "level")

class EnrollmentManager(models.Manager):
  def org_students_enrolled(self, organisation):
    return self.filter(student__organisation__name=organisation).all()

class Enrollment(TimeStampedModel):
  objects = EnrollmentManager()
  course = models.ForeignKey(to=Course, on_delete=models.CASCADE, default=None, null=False, related_name='enrollments')
  student = models.ForeignKey(to=Student, on_delete=models.CASCADE, default=None, null=False, related_name='enrollments')
  enrolled = models.DateTimeField()
  last_booking = models.DateTimeField()
  credits_total = models.SmallIntegerField(default=10)
  credits_balance = models.DecimalField(max_digits=5, decimal_places=2)

请注意自定义 EnrollmentManager,它允许我查找从给定组织注册的所有学生。

如何添加自定义管理器以从已注册学生的给定组织中检索所有课程?

我试过的

我想创建一个 CourseManager 并以某种方式 query/filter 从关系的那一边:

class CourseManager(models.Manager):
  def org_courses_enrolled(self, organisation):
    return self.filter(enrollment__student__organisation__name=organisation).all()

这有效,但它给了我相同的 100 个注册记录:(

我想得到的是: 基于给定的组织 找到所有注册的学生 然后(不同?)获取该组织的注册课程列表

这是观点:

class OrganisationCoursesView(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
  serializer_class = CourseSerializer
  queryset = Course.objects.get_courses(1)

和url:

# The below should allow: /api/v1/organisations/1/courses/
router.register('api/v1/organisations/(?P<organisation_pk>\d+)/courses', OrganisationCoursesView, 'organisation courses')

更新 1

根据 h1dd3n 的回答,我接下来尝试了这个:

class CourseManager(models.Manager):

  def get_queryset(self):
     return super(CourseManager, self).get_queryset()

  def get_courses(self, organisation):
    return self.get_queryset().filter(student__organisation_id=organisation)

但这会引发错误(如我所料):

FieldError: Cannot resolve keyword 'student' into field. Choices are: courses, created, id, language, level, modified, progress

更新 2 - 越来越近了!

在@AKX 的评论帮助下好的:

class CourseManager(models.Manager):

  def get_queryset(self):
     return super(CourseManager, self).get_queryset()

  def get_courses(self, organisation):
    return self.get_queryset().filter(courses__student__organisation_id=organisation)

现在开设 return 课程,但 return 每个注册学生都有一份副本。所以现在我需要对它们进行分组,以便每条记录只出现一次...

首先您需要将 self.filter 更改为 self.get_queryset().filter() 或在 manager.

中创建一个单独的方法
  def get_queryset(self):
     return super(CourseManager, self).get_queryset()

在管理器中创建一个函数

  def get_courses(self,organisation): 
     return self.get_queryset.filter(student__oraganisation=organisation)

这应该 return students 并且您不需要调用 .all() - 过滤后的 qs 无论哪种方式 return 都是它找到的对象。

编辑

试试这个:

class CourseManager(models.Manager):

  def get_queryset(self):
     return super(CourseManager, self).get_queryset()

  def get_courses(self, organisation):
    return self.get_queryset().filter( \
      enrollments__student__organisation_id=organisation).distinct()

更新 2

你可以试试 from django.db.models import Q https://docs.djangoproject.com/en/3.0/topics/db/queries/

annotate 基于这个答案 Get distinct values of Queryset by field 过滤掉每个学生,这样它就会出现一次。