RealtyCalendar plugin

This commit is contained in:
2024-12-24 15:43:43 +09:00
parent 77bf0b8381
commit d5622f41a2
58 changed files with 374 additions and 1714 deletions

View File

@@ -1,4 +1,3 @@
import logging
from datetime import timedelta
from urllib.parse import parse_qs
from django.utils import timezone
@@ -7,8 +6,8 @@ from hotels.models import Reservation, Hotel
from .models import UserActivityLog, ViolationLog
from touchh.utils.log import CustomLogger
# Настройка логирования
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger = CustomLogger(__name__).get_logger()
class ReservationChecker:
"""

View File

@@ -202,63 +202,56 @@ class DataSyncManager:
self.logger.error(f"Error fetching data: {e}")
return []
def update_sync_log(self, hotel, recieved_records, processed_records):
"""
Обновляет или создает запись в таблице SyncLog.
"""
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:
log.recieved_records = recieved_records
log.processed_records = processed_records
self.logger.info(f"Sync log created for hotel '{hotel.name}'.")
else:
log.recieved_records += recieved_records
log.processed_records += processed_records
log.save()
self.logger.info(f"Sync log updated for hotel '{hotel.name}'.")
self.logger.info(f"Sync log updated for hotel '{hotel.name}'.")
except Exception as 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):
"""
Обрабатывает и сохраняет данные из внешнего источника.
:param rows: Список строк данных, полученных из базы данных.
"""
seen_entries = set()
hotel_processed_counts = {} # Словарь для подсчёта записей по каждому отелю
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:
# Получение или создание отеля
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"))
if not hotel:
self.logger.warning(f"Skipping record: Failed to create or retrieve hotel with ID {hotel_id}")
continue
# Получение или создание комнаты
room = self.hotel_manager.get_or_create_room(hotel, room_number)
if not room:
self.logger.warning(f"Skipping record: Failed to create or retrieve room {room_number} in hotel {hotel.name}")
continue
# Создание или обновление записи активности пользователя
UserActivityLog.objects.update_or_create(
external_id=row.get("id"),
defaults={
@@ -270,14 +263,23 @@ class DataSyncManager:
"url_parameters": parsed_params,
"page_title": self.data_processor.decode_html_entities(row.get("page_title")) or "Untitled",
"page_url": row.get("page_url") or "",
"page_id": row.get("page_id") or 0,
"hits": row.get("hits") or 0,
}
)
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:
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):
@@ -292,7 +294,8 @@ class DataSyncManager:
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.")
active_db_settings = ExternalDBSettings.objects.filter(is_active=True)

View File

@@ -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': 'Несовпадения в заселении',
},
),
]

View File

@@ -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),
),
]

View File

@@ -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),
),
]

View File

@@ -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),
),
]

View File

@@ -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='Импортирован в основную базу')),
],
),
]

View File

@@ -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': 'Импортированные отели'},
),
]

View File

@@ -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),
),
]

View File

@@ -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'),
),
]

View File

@@ -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='Отображаемое имя'),
),
]

View File

@@ -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': 'Журналы синхронизации',
},
),
]

View File

@@ -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 отеля'),
),
]

View File

@@ -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': 'Журналы нарушений',
},
),
]

View File

@@ -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='Метка времени'),
),
]

View File

@@ -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 пользователя'),
),
]

View File

@@ -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 страницы'),
),
]

View File

@@ -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 пользователя'),
),
]

View File

@@ -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,
),
]

View File

@@ -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='Полученные записи'),
),
]

View File

@@ -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='Отель'),
),
]

View File

@@ -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='Количество обращений'),
),
]

View File

@@ -1,50 +1,24 @@
# settings.py
from decouple import config
from django.conf import settings
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')
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
load_database_settings()
# Чтение локальных баз данных
local_databases = LocalDatabase.objects.filter(is_active=True)
# Основная база данных
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': config('DB_NAME'),
'USER': config('DB_USER'),
'PASSWORD': config('DB_PASSWORD'),
'HOST': config('DB_HOST'),
'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,
}
try:
local_db_settings = LocalDatabase.objects.filter(is_active=True)
for db in local_db_settings:
databases[db.name] = {
'ENGINE': db.engine, # Можно хранить тип движка в базе
'NAME': db.database,
'USER': db.user,
'PASSWORD': db.password,
'HOST': db.host,
'PORT': db.port,
'ATOMIC_REQUESTS': True, # Убедитесь, что добавляете ATOMIC_REQUESTS
}
except Exception as e:
print(f"Ошибка загрузки локальных баз данных: {e}")

File diff suppressed because one or more lines are too long

View File

@@ -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 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 бота и планировщика"
help = "Запуск Telegram бота"
def handle(self, *args, **options):
# Установка Django окружения
@@ -21,42 +84,31 @@ class Command(BaseCommand):
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 бот и планировщик успешно запущены."))
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 бота и планировщика"))
self.stdout.write(self.style.ERROR("Завершение работы Telegram бота"))
finally:
loop.close()

View File

@@ -4,6 +4,9 @@ from hotels.models import Hotel, UserHotel
from users.models import User
from pms_integration.manager import PMSIntegrationManager
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):
"""Отображение списка отелей, связанных с пользователем."""
query = update.callback_query
@@ -81,7 +84,8 @@ async def check_pms(update, context):
try:
# Получение ID отеля из callback_data
hotel_id = query.data.split("_")[2]
logger.debug(f"Hotel ID: {hotel_id}")
logger.debug(f"Hotel ID type : {type(hotel_id)}")
# Получение конфигурации отеля и PMS
hotel = await sync_to_async(Hotel.objects.select_related('pms').get)(id=hotel_id)
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):
# Плагин поддерживает метод fetch_data
report = await pms_manager.plugin._fetch_data()
else:
await query.edit_message_text("Подходящий способ интеграции с PMS не найден.")
return
@@ -110,6 +115,7 @@ async def check_pms(update, context):
f"Обработано записей: {report['processed_items']}\n"
f"Ошибки: {len(report['errors'])}"
)
logger.info(f'Result_Message: {result_message}\n Result_meaage_type: {type(result_message)}')
if report["errors"]:
result_message += "\n\nСписок ошибок:\n" + "\n".join(report["errors"])

View File

@@ -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

View File

@@ -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': 'Отели',
},
),
]

View File

@@ -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='Отель'),
),
]

View File

@@ -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'),
),
]

View File

@@ -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),
),
]

View File

@@ -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='Импортированный отель'),
),
]

View File

@@ -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',
),
]

View File

@@ -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 отеля'),
),
]

View File

@@ -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='Часовой пояс'),
),
]

View File

@@ -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

View File

@@ -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='Номер комнаты'),
),
]

View File

@@ -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='Номер комнаты'),
),
]

View File

@@ -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='Номер комнаты'),
),
]

View File

@@ -18,7 +18,7 @@ class PluginLoader:
print("Загрузка плагинов:")
for file in os.listdir(PluginLoader.PLUGIN_PATH):
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]}"
try:
module = importlib.import_module(module_name)
@@ -26,7 +26,7 @@ class PluginLoader:
cls = getattr(module, attr)
if isinstance(cls, type) and issubclass(cls, BasePMSPlugin) and cls is not BasePMSPlugin:
plugin_name = file[:-7] # Убираем `_pms` из имени файла
print(f" Загружен плагин {plugin_name}: {cls.__name__}")
# print(f" Загружен плагин {plugin_name}: {cls.__name__}")
plugins[plugin_name] = cls
except Exception as e:
print(f" Ошибка загрузки плагина {module_name}: {e}")

View File

@@ -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')],
},
),
]

View File

@@ -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),
),
]

View File

@@ -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='Плагин'),
),
]

View File

@@ -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='Плагин'),
),
]

View File

@@ -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='Публичный ключ'),
),
]

View File

@@ -74,7 +74,7 @@ class EcviPMSPlugin(BasePMSPlugin):
except requests.exceptions.RequestException as e:
self.logger.error(f"Ошибка запроса: {e}")
return []
self.logger.debug(f"\n\n\n\n\ndata: {data}\n\n\n\n\n")
# Фильтрация данных
filtered_data = [
{
@@ -87,7 +87,6 @@ class EcviPMSPlugin(BasePMSPlugin):
} for item in data if isinstance(item, dict) and item.get('occupancy') in ['проживание', 'под выезд', 'под заезд']
]
self.logger.debug(f"filtered_data: {filtered_data}")
# Сохранение данных в базу данных
@@ -108,7 +107,6 @@ class EcviPMSPlugin(BasePMSPlugin):
# Проверяем, существует ли уже резервация с таким внешним ID
reservation_id = item.get('reservation_id')
self.logger.debug(f"-----\n\n\nITEM : {item}\n\n\n---")
if not reservation_id:
self.logger.error("Ошибка: 'reservation_id' отсутствует в данных.")
return
@@ -123,6 +121,7 @@ class EcviPMSPlugin(BasePMSPlugin):
await sync_to_async(Reservation.objects.update_or_create)(
reservation_id=reservation_id,
defaults={
''
'room_number': item.get('room_number'),
'room_type': item.get('room_type'),
'check_in': item.get('checkin'),

View File

@@ -4,17 +4,16 @@ import json
from .base_plugin import BasePMSPlugin
from datetime import datetime, timedelta
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):
"""Плагин для импорта данных из системы RealtyCalendar
"""
"""Плагин для импорта данных из системы RealtyCalendar."""
def __init__(self, config):
super().__init__(config)
self.public_key = config.public_key
self.private_key = config.private_key
self.api_url = config.url.rstrip("/")
self.logger = CustomLogger(name="RealtyCalendarPlugin", log_level="DEBUG").get_logger()
if not self.public_key or not self.private_key:
raise ValueError("Публичный или приватный ключ отсутствует для RealtyCalendar")
@@ -31,8 +30,8 @@ class RealtyCalendarPlugin(BasePMSPlugin):
"""
Возвращает отсортированный по имени список ключей.
"""
sorted_keys = sorted(list(obj.keys()))
print(f"[DEBUG] Отсортированные ключи: {sorted_keys}")
sorted_keys = sorted(obj.keys())
self.logger.debug(f"Отсортированные ключи: {sorted_keys}")
return sorted_keys
def _generate_data_string(self, obj):
@@ -41,7 +40,7 @@ class RealtyCalendarPlugin(BasePMSPlugin):
"""
sorted_keys = self._get_sorted_keys(obj)
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
def _generate_md5(self, string):
@@ -49,7 +48,7 @@ class RealtyCalendarPlugin(BasePMSPlugin):
Генерирует MD5-хеш от строки.
"""
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
def _generate_sign(self, data):
@@ -57,16 +56,17 @@ class RealtyCalendarPlugin(BasePMSPlugin):
Генерирует подпись для данных запроса.
"""
data_string = self._generate_data_string(data)
print(f"[DEBUG] Строка для подписи: {data_string}")
self.logger.debug(f"Строка для подписи: {data_string}")
sign = self._generate_md5(data_string)
print(f"[DEBUG] Подпись: {sign}")
self.logger.debug(f"Подпись: {sign}")
return sign
def _fetch_data(self):
async def _fetch_data(self):
"""
Выполняет запрос к 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 = {
"Accept": "application/json",
"Content-Type": "application/json",
@@ -77,81 +77,150 @@ class RealtyCalendarPlugin(BasePMSPlugin):
data = {
"begin_date": (now - timedelta(days=7)).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)
# Отправляем запрос
print(f"[DEBUG] URL запроса: {base_url}")
print(f"[DEBUG] Заголовки: {headers}")
print(f"[DEBUG] Данные запроса: {data}")
self.logger.debug(f"URL запроса: {base_url}")
self.logger.debug(f"Заголовки: {headers}")
self.logger.debug(f"Данные запроса: {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:
bookings = response.json().get("bookings", [])
print(f"[DEBUG] Полученные данные бронирований: {bookings}")
return bookings
else:
raise ValueError(f"Ошибка API RealtyCalendar: {response.status_code}, {response.text}")
try:
response_data = response.json()
self.logger.debug(f"Тип данных ответа: {type(response_data)}")
if not isinstance(response_data, dict) or "bookings" not in response_data:
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:
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)
batches = [data[i:i + batch_size] for i in range(0, total_records, batch_size)]
self.logger.info(f"Обработка {total_records} записей в {len(batches)} партиях...")
self.logger.info(f"Общее количество записей для обработки: {total_records}")
for batch_index, batch in enumerate(batches):
self.logger.info(f"Обработка партии {batch_index + 1}/{len(batches)}")
for index, item in enumerate(data, start=1):
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:
if item.get("is_delete", False):
self.logger.info(f"Пропущена запись с ID {item.get('id')} (удалена).")
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']}.")
check_in = timezone.make_aware(datetime.strptime(item.get('begin_date'), "%Y-%m-%d"))
check_out = timezone.make_aware(datetime.strptime(item.get('end_date'), "%Y-%m-%d"))
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:
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

View File

@@ -1,16 +1,14 @@
from django.contrib import admin
from django import forms
from django.utils.functional import cached_property
from .models import ScheduledTask
from django.templatetags.static import static
from scheduler.utils import get_project_functions
class CustomAdmin(admin.ModelAdmin):
class Media:
css = {"all": (static("scheduler/admin.css"),)}
js = (static("scheduler/admin.js"),)
class ScheduledTaskForm(forms.ModelForm):
"""
Форма для модели ScheduledTask с кастомным полем для выбора дней недели.
"""
DAYS_OF_WEEK_CHOICES = [
(0, "Воскресенье"),
(1, "Понедельник"),
@@ -25,7 +23,7 @@ class ScheduledTaskForm(forms.ModelForm):
choices=DAYS_OF_WEEK_CHOICES,
widget=forms.CheckboxSelectMultiple,
label="Дни недели",
required=False, # Опционально
required=False,
)
class Meta:
@@ -36,24 +34,32 @@ class ScheduledTaskForm(forms.ModelForm):
"minutes",
"hours",
"months",
"weekdays", # Используем только поле с галочками
"weekdays",
"active",
]
def clean_weekdays(self):
"""
Преобразуем список выбранных дней в строку для хранения в базе.
Преобразует список выбранных дней в строку для сохранения в базе.
"""
weekdays = self.cleaned_data.get("weekdays", [])
return ",".join(map(str, weekdays))
@admin.register(ScheduledTask)
class ScheduledTaskAdmin(admin.ModelAdmin):
"""
Кастомный класс для управления ScheduledTask в админке.
"""
form = ScheduledTaskForm
list_display = ("task_name", "function_path", "minutes", "hours", "months", "weekdays", "active", "formatted_last_run")
list_filter = ("active",)
search_fields = ("task_name", "function_path")
def formatted_last_run(self, obj):
"""
Отформатированный вывод времени последнего запуска задачи.
"""
return obj.last_run.strftime("%Y-%m-%d %H:%M:%S") if obj.last_run else "Никогда"
formatted_last_run.short_description = "Последний запуск"
formatted_last_run.short_description = "Последний запуск"

View File

@@ -1,7 +1,15 @@
from django.apps import AppConfig
from apscheduler.schedulers.asyncio import AsyncIOScheduler
scheduler_instance = AsyncIOScheduler()
class SchedulerConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'scheduler'
verbose_name="Планировщик заданий"
verbose_name = 'Планировщик задач'
def ready(self):
"""
Метод ready вызывается при старте приложения.
Здесь не нужно запускать scheduler_instance.start(), чтобы избежать ошибок.
"""
pass

View 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)

View File

@@ -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
View 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}"))

View File

@@ -5,7 +5,6 @@ from scheduler.models import ScheduledTask
import importlib
from apscheduler.triggers.cron import CronTrigger
def format_weekdays(weekdays):
"""Преобразует список дней недели в строку."""
if isinstance(weekdays, list):
@@ -43,7 +42,10 @@ def setup_scheduler():
print("Планировщик запущен.")
return scheduler
def load_tasks_to_scheduler(scheduler: BaseScheduler):
def load_tasks_to_scheduler(scheduler):
"""
Загружает активные задачи в планировщик.
"""
tasks = ScheduledTask.objects.filter(active=True)
for task in tasks:
try:

View File

@@ -16,6 +16,9 @@ import os
from dotenv import 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'.
@@ -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!
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 = [
'http://a66a-182-226-158-253.ngrok-free.app',
'http://588a-182-226-158-253.ngrok-free.app',
'https://*.ngrok-free.app', # Это подойдет для любых URL, связанных с ngrok
]
@@ -103,10 +106,11 @@ DATABASES = {
'PASSWORD': os.getenv('DB_PASSWORD'), # Пароль пользователя
'HOST': os.getenv('DB_HOST', default='0.0.0.0'), # Хост (по умолчанию localhost)
'PORT': os.getenv('DB_PORT', default=3308), # Порт (по умолчанию 3306)
'ATOMIC_REQUESTS': True,
},
}
# load_database_settings(DATABASES)
# Password validation
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators

View File

@@ -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': 'Подтверждения пользователей',
},
),
]

View File

@@ -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',
),
]