240 lines
13 KiB
Python
240 lines
13 KiB
Python
from django.db import models
|
||
from hotels.models import Hotel
|
||
from hotels.models import Reservation
|
||
from datetime import datetime, timezone
|
||
from geoip2.errors import AddressNotFoundError
|
||
from geoip2.database import Reader
|
||
from django.conf import settings
|
||
import logging
|
||
|
||
class UserActivityLog(models.Model):
|
||
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="Агент пользователя", 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 строка", 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 страницы", 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="Тип", blank=True, null=True)
|
||
last_counter = models.IntegerField(verbose_name="Последний счетчик", blank=True, null=True)
|
||
hits = models.IntegerField(verbose_name="Количество обращений",default="0", 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 страницы")
|
||
|
||
@property
|
||
def formatted_timestamp(self):
|
||
"""
|
||
Преобразует Unix-временную метку в читаемую дату и время.
|
||
"""
|
||
if self.timestamp is not None:
|
||
return datetime.fromtimestamp(self.timestamp, tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S')
|
||
return "Нет данных"
|
||
|
||
# Изменение имени столбца
|
||
|
||
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}"
|
||
|
||
class Meta:
|
||
verbose_name = "Регистрация посетителей"
|
||
verbose_name_plural = "Регистрации посетителей"
|
||
|
||
def get_location(self):
|
||
if not self.ip:
|
||
return "IP-адрес отсутствует"
|
||
|
||
try:
|
||
db_path = f"{settings.GEOIP_PATH}/GeoLite2-City.mmdb"
|
||
geoip_reader = Reader(db_path)
|
||
response = geoip_reader.city(self.ip)
|
||
|
||
# Извлекаем город и страну на русском языке
|
||
city = response.city.names.get('ru', "Город неизвестен")
|
||
country = response.country.names.get('ru', "Страна неизвестна")
|
||
|
||
return f"{city}, {country}"
|
||
|
||
except AddressNotFoundError:
|
||
return "IP-адрес не найден в базе"
|
||
except FileNotFoundError:
|
||
# logger.error(f"Файл базы данных GeoIP не найден по пути: {db_path}")
|
||
return "Файл базы данных GeoIP не найден"
|
||
except Exception as e:
|
||
# logger.error(f"Ошибка при определении местоположения: {e}")
|
||
return "Местоположение недоступно"
|
||
class ExternalDBSettings(models.Model):
|
||
name = models.CharField(max_length=255, unique=True, help_text="Имя подключения для идентификации.")
|
||
host = models.CharField(max_length=255, help_text="Адрес сервера базы данных.")
|
||
port = models.PositiveIntegerField(default=3306, help_text="Порт сервера базы данных.")
|
||
user = models.CharField(max_length=255, help_text="Имя пользователя базы данных.")
|
||
password = models.CharField(max_length=255, help_text="Пароль для подключения.")
|
||
database = models.CharField(max_length=255, default="u1510415_wp832", help_text="Имя базы данных.")
|
||
table_name = models.CharField(max_length=255, blank=True, default="wpts_user_activity_log", null=True, help_text="Имя таблицы для загрузки данных.")
|
||
selected_fields = models.TextField(blank=True, null=True, help_text="Список полей для загрузки (через запятую).")
|
||
is_active = models.BooleanField(default=True, help_text="Флаг активности подключения.")
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
|
||
def __str__(self):
|
||
return f"{self.name} ({self.host}:{self.port})"
|
||
|
||
class Meta:
|
||
verbose_name = "Настройка подключения к БД"
|
||
verbose_name_plural = "Настройки подключений к БД"
|
||
|
||
|
||
class RoomDiscrepancy(models.Model):
|
||
hotel = models.ForeignKey(Hotel, on_delete=models.CASCADE, verbose_name="Отель")
|
||
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(
|
||
max_length=50,
|
||
choices=[("early", "Раннее заселение"), ("late", "Позднее заселение"), ("missed", "Неявка")],
|
||
verbose_name="Тип несоответствия"
|
||
)
|
||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
|
||
|
||
def __str__(self):
|
||
return f"{self.hotel.name} - Room {self.room_number}: {self.discrepancy_type}"
|
||
|
||
class Meta:
|
||
verbose_name = "Несовпадение в заселении"
|
||
verbose_name_plural = "Несовпадения в заселении"
|
||
|
||
@staticmethod
|
||
def detect_discrepancies(expected_bookings, actual_check_ins):
|
||
"""
|
||
Сравнение ожидаемых и фактических данных о заселении.
|
||
"""
|
||
discrepancies = []
|
||
|
||
# Преобразуем фактические заселения в словарь для быстрого доступа
|
||
actual_dict = {
|
||
(entry.hotel_id, entry.room_number): entry.check_in_date
|
||
for entry in actual_check_ins
|
||
}
|
||
|
||
for booking in expected_bookings:
|
||
key = (booking.hotel_id, booking.room_number)
|
||
actual_date = actual_dict.get(key)
|
||
|
||
if actual_date is None:
|
||
discrepancies.append(RoomDiscrepancy(
|
||
hotel=booking.hotel,
|
||
room_number=booking.room_number,
|
||
booking_id=booking.booking_id,
|
||
check_in_date_expected=booking.check_in_date,
|
||
discrepancy_type="missed"
|
||
))
|
||
elif actual_date < booking.check_in_date:
|
||
discrepancies.append(RoomDiscrepancy(
|
||
hotel=booking.hotel,
|
||
room_number=booking.room_number,
|
||
booking_id=booking.booking_id,
|
||
check_in_date_expected=booking.check_in_date,
|
||
check_in_date_actual=actual_date,
|
||
discrepancy_type="early"
|
||
))
|
||
elif actual_date > booking.check_in_date:
|
||
discrepancies.append(RoomDiscrepancy(
|
||
hotel=booking.hotel,
|
||
room_number=booking.room_number,
|
||
booking_id=booking.booking_id,
|
||
check_in_date_expected=booking.check_in_date,
|
||
check_in_date_actual=actual_date,
|
||
discrepancy_type="late"
|
||
))
|
||
|
||
RoomDiscrepancy.objects.bulk_create(discrepancies)
|
||
|
||
from urllib.parse import unquote
|
||
from html import unescape
|
||
|
||
class ImportedHotel(models.Model):
|
||
id = models.BigAutoField(primary_key=True, auto_created=True, verbose_name="ID")
|
||
external_id = models.CharField(max_length=255, verbose_name="Внешний ID отеля")
|
||
name = models.CharField(max_length=255, verbose_name="Имя отеля")
|
||
display_name = models.CharField(max_length=255, null=True, blank=True, 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="Импортирован в основную базу")
|
||
|
||
def __str__(self):
|
||
return f"{self.display_name or self.name} ({self.external_id})"
|
||
|
||
class Meta:
|
||
verbose_name = "Импортированный отель"
|
||
verbose_name_plural = "Импортированные отели"
|
||
|
||
def set_display_name_from_page_title(self, page_title):
|
||
"""
|
||
Декодирует HTML-сущности, URL-кодировку и устанавливает display_name.
|
||
"""
|
||
if page_title:
|
||
decoded = unquote(unescape(page_title))
|
||
self.display_name = decoded
|
||
else:
|
||
self.display_name = self.name
|
||
self.save()
|
||
|
||
class SyncLog(models.Model):
|
||
"""
|
||
Журнал синхронизации в разрезе отелей.
|
||
"""
|
||
hotel = models.OneToOneField(Hotel, on_delete=models.CASCADE, verbose_name="Отель")
|
||
created = models.DateTimeField(auto_now=True, verbose_name="Дата обновления") # Последняя дата обновления записи
|
||
recieved_records = models.IntegerField(default=0, verbose_name="Полученные записи")
|
||
processed_records = models.IntegerField(default=0, verbose_name="Обработанные записи")
|
||
|
||
class Meta:
|
||
verbose_name = "Журнал синхронизации"
|
||
verbose_name_plural = "Журналы синхронизации"
|
||
|
||
def __str__(self):
|
||
return f"Отель: {self.hotel.name} | Получено: {self.recieved_records} | Обработано: {self.processed_records}"
|
||
|
||
|
||
|
||
class ViolationLog(models.Model):
|
||
hotel = models.ForeignKey(Hotel, on_delete=models.CASCADE, verbose_name="Отель")
|
||
room_number = models.CharField(max_length=50, verbose_name="Номер комнаты", null=True, blank=True)
|
||
violation_type = models.CharField(
|
||
max_length=50,
|
||
choices=[("missed", "Неявка"), ("early", "Раннее заселение"), ("late", "Позднее заселение")],
|
||
verbose_name="Тип нарушения"
|
||
)
|
||
violation_details = models.TextField(verbose_name="Детали нарушения", blank=True, null=True)
|
||
hits = models.IntegerField(verbose_name="Срабатывания")
|
||
detected_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата обнаружения")
|
||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
|
||
|
||
def __str__(self):
|
||
return f"{self.hotel.name} - {self.room_number or 'N/A'}: {self.violation_type}"
|
||
|
||
class Meta:
|
||
verbose_name = "Журнал нарушений"
|
||
verbose_name_plural = "Журналы нарушений"
|