use russian cities names for search params

This commit is contained in:
2025-05-27 11:51:51 +03:00
parent 46cd984395
commit bc3ef3fb57
11 changed files with 241 additions and 37 deletions

View File

@@ -22,14 +22,17 @@ class CountrySerializer(serializers.ModelSerializer):
return obj.international_name or obj.official_name
class CitySerializer(serializers.ModelSerializer):
value = serializers.CharField(source='name') # для совместимости с селектом
label = serializers.CharField(source='name') # для отображения в селекте
value = serializers.CharField(source='name') # для совместимости с селектом на фронте (используем английское название)
label = serializers.SerializerMethodField() # для отображения в селекте (используем русское название)
country_name = serializers.CharField(source='country.international_name')
class Meta:
model = City
fields = ['id', 'value', 'label', 'country_name']
def get_label(self, obj):
return obj.russian_name or obj.name # используем русское название если есть, иначе английское
class RouteSerializer(serializers.ModelSerializer):
from_city_name = serializers.SerializerMethodField()
to_city_name = serializers.SerializerMethodField()

View File

@@ -8,6 +8,7 @@ from django.shortcuts import get_object_or_404
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import Q
from api.auth.serializers import UserResponseSerializer
from api.models import UserProfile
@@ -133,7 +134,10 @@ class CityView(ViewSet):
# поиск по названию города
search = request.query_params.get('search')
if search:
cities = cities.filter(name__icontains=search)
cities = cities.filter(
Q(name__icontains=search) | # поиск по английскому названию
Q(russian_name__icontains=search) # поиск по русскому названию
)
# ограничиваем количество результатов и сортируем по имени
cities = cities.order_by('name')[:100]

View File

@@ -1,12 +1,40 @@
from rest_framework import generics
from typing import cast
from rest_framework.request import Request
from routes.models import Route
from .serializers import SearchRouteSerializer
from api.utils.pagination import StandardResultsSetPagination
from rest_framework.exceptions import ValidationError
from routes.constants.routeChoices import owner_type_choices
from django.utils import timezone
from django.db.models import Q
import cyrtranslit
from urllib.parse import unquote
def get_city_variations(city_name: str) -> list[str]:
"""
Получает варианты написания города, включая транслитерацию
"""
variations = set()
# добавляем оригинальное название и его варианты с разным регистром
variations.add(city_name)
variations.add(city_name.lower())
variations.add(city_name.title())
# пробуем добавить транслитерации
try:
# пробуем транслитерировать в обе стороны
lat = cyrtranslit.to_latin(city_name, 'ru')
cyr = cyrtranslit.to_cyrillic(city_name, 'ru')
# добавляем варианты транслитерации с разным регистром
for variant in [lat, cyr]:
variations.add(variant)
variations.add(variant.lower())
variations.add(variant.title())
except:
pass
return list(variations)
class SearchRouteListView(generics.ListAPIView):
serializer_class = SearchRouteSerializer
@@ -14,8 +42,45 @@ class SearchRouteListView(generics.ListAPIView):
def get_queryset(self):
owner_type = self.kwargs.get('owner_type')
from_city = cast(Request, self.request).query_params.get('from')
to_city = cast(Request, self.request).query_params.get('to')
# получаем маршрут из URL и разбиваем его на города
route = self.kwargs.get('route', '')
print(f"Raw route from URL: {route}")
if route:
# декодируем URL дважды, так как он может быть дважды закодирован
route = unquote(unquote(route))
print(f"Decoded route: {route}")
try:
from_city, to_city = route.split('-', 1)
print(f"Split cities - from: {from_city}, to: {to_city}")
except ValueError:
from_city = to_city = None
else:
# если маршрут не указан в URL, берем из query params и декодируем
from_city = self.request.query_params.get('from', '')
to_city = self.request.query_params.get('to', '')
# декодируем значения, если они закодированы
if from_city:
try:
# пробуем декодировать дважды для случая двойного кодирования
from_city = unquote(from_city)
if '%' in from_city: # если после первого декодирования остались %-коды
from_city = unquote(from_city)
except Exception as e:
print(f"Error decoding from_city: {e}")
if to_city:
try:
# пробуем декодировать дважды для случая двойного кодирования
to_city = unquote(to_city)
if '%' in to_city: # если после первого декодирования остались %-коды
to_city = unquote(to_city)
except Exception as e:
print(f"Error decoding to_city: {e}")
print(f"Query params - from: {from_city}, to: {to_city}")
valid_types = [choice[0] for choice in owner_type_choices]
current_time = timezone.now()
@@ -24,15 +89,35 @@ class SearchRouteListView(generics.ListAPIView):
raise ValidationError("Invalid or missing owner_type. Must be either 'customer' or 'mover'")
# базовый фильтр по типу владельца и актуальности
queryset = Route.objects.filter(
owner_type=owner_type,
status="actual")
queryset = Route.objects.all()
# Применяем базовые фильтры
queryset = queryset.filter(owner_type=owner_type, status='actual')
# фильтруем по городам если они указаны
if from_city:
queryset = queryset.filter(from_city__name__iexact=from_city)
print(f"Searching for from_city: {from_city}")
# Получаем варианты написания для поиска
from_city_variations = get_city_variations(from_city)
print(f"From city variations: {from_city_variations}")
# Используем Q objects для поиска по обоим полям
from_city_filter = (
Q(from_city__name__in=from_city_variations) |
Q(from_city__russian_name__in=from_city_variations)
)
queryset = queryset.filter(from_city_filter)
if to_city:
queryset = queryset.filter(to_city__name__iexact=to_city)
print(f"Searching for to_city: {to_city}")
# получаем варианты написания для поиска
to_city_variations = get_city_variations(to_city)
print(f"To city variations: {to_city_variations}")
# используем Q objects для поиска по обоим полям
to_city_filter = (
Q(to_city__name__in=to_city_variations) |
Q(to_city__russian_name__in=to_city_variations)
)
queryset = queryset.filter(to_city_filter)
# фильтруем по времени в зависимости от типа
if owner_type == 'mover':