diff --git a/app/conf/settings/base.py b/app/conf/settings/base.py index 9f06045..0cf4f96 100644 --- a/app/conf/settings/base.py +++ b/app/conf/settings/base.py @@ -60,6 +60,7 @@ DJANGO_APPS = [ "django.contrib.humanize", "django.contrib.admin", "django.forms", + "django.contrib.postgres", ] THIRD_PARTY_APPS = ["rest_framework", "corsheaders", "drf_yasg"] diff --git a/app/search/api/serializers.py b/app/search/api/serializers.py index aa79b4b..227e69f 100644 --- a/app/search/api/serializers.py +++ b/app/search/api/serializers.py @@ -2,9 +2,19 @@ from rest_framework import serializers from django.core.validators import MinLengthValidator, MinValueValidator +class QueryFilterSerializer(serializers.Serializer): + value = serializers.CharField(max_length=100) + type = serializers.CharField(max_length=100) + + def create(self, validated_data): + raise NotImplementedError + + def update(self, instance, validated_data): + raise NotImplementedError + class SearchSerializer(serializers.Serializer): - body = serializers.CharField(max_length=200) + body = serializers.ListSerializer(child=QueryFilterSerializer()) def create(self, validated_data): raise NotImplementedError diff --git a/app/search/api/views.py b/app/search/api/views.py index 81e5993..b81d958 100644 --- a/app/search/api/views.py +++ b/app/search/api/views.py @@ -5,47 +5,60 @@ from rest_framework.response import Response from rest_framework.views import APIView from search.api.serializers import HintRequestSerializer -from search.api.serializers import SearchSerializer, ResponseSerializer, HintResponseSerializer, AutoCompleteRequestSerializer, AutoCompleteResponseSerializer -from search.services.search import process_string +from search.api.serializers import ( + SearchSerializer, + ResponseSerializer, + HintResponseSerializer, + AutoCompleteRequestSerializer, + AutoCompleteResponseSerializer, +) +from search.services.search import process_search from search.services.autocomplete_schema import autocomplete_schema from search.services.hints import get_hints user_response = openapi.Response("search results", ResponseSerializer) hint_response = openapi.Response("hints", HintResponseSerializer) -autocomplete_response = openapi.Response("autocomplete schema", AutoCompleteResponseSerializer) +autocomplete_response = openapi.Response( + "autocomplete schema", AutoCompleteResponseSerializer +) class SearchApi(APIView): @swagger_auto_schema(request_body=SearchSerializer, responses={200: user_response}) - def post(self, request, format=None): + def post(self, request): serializer = SearchSerializer(data=request.data) serializer.is_valid(raise_exception=True) return Response( - process_string(serializer.data["body"]), status=status.HTTP_200_OK + process_search(serializer.data["body"]), status=status.HTTP_200_OK ) class HintApi(APIView): - @swagger_auto_schema(request_body=HintRequestSerializer, responses={200: hint_response}) - def post(self, request, format=None): + @swagger_auto_schema( + request_body=HintRequestSerializer, responses={200: hint_response} + ) + def post(self, request): serializer = HintRequestSerializer(data=request.data) serializer.is_valid(raise_exception=True) return Response( { - 'type': get_hints(serializer.data['content']), - 'value': serializer.data['content'] + "type": get_hints(serializer.data["content"]), + "value": serializer.data["content"], }, - status=status.HTTP_200_OK + status=status.HTTP_200_OK, ) + class AutoCompleteApi(APIView): - @swagger_auto_schema(request_body=AutoCompleteRequestSerializer, responses={200: autocomplete_response}) - def post(self, request, format=None): + @swagger_auto_schema( + request_body=AutoCompleteRequestSerializer, + responses={200: autocomplete_response}, + ) + def post(self, request): serializer = AutoCompleteRequestSerializer(data=request.data) serializer.is_valid(raise_exception=True) return Response( - { - 'nodes': autocomplete_schema(serializer.data['content']) - }, status=status.HTTP_200_OK + {"nodes": autocomplete_schema(serializer.data["content"])}, + status=status.HTTP_200_OK, ) diff --git a/app/search/models.py b/app/search/models.py index 6dc7e69..8cec9db 100644 --- a/app/search/models.py +++ b/app/search/models.py @@ -49,6 +49,8 @@ class Product(models.Model): Category, related_name="products", on_delete=models.CASCADE ) + # score = models.IntegerField(default=0) + def __str__(self): return str(self.name) @@ -56,13 +58,17 @@ class Product(models.Model): return { "name": self.name, "characteristic": [ - x.serialize_self() for x in self.characteristics.objects.all() + x.characteristic.serialize_self() for x in self.characteristics.all() ] - + [x.serialize_self() for x in self.unit_characteristics.objects.all()], + + [ + x.characteristic.serialize_self() + for x in self.unit_characteristics.all() + ], } class Meta: db_table = "product" + # ordering = ["score"] class ProductCharacteristic(models.Model): diff --git a/app/search/services/autocomplete_schema.py b/app/search/services/autocomplete_schema.py index 9e6b185..69db958 100644 --- a/app/search/services/autocomplete_schema.py +++ b/app/search/services/autocomplete_schema.py @@ -1,37 +1,38 @@ from search.models import Product, Category, Characteristic + def autocomplete_schema(val: str): schema = [] schema.extend( [ { - 'coordinate': product['name'].index(val), - 'value': { - 'type': 'Name', - 'value': product['name'], - } - } for product in Product.objects.filter(name__contains=val).values('name')] - ) - schema.extend( - [ - { - 'coordinate': cat['name'].index(val), - 'value': { - 'type': 'Category', - 'value': cat['name'] - } - } for cat in Category.objects.filter(name__contains=val).values('name') + "coordinate": product["name"].index(val), + "value": { + "type": "Name", + "value": product["name"], + }, + } + for product in Product.objects.filter(name__contains=val).values("name") ] ) schema.extend( [ { - 'coordinate': char.name.index(val), - 'value': { - 'type': char.name, - 'value': char.value - } - } for char in Characteristic.objects.filter(name__contains=val).values('name', 'value') + "coordinate": cat["name"].index(val), + "value": {"type": "Category", "value": cat["name"]}, + } + for cat in Category.objects.filter(name__contains=val).values("name") + ] + ) + schema.extend( + [ + { + "coordinate": char.name.index(val), + "value": {"type": char.name, "value": char.value}, + } + for char in Characteristic.objects.filter(name__contains=val).values( + "name", "value" + ) ] ) return schema diff --git a/app/search/services/hints.py b/app/search/services/hints.py index d7d7c42..b4579de 100644 --- a/app/search/services/hints.py +++ b/app/search/services/hints.py @@ -2,11 +2,11 @@ from search.models import Product, Category, Characteristic def get_hints(content: str) -> str: - category = 'Unknown' + category = "Unknown" if content in list(map(lambda product: product.name, Product.objects.all())): - category = 'Name' + category = "Name" elif content in list(map(lambda category: category.name, Category.objects.all())): - category = 'Category' + category = "Category" elif content in list(map(lambda char: char.value, Characteristic.objects.all())): category = Characteristic.objects.get(value=content).name return category diff --git a/app/search/services/search.py b/app/search/services/search.py index 5ebf07e..ed51108 100644 --- a/app/search/services/search.py +++ b/app/search/services/search.py @@ -1,6 +1,44 @@ -from search.models import Product +from search.models import Product, Characteristic, ProductCharacteristic from typing import List -def process_string(text: str) -> List[dict]: - return [x.serialize_self() for x in Product.objects.filter(name__contains=text)[5:]] +def process_search(data: List[dict]) -> List[dict]: + prep_data = [] + prep_dict = {} + prep_dict_char_type = {} + + for x in data: + dat = dict(x) + if x["type"] in ["Name", "Category", "Unknown"]: + prep_data.append(dat) + else: + if x["type"] in list(prep_dict.keys()): + prep_dict[x["type"]] = prep_dict[ + x["type"] + ] | ProductCharacteristic.objects.filter( + characteristic__in=prep_dict_char_type[x["type"]], + characteristic__value__unaccent__trigram_similar=x["value"], + ) + else: + prep_dict_char_type[x["type"]] = Characteristic.objects.filter( + name__contains=x["type"] + ) + prep_dict[x["type"]] = ProductCharacteristic.objects.filter( + characteristic__in=prep_dict_char_type[x["type"]], + characteristic__value__unaccent__trigram_similar=x["value"], + ) + for el, val in prep_dict.items(): + prep_data.append({"type": el, "value": val}) + qs = Product.objects.filter() + for x in prep_data: + typ = x["type"] + val = x["value"] + if typ == "Name": + qs = qs.filter(name__unaccent__trigram_similar=val) + elif typ == "Category": + qs = qs.filter(category__name__unaccent__trigram_similar=val) + elif typ == "Unknown": + continue + else: + qs = qs.filter(characteristics__in=val) + return [x.serialize_self() for x in qs[:5]] diff --git a/pg.sql b/pg.sql new file mode 100644 index 0000000..84bc687 --- /dev/null +++ b/pg.sql @@ -0,0 +1,2 @@ +CREATE EXTENSION unaccent; +CREATE EXTENSION pg_trgm;