Пользовательский поиск Django, rhs получает только %s

Я хочу создать пользовательский поиск icontainsall таким образом, что когда пользователь ищет фразу с пробелом в ней, я хочу преобразовать ее в подобие всех слов с помощью AND (так что порядок слов не имеет значения). Т.е. если пользователь ищет 'egg milk', предикат становится name LIKE %egg% AND name LIKE %milk%.

Вот как выглядит мой поиск:

from django.db.models import CharField, Lookup, TextField


class CaseInsensitiveContainsAllLookup(Lookup):
    lookup_name = 'icontainsall'

    def as_sql(self, compiler, connection):
        lhs, lhs_params = self.process_lhs(compiler, connection)
        rhs, rhs_params = self.process_rhs(compiler, connection)
        print(lhs, rhs, lhs_params, rhs_params)
        # shows "mytable"."name" %s [] ['egg milk']
        predicate, params = [], []
        # escape \ and %
        rhs_list = rhs.replace('\\', '\\\\').replace('%', '\\%').split(' ')
        for rhs in rhs_list:
            params += lhs_params + rhs_params
            predicate.append(f"LOWER({lhs}) LIKE LOWER({rhs})")
        return ' AND '.join(predicate), params


CharField.register_lookup(CaseInsensitiveContainsAllLookup)
TextField.register_lookup(CaseInsensitiveContainsAllLookup)

Однако rhs получает только %s, но не egg milk. Как мне добиться желаемого с помощью поиска?

Edit: Я отобразил lhs_params и rhs_params. Пытаюсь понять, почему это список. Если и когда я пойму, думаю, я смогу решить свою проблему.

Методы process_lhs и process_rhs возвращают кортеж, первым элементом которого является строка SQL-выражения, а вторым - список параметров SQL-выражения. Рассматривать rhs как %s имеет смысл, поскольку это реализация Lookup.process_rhs по умолчанию. Нужное вам значение присутствует в rhs_params.

Кроме того, вместо того, чтобы пытаться самостоятельно реализовать логику сравнения, не зависящую от регистра, вам следует позволить Django сделать тяжелую работу, используя IContains встроенный поиск:

from django.db.models.lookups import IContains


class CaseInsensitiveContainsAllLookup(Lookup):
    lookup_name = 'icontainsall'

    def as_sql(self, compiler, connection):
        lhs, lhs_params = self.process_lhs(compiler, connection)
        rhs, rhs_params = self.process_rhs(compiler, connection)
        
        flattened_rhs_params = []
        for param in rhs_params:
            flattened_rhs_params += param.split(' ')
        
        predicate, params = [], []
        for rhs_param in flattened_rhs_params:
            contains_sql, contains_params = IContains(self.lhs, rhs_param).as_sql(compiler, connection)
            params += contains_params
            predicate.append(contains_sql)
        return ' AND '.join(predicate), params

Примечание: если в вашем вводе есть несколько пробелов между словами, то при текущей реализации разбиения вы будете иметь пустые строки в flattened_rhs_params, в зависимости от вашего случая использования вы можете захотеть отфильтровать эти пустые строки.

Вернуться на верх