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

Posted

技术标签:

【中文标题】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? 【发布时间】:2020-07-28 19:22:02 【问题描述】:

我有这些模型:

组织

学生

课程

注册

学生属于组织

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

因此,注册记录基本上由给定的课程和给定的学生组成

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 并以某种方式从关系的那一侧查询/过滤:

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

这可行,但它给了我相同的 100 条注册记录 :(

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

这是风景:

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

和网址:

# 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:无法将关键字“学生”解析为字段。选择是: 课程、创建、id、语言、级别、修改、进度

更新 2 - 越来越近了!

好的,在 @AKX 的 cmets 的帮助下:

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)

现在确实会返回课程,但它会为每个注册学生返回一份副本。所以现在我需要对它们进行分组,这样每条记录只会出现一次......

【问题讨论】:

顺便说一句,由于Organisation.name 不是唯一的,如果有多个同名组织,使用organisation__name 可能会返回意外结果。 好地方,谢谢,我已经改用 id 了 filter(enrollment_set__student__organisation_id=organisation) 是否有用? 否 - 无法解析关键字 'enrollment_set' :( 请注意,课程的外键是在 Enrollment 中定义的(学生外键也是如此) 他们仍然有相反的关系,docs.djangoproject.com/en/3.0/topics/db/queries/… 【参考方案1】:

首先您需要将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)

这应该返回students,您不需要调用.all() - 过滤后的qs 会返回它找到的所有对象。

编辑

试试这个:

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 Qhttps://docs.djangoproject.com/en/3.0/topics/db/queries/

或使用 annotate 基于此答案 Get distinct values of Queryset by field 过滤掉每个学生,使其出现一次。

【讨论】:

感谢您的快速回复,但这并没有完全到达那里。 “get_courses”返回学生。所以我必须使用 StudentSerializer。我有 100 名学生(100 名注册)展示,而不仅仅是 100 名学生注册的 8 门课程。我想在某个地方/不知何故我需要按课程 ID 对注册进行分组 编辑到达那里 ;)

以上是关于Python 3 Django Rest Framework - 如何向这个 M-1-M 模型结构添加自定义管理器?的主要内容,如果未能解决你的问题,请参考以下文章

Django Rest Framework 安装

[python]django rest framework写POST和GET接口

Python 3 & Django Rest Framework - 如何查询 M-1-M 模型?

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

“未提供身份验证凭据。” Django 和 AWS 应用程序

19-Django REST framework-DRF工程搭建