Fix Unicode product search

This commit is contained in:
lizerk 2026-08-18 09:28:58 +03:00
parent ea0a10f7c4
commit 5b22e0a2c8
4 changed files with 72 additions and 2 deletions

View File

@ -29,6 +29,7 @@ from commerce.services import (
)
from commerce.services.credentials import CredentialError
from commerce.integrations.moysklad import MoySkladError
from commerce.search import UnicodeCasefoldSearchFilter
from commerce.tasks import enqueue_connection_sync
from .permissions import CommerceAccessPermission
@ -190,11 +191,16 @@ class ProductViewSet(viewsets.ModelViewSet):
permission_classes = [CommerceAccessPermission]
filter_backends = [
DjangoFilterBackend,
filters.SearchFilter,
UnicodeCasefoldSearchFilter,
filters.OrderingFilter,
]
filterset_fields = ["kind", "is_active", "parent"]
search_fields = ["sku", "name", "barcode", "bindings__external_sku"]
search_fields = [
"sku__unicode_casefold__contains",
"name__unicode_casefold__contains",
"barcode__unicode_casefold__contains",
"bindings__external_sku__unicode_casefold__contains",
]
ordering_fields = ["sku", "name", "created_at", "updated_at"]
ordering = ["name", "sku"]

View File

@ -7,4 +7,5 @@ class CommerceConfig(AppConfig):
verbose_name = "Товарная аналитика"
def ready(self):
from . import search # noqa: F401
from . import signals # noqa: F401

View File

@ -0,0 +1,47 @@
from django.db.backends.signals import connection_created
from django.db.models import CharField, Transform
from django.dispatch import receiver
from rest_framework.filters import SearchFilter
SQLITE_CASEFOLD_FUNCTION = "EUSRR_CASEFOLD"
def _casefold(value):
return None if value is None else str(value).casefold()
@receiver(connection_created, dispatch_uid="commerce.sqlite_unicode_casefold")
def register_sqlite_casefold(sender, connection, **kwargs):
if connection.vendor != "sqlite":
return
connection.connection.create_function(
SQLITE_CASEFOLD_FUNCTION,
1,
_casefold,
deterministic=True,
)
class UnicodeCasefold(Transform):
lookup_name = "unicode_casefold"
function = "LOWER"
output_field = CharField()
def as_sqlite(self, compiler, connection, **extra_context):
return super().as_sql(
compiler,
connection,
function=SQLITE_CASEFOLD_FUNCTION,
**extra_context,
)
CharField.register_lookup(UnicodeCasefold)
class UnicodeCasefoldSearchFilter(SearchFilter):
"""DRF substring search with Unicode-aware case normalization."""
def get_search_terms(self, request):
return [term.casefold() for term in super().get_search_terms(request)]

View File

@ -42,6 +42,22 @@ def test_employee_can_read_but_cannot_create_product(api_client, user_factory):
assert create_response.status_code == status.HTTP_403_FORBIDDEN
def test_product_search_matches_unicode_substring_without_case(api_client, user_factory):
user = user_factory()
Product.objects.create(sku="SEA-001", name="Автомат «Бродяги морей»")
Product.objects.create(sku="OTHER-001", name="Другой товар")
api_client.force_authenticate(user=user)
response = api_client.get(
reverse("api:v1:commerce:product-list"),
{"search": "бродяг"},
)
assert response.status_code == status.HTTP_200_OK
assert response.data["count"] == 1
assert response.data["results"][0]["name"] == "Автомат «Бродяги морей»"
def test_staff_can_create_product(api_client, user_factory):
user = user_factory(staff=True)
api_client.force_authenticate(user=user)