merge
This commit is contained in:
6
.docker/admin/.dockerignore
Normal file
6
.docker/admin/.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.venv
|
||||||
|
.venv/
|
||||||
|
.log
|
||||||
|
__pycache__
|
||||||
|
.history
|
||||||
|
.vscode
|
||||||
30
.docker/admin/Dockerfile
Normal file
30
.docker/admin/Dockerfile
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
FROM python:3.9-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Устанавливаем временную директорию
|
||||||
|
ENV TMPDIR=/tmp/tempdir
|
||||||
|
RUN mkdir -p $TMPDIR && chmod 1777 $TMPDIR
|
||||||
|
|
||||||
|
# Устанавливаем системные зависимости для Alpine
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
gcc \
|
||||||
|
musl-dev \
|
||||||
|
mariadb-dev \
|
||||||
|
netcat-openbsd \
|
||||||
|
net-tools \
|
||||||
|
iputils
|
||||||
|
|
||||||
|
# Копируем только requirements.txt для кэширования зависимостей
|
||||||
|
COPY .docker/admin/requirements.txt /app/requirements.txt
|
||||||
|
|
||||||
|
# Устанавливаем Python-зависимости
|
||||||
|
RUN pip install --upgrade pip && pip install --no-cache-dir -r /app/requirements.txt
|
||||||
|
|
||||||
|
# Копируем весь проект
|
||||||
|
COPY . /app
|
||||||
|
|
||||||
|
RUN chmod +x .docker/admin/entrypoint.sh
|
||||||
|
ENTRYPOINT [".docker/admin/entrypoint.sh"]
|
||||||
|
|
||||||
|
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
|
||||||
14
.docker/admin/entrypoint.sh
Executable file
14
.docker/admin/entrypoint.sh
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Ожидание доступности базы данных
|
||||||
|
until nc -z -v -w30 $DB_HOST $DB_PORT; do
|
||||||
|
echo "Ожидание базы данных..."
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Выполняем миграции
|
||||||
|
python manage.py makemigrations --no-input
|
||||||
|
python manage.py migrate --no-input
|
||||||
|
|
||||||
|
# Запускаем приложение
|
||||||
|
exec "$@"
|
||||||
44
.docker/admin/requirements.txt
Normal file
44
.docker/admin/requirements.txt
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
ace_tools
|
||||||
|
aiohappyeyeballs
|
||||||
|
aiohttp
|
||||||
|
aiosignal
|
||||||
|
APScheduler
|
||||||
|
Django
|
||||||
|
django-environ
|
||||||
|
django_extensions
|
||||||
|
django-filter
|
||||||
|
django-health-check
|
||||||
|
django-jazzmin
|
||||||
|
django-jet
|
||||||
|
et_xmlfile
|
||||||
|
fonttools
|
||||||
|
fpdf2
|
||||||
|
geoip2
|
||||||
|
git-filter-repo
|
||||||
|
httpcore
|
||||||
|
httpx
|
||||||
|
jsonschema
|
||||||
|
jsonschema-specifications
|
||||||
|
maxminddb
|
||||||
|
multidict
|
||||||
|
PyMySQL
|
||||||
|
numpy
|
||||||
|
openpyxl
|
||||||
|
pandas
|
||||||
|
pathspec
|
||||||
|
pillow
|
||||||
|
propcache
|
||||||
|
psycopg
|
||||||
|
PyMySQL
|
||||||
|
python-dateutil
|
||||||
|
python-decouple
|
||||||
|
python-dotenv
|
||||||
|
python-telegram-bot
|
||||||
|
PyYAML
|
||||||
|
requests
|
||||||
|
sqlparse
|
||||||
|
ua-parser
|
||||||
|
ua-parser-builtins
|
||||||
|
user-agents
|
||||||
|
yarl
|
||||||
|
cryptography
|
||||||
6
.docker/bot/.dockerignore
Normal file
6
.docker/bot/.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.venv
|
||||||
|
.venv/
|
||||||
|
.log
|
||||||
|
__pycache__
|
||||||
|
.history
|
||||||
|
.vscode
|
||||||
12
.docker/bot/Dockerfile
Normal file
12
.docker/bot/Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
FROM python:3.10-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Копируем весь проект в контейнер
|
||||||
|
COPY ../../ /app
|
||||||
|
|
||||||
|
# Устанавливаем зависимости только для bot
|
||||||
|
|
||||||
|
RUN pip install --upgrade pip && pip install --no-cache-dir -r .docker/bot/requirements.txt
|
||||||
|
# Команда запуска для бота
|
||||||
|
CMD ["python", "manage.py" ,"run_bot.py"]
|
||||||
44
.docker/bot/requirements.txt
Normal file
44
.docker/bot/requirements.txt
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
ace_tools
|
||||||
|
aiohappyeyeballs
|
||||||
|
aiohttp
|
||||||
|
aiosignal
|
||||||
|
APScheduler
|
||||||
|
Django
|
||||||
|
django-environ
|
||||||
|
django_extensions
|
||||||
|
django-filter
|
||||||
|
django-health-check
|
||||||
|
django-jazzmin
|
||||||
|
django-jet
|
||||||
|
et_xmlfile
|
||||||
|
fonttools
|
||||||
|
fpdf2
|
||||||
|
geoip2
|
||||||
|
git-filter-repo
|
||||||
|
httpcore
|
||||||
|
httpx
|
||||||
|
jsonschema
|
||||||
|
jsonschema-specifications
|
||||||
|
maxminddb
|
||||||
|
multidict
|
||||||
|
PyMySQL
|
||||||
|
numpy
|
||||||
|
openpyxl
|
||||||
|
pandas
|
||||||
|
pathspec
|
||||||
|
pillow
|
||||||
|
propcache
|
||||||
|
psycopg
|
||||||
|
PyMySQL
|
||||||
|
python-dateutil
|
||||||
|
python-decouple
|
||||||
|
python-dotenv
|
||||||
|
python-telegram-bot
|
||||||
|
PyYAML
|
||||||
|
requests
|
||||||
|
sqlparse
|
||||||
|
ua-parser
|
||||||
|
ua-parser-builtins
|
||||||
|
user-agents
|
||||||
|
yarl
|
||||||
|
cryptography
|
||||||
14
.docker/scheduler/Dockerfile
Normal file
14
.docker/scheduler/Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
FROM python:3.10-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Копируем весь проект в контейнер
|
||||||
|
COPY ../../ /app
|
||||||
|
|
||||||
|
RUN chmod +x .docker/scheduler/entrypoint.sh
|
||||||
|
ENTRYPOINT [".docker/scheduler/entrypoint.sh"]
|
||||||
|
# Устанавливаем зависимости только для scheduler
|
||||||
|
RUN pip install --upgrade pip && pip install --no-cache-dir -r .docker/scheduler/requirements.txt
|
||||||
|
|
||||||
|
# Команда запуска для планировщика
|
||||||
|
CMD ["python", "manage.py", "run_scheduler"]
|
||||||
6
.docker/scheduler/dockerignore
Normal file
6
.docker/scheduler/dockerignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.venv
|
||||||
|
.venv/
|
||||||
|
.log
|
||||||
|
__pycache__
|
||||||
|
.history
|
||||||
|
.vscode
|
||||||
7
.docker/scheduler/entrypoint.sh
Executable file
7
.docker/scheduler/entrypoint.sh
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Выполняем миграции
|
||||||
|
python manage.py migrate --no-input
|
||||||
|
|
||||||
|
# Запускаем приложение
|
||||||
|
exec "$@"
|
||||||
44
.docker/scheduler/requirements.txt
Normal file
44
.docker/scheduler/requirements.txt
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
ace_tools
|
||||||
|
aiohappyeyeballs
|
||||||
|
aiohttp
|
||||||
|
aiosignal
|
||||||
|
APScheduler
|
||||||
|
Django
|
||||||
|
django-environ
|
||||||
|
django-extensions
|
||||||
|
django-filter
|
||||||
|
django-health-check
|
||||||
|
django-jazzmin
|
||||||
|
django-jet
|
||||||
|
et_xmlfile
|
||||||
|
fonttools
|
||||||
|
fpdf2
|
||||||
|
geoip2
|
||||||
|
git-filter-repo
|
||||||
|
httpcore
|
||||||
|
httpx
|
||||||
|
jsonschema
|
||||||
|
jsonschema-specifications
|
||||||
|
maxminddb
|
||||||
|
multidict
|
||||||
|
PyMySQL
|
||||||
|
numpy
|
||||||
|
openpyxl
|
||||||
|
pandas
|
||||||
|
pathspec
|
||||||
|
pillow
|
||||||
|
propcache
|
||||||
|
psycopg
|
||||||
|
PyMySQL
|
||||||
|
python-dateutil
|
||||||
|
python-decouple
|
||||||
|
python-dotenv
|
||||||
|
python-telegram-bot
|
||||||
|
PyYAML
|
||||||
|
requests
|
||||||
|
sqlparse
|
||||||
|
ua-parser
|
||||||
|
ua-parser-builtins
|
||||||
|
user-agents
|
||||||
|
yarl
|
||||||
|
cryptography
|
||||||
32
antifroud/migrations/0002_initial.py
Normal file
32
antifroud/migrations/0002_initial.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2024-12-19 12:42
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('antifroud', '0001_initial'),
|
||||||
|
('hotels', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='roomdiscrepancy',
|
||||||
|
name='hotel',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Отель'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='synclog',
|
||||||
|
name='hotel',
|
||||||
|
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Отель'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='violationlog',
|
||||||
|
name='hotel',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Отель'),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
app_settings/__init__.py
Normal file
0
app_settings/__init__.py
Normal file
24
app_settings/admin.py
Normal file
24
app_settings/admin.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from .models import LocalDatabase, GlobalHotelSettings, GlobalSystemSettings, TelegramSettings, EmailSettings
|
||||||
|
|
||||||
|
@admin.register(LocalDatabase)
|
||||||
|
class LocalDatabaseAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['name', 'host', 'port', 'user', 'database', 'is_active']
|
||||||
|
search_fields = ['name', 'host', 'user', 'database']
|
||||||
|
|
||||||
|
@admin.register(GlobalHotelSettings)
|
||||||
|
class GlobalHotelSettingsAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['check_in_time', 'check_out_time', 'global_timezone']
|
||||||
|
list_filter = ['global_timezone']
|
||||||
|
|
||||||
|
@admin.register(GlobalSystemSettings)
|
||||||
|
class GlobalSystemSettingsAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['system_name', 'system_version', 'server_timezone']
|
||||||
|
|
||||||
|
@admin.register(TelegramSettings)
|
||||||
|
class TelegramSettingsAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['bot_token', 'username']
|
||||||
|
|
||||||
|
@admin.register(EmailSettings)
|
||||||
|
class EmailSettingsAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['smtp_server', 'smtp_port', 'smtp_user', 'from_email']
|
||||||
50
app_settings/app_settings.py
Normal file
50
app_settings/app_settings.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# settings.py
|
||||||
|
|
||||||
|
from .models import LocalDatabase
|
||||||
|
from decouple import config
|
||||||
|
from django.conf import settings
|
||||||
|
from .models import LocalDatabase
|
||||||
|
|
||||||
|
def load_database_settings():
|
||||||
|
# Загружаем настройки из базы данных
|
||||||
|
local_db_settings = LocalDatabase.objects.all()
|
||||||
|
|
||||||
|
for db in local_db_settings:
|
||||||
|
# Пример добавления дополнительной базы данных
|
||||||
|
settings.DATABASES[db.name] = {
|
||||||
|
'ENGINE': 'django.db.backends.mysql',
|
||||||
|
'NAME': db.db_name,
|
||||||
|
'USER': db.username,
|
||||||
|
'PASSWORD': db.password,
|
||||||
|
'HOST': db.host,
|
||||||
|
'PORT': db.port,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Вызов этой функции при старте проекта, например, в файле wsgi.py
|
||||||
|
load_database_settings()
|
||||||
|
|
||||||
|
# Чтение локальных баз данных
|
||||||
|
local_databases = LocalDatabase.objects.filter(is_active=True)
|
||||||
|
|
||||||
|
# Основная база данных
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.postgresql',
|
||||||
|
'NAME': config('DB_NAME'),
|
||||||
|
'USER': config('DB_USER'),
|
||||||
|
'PASSWORD': config('DB_PASSWORD'),
|
||||||
|
'HOST': config('DB_HOST'),
|
||||||
|
'PORT': config('DB_PORT'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Добавление локальных баз данных
|
||||||
|
for db in local_databases:
|
||||||
|
DATABASES[db.name] = {
|
||||||
|
'ENGINE': 'django.db.backends.postgresql',
|
||||||
|
'NAME': db.name,
|
||||||
|
'USER': db.user,
|
||||||
|
'PASSWORD': db.password,
|
||||||
|
'HOST': db.host,
|
||||||
|
'PORT': db.port,
|
||||||
|
}
|
||||||
14
app_settings/apps.py
Normal file
14
app_settings/apps.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from django.apps import AppConfig, apps
|
||||||
|
|
||||||
|
class AppSettingsConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'app_settings'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
# Проверяем, что приложения готовы
|
||||||
|
if not apps.ready:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
import app_settings.signals # Регистрация сигналов
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"Ошибка импорта signals: {e}")
|
||||||
86
app_settings/migrations/0001_initial.py
Normal file
86
app_settings/migrations/0001_initial.py
Normal file
File diff suppressed because one or more lines are too long
0
app_settings/migrations/__init__.py
Normal file
0
app_settings/migrations/__init__.py
Normal file
77
app_settings/models.py
Normal file
77
app_settings/models.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
from django.db import models
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
class LocalDatabase(models.Model):
|
||||||
|
name = models.CharField(max_length=255, verbose_name="Имя базы данных")
|
||||||
|
host = models.CharField(max_length=255, verbose_name="Хост базы данных", default="localhost")
|
||||||
|
port = models.IntegerField(default=5432, verbose_name="Порт базы данных")
|
||||||
|
user = models.CharField(max_length=255, verbose_name="Пользователь базы данных")
|
||||||
|
database = models.CharField(max_length=255, verbose_name="Название базы данных")
|
||||||
|
password = models.CharField(max_length=255, verbose_name="Пароль базы данных")
|
||||||
|
is_active = models.BooleanField(default=True, verbose_name="Активна ли база данных")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "База данных"
|
||||||
|
verbose_name_plural = "Базы данных"
|
||||||
|
|
||||||
|
class TelegramSettings(models.Model):
|
||||||
|
bot_token = models.CharField(max_length=255, help_text="Токен вашего бота Telegram")
|
||||||
|
chat_id = models.CharField(max_length=255, help_text="ID чата для отправки сообщений")
|
||||||
|
username = models.CharField(max_length=255, help_text="Имя пользователя для бота", blank=True, null=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Telegram Bot ({self.username})"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Telegram"
|
||||||
|
verbose_name_plural = "Telegram"
|
||||||
|
|
||||||
|
class EmailSettings(models.Model):
|
||||||
|
smtp_server = models.CharField(max_length=255, help_text="SMTP сервер для отправки почты")
|
||||||
|
smtp_port = models.IntegerField(default=587, help_text="SMTP порт для почты")
|
||||||
|
smtp_user = models.CharField(max_length=255, help_text="Имя пользователя для SMTP")
|
||||||
|
smtp_password = models.CharField(max_length=255, help_text="Пароль для SMTP")
|
||||||
|
from_email = models.EmailField(help_text="Email для отправки сообщений")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "E-mail"
|
||||||
|
verbose_name_plural = "E-mails"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Email Settings for {self.from_email}"
|
||||||
|
|
||||||
|
class GlobalHotelSettings(models.Model):
|
||||||
|
check_in_time = models.TimeField(help_text="Время заезда")
|
||||||
|
check_out_time = models.TimeField(help_text="Время выезда")
|
||||||
|
currency = models.CharField(max_length=3, help_text="Валюта")
|
||||||
|
global_timezone = models.CharField(
|
||||||
|
max_length=63,
|
||||||
|
choices=[(tz, tz) for tz in pytz.all_timezones],
|
||||||
|
default='UTC',
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Настройки отеля"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Настройки отеля"
|
||||||
|
verbose_name_plural = "Настройки отеля"
|
||||||
|
|
||||||
|
class GlobalSystemSettings(models.Model):
|
||||||
|
system_name = models.CharField(max_length=255, help_text="Название системы")
|
||||||
|
system_version = models.CharField(max_length=255, help_text="Версия системы")
|
||||||
|
server_timezone = models.CharField(
|
||||||
|
max_length=63,
|
||||||
|
choices=[(tz, tz) for tz in pytz.all_timezones],
|
||||||
|
default='UTC',
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Настройки системы"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Настройки системы"
|
||||||
|
verbose_name_plural = "Настройки системы"
|
||||||
9
app_settings/signals.py
Normal file
9
app_settings/signals.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from django.db.models.signals import post_save
|
||||||
|
from django.dispatch import receiver
|
||||||
|
from .models import GlobalSystemSettings
|
||||||
|
|
||||||
|
@receiver(post_save, sender=GlobalSystemSettings)
|
||||||
|
def update_system_settings(sender, instance, **kwargs):
|
||||||
|
# Безопасное использование сигнала
|
||||||
|
if instance:
|
||||||
|
print(f"Настройки системы обновлены: {instance.system_name}")
|
||||||
3
app_settings/tests.py
Normal file
3
app_settings/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
11
app_settings/urls.py
Normal file
11
app_settings/urls.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
app_name = 'settings'
|
||||||
|
|
||||||
|
def placeholder_view(request):
|
||||||
|
return HttpResponse("Placeholder for settings app.")
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', placeholder_view, name='settings_placeholder'),
|
||||||
|
]
|
||||||
3
app_settings/views.py
Normal file
3
app_settings/views.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
73
docker-compose.yml
Normal file
73
docker-compose.yml
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
version: '3.9'
|
||||||
|
|
||||||
|
services:
|
||||||
|
mysql:
|
||||||
|
image: mysql:8.0
|
||||||
|
container_name: mysql
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
|
||||||
|
MYSQL_DATABASE: ${DB_NAME}
|
||||||
|
MYSQL_USER: ${DB_USER}
|
||||||
|
MYSQL_PASSWORD: ${DB_PASSWORD}
|
||||||
|
TMPDIR: /var/tmp
|
||||||
|
ports:
|
||||||
|
- "${DB_PORT}:3306"
|
||||||
|
volumes:
|
||||||
|
- mysql_data:/var/lib/mysql
|
||||||
|
- /var/tmp:/var/tmp
|
||||||
|
|
||||||
|
django-admin:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: .docker/admin/Dockerfile
|
||||||
|
container_name: django-admin
|
||||||
|
restart: on-failure
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- DJANGO_SETTINGS_MODULE=touchh.settings
|
||||||
|
- DATABASE_URL=mysql://${DB_USER}:${DB_PASSWORD}@mysql:3306/${DB_NAME}
|
||||||
|
- LOG_LEVEL=${LOG_LEVEL}
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
command: python manage.py runserver 0.0.0.0:8000
|
||||||
|
|
||||||
|
bot:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: .docker/bot/Dockerfile
|
||||||
|
container_name: bot
|
||||||
|
restart: on-failure
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
environment:
|
||||||
|
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
|
||||||
|
- DJANGO_SETTINGS_MODULE=project.settings
|
||||||
|
- DATABASE_URL=mysql://${DB_USER}:${DB_PASSWORD}@mysql:3306/${DB_NAME}
|
||||||
|
- LOG_LEVEL=${LOG_LEVEL}
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
|
||||||
|
scheduler:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: .docker/scheduler/Dockerfile
|
||||||
|
container_name: scheduler
|
||||||
|
restart: on-failure
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- DJANGO_SETTINGS_MODULE=project.settings
|
||||||
|
- DATABASE_URL=mysql://${DB_USER}:${DB_PASSWORD}@mysql:3306/${DB_NAME}
|
||||||
|
- SCHEDULED_SYNC_LOG_LEVEL=${SCHEDULED_SYNC_LOG_LEVEL}
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql_data:
|
||||||
65
req1.txt
Normal file
65
req1.txt
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
ace_tools==0.0
|
||||||
|
aiohappyeyeballs==2.4.4
|
||||||
|
aiohttp==3.11.11
|
||||||
|
aiosignal==1.3.2
|
||||||
|
anyio==4.7.0
|
||||||
|
APScheduler==3.11.0
|
||||||
|
asgiref==3.8.1
|
||||||
|
async-timeout==5.0.1
|
||||||
|
attrs==24.3.0
|
||||||
|
certifi==2024.12.14
|
||||||
|
cffi==1.17.1
|
||||||
|
charset-normalizer==3.4.0
|
||||||
|
cryptography==44.0.0
|
||||||
|
defusedxml==0.7.1
|
||||||
|
Django==5.1.4
|
||||||
|
django-environ==0.11.2
|
||||||
|
django-extensions==3.2.3
|
||||||
|
django-filter==24.3
|
||||||
|
django-health-check==3.18.3
|
||||||
|
django-jazzmin==3.0.1
|
||||||
|
django-jet==1.0.8
|
||||||
|
et_xmlfile==2.0.0
|
||||||
|
exceptiongroup==1.2.2
|
||||||
|
fonttools==4.55.3
|
||||||
|
fpdf2==2.8.2
|
||||||
|
frozenlist==1.5.0
|
||||||
|
geoip2==4.8.1
|
||||||
|
git-filter-repo==2.47.0
|
||||||
|
h11==0.14.0
|
||||||
|
httpcore==1.0.7
|
||||||
|
httpx==0.28.1
|
||||||
|
idna==3.10
|
||||||
|
jsonschema==4.23.0
|
||||||
|
jsonschema-specifications==2024.10.1
|
||||||
|
maxminddb==2.6.2
|
||||||
|
multidict==6.1.0
|
||||||
|
numpy==2.2.0
|
||||||
|
openpyxl==3.1.5
|
||||||
|
pandas==2.2.3
|
||||||
|
pathspec==0.12.1
|
||||||
|
pillow==11.0.0
|
||||||
|
propcache==0.2.1
|
||||||
|
psycopg==3.2.3
|
||||||
|
pycparser==2.22
|
||||||
|
PyMySQL==1.1.1
|
||||||
|
python-dateutil==2.9.0.post0
|
||||||
|
python-decouple==3.8
|
||||||
|
python-dotenv==1.0.1
|
||||||
|
python-telegram-bot==21.9
|
||||||
|
pytz==2024.2
|
||||||
|
PyYAML==6.0.2
|
||||||
|
referencing==0.35.1
|
||||||
|
requests==2.32.3
|
||||||
|
rpds-py==0.22.3
|
||||||
|
six==1.17.0
|
||||||
|
sniffio==1.3.1
|
||||||
|
sqlparse==0.5.3
|
||||||
|
typing_extensions==4.12.2
|
||||||
|
tzdata==2024.2
|
||||||
|
tzlocal==5.2
|
||||||
|
ua-parser==1.0.0
|
||||||
|
ua-parser-builtins==0.18.0.post1
|
||||||
|
urllib3==2.2.3
|
||||||
|
user-agents==2.2.0
|
||||||
|
yarl==1.18.3
|
||||||
0
scheduler/management/commands/__init__.py
Normal file
0
scheduler/management/commands/__init__.py
Normal file
27
scheduler/management/commands/run_scheduler.py
Normal file
27
scheduler/management/commands/run_scheduler.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import os
|
||||||
|
import django
|
||||||
|
import asyncio
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from scheduler.tasks import setup_scheduler
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Запуск планировщика задач"
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
# Устанавливаем Django окружение
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "touchh.settings")
|
||||||
|
django.setup()
|
||||||
|
|
||||||
|
# Основная асинхронная функция
|
||||||
|
async def start_scheduler():
|
||||||
|
scheduler = await setup_scheduler()
|
||||||
|
self.stdout.write(self.style.SUCCESS("Планировщик задач успешно запущен."))
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
await asyncio.sleep(3600) # Бесконечный цикл для поддержания работы
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
scheduler.shutdown()
|
||||||
|
|
||||||
|
# Запускаем планировщик в асинхронном режиме
|
||||||
|
asyncio.run(start_scheduler())
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
95
scheduler/task_loader.py
Normal file
95
scheduler/task_loader.py
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import os
|
||||||
|
import inspect
|
||||||
|
import importlib
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
from typing import List, Tuple
|
||||||
|
from pathspec import PathSpec
|
||||||
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||||
|
|
||||||
|
# Настройка логирования
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def reload_tasks_periodically(scheduler: AsyncIOScheduler):
|
||||||
|
"""Перезагрузка задач из базы данных каждые 5 минут."""
|
||||||
|
async def reload():
|
||||||
|
from scheduler.tasks import load_tasks_to_scheduler
|
||||||
|
try:
|
||||||
|
await load_tasks_to_scheduler(scheduler)
|
||||||
|
logger.info("Задачи успешно перезагружены.")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка перезагрузки задач: {e}")
|
||||||
|
|
||||||
|
scheduler.add_job(lambda: asyncio.run(reload()), "interval", minutes=5)
|
||||||
|
|
||||||
|
def load_gitignore_patterns(project_root: str) -> PathSpec:
|
||||||
|
"""
|
||||||
|
Загружает паттерны из файла .gitignore.
|
||||||
|
"""
|
||||||
|
gitignore_path = os.path.join(project_root, ".gitignore")
|
||||||
|
try:
|
||||||
|
if os.path.exists(gitignore_path):
|
||||||
|
with open(gitignore_path, "r", encoding="utf-8") as f:
|
||||||
|
patterns = f.readlines()
|
||||||
|
return PathSpec.from_lines("gitwildmatch", patterns)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Ошибка загрузки .gitignore: {e}")
|
||||||
|
return PathSpec.from_lines("gitwildmatch", [])
|
||||||
|
|
||||||
|
def get_project_functions() -> List[Tuple[str, str]]:
|
||||||
|
"""
|
||||||
|
Сканирует проект и возвращает список всех функций в формате (путь, имя функции),
|
||||||
|
исключая файлы и папки, указанные в .gitignore.
|
||||||
|
"""
|
||||||
|
functions = []
|
||||||
|
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
# Загружаем паттерны из .gitignore
|
||||||
|
gitignore_spec = load_gitignore_patterns(project_root)
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(project_root):
|
||||||
|
# Исключаем директории, указанные в .gitignore
|
||||||
|
dirs[:] = [d for d in dirs if not gitignore_spec.match_file(os.path.relpath(os.path.join(root, d), project_root))]
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
file_path = os.path.relpath(os.path.join(root, file), project_root)
|
||||||
|
if (
|
||||||
|
file.endswith(".py") and
|
||||||
|
not file.startswith("__") and
|
||||||
|
not gitignore_spec.match_file(file_path)
|
||||||
|
):
|
||||||
|
module_path = os.path.relpath(os.path.join(root, file), project_root)
|
||||||
|
module_name = module_path.replace(os.sep, ".").replace(".py", "")
|
||||||
|
|
||||||
|
try:
|
||||||
|
spec = importlib.util.find_spec(module_name)
|
||||||
|
if spec is not None:
|
||||||
|
module = importlib.import_module(module_name)
|
||||||
|
for name, func in inspect.getmembers(module, inspect.isfunction):
|
||||||
|
functions.append((f"{module_name}.{name}", name))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при обработке модуля {module_name}: {e}")
|
||||||
|
|
||||||
|
return functions
|
||||||
|
|
||||||
|
def execute_function(function_path: str):
|
||||||
|
"""
|
||||||
|
Выполняет функцию по указанному пути.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
module_name, func_name = function_path.rsplit(".", 1)
|
||||||
|
spec = importlib.util.find_spec(module_name)
|
||||||
|
if spec is None:
|
||||||
|
raise ImportError(f"Модуль {module_name} не найден")
|
||||||
|
|
||||||
|
module = importlib.import_module(module_name)
|
||||||
|
if not hasattr(module, func_name):
|
||||||
|
raise AttributeError(f"Функция {func_name} отсутствует в модуле {module_name}")
|
||||||
|
|
||||||
|
func = getattr(module, func_name)
|
||||||
|
logger.info(f"Выполняется функция: {function_path}")
|
||||||
|
return func()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка выполнения функции {function_path}: {e}")
|
||||||
|
return None
|
||||||
14
scheduler/urls.py
Normal file
14
scheduler/urls.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
def placeholder_view(request):
|
||||||
|
"""
|
||||||
|
Заглушка для URL-адресов приложения scheduler.
|
||||||
|
"""
|
||||||
|
return HttpResponse("Это заглушка для приложения scheduler.")
|
||||||
|
|
||||||
|
app_name = "scheduler"
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("", placeholder_view, name="scheduler_placeholder"),
|
||||||
|
]
|
||||||
43
staticfiles/admin/js/collapse.js
Normal file
43
staticfiles/admin/js/collapse.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/*global gettext*/
|
||||||
|
'use strict';
|
||||||
|
{
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
// Add anchor tag for Show/Hide link
|
||||||
|
const fieldsets = document.querySelectorAll('fieldset.collapse');
|
||||||
|
for (const [i, elem] of fieldsets.entries()) {
|
||||||
|
// Don't hide if fields in this fieldset have errors
|
||||||
|
if (elem.querySelectorAll('div.errors, ul.errorlist').length === 0) {
|
||||||
|
elem.classList.add('collapsed');
|
||||||
|
const h2 = elem.querySelector('h2');
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.id = 'fieldsetcollapser' + i;
|
||||||
|
link.className = 'collapse-toggle';
|
||||||
|
link.href = '#';
|
||||||
|
link.textContent = gettext('Show');
|
||||||
|
h2.appendChild(document.createTextNode(' ('));
|
||||||
|
h2.appendChild(link);
|
||||||
|
h2.appendChild(document.createTextNode(')'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add toggle to hide/show anchor tag
|
||||||
|
const toggleFunc = function(ev) {
|
||||||
|
if (ev.target.matches('.collapse-toggle')) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
const fieldset = ev.target.closest('fieldset');
|
||||||
|
if (fieldset.classList.contains('collapsed')) {
|
||||||
|
// Show
|
||||||
|
ev.target.textContent = gettext('Hide');
|
||||||
|
fieldset.classList.remove('collapsed');
|
||||||
|
} else {
|
||||||
|
// Hide
|
||||||
|
ev.target.textContent = gettext('Show');
|
||||||
|
fieldset.classList.add('collapsed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.querySelectorAll('fieldset.module').forEach(function(el) {
|
||||||
|
el.addEventListener('click', toggleFunc);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user