ssMerge branch 'pms_plugins'

This commit is contained in:
2024-12-24 21:36:24 +09:00
66 changed files with 859 additions and 1967 deletions

View File

@@ -112,7 +112,7 @@ class ExternalDBSettingsAdmin(admin.ModelAdmin):
@admin.register(UserActivityLog)
class UserActivityLogAdmin(admin.ModelAdmin):
list_display = ("id", 'get_location',"formatted_timestamp", "date_time", "page_id", "url_parameters", "page_url" ,"created", "page_title", "type", "hits")
search_fields = ("page_title", "url_parameters")
search_fields = ("page_title", "url_parameters", "page_title")
list_filter = ("page_title", "created")
readonly_fields = ("created", "timestamp")
@@ -151,69 +151,83 @@ class UserActivityLogAdmin(admin.ModelAdmin):
get_hotel_name.short_description = "Отель"
get_room_number.short_description = "Комната"
from .views import import_selected_hotels
# Регистрируем admin класс для ImportedHotel
@admin.register(ImportedHotel)
class ImportedHotelAdmin(admin.ModelAdmin):
change_list_template = "antifroud/admin/import_hotels.html"
list_display = ("external_id", "display_name", "name", "created", "updated", "imported")
search_fields = ("name", "display_name", "external_id")
list_filter = ("name", "display_name", "external_id")
actions = ['mark_as_imported', 'delete_selected_hotels_action']
# from .views import import_selected_hotels
# # Регистрируем admin класс для ImportedHotel
# @admin.register(ImportedHotel)
# class ImportedHotelAdmin(admin.ModelAdmin):
# change_list_template = "antifroud/admin/import_hotels.html"
# list_display = ("external_id", "display_name", "name", "created", "updated", "imported")
# search_fields = ("name", "display_name", "external_id")
# list_filter = ("name", "display_name", "external_id")
# actions = ['mark_as_imported', 'delete_selected_hotels_action']
def get_urls(self):
# Получаем стандартные URL-адреса и добавляем наши
urls = super().get_urls()
custom_urls = [
path('import_selected_hotels/', import_selected_hotels, name='antifroud_importedhotels_import_selected_hotels'),
path('delete_selected_hotels/', self.delete_selected_hotels, name='delete_selected_hotels'),
path('delete_hotel/<int:hotel_id>/', self.delete_hotel, name='delete_hotel'), # Изменили на URL параметр
]
return custom_urls + urls
# def get_urls(self):
# # Получаем стандартные URL-адреса и добавляем наши
# urls = super().get_urls()
# custom_urls = [
# path('import_selected_hotels/', import_selected_hotels, name='antifroud_importedhotels_import_selected_hotels'),
# path('delete_selected_hotels/', self.delete_selected_hotels, name='delete_selected_hotels'),
# path('delete_hotel/<int:hotel_id>/', self.delete_hotel, name='delete_hotel'), # Изменили на URL параметр
# ]
# return custom_urls + urls
@transaction.atomic
def delete_selected_hotels(self, request):
if request.method == 'POST':
selected = request.POST.get('selected', '')
if selected:
external_ids = selected.split(',')
deleted_count, _ = ImportedHotel.objects.filter(external_id__in=external_ids).delete()
messages.success(request, f"Удалено отелей: {deleted_count}")
else:
messages.warning(request, "Не выбрано ни одного отеля для удаления.")
return redirect('admin:antifroud_importedhotel_changelist')
# @transaction.atomic
# def delete_selected_hotels(self, request):
# if request.method == 'POST':
# selected = request.POST.get('selected', '')
# if selected:
# external_ids = selected.split(',')
# deleted_count, _ = ImportedHotel.objects.filter(external_id__in=external_ids).delete()
# messages.success(request, f"Удалено отелей: {deleted_count}")
# else:
# messages.warning(request, "Не выбрано ни одного отеля для удаления.")
# return redirect('admin:antifroud_importedhotel_changelist')
def delete_selected_hotels(self, request, queryset):
deleted_count, _ = queryset.delete()
self.message_user(request, f'{deleted_count} отелей было удалено.')
delete_selected_hotels.short_description = "Удалить выбранные отели"
# def delete_selected_hotels(self, request, queryset):
# deleted_count, _ = queryset.delete()
# self.message_user(request, f'{deleted_count} отелей было удалено.')
# delete_selected_hotels.short_description = "Удалить выбранные отели"
def mark_as_imported(self, request, queryset):
updated = queryset.update(imported=True)
self.message_user(request, f"Отмечено как импортированное: {updated}", messages.SUCCESS)
mark_as_imported.short_description = "Отметить выбранные как импортированные"
# def mark_as_imported(self, request, queryset):
# updated = queryset.update(imported=True)
# self.message_user(request, f"Отмечено как импортированное: {updated}", messages.SUCCESS)
# mark_as_imported.short_description = "Отметить выбранные как импортированные"
# Метод для удаления одного отеля
@transaction.atomic
def delete_hotel(self, request, hotel_id):
imported_hotel = get_object_or_404(ImportedHotel, id=hotel_id)
imported_hotel.delete()
messages.success(request, f"Отель {imported_hotel.name} успешно удалён.")
return redirect('admin:antifroud_importedhotel_changelist')
# # Метод для удаления одного отеля
# @transaction.atomic
# def delete_hotel(self, request, hotel_id):
# imported_hotel = get_object_or_404(ImportedHotel, id=hotel_id)
# imported_hotel.delete()
# messages.success(request, f"Отель {imported_hotel.name} успешно удалён.")
# return redirect('admin:antifroud_importedhotel_changelist')
@admin.register(SyncLog)
class SyncLogAdmin(admin.ModelAdmin):
change_list_template = "antifroud/admin/sync_log.html"
list_display =['id', 'hotel', 'created', 'recieved_records', 'processed_records']
search_fields = ['id', 'hotel', 'created', 'recieved_records', 'processed_records']
list_filter = ['id', 'hotel', 'created', 'recieved_records', 'processed_records']
change_list_template = "antifroud/admin/sync_log.html" # Путь к вашему кастомному шаблону
list_display = ['id', 'hotel', 'created', 'recieved_records', 'processed_records']
search_fields = ['id', 'hotel__name', 'recieved_records', 'processed_records']
list_filter = ['hotel', 'created']
class Meta:
model = SyncLog
fields = ['hotel', 'recieved_records', 'processed_records']
def changelist_view(self, request, extra_context=None):
"""
Добавляет фильтрацию по отелям в шаблон.
"""
extra_context = extra_context or {}
# Получаем выбранный фильтр отеля из GET-параметров
hotel_id = request.GET.get('hotel')
hotels = Hotel.objects.all()
sync_logs = SyncLog.objects.all()
if hotel_id:
sync_logs = sync_logs.filter(hotel_id=hotel_id)
extra_context['sync_logs'] = sync_logs
extra_context['hotels'] = hotels
extra_context['selected_hotel'] = hotel_id # Чтобы отобразить выбранный отель
return super().changelist_view(request, extra_context=extra_context)
@admin.register(ViolationLog)
class ViolationLogAdmin(admin.ModelAdmin):
list_display = ['id', 'hotel', 'room_number' , 'hits', 'created_at', 'violation_type', 'violation_details', 'detected_at']
@@ -222,4 +236,15 @@ class ViolationLogAdmin(admin.ModelAdmin):
class Meta:
model = ViolationLog
fields = ['hotel', 'room_number', 'created_at', 'violation_type', 'violation_details', 'detected_at']
fields = ['hotel', 'room_number', 'created_at', 'violation_type', 'violation_details', 'detected_at']
@admin.register(RoomDiscrepancy)
class RoomDiscrepancyAdmin(admin.ModelAdmin):
list_display = ['hotel', 'room_number', 'booking_id','created_at', 'check_in_date_expected','check_in_date_actual','discrepancy_type']
search_fields = ['hotel', 'room_number', 'booking_id','created_at', 'check_in_date_expected','check_in_date_actual','discrepancy_type']
list_filter = ['hotel', 'room_number', 'booking_id','created_at', 'check_in_date_expected','check_in_date_actual','discrepancy_type']
class Meta:
model = RoomDiscrepancy
fields = ['hotel', 'room_number', 'booking_id','created_at', 'check_in_date_expected','check_in_date_actual','discrepancy_type']

View File

@@ -1,14 +1,13 @@
import logging
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
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

@@ -4,7 +4,7 @@
<style>
#table-data-preview {
max-height: 300px; /* Ограничиваем высоту предпросмотра */
max-height: 500px; /* Ограничиваем высоту предпросмотра */
overflow-y: auto; /* Прокрутка по вертикали */
overflow-x: auto; /* Прокрутка по горизонтали */
}
@@ -36,27 +36,27 @@
<form id="connection-form" method="post">
{% csrf_token %}
<div class="form-group mb-3">
<label for="db-name">Name</label>
<label for="db-name">Имя подключения</label>
<input id="db-name" class="form-control" type="text" name="name" value="{{ original.name }}" required />
</div>
<div class="form-group mb-3">
<label for="db-host">DB Host</label>
<label for="db-host">Сервер БД</label>
<input id="db-host" class="form-control" type="text" name="host" value="{{ original.host }}" required />
</div>
<div class="form-group mb-3">
<label for="db-port">DB Port</label>
<label for="db-port">Порт БД</label>
<input id="db-port" class="form-control" type="number" name="port" value="{{ original.port }}" required />
</div>
<div class="form-group mb-3">
<label for="db-user">User</label>
<label for="db-user">Пользователь</label>
<input id="db-user" class="form-control" type="text" name="user" value="{{ original.user }}" required />
</div>
<div class="form-group mb-3">
<label for="db-password">Password</label>
<label for="db-password">Пароль</label>
<input id="db-password" class="form-control" type="password" name="password" value="{{ original.password }}" />
</div>
<div class="form-group mb-3">
<label for="db-database">Database</label>
<label for="db-database">Имя Базы данных</label>
<input id="db-database" class="form-control" type="text" name="database" value="{{ original.database }}" required />
</div>
<div class="form-group mb-3">
@@ -78,10 +78,11 @@
</table>
</div>
</div>
<div class="form-group mb-3">
<label for="is-active">Активное подключение</label>
<input id="is-active" class="form-check-input" type="checkbox" name="is_active" {% if original.is_active %}checked{% endif %} />
</div>
<div class="form-group mb-3" style="text-center">
<input class="form-check-input" id="is-active" class="form-check-input" type="checkbox" name="is_active" {% if original.is_active %}checked{% endif %}>
<label class="form-check-label" for="inlineCheckbox2"><b>Активное подключение</b></label>
<div class="form-group text-center">
<button class="btn btn-success" type="submit">Сохранить</button>
<button class="btn btn-secondary" type="button" id="close-button">Закрыть</button>

View File

@@ -1,7 +1,6 @@
{% extends "admin/change_list.html" %}
{% block content %}
<div class="container-fluid">
<div class="row mt-4">
<div class="col">
<div class="card shadow-sm mb-2 db-graph">
@@ -9,53 +8,22 @@
<h6 class="text-white m-0 font-md">Журнал синхронизации</h6>
</div>
<div class="card-body">
<form method="post" action="{% url 'antifroud:sync_log_create' %}">
{% csrf_token %}
<div class="form-row">
<div class="col-md-9 col-xl-9">
<div class="box-bg">
<div class="form-row">
<div class="col-md-2 col-xl-2 align-self-center font-md text-dark-blue">
<label class="col-form-label p-0" for="hotel-id"><strong>Отель:</strong></label>
</div>
<div class="col-md-4 col-xl-3">
<div class="form-group mb-0">
<select class="custom-select custom-select-sm font-sm" name="hotel" id="hotel-id">
<option value="">--Выберите Отель --</option>
{% for hotel in hotels %}
<option value="{{ hotel.id }}">{{ hotel.name }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3 col-xl-3">
<div class="box-bg">
<div class="text-dark form-row">
<div class="col-xl-5 offset-xl-0 align-self-center">
<h6 class="mb-0 font-sm">Полученные записи:</h6>
</div>
<div class="col-xl-7 offset-xl-0 text-right align-self-center">
<div class="form-group mb-1">
<input class="form-control form-control-sm form-control font-sm" type="number" name="received_records" required />
</div>
</div>
<div class="col-xl-5 offset-xl-0 align-self-center">
<h6 class="mb-0 font-sm">Обработанные записи:</h6>
</div>
<div class="col-xl-7 offset-xl-0 text-right align-self-center">
<div class="form-group mb-1">
<input class="form-control form-control-sm form-control font-sm" type="number" name="processed_records" required />
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Форма фильтрации по отелям -->
<form method="get" class="form-inline mb-3">
<label for="hotel-filter" class="mr-2">Фильтр по отелям:</label>
<select name="hotel" id="hotel-filter" class="form-control mr-2">
<option value="">-- Все отели --</option>
{% for hotel in hotels %}
<option value="{{ hotel.id }}" {% if hotel.id|stringformat:"s" == selected_hotel %}selected{% endif %}>
{{ hotel.name }}
</option>
{% endfor %}
</select>
<button type="submit" class="btn btn-primary">Применить</button>
</form>
<!-- Список существующих журналов синхронизации -->
<div class="table-responsive tbl-wfx mt-1 kot-table">
<table class="table table-sm">
@@ -63,10 +31,8 @@
<tr class="text-dark-blue">
<th>#</th>
<th>Отель</th>
<th>ID бронирования</th>
<th> Дата синхронизации</th>
<th>Обработанные записи</th>
<th>Полученные записи</th>
<th>Создан</th>
</tr>
</thead>
<tbody>
@@ -74,14 +40,12 @@
<tr>
<td>{{ log.id }}</td>
<td>{{ log.hotel.name }}</td>
<td>{{ log.reservation_id }}</td>
<td>{{ log.processed_records }}</td>
<td>{{ log.recieved_records }}</td>
<td>{{ log.created }}</td>
<td>{{ log.processed_records }}</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center">Нет журналов.</td>
<td colspan="5" class="text-center">Нет записей.</td>
</tr>
{% endfor %}
</tbody>
@@ -93,4 +57,4 @@
</div>
</div>
{% endblock %}
{% endblock %}