Есть ли способ сделать нечувствительный к регистру IN запрос в Django?

Почти у каждого вида поиска в Django есть независимая от регистра версия, за исключением, по-видимому.

Это проблема, потому что иногда мне нужно искать, где я уверен, что дело будет неверным.

Products.objects.filter(code__in=[user_entered_data_as_list])

Что я могу сделать, чтобы справиться с этим? Люди придумали взломать, чтобы обойти эту проблему?

8 ответов

Решение

Я работал над этим, делая саму базу данных MySQL без учета регистра. Я сомневаюсь, что люди в Django заинтересованы в том, чтобы добавить это как функцию или предоставить документы о том, как обеспечить свой собственный поиск по полю (при условии, что это возможно даже без предоставления кода для каждого бд-конца БД)

Вот один из способов сделать это, правда, это неуклюже.

products = Product.objects.filter(**normal_filters_here)
results = Product.objects.none()
for d in user_entered_data_as_list:
    results |= products.filter(code__iexact=d)

Если ваша база данных MySQL, Django обрабатывает запросы IN без учета регистра. Хотя я не уверен в других

Изменить 1:

model_name.objects.filter(location__city__name__in': ['Tokio','Paris',])

даст следующий результат, в котором название города

Токио или Токио или Токио или Париж или Париж или Париж

Если это не приведет к конфликтам, возможный обходной путь может заключаться в преобразовании строк в верхний или нижний регистр как при сохранении объекта, так и в filter,

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

      from django.db.models.functions import Lower

Products.objects.annotate(lower_code=Lower('code')).filter(lower_code__in=[user_entered_data_as_list_lowered])

Другое решение, хотя и грубое, состоит в том, чтобы включить различные аргументы исходных строк в аргумент списка в фильтр "in". Например: вместо ['a', 'b', 'c'] используйте вместо этого ['a', 'b', 'c', 'A', 'B', 'C'].

Вот функция, которая создает такой список из списка строк:

def build_list_for_case_insensitive_query(the_strings):
    results = list()
    for the_string in the_strings:
        results.append(the_string)
        if the_string.upper() not in results:
            results.append(the_string.upper())
        if the_string.lower() not in results:
            results.append(the_string.lower())
    return results

Поиск с использованием Qобъект может быть построен так, чтобы попасть в базу данных только один раз:

from django.db.models import Q

user_inputed_codes = ['eN', 'De', 'FR']

lookup = Q()
for code in user_inputed_codes:
    lookup |= Q(code__iexact=code)
filtered_products = Products.objects.filter(lookup)

Вот решение, которое не требует подготовленных к регистру значений БД. Кроме того, он выполняет фильтрацию на стороне движка DB, что означает гораздо большую производительность, чем перебор objects.all(),

def case_insensitive_in_filter(fieldname, iterable):
    """returns Q(fieldname__in=iterable) but case insensitive"""
    q_list = map(lambda n: Q(**{fieldname+'__iexact': n}), iterable)
    return reduce(lambda a, b: a | b, q_list)

Другое эффективное решение - использовать extra с довольно переносимым raw-SQL. lower() функция:

MyModel.objects.extra(
    select={'lower_' + fieldname: 'lower(' + fieldname + ')'}
).filter('lover_' + fieldname + '__in'=[x.lower() for x in iterable])

Более элегантный способ был бы таков:

[x for x in Products.objects.all() if x.code.upper() in [y.upper() for y in user_entered_data_as_list]]
Другие вопросы по тегам