Fix Unicode product search
This commit is contained in:
parent
ea0a10f7c4
commit
5b22e0a2c8
@ -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"]
|
||||
|
||||
|
||||
@ -7,4 +7,5 @@ class CommerceConfig(AppConfig):
|
||||
verbose_name = "Товарная аналитика"
|
||||
|
||||
def ready(self):
|
||||
from . import search # noqa: F401
|
||||
from . import signals # noqa: F401
|
||||
|
||||
47
backend/commerce/search.py
Normal file
47
backend/commerce/search.py
Normal 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)]
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user