这与我的其他问题here相关,我认为我已经解决了。尽管如此,当通过
school
应用过滤器时,我得到了任何人属于该学校的任何项目。相反,我想仅过滤具有“首席研究员”(role
) 且属于特定学校的人员的项目。
现在,下面的代码输出一个项目,该项目已链接属于已过滤学校的任何人员,无论其角色如何。
我的
models.py
:
class School(models.Model):
name = models.CharField(max_length=200)
class Person(models.Model):
surname = models.CharField(max_length=100, blank=True, null=True)
forename = models.CharField(max_length=100, blank=True, null=True)
school = models.ForeignKey(School, null=True, blank=True, on_delete=models.CASCADE)
class PersonRole(models.Model):
ROLE_CHOICES = [
("Principal investigator", "Principal investigator"),
[...]
]
project = models.ForeignKey('Project', on_delete=models.CASCADE)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
person_role = models.CharField(choices=ROLE_CHOICES, max_length=30)
class Project(models.Model):
title = models.CharField(max_length=200)
person = models.ManyToManyField(Person, through=PersonRole)
我的
admin.py
:
class PISchoolFilter(admin.SimpleListFilter):
title = 'PI School'
parameter_name = 'school'
def lookups(self, request, model_admin):
return (
('Arts & Humanities Admin',('Arts & Humanities Admin')),
[...]
)
def queryset(self, request, queryset):
if self.value() == 'Arts & Humanities Admin':
return queryset.filter(
person__personrole__person_role__contains="Principal investigator",
person__personrole__person__school=5 #5 is 'Arts & Humanities Admin' school pk.
).distinct()
[...]
def choices(self, changelist):
super().choices(changelist)
return (
*self.lookup_choices,
)
class ProjectAdmin(NumericFilterModelAdmin, ImportExportModelAdmin):
list_filter = [PI_SchoolFilter]
为什么
(person__personrole__person_role__contains="Principal investigator", person__personrole__person__school=5)
不能在同一个 PersonRole
实例上工作?我也尝试过 Q(person__personrole__person_role__contains="Principal investigator") & Q(person__personrole__person__school=4).distinct()
并得到了同样的结果,即项目中是否有一个人担任该角色或与该学校相关。我想要两个都在同一个人身上,或者什么都没有。
PS:
我注意到,当我按学校过滤时(即我从管理下拉过滤列表中选择学校),加载页面的地址仅包含
?person__personrole__person__school=5
,完全忽略角色。如果我硬编码 ?person__personrole__person_role__contains="Principal investigator"
我得到
DisallowedModelAdminLookup at /admin/artsdb/project/
Filtering by person__personrole__person_role__contains not allowed
您面临的问题是由于 Django 处理多对多关系的方式造成的。当您在多对多字段上使用 filter() 时,Django 会为每个过滤条件创建一个 SQL JOIN。这意味着您的过滤条件不会应用于同一个 PersonRole 实例,而是应用于与项目关联的任何 PersonRole 实例。
为了确保这两个条件都应用于同一个 PersonRole 实例,您可以使用 Q 对象创建复杂的查找。以下是修改查询集方法的方法:
from django.db.models import Q
def queryset(self, request, queryset):
if self.value() == 'Arts & Humanities Admin':
return queryset.filter(
Q(person__personrole__person_role__contains="Principal investigator") &
Q(person__personrole__person__school=5)
).distinct()
这将仅返回同一个人既是“首席研究员”又属于 ID 为 5 的学校的项目。
关于 DisallowedModelAdminLookup 错误,这是因为由于潜在的性能问题,Django 不允许开箱即用的多对多字段进行过滤。您可以通过在 ModelAdmin 中设置排序来覆盖此行为:
class ProjectAdmin(NumericFilterModelAdmin, ImportExportModelAdmin):
ordering = ('person__personrole__person_role',)
list_filter = [PI_SchoolFilter]
这告诉 Django 允许按 person_role 排序,从而允许按此字段进行过滤。