Merge remote-tracking branch 'origin/PMSManager_refactor' into zorn-dev
This commit is contained in:
138
.drone.yml
138
.drone.yml
@@ -1,47 +1,111 @@
|
|||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: docker
|
name: Touchh Hotel AntiFraud Pipeline
|
||||||
name: Django CI/CD
|
namespace: touchh
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
# Шаг 1: Установка зависимостей, миграции и тесты
|
# Шаг 1: Клонирование репозитория
|
||||||
- name: test
|
- name: clone_repo
|
||||||
image: python:3.10
|
image: alpine/git
|
||||||
environment:
|
|
||||||
DATABASE_URL: mysql://root@R0sebud:0.0.0.0:3306/w1510415_wp832
|
|
||||||
commands:
|
commands:
|
||||||
- python -m venv .venv
|
- if [ ! -d .git ]; then git clone $DRONE_REPO_URL .; fi
|
||||||
- source .venv/bin/activate
|
- git fetch --all
|
||||||
- pip install --upgrade pip
|
- git reset --hard $DRONE_COMMIT
|
||||||
- pip install -r requirements.txt
|
|
||||||
- python manage.py migrate
|
|
||||||
- flake8 . # Линтер
|
|
||||||
- pytest # Запуск тестов
|
|
||||||
|
|
||||||
# Шаг 2: Запуск и проверка Telegram-бота
|
# Шаг 2: Обновление и запуск с помощью update.sh
|
||||||
- name: bot-check
|
- name: deploy_app
|
||||||
image: python:3.10
|
image: docker:24
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: mysql://root@R0sebud:0.0.0.0:3306/w1510415_wp832
|
MYSQL_PASSWORD: touchh
|
||||||
|
volumes:
|
||||||
|
- name: docker_sock
|
||||||
|
path: /var/run/docker.sock
|
||||||
commands:
|
commands:
|
||||||
- python -m venv .venv
|
- apk add --no-cache bash
|
||||||
- source .venv/bin/activate
|
- chmod +x ./bin/update
|
||||||
- pip install --upgrade pip
|
- docker-compose up -d
|
||||||
|
- until docker inspect -f '{{.State.Running}}' src-web-1 | grep true; do echo "Waiting for container to be running..."; sleep 5; done
|
||||||
|
- git branch --set-upstream-to=origin/PMSManager_refactor PMSManager_refactor || true
|
||||||
|
- ./bin/update
|
||||||
|
|
||||||
|
# Шаг 3: Миграция базы данных
|
||||||
|
- name: run_migrations
|
||||||
|
image: docker:24
|
||||||
|
environment:
|
||||||
|
MYSQL_PASSWORD: touchh
|
||||||
|
volumes:
|
||||||
|
- name: docker_sock
|
||||||
|
path: /var/run/docker.sock
|
||||||
|
depends_on:
|
||||||
|
- web
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache bash
|
||||||
|
- until docker inspect -f '{{.State.Running}}' src-web-1 | grep true; do echo "Waiting for container to be running..."; sleep 5; done
|
||||||
|
- chmod +x ./bin/cli
|
||||||
|
- ./bin/cli migrate
|
||||||
|
|
||||||
|
# Шаг 4: Тестирование
|
||||||
|
- name: run_tests
|
||||||
|
image: python:3.12-alpine
|
||||||
|
environment:
|
||||||
|
MYSQL_PASSWORD: touchh
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache mariadb-client mariadb-connector-c-dev gcc musl-dev pkgconfig
|
||||||
- pip install -r requirements.txt
|
- pip install -r requirements.txt
|
||||||
- python manage.py run_bot & # Запуск бота в фоне
|
- python manage.py test
|
||||||
- sleep 5 # Ждём, чтобы бот запустился
|
|
||||||
- python test_bot.py # Проверка работы бота
|
|
||||||
|
|
||||||
# services:
|
services:
|
||||||
# # Шаг 3: Сервис базы данных MySQL
|
# Сервис базы данных
|
||||||
# - name: mysql
|
- name: db
|
||||||
# image: mysql:8
|
image: mariadb:11.6
|
||||||
# environment:
|
environment:
|
||||||
# MYSQL_ROOT_PASSWORD: R0sebud
|
MYSQL_RANDOM_ROOT_PASSWORD: 1
|
||||||
# MYSQL_USER: user
|
MYSQL_DATABASE: touchh
|
||||||
# MYSQL_PASSWORD: password
|
MYSQL_USER: touchh
|
||||||
# MYSQL_DATABASE: w1510415_wp832
|
MYSQL_PASSWORD: touchh
|
||||||
|
volumes:
|
||||||
|
- name: mysql_data
|
||||||
|
temp: {}
|
||||||
|
|
||||||
trigger:
|
# Сервис Django (Web)
|
||||||
event:
|
- name: web
|
||||||
- push
|
image: touchh-py
|
||||||
- pull_request
|
environment:
|
||||||
|
MYSQL_PASSWORD: touchh
|
||||||
|
command: ['python3', 'manage.py', 'runserver', '0.0.0.0:8000']
|
||||||
|
ports:
|
||||||
|
- port: 8000
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
volumes:
|
||||||
|
- name: app_volume
|
||||||
|
path: /app
|
||||||
|
|
||||||
|
# Сервис Telegram Bot
|
||||||
|
- name: bot
|
||||||
|
image: touchh-py
|
||||||
|
environment:
|
||||||
|
MYSQL_PASSWORD: touchh
|
||||||
|
command: ['python3', 'manage.py', 'run_bot']
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
|
||||||
|
# Сервис планировщика задач
|
||||||
|
- name: scheduler
|
||||||
|
image: touchh-py
|
||||||
|
environment:
|
||||||
|
MYSQL_PASSWORD: touchh
|
||||||
|
command: ['python3', 'manage.py', 'start_scheduler']
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: docker_sock
|
||||||
|
host:
|
||||||
|
path: /var/run/docker.sock
|
||||||
|
- name: mysql_data
|
||||||
|
temp: {}
|
||||||
|
- name: app_volume
|
||||||
|
host:
|
||||||
|
path: ./
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -16,3 +16,5 @@ db.sqlite3
|
|||||||
# Ignore files
|
# Ignore files
|
||||||
.fake
|
.fake
|
||||||
docker-compose.override.yaml
|
docker-compose.override.yaml
|
||||||
|
tmp/*
|
||||||
|
tmp_data/*
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
FROM python:3.12-alpine
|
FROM python:3.12-alpine
|
||||||
|
|
||||||
COPY requirements.txt /
|
COPY requirements.txt /
|
||||||
|
COPY . .
|
||||||
RUN set -ex ;\
|
RUN set -ex ;\
|
||||||
apk add --no-cache musl-dev mariadb-connector-c-dev gcc ;\
|
apk add --no-cache musl-dev mariadb-connector-c-dev gcc ;\
|
||||||
pip3 install -r /requirements.txt ;\
|
pip3 install -r /requirements.txt ;\
|
||||||
|
|||||||
112
README.md
112
README.md
@@ -161,4 +161,114 @@ python manage.py runserver
|
|||||||
|
|
||||||
#### Проверка интеграции с PMS
|
#### Проверка интеграции с PMS
|
||||||
|
|
||||||
Для каждого отеля можно проверять статус интеграции с PMS (Bnovo, Travel Line, Realty) и получать ответ о доступности PMS.
|
Для каждого отеля можно проверять статус интеграции с PMS (Bnovo, Travel Line, Realty) и получать ответ о доступности PMS.
|
||||||
|
|
||||||
|
|
||||||
|
#### Разработка плагинов для интеграции с PMS
|
||||||
|
|
||||||
|
Для разработки плагина используются следующие инструменты:
|
||||||
|
|
||||||
|
- Django
|
||||||
|
- Python
|
||||||
|
- Pydantic
|
||||||
|
|
||||||
|
*код примера для плагина*
|
||||||
|
|
||||||
|
```python
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import requests
|
||||||
|
from asgiref.sync import sync_to_async
|
||||||
|
from hotels.models import Reservation
|
||||||
|
from .base_plugin import BasePMSPlugin
|
||||||
|
import logging
|
||||||
|
|
||||||
|
class ExamplePMSPlugin(BasePMSPlugin):
|
||||||
|
"""
|
||||||
|
Плагин для интеграции с PMS Example.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, hotel):
|
||||||
|
super().__init__(hotel.pms)
|
||||||
|
self.hotel = hotel
|
||||||
|
self.api_url = self.hotel.pms.url
|
||||||
|
self.token = self.hotel.pms.token
|
||||||
|
self.logger = self._configure_logger()
|
||||||
|
|
||||||
|
def _configure_logger(self):
|
||||||
|
logger = logging.getLogger(self.__class__.__name__)
|
||||||
|
handler_console = logging.StreamHandler()
|
||||||
|
handler_file = logging.FileHandler(f'{self.__class__.__name__.lower()}.log')
|
||||||
|
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
handler_console.setFormatter(formatter)
|
||||||
|
handler_file.setFormatter(formatter)
|
||||||
|
logger.addHandler(handler_console)
|
||||||
|
logger.addHandler(handler_file)
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
return logger
|
||||||
|
|
||||||
|
def get_default_parser_settings(self):
|
||||||
|
"""
|
||||||
|
Возвращает настройки для обработки данных.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"field_mapping": {
|
||||||
|
"check_in": "arrival_date",
|
||||||
|
"check_out": "departure_date",
|
||||||
|
"room_number": "room",
|
||||||
|
"status": "status",
|
||||||
|
},
|
||||||
|
"date_format": "%Y-%m-%d %H:%M:%S"
|
||||||
|
}
|
||||||
|
|
||||||
|
async def _fetch_data(self):
|
||||||
|
"""
|
||||||
|
Получает данные из API Example PMS.
|
||||||
|
"""
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {self.token}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
now = datetime.now()
|
||||||
|
payload = {
|
||||||
|
"from_date": (now - timedelta(days=7)).strftime("%Y-%m-%d"),
|
||||||
|
"to_date": now.strftime("%Y-%m-%d"),
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = await sync_to_async(requests.post)(
|
||||||
|
self.api_url, json=payload, headers=headers
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
return await self._process_data(data)
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
self.logger.error(f"Ошибка API: {e}")
|
||||||
|
return {"processed_items": 0, "errors": [str(e)]}
|
||||||
|
|
||||||
|
async def _process_data(self, data):
|
||||||
|
"""
|
||||||
|
Обрабатывает и сохраняет данные в базу.
|
||||||
|
"""
|
||||||
|
processed_items = 0
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
for item in data.get("bookings", []):
|
||||||
|
try:
|
||||||
|
reservation, created = await sync_to_async(Reservation.objects.update_or_create)(
|
||||||
|
reservation_id=item['id'],
|
||||||
|
defaults={
|
||||||
|
'room_number': item['room'],
|
||||||
|
'check_in': datetime.strptime(item['arrival_date'], "%Y-%m-%d"),
|
||||||
|
'check_out': datetime.strptime(item['departure_date'], "%Y-%m-%d"),
|
||||||
|
'status': item['status'],
|
||||||
|
'hotel': self.hotel,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
processed_items += 1
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Ошибка обработки записи {item['id']}: {e}")
|
||||||
|
errors.append(str(e))
|
||||||
|
|
||||||
|
return {"processed_items": processed_items, "errors": errors}
|
||||||
|
|
||||||
|
```
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from django.db.models import F
|
|||||||
class DatabaseConnector:
|
class DatabaseConnector:
|
||||||
def __init__(self, db_settings_id):
|
def __init__(self, db_settings_id):
|
||||||
self.db_settings_id = db_settings_id
|
self.db_settings_id = db_settings_id
|
||||||
self.logger = CustomLogger(name="DatabaseConnector", log_level="DEBUG").get_logger()
|
self.logger = CustomLogger(name="DatabaseConnector", log_level="WARNING").get_logger()
|
||||||
self.connection = None
|
self.connection = None
|
||||||
self.db_settings = self.get_db_settings()
|
self.db_settings = self.get_db_settings()
|
||||||
|
|
||||||
|
|||||||
@@ -53,11 +53,25 @@ class PMSIntegrationManager:
|
|||||||
Загружает плагин, соответствующий PMS конфигурации отеля.
|
Загружает плагин, соответствующий PMS конфигурации отеля.
|
||||||
"""
|
"""
|
||||||
pms_name = self.hotel.pms.plugin_name.lower() # Приводим название плагина к нижнему регистру
|
pms_name = self.hotel.pms.plugin_name.lower() # Приводим название плагина к нижнему регистру
|
||||||
if pms_name == "ecvi_intermark" or pms_name == "ecvi":
|
|
||||||
from pms_integration.plugins.ecvi_pms import EcviPMSPlugin
|
# Формируем имя модуля и класса плагина
|
||||||
self.plugin = EcviPMSPlugin(self.hotel)
|
plugin_module_name = f"pms_integration.plugins.{pms_name}_pms"
|
||||||
else:
|
plugin_class_name = f"{pms_name.capitalize()}PMSPlugin"
|
||||||
raise ValueError(f"Неизвестный PMS: {pms_name}")
|
|
||||||
|
try:
|
||||||
|
# Динамически импортируем модуль плагина
|
||||||
|
plugin_module = importlib.import_module(plugin_module_name)
|
||||||
|
|
||||||
|
# Динамически получаем класс плагина
|
||||||
|
plugin_class = getattr(plugin_module, plugin_class_name, None)
|
||||||
|
if not plugin_class or not issubclass(plugin_class, BasePMSPlugin):
|
||||||
|
raise ImportError(f"Класс {plugin_class_name} не найден или не является наследником BasePMSPlugin.")
|
||||||
|
|
||||||
|
# Инициализируем плагин
|
||||||
|
self.plugin = plugin_class(self.hotel)
|
||||||
|
|
||||||
|
except ImportError as e:
|
||||||
|
raise ValueError(f"Ошибка загрузки плагина для PMS {pms_name}: {e}")
|
||||||
def fetch_data(self):
|
def fetch_data(self):
|
||||||
"""
|
"""
|
||||||
Получает данные из PMS с использованием загруженного плагина.
|
Получает данные из PMS с использованием загруженного плагина.
|
||||||
|
|||||||
@@ -176,13 +176,13 @@ class BnovoPMSPlugin(BasePMSPlugin):
|
|||||||
"""Получение данных о бронированиях с помощью эндпоинта /dashboard."""
|
"""Получение данных о бронированиях с помощью эндпоинта /dashboard."""
|
||||||
logger.info("Начало процесса получения данных о бронированиях.")
|
logger.info("Начало процесса получения данных о бронированиях.")
|
||||||
|
|
||||||
# # Вызов функции получения данных аккаунта
|
# Вызов функции получения данных аккаунта
|
||||||
# try:
|
try:
|
||||||
# account_data = await self._fetch_and_log_account_data()
|
account_data = await self._fetch_and_log_account_data()
|
||||||
# logger.info(f"Данные аккаунта успешно получены:")
|
logger.info(f"Данные аккаунта успешно получены:")
|
||||||
# except Exception as e:
|
except Exception as e:
|
||||||
# logger.error(f"Ошибка получения данных аккаунта: {e}")
|
logger.error(f"Ошибка получения данных аккаунта: {e}")
|
||||||
# raise
|
raise
|
||||||
|
|
||||||
url = f"{self.api_url}/dashboard"
|
url = f"{self.api_url}/dashboard"
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class EcviPMSPlugin(BasePMSPlugin):
|
|||||||
handler_file.setFormatter(formatter)
|
handler_file.setFormatter(formatter)
|
||||||
self.logger.addHandler(handler_console)
|
self.logger.addHandler(handler_console)
|
||||||
self.logger.addHandler(handler_file)
|
self.logger.addHandler(handler_file)
|
||||||
self.logger.setLevel(logging.DEBUG)
|
self.logger.setLevel(logging.WARNING)
|
||||||
|
|
||||||
def get_default_parser_settings(self):
|
def get_default_parser_settings(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,34 +1,39 @@
|
|||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
|
import json
|
||||||
|
import os
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from asgiref.sync import sync_to_async
|
from asgiref.sync import sync_to_async
|
||||||
from pms_integration.models import PMSConfiguration
|
|
||||||
from hotels.models import Hotel, Reservation
|
from hotels.models import Hotel, Reservation
|
||||||
from .base_plugin import BasePMSPlugin
|
from .base_plugin import BasePMSPlugin
|
||||||
|
from touchh.utils.log import CustomLogger
|
||||||
|
class ShelterPMSPlugin(BasePMSPlugin):
|
||||||
class EcviPMSPlugin(BasePMSPlugin):
|
|
||||||
"""
|
"""
|
||||||
Плагин для интеграции с PMS Ecvi (интерфейс для получения данных об отеле).
|
Плагин для интеграции с PMS Shelter.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, pms_config):
|
def __init__(self, hotel):
|
||||||
super().__init__(pms_config)
|
super().__init__(hotel.pms) # Передаем PMS-конфигурацию в базовый класс
|
||||||
|
self.hotel = hotel # Сохраняем объект отеля
|
||||||
# Инициализация логгера
|
|
||||||
self.logger = logging.getLogger(self.__class__.__name__) # Логгер с именем класса
|
# Проверка PMS-конфигурации
|
||||||
handler = logging.StreamHandler() # Потоковый обработчик для вывода в консоль
|
if not self.hotel.pms:
|
||||||
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
raise ValueError(f"Отель {self.hotel.name} не имеет связанной PMS конфигурации.")
|
||||||
handler.setFormatter(formatter)
|
|
||||||
self.logger.addHandler(handler)
|
|
||||||
self.logger.setLevel(logging.DEBUG) # Уровень логирования
|
|
||||||
|
|
||||||
# Инициализация параметров API
|
# Инициализация параметров API
|
||||||
self.api_url = pms_config.url
|
self.api_url = self.hotel.pms.url
|
||||||
self.token = pms_config.token
|
self.token = self.hotel.pms.token
|
||||||
self.username = pms_config.username
|
|
||||||
self.password = pms_config.password
|
# Настройка логгера
|
||||||
self.pagination_count = 50 # Максимальное количество записей на страницу (если используется пагинация)
|
self.logger = CustomLogger(name="ShelterPMSPlugin", log_level="WARNING").get_logger()
|
||||||
|
handler_console = logging.StreamHandler()
|
||||||
|
handler_file = logging.FileHandler('var/log/shelter_pms_plugin.log')
|
||||||
|
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
handler_console.setFormatter(formatter)
|
||||||
|
handler_file.setFormatter(formatter)
|
||||||
|
self.logger.addHandler(handler_console)
|
||||||
|
self.logger.addHandler(handler_file)
|
||||||
|
self.logger.setLevel(logging.WARNING)
|
||||||
|
|
||||||
def get_default_parser_settings(self):
|
def get_default_parser_settings(self):
|
||||||
"""
|
"""
|
||||||
@@ -36,62 +41,145 @@ class EcviPMSPlugin(BasePMSPlugin):
|
|||||||
"""
|
"""
|
||||||
return {
|
return {
|
||||||
"field_mapping": {
|
"field_mapping": {
|
||||||
"check_in": "checkin",
|
"check_in": "from",
|
||||||
"check_out": "checkout",
|
"check_out": "until",
|
||||||
"room_number": "room_name",
|
"room_number": "roomNumber",
|
||||||
"room_type_name": "room_type",
|
"room_type_name": "roomTypeName",
|
||||||
"status": "occupancy",
|
"status": "checkInStatus",
|
||||||
},
|
},
|
||||||
"date_format": "%Y-%m-%dT%H:%M:%S"
|
"date_format": "%Y-%m-%dT%H:%M:%S"
|
||||||
}
|
}
|
||||||
|
|
||||||
async def _fetch_data(self):
|
async def _fetch_data(self):
|
||||||
"""
|
"""
|
||||||
Получает данные из PMS API, фильтрует и сохраняет в базу данных.
|
Получает данные из PMS API и сохраняет их в базу.
|
||||||
"""
|
"""
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
current_date = now.strftime('%Y-%m-%d')
|
start_date = (now - timedelta(days=60)).strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||||
yesterday_date = (now - timedelta(days=1)).strftime('%Y-%m-%d')
|
end_date = (now + timedelta(days=60)).strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "text/plain",
|
||||||
|
"Authorization": f"Bearer {self.token}"
|
||||||
}
|
}
|
||||||
data = {
|
|
||||||
"token": self.token,
|
from_index = 0
|
||||||
}
|
count_per_request = 50
|
||||||
|
all_items = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Запрос данных из PMS API
|
while True:
|
||||||
response = await sync_to_async(requests.post)(self.api_url, headers=headers, json=data, auth=(self.username, self.password))
|
data = {
|
||||||
response.raise_for_status() # Если ошибка, выбросит исключение
|
"from": start_date,
|
||||||
data = response.json() # Преобразуем ответ в JSON
|
"until": end_date,
|
||||||
self.logger.debug(f"Получены данные с API: {data}")
|
"pagination": {
|
||||||
except requests.exceptions.RequestException as e:
|
"from": from_index,
|
||||||
self.logger.error(f"Ошибка запроса: {e}")
|
"count": count_per_request
|
||||||
return []
|
}
|
||||||
|
|
||||||
# Фильтрация данных
|
|
||||||
filtered_data = []
|
|
||||||
for item in data:
|
|
||||||
if item.get('occupancy') in ['проживание', 'под выезд', 'под заезд']:
|
|
||||||
filtered_item = {
|
|
||||||
'checkin': datetime.strptime(item.get('checkin'), '%Y-%m-%d %H:%M:%S'),
|
|
||||||
'checkout': datetime.strptime(item.get('checkout'), '%Y-%m-%d %H:%M:%S'),
|
|
||||||
'room_number': item.get('room_name'),
|
|
||||||
'room_type': item.get('room_type'),
|
|
||||||
'status': item.get('occupancy')
|
|
||||||
}
|
}
|
||||||
filtered_data.append(filtered_item)
|
|
||||||
|
|
||||||
# Логируем результат фильтрации
|
response = await sync_to_async(requests.post)(self.api_url, headers=headers, data=json.dumps(data))
|
||||||
self.logger.debug(f"Отфильтрованные данные: {filtered_data}")
|
response.raise_for_status()
|
||||||
|
response_data = response.json()
|
||||||
|
|
||||||
# Сохранение данных в базу данных
|
items = response_data.get("items", [])
|
||||||
for item in filtered_data:
|
all_items.extend(items)
|
||||||
await self._save_to_db(item)
|
|
||||||
|
|
||||||
self.logger.debug(f"Данные успешно сохранены.")
|
total_count = response_data.get("count", 0)
|
||||||
return filtered_data
|
from_index += len(items)
|
||||||
|
|
||||||
|
if from_index >= total_count:
|
||||||
|
break
|
||||||
|
|
||||||
|
self.logger.info(f"Получено записей: {len(all_items)}")
|
||||||
|
|
||||||
|
# Сохранение данных во временный файл
|
||||||
|
temp_dir = os.path.join("temp", "shelter")
|
||||||
|
os.makedirs(temp_dir, exist_ok=True)
|
||||||
|
temp_file = os.path.join(temp_dir, f"shelter_data_{datetime.now().strftime('%Y%m%d%H%M%S')}.json")
|
||||||
|
|
||||||
|
with open(temp_file, 'w') as file:
|
||||||
|
json.dump(all_items, file)
|
||||||
|
|
||||||
|
self.logger.info(f"Данные сохранены во временный файл: {temp_file}")
|
||||||
|
|
||||||
|
return await self._process_data(all_items)
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
self.logger.error(f"Ошибка API: {e}")
|
||||||
|
return {
|
||||||
|
"processed_intervals": 0,
|
||||||
|
"processed_items": 0,
|
||||||
|
"errors": [str(e)]
|
||||||
|
}
|
||||||
|
|
||||||
|
async def _process_data(self, data):
|
||||||
|
"""
|
||||||
|
Обрабатывает данные и сохраняет их в базу.
|
||||||
|
"""
|
||||||
|
processed_items = 0
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
date_formats = ["%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"] # Поддержка нескольких форматов даты
|
||||||
|
|
||||||
|
for item in data:
|
||||||
|
try:
|
||||||
|
# Парсинг даты с поддержкой нескольких форматов
|
||||||
|
checkin = self._parse_date(item['from'], date_formats)
|
||||||
|
checkout = self._parse_date(item['until'], date_formats)
|
||||||
|
|
||||||
|
reservation, created = await sync_to_async(Reservation.objects.update_or_create)(
|
||||||
|
reservation_id=item['id'],
|
||||||
|
defaults={
|
||||||
|
'room_number': item.get('roomNumber'),
|
||||||
|
'room_type': item.get('roomTypeName'),
|
||||||
|
'check_in': checkin,
|
||||||
|
'check_out': checkout,
|
||||||
|
'status': item.get('checkInStatus'),
|
||||||
|
'hotel': self.hotel,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if created:
|
||||||
|
self.logger.debug(f"Создана новая резервация: {reservation.reservation_id}")
|
||||||
|
else:
|
||||||
|
self.logger.debug(f"Обновлена существующая резервация: {reservation.reservation_id}")
|
||||||
|
|
||||||
|
processed_items += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Ошибка обработки записи: {e}")
|
||||||
|
errors.append(str(e))
|
||||||
|
|
||||||
|
return {
|
||||||
|
"processed_intervals": 1,
|
||||||
|
"processed_items": processed_items,
|
||||||
|
"errors": errors
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse_date(date_str, formats):
|
||||||
|
"""
|
||||||
|
Парсит дату, пытаясь использовать несколько форматов.
|
||||||
|
"""
|
||||||
|
for fmt in formats:
|
||||||
|
try:
|
||||||
|
return datetime.strptime(date_str, fmt)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
raise ValueError(f"Дата '{date_str}' не соответствует ожидаемым форматам: {formats}")
|
||||||
|
|
||||||
|
def validate_plugin(self):
|
||||||
|
"""
|
||||||
|
Проверка корректности реализации плагина.
|
||||||
|
"""
|
||||||
|
required_methods = ["fetch_data", "get_default_parser_settings", "_fetch_data"]
|
||||||
|
for method in required_methods:
|
||||||
|
if not hasattr(self, method):
|
||||||
|
raise ValueError(f"Плагин {type(self).__name__} не реализует метод {method}.")
|
||||||
|
self.logger.debug(f"Плагин {self.__class__.__name__} прошел валидацию.")
|
||||||
|
return True
|
||||||
|
|
||||||
async def _save_to_db(self, item):
|
async def _save_to_db(self, item):
|
||||||
"""
|
"""
|
||||||
|
|||||||
1
temp/shelter/shelter_data_20241228120750.json
Normal file
1
temp/shelter/shelter_data_20241228120750.json
Normal file
File diff suppressed because one or more lines are too long
1
temp/shelter/shelter_data_20241228120811.json
Normal file
1
temp/shelter/shelter_data_20241228120811.json
Normal file
File diff suppressed because one or more lines are too long
1
temp/shelter/shelter_data_20241228121023.json
Normal file
1
temp/shelter/shelter_data_20241228121023.json
Normal file
File diff suppressed because one or more lines are too long
1
temp/shelter/shelter_data_20241228122016.json
Normal file
1
temp/shelter/shelter_data_20241228122016.json
Normal file
File diff suppressed because one or more lines are too long
1
temp/shelter/shelter_data_20241228125616.json
Normal file
1
temp/shelter/shelter_data_20241228125616.json
Normal file
File diff suppressed because one or more lines are too long
1
temp/shelter/shelter_data_20241228125705.json
Normal file
1
temp/shelter/shelter_data_20241228125705.json
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user