Skip to content

Commit

Permalink
Merge pull request #27 from hackathone-prosept-team2/feat/add_del_prices
Browse files Browse the repository at this point in the history
Feat/add del prices
  • Loading branch information
ratarov authored Dec 6, 2023
2 parents bfbb7ee + c07c83f commit 6b4cb53
Show file tree
Hide file tree
Showing 17 changed files with 92 additions and 81 deletions.
34 changes: 15 additions & 19 deletions apps/api/v1/dealers/filters.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
from django_filters import rest_framework as filters
from django.db.models import Q
from django_filters import rest_framework as filters

from apps.dealers.models import DealerKey
from config.constants import KeyStatus, MATCH_NUMBER
from config.constants import MATCH_NUMBER, KeyStatus


class DealerKeyFilter(filters.FilterSet):
"""Фильтр для ключей дилеров."""

article = filters.CharFilter(method="get_article")
status = filters.CharFilter(method="get_status")
# similarity = filters.NumberFilter(method="get_similarity")

class Meta:
model = DealerKey
fields = ("article", "dealer_id", "status")

def get_article(self, queryset, name, value):
"""Поиск по вхождению."""
"""Поиск по вхождению значения в артикул или наименование."""
if value:
return queryset.filter(
Q(key__icontains=value) | Q(name__icontains=value)
Expand All @@ -24,18 +25,13 @@ def get_article(self, queryset, name, value):

def get_status(self, queryset, name, value):
"""Поиск по статусу."""
if not value:
return queryset
if value == KeyStatus.FOUND:
return queryset.filter(product_id__isnull=False)
if value == KeyStatus.DECLINED:
return queryset.filter(declined=MATCH_NUMBER)
return queryset.filter(
product_id__isnull=True, declined__lt=MATCH_NUMBER
)

# def get_similarity(self, queryset, name, value):
# """Поиск по показателю схожести."""
# if value:
# return queryset.filter(similarity__gte=value)
# return queryset
if value:
if value == KeyStatus.FOUND:
return queryset.filter(product_id__isnull=False)
if value == KeyStatus.DECLINED:
return queryset.filter(declined=MATCH_NUMBER)
if value == KeyStatus.CHECK:
return queryset.filter(
product_id__isnull=True, declined__lt=MATCH_NUMBER
)
return queryset
6 changes: 5 additions & 1 deletion apps/api/v1/dealers/schema.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from drf_spectacular.utils import extend_schema, OpenApiParameter
from drf_spectacular.utils import OpenApiParameter, extend_schema

from config.constants import KeyStatus

Expand Down Expand Up @@ -45,6 +45,10 @@

choose_match_schema = {
"post": extend_schema(
description=(
"Выбор 1 предлагаемого соответствия Ключ - Продукт. "
"Остальные помечаются как 'Не подходит'."
),
request=ser.ChooseMatchSerializer(),
responses=ser.MatchSerializer(many=True),
)
Expand Down
2 changes: 0 additions & 2 deletions apps/api/v1/dealers/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ class KeySerializer(serializers.ModelSerializer):
product = ProductShortSerializer()
name = serializers.CharField()
last_price = serializers.DecimalField(max_digits=7, decimal_places=2)
# similarity = serializers.IntegerField()
status = serializers.SerializerMethodField()

class Meta:
Expand All @@ -68,7 +67,6 @@ class Meta:
"name",
"last_price",
"status",
# "similarity",
"dealer",
"product",
)
Expand Down
12 changes: 5 additions & 7 deletions apps/api/v1/dealers/views.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
from drf_spectacular.utils import extend_schema_view

from rest_framework import views, status
from rest_framework.response import Response
from rest_framework import status, views
from rest_framework.generics import ListAPIView
from rest_framework.response import Response
from rest_framework.viewsets import ReadOnlyModelViewSet

from apps.dealers.crud import (
list_dealers,
list_dealers_report_data,
list_keys,
list_matches,
list_dealers_report_data,
)
from apps.dealers.services import decline_matches, choose_match
from apps.dealers.services import choose_match, decline_matches

from ..pagination import CommonPagePagination
from . import schema, serializer as ser
from .filters import DealerKeyFilter
from . import schema
from . import serializer as ser


@extend_schema_view(**schema.dealer_schema)
Expand Down
29 changes: 29 additions & 0 deletions apps/api/v1/prices/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from drf_spectacular.utils import extend_schema

from config.constants import NESTED_PAGE


key_prices_schema = {
"list": extend_schema(
description=(
"Список цен по 1 указанному ключу. Пагинация - "
f"выдает по {NESTED_PAGE} записей"
)
),
}


prices_schema = {
"post": extend_schema(
description=(
"Загрузка файла с ценами по умолчанию и "
"запуск системы подбора рекомендаций"
),
),
"delete": extend_schema(
description=(
"Удаление записей цен, результатов работы подбора рекомендаций, "
"выбора соответствий."
)
),
}
11 changes: 7 additions & 4 deletions apps/api/v1/prices/views.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
from drf_spectacular.utils import extend_schema_view
from rest_framework import status, views
from rest_framework.generics import ListAPIView
from rest_framework import views, status
from rest_framework.response import Response

from apps.prices.crud import list_key_prices, there_are_prices_in_db
from apps.prices.services import delete_prices_and_relations, create_prices
from apps.prices.services import create_prices, delete_prices_and_relations

from ..pagination import NestedPagePagination
from . import serializer as ser
from . import schema, serializer as ser


@extend_schema_view(**schema.key_prices_schema)
class KeyPriceView(ListAPIView):
"""Цены дилеров компании Просепт по 1 ключу."""
"""Список цен по 1 указанному ключу."""

pagination_class = NestedPagePagination
serializer_class = ser.KeyPriceSerializer
Expand All @@ -20,6 +22,7 @@ def get_queryset(self):
return list_key_prices(key_pk=key_pk)


@extend_schema_view(**schema.prices_schema)
class PricesView(views.APIView):
"""Загрузка и удаление цен дилеров и связанных ключей дилеров."""

Expand Down
1 change: 0 additions & 1 deletion apps/api/v1/products/schema.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from drf_spectacular.utils import extend_schema


product_schema = {
"list": extend_schema(description="Получение списка продуктов."),
"retrieve": extend_schema(description="Просмотр экземпляра продукта."),
Expand Down
3 changes: 1 addition & 2 deletions apps/api/v1/products/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
from apps.products.crud import list_products

from ..pagination import CommonPagePagination
from . import schema
from . import serializer as ser
from . import schema, serializer as ser


@extend_schema_view(**schema.product_schema)
Expand Down
8 changes: 4 additions & 4 deletions apps/api/v1/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@

from .dealers.views import (
ChooseMatchView,
DealerViewset,
DealerKeyViewset,
MatchView,
DeclineMatchesView,
DealersReport,
DealerViewset,
DeclineMatchesView,
MatchView,
)
from .prices.views import PricesView, KeyPriceView
from .prices.views import KeyPriceView, PricesView
from .products.views import ProductViewset
from .users.views import UserViewset

Expand Down
21 changes: 4 additions & 17 deletions apps/dealers/crud.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
from django.db.models import (
QuerySet,
OuterRef,
Subquery,
Count,
Q,
Prefetch,
)
from django.db.models import Count, OuterRef, Prefetch, Q, QuerySet, Subquery
from django.shortcuts import get_object_or_404

from apps.prices.models import DealerPrice
Expand Down Expand Up @@ -65,11 +58,6 @@ def list_keys() -> QuerySet[DealerKey]:
declined=Count(
"matches", filter=Q(matches__status=Match.MatchStatus.NO)
),
# similarity=Subquery(
# Match.objects.filter(key_id=OuterRef("pk")).values(
# "similarity"
# )[:1]
# ),
)
# фильтр позволяет выгружать только ключи, которые есть в списке цен
.filter(name__isnull=False)
Expand Down Expand Up @@ -137,6 +125,7 @@ def delete_all_matches() -> None:
def get_or_create_dealer_key(
id: int, dealer_key: str, dealer_id: int
) -> tuple[DealerKey, bool]:
"""Получение или создание (при отсутствии) экземпляра ключа."""
return DealerKey.objects.get_or_create(
key=dealer_key,
dealer_id=dealer_id,
Expand All @@ -145,16 +134,14 @@ def get_or_create_dealer_key(


def get_first_free_dealer_key_id():
"""Получение первого свободного id Ключа дилера."""
return DealerKey.objects.last().id + 1


def matches_bulk_create(field_sets: list[dict]) -> None:
"""Создание в БД партии объектов Match (рекомендации)."""
fields = []
for field_set in field_sets:
fields.append(Match(**field_set))
Match.objects.bulk_create(fields)
return None


# def get_keys_values():
# return dict(DealerKey.objects.values_list("key", "id"))
2 changes: 1 addition & 1 deletion apps/dealers/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
from django.db.transaction import atomic

from .crud import (
list_matches,
change_status_to_declined,
choose_one_decline_others,
list_matches,
set_product_for_dealer_key,
)
from .models import Match
Expand Down
1 change: 1 addition & 0 deletions apps/prices/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def delete_all_prices() -> None:


def prices_bulk_create(fields_sets: dict) -> None:
"""Создание в БД партии объектов DealerPrice (цена дилера)."""
fields = []
for field_set in fields_sets:
fields.append(DealerPrice(**field_set))
Expand Down
9 changes: 7 additions & 2 deletions apps/prices/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
from django.db.transaction import atomic

from apps.dealers.crud import (
delete_new_dealer_keys,
delete_all_matches,
get_or_create_dealer_key,
delete_new_dealer_keys,
get_first_free_dealer_key_id,
get_or_create_dealer_key,
matches_bulk_create,
)
from apps.services.match_service import RecommendationService
Expand All @@ -24,6 +24,8 @@ def delete_prices_and_relations() -> None:


def get_prices_data() -> list[dict]:
"""Получение загружаемых данных по ценам дилеров."""
# TODO пока работает с файлом по умолчанию; расширяемо на json и csv
filename = Path("static", "fixtures", "marketing_dealerprice.csv")
with open(filename, mode="r", encoding="utf8") as file:
reader = csv.DictReader(file, delimiter=";")
Expand All @@ -32,6 +34,7 @@ def get_prices_data() -> list[dict]:


def form_price_obj_fields(id: int, dataset: dict) -> dict:
"""Формирование словаря с полями объекта DealerPrice."""
return {
"key_id": id,
"price": dataset["price"],
Expand All @@ -42,6 +45,7 @@ def form_price_obj_fields(id: int, dataset: dict) -> dict:


def recommend_products(key_datasets: dict[int, str]) -> None:
"""Запуск системы подбора продуктов к списку Ключей, запись результатов."""
service = RecommendationService()
matches_datasets = []
for key_id, name in key_datasets.items():
Expand All @@ -60,6 +64,7 @@ def recommend_products(key_datasets: dict[int, str]) -> None:

@atomic
def create_prices() -> None:
"""Сервис по созданию Цен дилеров с подбором продуктов к ним."""
data = get_prices_data()
id_counter = get_first_free_dealer_key_id()
prices_datasets = []
Expand Down
3 changes: 2 additions & 1 deletion apps/services/match_service.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pandas as pd
import re

import pandas as pd
from nltk.stem import WordNetLemmatizer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics import pairwise_distances
Expand Down
11 changes: 1 addition & 10 deletions config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,18 +93,9 @@
# Password validation

AUTH_PASSWORD_VALIDATORS = [
# {
# "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
# },
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
# {
# "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
# },
# {
# "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
# },
]

# Internationalization
Expand Down Expand Up @@ -162,7 +153,7 @@
"HIDE_USERS": False,
"PERMISSIONS": {
"user_list": [
"rest_framework.permissions.AllowAny",
"rest_framework.permissions.IsAuthenticated",
],
"user": [
"rest_framework.permissions.IsAuthenticated",
Expand Down
4 changes: 2 additions & 2 deletions deploy/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ services:

backend:
container_name: prosept_back
build: ../.
# image: ratarov/prosept_back:latest
# build: ../.
image: ratarov/prosept_back:latest
restart: always
volumes:
- static_data:/app/static/
Expand Down
16 changes: 8 additions & 8 deletions deploy/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ server {
proxy_pass http://backend:8000/admin/;
}

# location / {
# root /usr/share/nginx/html;
# index index.html index.htm;
# try_files $uri /index.html;
# proxy_set_header Host $host;
# proxy_set_header X-Forwarded-Host $host;
# proxy_set_header X-Forwarded-Server $host;
# }
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri /index.html;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
}
}

0 comments on commit 6b4cb53

Please sign in to comment.