Django поиск по значению массива JSONField
Допустим, у меня есть записи базы данных MySQL с такой структурой
{
"id": 44207,
"actors": [
{
"id": "9c88bd9c-f41b-59fa-bfb6-427b1755ea64",
"name": "APT41",
"scope": "confirmed"
},
{
"id": "6f82bd9c-f31b-59fa-bf26-427b1355ea64",
"name": "APT67",
"scope": "confirmed"
}
],
},
{
"id": 44208,
"actors": [
{
"id": "427b1355ea64-bfb6-59fa-bfb6-427b1755ea64",
"name": "APT21",
"scope": "confirmed"
},
{
"id": "9c88bd9c-f31b-59fa-bf26-427b1355ea64",
"name": "APT22",
"scope": "confirmed"
}
],
},
...
Как я могу отфильтровать все объекты, имя актеров которых содержит '67', например?
Ближайший вариант, который у меня есть - это то, что я заставил его работать так:
queryset.filter(actors__contains=[{"name":"APT67"}])
Но этот запрос соответствует точному значению actor.name, в то время как я хочу принять оператор 'contains'.
У меня это также работает при запросе со строгим индексом массива, например, так:
queryset.filter(actors__0__name__icontains='67')
Но он работает только если первый элемент в массиве соответствует моему запросу. А мне нужно, чтобы объект возвращался в любом из его актеров, соответствующих моему запросу, поэтому я ожидал, что будет работать что-то вроде queryset.filter(actors__name__icontains='67')
, но это не работает :(
До сих пор мне приходится использовать models.Q и несколько OR
для поддержки моих потребностей, вот так -
search_query = models.Q(actors__0__name__icontains='67') | models.Q(actors__1__name__icontains='67') | models.Q(actors__2__name__icontains='67') | models.Q(actors__3__name__icontains='67')
queryset.filter(search_query)
но это выглядит ужасно и поддерживает только 4 элемента поиска (или я должен включить больше OR'ов)
Есть ли подсказки, можно ли это решить нормальным способом в целом?
После этого ответа и связанного ответа в том же посте.
'contains' или 'icontains' ищет шаблоны '%string%', что в вашем случае предполагает, что '67' находится между символами. Но числовой шаблон находится в конце имени вашего актера.
Итак, основываясь на ответах, которые я связал, вы, вероятно, должны попробовать endswith или iendswith, чтобы искать шаблон '%67'
Моя модель данных:
class MyCustomModel(models.Model):
id = models.BigAutoField(primary_key=True)
actors = models.JSONField(blank=True, null=True)
В итоге я сделал довольно хакерский оператор lookup, который заменяет '$."' на '$[*]."' в моих запросах к полю JSON, что в моем случае делало правильный запрос, фильтруя все объекты, чье поле JSON с массивом объектов, содержит одно из нужных свойств.
Оператор поиска:
from django.db.models.lookups import IContains
from django.db.models import Field
# Custom lookup which acts like the default IContains lookup but also replaces field name to match all JSON array objects ['*'].field_name in the query instead of $.field_name.
# Maybe this could be done in a better way with better Q field path, but this works for now.
class JSONArrayContains(IContains):
lookup_name = 'jsonarraycontains'
def __init__(self, lhs, rhs):
self.lookup_name = 'icontains' # we fake the lookup name to get the right operators further
super().__init__(lhs, rhs)
def as_sql(self, compiler, connection):
lhs_sql, params = self.process_lhs(compiler, connection)
# !! HERE IS THE MAGIC
# we need to replace params parts which are like '$."name"' into parts like '$[*]."name"' if param is string and matches $." pattern
params = [param.replace('$."', '$[*]."') if isinstance(param, str) and param.startswith('$."') else param for param in params]
rhs_sql, rhs_params = self.process_rhs(compiler, connection)
params.extend(rhs_params)
rhs_sql = self.get_rhs_op(connection, rhs_sql)
return f'{lhs_sql} {rhs_sql}', params
Использование:
queryset.filter(actors__name__jsonarraycontains='67')
Фильтрует все записи, которые