在 Django admin 中过滤同一记录的多个条件

问题描述 投票:0回答:1

这与我的其他问题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
python django django-models django-admin
1个回答
0
投票

您面临的问题是由于 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 排序,从而允许按此字段进行过滤。

© www.soinside.com 2019 - 2024. All rights reserved.