bnovo plugin
scheduller
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -9,3 +9,6 @@ __pycache__
|
|||||||
node_modules
|
node_modules
|
||||||
package-lock.json
|
package-lock.json
|
||||||
package.json
|
package.json
|
||||||
|
old_bot
|
||||||
|
|
||||||
|
# Ignore files
|
||||||
BIN
1db.sqlite3
Normal file
BIN
1db.sqlite3
Normal file
Binary file not shown.
149
1db.sqlite3.sql
Normal file
149
1db.sqlite3.sql
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
BEGIN TRANSACTION;
|
||||||
|
INSERT INTO "django_migrations" ("id","app","name","applied") VALUES (1,'contenttypes','0001_initial','2024-12-09 09:30:10.251024'),
|
||||||
|
(2,'auth','0001_initial','2024-12-09 09:30:10.285571'),
|
||||||
|
(3,'admin','0001_initial','2024-12-09 09:30:10.304106'),
|
||||||
|
(4,'admin','0002_logentry_remove_auto_add','2024-12-09 09:30:10.327893'),
|
||||||
|
(5,'admin','0003_logentry_add_action_flag_choices','2024-12-09 09:30:10.347643'),
|
||||||
|
(6,'contenttypes','0002_remove_content_type_name','2024-12-09 09:30:10.391061'),
|
||||||
|
(7,'auth','0002_alter_permission_name_max_length','2024-12-09 09:30:10.411017'),
|
||||||
|
(8,'auth','0003_alter_user_email_max_length','2024-12-09 09:30:10.433375'),
|
||||||
|
(9,'auth','0004_alter_user_username_opts','2024-12-09 09:30:10.456000'),
|
||||||
|
(10,'auth','0005_alter_user_last_login_null','2024-12-09 09:30:10.487091'),
|
||||||
|
(11,'auth','0006_require_contenttypes_0002','2024-12-09 09:30:10.490205'),
|
||||||
|
(12,'auth','0007_alter_validators_add_error_messages','2024-12-09 09:30:10.505490'),
|
||||||
|
(13,'auth','0008_alter_user_username_max_length','2024-12-09 09:30:10.531170'),
|
||||||
|
(14,'auth','0009_alter_user_last_name_max_length','2024-12-09 09:30:10.554633'),
|
||||||
|
(15,'auth','0010_alter_group_name_max_length','2024-12-09 09:30:10.572891'),
|
||||||
|
(16,'auth','0011_update_proxy_permissions','2024-12-09 09:30:10.595627'),
|
||||||
|
(17,'auth','0012_alter_user_first_name_max_length','2024-12-09 09:30:10.626279'),
|
||||||
|
(18,'users','0001_initial','2024-12-09 09:30:10.697462'),
|
||||||
|
(19,'hotels','0001_initial','2024-12-09 09:30:10.728173'),
|
||||||
|
(20,'pms_integration','0001_initial','2024-12-09 09:30:10.753819'),
|
||||||
|
(21,'hotels','0002_initial','2024-12-09 09:30:10.883078'),
|
||||||
|
(22,'hotels','0003_initial','2024-12-09 09:30:11.011514'),
|
||||||
|
(23,'sessions','0001_initial','2024-12-09 09:30:11.033336');
|
||||||
|
INSERT INTO "django_admin_log" ("id","object_id","object_repr","action_flag","change_message","content_type_id","user_id","action_time") VALUES (1,'1','Shelter Golden Hills 3',1,'[{"added": {}}]',7,1,'2024-12-09 09:32:31.355706'),
|
||||||
|
(2,'2','Shelter Golden Hills 4',1,'[{"added": {}}]',7,1,'2024-12-09 09:33:10.530095'),
|
||||||
|
(3,'1','Golden Hills 3',1,'[{"added": {}}]',15,1,'2024-12-09 09:34:11.465732'),
|
||||||
|
(4,'2','Golden Hills 4',1,'[{"added": {}}]',15,1,'2024-12-09 09:34:21.783766'),
|
||||||
|
(5,'1','andrew',1,'[{"added": {}}]',18,1,'2024-12-09 09:35:20.367524'),
|
||||||
|
(6,'1','Настройки уведомлений для andrew',1,'[{"added": {}}]',19,1,'2024-12-09 09:35:40.518128'),
|
||||||
|
(7,'1','andrew - Golden Hills 3',1,'[{"added": {}}]',13,1,'2024-12-09 09:35:57.888800'),
|
||||||
|
(8,'2','andrew - Golden Hills 4',1,'[{"added": {}}]',13,1,'2024-12-09 09:36:06.799616'),
|
||||||
|
(9,'3','Как дома',1,'[{"added": {}}]',15,1,'2024-12-09 10:19:47.100883');
|
||||||
|
INSERT INTO "django_content_type" ("id","app_label","model") VALUES (1,'admin','logentry'),
|
||||||
|
(2,'auth','permission'),
|
||||||
|
(3,'auth','group'),
|
||||||
|
(4,'auth','user'),
|
||||||
|
(5,'contenttypes','contenttype'),
|
||||||
|
(6,'sessions','session'),
|
||||||
|
(7,'pms_integration','pmsconfiguration'),
|
||||||
|
(8,'pms_integration','pmsintegrationlog'),
|
||||||
|
(9,'hotels','apiconfiguration'),
|
||||||
|
(10,'hotels','fraudlog'),
|
||||||
|
(11,'hotels','guest'),
|
||||||
|
(12,'hotels','reservation'),
|
||||||
|
(13,'hotels','userhotel'),
|
||||||
|
(14,'hotels','apirequestlog'),
|
||||||
|
(15,'hotels','hotel'),
|
||||||
|
(16,'users','localuseractivitylog'),
|
||||||
|
(17,'users','useractivitylog'),
|
||||||
|
(18,'users','user'),
|
||||||
|
(19,'users','notificationsettings'),
|
||||||
|
(20,'users','userconfirmation');
|
||||||
|
INSERT INTO "auth_permission" ("id","content_type_id","codename","name") VALUES (1,1,'add_logentry','Can add log entry'),
|
||||||
|
(2,1,'change_logentry','Can change log entry'),
|
||||||
|
(3,1,'delete_logentry','Can delete log entry'),
|
||||||
|
(4,1,'view_logentry','Can view log entry'),
|
||||||
|
(5,2,'add_permission','Can add permission'),
|
||||||
|
(6,2,'change_permission','Can change permission'),
|
||||||
|
(7,2,'delete_permission','Can delete permission'),
|
||||||
|
(8,2,'view_permission','Can view permission'),
|
||||||
|
(9,3,'add_group','Can add group'),
|
||||||
|
(10,3,'change_group','Can change group'),
|
||||||
|
(11,3,'delete_group','Can delete group'),
|
||||||
|
(12,3,'view_group','Can view group'),
|
||||||
|
(13,4,'add_user','Can add user'),
|
||||||
|
(14,4,'change_user','Can change user'),
|
||||||
|
(15,4,'delete_user','Can delete user'),
|
||||||
|
(16,4,'view_user','Can view user'),
|
||||||
|
(17,5,'add_contenttype','Can add content type'),
|
||||||
|
(18,5,'change_contenttype','Can change content type'),
|
||||||
|
(19,5,'delete_contenttype','Can delete content type'),
|
||||||
|
(20,5,'view_contenttype','Can view content type'),
|
||||||
|
(21,6,'add_session','Can add session'),
|
||||||
|
(22,6,'change_session','Can change session'),
|
||||||
|
(23,6,'delete_session','Can delete session'),
|
||||||
|
(24,6,'view_session','Can view session'),
|
||||||
|
(25,7,'add_pmsconfiguration','Can add PMS система'),
|
||||||
|
(26,7,'change_pmsconfiguration','Can change PMS система'),
|
||||||
|
(27,7,'delete_pmsconfiguration','Can delete PMS система'),
|
||||||
|
(28,7,'view_pmsconfiguration','Can view PMS система'),
|
||||||
|
(29,8,'add_pmsintegrationlog','Can add Журнал интеграции PMS'),
|
||||||
|
(30,8,'change_pmsintegrationlog','Can change Журнал интеграции PMS'),
|
||||||
|
(31,8,'delete_pmsintegrationlog','Can delete Журнал интеграции PMS'),
|
||||||
|
(32,8,'view_pmsintegrationlog','Can view Журнал интеграции PMS'),
|
||||||
|
(33,9,'add_apiconfiguration','Can add Конфигурация API'),
|
||||||
|
(34,9,'change_apiconfiguration','Can change Конфигурация API'),
|
||||||
|
(35,9,'delete_apiconfiguration','Can delete Конфигурация API'),
|
||||||
|
(36,9,'view_apiconfiguration','Can view Конфигурация API'),
|
||||||
|
(37,10,'add_fraudlog','Can add Журнал мошенничества'),
|
||||||
|
(38,10,'change_fraudlog','Can change Журнал мошенничества'),
|
||||||
|
(39,10,'delete_fraudlog','Can delete Журнал мошенничества'),
|
||||||
|
(40,10,'view_fraudlog','Can view Журнал мошенничества'),
|
||||||
|
(41,11,'add_guest','Can add Гость'),
|
||||||
|
(42,11,'change_guest','Can change Гость'),
|
||||||
|
(43,11,'delete_guest','Can delete Гость'),
|
||||||
|
(44,11,'view_guest','Can view Гость'),
|
||||||
|
(45,12,'add_reservation','Can add Бронирование'),
|
||||||
|
(46,12,'change_reservation','Can change Бронирование'),
|
||||||
|
(47,12,'delete_reservation','Can delete Бронирование'),
|
||||||
|
(48,12,'view_reservation','Can view Бронирование'),
|
||||||
|
(49,13,'add_userhotel','Can add Пользователь отеля'),
|
||||||
|
(50,13,'change_userhotel','Can change Пользователь отеля'),
|
||||||
|
(51,13,'delete_userhotel','Can delete Пользователь отеля'),
|
||||||
|
(52,13,'view_userhotel','Can view Пользователь отеля'),
|
||||||
|
(53,14,'add_apirequestlog','Can add Журнал запросов API'),
|
||||||
|
(54,14,'change_apirequestlog','Can change Журнал запросов API'),
|
||||||
|
(55,14,'delete_apirequestlog','Can delete Журнал запросов API'),
|
||||||
|
(56,14,'view_apirequestlog','Can view Журнал запросов API'),
|
||||||
|
(57,15,'add_hotel','Can add Отель'),
|
||||||
|
(58,15,'change_hotel','Can change Отель'),
|
||||||
|
(59,15,'delete_hotel','Can delete Отель'),
|
||||||
|
(60,15,'view_hotel','Can view Отель'),
|
||||||
|
(61,16,'add_localuseractivitylog','Can add local user activity log'),
|
||||||
|
(62,16,'change_localuseractivitylog','Can change local user activity log'),
|
||||||
|
(63,16,'delete_localuseractivitylog','Can delete local user activity log'),
|
||||||
|
(64,16,'view_localuseractivitylog','Can view local user activity log'),
|
||||||
|
(65,17,'add_useractivitylog','Can add Журнал активности'),
|
||||||
|
(66,17,'change_useractivitylog','Can change Журнал активности'),
|
||||||
|
(67,17,'delete_useractivitylog','Can delete Журнал активности'),
|
||||||
|
(68,17,'view_useractivitylog','Can view Журнал активности'),
|
||||||
|
(69,18,'add_user','Can add Пользователь'),
|
||||||
|
(70,18,'change_user','Can change Пользователь'),
|
||||||
|
(71,18,'delete_user','Can delete Пользователь'),
|
||||||
|
(72,18,'view_user','Can view Пользователь'),
|
||||||
|
(73,19,'add_notificationsettings','Can add Способ оповещения'),
|
||||||
|
(74,19,'change_notificationsettings','Can change Способ оповещения'),
|
||||||
|
(75,19,'delete_notificationsettings','Can delete Способ оповещения'),
|
||||||
|
(76,19,'view_notificationsettings','Can view Способ оповещения'),
|
||||||
|
(77,20,'add_userconfirmation','Can add Подтверждение пользователя'),
|
||||||
|
(78,20,'change_userconfirmation','Can change Подтверждение пользователя'),
|
||||||
|
(79,20,'delete_userconfirmation','Can delete Подтверждение пользователя'),
|
||||||
|
(80,20,'view_userconfirmation','Can view Подтверждение пользователя');
|
||||||
|
INSERT INTO "auth_user" ("id","password","last_login","is_superuser","username","last_name","email","is_staff","is_active","date_joined","first_name") VALUES (1,'pbkdf2_sha256$870000$0tWRKvUavKHjKmwWjWsfYc$mfqBdzr5TB74K1f9OHCI3w/66VZE7vY53MEpgUT73/4=','2024-12-09 09:31:26.675259',1,'trevor1985','','shadow85@list.ru',1,1,'2024-12-09 09:30:44.551380','');
|
||||||
|
INSERT INTO "users_user" ("id","password","last_login","is_superuser","username","first_name","last_name","email","is_staff","is_active","date_joined","telegram_id","chat_id","role","confirmed") VALUES (1,'Andrey K. Tsoy','2024-12-09 09:34:41',1,'andrew','','','',0,1,'2024-12-09 09:34:27',556399210,556399210,'hotel_user',0);
|
||||||
|
INSERT INTO "users_notificationsettings" ("id","telegram_enabled","email_enabled","email","notification_time","user_id") VALUES (1,0,1,'a.choi@smartsoltech.kr','09:00:00',1);
|
||||||
|
INSERT INTO "hotels_hotel" ("id","name","created_at","api_id","pms_id") VALUES (1,'Golden Hills 3','2024-12-09 09:34:11.463934',NULL,1),
|
||||||
|
(2,'Golden Hills 4','2024-12-09 09:34:21.782571',NULL,2),
|
||||||
|
(3,'Как дома','2024-12-09 10:19:47.095457',NULL,NULL);
|
||||||
|
INSERT INTO "pms_integration_pmsconfiguration" ("id","name","url","token","username","password","plugin_name","created_at") VALUES (1,'Shelter Golden Hills 3','https://pms.frontdesk24.ru/sheltercloudapi/Reservations/ByFilter','A0DD4B7F-0381-4096-B46E-D22F1A571996',NULL,NULL,'Shelter PMS','2024-12-09 09:32:31.348604'),
|
||||||
|
(2,'Shelter Golden Hills 4','https://pms.frontdesk24.ru/sheltercloudapi/Reservations/ByFilter','A0DD4B7F-0381-4096-B46E-D22F1A571996',NULL,NULL,'Shelter PMS','2024-12-09 09:33:10.514492');
|
||||||
|
INSERT INTO "pms_integration_pmsintegrationlog" ("id","checked_at","status","message","hotel_id") VALUES (1,'2024-12-09 09:36:43.044421','error','Плагин для PMS Shelter PMS не найден.',2),
|
||||||
|
(2,'2024-12-09 09:38:09.595349','error','Плагин для PMS Shelter PMS не найден.',1),
|
||||||
|
(3,'2024-12-09 09:40:38.721678','error','Плагин для PMS Shelter PMS не найден.',2),
|
||||||
|
(4,'2024-12-09 10:07:44.968856','error','Плагин для PMS Shelter PMS не найден.',1);
|
||||||
|
INSERT INTO "hotels_userhotel" ("id","hotel_id","user_id") VALUES (1,1,1),
|
||||||
|
(2,2,1);
|
||||||
|
INSERT INTO "django_session" ("session_key","session_data","expire_date") VALUES ('wjcybubh02eoyth1e4pm9cgjc8c9rk1v','.eJxVjEEOwiAQRe_C2hBgpgVcuvcMZIBBqoYmpV0Z765NutDtf-_9lwi0rTVsnZcwZXEWWpx-t0jpwW0H-U7tNss0t3WZotwVedAur3Pm5-Vw_w4q9fqthxwtITBY9MlGtJ6ADZiiAdyQ7KhVyVgcGY7gEL1BVqMhH51iNl68P9F6N0w:1tKa6w:zJT5PgcESbBBG5gKuJsyfV6EOxizdevxDzI4QrGbLsc','2024-12-23 09:31:26.682056');
|
||||||
|
COMMIT;
|
||||||
@@ -5,63 +5,56 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from telegram.ext import Application
|
from telegram.ext import Application
|
||||||
from bot.utils.bot_setup import setup_bot
|
from bot.utils.bot_setup import setup_bot
|
||||||
from bot.utils.scheduler import setup_scheduler
|
from scheduler.tasks import load_tasks_to_scheduler
|
||||||
from dotenv import load_dotenv
|
|
||||||
from bot.operations.users import show_users
|
|
||||||
|
|
||||||
# Загрузка переменных окружения
|
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Запуск Telegram бота"
|
help = "Запуск Telegram бота и планировщика"
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
print("Запуск Telegram бота...")
|
# Установка Django окружения
|
||||||
|
|
||||||
# Настройка Django окружения
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "touchh.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "touchh.settings")
|
||||||
django.setup()
|
django.setup()
|
||||||
|
|
||||||
# Создание приложения Telegram
|
# Создаем новый цикл событий
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
|
||||||
|
# Настройка планировщика
|
||||||
|
scheduler = AsyncIOScheduler(event_loop=loop)
|
||||||
|
scheduler.start()
|
||||||
|
|
||||||
|
# Загрузка задач в планировщик
|
||||||
|
try:
|
||||||
|
load_tasks_to_scheduler(scheduler)
|
||||||
|
except Exception as e:
|
||||||
|
self.stderr.write(f"Ошибка при загрузке задач в планировщик: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Настройка Telegram бота
|
||||||
bot_token = os.getenv("TELEGRAM_BOT_TOKEN")
|
bot_token = os.getenv("TELEGRAM_BOT_TOKEN")
|
||||||
if not bot_token:
|
if not bot_token:
|
||||||
raise ValueError("Токен бота не найден в переменных окружения.")
|
raise ValueError("Токен бота не найден в переменных окружения.")
|
||||||
application = Application.builder().token(bot_token).build()
|
application = Application.builder().token(bot_token).build()
|
||||||
|
|
||||||
# Настройка бота и обработчиков
|
|
||||||
setup_bot(application)
|
setup_bot(application)
|
||||||
|
|
||||||
|
# Основная асинхронная функция
|
||||||
async def main():
|
async def main():
|
||||||
print("Настройка планировщика...")
|
await application.initialize()
|
||||||
scheduler = setup_scheduler()
|
await application.start()
|
||||||
scheduler.start()
|
await application.updater.start_polling()
|
||||||
|
self.stdout.write(self.style.SUCCESS("Telegram бот и планировщик успешно запущены."))
|
||||||
try:
|
try:
|
||||||
print("Инициализация Telegram бота...")
|
|
||||||
await application.initialize() # Инициализация приложения
|
|
||||||
print("Бот запущен. Ожидание сообщений...")
|
|
||||||
await application.start() # Запуск приложения
|
|
||||||
await application.updater.start_polling() # Запуск обработки сообщений
|
|
||||||
|
|
||||||
# Бесконечный цикл для удержания приложения активным
|
|
||||||
while True:
|
while True:
|
||||||
await asyncio.sleep(3600) # Ожидание 1 час
|
await asyncio.sleep(3600)
|
||||||
except Exception as e:
|
except asyncio.CancelledError:
|
||||||
print(f"Ошибка во время работы бота: {e}")
|
await application.stop()
|
||||||
finally:
|
scheduler.shutdown()
|
||||||
print("Остановка Telegram бота...")
|
|
||||||
await application.stop() # Завершаем приложение перед shutdown
|
|
||||||
print("Остановка планировщика...")
|
|
||||||
scheduler.shutdown(wait=False)
|
|
||||||
print("Планировщик остановлен.")
|
|
||||||
|
|
||||||
|
# Запуск асинхронной программы
|
||||||
try:
|
try:
|
||||||
asyncio.run(main())
|
loop.run_until_complete(main())
|
||||||
except RuntimeError as e:
|
except KeyboardInterrupt:
|
||||||
if str(e) == "This event loop is already running":
|
self.stdout.write(self.style.ERROR("Завершение работы Telegram бота и планировщика"))
|
||||||
print("Цикл событий уже запущен. Используем другой подход для запуска.")
|
finally:
|
||||||
loop = asyncio.get_event_loop()
|
loop.close()
|
||||||
loop.run_until_complete(main())
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|||||||
@@ -138,18 +138,18 @@ async def check_pms(update, context):
|
|||||||
|
|
||||||
# Создаем экземпляр PMSIntegrationManager
|
# Создаем экземпляр PMSIntegrationManager
|
||||||
pms_manager = PMSIntegrationManager(hotel_id=hotel_id)
|
pms_manager = PMSIntegrationManager(hotel_id=hotel_id)
|
||||||
await sync_to_async(pms_manager.load_hotel)()
|
await pms_manager.load_hotel()
|
||||||
await sync_to_async(pms_manager.load_plugin)()
|
await sync_to_async(pms_manager.load_plugin)()
|
||||||
|
|
||||||
# Проверяем, какой способ интеграции использовать
|
# Проверяем, какой способ интеграции использовать
|
||||||
if hasattr(pms_manager.plugin, 'fetch_data'):
|
if hasattr(pms_manager.plugin, 'fetch_data') and callable(pms_manager.plugin.fetch_data):
|
||||||
# Плагин поддерживает метод fetch_data
|
# Плагин поддерживает метод fetch_data
|
||||||
data = await sync_to_async(pms_manager.plugin.fetch_data)()
|
data = await pms_manager.plugin.fetch_data()
|
||||||
elif pms_config.api_url and pms_config.token:
|
elif pms_config.api_url and pms_config.token:
|
||||||
# Используем прямой запрос к API
|
# Используем прямой запрос к API
|
||||||
from pms_integration.api_client import APIClient
|
from pms_integration.api_client import APIClient
|
||||||
api_client = APIClient(base_url=pms_config.api_url, access_token=pms_config.token)
|
api_client = APIClient(base_url=pms_config.api_url, access_token=pms_config.token)
|
||||||
data = await sync_to_async(api_client.fetch_reservations)()
|
data = api_client.fetch_reservations()
|
||||||
else:
|
else:
|
||||||
# Если подходящий способ не найден
|
# Если подходящий способ не найден
|
||||||
await query.edit_message_text("Подходящий способ интеграции с PMS не найден.")
|
await query.edit_message_text("Подходящий способ интеграции с PMS не найден.")
|
||||||
@@ -163,7 +163,8 @@ async def check_pms(update, context):
|
|||||||
await query.edit_message_text(f"Интеграция PMS {pms_config.name} завершена успешно.")
|
await query.edit_message_text(f"Интеграция PMS {pms_config.name} завершена успешно.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Обрабатываем и логируем ошибки
|
# Обрабатываем и логируем ошибки
|
||||||
await query.edit_message_text(f"Ошибка: {str(e)}")
|
await query.edit_message_text(f"❌ Ошибка: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
async def setup_rooms(update: Update, context):
|
async def setup_rooms(update: Update, context):
|
||||||
"""Настроить номера отеля."""
|
"""Настроить номера отеля."""
|
||||||
|
|||||||
BIN
db2.sqlite3
Normal file
BIN
db2.sqlite3
Normal file
Binary file not shown.
186
db2.sqlite3.sql
Normal file
186
db2.sqlite3.sql
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
BEGIN TRANSACTION;
|
||||||
|
INSERT INTO "django_migrations" ("id","app","name","applied") VALUES (1,'contenttypes','0001_initial','2024-12-10 01:46:55.727280'),
|
||||||
|
(2,'auth','0001_initial','2024-12-10 01:46:55.748342'),
|
||||||
|
(3,'admin','0001_initial','2024-12-10 01:46:55.761857'),
|
||||||
|
(4,'admin','0002_logentry_remove_auto_add','2024-12-10 01:46:55.771717'),
|
||||||
|
(5,'admin','0003_logentry_add_action_flag_choices','2024-12-10 01:46:55.783546'),
|
||||||
|
(6,'contenttypes','0002_remove_content_type_name','2024-12-10 01:46:55.798657'),
|
||||||
|
(7,'auth','0002_alter_permission_name_max_length','2024-12-10 01:46:55.812626'),
|
||||||
|
(8,'auth','0003_alter_user_email_max_length','2024-12-10 01:46:55.826016'),
|
||||||
|
(9,'auth','0004_alter_user_username_opts','2024-12-10 01:46:55.836404'),
|
||||||
|
(10,'auth','0005_alter_user_last_login_null','2024-12-10 01:46:55.846555'),
|
||||||
|
(11,'auth','0006_require_contenttypes_0002','2024-12-10 01:46:55.850370'),
|
||||||
|
(12,'auth','0007_alter_validators_add_error_messages','2024-12-10 01:46:55.858610'),
|
||||||
|
(13,'auth','0008_alter_user_username_max_length','2024-12-10 01:46:55.868657'),
|
||||||
|
(14,'auth','0009_alter_user_last_name_max_length','2024-12-10 01:46:55.880462'),
|
||||||
|
(15,'auth','0010_alter_group_name_max_length','2024-12-10 01:46:55.893095'),
|
||||||
|
(16,'auth','0011_update_proxy_permissions','2024-12-10 01:46:55.900517'),
|
||||||
|
(17,'auth','0012_alter_user_first_name_max_length','2024-12-10 01:46:55.911606'),
|
||||||
|
(18,'users','0001_initial','2024-12-10 01:46:55.941858'),
|
||||||
|
(19,'hotels','0001_initial','2024-12-10 01:46:55.957904'),
|
||||||
|
(20,'pms_integration','0001_initial','2024-12-10 01:46:55.968957'),
|
||||||
|
(21,'hotels','0002_initial','2024-12-10 01:46:56.002327'),
|
||||||
|
(22,'hotels','0003_initial','2024-12-10 01:46:56.025083'),
|
||||||
|
(23,'sessions','0001_initial','2024-12-10 01:46:56.034581'),
|
||||||
|
(24,'pms_integration','0002_alter_pmsconfiguration_plugin_name','2024-12-10 02:52:26.722876'),
|
||||||
|
(25,'pms_integration','0003_alter_pmsconfiguration_plugin_name','2024-12-10 02:58:35.733611'),
|
||||||
|
(26,'pms_integration','0004_alter_pmsconfiguration_plugin_name','2024-12-10 03:00:06.725614'),
|
||||||
|
(27,'pms_integration','0005_pmsconfiguration_private_key_and_more','2024-12-10 03:04:00.440483'),
|
||||||
|
(28,'scheduler','0001_initial','2024-12-10 06:42:05.633067'),
|
||||||
|
(29,'scheduler','0002_alter_scheduledtask_options_and_more','2024-12-10 06:49:38.460869'),
|
||||||
|
(30,'scheduler','0003_alter_scheduledtask_options_and_more','2024-12-10 07:13:54.438968');
|
||||||
|
INSERT INTO "django_admin_log" ("id","object_id","object_repr","action_flag","change_message","content_type_id","user_id","action_time") VALUES (1,'1','Shelter Golden Hills 3',1,'[{"added": {}}]',7,1,'2024-12-09 09:32:31.355706'),
|
||||||
|
(2,'2','Shelter Golden Hills 4',1,'[{"added": {}}]',7,1,'2024-12-09 09:33:10.530095'),
|
||||||
|
(3,'1','Golden Hills 3',1,'[{"added": {}}]',15,1,'2024-12-09 09:34:11.465732'),
|
||||||
|
(4,'2','Golden Hills 4',1,'[{"added": {}}]',15,1,'2024-12-09 09:34:21.783766'),
|
||||||
|
(5,'1','andrew',1,'[{"added": {}}]',18,1,'2024-12-09 09:35:20.367524'),
|
||||||
|
(6,'1','Настройки уведомлений для andrew',1,'[{"added": {}}]',19,1,'2024-12-09 09:35:40.518128'),
|
||||||
|
(7,'1','andrew - Golden Hills 3',1,'[{"added": {}}]',13,1,'2024-12-09 09:35:57.888800'),
|
||||||
|
(8,'2','andrew - Golden Hills 4',1,'[{"added": {}}]',13,1,'2024-12-09 09:36:06.799616'),
|
||||||
|
(9,'3','Как дома',1,'[{"added": {}}]',15,1,'2024-12-09 10:19:47.100883'),
|
||||||
|
(10,'2','Shelter Golden Hills 4',2,'[{"changed": {"fields": ["\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u043b\u0430\u0433\u0438\u043d\u0430"]}}]',7,1,'2024-12-10 02:47:47.763286'),
|
||||||
|
(11,'1','Shelter Golden Hills 3',2,'[{"changed": {"fields": ["\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u043b\u0430\u0433\u0438\u043d\u0430"]}}]',7,1,'2024-12-10 02:47:51.978891'),
|
||||||
|
(12,'2','Shelter Golden Hills 4',2,'[{"changed": {"fields": ["plugin_name"]}}]',7,1,'2024-12-10 03:00:54.761679'),
|
||||||
|
(13,'2','Shelter Golden Hills 4',2,'[]',7,1,'2024-12-10 03:01:06.448068'),
|
||||||
|
(14,'3','Как дома / RealtyCalendar',1,'[{"added": {}}]',7,1,'2024-12-10 03:07:04.219584'),
|
||||||
|
(15,'3','andrew - Как дома',1,'[{"added": {}}]',13,1,'2024-12-10 03:07:13.403477'),
|
||||||
|
(16,'3','Как дома',2,'[{"changed": {"fields": ["PMS \u0441\u0438\u0441\u0442\u0435\u043c\u0430"]}}]',15,1,'2024-12-10 03:16:16.331411'),
|
||||||
|
(17,'4','Bnovo',1,'[{"added": {}}]',7,1,'2024-12-10 04:18:40.567772'),
|
||||||
|
(18,'4','Bnovo',2,'[{"changed": {"fields": ["\u041b\u043e\u0433\u0438\u043d", "\u041f\u0430\u0440\u043e\u043b\u044c"]}}]',7,1,'2024-12-10 04:22:39.182778'),
|
||||||
|
(19,'4','Test',1,'[{"added": {}}]',15,1,'2024-12-10 04:25:37.535780'),
|
||||||
|
(20,'4','andrew - Test',1,'[{"added": {}}]',13,1,'2024-12-10 04:25:43.910574'),
|
||||||
|
(21,'1','bot running',1,'[{"added": {}}]',21,1,'2024-12-10 07:04:41.982258'),
|
||||||
|
(22,'2','bot.check_pms',1,'[{"added": {}}]',21,1,'2024-12-10 07:07:20.515061'),
|
||||||
|
(23,'2','bot.check_pms',2,'[{"changed": {"fields": ["\u0424\u0443\u043d\u043a\u0446\u0438\u044f", "\u0410\u043a\u0442\u0438\u0432\u043d\u043e", "\u0414\u043d\u0438 \u043d\u0435\u0434\u0435\u043b\u0438"]}}]',21,1,'2024-12-10 08:14:49.799413'),
|
||||||
|
(24,'1','bot running',2,'[{"changed": {"fields": ["\u0424\u0443\u043d\u043a\u0446\u0438\u044f", "\u0414\u043d\u0438 \u043d\u0435\u0434\u0435\u043b\u0438", "\u0414\u043d\u0438 \u043d\u0435\u0434\u0435\u043b\u0438"]}}]',21,1,'2024-12-10 08:15:52.093648'),
|
||||||
|
(25,'1','andrew',2,'[{"changed": {"fields": ["\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d"]}}]',18,1,'2024-12-10 08:35:12.673349');
|
||||||
|
INSERT INTO "django_content_type" ("id","app_label","model") VALUES (1,'admin','logentry'),
|
||||||
|
(2,'auth','permission'),
|
||||||
|
(3,'auth','group'),
|
||||||
|
(4,'auth','user'),
|
||||||
|
(5,'contenttypes','contenttype'),
|
||||||
|
(6,'sessions','session'),
|
||||||
|
(7,'pms_integration','pmsconfiguration'),
|
||||||
|
(8,'pms_integration','pmsintegrationlog'),
|
||||||
|
(9,'hotels','apiconfiguration'),
|
||||||
|
(10,'hotels','fraudlog'),
|
||||||
|
(11,'hotels','guest'),
|
||||||
|
(12,'hotels','reservation'),
|
||||||
|
(13,'hotels','userhotel'),
|
||||||
|
(14,'hotels','apirequestlog'),
|
||||||
|
(15,'hotels','hotel'),
|
||||||
|
(16,'users','localuseractivitylog'),
|
||||||
|
(17,'users','useractivitylog'),
|
||||||
|
(18,'users','user'),
|
||||||
|
(19,'users','notificationsettings'),
|
||||||
|
(20,'users','userconfirmation'),
|
||||||
|
(21,'scheduler','scheduledtask');
|
||||||
|
INSERT INTO "auth_permission" ("id","content_type_id","codename","name") VALUES (1,1,'add_logentry','Can add log entry'),
|
||||||
|
(2,1,'change_logentry','Can change log entry'),
|
||||||
|
(3,1,'delete_logentry','Can delete log entry'),
|
||||||
|
(4,1,'view_logentry','Can view log entry'),
|
||||||
|
(5,2,'add_permission','Can add permission'),
|
||||||
|
(6,2,'change_permission','Can change permission'),
|
||||||
|
(7,2,'delete_permission','Can delete permission'),
|
||||||
|
(8,2,'view_permission','Can view permission'),
|
||||||
|
(9,3,'add_group','Can add group'),
|
||||||
|
(10,3,'change_group','Can change group'),
|
||||||
|
(11,3,'delete_group','Can delete group'),
|
||||||
|
(12,3,'view_group','Can view group'),
|
||||||
|
(13,4,'add_user','Can add user'),
|
||||||
|
(14,4,'change_user','Can change user'),
|
||||||
|
(15,4,'delete_user','Can delete user'),
|
||||||
|
(16,4,'view_user','Can view user'),
|
||||||
|
(17,5,'add_contenttype','Can add content type'),
|
||||||
|
(18,5,'change_contenttype','Can change content type'),
|
||||||
|
(19,5,'delete_contenttype','Can delete content type'),
|
||||||
|
(20,5,'view_contenttype','Can view content type'),
|
||||||
|
(21,6,'add_session','Can add session'),
|
||||||
|
(22,6,'change_session','Can change session'),
|
||||||
|
(23,6,'delete_session','Can delete session'),
|
||||||
|
(24,6,'view_session','Can view session'),
|
||||||
|
(25,7,'add_pmsconfiguration','Can add PMS система'),
|
||||||
|
(26,7,'change_pmsconfiguration','Can change PMS система'),
|
||||||
|
(27,7,'delete_pmsconfiguration','Can delete PMS система'),
|
||||||
|
(28,7,'view_pmsconfiguration','Can view PMS система'),
|
||||||
|
(29,8,'add_pmsintegrationlog','Can add Журнал интеграции PMS'),
|
||||||
|
(30,8,'change_pmsintegrationlog','Can change Журнал интеграции PMS'),
|
||||||
|
(31,8,'delete_pmsintegrationlog','Can delete Журнал интеграции PMS'),
|
||||||
|
(32,8,'view_pmsintegrationlog','Can view Журнал интеграции PMS'),
|
||||||
|
(33,9,'add_apiconfiguration','Can add Конфигурация API'),
|
||||||
|
(34,9,'change_apiconfiguration','Can change Конфигурация API'),
|
||||||
|
(35,9,'delete_apiconfiguration','Can delete Конфигурация API'),
|
||||||
|
(36,9,'view_apiconfiguration','Can view Конфигурация API'),
|
||||||
|
(37,10,'add_fraudlog','Can add Журнал мошенничества'),
|
||||||
|
(38,10,'change_fraudlog','Can change Журнал мошенничества'),
|
||||||
|
(39,10,'delete_fraudlog','Can delete Журнал мошенничества'),
|
||||||
|
(40,10,'view_fraudlog','Can view Журнал мошенничества'),
|
||||||
|
(41,11,'add_guest','Can add Гость'),
|
||||||
|
(42,11,'change_guest','Can change Гость'),
|
||||||
|
(43,11,'delete_guest','Can delete Гость'),
|
||||||
|
(44,11,'view_guest','Can view Гость'),
|
||||||
|
(45,12,'add_reservation','Can add Бронирование'),
|
||||||
|
(46,12,'change_reservation','Can change Бронирование'),
|
||||||
|
(47,12,'delete_reservation','Can delete Бронирование'),
|
||||||
|
(48,12,'view_reservation','Can view Бронирование'),
|
||||||
|
(49,13,'add_userhotel','Can add Пользователь отеля'),
|
||||||
|
(50,13,'change_userhotel','Can change Пользователь отеля'),
|
||||||
|
(51,13,'delete_userhotel','Can delete Пользователь отеля'),
|
||||||
|
(52,13,'view_userhotel','Can view Пользователь отеля'),
|
||||||
|
(53,14,'add_apirequestlog','Can add Журнал запросов API'),
|
||||||
|
(54,14,'change_apirequestlog','Can change Журнал запросов API'),
|
||||||
|
(55,14,'delete_apirequestlog','Can delete Журнал запросов API'),
|
||||||
|
(56,14,'view_apirequestlog','Can view Журнал запросов API'),
|
||||||
|
(57,15,'add_hotel','Can add Отель'),
|
||||||
|
(58,15,'change_hotel','Can change Отель'),
|
||||||
|
(59,15,'delete_hotel','Can delete Отель'),
|
||||||
|
(60,15,'view_hotel','Can view Отель'),
|
||||||
|
(61,16,'add_localuseractivitylog','Can add local user activity log'),
|
||||||
|
(62,16,'change_localuseractivitylog','Can change local user activity log'),
|
||||||
|
(63,16,'delete_localuseractivitylog','Can delete local user activity log'),
|
||||||
|
(64,16,'view_localuseractivitylog','Can view local user activity log'),
|
||||||
|
(65,17,'add_useractivitylog','Can add Журнал активности'),
|
||||||
|
(66,17,'change_useractivitylog','Can change Журнал активности'),
|
||||||
|
(67,17,'delete_useractivitylog','Can delete Журнал активности'),
|
||||||
|
(68,17,'view_useractivitylog','Can view Журнал активности'),
|
||||||
|
(69,18,'add_user','Can add Пользователь'),
|
||||||
|
(70,18,'change_user','Can change Пользователь'),
|
||||||
|
(71,18,'delete_user','Can delete Пользователь'),
|
||||||
|
(72,18,'view_user','Can view Пользователь'),
|
||||||
|
(73,19,'add_notificationsettings','Can add Способ оповещения'),
|
||||||
|
(74,19,'change_notificationsettings','Can change Способ оповещения'),
|
||||||
|
(75,19,'delete_notificationsettings','Can delete Способ оповещения'),
|
||||||
|
(76,19,'view_notificationsettings','Can view Способ оповещения'),
|
||||||
|
(77,20,'add_userconfirmation','Can add Подтверждение пользователя'),
|
||||||
|
(78,20,'change_userconfirmation','Can change Подтверждение пользователя'),
|
||||||
|
(79,20,'delete_userconfirmation','Can delete Подтверждение пользователя'),
|
||||||
|
(80,20,'view_userconfirmation','Can view Подтверждение пользователя'),
|
||||||
|
(81,21,'add_scheduledtask','Can add Задача'),
|
||||||
|
(82,21,'change_scheduledtask','Can change Задача'),
|
||||||
|
(83,21,'delete_scheduledtask','Can delete Задача'),
|
||||||
|
(84,21,'view_scheduledtask','Can view Задача');
|
||||||
|
INSERT INTO "auth_user" ("id","password","last_login","is_superuser","username","last_name","email","is_staff","is_active","date_joined","first_name") VALUES (1,'pbkdf2_sha256$870000$0tWRKvUavKHjKmwWjWsfYc$mfqBdzr5TB74K1f9OHCI3w/66VZE7vY53MEpgUT73/4=','2024-12-10 06:59:08.529292',1,'trevor1985','','shadow85@list.ru',1,1,'2024-12-09 09:30:44.551380','');
|
||||||
|
INSERT INTO "users_user" ("id","password","last_login","is_superuser","username","first_name","last_name","email","is_staff","is_active","date_joined","telegram_id","chat_id","role","confirmed") VALUES (1,'Andrey K. Tsoy','2024-12-09 09:34:41',1,'andrew','','','',0,1,'2024-12-09 09:34:27',556399210,556399210,'hotel_user',1);
|
||||||
|
INSERT INTO "users_notificationsettings" ("id","telegram_enabled","email_enabled","email","notification_time","user_id") VALUES (1,0,1,'a.choi@smartsoltech.kr','09:00:00',1);
|
||||||
|
INSERT INTO "hotels_hotel" ("id","name","created_at","api_id","pms_id") VALUES (1,'Golden Hills 3','2024-12-09 09:34:11.463934',NULL,1),
|
||||||
|
(2,'Golden Hills 4','2024-12-09 09:34:21.782571',NULL,2),
|
||||||
|
(3,'Как дома','2024-12-09 10:19:47.095457',NULL,3),
|
||||||
|
(4,'Test','2024-12-10 04:25:37.534927',NULL,4);
|
||||||
|
INSERT INTO "pms_integration_pmsintegrationlog" ("id","checked_at","status","message","hotel_id") VALUES (1,'2024-12-09 09:36:43.044421','error','Плагин для PMS Shelter PMS не найден.',2),
|
||||||
|
(2,'2024-12-09 09:38:09.595349','error','Плагин для PMS Shelter PMS не найден.',1),
|
||||||
|
(3,'2024-12-09 09:40:38.721678','error','Плагин для PMS Shelter PMS не найден.',2),
|
||||||
|
(4,'2024-12-09 10:07:44.968856','error','Плагин для PMS Shelter PMS не найден.',1);
|
||||||
|
INSERT INTO "hotels_userhotel" ("id","hotel_id","user_id") VALUES (1,1,1),
|
||||||
|
(2,2,1),
|
||||||
|
(3,3,1),
|
||||||
|
(4,4,1);
|
||||||
|
INSERT INTO "django_session" ("session_key","session_data","expire_date") VALUES ('wjcybubh02eoyth1e4pm9cgjc8c9rk1v','.eJxVjEEOwiAQRe_C2hBgpgVcuvcMZIBBqoYmpV0Z765NutDtf-_9lwi0rTVsnZcwZXEWWpx-t0jpwW0H-U7tNss0t3WZotwVedAur3Pm5-Vw_w4q9fqthxwtITBY9MlGtJ6ADZiiAdyQ7KhVyVgcGY7gEL1BVqMhH51iNl68P9F6N0w:1tKa6w:zJT5PgcESbBBG5gKuJsyfV6EOxizdevxDzI4QrGbLsc','2024-12-23 09:31:26.682056'),
|
||||||
|
('zhfmhutrq2o277smded60dpbxurhyqrv','.eJxVjEEOwiAQRe_C2hBgpgVcuvcMZIBBqoYmpV0Z765NutDtf-_9lwi0rTVsnZcwZXEWWpx-t0jpwW0H-U7tNss0t3WZotwVedAur3Pm5-Vw_w4q9fqthxwtITBY9MlGtJ6ADZiiAdyQ7KhVyVgcGY7gEL1BVqMhH51iNl68P9F6N0w:1tKpiq:sIJzaN8dmZGRQlRUklQ9SNyhbyMuSLYMj-62RXTX5no','2024-12-24 02:11:36.624898'),
|
||||||
|
('mz2w18vdzao572ss2ikr5tgj0w2rrbff','.eJxVjEEOwiAQRe_C2hBgpgVcuvcMZIBBqoYmpV0Z765NutDtf-_9lwi0rTVsnZcwZXEWWpx-t0jpwW0H-U7tNss0t3WZotwVedAur3Pm5-Vw_w4q9fqthxwtITBY9MlGtJ6ADZiiAdyQ7KhVyVgcGY7gEL1BVqMhH51iNl68P9F6N0w:1tKuD6:CESE5WIEOm7OzSRbIws0uyat8L3VjPyjOBTBeK_YzFY','2024-12-24 06:59:08.533787');
|
||||||
|
INSERT INTO "pms_integration_pmsconfiguration" ("id","name","url","token","username","password","created_at","plugin_name","private_key","public_key") VALUES (1,'Shelter Golden Hills 3','https://pms.frontdesk24.ru/sheltercloudapi/Reservations/ByFilter','A0DD4B7F-0381-4096-B46E-D22F1A571996',NULL,NULL,'2024-12-09 09:32:31.348604','Shelter',NULL,NULL),
|
||||||
|
(2,'Shelter Golden Hills 4','https://pms.frontdesk24.ru/sheltercloudapi/Reservations/ByFilter','A0DD4B7F-0381-4096-B46E-D22F1A571996',NULL,NULL,'2024-12-09 09:33:10.514492','shelter',NULL,NULL),
|
||||||
|
(3,'Как дома / RealtyCalendar','https://realtycalendar.ru/api/v1/bookings/',NULL,NULL,NULL,'2024-12-10 03:07:04.218499','realtycalendar','a3669a349b9911cf774ec30ba9523582','b95e293cf07c84dfce44ec41bdced96a'),
|
||||||
|
(4,'Bnovo','https://online.bnovo.ru',NULL,'16798','a46da27476f02d1f','2024-12-10 04:18:40.567212','bnovo',NULL,NULL);
|
||||||
|
INSERT INTO "scheduler_scheduledtask" ("id","last_run","days","hours","minutes","months","active","task_name","function_path","weekdays") VALUES (1,NULL,'*','*','*','*',1,'bot running','bot.utils.bot_setup.setup_bot','[6]'),
|
||||||
|
(2,NULL,'*','*','*','*',1,'bot.check_pms','manage.main','2');
|
||||||
|
COMMIT;
|
||||||
@@ -4,9 +4,7 @@ from .models import (
|
|||||||
Hotel,
|
Hotel,
|
||||||
UserHotel,
|
UserHotel,
|
||||||
APIConfiguration,
|
APIConfiguration,
|
||||||
APIRequestLog,
|
|
||||||
Reservation,
|
Reservation,
|
||||||
Guest,
|
|
||||||
FraudLog
|
FraudLog
|
||||||
)
|
)
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
@@ -69,22 +67,6 @@ class UserHotelAdmin(admin.ModelAdmin):
|
|||||||
# ordering = ('-hotel',)
|
# ordering = ('-hotel',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(APIConfiguration)
|
|
||||||
class ApiConfigurationAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('name', 'url', 'token', 'username', 'password', 'last_updated')
|
|
||||||
search_fields = ('name', 'url', 'token', 'username')
|
|
||||||
list_filter = ('last_updated',)
|
|
||||||
ordering = ('-last_updated',)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(APIRequestLog)
|
|
||||||
class ApiRequestLogAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('api', 'request_time', 'response_status', 'response_data')
|
|
||||||
search_fields = ('api__name', 'request_time', 'response_status')
|
|
||||||
list_filter = ('api', 'response_status', 'request_time')
|
|
||||||
ordering = ('-request_time',)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Reservation)
|
@admin.register(Reservation)
|
||||||
class ReservationAdmin(admin.ModelAdmin):
|
class ReservationAdmin(admin.ModelAdmin):
|
||||||
list_display = ('reservation_id', 'hotel', 'room_number', 'room_type', 'check_in', 'check_out', 'status', 'price', 'discount')
|
list_display = ('reservation_id', 'hotel', 'room_number', 'room_type', 'check_in', 'check_out', 'status', 'price', 'discount')
|
||||||
@@ -93,9 +75,3 @@ class ReservationAdmin(admin.ModelAdmin):
|
|||||||
ordering = ('-check_in',)
|
ordering = ('-check_in',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Guest)
|
|
||||||
class GuestAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('reservation', 'name', 'birthdate', 'phone', 'email')
|
|
||||||
search_fields = ('reservation__reservation_id', 'name', 'phone', 'email')
|
|
||||||
list_filter = ('reservation',)
|
|
||||||
ordering = ('-reservation',)
|
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
import json
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
# Load the JSON file
|
|
||||||
file_path = '../../modules/analyzed_9.json'
|
|
||||||
with open(file_path, 'r', encoding='utf-8') as file:
|
|
||||||
data = json.load(file)
|
|
||||||
|
|
||||||
# Process the data into a structured format
|
|
||||||
reservations = []
|
|
||||||
for booking in data:
|
|
||||||
for guest in booking.get("guests", []):
|
|
||||||
reservations.append({
|
|
||||||
"Reservation ID": booking.get("id"),
|
|
||||||
"Hotel Name": booking.get("hotelName"),
|
|
||||||
"Room Number": booking.get("roomNumber"),
|
|
||||||
"Room Type": booking.get("roomTypeName"),
|
|
||||||
"Check-in": booking.get("from"),
|
|
||||||
"Check-out": booking.get("until"),
|
|
||||||
"Status": booking.get("checkInStatus"),
|
|
||||||
"Price": booking.get("reservationPrice"),
|
|
||||||
"Discount": booking.get("discount"),
|
|
||||||
"Guest Name": f"{guest.get('lastName', '')} {guest.get('firstName', '')} {guest.get('middleName', '')}".strip(),
|
|
||||||
"Guest Birthdate": guest.get("birthDate"),
|
|
||||||
"Guest Phone": guest.get("phone"),
|
|
||||||
"Guest Email": guest.get("email"),
|
|
||||||
})
|
|
||||||
|
|
||||||
# Convert to DataFrame for better visualization
|
|
||||||
df_reservations = pd.DataFrame(reservations)
|
|
||||||
|
|
||||||
# Display the structured data
|
|
||||||
import ace_tools as tools; tools.display_dataframe_to_user(name="Structured Reservations Data", dataframe=df_reservations)
|
|
||||||
11
manage.py
11
manage.py
@@ -2,6 +2,17 @@
|
|||||||
"""Django's command-line utility for administrative tasks."""
|
"""Django's command-line utility for administrative tasks."""
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Настройка логирования
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO, # Уровень логирования (можно DEBUG для полной информации)
|
||||||
|
format="%(asctime)s - %(levelname)s - %(message)s",
|
||||||
|
handlers=[
|
||||||
|
logging.FileHandler("bot.log"), # Логи будут записываться в файл bot.log
|
||||||
|
logging.StreamHandler() # Логи также будут отображаться в консоли
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -8,53 +8,34 @@ from django.shortcuts import render
|
|||||||
from django import forms
|
from django import forms
|
||||||
from pms_integration.models import PMSConfiguration, PMSIntegrationLog
|
from pms_integration.models import PMSConfiguration, PMSIntegrationLog
|
||||||
|
|
||||||
|
|
||||||
class PMSConfigurationForm(forms.ModelForm):
|
class PMSConfigurationForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PMSConfiguration
|
model = PMSConfiguration
|
||||||
fields = "__all__"
|
fields = '__all__'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
# Загружаем доступные плагины
|
||||||
plugins = PluginLoader.load_plugins()
|
plugins = PluginLoader.load_plugins()
|
||||||
self.fields['plugin_name'].choices = [(plugin, plugin) for plugin in plugins.keys()]
|
plugin_choices = [(plugin_name, plugin_name) for plugin_name in plugins.keys()]
|
||||||
|
self.fields['plugin_name'] = forms.ChoiceField(choices=plugin_choices, required=False)
|
||||||
|
|
||||||
@admin.register(PMSConfiguration)
|
@admin.register(PMSConfiguration)
|
||||||
class PMSConfigurationAdmin(admin.ModelAdmin):
|
class PMSConfigurationAdmin(admin.ModelAdmin):
|
||||||
form = PMSConfigurationForm
|
form = PMSConfigurationForm
|
||||||
list_display = ('name', 'plugin_name', 'created_at', 'check_plugins_button')
|
list_display = ('name', 'plugin_name', 'created_at')
|
||||||
search_fields = ('name', 'description')
|
search_fields = ('name', 'plugin_name')
|
||||||
list_filter = ('created_at',)
|
|
||||||
ordering = ('-created_at',)
|
ordering = ('-created_at',)
|
||||||
|
|
||||||
def get_urls(self):
|
def save_model(self, request, obj, form, change):
|
||||||
"""Добавляем URL для проверки плагинов."""
|
# Проверка на наличие плагина
|
||||||
urls = super().get_urls()
|
|
||||||
custom_urls = [
|
|
||||||
path("check-plugins/", self.check_plugins, name="check-plugins"),
|
|
||||||
]
|
|
||||||
return custom_urls + urls
|
|
||||||
|
|
||||||
def check_plugins(self, request):
|
|
||||||
"""Проверка и отображение плагинов."""
|
|
||||||
plugins = PluginLoader.load_plugins()
|
plugins = PluginLoader.load_plugins()
|
||||||
plugin_details = [
|
if obj.plugin_name and obj.plugin_name not in plugins.keys():
|
||||||
{"name": plugin_name, "doc": plugins[plugin_name].__doc__ or "Нет документации"}
|
raise ValueError(f"Выберите корректный плагин. '{obj.plugin_name}' нет среди допустимых значений.")
|
||||||
for plugin_name in plugins
|
super().save_model(request, obj, form, change)
|
||||||
]
|
|
||||||
context = {
|
|
||||||
"title": "Проверка плагинов",
|
|
||||||
"plugin_details": plugin_details,
|
|
||||||
}
|
|
||||||
return render(request, "admin/check_plugins.html", context)
|
|
||||||
|
|
||||||
def check_plugins_button(self, obj):
|
|
||||||
"""Добавляем кнопку для проверки плагинов."""
|
|
||||||
return format_html(
|
|
||||||
'<a class="button" href="{}">Проверить плагины</a>',
|
|
||||||
"/admin/pms_integration/pmsconfiguration/check-plugins/",
|
|
||||||
)
|
|
||||||
check_plugins_button.short_description = "Проверить плагины"
|
|
||||||
|
|
||||||
@admin.register(PMSIntegrationLog)
|
@admin.register(PMSIntegrationLog)
|
||||||
class PMSIntegrationLogAdmin(admin.ModelAdmin):
|
class PMSIntegrationLogAdmin(admin.ModelAdmin):
|
||||||
list_display = ('hotel', 'checked_at', 'status', 'message')
|
list_display = ('hotel', 'checked_at', 'status', 'message')
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
from django import forms
|
|
||||||
from .models import PMSConfiguration
|
|
||||||
from .manager import PluginLoader
|
|
||||||
|
|
||||||
class PMSConfigurationForm(forms.ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = PMSConfiguration
|
|
||||||
fields = "__all__"
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
plugins = PluginLoader.load_plugins()
|
|
||||||
self.fields['plugin_name'].choices = [(plugin, plugin) for plugin in plugins.keys()]
|
|
||||||
@@ -7,27 +7,31 @@ from asgiref.sync import sync_to_async
|
|||||||
|
|
||||||
class PluginLoader:
|
class PluginLoader:
|
||||||
PLUGIN_PATH = Path(__file__).parent / "plugins"
|
PLUGIN_PATH = Path(__file__).parent / "plugins"
|
||||||
print("Путь к папке плагинов:", PLUGIN_PATH.resolve())
|
|
||||||
print("Содержимое папки:", list(PLUGIN_PATH.iterdir()))
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load_plugins():
|
def load_plugins():
|
||||||
plugins = {}
|
plugins = {}
|
||||||
|
if not PluginLoader.PLUGIN_PATH.exists():
|
||||||
|
print("Папка с плагинами не существует:", PluginLoader.PLUGIN_PATH)
|
||||||
|
return plugins
|
||||||
|
|
||||||
|
print("Загрузка плагинов:")
|
||||||
for file in os.listdir(PluginLoader.PLUGIN_PATH):
|
for file in os.listdir(PluginLoader.PLUGIN_PATH):
|
||||||
if file.endswith("_pms.py") and not file.startswith("__"):
|
if file.endswith("_pms.py") and not file.startswith("__"):
|
||||||
|
print(f" Plugin {file}")
|
||||||
module_name = f"pms_integration.plugins.{file[:-3]}"
|
module_name = f"pms_integration.plugins.{file[:-3]}"
|
||||||
try:
|
try:
|
||||||
module = importlib.import_module(module_name)
|
module = importlib.import_module(module_name)
|
||||||
for attr in dir(module):
|
for attr in dir(module):
|
||||||
cls = getattr(module, attr)
|
cls = getattr(module, attr)
|
||||||
if isinstance(cls, type) and issubclass(cls, BasePMSPlugin) and cls is not BasePMSPlugin:
|
if isinstance(cls, type) and issubclass(cls, BasePMSPlugin) and cls is not BasePMSPlugin:
|
||||||
plugins[cls.__name__] = cls
|
plugin_name = file[:-7] # Убираем `_pms` из имени файла
|
||||||
print(f"Загружен плагин: {cls.__name__}")
|
print(f" Загружен плагин {plugin_name}: {cls.__name__}")
|
||||||
|
plugins[plugin_name] = cls
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Ошибка при загрузке модуля {module_name}: {e}")
|
print(f" Ошибка загрузки плагина {module_name}: {e}")
|
||||||
print(f"Итоговый список плагинов: {list(plugins.keys())}")
|
|
||||||
return plugins
|
return plugins
|
||||||
|
|
||||||
|
|
||||||
class PMSIntegrationManager:
|
class PMSIntegrationManager:
|
||||||
def __init__(self, hotel_id):
|
def __init__(self, hotel_id):
|
||||||
self.hotel_id = hotel_id
|
self.hotel_id = hotel_id
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2024-12-10 02:52
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pms_integration', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='pmsconfiguration',
|
||||||
|
name='plugin_name',
|
||||||
|
field=models.CharField(blank=True, choices=[], max_length=255, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2024-12-10 02:58
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pms_integration', '0002_alter_pmsconfiguration_plugin_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='pmsconfiguration',
|
||||||
|
name='plugin_name',
|
||||||
|
field=models.CharField(blank=True, choices=[], max_length=255, null=True, verbose_name='Плагин'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2024-12-10 03:00
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pms_integration', '0003_alter_pmsconfiguration_plugin_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='pmsconfiguration',
|
||||||
|
name='plugin_name',
|
||||||
|
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Плагин'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2024-12-10 03:03
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pms_integration', '0004_alter_pmsconfiguration_plugin_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pmsconfiguration',
|
||||||
|
name='private_key',
|
||||||
|
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Приватный ключ'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='pmsconfiguration',
|
||||||
|
name='public_key',
|
||||||
|
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Публичный ключ'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -8,10 +8,12 @@ class PMSConfiguration(models.Model):
|
|||||||
name = models.CharField(max_length=255, verbose_name="Название PMS")
|
name = models.CharField(max_length=255, verbose_name="Название PMS")
|
||||||
url = models.URLField(verbose_name="URL API")
|
url = models.URLField(verbose_name="URL API")
|
||||||
token = models.CharField(max_length=255, blank=True, null=True, verbose_name="Токен")
|
token = models.CharField(max_length=255, blank=True, null=True, verbose_name="Токен")
|
||||||
|
public_key = models.CharField(max_length=255, blank=True, null=True, verbose_name="Публичный ключ")
|
||||||
|
private_key = models.CharField(max_length=255, blank=True, null=True, verbose_name="Приватный ключ")
|
||||||
username = models.CharField(max_length=255, blank=True, null=True, verbose_name="Логин")
|
username = models.CharField(max_length=255, blank=True, null=True, verbose_name="Логин")
|
||||||
password = models.CharField(max_length=255, blank=True, null=True, verbose_name="Пароль")
|
password = models.CharField(max_length=255, blank=True, null=True, verbose_name="Пароль")
|
||||||
plugin_name = models.CharField(max_length=255, verbose_name="Название плагина")
|
plugin_name = models.CharField(max_length=255, blank=True, null=True, verbose_name="Плагин")
|
||||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания") # Добавлено поле
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|||||||
@@ -1,71 +1,136 @@
|
|||||||
import requests
|
import requests
|
||||||
|
import json
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from .base_plugin import BasePMSPlugin
|
from .base_plugin import BasePMSPlugin
|
||||||
|
from asgiref.sync import sync_to_async
|
||||||
|
from pms_integration.models import PMSConfiguration # Убедитесь, что модель существует
|
||||||
|
|
||||||
|
|
||||||
class BnovoPMS(BasePMSPlugin):
|
class BnovoPMSPlugin(BasePMSPlugin):
|
||||||
"""
|
"""Плагин для работы с PMS Bnovo."""
|
||||||
Плагин для интеграции с Bnovo.
|
|
||||||
"""
|
def __init__(self, config):
|
||||||
json_schema = {
|
super().__init__(config)
|
||||||
"type": "object",
|
self.api_url = config.url.rstrip("/") # Убираем лишний `/` в конце URL
|
||||||
"properties": {
|
self.username = config.username
|
||||||
"id": {"type": "integer"},
|
self.password = config.password
|
||||||
"number": {"type": "integer"},
|
self.token = None # SID
|
||||||
"roomTypeName": {"type": "string"},
|
|
||||||
"checkInStatus": {"type": "string"},
|
if not self.api_url:
|
||||||
"guests": {"type": "array"},
|
raise ValueError("Не указан URL для работы плагина.")
|
||||||
},
|
if not self.username or not self.password:
|
||||||
"required": ["id", "number", "roomTypeName", "checkInStatus", "guests"]
|
raise ValueError("Не указаны логин или пароль для авторизации.")
|
||||||
}
|
|
||||||
def get_default_parser_settings(self):
|
def get_default_parser_settings(self):
|
||||||
"""
|
"""Возвращает настройки по умолчанию для обработки данных."""
|
||||||
Возвращает настройки парсера по умолчанию.
|
|
||||||
"""
|
|
||||||
return {
|
return {
|
||||||
"field_mapping": {
|
"date_format": "%Y-%m-%dT%H:%M:%S",
|
||||||
"room_name": "roomNumber",
|
"timezone": "UTC"
|
||||||
"check_in": "from",
|
|
||||||
"check_out": "until",
|
|
||||||
},
|
|
||||||
"date_format": "%Y-%m-%dT%H:%M:%S"
|
|
||||||
}
|
}
|
||||||
def fetch_data(self):
|
|
||||||
response = requests.get(self.pms_config.url, headers={"Authorization": f"Bearer {self.pms_config.token}"})
|
|
||||||
response.raise_for_status()
|
|
||||||
data = response.json()
|
|
||||||
|
|
||||||
# Проверка структуры
|
async def _save_token_to_db(self, sid):
|
||||||
expected_fields = self.pms_config.parser_settings.get("fields_mapping", {})
|
"""Сохраняет токен (SID) в базу данных."""
|
||||||
for field in expected_fields.values():
|
try:
|
||||||
if field not in data[0]: # Проверяем первую запись
|
await sync_to_async(PMSConfiguration.objects.update_or_create)(
|
||||||
raise ValueError(f"Поле {field} отсутствует в ответе API.")
|
plugin_name="bnovo",
|
||||||
|
defaults={"token": sid}
|
||||||
|
)
|
||||||
|
print(f"[DEBUG] Токен сохранен в БД: {sid}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ERROR] Ошибка сохранения токена в БД: {e}")
|
||||||
|
|
||||||
return data
|
def _get_auth_headers(self):
|
||||||
|
"""Создает заголовки авторизации."""
|
||||||
def fetch_and_parse(self):
|
headers = {
|
||||||
response = requests.get(
|
"Content-Type": "application/json",
|
||||||
self.pms_config.url,
|
"Accept": "application/json",
|
||||||
headers={"Authorization": f"Bearer {self.pms_config.token}"}
|
}
|
||||||
)
|
if self.token:
|
||||||
self.validate_response(response) # Проверка соответствия структуры
|
headers["Cookie"] = f"SID={self.token}"
|
||||||
if response.status_code != 200:
|
return headers
|
||||||
raise ValueError(f"Ошибка запроса к PMS Bnovo: {response.text}")
|
|
||||||
|
|
||||||
data = response.json()
|
async def _fetch_session(self):
|
||||||
parsed_data = self.parse_data(data)
|
"""Получает идентификатор сессии (SID) через запрос."""
|
||||||
return parsed_data
|
url = f"{self.api_url}/"
|
||||||
|
payload = {
|
||||||
|
"username": self.username,
|
||||||
|
"password": self.password,
|
||||||
|
}
|
||||||
|
|
||||||
def parse_data(self, data):
|
print(f"[DEBUG] URL авторизации: {url}")
|
||||||
# Пример разбора данных на основе JSON-маски
|
print(f"[DEBUG] Тело запроса: {json.dumps(payload, indent=2)}")
|
||||||
reservations = []
|
headers = self._get_auth_headers()
|
||||||
for item in data["reservations"]:
|
|
||||||
reservation = {
|
session = requests.Session()
|
||||||
"id": item["id"],
|
response = session.post(url, json=payload, headers=headers, allow_redirects=False)
|
||||||
"room_number": item["roomNumber"],
|
|
||||||
"check_in": item["checkIn"],
|
print(f"[DEBUG] Статус ответа: {response.status_code}")
|
||||||
"check_out": item["checkOut"],
|
print(f"[DEBUG] Ответ заголовков: {response.headers}")
|
||||||
"status": item["status"],
|
print(f"[DEBUG] Cookies: {session.cookies}")
|
||||||
}
|
|
||||||
reservations.append(reservation)
|
if response.status_code == 302 and "SID" in session.cookies:
|
||||||
return reservations
|
sid = session.cookies.get("SID")
|
||||||
|
self.token = sid
|
||||||
|
print(f"[DEBUG] Получен SID: {sid}")
|
||||||
|
|
||||||
|
# Правильное сохранение в БД через sync_to_async
|
||||||
|
try:
|
||||||
|
await self._save_token_to_db(sid)
|
||||||
|
print(f"[DEBUG] Токен сохранен в БД")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ERROR] Ошибка сохранения токена в БД: {e}")
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Не удалось получить SID из ответа: {response.text}")
|
||||||
|
|
||||||
|
async def _fetch_data(self):
|
||||||
|
"""Получает данные о бронированиях с помощью эндпоинта `/dashboard`."""
|
||||||
|
await self._fetch_session() # Авторизуемся перед каждым запросом
|
||||||
|
|
||||||
|
now = datetime.now()
|
||||||
|
create_from = (now - timedelta(days=90)).strftime("%d.%m.%Y") # Диапазон: последние 90 дней
|
||||||
|
create_to = now.strftime("%d.%m.%Y")
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"create_from": create_from,
|
||||||
|
"create_to": create_to,
|
||||||
|
"status_ids": "1",
|
||||||
|
"advanced_search": 2, # Обязательный параметр
|
||||||
|
"c": 100, # Количество элементов на странице (максимум 100)
|
||||||
|
"page": 1, # Начальная страница
|
||||||
|
"order_by": "create_date.asc", # Сортировка по возрастанию даты создания
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = self._get_auth_headers()
|
||||||
|
|
||||||
|
all_bookings = [] # Для сохранения всех бронирований
|
||||||
|
while True:
|
||||||
|
print(f"[DEBUG] Запрос к /dashboard с параметрами: {json.dumps(params, indent=2)}")
|
||||||
|
response = requests.get(f"{self.api_url}/dashboard", headers=headers, params=params)
|
||||||
|
|
||||||
|
print(f"[DEBUG] Статус ответа: {response.status_code}")
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise ValueError(f"Ошибка при получении данных: {response.status_code}, {response.text}")
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
print(json.dumps(data, indent=2))
|
||||||
|
bookings = data.get("bookings", [])
|
||||||
|
all_bookings.extend(bookings)
|
||||||
|
|
||||||
|
print(f"[DEBUG] Получено бронирований: {len(bookings)}")
|
||||||
|
print(f"[DEBUG] Всего бронирований: {len(all_bookings)}")
|
||||||
|
|
||||||
|
# Проверка на наличие следующей страницы
|
||||||
|
pages_info = data.get("pages", {})
|
||||||
|
current_page = pages_info.get("current_page", 1)
|
||||||
|
total_pages = pages_info.get("total_pages", 1)
|
||||||
|
|
||||||
|
if current_page >= total_pages:
|
||||||
|
break # Все страницы загружены
|
||||||
|
|
||||||
|
params["page"] += 1 # Переход на следующую страницу
|
||||||
|
|
||||||
|
if not all_bookings:
|
||||||
|
print("[DEBUG] Нет бронирований за указанный период.")
|
||||||
|
else:
|
||||||
|
print(f"[DEBUG] Полученные бронирования: {json.dumps(all_bookings, indent=2)}")
|
||||||
|
return all_bookings
|
||||||
@@ -1,78 +1,254 @@
|
|||||||
import hashlib
|
# import requests
|
||||||
import requests
|
# import hashlib
|
||||||
import json
|
# import json
|
||||||
from datetime import datetime
|
# from .base_plugin import BasePMSPlugin
|
||||||
from hotels.models import Reservation
|
# from datetime import datetime, timedelta
|
||||||
|
# from asgiref.sync import sync_to_async
|
||||||
|
|
||||||
from pms_integration.plugins.base_plugin import BasePMSPlugin
|
|
||||||
|
# class RealtyCalendarPlugin(BasePMSPlugin):
|
||||||
|
# """Плагин для импорта данных из системы RealtyCalendar
|
||||||
|
# """
|
||||||
|
# def __init__(self, config):
|
||||||
|
# super().__init__(config)
|
||||||
|
# self.public_key = config.public_key
|
||||||
|
# self.private_key = config.private_key
|
||||||
|
# self.api_url = config.url.rstrip("/") # Убираем лишний `/` в конце URL
|
||||||
|
|
||||||
|
# if not self.public_key or not self.private_key:
|
||||||
|
# raise ValueError("Публичный или приватный ключ отсутствует для RealtyCalendar")
|
||||||
|
# def get_default_parser_settings(self):
|
||||||
|
# """
|
||||||
|
# Возвращает настройки по умолчанию для обработки данных.
|
||||||
|
# """
|
||||||
|
# return {
|
||||||
|
# "date_format": "%Y-%m-%dT%H:%M:%S",
|
||||||
|
# "timezone": "UTC"
|
||||||
|
# }
|
||||||
|
# def _get_sorted_keys(self, obj):
|
||||||
|
# """
|
||||||
|
# Возвращает отсортированный по имени список ключей.
|
||||||
|
# """
|
||||||
|
# return sorted(list(obj.keys()))
|
||||||
|
|
||||||
|
# def _generate_data_string(self, obj):
|
||||||
|
# """
|
||||||
|
# Формирует строку параметров для подписи.
|
||||||
|
# """
|
||||||
|
# sorted_keys = self._get_sorted_keys(obj)
|
||||||
|
# string = "".join(f"{key}={obj[key]}" for key in sorted_keys)
|
||||||
|
# return string + self.private_key
|
||||||
|
|
||||||
|
# def _generate_md5(self, string):
|
||||||
|
# """
|
||||||
|
# Генерирует MD5-хеш от строки.
|
||||||
|
# """
|
||||||
|
# return hashlib.md5(string.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
# def _generate_sign(self, data):
|
||||||
|
# """
|
||||||
|
# Генерирует подпись для данных запроса.
|
||||||
|
# """
|
||||||
|
# data_string = self._generate_data_string(data)
|
||||||
|
# return self._generate_md5(data_string)
|
||||||
|
|
||||||
|
# def fetch_data(self):
|
||||||
|
# """
|
||||||
|
# Выполняет запрос к API RealtyCalendar для получения данных о бронированиях.
|
||||||
|
# """
|
||||||
|
# base_url = f"https://realtycalendar.ru/api/v1/bookings/{self.public_key}/"
|
||||||
|
# headers = {
|
||||||
|
# "Accept": "application/json",
|
||||||
|
# "Content-Type": "application/json",
|
||||||
|
# }
|
||||||
|
|
||||||
|
# # Определяем даты выборки
|
||||||
|
# now = datetime.now()
|
||||||
|
# data = {
|
||||||
|
# "begin_date": (now - timedelta(days=7)).strftime("%Y-%m-%d"),
|
||||||
|
# "end_date": now.strftime("%Y-%m-%d"),
|
||||||
|
# }
|
||||||
|
|
||||||
|
# # Генерация подписи
|
||||||
|
# data["sign"] = self._generate_sign(data)
|
||||||
|
|
||||||
|
# # Отправляем запрос
|
||||||
|
# print(f"URL запроса: {base_url}")
|
||||||
|
# print(f"Заголовки: {headers}")
|
||||||
|
# print(f"Данные запроса: {data}")
|
||||||
|
|
||||||
|
# response = requests.post(url=base_url, headers=headers, json=data)
|
||||||
|
|
||||||
|
# # Логируем результат
|
||||||
|
# print(f"Статус ответа: {response.status_code}")
|
||||||
|
# print(f"Ответ: {response.text}")
|
||||||
|
|
||||||
|
# # Проверяем успешность запроса
|
||||||
|
# if response.status_code == 200:
|
||||||
|
# return response.json().get("bookings", [])
|
||||||
|
# else:
|
||||||
|
# raise ValueError(f"Ошибка API RealtyCalendar: {response.status_code}, {response.text}")
|
||||||
|
|
||||||
|
# async def _save_to_db(self, data, hotel_id):
|
||||||
|
# """
|
||||||
|
# Сохраняет данные о бронированиях в базу данных.
|
||||||
|
# """
|
||||||
|
# from hotels.models import Reservation, Hotel
|
||||||
|
|
||||||
|
# hotel = await sync_to_async(Hotel.objects.get)(id=hotel_id)
|
||||||
|
# for item in data:
|
||||||
|
# try:
|
||||||
|
# reservation, created = await sync_to_async(Reservation.objects.update_or_create)(
|
||||||
|
# reservation_id=item["id"],
|
||||||
|
# hotel=hotel,
|
||||||
|
# defaults={
|
||||||
|
# "room_number": item.get("apartment_id", ""), # ID квартиры
|
||||||
|
# "check_in": datetime.strptime(item["begin_date"], "%Y-%m-%d"), # Дата заезда
|
||||||
|
# "check_out": datetime.strptime(item["end_date"], "%Y-%m-%d"), # Дата выезда
|
||||||
|
# "status": item.get("status", ""), # Статус бронирования
|
||||||
|
# "price": item.get("amount", 0), # Сумма оплаты
|
||||||
|
# "client_name": item["client"].get("fio", ""), # Имя клиента
|
||||||
|
# "client_email": item["client"].get("email", ""), # Email клиента
|
||||||
|
# "client_phone": item["client"].get("phone", ""), # Телефон клиента
|
||||||
|
# }
|
||||||
|
# )
|
||||||
|
# print(f"{'Создана' if created else 'Обновлена'} запись: {reservation}")
|
||||||
|
# except Exception as e:
|
||||||
|
# print(f"Ошибка при сохранении бронирования ID {item['id']}: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
from .base_plugin import BasePMSPlugin
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from asgiref.sync import sync_to_async
|
||||||
|
|
||||||
|
|
||||||
class RealtyCalendarPlugin(BasePMSPlugin):
|
class RealtyCalendarPlugin(BasePMSPlugin):
|
||||||
"""
|
"""Плагин для импорта данных из системы RealtyCalendar
|
||||||
Плагин для взаимодействия с RealtyCalendar.
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
self.public_key = config.token # Используем `token` как публичный ключ
|
self.public_key = config.public_key
|
||||||
self.private_key = config.password # Используем `password` как приватный ключ
|
self.private_key = config.private_key
|
||||||
self.base_url = config.url
|
self.api_url = config.url.rstrip("/")
|
||||||
|
|
||||||
def generate_sign(self, params):
|
if not self.public_key or not self.private_key:
|
||||||
"""
|
raise ValueError("Публичный или приватный ключ отсутствует для RealtyCalendar")
|
||||||
Генерация подписи запроса.
|
|
||||||
:param params: Параметры запроса.
|
|
||||||
:return: Подпись.
|
|
||||||
"""
|
|
||||||
sorted_keys = sorted(params.keys())
|
|
||||||
data_string = ''.join(f"{key}={params[key]}" for key in sorted_keys)
|
|
||||||
sign_string = f"{data_string}{self.private_key}"
|
|
||||||
return hashlib.md5(sign_string.encode('utf-8')).hexdigest()
|
|
||||||
|
|
||||||
def fetch_data(self, start_date=None, end_date=None):
|
def get_default_parser_settings(self):
|
||||||
"""
|
"""
|
||||||
Получение данных из RealtyCalendar.
|
Возвращает настройки по умолчанию для обработки данных.
|
||||||
:param start_date: Начальная дата (формат YYYY-MM-DD).
|
|
||||||
:param end_date: Конечная дата (формат YYYY-MM-DD).
|
|
||||||
:return: Список данных бронирования.
|
|
||||||
"""
|
"""
|
||||||
if not start_date:
|
return {
|
||||||
start_date = datetime.now().strftime('%Y-%m-%d')
|
"date_format": "%Y-%m-%dT%H:%M:%S",
|
||||||
if not end_date:
|
"timezone": "UTC"
|
||||||
end_date = (datetime.now() + timedelta(days=1)).strftime('%Y-%m-%d')
|
|
||||||
|
|
||||||
params = {
|
|
||||||
'begin_date': start_date,
|
|
||||||
'end_date': end_date,
|
|
||||||
}
|
}
|
||||||
params['sign'] = self.generate_sign(params)
|
|
||||||
|
|
||||||
url = f"{self.base_url}/bookings/{self.public_key}/"
|
def _get_sorted_keys(self, obj):
|
||||||
|
"""
|
||||||
|
Возвращает отсортированный по имени список ключей.
|
||||||
|
"""
|
||||||
|
sorted_keys = sorted(list(obj.keys()))
|
||||||
|
print(f"[DEBUG] Отсортированные ключи: {sorted_keys}")
|
||||||
|
return sorted_keys
|
||||||
|
|
||||||
|
def _generate_data_string(self, obj):
|
||||||
|
"""
|
||||||
|
Формирует строку параметров для подписи.
|
||||||
|
"""
|
||||||
|
sorted_keys = self._get_sorted_keys(obj)
|
||||||
|
string = "".join(f"{key}={obj[key]}" for key in sorted_keys)
|
||||||
|
print(f"[DEBUG] Сформированная строка данных: {string}")
|
||||||
|
return string + self.private_key
|
||||||
|
|
||||||
|
def _generate_md5(self, string):
|
||||||
|
"""
|
||||||
|
Генерирует MD5-хеш от строки.
|
||||||
|
"""
|
||||||
|
md5_hash = hashlib.md5(string.encode("utf-8")).hexdigest()
|
||||||
|
print(f"[DEBUG] Сформированный MD5-хеш: {md5_hash}")
|
||||||
|
return md5_hash
|
||||||
|
|
||||||
|
def _generate_sign(self, data):
|
||||||
|
"""
|
||||||
|
Генерирует подпись для данных запроса.
|
||||||
|
"""
|
||||||
|
data_string = self._generate_data_string(data)
|
||||||
|
print(f"[DEBUG] Строка для подписи: {data_string}")
|
||||||
|
sign = self._generate_md5(data_string)
|
||||||
|
print(f"[DEBUG] Подпись: {sign}")
|
||||||
|
return sign
|
||||||
|
|
||||||
|
def _fetch_data(self):
|
||||||
|
"""
|
||||||
|
Выполняет запрос к API RealtyCalendar для получения данных о бронированиях.
|
||||||
|
"""
|
||||||
|
base_url = f"https://realtycalendar.ru/api/v1/bookings/{self.public_key}/"
|
||||||
headers = {
|
headers = {
|
||||||
'Accept': 'application/json',
|
"Accept": "application/json",
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(url, json=params, headers=headers)
|
# Определяем даты выборки
|
||||||
response.raise_for_status()
|
now = datetime.now()
|
||||||
|
data = {
|
||||||
|
"begin_date": (now - timedelta(days=7)).strftime("%Y-%m-%d"),
|
||||||
|
"end_date": now.strftime("%Y-%m-%d"),
|
||||||
|
}
|
||||||
|
|
||||||
data = response.json()
|
print(f"[DEBUG] Даты выборки: {data}")
|
||||||
return data.get('bookings', [])
|
|
||||||
|
|
||||||
@staticmethod
|
# Генерация подписи
|
||||||
def save_data(bookings):
|
data["sign"] = self._generate_sign(data)
|
||||||
|
|
||||||
|
# Отправляем запрос
|
||||||
|
print(f"[DEBUG] URL запроса: {base_url}")
|
||||||
|
print(f"[DEBUG] Заголовки: {headers}")
|
||||||
|
print(f"[DEBUG] Данные запроса: {data}")
|
||||||
|
|
||||||
|
response = requests.post(url=base_url, headers=headers, json=data)
|
||||||
|
|
||||||
|
# Логируем результат
|
||||||
|
print(f"[DEBUG] Статус ответа: {response.status_code}")
|
||||||
|
print(f"[DEBUG] Ответ: {response.text}")
|
||||||
|
|
||||||
|
# Проверяем успешность запроса
|
||||||
|
if response.status_code == 200:
|
||||||
|
bookings = response.json().get("bookings", [])
|
||||||
|
print(f"[DEBUG] Полученные данные бронирований: {bookings}")
|
||||||
|
return bookings
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Ошибка API RealtyCalendar: {response.status_code}, {response.text}")
|
||||||
|
|
||||||
|
|
||||||
|
async def _save_to_db(self, data, hotel_id):
|
||||||
"""
|
"""
|
||||||
Сохранение данных бронирования в базу данных.
|
Сохраняет данные о бронированиях в базу данных.
|
||||||
:param bookings: Список бронирований.
|
|
||||||
"""
|
"""
|
||||||
for booking in bookings:
|
from hotels.models import Reservation, Hotel
|
||||||
Reservation.objects.update_or_create(
|
|
||||||
external_id=booking['id'],
|
hotel = await sync_to_async(Hotel.objects.get)(id=hotel_id)
|
||||||
defaults={
|
print(f"[DEBUG] Загружен отель: {hotel.name}")
|
||||||
'check_in': booking['begin_date'],
|
|
||||||
'check_out': booking['end_date'],
|
for item in data:
|
||||||
'amount': booking['amount'],
|
print(f"[DEBUG] Обработка бронирования: {item}")
|
||||||
'notes': booking.get('notes', ''),
|
try:
|
||||||
'guest_name': booking['client']['fio'],
|
reservation, created = await sync_to_async(Reservation.objects.update_or_create)(
|
||||||
'guest_phone': booking['client']['phone'],
|
reservation_id=item["id"],
|
||||||
},
|
hotel=hotel,
|
||||||
)
|
defaults={
|
||||||
|
"room_number": item.get("apartment_id", ""), # ID квартиры
|
||||||
|
"check_in": datetime.strptime(item["begin_date"], "%Y-%m-%d"), # Дата заезда
|
||||||
|
"check_out": datetime.strptime(item["end_date"], "%Y-%m-%d"), # Дата выезда
|
||||||
|
"status": item.get("status", ""), # Статус бронирования
|
||||||
|
"price": item.get("amount", 0), # Сумма оплаты
|
||||||
|
"client_name": item["client"].get("fio", ""), # Имя клиента
|
||||||
|
"client_email": item["client"].get("email", ""), # Email клиента
|
||||||
|
"client_phone": item["client"].get("phone", ""), # Телефон клиента
|
||||||
|
}
|
||||||
|
)
|
||||||
|
print(f"[DEBUG] {'Создана' if created else 'Обновлена'} запись: {reservation}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[DEBUG] Ошибка при сохранении бронирования ID {item['id']}: {e}")
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ from hotels.models import Hotel
|
|||||||
|
|
||||||
|
|
||||||
class Shelter(BasePMSPlugin):
|
class Shelter(BasePMSPlugin):
|
||||||
|
"""
|
||||||
|
Плагин для PMS Shelter Coud.
|
||||||
|
"""
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
self.token = config.token
|
self.token = config.token
|
||||||
|
|||||||
73
pms_integration/test_requests.py
Normal file
73
pms_integration/test_requests.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# import requests
|
||||||
|
# import json
|
||||||
|
|
||||||
|
# # Функция авторизации
|
||||||
|
# def authorize(username, password):
|
||||||
|
# url = "https://online.bnovo.ru/"
|
||||||
|
# headers = {
|
||||||
|
# "accept": "application/json",
|
||||||
|
# "Content-Type": "application/json"
|
||||||
|
# }
|
||||||
|
# payload = {
|
||||||
|
# "username": username,
|
||||||
|
# "password": password
|
||||||
|
# }
|
||||||
|
|
||||||
|
# response = requests.post(url, headers=headers, json=payload, allow_redirects=False)
|
||||||
|
# print(f"[DEBUG] Статус авторизации: {response.status_code}")
|
||||||
|
# print(f"[DEBUG] Заголовки ответа: {response.headers}")
|
||||||
|
|
||||||
|
# if response.status_code == 302 and "SID" in response.cookies:
|
||||||
|
# sid = response.cookies.get("SID")
|
||||||
|
# print(f"[DEBUG] Получен SID: {sid}")
|
||||||
|
# return sid
|
||||||
|
# else:
|
||||||
|
# raise ValueError(f"Ошибка авторизации: {response.text}")
|
||||||
|
|
||||||
|
# # Функция получения данных с /dashboard
|
||||||
|
# def fetch_dashboard(sid, create_from, create_to, status_ids, page=1, count=10):
|
||||||
|
# url = f"https://online.bnovo.ru/dashboard"
|
||||||
|
# headers = {
|
||||||
|
# "accept": "application/json",
|
||||||
|
# "Cookie": f"SID={sid}"
|
||||||
|
# }
|
||||||
|
# params = {
|
||||||
|
# "create_from": create_from,
|
||||||
|
# "create_to": create_to,
|
||||||
|
# "advanced_search": 2,
|
||||||
|
# "status_ids": status_ids,
|
||||||
|
# "c": count,
|
||||||
|
# "page": page,
|
||||||
|
# "order_by": "create_date.asc"
|
||||||
|
# }
|
||||||
|
|
||||||
|
# response = requests.get(url, headers=headers, params=params)
|
||||||
|
# print(f"[DEBUG] Статус запроса: {response.status_code}")
|
||||||
|
# print(f"[DEBUG] Ответ: {json.dumps(response.json(), indent=2, ensure_ascii=False)}")
|
||||||
|
|
||||||
|
# if response.status_code == 200:
|
||||||
|
# return response.json()
|
||||||
|
# else:
|
||||||
|
# raise ValueError(f"Ошибка при запросе данных: {response.text}")
|
||||||
|
|
||||||
|
# # Тестовый вызов
|
||||||
|
# try:
|
||||||
|
# username = "cto@hotelantifraud.ru"
|
||||||
|
# password = "tD8wC1zP9tiT6mY1"
|
||||||
|
|
||||||
|
# # Авторизация
|
||||||
|
# sid = authorize(username, password)
|
||||||
|
|
||||||
|
# # Получение бронирований
|
||||||
|
# bookings = fetch_dashboard(
|
||||||
|
# sid=sid,
|
||||||
|
# create_from="25.09.2024",
|
||||||
|
# create_to="05.10.2024",
|
||||||
|
# status_ids="1",
|
||||||
|
# page=1,
|
||||||
|
# count=10
|
||||||
|
# )
|
||||||
|
# print(f"[INFO] Полученные бронирования: {json.dumps(bookings, indent=2, ensure_ascii=False)}")
|
||||||
|
|
||||||
|
# except Exception as e:
|
||||||
|
# print(f"[ERROR] {e}")
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
ace_tools==0.0
|
ace_tools==0.0
|
||||||
|
aiohappyeyeballs==2.4.4
|
||||||
|
aiohttp==3.11.10
|
||||||
|
aiosignal==1.3.1
|
||||||
anyio==4.6.2.post1
|
anyio==4.6.2.post1
|
||||||
APScheduler==3.11.0
|
APScheduler==3.11.0
|
||||||
asgiref==3.8.1
|
asgiref==3.8.1
|
||||||
|
async-timeout==5.0.1
|
||||||
|
attrs==24.2.0
|
||||||
certifi==2024.8.30
|
certifi==2024.8.30
|
||||||
charset-normalizer==3.4.0
|
charset-normalizer==3.4.0
|
||||||
Django==5.1.4
|
Django==5.1.4
|
||||||
@@ -9,26 +14,41 @@ django-filter==24.3
|
|||||||
django-jazzmin==3.0.1
|
django-jazzmin==3.0.1
|
||||||
django-jet==1.0.8
|
django-jet==1.0.8
|
||||||
et_xmlfile==2.0.0
|
et_xmlfile==2.0.0
|
||||||
|
exceptiongroup==1.2.2
|
||||||
fpdf==1.7.2
|
fpdf==1.7.2
|
||||||
|
frozenlist==1.5.0
|
||||||
|
geoip2==4.8.1
|
||||||
h11==0.14.0
|
h11==0.14.0
|
||||||
httpcore==1.0.7
|
httpcore==1.0.7
|
||||||
httpx==0.28.0
|
httpx==0.28.0
|
||||||
idna==3.10
|
idna==3.10
|
||||||
|
jsonschema==4.23.0
|
||||||
|
jsonschema-specifications==2024.10.1
|
||||||
|
maxminddb==2.6.2
|
||||||
|
multidict==6.1.0
|
||||||
numpy==2.1.3
|
numpy==2.1.3
|
||||||
openpyxl==3.1.5
|
openpyxl==3.1.5
|
||||||
pandas==2.2.3
|
pandas==2.2.3
|
||||||
|
pathspec==0.12.1
|
||||||
pillow==11.0.0
|
pillow==11.0.0
|
||||||
|
propcache==0.2.1
|
||||||
PyMySQL==1.1.1
|
PyMySQL==1.1.1
|
||||||
python-dateutil==2.9.0.post0
|
python-dateutil==2.9.0.post0
|
||||||
python-dotenv==1.0.1
|
python-dotenv==1.0.1
|
||||||
python-telegram-bot==21.8
|
python-telegram-bot==21.8
|
||||||
pytz==2024.2
|
pytz==2024.2
|
||||||
PyYAML==6.0.2
|
PyYAML==6.0.2
|
||||||
|
referencing==0.35.1
|
||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
|
rpds-py==0.22.3
|
||||||
six==1.17.0
|
six==1.17.0
|
||||||
sniffio==1.3.1
|
sniffio==1.3.1
|
||||||
sqlparse==0.5.2
|
sqlparse==0.5.2
|
||||||
|
typing_extensions==4.12.2
|
||||||
tzdata==2024.2
|
tzdata==2024.2
|
||||||
tzlocal==5.2
|
tzlocal==5.2
|
||||||
|
ua-parser==1.0.0
|
||||||
|
ua-parser-builtins==0.18.0.post1
|
||||||
urllib3==2.2.3
|
urllib3==2.2.3
|
||||||
jsonschema
|
user-agents==2.2.0
|
||||||
|
yarl==1.18.3
|
||||||
|
|||||||
0
scheduler/__init__.py
Normal file
0
scheduler/__init__.py
Normal file
59
scheduler/admin.py
Normal file
59
scheduler/admin.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from django import forms
|
||||||
|
from django.utils.functional import cached_property
|
||||||
|
from .models import ScheduledTask
|
||||||
|
from django.templatetags.static import static
|
||||||
|
from scheduler.utils import get_project_functions
|
||||||
|
|
||||||
|
class CustomAdmin(admin.ModelAdmin):
|
||||||
|
class Media:
|
||||||
|
css = {"all": (static("scheduler/admin.css"),)}
|
||||||
|
js = (static("scheduler/admin.js"),)
|
||||||
|
|
||||||
|
class ScheduledTaskForm(forms.ModelForm):
|
||||||
|
DAYS_OF_WEEK_CHOICES = [
|
||||||
|
(0, "Воскресенье"),
|
||||||
|
(1, "Понедельник"),
|
||||||
|
(2, "Вторник"),
|
||||||
|
(3, "Среда"),
|
||||||
|
(4, "Четверг"),
|
||||||
|
(5, "Пятница"),
|
||||||
|
(6, "Суббота"),
|
||||||
|
]
|
||||||
|
|
||||||
|
weekdays = forms.MultipleChoiceField(
|
||||||
|
choices=DAYS_OF_WEEK_CHOICES,
|
||||||
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
label="Дни недели",
|
||||||
|
required=False, # Опционально
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ScheduledTask
|
||||||
|
fields = [
|
||||||
|
"task_name",
|
||||||
|
"function_path",
|
||||||
|
"minutes",
|
||||||
|
"hours",
|
||||||
|
"months",
|
||||||
|
"weekdays", # Используем только поле с галочками
|
||||||
|
"active",
|
||||||
|
]
|
||||||
|
|
||||||
|
def clean_weekdays(self):
|
||||||
|
"""
|
||||||
|
Преобразуем список выбранных дней в строку для хранения в базе.
|
||||||
|
"""
|
||||||
|
weekdays = self.cleaned_data.get("weekdays", [])
|
||||||
|
return ",".join(map(str, weekdays))
|
||||||
|
|
||||||
|
@admin.register(ScheduledTask)
|
||||||
|
class ScheduledTaskAdmin(admin.ModelAdmin):
|
||||||
|
form = ScheduledTaskForm
|
||||||
|
list_display = ("task_name", "function_path", "active", "formatted_last_run")
|
||||||
|
list_filter = ("active",)
|
||||||
|
search_fields = ("task_name", "function_path")
|
||||||
|
|
||||||
|
def formatted_last_run(self, obj):
|
||||||
|
return obj.last_run.strftime("%Y-%m-%d %H:%M:%S") if obj.last_run else "Никогда"
|
||||||
|
formatted_last_run.short_description = "Последний запуск"
|
||||||
6
scheduler/apps.py
Normal file
6
scheduler/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class SchedulerConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'scheduler'
|
||||||
30
scheduler/migrations/0001_initial.py
Normal file
30
scheduler/migrations/0001_initial.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2024-12-10 08:38
|
||||||
|
|
||||||
|
import scheduler.models
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ScheduledTask',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('task_name', models.CharField(max_length=255, verbose_name='Название задачи')),
|
||||||
|
('function_path', models.CharField(choices=scheduler.models.get_available_functions, max_length=500, verbose_name='Путь к функции (модуль.функция)')),
|
||||||
|
('minutes', models.CharField(default='*', max_length=255, verbose_name='Минуты')),
|
||||||
|
('hours', models.CharField(default='*', max_length=255, verbose_name='Часы')),
|
||||||
|
('days', models.CharField(default='*', max_length=255, verbose_name='Дни')),
|
||||||
|
('months', models.CharField(default='*', max_length=255, verbose_name='Месяцы')),
|
||||||
|
('weekdays', models.JSONField(default=list, verbose_name='Дни недели')),
|
||||||
|
('active', models.BooleanField(default=True, verbose_name='Активно')),
|
||||||
|
('last_run', models.DateTimeField(blank=True, null=True, verbose_name='Последний запуск')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
0
scheduler/migrations/__init__.py
Normal file
0
scheduler/migrations/__init__.py
Normal file
47
scheduler/models.py
Normal file
47
scheduler/models.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.utils.timezone import now
|
||||||
|
|
||||||
|
|
||||||
|
class ScheduledTask(models.Model):
|
||||||
|
task_name = models.CharField(max_length=255)
|
||||||
|
function_path = models.CharField(max_length=255)
|
||||||
|
minutes = models.CharField(max_length=255)
|
||||||
|
hours = models.CharField(max_length=255)
|
||||||
|
months = models.CharField(max_length=255)
|
||||||
|
weekdays = models.CharField(max_length=100, blank=True, default="")
|
||||||
|
active = models.BooleanField(default=True)
|
||||||
|
last_run = models.DateTimeField(null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def clean_weekdays(self):
|
||||||
|
"""Приводим список в строку при сохранении."""
|
||||||
|
if isinstance(self.weekdays, list):
|
||||||
|
self.weekdays = ",".join(map(str, self.weekdays))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Запланированная задача"
|
||||||
|
verbose_name_plural = "Запланированные задачи"
|
||||||
|
|
||||||
|
def get_available_functions():
|
||||||
|
from scheduler.utils import get_project_functions
|
||||||
|
return [(path, name) for path, name in get_project_functions()]
|
||||||
|
|
||||||
|
class ScheduledTask(models.Model):
|
||||||
|
task_name = models.CharField(max_length=255, verbose_name="Название задачи")
|
||||||
|
function_path = models.CharField(
|
||||||
|
max_length=500,
|
||||||
|
choices=get_available_functions,
|
||||||
|
verbose_name="Путь к функции (модуль.функция)",
|
||||||
|
)
|
||||||
|
minutes = models.CharField(max_length=255, verbose_name="Минуты", default="*")
|
||||||
|
hours = models.CharField(max_length=255, verbose_name="Часы", default="*")
|
||||||
|
days = models.CharField(max_length=255, verbose_name="Дни", default="*")
|
||||||
|
months = models.CharField(max_length=255, verbose_name="Месяцы", default="*")
|
||||||
|
weekdays = models.JSONField(default=list, verbose_name="Дни недели")
|
||||||
|
active = models.BooleanField(default=True, verbose_name="Активно")
|
||||||
|
last_run = models.DateTimeField(blank=True, null=True, verbose_name="Последний запуск")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.task_name
|
||||||
16
scheduler/static/scheduler/admin.css
Normal file
16
scheduler/static/scheduler/admin.css
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
.checkbox-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-row label {
|
||||||
|
display: inline-block;
|
||||||
|
width: auto;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row.last_run span {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
66
scheduler/tasks.py
Normal file
66
scheduler/tasks.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
from apscheduler.schedulers.base import BaseScheduler
|
||||||
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||||
|
from importlib import import_module
|
||||||
|
from scheduler.models import ScheduledTask
|
||||||
|
import importlib
|
||||||
|
from apscheduler.triggers.cron import CronTrigger
|
||||||
|
|
||||||
|
|
||||||
|
def format_weekdays(weekdays):
|
||||||
|
"""Преобразует список дней недели в строку."""
|
||||||
|
if isinstance(weekdays, list):
|
||||||
|
return ",".join(map(str, weekdays))
|
||||||
|
return str(weekdays)
|
||||||
|
|
||||||
|
def run_task(task):
|
||||||
|
"""
|
||||||
|
Выполняет задачу, указанную в модели ScheduledTask.
|
||||||
|
"""
|
||||||
|
module_name, func_name = task.module_path.rsplit(".", 1)
|
||||||
|
module = import_module(module_name)
|
||||||
|
func = getattr(module, func_name)
|
||||||
|
func()
|
||||||
|
|
||||||
|
def setup_scheduler():
|
||||||
|
"""Настройка планировщика задач из БД."""
|
||||||
|
print("Настройка планировщика задач...")
|
||||||
|
scheduler = AsyncIOScheduler()
|
||||||
|
|
||||||
|
tasks = ScheduledTask.objects.filter(active=True)
|
||||||
|
for task in tasks:
|
||||||
|
scheduler.add_job(
|
||||||
|
run_task,
|
||||||
|
"cron",
|
||||||
|
id=task.name,
|
||||||
|
minute=task.cron_minute,
|
||||||
|
hour=task.cron_hour,
|
||||||
|
day=task.cron_day,
|
||||||
|
month=task.cron_month,
|
||||||
|
day_of_week=task.cron_weekday,
|
||||||
|
args=[task],
|
||||||
|
)
|
||||||
|
scheduler.start()
|
||||||
|
print("Планировщик запущен.")
|
||||||
|
return scheduler
|
||||||
|
|
||||||
|
def load_tasks_to_scheduler(scheduler: BaseScheduler):
|
||||||
|
tasks = ScheduledTask.objects.filter(active=True)
|
||||||
|
for task in tasks:
|
||||||
|
try:
|
||||||
|
module_name, func_name = task.function_path.rsplit('.', 1)
|
||||||
|
module = import_module(module_name)
|
||||||
|
func = getattr(module, func_name)
|
||||||
|
|
||||||
|
scheduler.add_job(
|
||||||
|
func,
|
||||||
|
trigger="cron",
|
||||||
|
minute=task.minutes,
|
||||||
|
hour=task.hours,
|
||||||
|
day=task.days or "*",
|
||||||
|
month=task.months or "*",
|
||||||
|
day_of_week=task.weekdays or "*",
|
||||||
|
id=str(task.id),
|
||||||
|
replace_existing=True,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка при добавлении задачи '{task.task_name}': {e}")
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
{% extends "admin/change_form.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block extrahead %}
|
||||||
|
{{ block.super }}
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static 'scheduler/admin.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="form-container">
|
||||||
|
<form method="post" class="change-form" enctype="multipart/form-data" novalidate>
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ adminform.non_field_errors }}
|
||||||
|
<div>
|
||||||
|
{% for fieldset in adminform %}
|
||||||
|
<fieldset class="{{ fieldset.classes }}">
|
||||||
|
<legend>{{ fieldset.name }}</legend>
|
||||||
|
{% for line in fieldset %}
|
||||||
|
<div class="form-row {{ line.field.field.name }}">
|
||||||
|
{% if line.field.name == 'last_run' %}
|
||||||
|
<div>
|
||||||
|
<label>{{ line.label }}</label>
|
||||||
|
<span>{{ line.field.contents }}</span>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ line.errors }}
|
||||||
|
{{ line.field }}
|
||||||
|
{% if line.field.is_checkbox %}
|
||||||
|
<div class="checkbox-row">{{ line.field }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</fieldset>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="submit-row">
|
||||||
|
{{ submit_buttons }}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
6
scheduler/test_module.py
Normal file
6
scheduler/test_module.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
def test_function():
|
||||||
|
"""тестовая функция для проверки планировщика
|
||||||
|
|
||||||
|
"""
|
||||||
|
print("Hello, World!")
|
||||||
|
return "Hello, World!"
|
||||||
3
scheduler/tests.py
Normal file
3
scheduler/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
71
scheduler/utils.py
Normal file
71
scheduler/utils.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||||
|
import os
|
||||||
|
import inspect
|
||||||
|
import importlib
|
||||||
|
from typing import List, Tuple
|
||||||
|
from pathspec import PathSpec
|
||||||
|
|
||||||
|
def reload_tasks_periodically(scheduler: AsyncIOScheduler):
|
||||||
|
"""Перезагрузка задач из базы данных каждые 5 минут."""
|
||||||
|
from scheduler.tasks import load_tasks_to_scheduler
|
||||||
|
scheduler.add_job(lambda: load_tasks_to_scheduler(scheduler), "interval", minutes=5)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def load_gitignore_patterns(project_root: str) -> PathSpec:
|
||||||
|
"""
|
||||||
|
Загружает паттерны из файла .gitignore.
|
||||||
|
"""
|
||||||
|
gitignore_path = os.path.join(project_root, ".gitignore")
|
||||||
|
if os.path.exists(gitignore_path):
|
||||||
|
with open(gitignore_path, "r", encoding="utf-8") as f:
|
||||||
|
patterns = f.readlines()
|
||||||
|
return PathSpec.from_lines("gitwildmatch", patterns)
|
||||||
|
return PathSpec.from_lines("gitwildmatch", []) # Пустой PathSpec
|
||||||
|
|
||||||
|
def get_project_functions() -> List[Tuple[str, str]]:
|
||||||
|
"""
|
||||||
|
Сканирует проект и возвращает список всех функций в формате (путь, имя функции),
|
||||||
|
исключая файлы и папки, указанные в .gitignore.
|
||||||
|
"""
|
||||||
|
functions = []
|
||||||
|
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
# Загружаем паттерны из .gitignore
|
||||||
|
gitignore_spec = load_gitignore_patterns(project_root)
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(project_root):
|
||||||
|
# Исключаем директории, указанные в .gitignore
|
||||||
|
dirs[:] = [d for d in dirs if not gitignore_spec.match_file(os.path.relpath(os.path.join(root, d), project_root))]
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
file_path = os.path.relpath(os.path.join(root, file), project_root)
|
||||||
|
if (
|
||||||
|
file.endswith(".py") and
|
||||||
|
not file.startswith("__") and
|
||||||
|
not gitignore_spec.match_file(file_path)
|
||||||
|
):
|
||||||
|
module_path = os.path.relpath(os.path.join(root, file), project_root)
|
||||||
|
module_name = module_path.replace(os.sep, ".").replace(".py", "")
|
||||||
|
|
||||||
|
try:
|
||||||
|
module = importlib.import_module(module_name)
|
||||||
|
for name, func in inspect.getmembers(module, inspect.isfunction):
|
||||||
|
functions.append((f"{module_name}.{name}", name))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка загрузки модуля {module_name}: {e}")
|
||||||
|
|
||||||
|
return functions
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
def execute_function(function_path):
|
||||||
|
"""
|
||||||
|
Выполняет функцию по указанному пути.
|
||||||
|
"""
|
||||||
|
module_name, func_name = function_path.rsplit(".", 1)
|
||||||
|
module = importlib.import_module(module_name)
|
||||||
|
func = getattr(module, func_name)
|
||||||
|
return func()
|
||||||
3
scheduler/views.py
Normal file
3
scheduler/views.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
@@ -44,6 +44,7 @@ INSTALLED_APPS = [
|
|||||||
'pms_integration',
|
'pms_integration',
|
||||||
'hotels',
|
'hotels',
|
||||||
'users',
|
'users',
|
||||||
|
'scheduler'
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
|||||||
Reference in New Issue
Block a user