Отображение отфильтрованного результата из ManyToMany через модель в Admin

Вот мой models.py:

class Person(models.Model):

    surname = models.CharField(max_length=100, blank=True, null=True)
    forename = models.CharField(max_length=100, blank=True, null=True)

    def __str__(self):
        return '{}, {}'.format(self.surname, self.forename)

class PersonRole(models.Model):
    ROLE_CHOICES = [
        ("Principal investigator", "Principal investigator"),
        [etc...]
    ]
    title = models.CharField(choices=TITLE_CHOICES, max_length=9)
    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)

    def __str__(self):
        return '{}: {} as {}.'.format(self.project, self.person, self.person_role)

class Project(models.Model):

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

    def __str__(self):
        return self.title

    def get_PI(self, obj):
        return [p.person for p in self.person.all()] #I'll then need to filter where person_role is 'Principal investigator', which should be the easy bit.

В моем внутреннем блоке администратора я хочу отображать персону (главного исследователя) в главной таблице:

class ProjectAdmin(ImportExportModelAdmin):

    list_filter = [PersonFilter, FunderFilter]
    list_display = ("title", "get_PI")
    ordering = ('title',)

Что я хочу: отобразить человека с ролью "Главный исследователь" в таблице Projects в Admin.

Вы видите, что я создал get_PI() в models.py и ссылки в list_display. Я получаю Project.get_PI() missing 1 required positional argument: 'obj'. Что я делаю не так?

Удалите параметр obj:

class Project(models.Model):
    title = models.CharField(max_length=200)
    person = models.ManyToManyField(Person, through=PersonRole)

    def __str__(self):
        return self.title

    def get_PI(self):  # 🖘 no obj
        return [p.person for p in self.person.all()]

Однако это создаст проблему N+1, поэтому вам, вероятно, следует повысить эффективность с помощью:

class ProjectAdmin(ImportExportModelAdmin):
    list_filter = [PersonFilter, FunderFilter]
    list_display = ('title', 'get_PI')
    ordering = ('title',)

    def get_queryset(self, request):
        return super().get_queryset(request).prefetch_related('person')

В качестве фильтрации можно использовать:

class Project(models.Model):
    def get_PI(self):
        return [
            p
            for p in self.person.filter(
                personrole__title__contains='Principal investigator'
            )
        ]

Однако это снова замедлит выполнение запросов, поскольку теперь мы снова выполняем один запрос на запись.

Вернуться на верх