Skip to content

Commit

Permalink
Merge pull request #31 from hackathone-prosept-team2/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
ratarov authored Dec 7, 2023
2 parents 118b97b + 0965551 commit 94432d7
Show file tree
Hide file tree
Showing 42 changed files with 1,308 additions and 141 deletions.
7 changes: 7 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.env
venv
*.py[cod]
__pycache__
presentation/
htmlcov/
frontend/
4 changes: 1 addition & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,4 @@ jobs:
sudo docker rm prosept_nginx
sudo docker rmi ${{ secrets.DOCKER_USERNAME }}/prosept_back
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/prosept_back:latest
sudo docker-compose up -d --build
sudo docker exec -i prosept_back python manage.py migrate
sudo docker exec -i prosept_back python manage.py collectstatic --noinput
sudo docker-compose up -d --build
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -317,4 +317,5 @@ pip-selfcheck.json

# VS Code
.vscode/
my_data/
my_data/
frontend/
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ COPY poetry.lock pyproject.toml ./

RUN pip3 install poetry && curl -sSL 'https://install.python-poetry.org' | python3 -

ENV PYTHONPATH="$PYTHONPATH:/app"

RUN poetry config virtualenvs.create false \
&& poetry install --without dev --no-interaction --no-ansi

RUN python -m nltk.downloader wordnet

COPY . .

CMD ["gunicorn", "config.wsgi:application", "--bind", "0:8000" ]
ENTRYPOINT ["sh", "entrypoint.sh"]
114 changes: 91 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,107 @@
# backend_django

#### Запуск для тестирования API:
Скопировать проект
# YANDEX & PROSEPT HACKATHON: Сервис сопоставления товаров Просепт и наименований дилеров. Команда 2.
http://81.31.246.5/ <br>
данные для пробного входа на сайт и в админ-панель
```
git clone [email protected]:hackathone-prosept-team2/backend_django.git
email: [email protected]
password: Password-123
```
## Архивы и фото
Вставить сюда фотки с сайта

Если ранее уже скопировали и были обновления на бэке, надо подтянуть их из гита (ветка main):
```
git pull
```
## FRONTEND:
https://github.com/hackathone-prosept-team2/frontend

Создать файл .env (можно переименовать .env.example в .env)
## DATA-SCIENCE:
https://github.com/hackathone-prosept-team2/data-science

Перейти в папку deploy внутри проекта
## BACKEND:
### Инструменты:
![image](https://img.shields.io/badge/Python%203.11-FFD43B?style=for-the-badge&logo=python&logoColor=blue)
![image](https://img.shields.io/badge/Django%204.2-092E20?style=for-the-badge&logo=django&logoColor=green)
![image](https://img.shields.io/badge/django%20rest%203.14-ff1709?style=for-the-badge&logo=django&logoColor=white)
![image](https://img.shields.io/badge/PostgreSQL-316192?style=for-the-badge&logo=postgresql&logoColor=white)
![image](https://img.shields.io/badge/Docker-2CA5E0?style=for-the-badge&logo=docker&logoColor=white)
![image](https://img.shields.io/badge/Nginx-009639?style=for-the-badge&logo=nginx&logoColor=white)
![image](https://img.shields.io/badge/GitHub-100000?style=for-the-badge&logo=github&logoColor=white)
![image](https://img.shields.io/badge/GitHub_Actions-2088FF?style=for-the-badge&logo=github-actions&logoColor=white)
![image](https://img.shields.io/badge/Pytest-86D46B?style=for-the-badge&logo=redux%20saga&logoColor=999999)

### Доступ в админ-панель:
http://81.31.246.5/admin
```
cd backend_django/deploy/
email: [email protected]
password: Password-123
```

Запустить сборку контейнеров
### API-документация:
http://81.31.246.5/api/schema/swagger/#
```
docker-compose up -d --build
Авторизация через headers:
Authorization: Token <access-token>
```

После запуска контейнеров: добавить миграции в базу, собрать статику для документации
### Тестирование бэкенда:
![image](https://github.com/hackathone-prosept-team2/backend_django/blob/main/presentation/coverage.png)

### Описание возможностей
| Метод | Endpoint | Назначение |
|:-------|----------------------------------------------|--------------------------------------------------------------------------|
| POST |/api/v1/auth/token/login/ | Получение токена для пользователя (вход) |
| POST |/api/v1/auth/token/login/ | Уничтожение токена для пользователя (выход) |
| GET |/api/v1/users/ | Получение списка пользователей |
| POST |/api/v1/users/ | Создание нового пользователя |
| GET |/api/v1/users/{id}/ | Просмотр информации о пользователе с id {id} |
| GET |/api/v1/users/me/ | Просмотр информации о текущем пользователе |
| GET |/api/v1/attributes/ | Полный список словарей моделей-атрибутов |
| GET |/api/v1/dealers/ | Получение списка дилеров |
| GET |/api/v1/dealers/{id}/ | Просмотр информации о дилере с id {id} |
| GET |/api/v1/dealers/report/ | Отчет по дилерам и их ключам |
| GET |/api/v1/keys/ | Список ключей дилеров с возможностью фильтрации по параметрам |
| GET |/api/v1/keys/{id}/ | Информация о ключе дилера с id {id} |
| GET |/api/v1/keys/{id}/matches/ | Список подобранных возможных продуктов к этому ключу |
| POST |/api/v1/keys/{id}/choose_match/ | Выбор 1 продукта для сопоставления с ключем дилера |
| POST |/api/v1/keys/{id}/decline_matches/ | Пометка всех предложенных продуктов как неподходящие |
| GET |/api/v1/keys/{id}/prices/ | Список цен к ключу дилера |
| GET |/api/v1/keys/export/ | Выгрузка сопоставленных ключей и продуктов с фильтром по новым/дате/периоду |
| POST |/api/v1/prices/ | Загрузка в базу данных цен из предоставленного Просепт файла и запуск системы подбора |
| DELETE |/api/v1/prices/ | Удаление из базы данных всех цен, связанных рекомендаций и установленных связей |
| GET |/api/v1/products/ | Список продуктов компании |
| GET |/api/v1/products/{id}/ | Просмотр информации о продукте компании с id {id} |

### База данных и связи сущностей приложения
![image](https://github.com/hackathone-prosept-team2/backend_django/blob/main/presentation/database.png)


## Запуск проекта
### Переменные окружения
Файл .env хранится в корневой папке проекта; пример заполнения в .env.example.

### Запуск с установленным Docker
Копировать проект в папку целиком (для запуска контейнеров достаточно .env в корне проекта и папки /infra)
```
docker exec -it prosept_back python manage.py migrate
docker exec -it prosept_back python manage.py collectstatic --noinput
git clone [email protected]:hackathone-prosept-team2/backend_django.git
```

Создать суперпользователя для админки:
Перейти в папку deploy и запустить сборку контейнеров
```
cd backend_django/deploy
docker compose up -d
```
Сайт доступен по адресу http://127.0.0.1/<br>
В базе данных уже есть Суперпользователь с указанными с .env данными (или данными по умолчанию выше) и загружены файлы:
```
docker exec -it prosept_back python manage.py migrate
marketing_dealer
marketing_product
marketing_productdealerkey
```

Доступные ссылки:
http://127.0.0.1/api/schema/swagger/#/ - документация API
http://127.0.0.1/admin - админ-панель
## Команда
### Backend:
[Руслан Атаров](https://github.com/ratarov) <br>
### Data-Science:
[Кирилл Шулев](https://github.com/Kexxshas)<br>
Павел Барков
### Frontend:
[Максим Таланов](https://github.com/maxtalanov) <br>
[Линда Суховенко](https://github.com/SuhLinda)
### Project Manger
Виктория Мудрова
50 changes: 43 additions & 7 deletions apps/api/v1/dealers/filters.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
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 MATCH_NUMBER, KeyStatus


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

article = filters.CharFilter(method="get_article")
status = filters.CharFilter(method="get_status")

Expand All @@ -13,7 +16,7 @@ class Meta:
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 @@ -22,8 +25,41 @@ def get_article(self, queryset, name, value):

def get_status(self, queryset, name, value):
"""Поиск по статусу."""
if not value:
return queryset
if value == "-":
return queryset.filter(status=101)
return queryset.exclude(status=101)
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


class DealerKeyExportFilter(filters.FilterSet):
new = filters.BooleanFilter(method="get_new")
since = filters.DateFilter(method="get_since")
date = filters.DateFilter(method="get_date")

class Meta:
model = DealerKey
fields = ("new", "since", "date")

def get_new(self, queryset, name, value):
"""Фильтр по is_provided - ключи, которых не было в начальном csv."""
if value:
return queryset.filter(is_provided=False)
return queryset

def get_since(self, queryset, name, value):
"""Фильтр по дате присвоения продукта - начиная с даты."""
if value:
return queryset.filter(edited_at__gte=value)
return queryset

def get_date(self, queryset, name, value):
"""Фильтр по дате присвоения продукта - в дату."""
if value:
return queryset.filter(edited_at=value)
return queryset
46 changes: 43 additions & 3 deletions apps/api/v1/dealers/schema.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
from drf_spectacular.utils import extend_schema, OpenApiParameter
from datetime import date

from . import serializer as ser
from drf_spectacular.utils import OpenApiParameter, extend_schema

from config.constants import KeyStatus

from . import serializer as ser

dealer_schema = {
"list": extend_schema(description="Получение списка дилеров."),
"retrieve": extend_schema(description="Просмотр экземпляра дилера."),
}


filter_options = [KeyStatus.CHECK, KeyStatus.DECLINED, KeyStatus.FOUND]

key_schema = {
"list": extend_schema(
description="Получение списка уникальных ключей дилеров.",
Expand All @@ -23,7 +28,7 @@
),
OpenApiParameter(
name="status",
description="Фильтр по статусу; варианты: '-', '0', '1+'",
description=f"Фильтр по статусу: {filter_options}",
),
OpenApiParameter(
name="page",
Expand All @@ -35,13 +40,48 @@
}


key_export_schema = {
"get": extend_schema(
description="Получение списка уникальных ключей дилеров.",
parameters=[
OpenApiParameter(
name="new",
type=bool,
description=(
"Фильтр по ключам, которых не было в стартовом csv"
),
),
OpenApiParameter(
name="since",
type=date,
description=(
"Выводит ключи, записанные с указанной даты. "
"Формат ДД.ММ.ГГГГ"
),
),
OpenApiParameter(
name="date",
type=date,
description=(
"Выводит ключи, записанные в указанную дату. "
"Формат ДД.ММ.ГГГГ"
),
),
],
),
}

matches_schema = {
"post": extend_schema(responses=ser.MatchSerializer(many=True))
}


choose_match_schema = {
"post": extend_schema(
description=(
"Выбор 1 предлагаемого соответствия Ключ - Продукт. "
"Остальные помечаются как 'Не подходит'."
),
request=ser.ChooseMatchSerializer(),
responses=ser.MatchSerializer(many=True),
)
Expand Down
Loading

0 comments on commit 94432d7

Please sign in to comment.