antifroud_check
This commit is contained in:
@@ -5,7 +5,8 @@ from django.shortcuts import redirect, get_object_or_404
|
|||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from antifroud.models import UserActivityLog, ExternalDBSettings, RoomDiscrepancy, ImportedHotel, SyncLog, ViolationLog
|
from antifroud.models import UserActivityLog, ExternalDBSettings, RoomDiscrepancy, ImportedHotel, SyncLog, ViolationLog
|
||||||
from hotels.models import Hotel
|
|
||||||
|
from hotels.models import Hotel, Room
|
||||||
import pymysql
|
import pymysql
|
||||||
import logging
|
import logging
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@@ -114,14 +115,32 @@ class UserActivityLogAdmin(admin.ModelAdmin):
|
|||||||
search_fields = ("page_title", "url_parameters")
|
search_fields = ("page_title", "url_parameters")
|
||||||
list_filter = ("page_title", "created")
|
list_filter = ("page_title", "created")
|
||||||
readonly_fields = ("created", "timestamp")
|
readonly_fields = ("created", "timestamp")
|
||||||
|
def get_hotel_name(self):
|
||||||
|
"""
|
||||||
|
Возвращает название отеля на основе связанного page_id.
|
||||||
|
"""
|
||||||
|
if self.page_id:
|
||||||
|
try:
|
||||||
|
room = Room.objects.get(id=self.page_id)
|
||||||
|
return room.hotel.name
|
||||||
|
except Room.DoesNotExist:
|
||||||
|
return "Отель не найден"
|
||||||
|
return "Нет данных"
|
||||||
|
|
||||||
|
def get_room_number(self):
|
||||||
|
"""
|
||||||
|
Возвращает номер комнаты на основе связанного page_id.
|
||||||
|
"""
|
||||||
|
if self.page_id:
|
||||||
|
try:
|
||||||
|
room = Room.objects.get(id=self.page_id)
|
||||||
|
return room.number
|
||||||
|
except Room.DoesNotExist:
|
||||||
|
return "Комната не найдена"
|
||||||
|
return "Нет данных"
|
||||||
|
|
||||||
@admin.register(RoomDiscrepancy)
|
get_hotel_name.short_description = "Отель"
|
||||||
class RoomDiscrepancyAdmin(admin.ModelAdmin):
|
get_room_number.short_description = "Комната"
|
||||||
list_display = ("hotel", "room_number", "booking_id", "check_in_date_expected", "check_in_date_actual", "discrepancy_type", "created_at")
|
|
||||||
search_fields = ("hotel__name", "room_number", "booking_id")
|
|
||||||
list_filter = ("discrepancy_type", "created_at")
|
|
||||||
readonly_fields = ("created_at",)
|
|
||||||
|
|
||||||
|
|
||||||
from .views import import_selected_hotels
|
from .views import import_selected_hotels
|
||||||
|
|||||||
@@ -1,79 +1,160 @@
|
|||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
from urllib.parse import parse_qs
|
from urllib.parse import parse_qs
|
||||||
|
from django.utils import timezone
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from hotels.models import Reservation, Hotel
|
from hotels.models import Reservation, Hotel
|
||||||
from .models import UserActivityLog, ViolationLog
|
from .models import UserActivityLog, ViolationLog
|
||||||
|
|
||||||
# Настройка логирования
|
# Настройка логирования
|
||||||
logging.basicConfig(level=logging.INFO) # Устанавливаем уровень логирования
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__) # Создаем логгер для текущего модуля
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def check_reservations_and_generate_report():
|
class ReservationChecker:
|
||||||
now = datetime.now()
|
"""
|
||||||
start_time = (now - timedelta(hours=12))
|
Класс для проверки несоответствий между бронированиями и логами заселения.
|
||||||
end_time = now
|
"""
|
||||||
logger.info(f"Starting reservation check from {start_time} to {end_time}")
|
|
||||||
|
|
||||||
# Получаем логи активности за период
|
def __init__(self):
|
||||||
user_logs = UserActivityLog.objects.filter(created__range=(start_time, end_time))
|
"""
|
||||||
logger.info(f"Found {len(user_logs)} logs for analysis.")
|
Инициализация времени проверки и списка нарушений.
|
||||||
|
"""
|
||||||
|
self.start_time = timezone.now() - timedelta(days=30)
|
||||||
|
self.end_time = timezone.now()
|
||||||
|
self.violations = []
|
||||||
|
|
||||||
violations = [] # Список для записи нарушений
|
def log_info(self, message):
|
||||||
|
"""Логирование информационных сообщений."""
|
||||||
|
logger.info(message)
|
||||||
|
|
||||||
for i, log in enumerate(user_logs, start=1):
|
def log_warning(self, message):
|
||||||
logger.debug(f"Processing log {i}: {log}")
|
"""Логирование предупреждений."""
|
||||||
|
logger.warning(message)
|
||||||
|
|
||||||
if not log.url_parameters:
|
def log_error(self, message):
|
||||||
logger.warning(f"Log {i} skipped due to missing URL parameters.")
|
"""Логирование ошибок."""
|
||||||
continue # Пропускаем записи без параметров URL
|
logger.error(message)
|
||||||
|
|
||||||
# Парсим параметры URL
|
def fetch_user_logs(self):
|
||||||
params = parse_qs(log.url_parameters)
|
"""
|
||||||
external_id = params.get("utm_content", [None])[0] # ID отеля
|
Извлекает записи из UserActivityLog за последние 12 часов.
|
||||||
room_number = params.get("utm_term", [None])[0] # Номер комнаты
|
"""
|
||||||
|
print(f"Fetching user logs from {self.start_time} to {self.end_time}")
|
||||||
|
user_logs = UserActivityLog.objects.filter(created__range=(self.start_time, self.end_time))
|
||||||
|
print(f"Found {user_logs.count()} logs for analysis.")
|
||||||
|
return user_logs
|
||||||
|
|
||||||
logger.debug(f"Log {i} parsed parameters: external_id={external_id}, room_number={room_number}")
|
def fetch_hotels(self, hotel_ids):
|
||||||
|
"""
|
||||||
|
Извлекает отели по hotel_id из логов.
|
||||||
|
"""
|
||||||
|
hotels = {hotel.hotel_id: hotel for hotel in Hotel.objects.filter(hotel_id__in=hotel_ids)}
|
||||||
|
self.log_info(f"Найдено {len(hotels)} отелей для сверки.")
|
||||||
|
return hotels
|
||||||
|
|
||||||
if not external_id or not room_number:
|
def find_violations(self, user_logs, hotels):
|
||||||
logger.warning(f"Log {i} skipped due to missing external_id or room_number.")
|
"""
|
||||||
continue # Пропускаем, если данные не извлечены
|
Сопоставляет логи активности с бронированиями и фиксирует нарушения.
|
||||||
|
"""
|
||||||
|
for log in user_logs:
|
||||||
|
if not log.url_parameters:
|
||||||
|
self.log_warning(f"Пропущена запись ID {log.id}: отсутствуют URL-параметры.")
|
||||||
|
continue
|
||||||
|
|
||||||
try:
|
# Парсинг параметров URL
|
||||||
# Находим отель по external_id
|
params = parse_qs(log.url_parameters)
|
||||||
hotel = Hotel.objects.get(external_id=external_id)
|
hotel_id = params.get("utm_content", [None])[0]
|
||||||
logger.debug(f"Log {i}: Found hotel {hotel.name} with external_id {external_id}.")
|
room_number = params.get("utm_term", [None])[0]
|
||||||
except Hotel.DoesNotExist:
|
|
||||||
logger.error(f"Log {i} skipped: No hotel found with external_id {external_id}.")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Ищем бронирование в Reservation
|
print(f"Processing log ID {log.id} with hotel ID {hotel_id} and room number {room_number}")
|
||||||
matching_reservations = Reservation.objects.filter(
|
|
||||||
hotel=hotel,
|
|
||||||
room_number=room_number,
|
|
||||||
check_in__lte=log.created,
|
|
||||||
check_out__gte=log.created
|
|
||||||
)
|
|
||||||
logger.debug(f"Log {i}: Found {len(matching_reservations)} matching reservations.")
|
|
||||||
|
|
||||||
if not matching_reservations.exists():
|
if not hotel_id or not room_number:
|
||||||
# Если бронирование не найдено — записываем нарушение
|
self.log_warning(f"Пропущена запись ID {log.id}: некорректные параметры URL.")
|
||||||
violation_details = (
|
continue
|
||||||
f"Log {i}: No reservation found for room {room_number} in hotel {external_id} at {log.created}."
|
|
||||||
)
|
if hotel_id not in hotels:
|
||||||
violations.append(ViolationLog(
|
self.log_warning(f"Пропущена запись ID {log.id}: отель с ID {hotel_id} не найден.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
hotel = hotels[hotel_id]
|
||||||
|
log_time = timezone.localtime(log.created)
|
||||||
|
|
||||||
|
# Проверка существования бронирования
|
||||||
|
matching_reservations = Reservation.objects.filter(
|
||||||
hotel=hotel,
|
hotel=hotel,
|
||||||
room_number=room_number,
|
room_number=room_number,
|
||||||
violation_type="missed",
|
check_in__lte=log_time,
|
||||||
violation_details=violation_details
|
check_out__gte=log_time
|
||||||
))
|
)
|
||||||
logger.warning(f"Log {i}: Violation recorded - {violation_details}")
|
|
||||||
|
|
||||||
# Сохраняем все нарушения в базу
|
print(f"Found {matching_reservations.count()} matching reservations")
|
||||||
if violations:
|
|
||||||
ViolationLog.objects.bulk_create(violations)
|
|
||||||
logger.info(f"Created {len(violations)} records in violation log.")
|
|
||||||
else:
|
|
||||||
logger.info("No violations found during this check.")
|
|
||||||
|
|
||||||
logger.info("Reservation check completed.")
|
if not matching_reservations.exists():
|
||||||
|
violation_details = (
|
||||||
|
f"Не найдено бронирование для номера {room_number} в отеле '{hotel.name}' на {log_time}."
|
||||||
|
)
|
||||||
|
# Добавляем нарушение, если его ещё нет в базе
|
||||||
|
if not ViolationLog.objects.filter(
|
||||||
|
hotel=hotel,
|
||||||
|
room_number=room_number,
|
||||||
|
violation_type="missed",
|
||||||
|
violation_details=violation_details
|
||||||
|
).exists():
|
||||||
|
self.violations.append(ViolationLog(
|
||||||
|
hotel=hotel,
|
||||||
|
room_number=room_number,
|
||||||
|
violation_type="missed",
|
||||||
|
violation_details=violation_details
|
||||||
|
))
|
||||||
|
self.log_warning(f"Зафиксировано нарушение: {violation_details}")
|
||||||
|
|
||||||
|
def save_violations(self):
|
||||||
|
"""
|
||||||
|
Сохраняет найденные нарушения в базу данных.
|
||||||
|
"""
|
||||||
|
if self.violations:
|
||||||
|
ViolationLog.objects.bulk_create(self.violations)
|
||||||
|
self.log_info(f"Создано {len(self.violations)} записей в ViolationLog.")
|
||||||
|
else:
|
||||||
|
self.log_info("Нарушений не обнаружено.")
|
||||||
|
|
||||||
|
def run_check(self):
|
||||||
|
"""
|
||||||
|
Основной метод для запуска проверки.
|
||||||
|
"""
|
||||||
|
self.log_info(f"Запуск проверки бронирований с {self.start_time} по {self.end_time}.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Получаем логи пользователей
|
||||||
|
user_logs = self.fetch_user_logs()
|
||||||
|
|
||||||
|
# Извлекаем hotel_id из логов
|
||||||
|
hotel_ids = set()
|
||||||
|
for log in user_logs:
|
||||||
|
if log.url_parameters:
|
||||||
|
params = parse_qs(log.url_parameters)
|
||||||
|
hotel_id = params.get("utm_content", [None])[0]
|
||||||
|
if hotel_id:
|
||||||
|
hotel_ids.add(hotel_id)
|
||||||
|
|
||||||
|
# Предзагружаем отели
|
||||||
|
hotels = self.fetch_hotels(hotel_ids)
|
||||||
|
|
||||||
|
# Сравниваем логи с бронированиями
|
||||||
|
self.find_violations(user_logs, hotels)
|
||||||
|
|
||||||
|
# Сохраняем результаты
|
||||||
|
self.save_violations()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log_error(f"Произошла ошибка при выполнении проверки: {e}")
|
||||||
|
|
||||||
|
self.log_info("Проверка бронирований завершена.")
|
||||||
|
|
||||||
|
# Функция для запуска проверки из планировщика
|
||||||
|
def run_reservation_check():
|
||||||
|
"""
|
||||||
|
Функция для запуска проверки бронирований.
|
||||||
|
"""
|
||||||
|
checker = ReservationChecker()
|
||||||
|
checker.run_check()
|
||||||
File diff suppressed because it is too large
Load Diff
18
antifroud/migrations/0013_alter_useractivitylog_timestamp.py
Normal file
18
antifroud/migrations/0013_alter_useractivitylog_timestamp.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 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='Метка времени'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# 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 пользователя'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
antifroud/migrations/0015_alter_useractivitylog_page_id.py
Normal file
18
antifroud/migrations/0015_alter_useractivitylog_page_id.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 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 страницы'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# 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 пользователя'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -4,30 +4,40 @@ from hotels.models import Reservation
|
|||||||
|
|
||||||
|
|
||||||
class UserActivityLog(models.Model):
|
class UserActivityLog(models.Model):
|
||||||
external_id = models.CharField(max_length=255, null=True, blank=True)
|
external_id = models.CharField(max_length=255, unique=True, verbose_name="Внешний ID", db_index=True)
|
||||||
user_id = models.BigIntegerField(verbose_name="ID пользователя")
|
user_id = models.BigIntegerField(verbose_name="ID пользователя", blank=True, null=True, db_index=True)
|
||||||
ip = models.GenericIPAddressField(verbose_name="IP-адрес")
|
ip = models.GenericIPAddressField(verbose_name="IP-адрес", blank=True, null=True, db_index=True)
|
||||||
created = models.DateTimeField(verbose_name="Дата создания")
|
created = models.DateTimeField(verbose_name="Дата создания", blank=True, null=True, db_index=True)
|
||||||
timestamp = models.BigIntegerField(verbose_name="Метка времени")
|
timestamp = models.BigIntegerField(verbose_name="Метка времени", blank=True, null=True)
|
||||||
date_time = models.DateTimeField(verbose_name="Дата и время")
|
date_time = models.DateTimeField(verbose_name="Дата и время", blank=True, null=True)
|
||||||
referred = models.TextField(blank=True, null=True, verbose_name="Реферальная ссылка")
|
referred = models.TextField(blank=True, null=True, verbose_name="Реферальная ссылка")
|
||||||
agent = models.TextField(verbose_name="Агент пользователя")
|
agent = models.TextField(verbose_name="Агент пользователя", blank=True, null=True)
|
||||||
platform = models.CharField(max_length=255, blank=True, null=True, verbose_name="Платформа")
|
platform = models.CharField(max_length=255, blank=True, null=True, verbose_name="Платформа")
|
||||||
version = models.CharField(max_length=255, blank=True, null=True, verbose_name="Версия")
|
version = models.CharField(max_length=255, blank=True, null=True, verbose_name="Версия")
|
||||||
model = models.CharField(max_length=255, blank=True, null=True, verbose_name="Модель устройства")
|
model = models.CharField(max_length=255, blank=True, null=True, verbose_name="Модель устройства")
|
||||||
device = models.CharField(max_length=255, blank=True, null=True, verbose_name="Тип устройства")
|
device = models.CharField(max_length=255, blank=True, null=True, verbose_name="Тип устройства")
|
||||||
UAString = models.TextField(verbose_name="User-Agent строка")
|
UAString = models.TextField(verbose_name="User-Agent строка", blank=True, null=True)
|
||||||
location = models.CharField(max_length=255, blank=True, null=True, verbose_name="Местоположение")
|
location = models.CharField(max_length=255, blank=True, null=True, verbose_name="Местоположение")
|
||||||
page_id = models.BigIntegerField(blank=True, null=True, verbose_name="ID страницы")
|
page_id = models.BigIntegerField(blank=True, null=True, verbose_name="ID страницы", db_index=True)
|
||||||
url_parameters = models.TextField(blank=True, null=True, verbose_name="Параметры URL")
|
url_parameters = models.TextField(blank=True, null=True, verbose_name="Параметры URL")
|
||||||
page_title = models.TextField(blank=True, null=True, verbose_name="Заголовок страницы")
|
page_title = models.TextField(blank=True, null=True, verbose_name="Заголовок страницы")
|
||||||
type = models.CharField(max_length=50, verbose_name="Тип")
|
type = models.CharField(max_length=50, verbose_name="Тип", blank=True, null=True)
|
||||||
last_counter = models.IntegerField(verbose_name="Последний счетчик")
|
last_counter = models.IntegerField(verbose_name="Последний счетчик", blank=True, null=True)
|
||||||
hits = models.IntegerField(verbose_name="Количество обращений")
|
hits = models.IntegerField(verbose_name="Количество обращений", blank=True, null=True)
|
||||||
honeypot = models.BooleanField(verbose_name="Метка honeypot")
|
honeypot = models.BooleanField(verbose_name="Метка honeypot", blank=True, null=True)
|
||||||
reply = models.BooleanField(verbose_name="Ответ пользователя")
|
reply = models.BooleanField(verbose_name="Ответ пользователя", blank=True, null=True)
|
||||||
page_url = models.URLField(blank=True, null=True, verbose_name="URL страницы")
|
page_url = models.URLField(blank=True, null=True, verbose_name="URL страницы")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["external_id"], name="idx_external_id"),
|
||||||
|
models.Index(fields=["user_id"], name="idx_user_id"),
|
||||||
|
models.Index(fields=["ip"], name="idx_ip"),
|
||||||
|
models.Index(fields=["created"], name="idx_created"),
|
||||||
|
models.Index(fields=["page_id"], name="idx_page_id"),
|
||||||
|
]
|
||||||
|
verbose_name = "Лог активности пользователя"
|
||||||
|
verbose_name_plural = "Логи активности пользователей"
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"UserActivityLog {self.id}: {self.page_title}"
|
return f"UserActivityLog {self.id}: {self.page_title}"
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from .models import (
|
|||||||
UserHotel,
|
UserHotel,
|
||||||
APIConfiguration,
|
APIConfiguration,
|
||||||
Reservation,
|
Reservation,
|
||||||
FraudLog
|
Room
|
||||||
)
|
)
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
@@ -27,8 +27,9 @@ class HotelForm(forms.ModelForm):
|
|||||||
|
|
||||||
@admin.register(Hotel)
|
@admin.register(Hotel)
|
||||||
class HotelAdmin(admin.ModelAdmin):
|
class HotelAdmin(admin.ModelAdmin):
|
||||||
list_display = ['name', 'hotel_id', 'pms', 'timezone', 'description']
|
list_display = ['name', 'hotel_id','room_count', 'pms', 'timezone', 'description']
|
||||||
list_filter = ['name', 'pms', 'timezone']
|
list_filter = ['name', 'pms', 'timezone']
|
||||||
|
list_sorting = ['name', 'pms', 'room_count', 'timezone']
|
||||||
def sync_button(self, obj):
|
def sync_button(self, obj):
|
||||||
return format_html(
|
return format_html(
|
||||||
'<a class="button" href="{}">Синхронизировать</a>',
|
'<a class="button" href="{}">Синхронизировать</a>',
|
||||||
@@ -41,8 +42,36 @@ class HotelAdmin(admin.ModelAdmin):
|
|||||||
path('sync/<int:hotel_id>/', self.sync_hotel_data),
|
path('sync/<int:hotel_id>/', self.sync_hotel_data),
|
||||||
]
|
]
|
||||||
return custom_urls + urls
|
return custom_urls + urls
|
||||||
|
def room_count(self, obj):
|
||||||
|
"""
|
||||||
|
Подсчитывает количество комнат, связанных с отелем.
|
||||||
|
"""
|
||||||
|
return Room.objects.filter(hotel=obj).count()
|
||||||
|
room_count.short_description = "Количество комнат"
|
||||||
|
|
||||||
|
def sync_button(self, obj):
|
||||||
|
"""
|
||||||
|
Кнопка синхронизации данных.
|
||||||
|
"""
|
||||||
|
return format_html(
|
||||||
|
'<a class="button" href="{}">Синхронизировать</a>',
|
||||||
|
f"/admin/hotels/sync/{obj.id}/"
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_urls(self):
|
||||||
|
"""
|
||||||
|
Добавление кастомного URL для синхронизации.
|
||||||
|
"""
|
||||||
|
urls = super().get_urls()
|
||||||
|
custom_urls = [
|
||||||
|
path('sync/<int:hotel_id>/', self.sync_hotel_data),
|
||||||
|
]
|
||||||
|
return custom_urls + urls
|
||||||
|
|
||||||
def sync_hotel_data(self, request, hotel_id):
|
def sync_hotel_data(self, request, hotel_id):
|
||||||
|
"""
|
||||||
|
Метод синхронизации данных отеля.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
hotel = Hotel.objects.get(id=hotel_id)
|
hotel = Hotel.objects.get(id=hotel_id)
|
||||||
client = APIClient(hotel.pms)
|
client = APIClient(hotel.pms)
|
||||||
@@ -66,4 +95,9 @@ class ReservationAdmin(admin.ModelAdmin):
|
|||||||
list_filter = ('hotel', 'room_number', 'room_type', 'check_in', 'check_out', 'status', 'price', 'discount')
|
list_filter = ('hotel', 'room_number', 'room_type', 'check_in', 'check_out', 'status', 'price', 'discount')
|
||||||
ordering = ('-check_in',)
|
ordering = ('-check_in',)
|
||||||
|
|
||||||
|
@admin.register(Room)
|
||||||
|
class RoomAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('hotel', 'number', 'external_id', 'description', 'created_at', 'updated_at')
|
||||||
|
search_fields = ('hotel', 'number', 'external_id', 'description')
|
||||||
|
list_filter = ('hotel', 'number', 'external_id','description', 'created_at', 'updated_at')
|
||||||
|
ordering = ('-hotel', '-number')
|
||||||
File diff suppressed because one or more lines are too long
18
hotels/migrations/0012_alter_room_number.py
Normal file
18
hotels/migrations/0012_alter_room_number.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 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='Номер комнаты'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
hotels/migrations/0013_alter_room_number.py
Normal file
18
hotels/migrations/0013_alter_room_number.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 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='Номер комнаты'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
hotels/migrations/0014_alter_room_number.py
Normal file
18
hotels/migrations/0014_alter_room_number.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 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='Номер комнаты'),
|
||||||
|
),
|
||||||
|
]
|
||||||
169
hotels/models.py
169
hotels/models.py
@@ -1,5 +1,7 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
from django.core.validators import MinValueValidator, MaxValueValidator, RegexValidator
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
|
||||||
class APIConfiguration(models.Model):
|
class APIConfiguration(models.Model):
|
||||||
@@ -17,25 +19,33 @@ class APIConfiguration(models.Model):
|
|||||||
verbose_name = "Конфигурация API"
|
verbose_name = "Конфигурация API"
|
||||||
verbose_name_plural = "Конфигурации API"
|
verbose_name_plural = "Конфигурации API"
|
||||||
|
|
||||||
import pytz
|
|
||||||
|
|
||||||
class Hotel(models.Model):
|
class Hotel(models.Model):
|
||||||
name = models.CharField(max_length=255, verbose_name="Название отеля")
|
name = models.CharField(max_length=255, verbose_name="Название отеля")
|
||||||
hotel_id = models.CharField(max_length=255, unique=True, null=True, blank=True, verbose_name="ID отеля")
|
hotel_id = models.CharField(max_length=255, unique=True, null=True, blank=True, verbose_name="ID отеля")
|
||||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Создан")
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Создан")
|
||||||
phone= models.CharField(max_length=50, null=True, blank=True, verbose_name="Телефон")
|
phone = models.CharField(
|
||||||
|
max_length=50,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name="Телефон",
|
||||||
|
validators=[
|
||||||
|
RegexValidator(regex=r'^\+?1?\d{9,15}$', message="Введите корректный номер телефона (до 15 цифр).")
|
||||||
|
],
|
||||||
|
)
|
||||||
email = models.EmailField(null=True, blank=True, verbose_name="Email")
|
email = models.EmailField(null=True, blank=True, verbose_name="Email")
|
||||||
address = models.CharField(max_length=255, null=True, blank=True, verbose_name="Адрес")
|
address = models.CharField(max_length=255, null=True, blank=True, verbose_name="Адрес")
|
||||||
city = models.CharField(max_length=255, null=True, blank=True, verbose_name="Город")
|
city = models.CharField(max_length=255, null=True, blank=True, verbose_name="Город")
|
||||||
timezone = models.CharField(
|
timezone = models.CharField(
|
||||||
max_length=63,
|
max_length=63,
|
||||||
choices=[(tz, tz) for tz in pytz.all_timezones], # Список всех часовых поясов
|
choices=[(tz, tz) for tz in pytz.all_timezones],
|
||||||
default='UTC', # Значение по умолчанию
|
default='UTC',
|
||||||
|
verbose_name="Часовой пояс",
|
||||||
)
|
)
|
||||||
description = models.TextField(null=True, blank=True, verbose_name="Описание")
|
description = models.TextField(null=True, blank=True, verbose_name="Описание")
|
||||||
|
|
||||||
pms = models.ForeignKey(
|
pms = models.ForeignKey(
|
||||||
'pms_integration.PMSConfiguration',
|
'pms_integration.PMSConfiguration',
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
@@ -48,13 +58,11 @@ class Hotel(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Отель"
|
verbose_name = "Отель"
|
||||||
verbose_name_plural = "Отели"
|
verbose_name_plural = "Отели"
|
||||||
|
|
||||||
|
|
||||||
class Room(models.Model):
|
class Room(models.Model):
|
||||||
"""
|
|
||||||
Модель номера отеля.
|
|
||||||
"""
|
|
||||||
hotel = models.ForeignKey(Hotel, on_delete=models.CASCADE, related_name="rooms", verbose_name="Отель")
|
hotel = models.ForeignKey(Hotel, on_delete=models.CASCADE, related_name="rooms", verbose_name="Отель")
|
||||||
number = models.CharField(max_length=50, verbose_name="Номер комнаты")
|
number = models.CharField(max_length=50, unique=True, verbose_name="Номер комнаты")
|
||||||
external_id = models.CharField(max_length=255, unique=True, verbose_name="Внешний ID комнаты")
|
external_id = models.CharField(max_length=255, unique=True, verbose_name="Внешний ID комнаты")
|
||||||
description = models.TextField(blank=True, null=True, verbose_name="Описание")
|
description = models.TextField(blank=True, null=True, verbose_name="Описание")
|
||||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
|
||||||
@@ -66,17 +74,23 @@ class Room(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Номер"
|
verbose_name = "Номер"
|
||||||
verbose_name_plural = "Номера"
|
verbose_name_plural = "Номера"
|
||||||
unique_together = ("hotel", "number") # Уникальность пары (отель, номер)
|
constraints = [
|
||||||
|
models.UniqueConstraint(fields=["hotel", "number"], name="unique_hotel_room")
|
||||||
|
]
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["hotel", "number"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class UserHotel(models.Model):
|
class UserHotel(models.Model):
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
'users.User', # Используем строковую ссылку
|
'users.User',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name="user_hotels",
|
related_name="user_hotels",
|
||||||
verbose_name="Пользователь"
|
verbose_name="Пользователь"
|
||||||
)
|
)
|
||||||
hotel = models.ForeignKey(
|
hotel = models.ForeignKey(
|
||||||
'hotels.Hotel',
|
Hotel,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name="hotel_users",
|
related_name="hotel_users",
|
||||||
verbose_name="Отель"
|
verbose_name="Отель"
|
||||||
@@ -90,6 +104,76 @@ class UserHotel(models.Model):
|
|||||||
verbose_name_plural = "Пользователи отелей"
|
verbose_name_plural = "Пользователи отелей"
|
||||||
|
|
||||||
|
|
||||||
|
class Reservation(models.Model):
|
||||||
|
id = models.BigAutoField(primary_key=True, auto_created=True, verbose_name="ID")
|
||||||
|
hotel = models.ForeignKey(Hotel, on_delete=models.CASCADE, verbose_name="Отель")
|
||||||
|
reservation_id = models.BigIntegerField(unique=True, verbose_name="ID бронирования")
|
||||||
|
room_number = models.CharField(max_length=255, null=True, blank=True, 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(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name="Цена")
|
||||||
|
discount = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name="Скидка")
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
if self.check_out and self.check_in and self.check_out <= self.check_in:
|
||||||
|
raise ValidationError("Дата выезда должна быть позже даты заезда.")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Бронирование {self.reservation_id} - {self.hotel.name}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Бронирование"
|
||||||
|
verbose_name_plural = "Бронирования"
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["hotel", "check_in", "check_out"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Guest(models.Model):
|
||||||
|
reservation = models.ForeignKey(Reservation, on_delete=models.CASCADE, related_name="guests", verbose_name="Бронирование")
|
||||||
|
name = models.CharField(max_length=255, verbose_name="Имя гостя")
|
||||||
|
birthdate = models.DateField(null=True, blank=True, verbose_name="Дата рождения")
|
||||||
|
phone = models.CharField(
|
||||||
|
max_length=50,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name="Телефон",
|
||||||
|
validators=[
|
||||||
|
RegexValidator(regex=r'^\+?1?\d{9,15}$', message="Введите корректный номер телефона (до 15 цифр).")
|
||||||
|
],
|
||||||
|
)
|
||||||
|
email = models.EmailField(null=True, blank=True, verbose_name="Email")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name} ({self.birthdate})" if self.birthdate else self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Гость"
|
||||||
|
verbose_name_plural = "Гости"
|
||||||
|
|
||||||
|
|
||||||
|
class FraudLog(models.Model):
|
||||||
|
hotel = models.ForeignKey(Hotel, on_delete=models.CASCADE, related_name="frauds")
|
||||||
|
reservation_id = models.BigIntegerField(verbose_name="ID бронирования")
|
||||||
|
guest_name = models.CharField(max_length=255, null=True, blank=True)
|
||||||
|
check_in_date = models.DateField(verbose_name="Дата заезда")
|
||||||
|
detected_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата обнаружения")
|
||||||
|
message = models.TextField(verbose_name="Сообщение")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"FRAUD: {self.guest_name} ({self.check_in_date})"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Журнал мошенничества"
|
||||||
|
verbose_name_plural = "Журналы мошенничества"
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["reservation_id"]),
|
||||||
|
models.Index(fields=["detected_at"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class APIRequestLog(models.Model):
|
class APIRequestLog(models.Model):
|
||||||
api = models.ForeignKey(APIConfiguration, on_delete=models.CASCADE, verbose_name="API")
|
api = models.ForeignKey(APIConfiguration, on_delete=models.CASCADE, verbose_name="API")
|
||||||
request_time = models.DateTimeField(auto_now_add=True, verbose_name="Время запроса")
|
request_time = models.DateTimeField(auto_now_add=True, verbose_name="Время запроса")
|
||||||
@@ -103,57 +187,6 @@ class APIRequestLog(models.Model):
|
|||||||
verbose_name = "Журнал запросов API"
|
verbose_name = "Журнал запросов API"
|
||||||
verbose_name_plural = "Журналы запросов API"
|
verbose_name_plural = "Журналы запросов API"
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['api']),
|
models.Index(fields=["api"]),
|
||||||
models.Index(fields=['request_time']),
|
models.Index(fields=["request_time"]),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class Reservation(models.Model):
|
|
||||||
id = models.BigAutoField(primary_key=True, auto_created=True, verbose_name="ID")
|
|
||||||
hotel = models.ForeignKey(Hotel, on_delete=models.CASCADE, verbose_name="Отель")
|
|
||||||
reservation_id = models.BigIntegerField(unique=True, verbose_name="ID бронирования")
|
|
||||||
room_number = models.CharField(max_length=255, null=True, blank=True)
|
|
||||||
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(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name="Цена")
|
|
||||||
discount = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name="Скидка")
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"Бронирование {self.reservation_id} - {self.hotel.name}"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Бронирование"
|
|
||||||
verbose_name_plural = "Бронирования"
|
|
||||||
|
|
||||||
|
|
||||||
class Guest(models.Model):
|
|
||||||
reservation = models.ForeignKey(Reservation, on_delete=models.CASCADE, related_name="guests", verbose_name="Бронирование")
|
|
||||||
name = models.CharField(max_length=255, verbose_name="Имя гостя")
|
|
||||||
birthdate = models.DateField(null=True, blank=True, verbose_name="Дата рождения")
|
|
||||||
phone = models.CharField(max_length=50, null=True, blank=True, verbose_name="Телефон")
|
|
||||||
email = models.EmailField(null=True, blank=True, verbose_name="Email")
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Гость"
|
|
||||||
verbose_name_plural = "Гости"
|
|
||||||
|
|
||||||
|
|
||||||
class FraudLog(models.Model):
|
|
||||||
hotel = models.ForeignKey(Hotel, on_delete=models.CASCADE, related_name="frauds")
|
|
||||||
reservation_id = models.BigIntegerField(unique=True, verbose_name="ID бронирования")
|
|
||||||
guest_name = models.CharField(max_length=255, null=True, blank=True)
|
|
||||||
check_in_date = models.DateField()
|
|
||||||
detected_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
message = models.TextField()
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"FRAUD: {self.guest_name} ({self.check_in_date})"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Журнал мошенничества"
|
|
||||||
verbose_name_plural = "Журналы мошенничества"
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2024-12-17 11:21
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('settings', '0006_remove_globalhotelsettings_timezone_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='emailsettings',
|
||||||
|
options={'verbose_name': 'E-mail', 'verbose_name_plural': 'E-mails'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='globalhotelsettings',
|
||||||
|
options={'verbose_name': 'Настройки отеля', 'verbose_name_plural': 'Настройки отеля'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='globalsystemsettings',
|
||||||
|
options={'verbose_name': 'Настройки системы', 'verbose_name_plural': 'Настройки системы'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='localdatabase',
|
||||||
|
options={'verbose_name': 'База данных', 'verbose_name_plural': 'Базы данных'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='telegramsettings',
|
||||||
|
options={'verbose_name': 'Telegram', 'verbose_name_plural': 'Telegram'},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -15,8 +15,8 @@ class LocalDatabase(models.Model):
|
|||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Локальная база данных"
|
verbose_name = "База данных"
|
||||||
verbose_name_plural = "Локальные базы данных"
|
verbose_name_plural = "Базы данных"
|
||||||
|
|
||||||
class TelegramSettings(models.Model):
|
class TelegramSettings(models.Model):
|
||||||
bot_token = models.CharField(max_length=255, help_text="Токен вашего бота Telegram")
|
bot_token = models.CharField(max_length=255, help_text="Токен вашего бота Telegram")
|
||||||
@@ -27,8 +27,8 @@ class TelegramSettings(models.Model):
|
|||||||
return f"Telegram Bot ({self.username})"
|
return f"Telegram Bot ({self.username})"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Настройки Telegram"
|
verbose_name = "Telegram"
|
||||||
verbose_name_plural = "Настройки Telegram"
|
verbose_name_plural = "Telegram"
|
||||||
|
|
||||||
|
|
||||||
class EmailSettings(models.Model):
|
class EmailSettings(models.Model):
|
||||||
@@ -39,8 +39,8 @@ class EmailSettings(models.Model):
|
|||||||
from_email = models.EmailField(help_text="Email для отправки сообщений")
|
from_email = models.EmailField(help_text="Email для отправки сообщений")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Настройки почты"
|
verbose_name = "E-mail"
|
||||||
verbose_name_plural = "Настройки почты"
|
verbose_name_plural = "E-mails"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Email Settings for {self.from_email}"
|
return f"Email Settings for {self.from_email}"
|
||||||
@@ -56,11 +56,11 @@ class GlobalHotelSettings(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Глобальные настройки отеля"
|
return "Настройки отеля"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Глобальные настройки отеля"
|
verbose_name = "Настройки отеля"
|
||||||
verbose_name_plural = "Глобальные настройки отеля"
|
verbose_name_plural = "Настройки отеля"
|
||||||
|
|
||||||
class GlobalSystemSettings(models.Model):
|
class GlobalSystemSettings(models.Model):
|
||||||
system_name = models.CharField(max_length=255, help_text="Название системы")
|
system_name = models.CharField(max_length=255, help_text="Название системы")
|
||||||
@@ -71,9 +71,9 @@ class GlobalSystemSettings(models.Model):
|
|||||||
default='UTC', # Значение по умолчанию
|
default='UTC', # Значение по умолчанию
|
||||||
)
|
)
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Глобальные настройки системы"
|
return "Настройки системы"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Глобальные настройки системы"
|
verbose_name = "Настройки системы"
|
||||||
verbose_name_plural = "Глобальные настройки системы"
|
verbose_name_plural = "Настройки системы"
|
||||||
|
|
||||||
@@ -31,10 +31,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', '5dec-182-226-158-253.ngrok-free.app', '*.ngrok-free.app']
|
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']
|
||||||
|
|
||||||
CSRF_TRUSTED_ORIGINS = [
|
CSRF_TRUSTED_ORIGINS = [
|
||||||
'http://5dec-182-226-158-253.ngrok-free.app',
|
'http://a66a-182-226-158-253.ngrok-free.app',
|
||||||
'https://*.ngrok-free.app', # Это подойдет для любых URL, связанных с ngrok
|
'https://*.ngrok-free.app', # Это подойдет для любых URL, связанных с ngrok
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user