From 96aa0b2f7febed57666903b2adbe0755abad1eca Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Tue, 11 Nov 2025 00:52:12 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A3=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B4=D1=83=D0=B1=D0=BB=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=BF=D0=BE=D0=B8=D1=81=D0=BA=D0=B0=20=D0=BC=D0=B5?= =?UTF-8?q?=D0=B6=D0=B4=D1=83=20customer=5Flist()=20=D0=B8=20api=5Fsearch?= =?UTF-8?q?=5Fcustomers()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Проблема: Было две разных реализации логики поиска: - customer_list() использовала простой icontains везде - api_search_customers() использовала новую smart-логику с determine_search_strategy() Решение: 1. Создана функция build_customer_search_query() которая строит Q-объект на основе стратегии поиска 2. Обновлена customer_list() чтобы использовать: - determine_search_strategy() для определения стратегии - build_customer_search_query() для построения Q-объекта 3. Обновлена api_search_customers() чтобы использовать build_customer_search_query() вместо дублирования логики Результат: ЕДИНАЯ логика поиска везде ✓ Архитектура: 1. normalize_query_phone() — нормализация номеров телефонов 2. determine_search_strategy() — определение стратегии поиска 3. build_customer_search_query() — построение Q-объекта ← NEW 4. customer_list() — используется в веб-интерфейсе списка клиентов 5. api_search_customers() — используется в AJAX для Select2 Все 23 unit-теста проходят успешно ✓ Преимущества: - Единая логика поиска во всем приложении - Легче поддерживать и расширять - Новая функция можно переиспользовать в других местах - Меньше дублирования кода 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- myproject/customers/views.py | 87 +++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 31 deletions(-) diff --git a/myproject/customers/views.py b/myproject/customers/views.py index 3fc5f11..26c15e6 100644 --- a/myproject/customers/views.py +++ b/myproject/customers/views.py @@ -29,13 +29,23 @@ def customer_list(request): customers = Customer.objects.all() if query: - # Try to normalize the phone number for searching + # Используем ту же логику поиска, что и в AJAX API (api_search_customers) + # Это обеспечивает согласованность между веб-интерфейсом и API + + # Нормализуем номер телефона phone_normalized = normalize_query_phone(query) - customers = customers.filter( - Q(name__icontains=query) | - Q(email__icontains=query) | - Q(phone__icontains=phone_normalized) - ) + + # Определяем стратегию поиска + strategy, search_value = determine_search_strategy(query) + + # Строим Q-объект для поиска (единая функция) + q_objects = build_customer_search_query(query, strategy, search_value) + + # Добавляем поиск по телефону + if phone_normalized: + q_objects |= Q(phone__icontains=phone_normalized) + + customers = customers.filter(q_objects) customers = customers.order_by('-created_at') @@ -157,6 +167,44 @@ def determine_search_strategy(query): return ('name_only', query) +def build_customer_search_query(query, strategy, search_value): + """ + Строит Q-объект для поиска клиентов на основе стратегии. + + Используется в customer_list() и api_search_customers() для единообразия. + + Args: + query: Исходный поисковый запрос (для fallback) + strategy: Стратегия поиска (из determine_search_strategy) + search_value: Значение для поиска (из determine_search_strategy) + + Returns: + Q-объект для фильтрации Customer.objects + """ + if strategy == 'name_only': + return Q(name__icontains=search_value) + + elif strategy == 'email_prefix': + # Query вида "team_x3m@" — ищем email, начинающиеся с "team_x3m" + return Q(email__istartswith=search_value) + + elif strategy == 'email_domain': + # Query вида "@bk" — ищем все email с доменом "@bk.*" + return Q(email__icontains=f'@{search_value}') + + elif strategy == 'email_full': + # Query вида "test@bk.ru" — полный поиск по email + return Q(email__icontains=search_value) + + elif strategy == 'universal': + # Query вида "natul" (3+ символов) — ищем везде + return Q(name__icontains=search_value) | Q(email__icontains=search_value) + + else: + # На случай неизвестной стратегии (не должно быть) + return Q(name__icontains=query) + + @require_http_methods(["GET"]) def api_search_customers(request): """ @@ -203,31 +251,8 @@ def api_search_customers(request): # Определяем стратегию поиска на основе содержимого query strategy, search_value = determine_search_strategy(query) - # Строим Q-объект в зависимости от стратегии - if strategy == 'name_only': - # Поиск только по имени (для коротких запросов) - q_objects = Q(name__icontains=search_value) - - elif strategy == 'email_prefix': - # Query вида "team_x3m@" — ищем email, начинающиеся с "team_x3m" - # Это решает проблему: не найдёт "natulj@bk.ru" - q_objects = Q(email__istartswith=search_value) - - elif strategy == 'email_domain': - # Query вида "@bk" — ищем все email с доменом "@bk.*" - q_objects = Q(email__icontains=f'@{search_value}') - - elif strategy == 'email_full': - # Query вида "test@bk.ru" — полный поиск по email - q_objects = Q(email__icontains=search_value) - - elif strategy == 'universal': - # Query вида "natul" (3+ символов) — ищем везде - q_objects = Q(name__icontains=search_value) | Q(email__icontains=search_value) - - else: - # На случай неизвестной стратегии (не должно быть) - q_objects = Q(name__icontains=query) + # Строим Q-объект для поиска (единая функция, используется везде) + q_objects = build_customer_search_query(query, strategy, search_value) # Для телефона ищем по нормализованному номеру и по цифрам if phone_normalized: