在Django Admin中通过带Through的多对多字段实现学校过滤
解决Django Admin中按项目关联人员所属学校过滤Project的问题
我有以下Django模型:
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)
想要在Django Admin的Project列表页添加筛选器,实现按项目关联人员所属的学校过滤——比如如果"第一学校"的John关联了项目3,筛选后列表只显示项目3。
我尝试给模型加了方法并配置Admin,但运行报错:'ManyRelatedManager' object has no attribute 'get_school',尝试的代码如下:
models.py修改:
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",) # 先测试字段是否能显示
报错原因
self.person是Django的ManyRelatedManager对象(对应多对多关联),它本身没有get_school方法。你需要遍历关联的Person实例,再逐个获取每个Person的school属性。
完整解决方案
1. 修复Project的显示字段方法
先把列表页显示学校的方法改对,避免重复显示同一学校:
class Project(models.Model): title = models.CharField(max_length=200) person = models.ManyToManyField(Person, through=PersonRole) @admin.display(description='关联学校') def get_PI_school(self): # 用集合去重,再拼接成字符串 related_schools = set() for person in self.person.all(): if person.school: related_schools.add(person.school.name) return ', '.join(related_schools)
2. 自定义学校筛选器
在admin.py中创建自定义SimpleListFilter,实现筛选逻辑:
from django.contrib import admin from django.contrib.admin import SimpleListFilter from .models import Project, School class ProjectSchoolFilter(SimpleListFilter): title = '所属学校' # 筛选器在页面上的显示名称 parameter_name = 'school' # URL中使用的参数标识 def lookups(self, request, model_admin): # 只获取关联过项目的学校,避免显示无关联的无效选项 related_school_ids = Project.objects.filter(person__school__isnull=False)\ .values_list('person__school__id', flat=True).distinct() return [(school.id, school.name) for school in School.objects.filter(id__in=related_school_ids)] def queryset(self, request, queryset): # 根据选中的学校过滤项目,distinct()避免重复结果 if self.value(): return queryset.filter(person__school_id=self.value()).distinct() return queryset
3. 配置ProjectAdmin
把筛选器和显示字段添加到Admin配置:
class ProjectAdmin(admin.ModelAdmin): list_display = ('title', 'get_PI_school') # 显示项目标题和关联学校 list_filter = (ProjectSchoolFilter,) # 启用自定义学校筛选器 admin.site.register(Project, ProjectAdmin)
配置完成后,Admin的Project列表页会出现"所属学校"筛选器,选择对应学校就能过滤出所有关联该学校人员的项目,同时列表页也会清晰显示每个项目关联的学校名称。
内容的提问来源于stack exchange,提问作者HBMCS
相关产品推荐
相关产品推荐

