Фильтр Django JSONField список dicts

Я запускаю Django 1.9 с новым JSONField и имею следующую тестовую модель:

class Test(TimeStampedModel):
    actions = JSONField()

Скажем, действие JSONField выглядит так:

[
  {
    "fixed_key_1": "foo1",
    "fixed_key_2": {
      "random_key_1": "bar1",
      "random_key_2": "bar2",
    }
  },
  {
    "fixed_key_1": "foo2",
    "fixed_key_2": {
      "random_key_3": "bar2",
      "random_key_4": "bar3",
    }
  }
]

Я хочу иметь возможность фильтровать ключи foo1 и foo2 для каждого элемента списка. Когда я это сделаю:

>>> Test.objects.filter(actions__1__fixed_key_1="foo2")

Тест находится в запросе. Но когда я это делаю:

>>> Test.objects.filter(actions__0__fixed_key_1="foo2")

Это не так, что имеет смысл. Я хочу сделать что-то вроде:

>>> Test.objects.filter(actions__values__fixed_key_1="foo2")

или

>>> Test.objects.filter(actions__values__fixed_key_2__values__contains="bar3")

И проверите тест в наборе запросов.

Любая идея, если это можно сделать и как?

Ответ 1

Если вы не хотите фильтровать свои данные одним из полей в вашем массиве dicts, вы можете попробовать этот запрос:

Test.objects.filter(actions__contains=[{'fixed_key_1': 'foo2'}])

Он отобразит все объекты Test, у которых есть хотя бы один объект в поле actions, который содержит ключ fixed_key_1 значения foo2.

Также он должен работать для вложенного поиска, даже если вы не знаете фактических индексов:

Test(actions=[
    {'fixed_key_1': 'foo4', 'fixed_key_3': [
        {'key1': 'foo2'},
    ]}
}).save()

Test.objects.filter(actions__contains=[{'fixed_key_3': [{'key1': 'foo2'}]}])

Простыми словами, contains будет игнорировать все остальное.

К сожалению, если вложенный элемент является объектом, вы должны знать имя ключа. В этом случае поиск по значению не будет работать.

Ответ 2

Вы можете использовать пакет django-jsonfield, я думаю, это уже тот, который вы используете.

from jsonfield import JSONField
class Test(TimeStampedModel):
    actions = JSONField()

Итак, чтобы выполнить поиск по определенному свойству, вы можете просто сделать это:

def test_filter(**kwargs):
    result = Test.objects.filter(actions__contains=kwargs)
    return result

Если вы используете PostgreSQL, возможно, вы можете использовать PostgreSQL конкретные поля модели.

PS: Если вы имеете дело с большим количеством структуры JSON, возможно, вам стоит рассмотреть возможность использования базы данных NoSQL.

Ответ 3

Вы должны иметь возможность использовать поиск __contains для этого и передавать запрашиваемые значения в виде списка, как __contains здесь. Поиск будет вести себя точно так же, как ArrayField. Итак, что-то вроде этого должно работать:

Test.objects.filter(actions__contains=[{'fixed_key_1': 'foo2'}])