通过Through过滤Django Admin中的ManyToMany字段

1 投票
2 回答
36 浏览
提问于 2025-04-12 17:05

我的 models.py 文件是这样的:

class Subject(models.Model):
    name = models.CharField(max_length=200)

class Person(models.Model):
    subject = models.ForeignKey(Subject, on_delete=models.CASCADE, blank=True, null=True)
    school = models.ForeignKey(School, on_delete=models.CASCADE, blank=True, null=True)

class PersonRole(models.Model):
    project = models.ForeignKey('Project', on_delete=models.CASCADE)
    person = models.ForeignKey(Person, on_delete=models.CASCADE)

class Project(models.Model):

    title = models.CharField(max_length=200)
    person = models.ManyToManyField(Person, through=PersonRole)

现在,在我的后台管理系统中,我想添加一个很不错的过滤功能,使用 list_filter。这个过滤器应该能根据一个人参与的项目来筛选他们所属的学校。换句话说,如果约翰(属于学校“1号”)参与了3号项目,我希望后台的项目表格只显示3号项目。

我想我应该自定义一个 simplelistfilter。不过,首先我有点困惑,不知道怎么获取与项目相关的人的学校列表。

到目前为止,我的尝试是:

class PersonRole(models.Model):

    [...]

    def get_school(self):
        return self.person.school


class Project(models.Model):

    @admin.display(description='PI School')
    def get_PI_school(self):
        return [p for p in self.person.get_school()] 

我的 admin.py 文件是这样的:

class ProjectAdmin(admin.ModelAdmin):
    list_display = ("get_PI_school",) #This is just to see if the field is populated

这样做后,我得到了 'ManyRelatedManager' object has no attribute 'get_school' 的错误。

2 个回答

1

你在 PersonRole 对象上使用 .get_school() 方法:

class Project(models.Model):
    @admin.display(description='PI School')
    def get_PI_school(self):
        return [p.get_school() for p in self.personrole_set.all()]

我们可以通过在同一个查询中同时获取相关的 PersonSchool 来提高效率:

class Project(models.Model):
    @admin.display(description='PI School')
    def get_PI_school(self):
        return [
            p.get_school()
            for p in self.personrole_set.select_related('person__school')
        ]

你还可以通过以下方式筛选出 PI 角色:

class Project(models.Model):
    @admin.display(description='PI School')
    def get_PI_school(self):
        return [
            p.get_school()
            for p in self.personrole_set.filter(
                title__contains="Principal investigator"
            )
        ]

当然,这样做的前提是你的 PersonRole 模型里仍然有 title 这个字段。

撰写回答