Есть ли способ сделать нечувствительный к регистру 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]]