使用下拉列表通过django-filters筛选django-tables2表格问题
Django-tables2 + Django-filter 多对多筛选问题排查与解决
最近我在用Django-tables2展示Brief模型的数据列表时碰到了两个头疼的问题,跟大家分享下排查过程和解决办法:
问题描述
- 筛选失效:
Brief和Tower是多对多关系,原本用复选框实现按Tower筛选一切正常,但改成下拉列表后,请求/brief/?towers=1直接返回空列表——可明明在Django Shell里用相同条件查询能得到2条测试数据。 - 空白选项:下拉列表里有一个可选中的空白项,看起来很不美观。
下面是相关的代码片段:
模型代码
class Attribute(models.Model): class Meta: abstract = True ordering = ['name'] name = models.CharField(max_length=100, unique=True) created_on = models.DateTimeField(auto_now_add=True) created_by = models.ForeignKey(User, related_name='%(class)s_created_by', null=True, blank=True, on_delete=models.SET_NULL) modified_dt = models.DateTimeField(auto_now=True) modified_by = models.ForeignKey(User, related_name='%(class)s_modified_by', null=True, blank=True, on_delete=models.SET_NULL) def __str__(self): return self.name class Tower(Attribute): pass class Brief (Attribute): link = models.URLField() sources = models.ManyToManyField(SourceSystem) format = models.ForeignKey(ReportFormat, on_delete=models.PROTECT) towers = models.ManyToManyField(Tower) type = models.ForeignKey(ReportType, on_delete=models.PROTECT) project = models.ForeignKey(Project, on_delete=models.PROTECT) def tower_list(self): return ", ".join([str(obj) for obj in self.towers.all()]) def source_list(self): return ", ".join([str(obj) for obj in self.sources.all()])
tables.py代码
class BriefTable(tables.Table): name = tables.LinkColumn('brief_detail', args=[A('pk')]) class Meta: model = Brief template_name = 'django_tables2/bootstrap.html' sequence = ('id', 'name', 'type', 'project', 'format',) exclude = ('link', 'created_on', 'created_by', 'modified_dt', 'modified_by', 'info')
filters.py代码
class BriefFilter(django_filters.FilterSet): towers=django_filters.ModelMultipleChoiceFilter( queryset=Tower.objects.all(), widget=forms.Select) class Meta: model = Brief fields = ['towers']
视图代码
class FilteredBriefListView(LoginRequiredMixin, SingleTableMixin, FilterView): table_class = BriefTable template_name = 'brief/brief_list.html' paginate_by = 20 filterset_class = BriefFilter
路由代码
urlpatterns = [ path('', FilteredBriefListView.as_view(), name="home"), ]
问题原因分析
- 筛选失效的根源:我用了
ModelMultipleChoiceFilter但搭配了forms.Select控件——这俩根本不搭!ModelMultipleChoiceFilter默认是处理多选参数的(比如?towers=1&towers=2这种多值提交),而Select是单选控件,只会提交一个单一值。当后台接收到?towers=1时,ModelMultipleChoiceFilter会把它当成一个单值而非列表,导致多对多关系的查询逻辑出错,自然查不到数据。 - 空白选项的原因:
ModelMultipleChoiceFilter(包括ModelChoiceFilter)默认会添加一个空选项,用来表示“不筛选”,如果不需要这个选项,得手动关闭。
解决方案
分两种情况处理,看你需要单选还是多选:
情况1:改成单选筛选(只能选一个Tower)
如果你的需求是单选Tower,那就把ModelMultipleChoiceFilter换成ModelChoiceFilter,同时设置empty_label=None去掉空白项:
class BriefFilter(django_filters.FilterSet): towers = django_filters.ModelChoiceFilter( queryset=Tower.objects.all(), widget=forms.Select, empty_label=None # 移除空白选项 ) class Meta: model = Brief fields = ['towers']
改完之后,请求/brief/?towers=1就能正确返回关联了Tower 1的Brief数据,下拉列表也没有空白项了。
情况2:保留多选功能(下拉选多个Tower)
如果还是想要多选,那得把控件换成支持多选的SelectMultiple,这样控件会提交多个值,ModelMultipleChoiceFilter就能正确处理了:
class BriefFilter(django_filters.FilterSet): towers = django_filters.ModelMultipleChoiceFilter( queryset=Tower.objects.all(), widget=forms.SelectMultiple, empty_label=None # 移除空白选项 ) class Meta: model = Brief fields = ['towers']
这种情况下,你可以按住Ctrl(Windows)或Cmd(Mac)来多选Tower,提交的参数会是?towers=1&towers=2,后台能正确筛选出关联了这些Tower的Brief数据。如果觉得原生SelectMultiple体验不好,也可以用django-select2这类第三方插件来美化多选下拉框。
内容的提问来源于stack exchange,提问作者Scott Ertel
相关产品推荐
相关产品推荐

