Фильтрация в админке Django для нескольких условий на одной записи
Это связано с моим другим вопросом здесь, который, как мне казалось, я решил. Тем не менее, при применении фильтра по 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 для создания сложного поиска. Вот как можно модифицировать метод queryset:
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()
Это вернет только те проекты, в которых один и тот же человек является и "главным исследователем", и принадлежит школе с идентификатором 5.
Что касается ошибки DisallowedModelAdminLookup, то это связано с тем, что Django не позволяет фильтровать по полю "многие-ко-многим" из-за потенциальных проблем с производительностью. Вы можете отменить это поведение, установив упорядочивание в ModelAdmin:
class ProjectAdmin(NumericFilterModelAdmin, ImportExportModelAdmin):
ordering = ('person__personrole__person_role',)
list_filter = [PI_SchoolFilter]
Это указывает Django на разрешение упорядочивания по person_role, что позволяет фильтровать по этому полю.
Проблема с вашим текущим подходом к фильтрации заключается в том, как вы обходите отношения между моделями. Вот почему ваш код не фильтрует так, как ожидалось, и как это исправить:
Анализ проблемы:
person__personrole__person_role__contains="Principal investigator"
: Проверяется, содержит ли полеperson_role
строку "Principal investigator". Это может соответствовать проектам, в которых человек имеет другую роль, содержащую эту строку.
Решения:
- Точное соответствие роли:
Измените условие, чтобы использовать точное совпадение для роли:
person__personrole__person_role="Principal investigator"
- Извлечение связанных объектов:
Используйте prefetch_related
для эффективной загрузки связанных объектов за один раз. Это позволяет избежать множества запросов к базе данных и упрощает процесс фильтрации:
from django.db import models
class ProjectAdmin(NumericFilterModelAdmin, ImportExportModelAdmin):
list_filter = [PI_SchoolFilter]
def get_queryset(self, request):
queryset = super().get_queryset(request)
return queryset.prefetch_related('personrole_set')
# ... rest of your admin class definition
- Пользовательский менеджер или аннотации:
Для более сложной фильтрации рассмотрите возможность создания пользовательского менеджера на модели Project
или используйте аннотации для фильтрации на основе атрибутов связанных объектов.
Пояснение:
- Оптимизация prefetch_related позволяет избежать запроса
PersonRole
объектов для каждого проекта в отфильтрованном списке. - Фильтрация с помощью
person__personrole__person_role="Principal investigator"
обеспечивает точное совпадение для требуемой роли.
Адресация отброшена Параметр фильтра:
В URL отображается только фильтр школы (person__personrole__person__school=5
), что может быть связано с тем, что класс PI_SchoolFilter
неправильно обрабатывает фильтр роли. Убедитесь, что метод value()
возвращает значение роли, если оно присутствует в строке запроса.
Помни:
- Сочетание prefetch_related с фильтрацией по атрибутам связанных объектов может потребовать дополнительной логики фильтрации в представлении или шаблоне.
При реализации одного из этих решений ваш фильтр администратора должен точно отфильтровывать проекты с людьми, имеющими роль "Главный исследователь" и принадлежащими к выбранной школе.