RealtyCalendar plugin
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
import logging
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from urllib.parse import parse_qs
|
from urllib.parse import parse_qs
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
@@ -7,8 +6,8 @@ from hotels.models import Reservation, Hotel
|
|||||||
from .models import UserActivityLog, ViolationLog
|
from .models import UserActivityLog, ViolationLog
|
||||||
from touchh.utils.log import CustomLogger
|
from touchh.utils.log import CustomLogger
|
||||||
# Настройка логирования
|
# Настройка логирования
|
||||||
logging.basicConfig(level=logging.INFO)
|
logger = CustomLogger(__name__).get_logger()
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class ReservationChecker:
|
class ReservationChecker:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -202,63 +202,56 @@ class DataSyncManager:
|
|||||||
self.logger.error(f"Error fetching data: {e}")
|
self.logger.error(f"Error fetching data: {e}")
|
||||||
return []
|
return []
|
||||||
def update_sync_log(self, hotel, recieved_records, processed_records):
|
def update_sync_log(self, hotel, recieved_records, processed_records):
|
||||||
|
"""
|
||||||
|
Обновляет или создает запись в таблице SyncLog.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
log, created = SyncLog.objects.get_or_create(hotel=hotel)
|
log, created = SyncLog.objects.update_or_create(
|
||||||
|
hotel=hotel,
|
||||||
|
defaults={
|
||||||
|
"recieved_records": recieved_records,
|
||||||
|
"processed_records": processed_records,
|
||||||
|
"created": timezone.now(), # Убедитесь, что дата обновляется
|
||||||
|
}
|
||||||
|
)
|
||||||
if created:
|
if created:
|
||||||
log.recieved_records = recieved_records
|
self.logger.info(f"Sync log created for hotel '{hotel.name}'.")
|
||||||
log.processed_records = processed_records
|
|
||||||
else:
|
else:
|
||||||
log.recieved_records += recieved_records
|
self.logger.info(f"Sync log updated for hotel '{hotel.name}'.")
|
||||||
log.processed_records += processed_records
|
|
||||||
log.save()
|
|
||||||
self.logger.info(f"Sync log updated for hotel '{hotel.name}'.")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error updating sync log for hotel '{hotel.name}': {e}")
|
self.logger.error(f"Error updating sync log for hotel '{hotel.name}': {e}")
|
||||||
|
|
||||||
|
self.logger.info(f"Attempting to update sync log for hotel: {hotel.name}")
|
||||||
|
self.update_sync_log(hotel, recieved_records, processed_records)
|
||||||
|
|
||||||
def process_and_save_data(self, rows):
|
def process_and_save_data(self, rows):
|
||||||
"""
|
hotel_processed_counts = {} # Словарь для подсчёта записей по каждому отелю
|
||||||
Обрабатывает и сохраняет данные из внешнего источника.
|
|
||||||
|
|
||||||
:param rows: Список строк данных, полученных из базы данных.
|
|
||||||
"""
|
|
||||||
seen_entries = set()
|
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
# Получение и декодирование URL-параметров
|
|
||||||
url_parameters = row.get("url_parameters")
|
|
||||||
if not url_parameters:
|
|
||||||
self.logger.warning(f"Skipping record with missing URL parameters: {row}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
parsed_params = self.data_processor.parse_url_parameters(url_parameters)
|
|
||||||
hotel_id = parsed_params.get("utm_content") # Извлекаем hotel_id из параметров
|
|
||||||
room_number = parsed_params.get("utm_term") # Извлекаем room_number из параметров
|
|
||||||
|
|
||||||
if not hotel_id or not room_number:
|
|
||||||
self.logger.warning(f"Skipping record with missing data: hotel_id={hotel_id}, room_number={room_number}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Проверка на дубликаты
|
|
||||||
if (hotel_id, room_number) in seen_entries:
|
|
||||||
self.logger.warning(f"Duplicate record skipped: hotel_id={hotel_id}, room_number={room_number}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
seen_entries.add((hotel_id, room_number))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Получение или создание отеля
|
url_parameters = row.get("url_parameters")
|
||||||
|
if not url_parameters:
|
||||||
|
self.logger.warning(f"Skipping record with missing URL parameters: {row}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
parsed_params = self.data_processor.parse_url_parameters(url_parameters)
|
||||||
|
hotel_id = parsed_params.get("utm_content")
|
||||||
|
room_number = parsed_params.get("utm_term")
|
||||||
|
|
||||||
|
if not hotel_id or not room_number:
|
||||||
|
self.logger.warning(f"Skipping record with missing data: hotel_id={hotel_id}, room_number={room_number}")
|
||||||
|
continue
|
||||||
|
|
||||||
hotel = self.hotel_manager.get_or_create_hotel(hotel_id, row.get("page_title"))
|
hotel = self.hotel_manager.get_or_create_hotel(hotel_id, row.get("page_title"))
|
||||||
if not hotel:
|
if not hotel:
|
||||||
self.logger.warning(f"Skipping record: Failed to create or retrieve hotel with ID {hotel_id}")
|
self.logger.warning(f"Skipping record: Failed to create or retrieve hotel with ID {hotel_id}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Получение или создание комнаты
|
|
||||||
room = self.hotel_manager.get_or_create_room(hotel, room_number)
|
room = self.hotel_manager.get_or_create_room(hotel, room_number)
|
||||||
if not room:
|
if not room:
|
||||||
self.logger.warning(f"Skipping record: Failed to create or retrieve room {room_number} in hotel {hotel.name}")
|
self.logger.warning(f"Skipping record: Failed to create or retrieve room {room_number} in hotel {hotel.name}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Создание или обновление записи активности пользователя
|
|
||||||
UserActivityLog.objects.update_or_create(
|
UserActivityLog.objects.update_or_create(
|
||||||
external_id=row.get("id"),
|
external_id=row.get("id"),
|
||||||
defaults={
|
defaults={
|
||||||
@@ -270,14 +263,23 @@ class DataSyncManager:
|
|||||||
"url_parameters": parsed_params,
|
"url_parameters": parsed_params,
|
||||||
"page_title": self.data_processor.decode_html_entities(row.get("page_title")) or "Untitled",
|
"page_title": self.data_processor.decode_html_entities(row.get("page_title")) or "Untitled",
|
||||||
"page_url": row.get("page_url") or "",
|
"page_url": row.get("page_url") or "",
|
||||||
|
"page_id": row.get("page_id") or 0,
|
||||||
"hits": row.get("hits") or 0,
|
"hits": row.get("hits") or 0,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.logger.info(f"Record ID {row.get('id')} processed successfully.")
|
self.logger.info(f"Record ID {row.get('id')} processed successfully.")
|
||||||
|
|
||||||
|
if hotel.id not in hotel_processed_counts:
|
||||||
|
hotel_processed_counts[hotel.id] = {"recieved_records": 0, "processed_records": 0}
|
||||||
|
hotel_processed_counts[hotel.id]["processed_records"] += 1
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error processing record ID {row.get('id')}: {e}")
|
self.logger.error(f"Error processing record ID {row.get('id')}: {e}")
|
||||||
|
|
||||||
self.logger.info(f"Data processing completed. Processed {len(seen_entries)} unique records.")
|
for hotel_id, counts in hotel_processed_counts.items():
|
||||||
|
hotel = Hotel.objects.get(id=hotel_id)
|
||||||
|
self.update_sync_log(hotel, recieved_records=len(rows), processed_records=counts["processed_records"])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def sync(self):
|
def sync(self):
|
||||||
@@ -292,7 +294,8 @@ class DataSyncManager:
|
|||||||
|
|
||||||
|
|
||||||
def scheduled_sync():
|
def scheduled_sync():
|
||||||
logger = CustomLogger(name="DatabaseSyncScheduler", log_level="ERROR").get_logger()
|
import os
|
||||||
|
logger = CustomLogger(name="DatabaseSyncScheduler", log_level=os.getenv("SCHEDULED_SYNC_LOG_LEVEL", default="ERROR")).get_logger()
|
||||||
logger.info("Starting scheduled sync.")
|
logger.info("Starting scheduled sync.")
|
||||||
|
|
||||||
active_db_settings = ExternalDBSettings.objects.filter(is_active=True)
|
active_db_settings = ExternalDBSettings.objects.filter(is_active=True)
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-12 12:28
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('hotels', '0004_alter_reservation_room_number'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ExternalDBSettings',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(help_text='Имя подключения для идентификации.', max_length=255, unique=True)),
|
|
||||||
('host', models.CharField(help_text='Адрес сервера базы данных.', max_length=255)),
|
|
||||||
('port', models.PositiveIntegerField(default=3306, help_text='Порт сервера базы данных.')),
|
|
||||||
('database', models.CharField(help_text='Имя базы данных.', max_length=255)),
|
|
||||||
('user', models.CharField(help_text='Имя пользователя базы данных.', max_length=255)),
|
|
||||||
('password', models.CharField(help_text='Пароль для подключения.', max_length=255)),
|
|
||||||
('table_name', models.CharField(blank=True, help_text='Имя таблицы для загрузки данных.', max_length=255, null=True)),
|
|
||||||
('selected_fields', models.TextField(blank=True, help_text='Список полей для загрузки (через запятую).', null=True)),
|
|
||||||
('is_active', models.BooleanField(default=True, help_text='Флаг активности подключения.')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Настройки подключения к БД',
|
|
||||||
'verbose_name_plural': 'Настройки подключений к БД',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='UserActivityLog',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigIntegerField(primary_key=True, serialize=False)),
|
|
||||||
('user_id', models.BigIntegerField(verbose_name='ID пользователя')),
|
|
||||||
('ip', models.GenericIPAddressField(verbose_name='IP-адрес')),
|
|
||||||
('created', models.DateTimeField(verbose_name='Дата создания')),
|
|
||||||
('timestamp', models.BigIntegerField(verbose_name='Метка времени')),
|
|
||||||
('date_time', models.DateTimeField(verbose_name='Дата и время')),
|
|
||||||
('referred', models.TextField(blank=True, null=True, verbose_name='Реферальная ссылка')),
|
|
||||||
('agent', models.TextField(verbose_name='Агент пользователя')),
|
|
||||||
('platform', models.CharField(blank=True, max_length=255, null=True, verbose_name='Платформа')),
|
|
||||||
('version', models.CharField(blank=True, max_length=255, null=True, verbose_name='Версия')),
|
|
||||||
('model', models.CharField(blank=True, max_length=255, null=True, verbose_name='Модель устройства')),
|
|
||||||
('device', models.CharField(blank=True, max_length=255, null=True, verbose_name='Тип устройства')),
|
|
||||||
('UAString', models.TextField(verbose_name='User-Agent строка')),
|
|
||||||
('location', models.CharField(blank=True, max_length=255, null=True, verbose_name='Местоположение')),
|
|
||||||
('page_id', models.BigIntegerField(blank=True, null=True, verbose_name='ID страницы')),
|
|
||||||
('url_parameters', models.TextField(blank=True, null=True, verbose_name='Параметры URL')),
|
|
||||||
('page_title', models.TextField(blank=True, null=True, verbose_name='Заголовок страницы')),
|
|
||||||
('type', models.CharField(max_length=50, verbose_name='Тип')),
|
|
||||||
('last_counter', models.IntegerField(verbose_name='Последний счетчик')),
|
|
||||||
('hits', models.IntegerField(verbose_name='Количество обращений')),
|
|
||||||
('honeypot', models.BooleanField(verbose_name='Метка honeypot')),
|
|
||||||
('reply', models.BooleanField(verbose_name='Ответ пользователя')),
|
|
||||||
('page_url', models.URLField(blank=True, null=True, verbose_name='URL страницы')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Регистрация посетителей',
|
|
||||||
'verbose_name_plural': 'Регистрации посетителей',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='RoomDiscrepancy',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('room_number', models.CharField(max_length=50, verbose_name='Номер комнаты')),
|
|
||||||
('booking_id', models.CharField(max_length=255, verbose_name='ID бронирования')),
|
|
||||||
('check_in_date_expected', models.DateField(verbose_name='Ожидаемая дата заселения')),
|
|
||||||
('check_in_date_actual', models.DateField(verbose_name='Фактическая дата заселения')),
|
|
||||||
('discrepancy_type', models.CharField(choices=[('early', 'Раннее заселение'), ('late', 'Позднее заселение'), ('missed', 'Неявка')], max_length=50, verbose_name='Тип несоответствия')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
|
||||||
('hotel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Отель')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Несовпадение в заселении',
|
|
||||||
'verbose_name_plural': 'Несовпадения в заселении',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-12 13:07
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('antifroud', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='externaldbsettings',
|
|
||||||
name='database',
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='externaldbsettings',
|
|
||||||
name='host',
|
|
||||||
field=models.CharField(default='', help_text='Адрес сервера базы данных.', max_length=255),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='externaldbsettings',
|
|
||||||
name='is_active',
|
|
||||||
field=models.BooleanField(default=False, help_text='Флаг активности подключения.'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='externaldbsettings',
|
|
||||||
name='name',
|
|
||||||
field=models.CharField(default='Новая настройка', help_text='Имя подключения для идентификации.', max_length=255, unique=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='externaldbsettings',
|
|
||||||
name='password',
|
|
||||||
field=models.CharField(default='', help_text='Пароль для подключения.', max_length=255),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='externaldbsettings',
|
|
||||||
name='user',
|
|
||||||
field=models.CharField(default='', help_text='Имя пользователя базы данных.', max_length=255),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-12 13:10
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('antifroud', '0002_remove_externaldbsettings_database_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='externaldbsettings',
|
|
||||||
name='database',
|
|
||||||
field=models.CharField(default='', help_text='Имя базы данных.', max_length=255),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='externaldbsettings',
|
|
||||||
name='host',
|
|
||||||
field=models.CharField(help_text='Адрес сервера базы данных.', max_length=255),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='externaldbsettings',
|
|
||||||
name='is_active',
|
|
||||||
field=models.BooleanField(default=True, help_text='Флаг активности подключения.'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='externaldbsettings',
|
|
||||||
name='name',
|
|
||||||
field=models.CharField(help_text='Имя подключения для идентификации.', max_length=255, unique=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='externaldbsettings',
|
|
||||||
name='password',
|
|
||||||
field=models.CharField(help_text='Пароль для подключения.', max_length=255),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='externaldbsettings',
|
|
||||||
name='user',
|
|
||||||
field=models.CharField(help_text='Имя пользователя базы данных.', max_length=255),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-12 14:42
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('antifroud', '0003_externaldbsettings_database_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='externaldbsettings',
|
|
||||||
options={'verbose_name': 'Настройка подключения к БД', 'verbose_name_plural': 'Настройки подключений к БД'},
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='externaldbsettings',
|
|
||||||
name='database',
|
|
||||||
field=models.CharField(default='u1510415_wp832', help_text='Имя базы данных.', max_length=255),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='externaldbsettings',
|
|
||||||
name='table_name',
|
|
||||||
field=models.CharField(blank=True, default='wpts_user_activity_log', help_text='Имя таблицы для загрузки данных.', max_length=255, null=True),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-12 23:57
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('antifroud', '0004_alter_externaldbsettings_options_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ImportedHotel',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('external_id', models.CharField(max_length=255, unique=True, verbose_name='Внешний ID отеля')),
|
|
||||||
('name', models.CharField(max_length=255, verbose_name='Имя отеля')),
|
|
||||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
|
||||||
('updated', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')),
|
|
||||||
('imported', models.BooleanField(default=False, verbose_name='Импортирован в основную базу')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-12 23:58
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('antifroud', '0005_importedhotel'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='importedhotel',
|
|
||||||
options={'verbose_name': 'Импортированный отель', 'verbose_name_plural': 'Импортированные отели'},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-13 00:03
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('antifroud', '0006_alter_importedhotel_options'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='useractivitylog',
|
|
||||||
name='external_id',
|
|
||||||
field=models.CharField(blank=True, max_length=255, null=True),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-13 00:09
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('antifroud', '0007_useractivitylog_external_id'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='useractivitylog',
|
|
||||||
name='id',
|
|
||||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-13 00:32
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('antifroud', '0008_alter_useractivitylog_id'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='importedhotel',
|
|
||||||
name='display_name',
|
|
||||||
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Отображаемое имя'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-14 04:29
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('antifroud', '0009_importedhotel_display_name'),
|
|
||||||
('hotels', '0010_alter_hotel_timezone'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SyncLog',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigIntegerField(primary_key=True, serialize=False, unique=True, verbose_name='ID')),
|
|
||||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
|
||||||
('recieved_records', models.IntegerField(verbose_name='Полученные записи')),
|
|
||||||
('processed_records', models.IntegerField(verbose_name='Обработанные записи')),
|
|
||||||
('hotel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Отель')),
|
|
||||||
('reservation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.reservation', verbose_name='Бронирование')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Журнал синхронизации',
|
|
||||||
'verbose_name_plural': 'Журналы синхронизации',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-14 06:13
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('antifroud', '0010_synclog'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='importedhotel',
|
|
||||||
name='external_id',
|
|
||||||
field=models.CharField(max_length=255, verbose_name='Внешний ID отеля'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-16 23:37
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('antifroud', '0011_alter_importedhotel_external_id'),
|
|
||||||
('hotels', '0010_alter_hotel_timezone'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ViolationLog',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('room_number', models.CharField(blank=True, max_length=50, null=True, verbose_name='Номер комнаты')),
|
|
||||||
('violation_type', models.CharField(choices=[('missed', 'Неявка'), ('early', 'Раннее заселение'), ('late', 'Позднее заселение')], max_length=50, verbose_name='Тип нарушения')),
|
|
||||||
('violation_details', models.TextField(blank=True, null=True, verbose_name='Детали нарушения')),
|
|
||||||
('detected_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата обнаружения')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
|
||||||
('hotel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Отель')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Журнал нарушений',
|
|
||||||
'verbose_name_plural': 'Журналы нарушений',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-17 03:23
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('antifroud', '0012_violationlog'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='useractivitylog',
|
|
||||||
name='timestamp',
|
|
||||||
field=models.BigIntegerField(blank=True, null=True, verbose_name='Метка времени'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-17 03:26
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('antifroud', '0013_alter_useractivitylog_timestamp'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='useractivitylog',
|
|
||||||
name='UAString',
|
|
||||||
field=models.TextField(blank=True, null=True, verbose_name='User-Agent строка'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='useractivitylog',
|
|
||||||
name='agent',
|
|
||||||
field=models.TextField(blank=True, null=True, verbose_name='Агент пользователя'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='useractivitylog',
|
|
||||||
name='created',
|
|
||||||
field=models.DateTimeField(blank=True, null=True, verbose_name='Дата создания'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='useractivitylog',
|
|
||||||
name='date_time',
|
|
||||||
field=models.DateTimeField(blank=True, null=True, verbose_name='Дата и время'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='useractivitylog',
|
|
||||||
name='hits',
|
|
||||||
field=models.IntegerField(blank=True, null=True, verbose_name='Количество обращений'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='useractivitylog',
|
|
||||||
name='honeypot',
|
|
||||||
field=models.BooleanField(blank=True, null=True, verbose_name='Метка honeypot'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='useractivitylog',
|
|
||||||
name='ip',
|
|
||||||
field=models.GenericIPAddressField(blank=True, null=True, verbose_name='IP-адрес'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='useractivitylog',
|
|
||||||
name='last_counter',
|
|
||||||
field=models.IntegerField(blank=True, null=True, verbose_name='Последний счетчик'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='useractivitylog',
|
|
||||||
name='reply',
|
|
||||||
field=models.BooleanField(blank=True, null=True, verbose_name='Ответ пользователя'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='useractivitylog',
|
|
||||||
name='type',
|
|
||||||
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Тип'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='useractivitylog',
|
|
||||||
name='user_id',
|
|
||||||
field=models.BigIntegerField(blank=True, null=True, verbose_name='ID пользователя'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-17 04:49
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('antifroud', '0014_alter_useractivitylog_uastring_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='useractivitylog',
|
|
||||||
name='page_id',
|
|
||||||
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='ID страницы'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-17 05:27
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('antifroud', '0015_alter_useractivitylog_page_id'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='useractivitylog',
|
|
||||||
name='created',
|
|
||||||
field=models.DateTimeField(blank=True, db_index=True, null=True, verbose_name='Дата создания'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='useractivitylog',
|
|
||||||
name='external_id',
|
|
||||||
field=models.CharField(db_index=True, default=1, max_length=255, unique=True, verbose_name='Внешний ID'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='useractivitylog',
|
|
||||||
name='ip',
|
|
||||||
field=models.GenericIPAddressField(blank=True, db_index=True, null=True, verbose_name='IP-адрес'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='useractivitylog',
|
|
||||||
name='page_id',
|
|
||||||
field=models.BigIntegerField(blank=True, db_index=True, null=True, verbose_name='ID страницы'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='useractivitylog',
|
|
||||||
name='user_id',
|
|
||||||
field=models.BigIntegerField(blank=True, db_index=True, null=True, verbose_name='ID пользователя'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-18 01:13
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('antifroud', '0016_alter_useractivitylog_created_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='violationlog',
|
|
||||||
name='hits',
|
|
||||||
field=models.IntegerField(default=1, verbose_name='Срабатывания'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-18 04:46
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('antifroud', '0017_violationlog_hits'),
|
|
||||||
('hotels', '0014_alter_room_number'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='synclog',
|
|
||||||
name='reservation',
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='synclog',
|
|
||||||
name='created',
|
|
||||||
field=models.DateTimeField(auto_now=True, verbose_name='Дата обновления'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='synclog',
|
|
||||||
name='hotel',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', unique=True, verbose_name='Отель'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='synclog',
|
|
||||||
name='id',
|
|
||||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='synclog',
|
|
||||||
name='processed_records',
|
|
||||||
field=models.IntegerField(default=0, verbose_name='Обработанные записи'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='synclog',
|
|
||||||
name='recieved_records',
|
|
||||||
field=models.IntegerField(default=0, verbose_name='Полученные записи'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-18 04:55
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('antifroud', '0018_remove_synclog_reservation_alter_synclog_created_and_more'),
|
|
||||||
('hotels', '0014_alter_room_number'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='synclog',
|
|
||||||
name='hotel',
|
|
||||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Отель'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-18 10:45
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('antifroud', '0019_alter_synclog_hotel'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='useractivitylog',
|
|
||||||
name='hits',
|
|
||||||
field=models.IntegerField(blank=True, default='0', null=True, verbose_name='Количество обращений'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,50 +1,24 @@
|
|||||||
# settings.py
|
|
||||||
|
|
||||||
from decouple import config
|
|
||||||
from django.conf import settings
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
|
||||||
def load_database_settings():
|
def load_database_settings(databases):
|
||||||
# Загружаем настройки из базы данных
|
"""
|
||||||
|
Загружает дополнительные базы данных из таблицы LocalDatabase и добавляет их в конфигурацию.
|
||||||
|
:param databases: Существующий словарь DATABASES
|
||||||
|
"""
|
||||||
LocalDatabase = apps.get_model('app_settings', 'LocalDatabase')
|
LocalDatabase = apps.get_model('app_settings', 'LocalDatabase')
|
||||||
local_db_settings = LocalDatabase.objects.all()
|
|
||||||
|
|
||||||
for db in local_db_settings:
|
|
||||||
# Пример добавления дополнительной базы данных
|
|
||||||
settings.DATABASES[db.name] = {
|
|
||||||
'ENGINE': 'django.db.backends.mysql',
|
|
||||||
'NAME': db.db_name,
|
|
||||||
'USER': db.username,
|
|
||||||
'PASSWORD': db.password,
|
|
||||||
'HOST': db.host,
|
|
||||||
'PORT': db.port,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Вызов этой функции при старте проекта, например, в файле wsgi.py
|
try:
|
||||||
load_database_settings()
|
local_db_settings = LocalDatabase.objects.filter(is_active=True)
|
||||||
|
for db in local_db_settings:
|
||||||
# Чтение локальных баз данных
|
databases[db.name] = {
|
||||||
local_databases = LocalDatabase.objects.filter(is_active=True)
|
'ENGINE': db.engine, # Можно хранить тип движка в базе
|
||||||
|
'NAME': db.database,
|
||||||
# Основная база данных
|
'USER': db.user,
|
||||||
DATABASES = {
|
'PASSWORD': db.password,
|
||||||
'default': {
|
'HOST': db.host,
|
||||||
'ENGINE': 'django.db.backends.postgresql',
|
'PORT': db.port,
|
||||||
'NAME': config('DB_NAME'),
|
'ATOMIC_REQUESTS': True, # Убедитесь, что добавляете ATOMIC_REQUESTS
|
||||||
'USER': config('DB_USER'),
|
}
|
||||||
'PASSWORD': config('DB_PASSWORD'),
|
except Exception as e:
|
||||||
'HOST': config('DB_HOST'),
|
print(f"Ошибка загрузки локальных баз данных: {e}")
|
||||||
'PORT': config('DB_PORT'),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
# Добавление локальных баз данных
|
|
||||||
for db in local_databases:
|
|
||||||
DATABASES[db.name] = {
|
|
||||||
'ENGINE': 'django.db.backends.postgresql',
|
|
||||||
'NAME': db.name,
|
|
||||||
'USER': db.user,
|
|
||||||
'PASSWORD': db.password,
|
|
||||||
'HOST': db.host,
|
|
||||||
'PORT': db.port,
|
|
||||||
}
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,16 +1,79 @@
|
|||||||
|
# import os
|
||||||
|
# import django
|
||||||
|
# import asyncio
|
||||||
|
# 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 scheduler.tasks import load_tasks_to_scheduler
|
||||||
|
# from app_settings.models import TelegramSettings
|
||||||
|
# from touchh.utils.log import CustomLogger
|
||||||
|
|
||||||
|
# class Command(BaseCommand):
|
||||||
|
# help = "Запуск Telegram бота и планировщика"
|
||||||
|
|
||||||
|
# def handle(self, *args, **options):
|
||||||
|
# # Установка Django окружения
|
||||||
|
# os.environ.setdefault("DJANGO_SETTINGS_MODULE", "touchh.settings")
|
||||||
|
# django.setup()
|
||||||
|
|
||||||
|
# # Создаем новый цикл событий
|
||||||
|
# loop = asyncio.new_event_loop()
|
||||||
|
# asyncio.set_event_loop(loop)
|
||||||
|
|
||||||
|
# # Настройка планировщика
|
||||||
|
# scheduler = AsyncIOScheduler(event_loop=loop)
|
||||||
|
# scheduler.start()
|
||||||
|
|
||||||
|
# # Загрузка задач в планировщик
|
||||||
|
# try:
|
||||||
|
# load_tasks_to_scheduler(scheduler)
|
||||||
|
# except Exception as e:
|
||||||
|
# self.stderr.write(f"Ошибка при загрузке задач в планировщик: {e}")
|
||||||
|
# return
|
||||||
|
|
||||||
|
# # Настройка Telegram бота
|
||||||
|
# # bot_token = os.getenv("TELEGRAM_BOT_TOKEN")
|
||||||
|
# bot_token = TelegramSettings.objects.first().bot_token
|
||||||
|
|
||||||
|
# if not bot_token:
|
||||||
|
# raise ValueError("Токен бота не найден в переменных окружения.")
|
||||||
|
# application = Application.builder().token(bot_token).build()
|
||||||
|
# setup_bot(application)
|
||||||
|
# # Основная асинхронная функция
|
||||||
|
# async def main():
|
||||||
|
# await application.initialize()
|
||||||
|
# await application.start()
|
||||||
|
# await application.updater.start_polling()
|
||||||
|
# self.stdout.write(self.style.SUCCESS("Telegram бот и планировщик успешно запущены."))
|
||||||
|
# try:
|
||||||
|
# while True:
|
||||||
|
# await asyncio.sleep(3600)
|
||||||
|
# except asyncio.CancelledError:
|
||||||
|
# await application.stop()
|
||||||
|
# scheduler.shutdown()
|
||||||
|
|
||||||
|
# # Запуск асинхронной программы
|
||||||
|
# try:
|
||||||
|
# loop.run_until_complete(main())
|
||||||
|
# except KeyboardInterrupt:
|
||||||
|
# self.stdout.write(self.style.ERROR("Завершение работы Telegram бота и планировщика"))
|
||||||
|
# finally:
|
||||||
|
# loop.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import django
|
import django
|
||||||
import asyncio
|
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
|
from telegram.ext import Application
|
||||||
from bot.utils.bot_setup import setup_bot
|
from bot.utils.bot_setup import setup_bot
|
||||||
from scheduler.tasks import load_tasks_to_scheduler
|
|
||||||
from app_settings.models import TelegramSettings
|
from app_settings.models import TelegramSettings
|
||||||
from touchh.utils.log import CustomLogger
|
from touchh.utils.log import CustomLogger
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Запуск Telegram бота и планировщика"
|
help = "Запуск Telegram бота"
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
# Установка Django окружения
|
# Установка Django окружения
|
||||||
@@ -21,42 +84,31 @@ class Command(BaseCommand):
|
|||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
asyncio.set_event_loop(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 бота
|
# Настройка Telegram бота
|
||||||
# bot_token = os.getenv("TELEGRAM_BOT_TOKEN")
|
|
||||||
bot_token = TelegramSettings.objects.first().bot_token
|
bot_token = TelegramSettings.objects.first().bot_token
|
||||||
|
|
||||||
if not bot_token:
|
if not bot_token:
|
||||||
raise ValueError("Токен бота не найден в переменных окружения.")
|
raise ValueError("Токен бота не найден в переменных окружения.")
|
||||||
|
|
||||||
application = Application.builder().token(bot_token).build()
|
application = Application.builder().token(bot_token).build()
|
||||||
setup_bot(application)
|
setup_bot(application)
|
||||||
|
|
||||||
# Основная асинхронная функция
|
# Основная асинхронная функция
|
||||||
async def main():
|
async def main():
|
||||||
await application.initialize()
|
await application.initialize()
|
||||||
await application.start()
|
await application.start()
|
||||||
await application.updater.start_polling()
|
await application.updater.start_polling()
|
||||||
self.stdout.write(self.style.SUCCESS("Telegram бот и планировщик успешно запущены."))
|
self.stdout.write(self.style.SUCCESS("Telegram бот успешно запущен."))
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
await asyncio.sleep(3600)
|
await asyncio.sleep(3600)
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
await application.stop()
|
await application.stop()
|
||||||
scheduler.shutdown()
|
|
||||||
|
|
||||||
# Запуск асинхронной программы
|
# Запуск асинхронной программы
|
||||||
try:
|
try:
|
||||||
loop.run_until_complete(main())
|
loop.run_until_complete(main())
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
self.stdout.write(self.style.ERROR("Завершение работы Telegram бота и планировщика"))
|
self.stdout.write(self.style.ERROR("Завершение работы Telegram бота"))
|
||||||
finally:
|
finally:
|
||||||
loop.close()
|
loop.close()
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ from hotels.models import Hotel, UserHotel
|
|||||||
from users.models import User
|
from users.models import User
|
||||||
from pms_integration.manager import PMSIntegrationManager
|
from pms_integration.manager import PMSIntegrationManager
|
||||||
from bot.utils.froud_check import detect_fraud
|
from bot.utils.froud_check import detect_fraud
|
||||||
|
from touchh.utils.log import CustomLogger
|
||||||
|
|
||||||
|
logger = CustomLogger(name="BOT-hotels Manager", log_level="DEBUG").get_logger()
|
||||||
async def manage_hotels(update: Update, context):
|
async def manage_hotels(update: Update, context):
|
||||||
"""Отображение списка отелей, связанных с пользователем."""
|
"""Отображение списка отелей, связанных с пользователем."""
|
||||||
query = update.callback_query
|
query = update.callback_query
|
||||||
@@ -81,7 +84,8 @@ async def check_pms(update, context):
|
|||||||
try:
|
try:
|
||||||
# Получение ID отеля из callback_data
|
# Получение ID отеля из callback_data
|
||||||
hotel_id = query.data.split("_")[2]
|
hotel_id = query.data.split("_")[2]
|
||||||
|
logger.debug(f"Hotel ID: {hotel_id}")
|
||||||
|
logger.debug(f"Hotel ID type : {type(hotel_id)}")
|
||||||
# Получение конфигурации отеля и PMS
|
# Получение конфигурации отеля и PMS
|
||||||
hotel = await sync_to_async(Hotel.objects.select_related('pms').get)(id=hotel_id)
|
hotel = await sync_to_async(Hotel.objects.select_related('pms').get)(id=hotel_id)
|
||||||
pms_config = hotel.pms
|
pms_config = hotel.pms
|
||||||
@@ -99,6 +103,7 @@ async def check_pms(update, context):
|
|||||||
if hasattr(pms_manager.plugin, 'fetch_data') and callable(pms_manager.plugin.fetch_data):
|
if hasattr(pms_manager.plugin, 'fetch_data') and callable(pms_manager.plugin.fetch_data):
|
||||||
# Плагин поддерживает метод fetch_data
|
# Плагин поддерживает метод fetch_data
|
||||||
report = await pms_manager.plugin._fetch_data()
|
report = await pms_manager.plugin._fetch_data()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
await query.edit_message_text("Подходящий способ интеграции с PMS не найден.")
|
await query.edit_message_text("Подходящий способ интеграции с PMS не найден.")
|
||||||
return
|
return
|
||||||
@@ -110,6 +115,7 @@ async def check_pms(update, context):
|
|||||||
f"Обработано записей: {report['processed_items']}\n"
|
f"Обработано записей: {report['processed_items']}\n"
|
||||||
f"Ошибки: {len(report['errors'])}"
|
f"Ошибки: {len(report['errors'])}"
|
||||||
)
|
)
|
||||||
|
logger.info(f'Result_Message: {result_message}\n Result_meaage_type: {type(result_message)}')
|
||||||
if report["errors"]:
|
if report["errors"]:
|
||||||
result_message += "\n\nСписок ошибок:\n" + "\n".join(report["errors"])
|
result_message += "\n\nСписок ошибок:\n" + "\n".join(report["errors"])
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|
||||||
from bot.operations.notifications import schedule_notifications
|
|
||||||
|
|
||||||
|
|
||||||
def setup_scheduler():
|
|
||||||
"""Настройка планировщика уведомлений."""
|
|
||||||
print("Настройка планировщика...")
|
|
||||||
scheduler = AsyncIOScheduler()
|
|
||||||
scheduler.add_job(schedule_notifications, "cron", minute="*")
|
|
||||||
return scheduler
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-09 09:30
|
|
||||||
|
|
||||||
import django.core.validators
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
]
|
|
||||||
|
|
||||||
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='FraudLog',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('reservation_id', models.BigIntegerField(unique=True, verbose_name='ID бронирования')),
|
|
||||||
('guest_name', models.CharField(blank=True, max_length=255, null=True)),
|
|
||||||
('check_in_date', models.DateField()),
|
|
||||||
('detected_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('message', models.TextField()),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Журнал мошенничества',
|
|
||||||
'verbose_name_plural': 'Журналы мошенничества',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Guest',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=255, verbose_name='Имя гостя')),
|
|
||||||
('birthdate', models.DateField(blank=True, null=True, verbose_name='Дата рождения')),
|
|
||||||
('phone', models.CharField(blank=True, max_length=50, null=True, verbose_name='Телефон')),
|
|
||||||
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Email')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Гость',
|
|
||||||
'verbose_name_plural': 'Гости',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Reservation',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('reservation_id', models.BigIntegerField(unique=True, verbose_name='ID бронирования')),
|
|
||||||
('room_number', models.CharField(max_length=50, verbose_name='Номер комнаты')),
|
|
||||||
('room_type', models.CharField(max_length=255, verbose_name='Тип комнаты')),
|
|
||||||
('check_in', models.DateTimeField(verbose_name='Дата заезда')),
|
|
||||||
('check_out', models.DateTimeField(verbose_name='Дата выезда')),
|
|
||||||
('status', models.CharField(max_length=50, verbose_name='Статус')),
|
|
||||||
('price', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Цена')),
|
|
||||||
('discount', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Скидка')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Бронирование',
|
|
||||||
'verbose_name_plural': 'Бронирования',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='UserHotel',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Пользователь отеля',
|
|
||||||
'verbose_name_plural': 'Пользователи отелей',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
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(validators=[django.core.validators.MinValueValidator(100), django.core.validators.MaxValueValidator(599)], 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.CreateModel(
|
|
||||||
name='Hotel',
|
|
||||||
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='Создан')),
|
|
||||||
('api', models.OneToOneField(blank=True, help_text='API, связанный с этим отелем.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='hotels.apiconfiguration', verbose_name='API')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Отель',
|
|
||||||
'verbose_name_plural': 'Отели',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-09 09:30
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('hotels', '0001_initial'),
|
|
||||||
('pms_integration', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='hotel',
|
|
||||||
name='pms',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='pms_integration.pmsconfiguration', verbose_name='PMS система'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='fraudlog',
|
|
||||||
name='hotel',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frauds', to='hotels.hotel'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='reservation',
|
|
||||||
name='hotel',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Отель'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='guest',
|
|
||||||
name='reservation',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='guests', to='hotels.reservation', verbose_name='Бронирование'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='userhotel',
|
|
||||||
name='hotel',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='hotel_users', to='hotels.hotel', verbose_name='Отель'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-09 09:30
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('hotels', '0002_initial'),
|
|
||||||
('users', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='userhotel',
|
|
||||||
name='user',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_hotels', to='users.user', verbose_name='Пользователь'),
|
|
||||||
),
|
|
||||||
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'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-11 10:52
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('hotels', '0003_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='reservation',
|
|
||||||
name='room_number',
|
|
||||||
field=models.CharField(blank=True, max_length=255, null=True),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-13 01:01
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('hotels', '0004_alter_reservation_room_number'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='hotel',
|
|
||||||
name='import_status',
|
|
||||||
field=models.CharField(choices=[('not_started', 'Не начат'), ('in_progress', 'В процессе'), ('completed', 'Завершен')], default='not_started', max_length=50, verbose_name='Статус импорта'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='hotel',
|
|
||||||
name='imported_at',
|
|
||||||
field=models.DateTimeField(blank=True, null=True, verbose_name='Дата импорта'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='hotel',
|
|
||||||
name='imported_from',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='imported_hotels', to='hotels.hotel', verbose_name='Импортированный отель'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# hotels/migrations/0006_remove_hotel_import_status_remove_hotel_imported_at_and_more.py
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
def remove_unused_fields(apps, schema_editor):
|
|
||||||
Hotel = apps.get_model('hotels', 'Hotel')
|
|
||||||
Hotel._meta.get_field('import_status').remote_field.model._meta.db_table
|
|
||||||
Hotel._meta.get_field('imported_at').remote_field.model._meta.db_table
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('hotels', '0005_hotel_import_status_hotel_imported_at_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='hotel',
|
|
||||||
name='import_status',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='hotel',
|
|
||||||
name='imported_at',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='hotel',
|
|
||||||
name='imported_from',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='hotel',
|
|
||||||
name='api',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-14 02:43
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('hotels', '0006_remove_hotel_api_remove_hotel_import_status_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='hotel',
|
|
||||||
name='hotel_id',
|
|
||||||
field=models.CharField(blank=True, max_length=255, null=True, unique=True, verbose_name='ID отеля'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-14 02:55
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('hotels', '0007_hotel_hotel_id'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='hotel',
|
|
||||||
name='address',
|
|
||||||
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Адрес'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='hotel',
|
|
||||||
name='city',
|
|
||||||
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Город'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='hotel',
|
|
||||||
name='email',
|
|
||||||
field=models.EmailField(blank=True, max_length=254, null=True, verbose_name='Email'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='hotel',
|
|
||||||
name='phone',
|
|
||||||
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Телефон'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='hotel',
|
|
||||||
name='timezone',
|
|
||||||
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Часовой пояс'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-14 02:55
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('hotels', '0008_hotel_address_hotel_city_hotel_email_hotel_phone_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='hotel',
|
|
||||||
name='description',
|
|
||||||
field=models.TextField(blank=True, null=True, verbose_name='Описание'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-17 11:21
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('hotels', '0011_room_alter_fraudlog_check_in_date_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='room',
|
|
||||||
name='number',
|
|
||||||
field=models.CharField(max_length=50, unique=True, verbose_name='Номер комнаты'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-17 11:21
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('hotels', '0012_alter_room_number'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='room',
|
|
||||||
name='number',
|
|
||||||
field=models.CharField(max_length=50, verbose_name='Номер комнаты'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-17 11:24
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('hotels', '0013_alter_room_number'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='room',
|
|
||||||
name='number',
|
|
||||||
field=models.CharField(max_length=50, unique=True, verbose_name='Номер комнаты'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -18,7 +18,7 @@ class PluginLoader:
|
|||||||
print("Загрузка плагинов:")
|
print("Загрузка плагинов:")
|
||||||
for file in os.listdir(PluginLoader.PLUGIN_PATH):
|
for file in os.listdir(PluginLoader.PLUGIN_PATH):
|
||||||
if file.endswith("_pms.py") and not file.startswith("__"):
|
if file.endswith("_pms.py") and not file.startswith("__"):
|
||||||
print(f" Plugin {file}")
|
# print(f" Plugin {file}")
|
||||||
module_name = f"pms_integration.plugins.{file[:-3]}"
|
module_name = f"pms_integration.plugins.{file[:-3]}"
|
||||||
try:
|
try:
|
||||||
module = importlib.import_module(module_name)
|
module = importlib.import_module(module_name)
|
||||||
@@ -26,7 +26,7 @@ class PluginLoader:
|
|||||||
cls = getattr(module, attr)
|
cls = getattr(module, attr)
|
||||||
if isinstance(cls, type) and issubclass(cls, BasePMSPlugin) and cls is not BasePMSPlugin:
|
if isinstance(cls, type) and issubclass(cls, BasePMSPlugin) and cls is not BasePMSPlugin:
|
||||||
plugin_name = file[:-7] # Убираем `_pms` из имени файла
|
plugin_name = file[:-7] # Убираем `_pms` из имени файла
|
||||||
print(f" Загружен плагин {plugin_name}: {cls.__name__}")
|
# print(f" Загружен плагин {plugin_name}: {cls.__name__}")
|
||||||
plugins[plugin_name] = cls
|
plugins[plugin_name] = cls
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" Ошибка загрузки плагина {module_name}: {e}")
|
print(f" Ошибка загрузки плагина {module_name}: {e}")
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-09 09:30
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('hotels', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
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')),
|
|
||||||
('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='Пароль')),
|
|
||||||
('plugin_name', models.CharField(max_length=255, verbose_name='Название плагина')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'PMS система',
|
|
||||||
'verbose_name_plural': 'PMS системы',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
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',
|
|
||||||
'indexes': [models.Index(fields=['hotel'], name='pms_integra_hotel_i_ade4da_idx'), models.Index(fields=['checked_at'], name='pms_integra_checked_938acc_idx'), models.Index(fields=['status'], name='pms_integra_status_358b64_idx')],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# 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),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# 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='Плагин'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# 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='Плагин'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# 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='Публичный ключ'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -74,7 +74,7 @@ class EcviPMSPlugin(BasePMSPlugin):
|
|||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
self.logger.error(f"Ошибка запроса: {e}")
|
self.logger.error(f"Ошибка запроса: {e}")
|
||||||
return []
|
return []
|
||||||
self.logger.debug(f"\n\n\n\n\ndata: {data}\n\n\n\n\n")
|
|
||||||
# Фильтрация данных
|
# Фильтрация данных
|
||||||
filtered_data = [
|
filtered_data = [
|
||||||
{
|
{
|
||||||
@@ -87,7 +87,6 @@ class EcviPMSPlugin(BasePMSPlugin):
|
|||||||
} for item in data if isinstance(item, dict) and item.get('occupancy') in ['проживание', 'под выезд', 'под заезд']
|
} for item in data if isinstance(item, dict) and item.get('occupancy') in ['проживание', 'под выезд', 'под заезд']
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
self.logger.debug(f"filtered_data: {filtered_data}")
|
self.logger.debug(f"filtered_data: {filtered_data}")
|
||||||
|
|
||||||
# Сохранение данных в базу данных
|
# Сохранение данных в базу данных
|
||||||
@@ -108,7 +107,6 @@ class EcviPMSPlugin(BasePMSPlugin):
|
|||||||
|
|
||||||
# Проверяем, существует ли уже резервация с таким внешним ID
|
# Проверяем, существует ли уже резервация с таким внешним ID
|
||||||
reservation_id = item.get('reservation_id')
|
reservation_id = item.get('reservation_id')
|
||||||
self.logger.debug(f"-----\n\n\nITEM : {item}\n\n\n---")
|
|
||||||
if not reservation_id:
|
if not reservation_id:
|
||||||
self.logger.error("Ошибка: 'reservation_id' отсутствует в данных.")
|
self.logger.error("Ошибка: 'reservation_id' отсутствует в данных.")
|
||||||
return
|
return
|
||||||
@@ -123,6 +121,7 @@ class EcviPMSPlugin(BasePMSPlugin):
|
|||||||
await sync_to_async(Reservation.objects.update_or_create)(
|
await sync_to_async(Reservation.objects.update_or_create)(
|
||||||
reservation_id=reservation_id,
|
reservation_id=reservation_id,
|
||||||
defaults={
|
defaults={
|
||||||
|
''
|
||||||
'room_number': item.get('room_number'),
|
'room_number': item.get('room_number'),
|
||||||
'room_type': item.get('room_type'),
|
'room_type': item.get('room_type'),
|
||||||
'check_in': item.get('checkin'),
|
'check_in': item.get('checkin'),
|
||||||
|
|||||||
@@ -4,17 +4,16 @@ import json
|
|||||||
from .base_plugin import BasePMSPlugin
|
from .base_plugin import BasePMSPlugin
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from asgiref.sync import sync_to_async
|
from asgiref.sync import sync_to_async
|
||||||
from math import ceil
|
from touchh.utils.log import CustomLogger
|
||||||
|
from hotels.models import Hotel, Reservation
|
||||||
class RealtyCalendarPlugin(BasePMSPlugin):
|
class RealtyCalendarPlugin(BasePMSPlugin):
|
||||||
"""Плагин для импорта данных из системы RealtyCalendar
|
"""Плагин для импорта данных из системы RealtyCalendar."""
|
||||||
"""
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
self.public_key = config.public_key
|
self.public_key = config.public_key
|
||||||
self.private_key = config.private_key
|
self.private_key = config.private_key
|
||||||
self.api_url = config.url.rstrip("/")
|
self.api_url = config.url.rstrip("/")
|
||||||
|
self.logger = CustomLogger(name="RealtyCalendarPlugin", log_level="DEBUG").get_logger()
|
||||||
if not self.public_key or not self.private_key:
|
if not self.public_key or not self.private_key:
|
||||||
raise ValueError("Публичный или приватный ключ отсутствует для RealtyCalendar")
|
raise ValueError("Публичный или приватный ключ отсутствует для RealtyCalendar")
|
||||||
|
|
||||||
@@ -31,8 +30,8 @@ class RealtyCalendarPlugin(BasePMSPlugin):
|
|||||||
"""
|
"""
|
||||||
Возвращает отсортированный по имени список ключей.
|
Возвращает отсортированный по имени список ключей.
|
||||||
"""
|
"""
|
||||||
sorted_keys = sorted(list(obj.keys()))
|
sorted_keys = sorted(obj.keys())
|
||||||
print(f"[DEBUG] Отсортированные ключи: {sorted_keys}")
|
self.logger.debug(f"Отсортированные ключи: {sorted_keys}")
|
||||||
return sorted_keys
|
return sorted_keys
|
||||||
|
|
||||||
def _generate_data_string(self, obj):
|
def _generate_data_string(self, obj):
|
||||||
@@ -41,7 +40,7 @@ class RealtyCalendarPlugin(BasePMSPlugin):
|
|||||||
"""
|
"""
|
||||||
sorted_keys = self._get_sorted_keys(obj)
|
sorted_keys = self._get_sorted_keys(obj)
|
||||||
string = "".join(f"{key}={obj[key]}" for key in sorted_keys)
|
string = "".join(f"{key}={obj[key]}" for key in sorted_keys)
|
||||||
print(f"[DEBUG] Сформированная строка данных: {string}")
|
self.logger.debug(f"Сформированная строка данных: {string}")
|
||||||
return string + self.private_key
|
return string + self.private_key
|
||||||
|
|
||||||
def _generate_md5(self, string):
|
def _generate_md5(self, string):
|
||||||
@@ -49,7 +48,7 @@ class RealtyCalendarPlugin(BasePMSPlugin):
|
|||||||
Генерирует MD5-хеш от строки.
|
Генерирует MD5-хеш от строки.
|
||||||
"""
|
"""
|
||||||
md5_hash = hashlib.md5(string.encode("utf-8")).hexdigest()
|
md5_hash = hashlib.md5(string.encode("utf-8")).hexdigest()
|
||||||
print(f"[DEBUG] Сформированный MD5-хеш: {md5_hash}")
|
self.logger.debug(f"Сформированный MD5-хеш: {md5_hash}")
|
||||||
return md5_hash
|
return md5_hash
|
||||||
|
|
||||||
def _generate_sign(self, data):
|
def _generate_sign(self, data):
|
||||||
@@ -57,16 +56,17 @@ class RealtyCalendarPlugin(BasePMSPlugin):
|
|||||||
Генерирует подпись для данных запроса.
|
Генерирует подпись для данных запроса.
|
||||||
"""
|
"""
|
||||||
data_string = self._generate_data_string(data)
|
data_string = self._generate_data_string(data)
|
||||||
print(f"[DEBUG] Строка для подписи: {data_string}")
|
self.logger.debug(f"Строка для подписи: {data_string}")
|
||||||
sign = self._generate_md5(data_string)
|
sign = self._generate_md5(data_string)
|
||||||
print(f"[DEBUG] Подпись: {sign}")
|
self.logger.debug(f"Подпись: {sign}")
|
||||||
return sign
|
return sign
|
||||||
|
|
||||||
def _fetch_data(self):
|
async def _fetch_data(self):
|
||||||
"""
|
"""
|
||||||
Выполняет запрос к API RealtyCalendar для получения данных о бронированиях.
|
Выполняет запрос к API RealtyCalendar для получения данных о бронированиях.
|
||||||
"""
|
"""
|
||||||
base_url = f"https://realtycalendar.ru/api/v1/bookings/{self.public_key}/"
|
self.logger.debug("Начало выполнения функции _fetch_data")
|
||||||
|
base_url = f"{self.api_url}/api/v1/bookings/{self.public_key}/"
|
||||||
headers = {
|
headers = {
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -77,81 +77,150 @@ class RealtyCalendarPlugin(BasePMSPlugin):
|
|||||||
data = {
|
data = {
|
||||||
"begin_date": (now - timedelta(days=7)).strftime("%Y-%m-%d"),
|
"begin_date": (now - timedelta(days=7)).strftime("%Y-%m-%d"),
|
||||||
"end_date": now.strftime("%Y-%m-%d"),
|
"end_date": now.strftime("%Y-%m-%d"),
|
||||||
"room_number": ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
print(f"[DEBUG] Даты выборки: {data}")
|
self.logger.debug(f"Даты выборки: {data}")
|
||||||
|
|
||||||
# Генерация подписи
|
# Генерация подписи
|
||||||
data["sign"] = self._generate_sign(data)
|
data["sign"] = self._generate_sign(data)
|
||||||
|
|
||||||
# Отправляем запрос
|
# Отправляем запрос
|
||||||
print(f"[DEBUG] URL запроса: {base_url}")
|
self.logger.debug(f"URL запроса: {base_url}")
|
||||||
print(f"[DEBUG] Заголовки: {headers}")
|
self.logger.debug(f"Заголовки: {headers}")
|
||||||
print(f"[DEBUG] Данные запроса: {data}")
|
self.logger.debug(f"Данные запроса: {data}")
|
||||||
|
|
||||||
response = requests.post(url=base_url, headers=headers, json=data)
|
response = requests.post(url=base_url, headers=headers, json=data)
|
||||||
|
self.logger.debug(f"Запрос: {response}")
|
||||||
|
self.logger.debug(f"Статус ответа: {response.status_code}")
|
||||||
|
# self.logger.debug(f"Ответ: {response.text}")
|
||||||
|
|
||||||
# Логируем результат
|
|
||||||
print(f"[DEBUG] Статус ответа: {response.status_code}")
|
|
||||||
print(f"[DEBUG] Ответ: {response.text}")
|
|
||||||
|
|
||||||
# Проверяем успешность запроса
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
bookings = response.json().get("bookings", [])
|
try:
|
||||||
print(f"[DEBUG] Полученные данные бронирований: {bookings}")
|
response_data = response.json()
|
||||||
return bookings
|
self.logger.debug(f"Тип данных ответа: {type(response_data)}")
|
||||||
else:
|
if not isinstance(response_data, dict) or "bookings" not in response_data:
|
||||||
raise ValueError(f"Ошибка API RealtyCalendar: {response.status_code}, {response.text}")
|
raise ValueError(f"Неожиданная структура ответа: {response_data}")
|
||||||
|
bookings = response_data.get("bookings", [])
|
||||||
|
# self.logger.debug(f"Полученные данные бронирований: {bookings}")
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
self.logger.error(f"Ошибка декодирования JSON: {e}")
|
||||||
|
raise ValueError("Ошибка декодирования JSON ответа.")
|
||||||
|
|
||||||
|
# Фильтрация данных
|
||||||
|
filtered_data = [
|
||||||
|
{
|
||||||
|
"id": item.get("id"),
|
||||||
|
"begin_date": item.get("begin_date"),
|
||||||
|
"end_date": item.get("end_date"),
|
||||||
|
"amount": item.get("amount"),
|
||||||
|
"status": item.get("status"),
|
||||||
|
"is_delete": item.get("is_delete"),
|
||||||
|
"apartment_id": item.get("apartment_id"),
|
||||||
|
"prepayment": item.get("prepayment"),
|
||||||
|
"deposit": item.get("deposit"),
|
||||||
|
"source": item.get("source"),
|
||||||
|
"notes": item.get("notes"),
|
||||||
|
}
|
||||||
|
for item in bookings
|
||||||
|
if isinstance(item, dict) and item.get("status") in ["booked", "request"]
|
||||||
|
]
|
||||||
|
self.logger.debug(f"Отфильтрованные данные: {type(filtered_data)}")
|
||||||
|
|
||||||
async def _save_to_db(self, data, hotel_id, batch_size=50):
|
for item in filtered_data:
|
||||||
|
self.logger.debug(f"Данные бронирования: {item}")
|
||||||
|
await self._save_to_db(item)
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
async def _save_to_db(self, data):
|
||||||
|
from django.utils import timezone
|
||||||
"""
|
"""
|
||||||
Сохраняет данные о бронированиях в базу данных партиями.
|
Сохраняет данные в БД (например, информацию о номере).
|
||||||
"""
|
"""
|
||||||
from hotels.models import Reservation, Hotel
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
hotel = await sync_to_async(Hotel.objects.get)(id=hotel_id)
|
# Проверяем общее количество записей для обработки
|
||||||
self.logger.info(f"Загружен отель: {hotel.name}")
|
if not isinstance(data, list):
|
||||||
|
self.logger.error(f"Ожидался список записей, но получен {type(data).__name__}")
|
||||||
|
return
|
||||||
|
|
||||||
# Разделение данных на батчи
|
|
||||||
total_records = len(data)
|
total_records = len(data)
|
||||||
batches = [data[i:i + batch_size] for i in range(0, total_records, batch_size)]
|
self.logger.info(f"Общее количество записей для обработки: {total_records}")
|
||||||
self.logger.info(f"Обработка {total_records} записей в {len(batches)} партиях...")
|
|
||||||
|
|
||||||
for batch_index, batch in enumerate(batches):
|
for index, item in enumerate(data, start=1):
|
||||||
self.logger.info(f"Обработка партии {batch_index + 1}/{len(batches)}")
|
try:
|
||||||
|
self.logger.info(f"Обработка записи {index}/{total_records}")
|
||||||
|
|
||||||
for item in batch:
|
# Проверка типа данных
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
self.logger.error(f"Пропущена запись {index}/{total_records}: ожидался dict, но получен {type(item).__name__}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Получаем отель по настройкам PMS
|
||||||
|
hotel = await sync_to_async(Hotel.objects.get)(pms=self.pms_config)
|
||||||
|
self.logger.debug(f"Отель найден: {hotel.name}")
|
||||||
|
|
||||||
|
# Проверяем, существует ли уже резервация с таким внешним ID
|
||||||
|
reservation_id = item.get('id')
|
||||||
|
if not reservation_id:
|
||||||
|
self.logger.error(f"Пропущена запись {index}/{total_records}: отсутствует 'id' в данных.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Преобразуем даты в "aware" объекты
|
||||||
try:
|
try:
|
||||||
if item.get("is_delete", False):
|
check_in = timezone.make_aware(datetime.strptime(item.get('begin_date'), "%Y-%m-%d"))
|
||||||
self.logger.info(f"Пропущена запись с ID {item.get('id')} (удалена).")
|
check_out = timezone.make_aware(datetime.strptime(item.get('end_date'), "%Y-%m-%d"))
|
||||||
continue
|
|
||||||
|
|
||||||
client_data = item.get("client", {})
|
|
||||||
if not item.get("id") or not item.get("begin_date") or not item.get("end_date"):
|
|
||||||
self.logger.warning(f"Пропущена запись с неполными данными: {item}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
reservation_defaults = {
|
|
||||||
"room_number": item.get("apartment_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": client_data.get("fio", ""),
|
|
||||||
"client_email": client_data.get("email", ""),
|
|
||||||
"client_phone": client_data.get("phone", ""),
|
|
||||||
}
|
|
||||||
|
|
||||||
await sync_to_async(Reservation.objects.update_or_create)(
|
|
||||||
reservation_id=item["id"],
|
|
||||||
hotel=hotel,
|
|
||||||
defaults=reservation_defaults
|
|
||||||
)
|
|
||||||
self.logger.info(f"Сохранена запись для бронирования ID {item['id']}.")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Ошибка при обработке бронирования ID {item.get('id', 'неизвестно')}: {e}")
|
self.logger.error(f"Ошибка преобразования дат для записи {index}/{total_records}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
existing_reservation = await sync_to_async(Reservation.objects.filter)(reservation_id=reservation_id)
|
||||||
|
|
||||||
|
# Теперь вызываем .first() после асинхронного вызова
|
||||||
|
existing_reservation = await sync_to_async(existing_reservation.first)()
|
||||||
|
|
||||||
|
if existing_reservation:
|
||||||
|
self.logger.debug(f"Резервация {reservation_id} уже существует. Обновляем...")
|
||||||
|
await sync_to_async(Reservation.objects.update_or_create)(
|
||||||
|
reservation_id=reservation_id,
|
||||||
|
defaults={
|
||||||
|
'room_number': item.get('apartment_id'),
|
||||||
|
'room_type': 'Описание отсутствует',
|
||||||
|
'check_in': check_in,
|
||||||
|
'check_out': check_out,
|
||||||
|
'status': item.get('status'),
|
||||||
|
'hotel': hotel
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.logger.debug(f"Резервация обновлена.")
|
||||||
|
else:
|
||||||
|
self.logger.debug(f"Резервация не найдена, создаем новую...")
|
||||||
|
reservation = await sync_to_async(Reservation.objects.create)(
|
||||||
|
reservation_id=reservation_id,
|
||||||
|
room_number=item.get('apartment_id'),
|
||||||
|
room_type='Описание отсутствует',
|
||||||
|
check_in=check_in,
|
||||||
|
check_out=check_out,
|
||||||
|
status=item.get('status'),
|
||||||
|
hotel=hotel
|
||||||
|
)
|
||||||
|
self.logger.debug(f"Новая резервация создана с ID: {reservation.reservation_id}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Ошибка при обработке записи {index}/{total_records}: {e}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Ошибка при обработке данных: {e}")
|
self.logger.error(f"Ошибка обработки данных в _save_to_db: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def validate_plugin(self):
|
||||||
|
"""
|
||||||
|
Проверка на соответствие требованиям.
|
||||||
|
Можно проверить наличие методов или полей.
|
||||||
|
"""
|
||||||
|
# Проверяем наличие обязательных методов
|
||||||
|
required_methods = ["fetch_data", "get_default_parser_settings", "_fetch_data"]
|
||||||
|
for m in required_methods:
|
||||||
|
if not hasattr(self, m):
|
||||||
|
raise ValueError(f"Плагин {type(self).__name__} не реализует метод {m}.")
|
||||||
|
self.logger.debug(f"Плагин {self.__class__.__name__} прошел валидацию.")
|
||||||
|
return True
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.functional import cached_property
|
|
||||||
from .models import ScheduledTask
|
from .models import ScheduledTask
|
||||||
from django.templatetags.static import static
|
from django.templatetags.static import static
|
||||||
from scheduler.utils import get_project_functions
|
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):
|
class ScheduledTaskForm(forms.ModelForm):
|
||||||
|
"""
|
||||||
|
Форма для модели ScheduledTask с кастомным полем для выбора дней недели.
|
||||||
|
"""
|
||||||
DAYS_OF_WEEK_CHOICES = [
|
DAYS_OF_WEEK_CHOICES = [
|
||||||
(0, "Воскресенье"),
|
(0, "Воскресенье"),
|
||||||
(1, "Понедельник"),
|
(1, "Понедельник"),
|
||||||
@@ -25,7 +23,7 @@ class ScheduledTaskForm(forms.ModelForm):
|
|||||||
choices=DAYS_OF_WEEK_CHOICES,
|
choices=DAYS_OF_WEEK_CHOICES,
|
||||||
widget=forms.CheckboxSelectMultiple,
|
widget=forms.CheckboxSelectMultiple,
|
||||||
label="Дни недели",
|
label="Дни недели",
|
||||||
required=False, # Опционально
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -36,24 +34,32 @@ class ScheduledTaskForm(forms.ModelForm):
|
|||||||
"minutes",
|
"minutes",
|
||||||
"hours",
|
"hours",
|
||||||
"months",
|
"months",
|
||||||
"weekdays", # Используем только поле с галочками
|
"weekdays",
|
||||||
"active",
|
"active",
|
||||||
]
|
]
|
||||||
|
|
||||||
def clean_weekdays(self):
|
def clean_weekdays(self):
|
||||||
"""
|
"""
|
||||||
Преобразуем список выбранных дней в строку для хранения в базе.
|
Преобразует список выбранных дней в строку для сохранения в базе.
|
||||||
"""
|
"""
|
||||||
weekdays = self.cleaned_data.get("weekdays", [])
|
weekdays = self.cleaned_data.get("weekdays", [])
|
||||||
return ",".join(map(str, weekdays))
|
return ",".join(map(str, weekdays))
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ScheduledTask)
|
@admin.register(ScheduledTask)
|
||||||
class ScheduledTaskAdmin(admin.ModelAdmin):
|
class ScheduledTaskAdmin(admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
Кастомный класс для управления ScheduledTask в админке.
|
||||||
|
"""
|
||||||
form = ScheduledTaskForm
|
form = ScheduledTaskForm
|
||||||
list_display = ("task_name", "function_path", "minutes", "hours", "months", "weekdays", "active", "formatted_last_run")
|
list_display = ("task_name", "function_path", "minutes", "hours", "months", "weekdays", "active", "formatted_last_run")
|
||||||
list_filter = ("active",)
|
list_filter = ("active",)
|
||||||
search_fields = ("task_name", "function_path")
|
search_fields = ("task_name", "function_path")
|
||||||
|
|
||||||
def formatted_last_run(self, obj):
|
def formatted_last_run(self, obj):
|
||||||
|
"""
|
||||||
|
Отформатированный вывод времени последнего запуска задачи.
|
||||||
|
"""
|
||||||
return obj.last_run.strftime("%Y-%m-%d %H:%M:%S") if obj.last_run else "Никогда"
|
return obj.last_run.strftime("%Y-%m-%d %H:%M:%S") if obj.last_run else "Никогда"
|
||||||
formatted_last_run.short_description = "Последний запуск"
|
|
||||||
|
formatted_last_run.short_description = "Последний запуск"
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||||
|
|
||||||
|
scheduler_instance = AsyncIOScheduler()
|
||||||
|
|
||||||
class SchedulerConfig(AppConfig):
|
class SchedulerConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'scheduler'
|
name = 'scheduler'
|
||||||
verbose_name="Планировщик заданий"
|
verbose_name = 'Планировщик задач'
|
||||||
|
def ready(self):
|
||||||
|
"""
|
||||||
|
Метод ready вызывается при старте приложения.
|
||||||
|
Здесь не нужно запускать scheduler_instance.start(), чтобы избежать ошибок.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|||||||
39
scheduler/management/commands/start_scheduler.py
Normal file
39
scheduler/management/commands/start_scheduler.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import asyncio
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from scheduler.apps import scheduler_instance
|
||||||
|
from scheduler.tasks import load_tasks_to_scheduler
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Запуск планировщика задач"
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Создаёт новый event loop, запускает планировщик и загружает задачи.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
print("Проверка состояния перед запуском:")
|
||||||
|
print(f"Scheduler instance: {scheduler_instance}")
|
||||||
|
|
||||||
|
# Создаём новый event loop
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
|
||||||
|
# Устанавливаем event loop в планировщик
|
||||||
|
scheduler_instance.configure(event_loop=loop)
|
||||||
|
|
||||||
|
# Запускаем планировщик
|
||||||
|
scheduler_instance.start()
|
||||||
|
|
||||||
|
# Загружаем задачи
|
||||||
|
load_tasks_to_scheduler(scheduler_instance)
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS("Планировщик успешно запущен."))
|
||||||
|
|
||||||
|
# Удерживаем цикл событий
|
||||||
|
loop.run_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
self.stdout.write(self.style.WARNING("Остановка планировщика."))
|
||||||
|
finally:
|
||||||
|
# Завершаем работу планировщика
|
||||||
|
scheduler_instance.shutdown(wait=False)
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# 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='Последний запуск')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
17
scheduler/reload_tasks.py
Normal file
17
scheduler/reload_tasks.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from scheduler.tasks import load_tasks_to_scheduler
|
||||||
|
from scheduler.apps import scheduler_instance
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Перезагрузка задач в планировщике"
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
# Удаляем все существующие задачи
|
||||||
|
scheduler_instance.remove_all_jobs()
|
||||||
|
|
||||||
|
# Загружаем задачи заново
|
||||||
|
load_tasks_to_scheduler(scheduler_instance)
|
||||||
|
self.stdout.write(self.style.SUCCESS("Задачи успешно перезагружены."))
|
||||||
|
except Exception as e:
|
||||||
|
self.stdout.write(self.style.ERROR(f"Ошибка перезагрузки задач: {e}"))
|
||||||
@@ -5,7 +5,6 @@ from scheduler.models import ScheduledTask
|
|||||||
import importlib
|
import importlib
|
||||||
from apscheduler.triggers.cron import CronTrigger
|
from apscheduler.triggers.cron import CronTrigger
|
||||||
|
|
||||||
|
|
||||||
def format_weekdays(weekdays):
|
def format_weekdays(weekdays):
|
||||||
"""Преобразует список дней недели в строку."""
|
"""Преобразует список дней недели в строку."""
|
||||||
if isinstance(weekdays, list):
|
if isinstance(weekdays, list):
|
||||||
@@ -43,7 +42,10 @@ def setup_scheduler():
|
|||||||
print("Планировщик запущен.")
|
print("Планировщик запущен.")
|
||||||
return scheduler
|
return scheduler
|
||||||
|
|
||||||
def load_tasks_to_scheduler(scheduler: BaseScheduler):
|
def load_tasks_to_scheduler(scheduler):
|
||||||
|
"""
|
||||||
|
Загружает активные задачи в планировщик.
|
||||||
|
"""
|
||||||
tasks = ScheduledTask.objects.filter(active=True)
|
tasks = ScheduledTask.objects.filter(active=True)
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ import os
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
import os
|
||||||
|
from pprint import pprint
|
||||||
|
from app_settings.app_settings import load_database_settings
|
||||||
|
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
@@ -31,10 +34,10 @@ SECRET_KEY = 'django-insecure-l_8uu8#p*^zf)9zry80)6u+!+2g1a4tg!wx7@^!uw(+^axyh&h
|
|||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
ALLOWED_HOSTS = ['0.0.0.0', '192.168.219.140', '127.0.0.1', '192.168.219.114', 'a66a-182-226-158-253.ngrok-free.app', '*.ngrok-free.app']
|
ALLOWED_HOSTS = ['0.0.0.0', '192.168.219.140', '127.0.0.1', 'localhost', '192.168.219.114', '588a-182-226-158-253.ngrok-free.app', '*.ngrok-free.app']
|
||||||
|
|
||||||
CSRF_TRUSTED_ORIGINS = [
|
CSRF_TRUSTED_ORIGINS = [
|
||||||
'http://a66a-182-226-158-253.ngrok-free.app',
|
'http://588a-182-226-158-253.ngrok-free.app',
|
||||||
'https://*.ngrok-free.app', # Это подойдет для любых URL, связанных с ngrok
|
'https://*.ngrok-free.app', # Это подойдет для любых URL, связанных с ngrok
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -103,10 +106,11 @@ DATABASES = {
|
|||||||
'PASSWORD': os.getenv('DB_PASSWORD'), # Пароль пользователя
|
'PASSWORD': os.getenv('DB_PASSWORD'), # Пароль пользователя
|
||||||
'HOST': os.getenv('DB_HOST', default='0.0.0.0'), # Хост (по умолчанию localhost)
|
'HOST': os.getenv('DB_HOST', default='0.0.0.0'), # Хост (по умолчанию localhost)
|
||||||
'PORT': os.getenv('DB_PORT', default=3308), # Порт (по умолчанию 3306)
|
'PORT': os.getenv('DB_PORT', default=3308), # Порт (по умолчанию 3306)
|
||||||
|
'ATOMIC_REQUESTS': True,
|
||||||
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
# load_database_settings(DATABASES)
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-09 09:30
|
|
||||||
|
|
||||||
import django.contrib.auth.models
|
|
||||||
import django.contrib.auth.validators
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django.utils.timezone
|
|
||||||
import uuid
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('auth', '0012_alter_user_first_name_max_length'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='LocalUserActivityLog',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
|
||||||
('user_id', models.IntegerField()),
|
|
||||||
('activity_type', models.CharField(max_length=255)),
|
|
||||||
('timestamp', models.DateTimeField()),
|
|
||||||
('additional_data', models.JSONField(blank=True, null=True)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='UserActivityLog',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('user_id', models.BigIntegerField(verbose_name='ID пользователя')),
|
|
||||||
('ip', models.CharField(blank=True, max_length=100, null=True, verbose_name='IP адрес')),
|
|
||||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Создан')),
|
|
||||||
('timestamp', models.IntegerField(verbose_name='Время')),
|
|
||||||
('date_time', models.DateTimeField(verbose_name='Дата')),
|
|
||||||
('referred', models.CharField(blank=True, max_length=255, null=True)),
|
|
||||||
('agent', models.CharField(blank=True, max_length=255, null=True, verbose_name='Браузер')),
|
|
||||||
('platform', models.CharField(blank=True, max_length=255, null=True)),
|
|
||||||
('version', models.CharField(blank=True, max_length=50, null=True)),
|
|
||||||
('model', models.CharField(blank=True, max_length=255, null=True)),
|
|
||||||
('device', models.CharField(blank=True, max_length=50, null=True)),
|
|
||||||
('UAString', models.TextField(blank=True, null=True)),
|
|
||||||
('location', models.CharField(blank=True, max_length=255, null=True)),
|
|
||||||
('page_id', models.BigIntegerField(blank=True, null=True)),
|
|
||||||
('url_parameters', models.TextField(blank=True, null=True)),
|
|
||||||
('page_title', models.CharField(blank=True, max_length=255, null=True)),
|
|
||||||
('type', models.CharField(blank=True, max_length=50, null=True)),
|
|
||||||
('last_counter', models.IntegerField(blank=True, null=True)),
|
|
||||||
('hits', models.IntegerField(blank=True, null=True)),
|
|
||||||
('honeypot', models.BooleanField(blank=True, null=True)),
|
|
||||||
('reply', models.BooleanField(blank=True, null=True)),
|
|
||||||
('page_url', models.CharField(blank=True, max_length=255, null=True)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Журнал активности',
|
|
||||||
'verbose_name_plural': 'Журналы активности',
|
|
||||||
'db_table': 'user_activity_log',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='User',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
|
||||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
|
||||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
|
||||||
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
|
||||||
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
|
||||||
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
|
||||||
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
|
|
||||||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
|
||||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
|
||||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
|
||||||
('telegram_id', models.BigIntegerField(blank=True, null=True, unique=True, verbose_name='ID Телеграм')),
|
|
||||||
('chat_id', models.BigIntegerField(blank=True, null=True, unique=True, verbose_name='ID чата в телеграм')),
|
|
||||||
('role', models.CharField(choices=[('admin', 'Администратор системы'), ('hotel_user', 'Сотрудник отеля')], default='hotel_user', max_length=20, verbose_name='Роль')),
|
|
||||||
('confirmed', models.BooleanField(default=False, verbose_name='Подтвержден')),
|
|
||||||
('groups', models.ManyToManyField(blank=True, related_name='custom_user_set', to='auth.group')),
|
|
||||||
('user_permissions', models.ManyToManyField(blank=True, related_name='custom_user_set', to='auth.permission')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Пользователь',
|
|
||||||
'verbose_name_plural': 'Пользователи',
|
|
||||||
},
|
|
||||||
managers=[
|
|
||||||
('objects', django.contrib.auth.models.UserManager()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
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': 'Способы оповещений',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='UserConfirmation',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('confirmation_code', models.UUIDField(default=uuid.uuid4, verbose_name='Код подтверждения')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создан: ')),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='Пользователь')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Подтверждение пользователя',
|
|
||||||
'verbose_name_plural': 'Подтверждения пользователей',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2024-12-13 00:15
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('users', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='LocalUserActivityLog',
|
|
||||||
),
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='UserActivityLog',
|
|
||||||
),
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='UserConfirmation',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
Reference in New Issue
Block a user