antifroud_check

This commit is contained in:
2024-12-17 21:39:33 +09:00
parent 66750015e2
commit 0bf2bb8dff
17 changed files with 1298 additions and 524 deletions

View File

@@ -5,7 +5,8 @@ from django.shortcuts import redirect, get_object_or_404
from django.contrib import messages
from django.db import transaction
from antifroud.models import UserActivityLog, ExternalDBSettings, RoomDiscrepancy, ImportedHotel, SyncLog, ViolationLog
from hotels.models import Hotel
from hotels.models import Hotel, Room
import pymysql
import logging
from django.urls import reverse
@@ -114,14 +115,32 @@ class UserActivityLogAdmin(admin.ModelAdmin):
search_fields = ("page_title", "url_parameters")
list_filter = ("page_title", "created")
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)
class RoomDiscrepancyAdmin(admin.ModelAdmin):
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",)
get_hotel_name.short_description = "Отель"
get_room_number.short_description = "Комната"
from .views import import_selected_hotels

View File

@@ -1,79 +1,160 @@
import logging
from datetime import datetime, timedelta
from datetime import timedelta
from urllib.parse import parse_qs
from django.utils import timezone
from django.db.models import Q
from hotels.models import Reservation, Hotel
from .models import UserActivityLog, ViolationLog
# Настройка логирования
logging.basicConfig(level=logging.INFO) # Устанавливаем уровень логирования
logger = logging.getLogger(__name__) # Создаем логгер для текущего модуля
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def check_reservations_and_generate_report():
now = datetime.now()
start_time = (now - timedelta(hours=12))
end_time = now
logger.info(f"Starting reservation check from {start_time} to {end_time}")
class ReservationChecker:
"""
Класс для проверки несоответствий между бронированиями и логами заселения.
"""
# Получаем логи активности за период
user_logs = UserActivityLog.objects.filter(created__range=(start_time, end_time))
logger.info(f"Found {len(user_logs)} logs for analysis.")
def __init__(self):
"""
Инициализация времени проверки и списка нарушений.
"""
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):
logger.debug(f"Processing log {i}: {log}")
def log_warning(self, message):
"""Логирование предупреждений."""
logger.warning(message)
if not log.url_parameters:
logger.warning(f"Log {i} skipped due to missing URL parameters.")
continue # Пропускаем записи без параметров URL
def log_error(self, message):
"""Логирование ошибок."""
logger.error(message)
# Парсим параметры URL
params = parse_qs(log.url_parameters)
external_id = params.get("utm_content", [None])[0] # ID отеля
room_number = params.get("utm_term", [None])[0] # Номер комнаты
def fetch_user_logs(self):
"""
Извлекает записи из UserActivityLog за последние 12 часов.
"""
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:
logger.warning(f"Log {i} skipped due to missing external_id or room_number.")
continue # Пропускаем, если данные не извлечены
def find_violations(self, user_logs, hotels):
"""
Сопоставляет логи активности с бронированиями и фиксирует нарушения.
"""
for log in user_logs:
if not log.url_parameters:
self.log_warning(f"Пропущена запись ID {log.id}: отсутствуют URL-параметры.")
continue
try:
# Находим отель по external_id
hotel = Hotel.objects.get(external_id=external_id)
logger.debug(f"Log {i}: Found hotel {hotel.name} with external_id {external_id}.")
except Hotel.DoesNotExist:
logger.error(f"Log {i} skipped: No hotel found with external_id {external_id}.")
continue
# Парсинг параметров URL
params = parse_qs(log.url_parameters)
hotel_id = params.get("utm_content", [None])[0]
room_number = params.get("utm_term", [None])[0]
# Ищем бронирование в Reservation
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.")
print(f"Processing log ID {log.id} with hotel ID {hotel_id} and room number {room_number}")
if not matching_reservations.exists():
# Если бронирование не найдено — записываем нарушение
violation_details = (
f"Log {i}: No reservation found for room {room_number} in hotel {external_id} at {log.created}."
)
violations.append(ViolationLog(
if not hotel_id or not room_number:
self.log_warning(f"Пропущена запись ID {log.id}: некорректные параметры URL.")
continue
if hotel_id not in hotels:
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,
room_number=room_number,
violation_type="missed",
violation_details=violation_details
))
logger.warning(f"Log {i}: Violation recorded - {violation_details}")
check_in__lte=log_time,
check_out__gte=log_time
)
# Сохраняем все нарушения в базу
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.")
print(f"Found {matching_reservations.count()} matching reservations")
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

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

View File

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

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

View File

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

View File

@@ -4,30 +4,40 @@ from hotels.models import Reservation
class UserActivityLog(models.Model):
external_id = models.CharField(max_length=255, null=True, blank=True)
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="Дата и время")
external_id = models.CharField(max_length=255, unique=True, verbose_name="Внешний ID", db_index=True)
user_id = models.BigIntegerField(verbose_name="ID пользователя", blank=True, null=True, db_index=True)
ip = models.GenericIPAddressField(verbose_name="IP-адрес", blank=True, null=True, db_index=True)
created = models.DateTimeField(verbose_name="Дата создания", blank=True, null=True, db_index=True)
timestamp = models.BigIntegerField(verbose_name="Метка времени", blank=True, null=True)
date_time = models.DateTimeField(verbose_name="Дата и время", blank=True, null=True)
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="Платформа")
version = 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="Тип устройства")
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="Местоположение")
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")
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="Ответ пользователя")
type = models.CharField(max_length=50, verbose_name="Тип", blank=True, null=True)
last_counter = models.IntegerField(verbose_name="Последний счетчик", blank=True, null=True)
hits = models.IntegerField(verbose_name="Количество обращений", blank=True, null=True)
honeypot = models.BooleanField(verbose_name="Метка honeypot", blank=True, null=True)
reply = models.BooleanField(verbose_name="Ответ пользователя", blank=True, null=True)
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):
return f"UserActivityLog {self.id}: {self.page_title}"

View File

@@ -5,7 +5,7 @@ from .models import (
UserHotel,
APIConfiguration,
Reservation,
FraudLog
Room
)
from django.urls import path
from django.shortcuts import redirect
@@ -27,8 +27,9 @@ class HotelForm(forms.ModelForm):
@admin.register(Hotel)
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_sorting = ['name', 'pms', 'room_count', 'timezone']
def sync_button(self, obj):
return format_html(
'<a class="button" href="{}">Синхронизировать</a>',
@@ -41,8 +42,36 @@ class HotelAdmin(admin.ModelAdmin):
path('sync/<int:hotel_id>/', self.sync_hotel_data),
]
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):
"""
Метод синхронизации данных отеля.
"""
try:
hotel = Hotel.objects.get(id=hotel_id)
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')
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

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

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

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

View File

@@ -1,5 +1,7 @@
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):
@@ -17,25 +19,33 @@ class APIConfiguration(models.Model):
verbose_name = "Конфигурация API"
verbose_name_plural = "Конфигурации API"
import pytz
class Hotel(models.Model):
name = models.CharField(max_length=255, verbose_name="Название отеля")
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="Создан")
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")
address = 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(
max_length=63,
choices=[(tz, tz) for tz in pytz.all_timezones], # Список всех часовых поясов
default='UTC', # Значение по умолчанию
choices=[(tz, tz) for tz in pytz.all_timezones],
default='UTC',
verbose_name="Часовой пояс",
)
description = models.TextField(null=True, blank=True, verbose_name="Описание")
pms = models.ForeignKey(
'pms_integration.PMSConfiguration',
'pms_integration.PMSConfiguration',
on_delete=models.SET_NULL,
null=True,
blank=True,
@@ -48,13 +58,11 @@ class Hotel(models.Model):
class Meta:
verbose_name = "Отель"
verbose_name_plural = "Отели"
class Room(models.Model):
"""
Модель номера отеля.
"""
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 комнаты")
description = models.TextField(blank=True, null=True, verbose_name="Описание")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
@@ -66,17 +74,23 @@ class Room(models.Model):
class Meta:
verbose_name = "Номер"
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):
user = models.ForeignKey(
'users.User', # Используем строковую ссылку
'users.User',
on_delete=models.CASCADE,
related_name="user_hotels",
verbose_name="Пользователь"
)
hotel = models.ForeignKey(
'hotels.Hotel',
Hotel,
on_delete=models.CASCADE,
related_name="hotel_users",
verbose_name="Отель"
@@ -90,6 +104,76 @@ class UserHotel(models.Model):
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):
api = models.ForeignKey(APIConfiguration, on_delete=models.CASCADE, verbose_name="API")
request_time = models.DateTimeField(auto_now_add=True, verbose_name="Время запроса")
@@ -103,57 +187,6 @@ class APIRequestLog(models.Model):
verbose_name = "Журнал запросов API"
verbose_name_plural = "Журналы запросов API"
indexes = [
models.Index(fields=['api']),
models.Index(fields=['request_time']),
models.Index(fields=["api"]),
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 = "Журналы мошенничества"

View File

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

View File

@@ -15,8 +15,8 @@ class LocalDatabase(models.Model):
return self.name
class Meta:
verbose_name = "Локальная база данных"
verbose_name_plural = "Локальные базы данных"
verbose_name = "База данных"
verbose_name_plural = "Базы данных"
class TelegramSettings(models.Model):
bot_token = models.CharField(max_length=255, help_text="Токен вашего бота Telegram")
@@ -27,8 +27,8 @@ class TelegramSettings(models.Model):
return f"Telegram Bot ({self.username})"
class Meta:
verbose_name = "Настройки Telegram"
verbose_name_plural = "Настройки Telegram"
verbose_name = "Telegram"
verbose_name_plural = "Telegram"
class EmailSettings(models.Model):
@@ -39,8 +39,8 @@ class EmailSettings(models.Model):
from_email = models.EmailField(help_text="Email для отправки сообщений")
class Meta:
verbose_name = "Настройки почты"
verbose_name_plural = "Настройки почты"
verbose_name = "E-mail"
verbose_name_plural = "E-mails"
def __str__(self):
return f"Email Settings for {self.from_email}"
@@ -56,11 +56,11 @@ class GlobalHotelSettings(models.Model):
)
def __str__(self):
return "Глобальные настройки отеля"
return "Настройки отеля"
class Meta:
verbose_name = "Глобальные настройки отеля"
verbose_name_plural = "Глобальные настройки отеля"
verbose_name = "Настройки отеля"
verbose_name_plural = "Настройки отеля"
class GlobalSystemSettings(models.Model):
system_name = models.CharField(max_length=255, help_text="Название системы")
@@ -71,9 +71,9 @@ class GlobalSystemSettings(models.Model):
default='UTC', # Значение по умолчанию
)
def __str__(self):
return "Глобальные настройки системы"
return "Настройки системы"
class Meta:
verbose_name = "Глобальные настройки системы"
verbose_name_plural = "Глобальные настройки системы"
verbose_name = "Настройки системы"
verbose_name_plural = "Настройки системы"

View File

@@ -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!
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 = [
'http://5dec-182-226-158-253.ngrok-free.app',
'http://a66a-182-226-158-253.ngrok-free.app',
'https://*.ngrok-free.app', # Это подойдет для любых URL, связанных с ngrok
]