Нечувствительный фильтр регистра

Создано: 21 ноября 2024 г.

Отвечено с использованием OpenAI 01 Preview от OpenAI01

Вопрос

фильтр не работает корректно. Нужен не чувствительный к регистру фильтр.
class UnidecodeCharFilter(filters.CharFilter):
def filter(self, queryset, value):
value = unidecode.unidecode(value)
return super().filter(queryset, value)


class IngredientFilter(filters.FilterSet):
name_startswith = UnidecodeCharFilter(
field_name='name', lookup_expr='istartswith')
name_contains = UnidecodeCharFilter(
field_name='name', lookup_expr='icontains')

class Meta:
model = Ingredient
fields = ['name']

def filter_queryset(self, queryset):
search_term = self.form.cleaned_data.get('name_startswith')
if search_term:
search_term = search_term.casefold()
queryset = queryset.annotate(
starts_with=Case(
When(name__istartswith=search_term, then=1),
default=0,
output_field=IntegerField()
)
).order_by('-starts_with', 'name')
return queryset.filter(
Q(name__istartswith=search_term) | Q(name__icontains=search_term)
)
запрос
/api/ingredients/?name=Б
ответ
{
"id": 169,
"name": "Буррата",
"measurement_unit": "г"
},
{
"id": 1194,
"name": "Панифарин",
"measurement_unit": "г"
},
{
"id": 1,
"name": "абрикосовое пюре",
"measurement_unit": "г"
},
{
"id": 2,
"name": "абрикосовый джем",
"measurement_unit": "г"
},
{
"id": 3,
"name": "абрикосовый сок",
"measurement_unit": "мл"
},
{
"id": 4,
"name": "абрикосы",
"measurement_unit": "г"
},
{
"id": 5,
"name": "абрикосы консервированные",
"measurement_unit": "г"
},
{
"id": 6,
"name": "авокадо",
"measurement_unit": "г"
},
{
"id": 7,
"name": "агава сироп",
"measurement_unit": "мл"
},
{
"id": 8,
"name": "агар-агар",
"measurement_unit": "г"
},
{
"id": 9,
"name": "аграм",
"measurement_unit": "г"
},
{
"id": 10,
"name": "аджика",
"measurement_unit": "г"
},

Мысль

Рассматриваю проблему фильтрации

Пользователь сообщает об ошибке в фильтре, который не работает как нужно, и спрашивает о фильтре без учёта регистра. Это наводит на мысль, что проблема может быть конкретно в обработке регистров.

Производим обработку запрашиваемого термина

Я исследую запрос на ингредиенты и замечаю, что применяю unidecode для нормализации текста и использую фильтр для сопоставления, чтобы найти ингредиенты, которые начинаются или содержат этот термин.

Анализирую фильтр
Окей, давайте посмотрим на фильтры имени: используем UnidecodeCharFilter для нормализации. Затем в filter_queryset анализируем, начинается ли имя с search_term. Это заставляет меня задуматься о том, как улучшить точность фильтрации.

Оценивая варианты

Пробую понять, как фильтр на 'name_startswith' влияет на запрос, где используется параметр 'name'. Возможно, это приводит к неправильным результатам.

Обдумываю фильтр

Подозреваю, что проблема в parameter 'name'. Возможно, стоит изменить его на 'name_startswith' или 'name_contains' для правильной обработки.

Ищем решение

Я разбираюсь, как правильно обработать параметр запроса 'name'. Возможно изменить фильтры, чтобы 'name' трактовался как 'name__istartswith'.

Ответ

Проблема возникает из-за несоответствия между именем параметра запроса и определенными фильтрами в вашем классе IngredientFilter. В вашем запросе вы используете параметр name, но в фильтре у вас определены name_startswith и name_contains. Это приводит к тому, что фильтр не применяет корректно условия поиска.

Кроме того, в методе filter_queryset вы обращаетесь к self.form.cleaned_data.get('name_startswith'), но если в запросе используется name, это значение будет None.

Чтобы решить проблему и сделать фильтр нечувствительным к регистру, вы можете сделать следующие изменения:

  1. Измените фильтр name_startswith на name и используйте его для поиска с istartswith:
class IngredientFilter(filters.FilterSet): name = UnidecodeCharFilter( field_name='name', lookup_expr='istartswith') class Meta: model = Ingredient fields = ['name']
  1. Обновите метод filter_queryset, чтобы использовать параметр name:
def filter_queryset(self, queryset): search_term = self.form.cleaned_data.get('name') if search_term: search_term = search_term.casefold() queryset = queryset.annotate( starts_with=Case( When(name__istartswith=search_term, then=1), default=0, output_field=IntegerField() ) ).order_by('-starts_with', 'name') return queryset.filter( Q(name__istartswith=search_term) | Q(name__icontains=search_term) ) return queryset

Теперь, когда вы делаете запрос /api/ingredients/?name=Б, фильтр будет корректно обрабатывать его, используя нечувствительное к регистру сравнение и начнет поиск с указанной буквы.

Объяснение:

  • Параметр запроса и фильтр: Убедитесь, что имя параметра запроса соответствует имени фильтра. Если вы используете name в запросе, то и фильтр должен называться name.

  • Нечувствительность к регистру: Использование lookup_expr='istartswith' делает поиск нечувствительным к регистру. Кроме того, вы используете casefold() для нормализации регистра в search_term.

  • Использование UnidecodeCharFilter: Этот фильтр преобразует входное значение, что помогает в обработке специальных символов и делает поиск более универсальным.

Примечание:

Если вам также нужен фильтр для name_contains, вы можете добавить его аналогично:

name_contains = UnidecodeCharFilter( field_name='name', lookup_expr='icontains')

И обновить fields в Meta:

class Meta: model = Ingredient fields = ['name', 'name_contains']

И не забудьте соответствующим образом изменить метод filter_queryset.

Поделиться этим Q&A