如何扩展SimpleListFilter实现JSON字段关键词筛选关联页面?
通过Product的JSON字段内容筛选关联的Page
我的项目中,Product信息存储在JSON字段中,且与Page模型通过多对多(m2m)关联(Page类似目录页面)。模型定义如下:
class Page(models.Model): ... products = models.ManyToManyField(Product, related_name="%(class)s_name", related_query_name="product_page_qs", blank=True) class Product(models.Model): ... number = models.PositiveIntegerField() data = models.JSONField()
我已创建SimpleListFilter实现文本搜索,可获取包含指定product.id的Page列表(功能正常):
class PageProductSearch(admin.SimpleListFilter): title = "product" parameter_name = "product" template = "search_filter.html" def lookups(self, request, model_admin): return ((None, None),) def choices(self, changelist): super().choices(changelist) return (*self.lookup_choices,) def queryset(self, request, queryset): value = self.value() if value: return queryset.filter(products=value)
当前问题是必须知晓product.id才能筛选,想扩展查询集实现对JSON字段中键值的搜索,尝试了两种方法均失败:
- 使用
queryset.filter(products__contain=value),报错django.core.exceptions.FieldError: Unsupported lookup 'contain' for ForeignKey or join on the field not permitted.,推测是因为使用SQLite; - 使用Q对象和regex:
queryset.filter(Q(data__regex=r'"name":.*"test"')),但始终返回空查询集,不过认可regex思路无需考虑data是否包含name键。
解决方案
方法1:利用Django JSON字段的专用Lookup(适配SQLite)
Django对JSONField提供了__contains、__icontains等查询方法,但需要注意关联查询的写法——必须通过products__data__前缀关联到Product表的JSON字段。
如果要搜索JSON中name键的值包含指定内容,代码修改如下:
def queryset(self, request, queryset): value = self.value() if value: # 匹配data中name键的值包含value,不区分大小写用__icontains return queryset.filter(products__data__name__contains=value)
注意:此方法要求SQLite版本≥3.31.0(Django 3.2+支持),Django会自动转换为SQLite兼容的JSON查询语法。
方法2:修正正则表达式的关联查询写法
之前的错误是直接调用data__regex,未关联到Product表,需加上products__前缀。同时SQLite存储JSON时可能存在空格差异,正则需兼容这种情况:
import re from django.db.models import Q def queryset(self, request, queryset): value = self.value() if value: # 兼容键值对之间的空格,用re.escape避免特殊字符破坏正则 regex_pattern = r'"name"\s*:\s*".*{}.*"'.format(re.escape(value)) return queryset.filter(products__data__regex=regex_pattern)
补充说明
- 若SQLite版本过低不支持JSON函数,建议升级SQLite或切换到PostgreSQL,后者对JSON字段的查询支持更完善;
- 若要实现全局模糊搜索(不指定具体键),可直接用
products__data__icontains=value,匹配JSON字符串中是否包含指定文本。
内容的提问来源于stack exchange,提问作者xtlc
相关产品推荐
相关产品推荐

