Bot functionsa mainly operational

This commit is contained in:
2024-12-07 09:09:07 +09:00
parent 1d99c6b57c
commit e1c2ddbdb9
17 changed files with 1048 additions and 60 deletions

View File

@@ -1,29 +1,435 @@
from telegram import Update from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.ext import ContextTypes from telegram.ext import ContextTypes
from users.models import User from users.models import User
from hotels.models import Hotel from hotels.models import Hotel, UserHotel
from asgiref.sync import sync_to_async # Импортируем sync_to_async from users.models import NotificationSettings
from asgiref.sync import sync_to_async
import smtplib
from hotels.models import PMSIntegrationLog
import requests
from email.mime.text import MIMEText
from django.core.mail import send_mail
from datetime import datetime
# --- Вспомогательные функции ---
async def get_user_from_chat_id(chat_id):
"""Получение пользователя из базы по chat_id."""
return await sync_to_async(User.objects.filter(chat_id=chat_id).first)()
async def get_hotel_by_id(hotel_id):
"""Получение отеля по ID."""
return await sync_to_async(Hotel.objects.filter(id=hotel_id).first)()
# --- Обработчики команд ---
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Обработчик команды /start""" """Обработчик команды /start с проверкой chat_id"""
await update.message.reply_text("Привет! Я бот, работающий с Django. Используй /users или /hotels для проверки базы данных.") user_id = update.message.from_user.id
print(f"Пользователь {user_id} вызвал команду /start")
async def list_users(update: Update, context: ContextTypes.DEFAULT_TYPE): user = await get_user_from_chat_id(user_id)
"""Обработчик команды /users""" if user:
# Выполняем запрос к базе данных через sync_to_async keyboard = [
users = await sync_to_async(list)(User.objects.all()) # Преобразуем QuerySet в список [InlineKeyboardButton("📊 Статистика", callback_data="stats")],
if users: [InlineKeyboardButton("🏨 Управление отелями", callback_data="manage_hotels")],
user_list = "\n".join([f"{user.id}: {user.username}" for user in users]) [InlineKeyboardButton("👤 Пользователи", callback_data="manage_users")],
await update.message.reply_text(f"Список пользователей:\n{user_list}") [InlineKeyboardButton("⚙️ Настройки", callback_data="settings")],
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text("Выберите действие:", reply_markup=reply_markup)
else: else:
await update.message.reply_text("В базе данных нет пользователей.") print(f"Пользователь {user_id} не зарегистрирован.")
await update.message.reply_text("Вы не зарегистрированы в системе. Обратитесь к администратору.")
async def manage_hotels(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Вывод списка отелей, связанных с пользователем"""
query = update.callback_query
await query.answer()
user_id = query.from_user.id
print(f"Пользователь {user_id} выбрал 'Управление отелями'")
user = await get_user_from_chat_id(user_id)
if not user:
print(f"Пользователь {user_id} не зарегистрирован.")
await query.edit_message_text("Вы не зарегистрированы в системе.")
return
user_hotels = await sync_to_async(list)(
UserHotel.objects.filter(user=user).select_related("hotel")
)
if not user_hotels:
print(f"У пользователя {user_id} нет связанных отелей.")
await query.edit_message_text("У вас нет связанных отелей.")
return
keyboard = [
[InlineKeyboardButton(f"🏨 {hotel.hotel.name}", callback_data=f"hotel_{hotel.hotel.id}")]
for hotel in user_hotels
]
reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text("Выберите отель:", reply_markup=reply_markup)
# --- Обработчики кнопок ---
async def handle_button_click(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Обработчик всех нажатий кнопок"""
query = update.callback_query
print(f"Обработчик кнопок: Получен callback_data = {query.data}")
if query.data == "manage_hotels":
await manage_hotels(update, context)
elif query.data.startswith("hotel_"):
await hotel_actions(update, context)
elif query.data.startswith("delete_hotel_"):
await delete_hotel(update, context)
elif query.data.startswith("check_pms_"):
await check_pms(update, context)
elif query.data.startswith("setup_rooms_"):
await setup_rooms(update, context)
elif query.data == "settings":
await settings_menu(update, context)
elif query.data == "toggle_telegram":
await toggle_telegram(update, context)
elif query.data == "toggle_email":
await toggle_email(update, context)
elif query.data == "set_notification_time":
await set_notification_time(update, context)
elif query.data == "current_settings":
await show_current_settings(update, context)
async def list_hotels(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Обработчик команды /hotels"""
# Выполняем запрос к базе данных через sync_to_async
hotels = await sync_to_async(list)(Hotel.objects.all()) # Преобразуем QuerySet в список
if hotels:
hotel_list = "\n".join([f"{hotel.id}: {hotel.name}" for hotel in hotels])
await update.message.reply_text(f"Список отелей:\n{hotel_list}")
else: else:
await update.message.reply_text("В базе данных нет отелей.") print(f"Неизвестный callback_data: {query.data}")
await query.edit_message_text("Команда не распознана.")
async def hotel_actions(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Обработчик действий для выбранного отеля"""
query = update.callback_query
await query.answer()
hotel_id = int(query.data.split("_")[1])
print(f"Пользователь {query.from_user.id} выбрал отель с ID {hotel_id}")
hotel = await get_hotel_by_id(hotel_id)
if not hotel:
print(f"Отель с ID {hotel_id} не найден.")
await query.edit_message_text("Отель не найден.")
return
keyboard = [
[InlineKeyboardButton("🗑️ Удалить отель", callback_data=f"delete_hotel_{hotel_id}")],
[InlineKeyboardButton("🔗 Проверить интеграцию с PMS", callback_data=f"check_pms_{hotel_id}")],
[InlineKeyboardButton("🛏️ Настроить номера", callback_data=f"setup_rooms_{hotel_id}")],
]
reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text(f"Управление отелем: {hotel.name}", reply_markup=reply_markup)
async def delete_hotel(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Удаление отеля"""
query = update.callback_query
await query.answer()
hotel_id = int(query.data.split("_")[2])
print(f"Пользователь {query.from_user.id} выбрал удаление отеля с ID {hotel_id}")
hotel = await get_hotel_by_id(hotel_id)
if hotel:
hotel_name = hotel.name
await sync_to_async(hotel.delete)()
print(f"Отель {hotel_name} удалён.")
await query.edit_message_text(f"Отель {hotel_name} успешно удалён.")
else:
print(f"Отель с ID {hotel_id} не найден.")
await query.edit_message_text("Отель не найден.")
async def check_pms(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Проверить интеграцию с PMS"""
query = update.callback_query
await query.answer()
hotel_id = int(query.data.split("_")[2])
print(f"Пользователь {query.from_user.id} проверяет интеграцию PMS для отеля с ID {hotel_id}")
# Асинхронно получаем отель
hotel = await sync_to_async(Hotel.objects.select_related('api', 'pms').get)(id=hotel_id)
if not hotel:
print(f"Отель с ID {hotel_id} не найден.")
await query.edit_message_text("Отель не найден.")
return
# Асинхронно извлекаем связанные данные
api_name = hotel.api.name if hotel.api else "Не настроен"
pms_name = hotel.pms.name if hotel.pms else "Не указана"
# Формируем сообщение
status_message = (
f"Отель: {hotel.name}\n"
f"PMS система: {pms_name}\n"
f"API: {api_name}"
)
await query.edit_message_text(status_message)
async def setup_rooms(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Настроить номера отеля"""
query = update.callback_query
await query.answer()
hotel_id = int(query.data.split("_")[2])
print(f"Пользователь {query.from_user.id} настраивает номера для отеля с ID {hotel_id}")
hotel = await get_hotel_by_id(hotel_id)
if not hotel:
print(f"Отель с ID {hotel_id} не найден.")
await query.edit_message_text("Отель не найден.")
return
await query.edit_message_text(f"Настройка номеров для отеля: {hotel.name}")
async def settings_menu(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Меню настроек уведомлений."""
query = update.callback_query
await query.answer()
user_id = query.from_user.id
user = await get_user_from_chat_id(user_id)
if not user:
await query.edit_message_text("Вы не зарегистрированы.")
return
settings, _ = await sync_to_async(NotificationSettings.objects.get_or_create)(user=user)
telegram_status = "" if settings.telegram_enabled else ""
email_status = "" if settings.email_enabled else ""
keyboard = [
[InlineKeyboardButton(f"{telegram_status} Уведомления в Telegram", callback_data="toggle_telegram")],
[InlineKeyboardButton(f"{email_status} Уведомления по Email", callback_data="toggle_email")],
[InlineKeyboardButton("🕒 Настроить время уведомлений", callback_data="set_notification_time")],
[InlineKeyboardButton("📋 Показать текущие настройки", callback_data="current_settings")],
]
reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text("Настройки уведомлений:", reply_markup=reply_markup)
async def toggle_telegram(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Переключение состояния Telegram-уведомлений."""
query = update.callback_query
await query.answer()
user_id = query.from_user.id
user = await get_user_from_chat_id(user_id)
if not user:
await query.edit_message_text("Вы не зарегистрированы.")
return
settings, _ = await sync_to_async(NotificationSettings.objects.get_or_create)(user=user)
settings.telegram_enabled = not settings.telegram_enabled
await sync_to_async(settings.save)()
print(f"Пользователь {user_id} переключил Telegram-уведомления: {settings.telegram_enabled}")
await settings_menu(update, context)
async def toggle_email(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Переключение состояния Email-уведомлений."""
query = update.callback_query
await query.answer()
user_id = query.from_user.id
user = await get_user_from_chat_id(user_id)
if not user:
await query.edit_message_text("Вы не зарегистрированы.")
return
settings, _ = await sync_to_async(NotificationSettings.objects.get_or_create)(user=user)
settings.email_enabled = not settings.email_enabled
await sync_to_async(settings.save)()
print(f"Пользователь {user_id} переключил Email-уведомления: {settings.email_enabled}")
await settings_menu(update, context)
async def set_notification_time(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Настройка времени уведомлений."""
query = update.callback_query
await query.answer()
user_id = query.from_user.id
user = await get_user_from_chat_id(user_id)
if not user:
await query.edit_message_text("Вы не зарегистрированы.")
return
await query.edit_message_text("Введите новое время для уведомлений в формате HH:MM (например, 08:30):")
context.user_data["set_time"] = True
print(f"Пользователь {user_id} настраивает время уведомлений.")
async def handle_notification_time(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Обработка ввода времени уведомлений."""
if context.user_data.get("set_time"):
user_id = update.message.from_user.id
new_time = update.message.text
try:
hour, minute = map(int, new_time.split(":"))
user = await get_user_from_chat_id(user_id)
if user:
settings, _ = await sync_to_async(NotificationSettings.objects.get_or_create)(user=user)
settings.notification_time = f"{hour:02}:{minute:02}"
await sync_to_async(settings.save)()
print(f"Пользователь {user_id} установил новое время уведомлений: {new_time}")
await update.message.reply_text(f"Время уведомлений обновлено на {new_time}.")
except ValueError:
print(f"Пользователь {user_id} ввёл некорректное время: {new_time}")
await update.message.reply_text("Неверный формат. Введите время в формате HH:MM.")
finally:
context.user_data["set_time"] = False
async def settings_menu(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Меню настроек уведомлений."""
query = update.callback_query
await query.answer()
user_id = query.from_user.id
user = await get_user_from_chat_id(user_id)
if not user:
await query.edit_message_text("Вы не зарегистрированы.")
return
settings, _ = await sync_to_async(NotificationSettings.objects.get_or_create)(user=user)
telegram_status = "" if settings.telegram_enabled else ""
email_status = "" if settings.email_enabled else ""
keyboard = [
[InlineKeyboardButton(f"{telegram_status} Уведомления в Telegram", callback_data="toggle_telegram")],
[InlineKeyboardButton(f"{email_status} Уведомления по Email", callback_data="toggle_email")],
[InlineKeyboardButton("🕒 Настроить время уведомлений", callback_data="set_notification_time")],
]
reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text("Настройки уведомлений:", reply_markup=reply_markup)
async def send_telegram_notification(user, message):
"""Отправка уведомления через Telegram."""
# bot = Bot(token="ВАШ_ТОКЕН")
if user.chat_id:
try:
await bot.send_message(chat_id=user.chat_id, text=message)
print(f"Telegram-уведомление отправлено пользователю {user.chat_id}: {message}")
except Exception as e:
print(f"Ошибка отправки Telegram-уведомления пользователю {user.chat_id}: {e}")
def send_email_notification(user, message):
"""Отправка уведомления через Email."""
if user.email:
try:
send_mail(
subject="Уведомление от системы",
message=message,
from_email="noreply@yourdomain.com",
recipient_list=[user.email],
fail_silently=False,
)
print(f"Email-уведомление отправлено на {user.email}: {message}")
except Exception as e:
print(f"Ошибка отправки Email-уведомления пользователю {user.email}: {e}")
async def schedule_notifications():
"""Планировщик уведомлений."""
print("Запуск планировщика уведомлений...")
now = datetime.now().strftime("%H:%M")
users = await sync_to_async(list)(User.objects.all())
for user in users:
settings, _ = await sync_to_async(NotificationSettings.objects.get_or_create)(user=user)
if settings.notification_time == now:
message = "Это ваше уведомление от системы."
if settings.telegram_enabled:
await send_telegram_notification(user, message)
if settings.email_enabled:
send_email_notification(user, message)
async def show_current_settings(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Отображение текущих настроек уведомлений."""
query = update.callback_query
await query.answer()
user_id = query.from_user.id
user = await get_user_from_chat_id(user_id)
if not user:
await query.edit_message_text("Вы не зарегистрированы.")
return
settings, _ = await sync_to_async(NotificationSettings.objects.get_or_create)(user=user)
telegram_status = "✅ Включены" if settings.telegram_enabled else "❌ Выключены"
email_status = "✅ Включены" if settings.email_enabled else "❌ Выключены"
notification_time = settings.notification_time or "Не установлено"
message = (
f"📋 Ваши настройки уведомлений:\n"
f"🔔 Telegram: {telegram_status}\n"
f"📧 Email: {email_status}\n"
f"🕒 Время: {notification_time}"
)
await query.edit_message_text(message)
async def check_pms_integration(update: Update, context: ContextTypes.DEFAULT_TYPE):
query = update.callback_query
await query.answer()
hotel_id = int(query.data.split("_")[2])
hotel = await sync_to_async(Hotel.objects.get)(id=hotel_id)
pms_settings = hotel.pms.parser_settings # Настройки из связанной PMSConfiguration
try:
# Выполняем запрос к PMS
response = requests.post(
url=pms_settings["url"],
headers={
"Authorization": f"Bearer {hotel.api.api_key}",
"Content-Type": "application/json",
},
json={
"from": "2024-01-01T00:00:00Z",
"until": "2024-01-10T00:00:00Z",
"pagination": {"from": 0, "count": 10},
},
)
# Проверяем результат
if response.status_code == 200:
await sync_to_async(PMSIntegrationLog.objects.create)(
hotel=hotel,
status="success",
message="Интеграция успешно проверена.",
)
await query.edit_message_text(f"Интеграция с PMS для отеля '{hotel.name}' успешна.")
else:
await sync_to_async(PMSIntegrationLog.objects.create)(
hotel=hotel,
status="error",
message=f"Ошибка: {response.status_code}",
)
await query.edit_message_text(f"Ошибка интеграции с PMS для отеля '{hotel.name}': {response.status_code}")
except Exception as e:
await sync_to_async(PMSIntegrationLog.objects.create)(
hotel=hotel,
status="error",
message=str(e),
)
await query.edit_message_text(f"Произошла ошибка: {str(e)}")

View File

@@ -1,28 +1,97 @@
import os import os
import django import django
import asyncio
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, CommandHandler from telegram.ext import (
from bot.handlers import start, list_users, list_hotels # Импорт обработчиков Application,
CommandHandler,
CallbackQueryHandler,
MessageHandler,
filters,
)
from bot.handlers import (
start,
handle_button_click,
manage_hotels,
hotel_actions,
delete_hotel,
check_pms,
setup_rooms,
settings_menu,
toggle_telegram,
toggle_email,
set_notification_time,
handle_notification_time,
schedule_notifications,
show_current_settings,
)
# Настройка Django окружения # Настройка Django окружения
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'touchh.settings') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'touchh.settings')
django.setup() django.setup()
def main():
# Создаём приложение Telegram
application = Application.builder().token("8125171867:AAGxDcSpQxJy3_pmq3TDBWtqaAVCj7b-F5k").build()
# Регистрируем обработчики команд async def start_bot(application):
"""Настройка и запуск Telegram бота."""
print("Настройка Telegram приложения...")
# Регистрация обработчиков команд
print("Регистрация обработчиков команд...")
application.add_handler(CommandHandler("start", start)) application.add_handler(CommandHandler("start", start))
application.add_handler(CommandHandler("users", list_users))
application.add_handler(CommandHandler("hotels", list_hotels))
# Запускаем бота # Регистрация обработчиков кнопок
application.run_polling() print("Регистрация обработчиков кнопок...")
application.add_handler(CallbackQueryHandler(handle_button_click))
application.add_handler(CallbackQueryHandler(manage_hotels, pattern="^manage_hotels$"))
application.add_handler(CallbackQueryHandler(settings_menu, pattern="^settings$"))
application.add_handler(CallbackQueryHandler(toggle_telegram, pattern="^toggle_telegram$"))
application.add_handler(CallbackQueryHandler(toggle_email, pattern="^toggle_email$"))
application.add_handler(CallbackQueryHandler(set_notification_time, pattern="^set_notification_time$"))
application.add_handler(CallbackQueryHandler(show_current_settings, pattern="^current_settings$"))
application.add_handler(CallbackQueryHandler(hotel_actions, pattern="^hotel_"))
application.add_handler(CallbackQueryHandler(delete_hotel, pattern="^delete_hotel_"))
application.add_handler(CallbackQueryHandler(check_pms, pattern="^check_pms_"))
application.add_handler(CallbackQueryHandler(setup_rooms, pattern="^setup_rooms_"))
# Регистрация обработчиков текстовых сообщений
print("Регистрация обработчиков текстовых сообщений...")
application.add_handler(MessageHandler(filters.TEXT & filters.ChatType.PRIVATE, handle_notification_time))
# Настройка планировщика
print("Настройка планировщика уведомлений...")
scheduler = AsyncIOScheduler()
scheduler.add_job(schedule_notifications, "cron", minute="*")
scheduler.start()
# Запуск бота
print("Запуск Telegram бота...")
await application.initialize()
await application.start()
print("Бот успешно запущен. Ожидание событий...")
await application.updater.start_polling()
class Command(BaseCommand): class Command(BaseCommand):
help = "Запуск Telegram бота" help = "Запуск Telegram бота"
def handle(self, *args, **options): def handle(self, *args, **options):
self.stdout.write("Запуск Telegram бота...") print("Запуск Telegram бота...")
main()
# Получаем текущий цикл событий
loop = asyncio.get_event_loop()
# Создаём экземпляр приложения
application = Application.builder().token("8125171867:AAGxDcSpQxJy3_pmq3TDBWtqaAVCj7b-F5k").build()
# Добавляем задачу для запуска бота
loop.create_task(start_bot(application))
# Запускаем цикл событий
try:
loop.run_forever()
except KeyboardInterrupt:
print("Остановка Telegram бота...")
loop.run_until_complete(application.stop())
scheduler = AsyncIOScheduler()
scheduler.shutdown(wait=False)

View File

@@ -1,12 +1,23 @@
from django.contrib import admin from django.contrib import admin
from .models import Hotel, UserHotel from .models import Hotel, UserHotel, APIConfiguration, APIRequestLog, PMSConfiguration, PMSIntegrationLog
from django import forms
class HotelForm(forms.ModelForm):
class Meta:
model = Hotel
fields = "__all__"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Исключаем API, которые уже связаны с другими отелями
used_apis = Hotel.objects.exclude(api__isnull=True).values_list('api', flat=True)
self.fields['api'].queryset = APIConfiguration.objects.exclude(id__in=used_apis)
@admin.register(Hotel)
class HotelAdmin(admin.ModelAdmin): class HotelAdmin(admin.ModelAdmin):
list_display = ('name', 'pms_type', 'created_at') form = HotelForm
list_display = ('name', 'api', 'created_at', 'pms')
search_fields = ('name',) search_fields = ('name',)
list_filter = ('pms_type',)
ordering = ('-created_at',) admin.site.register(Hotel, HotelAdmin)
@admin.register(UserHotel) @admin.register(UserHotel)
class UserHotelAdmin(admin.ModelAdmin): class UserHotelAdmin(admin.ModelAdmin):
@@ -14,4 +25,32 @@ class UserHotelAdmin(admin.ModelAdmin):
search_fields = ('user', 'hotel') search_fields = ('user', 'hotel')
list_filter = ('hotel',) list_filter = ('hotel',)
ordering = ('-hotel',) ordering = ('-hotel',)
@admin.register(APIConfiguration)
class ApiConfigurationAdmin(admin.ModelAdmin):
list_display = ('name', 'url', 'token', 'username', 'password')
search_fields = ('name', 'url', 'token', 'username', 'password')
list_filter = ('name', 'url', 'token', 'username', 'password')
ordering = ('-name',)
@admin.register(APIRequestLog)
class ApiRequestLogAdmin(admin.ModelAdmin):
list_display = ('api', 'request_time', 'response_status', 'response_data')
search_fields = ('api', 'request_time', 'response_status', 'response_data')
list_filter = ('api', 'request_time', 'response_status', 'response_data')
ordering = ('-api',)
@admin.register(PMSConfiguration)
class PMSConfigurationAdmin(admin.ModelAdmin):
list_display = ('name', 'parser_settings', 'description')
@admin.register(PMSIntegrationLog)
class PMSIntegreationLogAdmin(admin.ModelAdmin):
list_display = ('hotel', 'checked_at', 'status', 'message')
search_fields = ('hotel', 'checked_at', 'status', 'message')
list_filter = ('hotel', 'checked_at', 'status', 'message')
ordering = ('-checked_at',)

View File

@@ -0,0 +1,26 @@
# Generated by Django 5.1.4 on 2024-12-06 13:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0003_alter_hotel_options_alter_userhotel_options_and_more'),
]
operations = [
migrations.CreateModel(
name='DataLog',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Название')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('data', models.JSONField(verbose_name='Данные')),
],
options={
'verbose_name': 'Лог данных',
'verbose_name_plural': 'Логи данных',
},
),
]

View File

@@ -0,0 +1,47 @@
# Generated by Django 5.1.4 on 2024-12-06 13:51
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0004_datalog'),
]
operations = [
migrations.CreateModel(
name='APIConfiguration',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Название API')),
('url', models.URLField(verbose_name='URL API')),
('token', models.CharField(blank=True, max_length=255, null=True, verbose_name='Токен')),
('username', models.CharField(blank=True, max_length=255, null=True, verbose_name='Логин')),
('password', models.CharField(blank=True, max_length=255, null=True, verbose_name='Пароль')),
('last_updated', models.DateTimeField(auto_now=True, verbose_name='Дата последнего обновления')),
],
options={
'verbose_name': 'Конфигурация API',
'verbose_name_plural': 'Конфигурации API',
},
),
migrations.CreateModel(
name='APIRequestLog',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('request_time', models.DateTimeField(auto_now_add=True, verbose_name='Время запроса')),
('response_status', models.IntegerField(verbose_name='HTTP статус ответа')),
('response_data', models.JSONField(blank=True, null=True, verbose_name='Данные ответа')),
('api', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.apiconfiguration', verbose_name='API')),
],
options={
'verbose_name': 'Журнал запросов API',
'verbose_name_plural': 'Журналы запросов API',
},
),
migrations.DeleteModel(
name='DataLog',
),
]

View File

@@ -0,0 +1,45 @@
# Generated by Django 5.1.4 on 2024-12-06 14:09
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0005_apiconfiguration_apirequestlog_delete_datalog'),
]
operations = [
migrations.CreateModel(
name='PMSConfiguration',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Название PMS')),
('parser_settings', models.JSONField(default=dict, verbose_name='Настройки разбора данных')),
('description', models.TextField(blank=True, null=True, verbose_name='Описание')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
],
options={
'verbose_name': 'PMS система',
'verbose_name_plural': 'PMS системы',
},
),
migrations.RemoveField(
model_name='hotel',
name='api_key',
),
migrations.RemoveField(
model_name='hotel',
name='pms_type',
),
migrations.RemoveField(
model_name='hotel',
name='public_key',
),
migrations.AddField(
model_name='hotel',
name='api',
field=models.OneToOneField(blank=True, help_text='API, связанный с этим отелем.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='hotels.apiconfiguration', verbose_name='API'),
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 5.1.4 on 2024-12-06 23:02
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0006_pmsconfiguration_remove_hotel_api_key_and_more'),
]
operations = [
migrations.CreateModel(
name='PMSIntegrationLog',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('checked_at', models.DateTimeField(auto_now_add=True, verbose_name='Время проверки')),
('status', models.CharField(choices=[('success', 'Успех'), ('error', 'Ошибка')], max_length=50, verbose_name='Статус')),
('message', models.TextField(blank=True, null=True, verbose_name='Сообщение')),
('hotel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Отель')),
],
options={
'verbose_name': 'Журнал интеграции PMS',
'verbose_name_plural': 'Журналы интеграции PMS',
},
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.1.4 on 2024-12-06 23:38
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0007_pmsintegrationlog'),
]
operations = [
migrations.AddField(
model_name='hotel',
name='pms',
field=models.OneToOneField(blank=True, help_text='PMS система? используемая в заведении', null=True, on_delete=django.db.models.deletion.SET_NULL, to='hotels.pmsconfiguration', verbose_name='PMS система'),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.1.4 on 2024-12-06 23:57
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0008_hotel_pms'),
]
operations = [
migrations.AlterField(
model_name='hotel',
name='pms',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='hotels.pmsconfiguration', verbose_name='PMS система'),
),
]

View File

@@ -0,0 +1,33 @@
# Generated by Django 5.1.4 on 2024-12-07 00:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0009_alter_hotel_pms'),
]
operations = [
migrations.AddIndex(
model_name='apirequestlog',
index=models.Index(fields=['api'], name='hotels_apir_api_id_686bb0_idx'),
),
migrations.AddIndex(
model_name='apirequestlog',
index=models.Index(fields=['request_time'], name='hotels_apir_request_f65147_idx'),
),
migrations.AddIndex(
model_name='pmsintegrationlog',
index=models.Index(fields=['hotel'], name='hotels_pmsi_hotel_i_718b32_idx'),
),
migrations.AddIndex(
model_name='pmsintegrationlog',
index=models.Index(fields=['checked_at'], name='hotels_pmsi_checked_e7768d_idx'),
),
migrations.AddIndex(
model_name='pmsintegrationlog',
index=models.Index(fields=['status'], name='hotels_pmsi_status_dbf1d8_idx'),
),
]

View File

@@ -1,25 +1,72 @@
from django.db import models from django.db import models
from users.models import User from users.models import User
class Hotel(models.Model):
name = models.CharField(max_length=255, verbose_name="Название отеля") class PMSConfiguration(models.Model):
pms_type = models.CharField( name = models.CharField(max_length=255, verbose_name="Название PMS")
max_length=50, parser_settings = models.JSONField(default=dict, verbose_name="Настройки разбора данных")
choices=[('bnovo', 'Bnovo'), ('travelline', 'Travel Line')], description = models.TextField(blank=True, null=True, verbose_name="Описание")
verbose_name="PMS система" created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
)
api_key = models.CharField(max_length=255, blank=True, null=True, verbose_name="API ключ")
public_key = models.CharField(max_length=255, blank=True, null=True, verbose_name="Публичный ключ")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Создан")
def __str__(self): def __str__(self):
return self.name return self.name
class Meta:
verbose_name = "PMS система"
verbose_name_plural = "PMS системы"
class APIConfiguration(models.Model):
name = models.CharField(max_length=255, verbose_name="Название API")
url = models.URLField(verbose_name="URL API")
token = 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="Пароль")
last_updated = models.DateTimeField(auto_now=True, verbose_name="Дата последнего обновления")
def __str__(self):
return self.name
class Meta:
verbose_name = "Конфигурация API"
verbose_name_plural = "Конфигурации API"
class Hotel(models.Model):
name = models.CharField(max_length=255, verbose_name="Название отеля")
api = models.OneToOneField(
APIConfiguration,
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name="API",
help_text="API, связанный с этим отелем."
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Создан")
pms = models.ForeignKey(PMSConfiguration, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="PMS система")
def __str__(self):
return self.name
class Meta: class Meta:
verbose_name = "Отель" verbose_name = "Отель"
verbose_name_plural = "Отели" verbose_name_plural = "Отели"
class PMSIntegrationLog(models.Model):
hotel = models.ForeignKey(Hotel, on_delete=models.CASCADE, verbose_name="Отель")
checked_at = models.DateTimeField(auto_now_add=True, verbose_name="Время проверки")
status = models.CharField(max_length=50, verbose_name="Статус", choices=[('success', 'Успех'), ('error', 'Ошибка')])
message = models.TextField(verbose_name="Сообщение", blank=True, null=True)
def __str__(self):
return f"{self.hotel.name} - {self.status} - {self.checked_at}"
class Meta:
verbose_name = "Журнал интеграции PMS"
verbose_name_plural = "Журналы интеграции PMS"
indexes = [
models.Index(fields=['hotel']),
models.Index(fields=['checked_at']),
models.Index(fields=['status']),
]
class UserHotel(models.Model): class UserHotel(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="Пользователь") user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="Пользователь")
@@ -31,4 +78,26 @@ class UserHotel(models.Model):
class Meta: class Meta:
verbose_name = "Пользователь отеля" verbose_name = "Пользователь отеля"
verbose_name_plural = "Пользователи отелей" verbose_name_plural = "Пользователи отелей"
from django.db import models
class APIRequestLog(models.Model):
api = models.ForeignKey(APIConfiguration, on_delete=models.CASCADE, verbose_name="API")
request_time = models.DateTimeField(auto_now_add=True, verbose_name="Время запроса")
response_status = models.IntegerField(verbose_name="HTTP статус ответа")
response_data = models.JSONField(verbose_name="Данные ответа", blank=True, null=True)
def __str__(self):
return f"{self.api.name} - {self.request_time}"
class Meta:
verbose_name = "Журнал запросов API"
verbose_name_plural = "Журналы запросов API"
indexes = [
models.Index(fields=['api']),
models.Index(fields=['request_time']),
]
from django.db import models

92
hotels/pms_check.py Normal file
View File

@@ -0,0 +1,92 @@
import os
import json
from datetime import datetime, timedelta
import requests
from django.db import models
from asgiref.sync import sync_to_async
class APIDataLogger:
"""Класс для работы с API, сохранения и обработки данных."""
def __init__(self, name, url, token=None, username=None, password=None):
self.name = name
self.url = url
self.token = token
self.username = username
self.password = password
self.output_dir = "modules"
def ensure_directory_exists(self, path):
"""Создать директорию, если она не существует."""
if not os.path.exists(path):
os.makedirs(path)
def fetch_data(self, additional_data=None):
"""Получить данные из API."""
headers = {"Content-Type": "application/json"}
data = additional_data or {}
if self.token:
data["token"] = self.token
response = requests.post(self.url, headers=headers, json=data, auth=(self.username, self.password) if self.username and self.password else None)
if response.status_code != 200:
print(f'{self.name}: API запрос не удался. Код статуса: {response.status_code}')
return []
return response.json()
def save_data(self, data, suffix):
"""Сохранить данные в файл JSON."""
now = datetime.now()
current_date = now.strftime('%Y-%m-%d')
directory = os.path.join(self.output_dir, current_date, self.name)
self.ensure_directory_exists(directory)
filename = f"{self.name} {suffix}.json"
filepath = os.path.join(directory, filename)
with open(filepath, 'w') as file:
json.dump(data, file)
return filepath
def load_previous_data(self, suffixes):
"""Загрузить данные из файлов за текущий и предыдущий интервалы."""
now = datetime.now()
current_date = now.strftime('%Y-%m-%d')
yesterday_date = (now - timedelta(days=1)).strftime('%Y-%m-%d')
directories = [(yesterday_date, "21"), (current_date, "9")] if 9 <= now.hour < 21 else [(current_date, "9"), (current_date, "21")]
data_combined = []
for date, suffix in directories:
filepath = os.path.join(self.output_dir, date, self.name, f"{self.name} {suffix}.json")
try:
with open(filepath, 'r') as file:
data_combined.extend(json.load(file))
except FileNotFoundError:
pass
return data_combined
def filter_data(self, data, filter_function):
"""Фильтрация данных с использованием переданной функции."""
return filter_function(data)
def process_and_save(self, additional_data=None, filter_function=None):
"""Основной процесс: запрос, сохранение, чтение, фильтрация."""
now = datetime.now()
suffix = "9" if 9 <= now.hour < 21 else "21"
# Шаг 1: Получить данные
raw_data = self.fetch_data(additional_data)
self.save_data(raw_data, suffix)
# Шаг 2: Загрузить данные за текущий и предыдущий интервал
combined_data = self.load_previous_data(["9", "21"])
# Шаг 3: Фильтрация
if filter_function:
combined_data = self.filter_data(combined_data, filter_function)
return combined_data

53
hotels/pms_parse.py Normal file
View File

@@ -0,0 +1,53 @@
import json
from datetime import datetime
def parse_pms_data(data, parser_settings):
date_format = parser_settings["date_format"]
fields_mapping = parser_settings["fields_mapping"]
conditions = parser_settings.get("conditions", {})
parsed_data = []
for record in data:
# Применение условий фильтрации
if conditions:
for condition_field, expected_value in conditions.items():
if record.get(condition_field) != expected_value:
break
else:
# Условие выполнено
pass
else:
# Условие отсутствует
pass
# Разбор полей
parsed_record = {}
for internal_field, external_field in fields_mapping.items():
if "." in external_field: # Например, "guests[0].lastName"
keys = external_field.split(".")
value = record
try:
for key in keys:
if key.endswith("]"): # Обработка индексов, например, "guests[0]"
key, index = key[:-1].split("[")
value = value[key][int(index)]
else:
value = value[key]
except (KeyError, IndexError, TypeError):
value = None
else:
value = record.get(external_field)
# Преобразование дат
if "date" in internal_field or "time" in internal_field:
try:
value = datetime.strptime(value, date_format)
except (ValueError, TypeError):
value = None
parsed_record[internal_field] = value
parsed_data.append(parsed_record)
return parsed_data

View File

@@ -87,17 +87,10 @@ DATABASES = {
'wordpress': { 'wordpress': {
'ENGINE': 'django.db.backends.mysql', 'ENGINE': 'django.db.backends.mysql',
'NAME': 'u1510415_wp832', 'NAME': 'u1510415_wp832',
<<<<<<< HEAD
'USER': 'root',
'PASSWORD': 'R0sebud',
'HOST': '0.0.0.0',
'PORT': '3308',
=======
'USER': 'u1510415_wp832', 'USER': 'u1510415_wp832',
'PASSWORD': 'yZ1gV6kH6lzD2cQ3', 'PASSWORD': 'yZ1gV6kH6lzD2cQ3',
'HOST': 'server231.hosting.reg.ru', 'HOST': 'server231.hosting.reg.ru',
'PORT': '3306', 'PORT': '3306',
>>>>>>> cea14d40d73e3a67c30dda8765458d91652e6bf4
}, },
} }
# Password validation # Password validation

View File

@@ -1,5 +1,5 @@
from django.contrib import admin from django.contrib import admin
from .models import User, UserConfirmation, UserActivityLog from .models import User, UserConfirmation, UserActivityLog, NotificationSettings
@admin.register(User) @admin.register(User)
class UserAdmin(admin.ModelAdmin): class UserAdmin(admin.ModelAdmin):
@@ -20,4 +20,11 @@ class UserActivityLogAdmin(admin.ModelAdmin):
search_fields = ('user_id', 'ip', 'datetime', 'agent', 'platform', 'version', 'model', 'device', 'UAString', 'location', 'page_id', 'url_parameters', 'page_title', 'type', 'last_counter', 'hits', 'honeypot', 'reply', 'page_url') search_fields = ('user_id', 'ip', 'datetime', 'agent', 'platform', 'version', 'model', 'device', 'UAString', 'location', 'page_id', 'url_parameters', 'page_title', 'type', 'last_counter', 'hits', 'honeypot', 'reply', 'page_url')
list_filter = ('page_title', 'user_id', 'ip') list_filter = ('page_title', 'user_id', 'ip')
ordering = ('-id',) ordering = ('-id',)
@admin.register(NotificationSettings)
class NotificationSettingsAdmin(admin.ModelAdmin):
list_display = ('user', 'email', 'telegram_enabled', 'email_enabled', 'notification_time')
search_fields = ('user', 'email', 'telegram_enabled', 'email_enabled', 'notification_time')
list_filter = ('user', 'email', 'telegram_enabled', 'email_enabled', 'notification_time')
ordering = ('-id',)

View File

@@ -0,0 +1,29 @@
# Generated by Django 5.1.4 on 2024-12-06 12:01
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0004_alter_user_options_alter_userconfirmation_options_and_more'),
]
operations = [
migrations.CreateModel(
name='NotificationSettings',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('telegram_enabled', models.BooleanField(default=True, verbose_name='Уведомления в Telegram')),
('email_enabled', models.BooleanField(default=False, verbose_name='Уведомления по Email')),
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Email для уведомлений')),
('notification_time', models.TimeField(default='09:00', verbose_name='Время отправки уведомлений')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='Пользователь')),
],
options={
'verbose_name': 'Способ оповещения',
'verbose_name_plural': 'Способы оповещений',
},
),
]

View File

@@ -112,4 +112,18 @@ class UserActivityLog(models.Model):
verbose_name_plural = 'Журналы активности' verbose_name_plural = 'Журналы активности'
def __str__(self): def __str__(self):
return f"User {self.user_id} - {self.type} - {self.date_time}" return f"User {self.user_id} - {self.type} - {self.date_time}"
class NotificationSettings(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name="Пользователь")
telegram_enabled = models.BooleanField(default=True, verbose_name="Уведомления в Telegram")
email_enabled = models.BooleanField(default=False, verbose_name="Уведомления по Email")
email = models.EmailField(blank=True, null=True, verbose_name="Email для уведомлений")
notification_time = models.TimeField(default="09:00", verbose_name="Время отправки уведомлений")
def __str__(self):
return f"Настройки уведомлений для {self.user.username}"
class Meta:
verbose_name = "Способ оповещения"
verbose_name_plural = "Способы оповещений"