这始于 Google 讨论组:
https://groups.google.com/forum/#!topic/django -filter/RwNfoWsdeLQ
我对能够使用 django-filter 过滤 contrib.postgres JSONFields 很感兴趣。
我有一个过滤器可用于一些示例。 这比我想象的要复杂,因为您并不像使用 IntegerField 之类的东西那样提前知道 JSON 中数据的类型。 我可能只是让它过于复杂。
这是一个命中我的 JSONField 的示例过滤器
http://127.0.0.1 :8000/api/v1/craters?data= latitude:float :-57:lte~!@!~ age:str :PC
这是模型和过滤器代码:
https://gist.github.com/jzmiller1/627071f555186cd1a58bb8f065205ff7
我将继续搞砸它。 如果有人有任何想法或反馈,请告诉我...
嗨@jzmiller1。 你到底想达到什么目的? 不知道你是不是:
data
共有的特定属性(纬度、年龄)。 这些属性本质上就是你的data
的模式。前者很有趣,但正如您发现的那样,复杂之处在于 JSONField 本质上是无模式的。 如果没有架构,您将无法编写代码来自动生成过滤器。 您的 MethodFilter 的工作原理是它允许任意属性查找,但您无法验证这些查找。 例如, ?data=latitude:char:PC:isnull
是可能的,但毫无意义。 这里的任何解决方案都需要权衡取舍。 完全任意的过滤器将无法验证查找,验证过滤器需要某种方式来提供模式。
对于第二种情况,解决方案冗长/乏味,但直截了当。
class CratersFilter(filters.FilterSet):
latitude = filters.NumberFilter(name='data__latitude', lookup_expr='exact')
latitude__lt = filters.NumberFilter(name='data__latitude', lookup_expr='lt')
latitude__gt = filters.NumberFilter(name='data__latitude', lookup_expr='gt')
latitude__isnull = filters.BooleanFilter(name='data__latitude', lookup_expr='isnull')
# not sure if 'isnull' is a valid lookup for JSONFields - just demonstrating that
# different lookups expect different value types.
age = filters.CharFilter(name='data__age', lookup_expr='exact')
...
您的查询将如下所示:
http://127.0.0.1:8000/api/v1/craters?latitude__lte=-57&age=PC
我的目标是创建一个通用的 JSONFilter,它允许查询 JSONField 中的任意属性。 对于我正在做的事情,我真的不知道特定陨石坑的数据里面有什么,但如果那里有我正在寻找的钥匙,我希望能够查询它。
就无法验证查找类型而言,我认为我将取决于进行查询的用户是否意识到查询是无意义的,而不是一开始就这样做。
我不确定我正在尝试做的事情是否是在浪费时间。 对于我想要完成的事情,可能有更好的解决方案。 我很好奇是否有人看到了会阻止这种情况发生的重大问题,或者是否有人有一个有用的用例。 感谢您查看它!
根据@rpkilby的观点,我的第一个想法是关于验证。 从开发人员的角度来看,无模式很好——但我不确定您是否希望它直接连接到可寻址的 URL。
让我们暂时保持开放。 我可以看到这是一个受欢迎的要求。 (因此,即使在文档中的 _"Here's an example MethodFilter
"_ 级别解决它也是值得的。)
对于我正在做的事情,我真的不知道特定陨石坑的数据里面有什么,但如果那里有我正在寻找的钥匙,我希望能够查询它。
这似乎......有点时髦。 您正在为陨石坑数据提供 API,但您不知道您提供的数据中有什么? 您的意思是某些记录会缺少通用模式的属性,还是个别记录完全是任意的?
我暂时将其关闭为超出范围。 很高兴考虑记录的、经过测试的拉取请求。 我们将来可能有能力重新考虑。
我认为让 JSONFilter 启用诸如jsonfield__a_random_key=value
之类的查询真的很棒。 我知道你可以用objects.filter
方法做到这一点。 也许权衡可能是过滤器验证?
刚刚使用 Q 对象完成了 QuerySet 过滤器的“自然”查询的实现。 它已经使用 JsonField 针对约 1000 条记录的查询集进行了单元测试。 实施在:
https://github.com/shallquist/DJangoQuerySetFilter/blob/master/queryparser.py
嘿@shallquist我不确定如何在 django-filter 的上下文中使用QuerySetFilter
。 您是否在某处记录了使用情况?
使用起来非常简单,如 github 上的自述文件所示。 应该支持正常查询,即。
QuerySetFilter('friends').get_Query((person__address__city = Denver | person__address__city = Boulder) & person__address_state ~= CO)
这将构建一个查询来检索住在 Dever 或 Boulder colorado 的所有朋友,其中 friends 是一个 jsonfield。
顺便说一句,这还没有经过太多测试,并且由于 Django 过滤器不支持查询嵌入式数组对象,我已经放弃了这种方法。
https://github.com/carltongibson/django-filter/issues/426#issuecomment -380224133
我认为让 JSONFilter 启用查询(例如 jsonfield__a_random_key=value)真的很棒。 我知道你可以使用 objects.filter 方法来做到这一点。 也许权衡可能是过滤器验证?
嘿@carltongibson , @rpkilby我想听听您对此的看法。 假设my_field
是 postgres JSONField
,我想:
my_field__etc=value
形状的 REST 过滤器,其中etc
是JSONField
和value
支持的任何查询,无论 REST 用户提供什么。MyModel.objects.filter(my_field__etc=value)
的形式将etc
和value
传递给模型对象管理器。这似乎是非常微不足道的,但我还没有想到要去做这样的事情。 如果你们给我一点线索,我可以尝试实现它。
任何想法将不胜感激!
像下面这样的东西不起作用吗?
class MyFilter(FilterSet):
my_field__etc = filters.NumberFilter(field_name='my_field', lookup_expr='etc')
一般来说, field_name
应该与底层模型字段名称匹配,而转换和查找(在这种情况下是关键转换)应该包含在lookup_expr
中。
@rpkilby非常感谢如此快速的响应 - 没错,但我希望etc
由用户在请求中提供...所以我无法在过滤器中对其进行硬编码💭
过滤器应该看起来更像:
class MyFilter(FilterSet):
my_field = JSONFieldFilter(field_name='my_field')
因此,一个 JSON 过滤器可以处理任意查询参数,例如?my_field__etc=value
。
我看到两个问题。 首先,django-filter 的部分价值在于它验证查询参数。 因为JSONField
没有架构,所以无法生成适当地验证入站数据的过滤器。 例如,如果您的 JSON 字段有一个“计数”键,则不可能凭直觉认为只有正数是有效的。 可以做的最好的事情是保证该值是有效的 JSON。 所以查询至少是有效的,但可能是荒谬的(例如, data__count__gt='cat'
)。
第二个是此过滤器将具有与基于MultiWidget
的过滤器相同的限制。 例如,它不会为正确的参数名称生成验证错误。 但在深入研究之前,这就是我可能实现过滤器的方式。 我们需要:
my_field__*
参数的数据的小部件。class JSONWidget(widgets.Textarea):
"""A widget that handles multiple parameters prefixed with the field name."""
def value_from_datadict(self, data, files, name):
prefix = f'{name}{LOOKUP_SEP}'
# this is doing two things:
# - matches multiple params for the base field name
# - in addition to returning the value, we also need the full parameter name
# for querying. otherwise, values will be filtered against the base `name`.
return {k: v for k, v in data.items() if k.startswith(prefix)}
def get_context(self, name, value, attrs):
# to support rendering the widget, you would need to generate subwidgets
# similar to MultiWidget.get_context.
pass
class JSONField(postgres.forms.JSONField):
widget = JSONWidget
def clean(self, value):
# note that it's not possible to collect/reraise any validation errors under
# their actual parameter names. `form.add_error` should be used here, however
# the field class does not have access to the form instance. raising
# ValidationError({k: str(original_exc)}) also does not work.
# clean/convert each value
return {k: super().clean(v) for k, v in value.items()}
class JSONFilter(filters.Filter):
field_class = JSONField
def filter(self, qs, value):
if value in EMPTY_VALUES:
return qs
return qs.filter(**value)
我没有测试过上面的,但它应该是大致正确的。 但是,有一些限制:
ValidationError
sMultiWidget
不兼容。 由于相同的原因,此过滤器/小部件会遇到相同的问题。@rpkilby非常感谢您的彻底回复。
如果您的 JSON 字段有一个“计数”键,则不可能凭直觉认为只有正数是有效的。
这是一个很好的观点,我们无法验证查询值的类型这一事实使得它非常具有挑战性,因为MyModel.objects.filter(data__count="1")
不会返回与MyModel.objects.filter(data__count=1)
相同的值。 正如您所说,无法从查询参数中猜测值的类型。
因此,只留下在查询值中嵌入类型信息的选项,例如?data__count=1:int
搜索整数和?data__count=1:str
搜索字符串等等。 但正如这里所建议的那样,不建议这样做。
我现在明白了为什么明确定义过滤器如此有价值。 不过,我会尝试一下你的建议! 再次感谢
@rpkilby ,我也有类似的需求。
我有一个这样的配置表,有两列
meta_structure of type jsonb (This column has info like key1 of type string, key2 of type integer)
我有另一个名为 config_data 的表,它将有 3 列。
config_id -> Foreign key to config table
meta_info -> jsonb type
注意:上面提到的表格不是确切的表格。 它们只是传达信息的代表版本。
我目前通过检查配置表中的匹配来验证 meta_info 表中的字段,然后再保存。
需要的是我想使用 config_data 表的 meta_info 列进行过滤。 例如。 meta_info__key1='abc'。 (key1 可以是任何东西)
我试图使用您在上面给出的方法,但问题是如何使用您在上面创建的 JSONFilter 类。
例如。
class ConfigDataFilterSet(django_filters.FilterSet):
meta_info = JSONFilter(field_name='meta_info')
pp = ConfigDataFilterSet(data={'meta_info__key1': 'abc'})
现在,如果我运行pp.qs
或pp.filter_queryset()
,它实际上不会对 meta_info 字段应用过滤器,因为在 ConfigDataFilterSet 类中分配的字段名称是 meta_info。 你能帮我克服这个障碍吗?
最有用的评论
我认为让 JSONFilter 启用诸如
jsonfield__a_random_key=value
之类的查询真的很棒。 我知道你可以用objects.filter
方法做到这一点。 也许权衡可能是过滤器验证?