bnovo plugin
scheduller
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -9,3 +9,6 @@ __pycache__
|
||||
node_modules
|
||||
package-lock.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 telegram.ext import Application
|
||||
from bot.utils.bot_setup import setup_bot
|
||||
from bot.utils.scheduler import setup_scheduler
|
||||
from dotenv import load_dotenv
|
||||
from bot.operations.users import show_users
|
||||
|
||||
# Загрузка переменных окружения
|
||||
load_dotenv()
|
||||
from scheduler.tasks import load_tasks_to_scheduler
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Запуск Telegram бота"
|
||||
help = "Запуск Telegram бота и планировщика"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
print("Запуск Telegram бота...")
|
||||
|
||||
# Настройка Django окружения
|
||||
# Установка Django окружения
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "touchh.settings")
|
||||
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")
|
||||
if not bot_token:
|
||||
raise ValueError("Токен бота не найден в переменных окружения.")
|
||||
application = Application.builder().token(bot_token).build()
|
||||
|
||||
# Настройка бота и обработчиков
|
||||
setup_bot(application)
|
||||
|
||||
# Основная асинхронная функция
|
||||
async def main():
|
||||
print("Настройка планировщика...")
|
||||
scheduler = setup_scheduler()
|
||||
scheduler.start()
|
||||
|
||||
await application.initialize()
|
||||
await application.start()
|
||||
await application.updater.start_polling()
|
||||
self.stdout.write(self.style.SUCCESS("Telegram бот и планировщик успешно запущены."))
|
||||
try:
|
||||
print("Инициализация Telegram бота...")
|
||||
await application.initialize() # Инициализация приложения
|
||||
print("Бот запущен. Ожидание сообщений...")
|
||||
await application.start() # Запуск приложения
|
||||
await application.updater.start_polling() # Запуск обработки сообщений
|
||||
|
||||
# Бесконечный цикл для удержания приложения активным
|
||||
while True:
|
||||
await asyncio.sleep(3600) # Ожидание 1 час
|
||||
except Exception as e:
|
||||
print(f"Ошибка во время работы бота: {e}")
|
||||
finally:
|
||||
print("Остановка Telegram бота...")
|
||||
await application.stop() # Завершаем приложение перед shutdown
|
||||
print("Остановка планировщика...")
|
||||
scheduler.shutdown(wait=False)
|
||||
print("Планировщик остановлен.")
|
||||
await asyncio.sleep(3600)
|
||||
except asyncio.CancelledError:
|
||||
await application.stop()
|
||||
scheduler.shutdown()
|
||||
|
||||
# Запуск асинхронной программы
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except RuntimeError as e:
|
||||
if str(e) == "This event loop is already running":
|
||||
print("Цикл событий уже запущен. Используем другой подход для запуска.")
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(main())
|
||||
else:
|
||||
raise
|
||||
loop.run_until_complete(main())
|
||||
except KeyboardInterrupt:
|
||||
self.stdout.write(self.style.ERROR("Завершение работы Telegram бота и планировщика"))
|
||||
finally:
|
||||
loop.close()
|
||||
|
||||
@@ -138,18 +138,18 @@ async def check_pms(update, context):
|
||||
|
||||
# Создаем экземпляр PMSIntegrationManager
|
||||
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)()
|
||||
|
||||
# Проверяем, какой способ интеграции использовать
|
||||
if hasattr(pms_manager.plugin, 'fetch_data'):
|
||||
if hasattr(pms_manager.plugin, 'fetch_data') and callable(pms_manager.plugin.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:
|
||||
# Используем прямой запрос к API
|
||||
from pms_integration.api_client import APIClient
|
||||
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:
|
||||
# Если подходящий способ не найден
|
||||
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} завершена успешно.")
|
||||
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):
|
||||
"""Настроить номера отеля."""
|
||||
|
||||
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,
|
||||
UserHotel,
|
||||
APIConfiguration,
|
||||
APIRequestLog,
|
||||
Reservation,
|
||||
Guest,
|
||||
FraudLog
|
||||
)
|
||||
from django.urls import path
|
||||
@@ -69,22 +67,6 @@ class UserHotelAdmin(admin.ModelAdmin):
|
||||
# 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)
|
||||
class ReservationAdmin(admin.ModelAdmin):
|
||||
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',)
|
||||
|
||||
|
||||
@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."""
|
||||
import os
|
||||
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():
|
||||
|
||||
@@ -8,52 +8,33 @@ from django.shortcuts import render
|
||||
from django import forms
|
||||
from pms_integration.models import PMSConfiguration, PMSIntegrationLog
|
||||
|
||||
|
||||
class PMSConfigurationForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = PMSConfiguration
|
||||
fields = "__all__"
|
||||
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()]
|
||||
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)
|
||||
class PMSConfigurationAdmin(admin.ModelAdmin):
|
||||
form = PMSConfigurationForm
|
||||
list_display = ('name', 'plugin_name', 'created_at', 'check_plugins_button')
|
||||
search_fields = ('name', 'description')
|
||||
list_filter = ('created_at',)
|
||||
list_display = ('name', 'plugin_name', 'created_at')
|
||||
search_fields = ('name', 'plugin_name')
|
||||
ordering = ('-created_at',)
|
||||
|
||||
def get_urls(self):
|
||||
"""Добавляем 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):
|
||||
"""Проверка и отображение плагинов."""
|
||||
def save_model(self, request, obj, form, change):
|
||||
# Проверка на наличие плагина
|
||||
plugins = PluginLoader.load_plugins()
|
||||
plugin_details = [
|
||||
{"name": plugin_name, "doc": plugins[plugin_name].__doc__ or "Нет документации"}
|
||||
for plugin_name in plugins
|
||||
]
|
||||
context = {
|
||||
"title": "Проверка плагинов",
|
||||
"plugin_details": plugin_details,
|
||||
}
|
||||
return render(request, "admin/check_plugins.html", context)
|
||||
if obj.plugin_name and obj.plugin_name not in plugins.keys():
|
||||
raise ValueError(f"Выберите корректный плагин. '{obj.plugin_name}' нет среди допустимых значений.")
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
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)
|
||||
class PMSIntegrationLogAdmin(admin.ModelAdmin):
|
||||
|
||||
@@ -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:
|
||||
PLUGIN_PATH = Path(__file__).parent / "plugins"
|
||||
print("Путь к папке плагинов:", PLUGIN_PATH.resolve())
|
||||
print("Содержимое папки:", list(PLUGIN_PATH.iterdir()))
|
||||
|
||||
@staticmethod
|
||||
def load_plugins():
|
||||
plugins = {}
|
||||
if not PluginLoader.PLUGIN_PATH.exists():
|
||||
print("Папка с плагинами не существует:", PluginLoader.PLUGIN_PATH)
|
||||
return plugins
|
||||
|
||||
print("Загрузка плагинов:")
|
||||
for file in os.listdir(PluginLoader.PLUGIN_PATH):
|
||||
if file.endswith("_pms.py") and not file.startswith("__"):
|
||||
print(f" Plugin {file}")
|
||||
module_name = f"pms_integration.plugins.{file[:-3]}"
|
||||
try:
|
||||
module = importlib.import_module(module_name)
|
||||
for attr in dir(module):
|
||||
cls = getattr(module, attr)
|
||||
if isinstance(cls, type) and issubclass(cls, BasePMSPlugin) and cls is not BasePMSPlugin:
|
||||
plugins[cls.__name__] = cls
|
||||
print(f"Загружен плагин: {cls.__name__}")
|
||||
plugin_name = file[:-7] # Убираем `_pms` из имени файла
|
||||
print(f" Загружен плагин {plugin_name}: {cls.__name__}")
|
||||
plugins[plugin_name] = cls
|
||||
except Exception as e:
|
||||
print(f"Ошибка при загрузке модуля {module_name}: {e}")
|
||||
print(f"Итоговый список плагинов: {list(plugins.keys())}")
|
||||
print(f" Ошибка загрузки плагина {module_name}: {e}")
|
||||
return plugins
|
||||
|
||||
|
||||
class PMSIntegrationManager:
|
||||
def __init__(self, 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")
|
||||
url = models.URLField(verbose_name="URL API")
|
||||
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="Логин")
|
||||
password = models.CharField(max_length=255, blank=True, null=True, verbose_name="Пароль")
|
||||
plugin_name = models.CharField(max_length=255, verbose_name="Название плагина")
|
||||
created_at = models.DateTimeField(auto_now_add=True, 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="Дата создания")
|
||||
|
||||
|
||||
def __str__(self):
|
||||
|
||||
@@ -1,71 +1,136 @@
|
||||
import requests
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from .base_plugin import BasePMSPlugin
|
||||
from asgiref.sync import sync_to_async
|
||||
from pms_integration.models import PMSConfiguration # Убедитесь, что модель существует
|
||||
|
||||
|
||||
class BnovoPMS(BasePMSPlugin):
|
||||
"""
|
||||
Плагин для интеграции с Bnovo.
|
||||
"""
|
||||
json_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "integer"},
|
||||
"number": {"type": "integer"},
|
||||
"roomTypeName": {"type": "string"},
|
||||
"checkInStatus": {"type": "string"},
|
||||
"guests": {"type": "array"},
|
||||
},
|
||||
"required": ["id", "number", "roomTypeName", "checkInStatus", "guests"]
|
||||
}
|
||||
class BnovoPMSPlugin(BasePMSPlugin):
|
||||
"""Плагин для работы с PMS Bnovo."""
|
||||
|
||||
def __init__(self, config):
|
||||
super().__init__(config)
|
||||
self.api_url = config.url.rstrip("/") # Убираем лишний `/` в конце URL
|
||||
self.username = config.username
|
||||
self.password = config.password
|
||||
self.token = None # SID
|
||||
|
||||
if not self.api_url:
|
||||
raise ValueError("Не указан URL для работы плагина.")
|
||||
if not self.username or not self.password:
|
||||
raise ValueError("Не указаны логин или пароль для авторизации.")
|
||||
|
||||
def get_default_parser_settings(self):
|
||||
"""
|
||||
Возвращает настройки парсера по умолчанию.
|
||||
"""
|
||||
"""Возвращает настройки по умолчанию для обработки данных."""
|
||||
return {
|
||||
"field_mapping": {
|
||||
"room_name": "roomNumber",
|
||||
"check_in": "from",
|
||||
"check_out": "until",
|
||||
},
|
||||
"date_format": "%Y-%m-%dT%H:%M:%S"
|
||||
"date_format": "%Y-%m-%dT%H:%M:%S",
|
||||
"timezone": "UTC"
|
||||
}
|
||||
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()
|
||||
|
||||
# Проверка структуры
|
||||
expected_fields = self.pms_config.parser_settings.get("fields_mapping", {})
|
||||
for field in expected_fields.values():
|
||||
if field not in data[0]: # Проверяем первую запись
|
||||
raise ValueError(f"Поле {field} отсутствует в ответе API.")
|
||||
async def _save_token_to_db(self, sid):
|
||||
"""Сохраняет токен (SID) в базу данных."""
|
||||
try:
|
||||
await sync_to_async(PMSConfiguration.objects.update_or_create)(
|
||||
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):
|
||||
"""Создает заголовки авторизации."""
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if self.token:
|
||||
headers["Cookie"] = f"SID={self.token}"
|
||||
return headers
|
||||
|
||||
def fetch_and_parse(self):
|
||||
response = requests.get(
|
||||
self.pms_config.url,
|
||||
headers={"Authorization": f"Bearer {self.pms_config.token}"}
|
||||
)
|
||||
self.validate_response(response) # Проверка соответствия структуры
|
||||
if response.status_code != 200:
|
||||
raise ValueError(f"Ошибка запроса к PMS Bnovo: {response.text}")
|
||||
async def _fetch_session(self):
|
||||
"""Получает идентификатор сессии (SID) через запрос."""
|
||||
url = f"{self.api_url}/"
|
||||
payload = {
|
||||
"username": self.username,
|
||||
"password": self.password,
|
||||
}
|
||||
|
||||
data = response.json()
|
||||
parsed_data = self.parse_data(data)
|
||||
return parsed_data
|
||||
print(f"[DEBUG] URL авторизации: {url}")
|
||||
print(f"[DEBUG] Тело запроса: {json.dumps(payload, indent=2)}")
|
||||
headers = self._get_auth_headers()
|
||||
|
||||
def parse_data(self, data):
|
||||
# Пример разбора данных на основе JSON-маски
|
||||
reservations = []
|
||||
for item in data["reservations"]:
|
||||
reservation = {
|
||||
"id": item["id"],
|
||||
"room_number": item["roomNumber"],
|
||||
"check_in": item["checkIn"],
|
||||
"check_out": item["checkOut"],
|
||||
"status": item["status"],
|
||||
}
|
||||
reservations.append(reservation)
|
||||
return reservations
|
||||
session = requests.Session()
|
||||
response = session.post(url, json=payload, headers=headers, allow_redirects=False)
|
||||
|
||||
print(f"[DEBUG] Статус ответа: {response.status_code}")
|
||||
print(f"[DEBUG] Ответ заголовков: {response.headers}")
|
||||
print(f"[DEBUG] Cookies: {session.cookies}")
|
||||
|
||||
if response.status_code == 302 and "SID" in session.cookies:
|
||||
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 json
|
||||
from datetime import datetime
|
||||
from hotels.models import Reservation
|
||||
# import requests
|
||||
# import hashlib
|
||||
# import json
|
||||
# from .base_plugin import BasePMSPlugin
|
||||
# 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):
|
||||
"""
|
||||
Плагин для взаимодействия с RealtyCalendar.
|
||||
"""Плагин для импорта данных из системы RealtyCalendar
|
||||
"""
|
||||
def __init__(self, config):
|
||||
super().__init__(config)
|
||||
self.public_key = config.token # Используем `token` как публичный ключ
|
||||
self.private_key = config.password # Используем `password` как приватный ключ
|
||||
self.base_url = config.url
|
||||
self.public_key = config.public_key
|
||||
self.private_key = config.private_key
|
||||
self.api_url = config.url.rstrip("/")
|
||||
|
||||
def generate_sign(self, params):
|
||||
"""
|
||||
Генерация подписи запроса.
|
||||
: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()
|
||||
if not self.public_key or not self.private_key:
|
||||
raise ValueError("Публичный или приватный ключ отсутствует для RealtyCalendar")
|
||||
|
||||
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:
|
||||
start_date = datetime.now().strftime('%Y-%m-%d')
|
||||
if not end_date:
|
||||
end_date = (datetime.now() + timedelta(days=1)).strftime('%Y-%m-%d')
|
||||
|
||||
params = {
|
||||
'begin_date': start_date,
|
||||
'end_date': end_date,
|
||||
return {
|
||||
"date_format": "%Y-%m-%dT%H:%M:%S",
|
||||
"timezone": "UTC"
|
||||
}
|
||||
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 = {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
"Accept": "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()
|
||||
return data.get('bookings', [])
|
||||
print(f"[DEBUG] Даты выборки: {data}")
|
||||
|
||||
@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:
|
||||
Reservation.objects.update_or_create(
|
||||
external_id=booking['id'],
|
||||
defaults={
|
||||
'check_in': booking['begin_date'],
|
||||
'check_out': booking['end_date'],
|
||||
'amount': booking['amount'],
|
||||
'notes': booking.get('notes', ''),
|
||||
'guest_name': booking['client']['fio'],
|
||||
'guest_phone': booking['client']['phone'],
|
||||
},
|
||||
)
|
||||
from hotels.models import Reservation, Hotel
|
||||
|
||||
hotel = await sync_to_async(Hotel.objects.get)(id=hotel_id)
|
||||
print(f"[DEBUG] Загружен отель: {hotel.name}")
|
||||
|
||||
for item in data:
|
||||
print(f"[DEBUG] Обработка бронирования: {item}")
|
||||
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"[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):
|
||||
"""
|
||||
Плагин для PMS Shelter Coud.
|
||||
"""
|
||||
def __init__(self, config):
|
||||
super().__init__(config)
|
||||
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
|
||||
aiohappyeyeballs==2.4.4
|
||||
aiohttp==3.11.10
|
||||
aiosignal==1.3.1
|
||||
anyio==4.6.2.post1
|
||||
APScheduler==3.11.0
|
||||
asgiref==3.8.1
|
||||
async-timeout==5.0.1
|
||||
attrs==24.2.0
|
||||
certifi==2024.8.30
|
||||
charset-normalizer==3.4.0
|
||||
Django==5.1.4
|
||||
@@ -9,26 +14,41 @@ django-filter==24.3
|
||||
django-jazzmin==3.0.1
|
||||
django-jet==1.0.8
|
||||
et_xmlfile==2.0.0
|
||||
exceptiongroup==1.2.2
|
||||
fpdf==1.7.2
|
||||
frozenlist==1.5.0
|
||||
geoip2==4.8.1
|
||||
h11==0.14.0
|
||||
httpcore==1.0.7
|
||||
httpx==0.28.0
|
||||
idna==3.10
|
||||
jsonschema==4.23.0
|
||||
jsonschema-specifications==2024.10.1
|
||||
maxminddb==2.6.2
|
||||
multidict==6.1.0
|
||||
numpy==2.1.3
|
||||
openpyxl==3.1.5
|
||||
pandas==2.2.3
|
||||
pathspec==0.12.1
|
||||
pillow==11.0.0
|
||||
propcache==0.2.1
|
||||
PyMySQL==1.1.1
|
||||
python-dateutil==2.9.0.post0
|
||||
python-dotenv==1.0.1
|
||||
python-telegram-bot==21.8
|
||||
pytz==2024.2
|
||||
PyYAML==6.0.2
|
||||
referencing==0.35.1
|
||||
requests==2.32.3
|
||||
rpds-py==0.22.3
|
||||
six==1.17.0
|
||||
sniffio==1.3.1
|
||||
sqlparse==0.5.2
|
||||
typing_extensions==4.12.2
|
||||
tzdata==2024.2
|
||||
tzlocal==5.2
|
||||
ua-parser==1.0.0
|
||||
ua-parser-builtins==0.18.0.post1
|
||||
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',
|
||||
'hotels',
|
||||
'users',
|
||||
'scheduler'
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
||||
Reference in New Issue
Block a user