added score system, range unit characteristics, minor improvements

This commit is contained in:
Alexander Karpov 2022-10-22 16:14:43 +03:00
parent c68dfce10a
commit faaccff7dc
7 changed files with 93 additions and 29 deletions

View File

@ -1,9 +1,15 @@
from django.urls import path from django.urls import path
from search.api.views import SearchApi, HintApi, AutoCompleteApi from search.api.views import (
SearchApi,
HintApi,
AutoCompleteApi,
IncreaseProductScoreApi,
)
urlpatterns = [ urlpatterns = [
path("search", SearchApi.as_view(), name="search_api"), path("search", SearchApi.as_view(), name="search_api"),
path("hint", HintApi.as_view(), name="hint api"), path("hint", HintApi.as_view(), name="hint_api"),
path('autocomplete_schema', AutoCompleteApi.as_view(), name='autocomplete api') path("autocomplete_schema", AutoCompleteApi.as_view(), name="autocomplete_api"),
path("score/<int:pk>", IncreaseProductScoreApi.as_view(), name="score_api"),
] ]

View File

@ -58,7 +58,7 @@ class HintResponseSerializer(serializers.Serializer):
class AutoCompleteRequestSerializer(serializers.Serializer): class AutoCompleteRequestSerializer(serializers.Serializer):
content = serializers.CharField(validators=[MinLengthValidator(3)]) content = serializers.CharField(validators=[MinLengthValidator(3)])
exclude = serializers.ListSerializer(child=QueryFilterSerializer()) exclude = serializers.ListSerializer(child=QueryFilterSerializer(), default=[])
def create(self, validated_data): def create(self, validated_data):
raise NotImplementedError raise NotImplementedError

View File

@ -1,6 +1,7 @@
from drf_yasg import openapi from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema from drf_yasg.utils import swagger_auto_schema
from rest_framework import status from rest_framework import status
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from search.api.serializers import HintRequestSerializer from search.api.serializers import HintRequestSerializer
@ -12,6 +13,7 @@ from search.api.serializers import (
AutoCompleteRequestSerializer, AutoCompleteRequestSerializer,
AutoCompleteResponseSerializer, AutoCompleteResponseSerializer,
) )
from search.models import Product
from search.services.search import process_search from search.services.search import process_search
from search.services.autocomplete_schema import autocomplete_schema from search.services.autocomplete_schema import autocomplete_schema
@ -64,6 +66,28 @@ class AutoCompleteApi(APIView):
serializer = AutoCompleteRequestSerializer(data=request.data) serializer = AutoCompleteRequestSerializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
return Response( 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, 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)

View File

@ -18,14 +18,22 @@ class Characteristic(models.Model):
class UnitCharacteristic(models.Model): class UnitCharacteristic(models.Model):
name = models.TextField("Имя", blank=False) name = models.TextField("Имя", blank=False)
value = 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) unit = models.TextField("Размерность", blank=False)
def __str__(self): def __str__(self):
return str(self.name) return str(self.name)
def serialize_self(self): 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: class Meta:
db_table = "unit_characteristic" db_table = "unit_characteristic"
@ -50,14 +58,16 @@ class Product(models.Model):
Category, related_name="products", on_delete=models.CASCADE Category, related_name="products", on_delete=models.CASCADE
) )
# score = models.IntegerField(default=0) score = models.IntegerField(default=0)
def __str__(self): def __str__(self):
return str(self.name) return str(self.name)
def serialize_self(self) -> dict: def serialize_self(self) -> dict:
return { return {
"id": self.id,
"name": self.name, "name": self.name,
"score": self.score,
"characteristic": [ "characteristic": [
x.characteristic.serialize_self() for x in self.characteristics.all() x.characteristic.serialize_self() for x in self.characteristics.all()
] ]
@ -69,7 +79,7 @@ class Product(models.Model):
class Meta: class Meta:
db_table = "product" db_table = "product"
# ordering = ["score"] ordering = ["-score"]
class ProductCharacteristic(models.Model): class ProductCharacteristic(models.Model):

View File

@ -18,9 +18,9 @@ def autocomplete_schema(val: str, exclude: List[Dict]):
"value": product["name"], "value": product["name"],
}, },
} }
for product in Product.objects.filter( for product in Product.objects.filter(name__unaccent__icontains=val)[
name__unaccent__icontains=val :20
).values("name") ].values("name")
] ]
) )
if not category_exclude: if not category_exclude:
@ -30,9 +30,9 @@ def autocomplete_schema(val: str, exclude: List[Dict]):
"coordinate": cat["name"].lower().index(val.lower()), "coordinate": cat["name"].lower().index(val.lower()),
"value": {"type": "Category", "value": cat["name"]}, "value": {"type": "Category", "value": cat["name"]},
} }
for cat in Category.objects.filter( for cat in Category.objects.filter(name__unaccent__icontains=val)[
name__unaccent__icontains=val :20
).values("name") ].values("name")
] ]
) )
schema.extend( schema.extend(
@ -41,9 +41,9 @@ def autocomplete_schema(val: str, exclude: List[Dict]):
"coordinate": char["value"].lower().index(val.lower()), "coordinate": char["value"].lower().index(val.lower()),
"value": {"type": char["name"], "value": char["value"]}, "value": {"type": char["name"], "value": char["value"]},
} }
for char in Characteristic.objects.filter( for char in Characteristic.objects.filter(value__unaccent__icontains=val)[
value__unaccent__icontains=val :20
).values("name", "value") ].values("name", "value")
] ]
) )
return schema return schema

View File

@ -64,12 +64,23 @@ def load_excel():
def process_unit_character(): def process_unit_character():
for el in UnitCharacteristic.objects.all(): for el in UnitCharacteristic.objects.all():
nums = re.findall("[-+]?[.]?[\d]+(?:,\d\d\d)*[\.]?\d*(?:[eE][-+]?\d+)?", el.value) nums = re.findall(
if len(nums) != 1: "[-+]?[.]?[\d]+(?:,\d\d\d)*[\.]?\d*(?:[eE][-+]?\d+)?", el.value
el.delete() )
else: if len(nums) == 1:
try: 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() el.save()
except ValueError: except ValueError:
el.delete() el.delete()

View File

@ -16,15 +16,26 @@ from search.services.translate import translate_en_ru, translate_ru_en
def process_unit_operation(unit: ProductUnitCharacteristic.objects, operation: str): def process_unit_operation(unit: ProductUnitCharacteristic.objects, operation: str):
if operation.startswith("<=") or operation.startswith("=<"): 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(">="): 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(">"): 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("<"): 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("="): 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 return unit
@ -33,7 +44,9 @@ def apply_qs_search(qs: Product.objects, text: str):
text = text.replace(st, " ") text = text.replace(st, " ")
text = text.split() text = text.split()
for word in text: 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 return qs