diff --git a/app/conf/api.py b/app/conf/api.py index ad55947..472a8f8 100644 --- a/app/conf/api.py +++ b/app/conf/api.py @@ -1,9 +1,15 @@ from django.urls import path -from search.api.views import SearchApi, HintApi, AutoCompleteApi +from search.api.views import ( + SearchApi, + HintApi, + AutoCompleteApi, + IncreaseProductScoreApi, +) urlpatterns = [ path("search", SearchApi.as_view(), name="search_api"), - path("hint", HintApi.as_view(), name="hint api"), - path('autocomplete_schema', AutoCompleteApi.as_view(), name='autocomplete api') + path("hint", HintApi.as_view(), name="hint_api"), + path("autocomplete_schema", AutoCompleteApi.as_view(), name="autocomplete_api"), + path("score/", IncreaseProductScoreApi.as_view(), name="score_api"), ] diff --git a/app/search/api/serializers.py b/app/search/api/serializers.py index acc6ac5..e408e3a 100644 --- a/app/search/api/serializers.py +++ b/app/search/api/serializers.py @@ -58,7 +58,7 @@ class HintResponseSerializer(serializers.Serializer): class AutoCompleteRequestSerializer(serializers.Serializer): content = serializers.CharField(validators=[MinLengthValidator(3)]) - exclude = serializers.ListSerializer(child=QueryFilterSerializer()) + exclude = serializers.ListSerializer(child=QueryFilterSerializer(), default=[]) def create(self, validated_data): raise NotImplementedError diff --git a/app/search/api/views.py b/app/search/api/views.py index 0b4d574..e450be0 100644 --- a/app/search/api/views.py +++ b/app/search/api/views.py @@ -1,6 +1,7 @@ from drf_yasg import openapi from drf_yasg.utils import swagger_auto_schema from rest_framework import status +from rest_framework.generics import get_object_or_404 from rest_framework.response import Response from rest_framework.views import APIView from search.api.serializers import HintRequestSerializer @@ -12,6 +13,7 @@ from search.api.serializers import ( AutoCompleteRequestSerializer, AutoCompleteResponseSerializer, ) +from search.models import Product from search.services.search import process_search from search.services.autocomplete_schema import autocomplete_schema @@ -64,6 +66,28 @@ class AutoCompleteApi(APIView): serializer = AutoCompleteRequestSerializer(data=request.data) serializer.is_valid(raise_exception=True) return Response( - {"nodes": autocomplete_schema(serializer.data["content"], serializer.data["exclude"])}, + { + "nodes": autocomplete_schema( + serializer.data["content"], serializer.data["exclude"] + ) + }, status=status.HTTP_200_OK, ) + + +class IncreaseProductScoreApi(APIView): + @swagger_auto_schema( + manual_parameters=[ + openapi.Parameter( + "id", + openapi.IN_PATH, + description="Product id", + type=openapi.TYPE_INTEGER, + ) + ] + ) + def post(self, request, pk): + product = get_object_or_404(Product, id=pk) + product.score += 1 + product.save(update_fields=["score"]) + return Response({"score": product.score}, status=status.HTTP_200_OK) diff --git a/app/search/models.py b/app/search/models.py index 55cf53f..329293c 100644 --- a/app/search/models.py +++ b/app/search/models.py @@ -18,14 +18,22 @@ class Characteristic(models.Model): class UnitCharacteristic(models.Model): name = models.TextField("Имя", blank=False) value = models.TextField("Значение", blank=False) - numeric_value = models.IntegerField(default=0) + numeric_value_min = models.IntegerField(default=0) + numeric_value_max = models.IntegerField(default=0) unit = models.TextField("Размерность", blank=False) def __str__(self): return str(self.name) def serialize_self(self): - return {"name": self.name, "value": self.numeric_value, "unit": self.unit} + return { + "id": self.id, + "name": self.name, + "value": self.numeric_value_min + if self.numeric_value_min == self.numeric_value_max + else f"{self.numeric_value_min}:{self.numeric_value_max}", + "unit": self.unit, + } class Meta: db_table = "unit_characteristic" @@ -50,14 +58,16 @@ class Product(models.Model): Category, related_name="products", on_delete=models.CASCADE ) - # score = models.IntegerField(default=0) + score = models.IntegerField(default=0) def __str__(self): return str(self.name) def serialize_self(self) -> dict: return { + "id": self.id, "name": self.name, + "score": self.score, "characteristic": [ x.characteristic.serialize_self() for x in self.characteristics.all() ] @@ -69,7 +79,7 @@ class Product(models.Model): class Meta: db_table = "product" - # ordering = ["score"] + ordering = ["-score"] class ProductCharacteristic(models.Model): diff --git a/app/search/services/autocomplete_schema.py b/app/search/services/autocomplete_schema.py index d0568ee..f7f003e 100644 --- a/app/search/services/autocomplete_schema.py +++ b/app/search/services/autocomplete_schema.py @@ -18,9 +18,9 @@ def autocomplete_schema(val: str, exclude: List[Dict]): "value": product["name"], }, } - for product in Product.objects.filter( - name__unaccent__icontains=val - ).values("name") + for product in Product.objects.filter(name__unaccent__icontains=val)[ + :20 + ].values("name") ] ) if not category_exclude: @@ -30,9 +30,9 @@ def autocomplete_schema(val: str, exclude: List[Dict]): "coordinate": cat["name"].lower().index(val.lower()), "value": {"type": "Category", "value": cat["name"]}, } - for cat in Category.objects.filter( - name__unaccent__icontains=val - ).values("name") + for cat in Category.objects.filter(name__unaccent__icontains=val)[ + :20 + ].values("name") ] ) schema.extend( @@ -41,9 +41,9 @@ def autocomplete_schema(val: str, exclude: List[Dict]): "coordinate": char["value"].lower().index(val.lower()), "value": {"type": char["name"], "value": char["value"]}, } - for char in Characteristic.objects.filter( - value__unaccent__icontains=val - ).values("name", "value") + for char in Characteristic.objects.filter(value__unaccent__icontains=val)[ + :20 + ].values("name", "value") ] ) return schema diff --git a/app/search/services/load_products.py b/app/search/services/load_products.py index af0c235..192c4d4 100644 --- a/app/search/services/load_products.py +++ b/app/search/services/load_products.py @@ -64,12 +64,23 @@ def load_excel(): def process_unit_character(): for el in UnitCharacteristic.objects.all(): - nums = re.findall("[-+]?[.]?[\d]+(?:,\d\d\d)*[\.]?\d*(?:[eE][-+]?\d+)?", el.value) - if len(nums) != 1: - el.delete() - else: + nums = re.findall( + "[-+]?[.]?[\d]+(?:,\d\d\d)*[\.]?\d*(?:[eE][-+]?\d+)?", el.value + ) + if len(nums) == 1: try: - el.numeric_value = int(float(nums[0].replace(",", "."))) + el.numeric_value_min = int(float(nums[0].replace(",", "."))) + el.numeric_value_max = int(float(nums[0].replace(",", "."))) + el.save() + except ValueError: + el.delete() + elif len(nums): + try: + nums = [int(float(x.replace(",", "."))) for x in nums] + min_num = min(nums) + max_num = max(nums) + el.numeric_value_min = min_num + el.numeric_value_max = max_num el.save() except ValueError: el.delete() diff --git a/app/search/services/search.py b/app/search/services/search.py index 83c37d7..6d221aa 100644 --- a/app/search/services/search.py +++ b/app/search/services/search.py @@ -16,15 +16,26 @@ from search.services.translate import translate_en_ru, translate_ru_en def process_unit_operation(unit: ProductUnitCharacteristic.objects, operation: str): if operation.startswith("<=") or operation.startswith("=<"): - return unit.filter(characteristic__numeric_value__lte=int(float(operation[2:]))) + return unit.filter( + characteristic__numeric_value_max__lte=int(float(operation[2:])) + ) elif operation.startswith("=>") or operation.startswith(">="): - return unit.filter(characteristic__numeric_value__gte=int(float(operation[2:]))) + return unit.filter( + characteristic__numeric_value_min__gte=int(float(operation[2:])) + ) elif operation.startswith(">"): - return unit.filter(characteristic__numeric_value__gt=int(float(operation[1:]))) + return unit.filter( + characteristic__numeric_value_min__gt=int(float(operation[1:])) + ) elif operation.startswith("<"): - return unit.filter(characteristic__numeric_value__lt=int(float(operation[1:]))) + return unit.filter( + characteristic__numeric_value_max__lt=int(float(operation[1:])) + ) elif operation.startswith("="): - return unit.filter(characteristic__numeric_value=int(float(operation[1:]))) + return unit.filter( + characteristic__numeric_value_min__gte=int(float(operation[1:])), + characteristic__numeric_value_max__lte=int(float(operation[1:])), + ) return unit @@ -33,7 +44,9 @@ def apply_qs_search(qs: Product.objects, text: str): text = text.replace(st, " ") text = text.split() for word in text: - qs = qs.filter(name__unaccent__trigram_similar=word) | qs.filter(name__unaccent__icontains=word) + qs = qs.filter(name__unaccent__trigram_similar=word) | qs.filter( + name__unaccent__icontains=word + ) return qs