Django-filter: 过滤 contrib.postgres JSONField

创建于 2016-06-04  ·  16评论  ·  资料来源: carltongibson/django-filter

这始于 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

我将继续搞砸它。 如果有人有任何想法或反馈,请告诉我...

最有用的评论

我认为让 JSONFilter 启用诸如jsonfield__a_random_key=value之类的查询真的很棒。 我知道你可以用objects.filter方法做到这一点。 也许权衡可能是过滤器验证?

所有16条评论

嗨@jzmiller1。 你到底想达到什么目的? 不知道你是不是:

  • 试图创建一个通用的 JSONFilter,它允许您查询 JSONField 中的任意属性。 或者,
  • 试图暴露你的陨石坑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 过滤器,其中etcJSONFieldvalue支持的任何查询,无论 REST 用户提供什么。
  • 然后我想以 $# MyModel.objects.filter(my_field__etc=value)的形式将etcvalue传递给模型对象管理器。
  • 最后检索过滤器返回的任何内容。

这似乎是非常微不足道的,但我还没有想到要去做这样的事情。 如果你们给我一点线索,我可以尝试实现它。

任何想法将不胜感激!

像下面这样的东西不起作用吗?

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的过滤器相同的限制。 例如,它不会为正确的参数名称生成验证错误。 但在深入研究之前,这就是我可能实现过滤器的方式。 我们需要:

  • 执行实际过滤的过滤器类,它应该处理多个参数
  • 用于验证 JSON 数据的表单字段
  • 用于获取任意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 s
  • 糟糕的 OpenAPI/CoreAPI 模式支持? 不知道这会是什么样子。
  • djangorestframework-filtersMultiWidget不兼容。 由于相同的原因,此过滤器/小部件会遇到相同的问题。

@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.qspp.filter_queryset() ,它实际上不会对 meta_info 字段应用过滤器,因为在 ConfigDataFilterSet 类中分配的字段名称是 meta_info。 你能帮我克服这个障碍吗?

此页面是否有帮助?
0 / 5 - 0 等级