From a196a2b941c99bf60730d039dbc84dd4346e3a27 Mon Sep 17 00:00:00 2001 From: lizerk Date: Tue, 10 Mar 2026 12:41:04 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B5=D1=89=D0=B5=20-=20=D0=BB=D0=B8=D1=88?= =?UTF-8?q?=D0=BD=D0=B5=D0=B5=20=D0=B2=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA?= =?UTF-8?q?=D1=82=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/CELERY_NEXT_STEPS.md | 201 ------------------------- backend/check_event_params.py | 48 ------ backend/docker-compose.test.yml | 57 ------- backend/test_notification_flow.py | 146 ------------------ backend/test_notifications.py | 237 ------------------------------ 5 files changed, 689 deletions(-) delete mode 100644 backend/CELERY_NEXT_STEPS.md delete mode 100644 backend/check_event_params.py delete mode 100644 backend/docker-compose.test.yml delete mode 100644 backend/test_notification_flow.py delete mode 100644 backend/test_notifications.py diff --git a/backend/CELERY_NEXT_STEPS.md b/backend/CELERY_NEXT_STEPS.md deleted file mode 100644 index ae799d96..00000000 --- a/backend/CELERY_NEXT_STEPS.md +++ /dev/null @@ -1,201 +0,0 @@ -# Celery Integration - Следующие шаги - -## ✅ Что уже сделано - -1. ✅ Добавлены зависимости в `requirements.txt`: - - `celery==5.5.3` - - `redis==5.2.1` - - `django-celery-beat==2.7.0` - - `django-celery-results==2.5.1` - - `flower==2.0.1` - -2. ✅ Создан файл конфигурации `eusrr_backend/celery.py` - - Настроена автоматическая регистрация задач - - Добавлен debug task для тестирования - -3. ✅ Обновлен `eusrr_backend/__init__.py` - - Автоматический импорт celery_app при запуске Django - -4. ✅ Добавлены настройки Celery в `settings.py`: - - `CELERY_BROKER_URL` - Redis как брокер сообщений - - `CELERY_RESULT_BACKEND` - Redis для хранения результатов - - Настройка роутинга задач по очередям - - Таймауты и лимиты для workers - -5. ✅ Добавлены приложения в `INSTALLED_APPS`: - - `django_celery_beat` - для периодических задач - - `django_celery_results` - для хранения результатов - -6. ✅ Созданы Celery задачи в `notifications/tasks.py`: - - `send_notification_task` - отправка одного уведомления - - `send_bulk_notifications_task` - массовая отправка - - `cleanup_old_notifications_task` - очистка старых уведомлений - - `send_digest_email_task` - email-дайджесты - -## 🔄 Следующие шаги (выполнить вручную) - -### Шаг 1: Установка зависимостей - -Запустите задачу в VS Code: -- **Pip: install requirements** - -Или вручную: -```bash -cd backend -..\.venv\Scripts\pip install -r requirements.txt -``` - -### Шаг 2: Установка и запуск Redis - -#### Вариант 1: Windows (рекомендуется Memurai) -1. Скачайте Memurai (Redis для Windows): https://www.memurai.com/ -2. Установите и запустите как службу Windows -3. Redis будет доступен на `localhost:6379` - -#### Вариант 2: Docker -```bash -docker run -d -p 6379:6379 --name redis redis:7-alpine -``` - -#### Проверка Redis: -```bash -redis-cli ping -# Должно вернуть: PONG -``` - -### Шаг 3: Применение миграций - -Запустите: -```bash -cd backend -migrate_celery.bat -``` - -Или вручную: -```bash -..\.venv\Scripts\python manage.py migrate django_celery_beat -..\.venv\Scripts\python manage.py migrate django_celery_results -``` - -### Шаг 4: Тестирование Celery Worker - -В отдельном терминале запустите worker: -```bash -cd backend -..\.venv\Scripts\celery -A eusrr_backend worker -l info --pool=solo -``` - -**Примечание**: На Windows используйте `--pool=solo` или `--pool=gevent` - -### Шаг 5: Тестирование отправки задачи - -В Django shell: -```python -..\.venv\Scripts\python manage.py shell - -from celery import current_app -current_app.send_task('eusrr_backend.debug_task') -``` - -Вы должны увидеть выполнение задачи в логах worker. - -### Шаг 6: Интеграция с NotificationService - -Обновите `notifications/services.py`: -```python -from notifications.tasks import send_notification_task, send_bulk_notifications_task - -class NotificationService: - @staticmethod - def create_notification(...): - # Вместо синхронного создания - send_notification_task.delay( - notification_type=notification_type, - user_id=user.id, - title=title, - message=message, - link=link, - sender_id=sender.id if sender else None, - metadata=metadata, - ) -``` - -### Шаг 7: Запуск Flower (мониторинг) - -```bash -cd backend -..\.venv\Scripts\celery -A eusrr_backend flower -``` - -Откройте http://localhost:5555 для просмотра статистики задач. - -### Шаг 8: Настройка периодических задач - -В Django Admin: -1. Перейдите в "Periodic Tasks" -2. Добавьте задачу для `notifications.cleanup_old_notifications` -3. Настройте интервал (например, раз в день) - -Или программно в `eusrr_backend/celery.py`: -```python -from celery.schedules import crontab - -app.conf.beat_schedule = { - 'cleanup-old-notifications': { - 'task': 'notifications.cleanup_old_notifications', - 'schedule': crontab(hour=3, minute=0), # Каждый день в 3:00 - 'args': (90,) # Удалять уведомления старше 90 дней - }, -} -``` - -Запустите beat scheduler: -```bash -..\.venv\Scripts\celery -A eusrr_backend beat -l info -``` - -## 📊 Ожидаемый результат - -**До внедрения Celery:** -- Отправка сообщения в группу с 8 участниками: ~500ms -- Блокировка API response до отправки всех уведомлений - -**После внедрения Celery:** -- Отправка сообщения: <50ms (только создание задачи) -- Уведомления обрабатываются асинхронно -- API мгновенно отвечает пользователю - -## 🔍 Отладка - -### Проверка статуса Celery -```bash -..\.venv\Scripts\celery -A eusrr_backend inspect active -..\.venv\Scripts\celery -A eusrr_backend inspect stats -``` - -### Проверка задач в очереди -```bash -..\.venv\Scripts\celery -A eusrr_backend inspect reserved -``` - -### Логи -- Worker логи: консоль где запущен worker -- Flower: http://localhost:5555 -- Django логи: стандартные логи приложения - -## 📚 Документация - -- Подробная документация: `docs/guides/CELERY_SETUP.md` -- План внедрения (5 дней): `docs/in-progress/CELERY_INTEGRATION_PLAN.md` -- Celery docs: https://docs.celeryq.dev/ - -## ⚠️ Production Deployment - -Для production: -1. Используйте Redis Cluster или Redis Sentinel -2. Запускайте несколько workers с supervisord/systemd -3. Настройте мониторинг (Flower + Prometheus) -4. Добавьте retry policies и dead letter queue -5. Настройте автоматический перезапуск workers - -См. `docs/in-progress/CELERY_INTEGRATION_PLAN.md` для деталей. diff --git a/backend/check_event_params.py b/backend/check_event_params.py deleted file mode 100644 index 53e8a0e4..00000000 --- a/backend/check_event_params.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python -"""Проверка параметров события в базе данных.""" -import os -import sys -import django - -# Setup Django -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eusrr_backend.settings") -django.setup() - -# ЯВНЫЙ импорт патча -import schedule_patch # noqa - -from schedule.models import Event, Rule - -# Найдём событие по названию -event = Event.objects.filter(title__icontains='ыфвыфыф').first() - -if event: - print(f"=" * 80) - print(f"Event ID: {event.id}") - print(f"Title: {event.title}") - print(f"Start: {event.start} (weekday: {event.start.weekday()})") - print(f"End recurring: {event.end_recurring_period}") - print(f"=" * 80) - - if event.rule: - print(f"\nRule ID: {event.rule.id}") - print(f"Frequency: {event.rule.frequency}") - print(f"Params: {event.rule.params}") - print(f"=" * 80) - - # Проверим occurrences - from datetime import datetime - from django.utils import timezone - start = timezone.make_aware(datetime(2026, 2, 1, 0, 0, 0)) - end = timezone.make_aware(datetime(2026, 2, 28, 23, 59, 59)) - occurrences = event.get_occurrences(start, end) - - print(f"\nOccurrences в феврале 2026:") - for occ in occurrences: - print(f" - {occ.start.strftime('%Y-%m-%d %A')} {occ.start.strftime('%H:%M')}") - print(f"=" * 80) - else: - print("НЕТ ПРАВИЛА!") -else: - print("Событие не найдено!") diff --git a/backend/docker-compose.test.yml b/backend/docker-compose.test.yml deleted file mode 100644 index 1964acd3..00000000 --- a/backend/docker-compose.test.yml +++ /dev/null @@ -1,57 +0,0 @@ -version: '3.8' - -services: - # OpenLDAP сервер для тестирования - test-ldap: - image: osixia/openldap:1.5.0 - container_name: eusrr-test-ldap - hostname: ldap.test.local - environment: - # Базовая конфигурация - LDAP_ORGANISATION: "EUSRR Test" - LDAP_DOMAIN: "test.local" - LDAP_BASE_DN: "dc=test,dc=local" - LDAP_ADMIN_PASSWORD: "test_change-me-redacted-secret" - LDAP_CONFIG_PASSWORD: "test_config_password" - LDAP_READONLY_USER: "false" - - # Настройки для совместимости с вашим кодом - LDAP_RFC2307BIS_SCHEMA: "true" - LDAP_BACKEND: "mdb" - LDAP_TLS: "false" - LDAP_TLS_VERIFY_CLIENT: "never" - - # Логирование для отладки - LDAP_LOG_LEVEL: "256" - ports: - - "10389:389" # Используем порт 10389 чтобы не конфликтовать с prod - - "10636:636" # SSL порт (если понадобится) - volumes: - - ./ldap_test_data:/var/lib/ldap - - ./ldap_test_config:/etc/ldap/slapd.d - - ./tests/ldap_fixtures:/container/service/slapd/assets/config/bootstrap/ldif/custom - networks: - - test-network - command: --copy-service --loglevel debug - - # phpLDAPadmin - веб-интерфейс для управления (опционально) - ldap-admin: - image: osixia/phpldapadmin:0.9.0 - container_name: eusrr-ldap-admin - environment: - PHPLDAPADMIN_LDAP_HOSTS: "test-ldap" - PHPLDAPADMIN_HTTPS: "false" - ports: - - "8090:80" - depends_on: - - test-ldap - networks: - - test-network - -networks: - test-network: - driver: bridge - -volumes: - ldap_test_data: - ldap_test_config: diff --git a/backend/test_notification_flow.py b/backend/test_notification_flow.py deleted file mode 100644 index 674cf325..00000000 --- a/backend/test_notification_flow.py +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env python -""" -Тест полного цикла уведомлений - -Проверяет: -1. Создание уведомления через notify.send() -2. Срабатывание post_save сигнала -3. Отправку задач в Celery -4. Наличие уведомления в БД -5. Выполнение задач Celery -""" - -import django -import os -import sys -import time - -# Setup Django -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'eusrr_backend.settings') -sys.path.insert(0, os.path.dirname(__file__)) -django.setup() - -from django.contrib.auth import get_user_model -from notifications.signals import notify -from notifications.models import Notification - -User = get_user_model() - - -def test_notification_creation(): - """Тест создания уведомления""" - - print("\n" + "="*60) - print("ТЕСТ СИСТЕМЫ УВЕДОМЛЕНИЙ") - print("="*60) - - # 1. Находим двух пользователей - print("\n1. Поиск пользователей...") - users = User.objects.filter(is_active=True)[:2] - - if users.count() < 2: - print("❌ Нужно минимум 2 активных пользователя") - return - - sender = users[0] - recipient = users[1] - - print(f" ✅ Отправитель: {sender.username} (ID: {sender.id})") - print(f" ✅ Получатель: {recipient.username} (ID: {recipient.id})") - - # 2. Проверяем настройки получателя - print("\n2. Проверка настроек получателя...") - try: - prefs = recipient.channel_preferences - print(f" ✅ web_enabled: {prefs.web_enabled}") - print(f" ✅ email_enabled: {prefs.email_enabled}") - print(f" ✅ push_enabled: {prefs.push_enabled}") - print(f" ✅ email_frequency: {prefs.email_frequency}") - print(f" ✅ DND период: {prefs.is_in_dnd_period()}") - except Exception as e: - print(f" ⚠️ Нет настроек (будут созданы): {e}") - - # 3. Подсчет уведомлений до создания - print("\n3. Подсчет уведомлений перед тестом...") - count_before = Notification.objects.count() - print(f" Всего уведомлений в БД: {count_before}") - - # 4. Создаем уведомление - print("\n4. Создание уведомления через notify.send()...") - - try: - result = notify.send( - sender=sender, - recipient=recipient, - verb='test_notification', - description=f'🧪 Тестовое уведомление от {sender.username}', - action_url='/test/', - data={ - 'test': True, - 'timestamp': time.time(), - } - ) - - print(f" ✅ notify.send() выполнен") - print(f" Результат: {result}") - - except Exception as e: - print(f" ❌ Ошибка при создании: {e}") - import traceback - traceback.print_exc() - return - - # 5. Проверяем что уведомление создано в БД - print("\n5. Проверка создания в БД...") - time.sleep(0.1) # Небольшая задержка для сохранения - - count_after = Notification.objects.count() - new_notifications = count_after - count_before - - print(f" Всего уведомлений в БД: {count_after}") - print(f" Создано новых: {new_notifications}") - - if new_notifications == 0: - print(" ❌ Уведомление не создано в БД!") - return - - # 6. Получаем созданное уведомление - print("\n6. Детали созданного уведомления...") - notification = Notification.objects.filter(recipient=recipient).order_by('-id').first() - - if notification: - print(f" ✅ ID: {notification.id}") - print(f" ✅ verb: {notification.verb}") - print(f" ✅ description: {notification.description}") - print(f" ✅ recipient: {notification.recipient.username}") - print(f" ✅ actor: {notification.actor}") - print(f" ✅ unread: {notification.unread}") - print(f" ✅ timestamp: {notification.timestamp}") - else: - print(" ❌ Уведомление не найдено!") - return - - # 7. Ждем выполнения Celery задач - print("\n7. Ожидание выполнения Celery задач...") - print(" ⏳ Ждем 2 секунды...") - time.sleep(2) - - # 8. Проверяем логи Celery - print("\n8. Результаты:") - print(" 📋 Проверьте логи Celery worker на наличие:") - print(f" - Task notifications.send_websocket_notification[...] received") - print(f" - Notification {notification.id} not found (если есть race condition)") - print(f" - Task ... succeeded (если всё ОК)") - - print("\n" + "="*60) - print("ТЕСТ ЗАВЕРШЕН") - print("="*60) - print("\nПроверьте:") - print("1. Логи Celery worker") - print("2. WebSocket соединение в браузере") - print("3. Появилось ли уведомление в UI") - print() - - -if __name__ == '__main__': - test_notification_creation() diff --git a/backend/test_notifications.py b/backend/test_notifications.py deleted file mode 100644 index 8fdf8bb3..00000000 --- a/backend/test_notifications.py +++ /dev/null @@ -1,237 +0,0 @@ -#!/usr/bin/env python -""" -Тестовый скрипт для проверки системы уведомлений v2.0 - -Использование: - python test_notifications.py - -Что тестируется: -- Создание уведомления через notify.send() -- Сохранение в БД -- Отправка через Celery задачи (WebSocket, Email, Push) -- Channel preferences -- API endpoints -""" -import os -import django - -# Настройка Django -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'eusrr_backend.settings') -django.setup() - -from django.contrib.auth import get_user_model -from notifications.signals import notify -from notifications.models import Notification, UserChannelPreferences - -User = get_user_model() - - -def test_notify_send(): - """Тест создания уведомления через notify.send()""" - print("\n" + "="*80) - print("ТЕСТ 1: Создание уведомления через notify.send()") - print("="*80) - - try: - # Получаем первых двух пользователей - users = User.objects.all()[:2] - if len(users) < 2: - print("❌ Нужно минимум 2 пользователя в БД") - return False - - sender_user = users[0] - recipient_user = users[1] - - print(f"📤 Отправитель: {sender_user.username}") - print(f"📥 Получатель: {recipient_user.username}") - - # Создаем уведомление - notification = notify.send( - sender=sender_user, - recipient=recipient_user, - verb='liked', - description=f'{sender_user.get_full_name() or sender_user.username} liked your post', - action_url='/posts/123/', - ) - - if isinstance(notification, list): - notification = notification[0] - - print(f"✅ Уведомление создано: ID={notification.id}") - print(f" Verb: {notification.verb}") - print(f" Description: {notification.description}") - print(f" Actor: {notification.actor}") - print(f" Unread: {notification.unread}") - - return True - - except Exception as e: - print(f"❌ Ошибка: {e}") - import traceback - traceback.print_exc() - return False - - -def test_channel_preferences(): - """Тест настроек каналов""" - print("\n" + "="*80) - print("ТЕСТ 2: UserChannelPreferences") - print("="*80) - - try: - user = User.objects.first() - if not user: - print("❌ Нет пользователей в БД") - return False - - print(f"👤 Пользователь: {user.username}") - - # Получаем или создаем настройки - prefs, created = UserChannelPreferences.objects.get_or_create(user=user) - - if created: - print("✅ Созданы новые настройки с дефолтными значениями") - else: - print("✅ Настройки уже существуют") - - print(f" Web: {prefs.web_enabled}") - print(f" Email: {prefs.email_enabled} (frequency: {prefs.email_frequency})") - print(f" Push: {prefs.push_enabled}") - print(f" DND: {prefs.dnd_enabled} ({prefs.dnd_start_time} - {prefs.dnd_end_time})") - - # Проверяем is_in_dnd_period() - is_dnd = prefs.is_in_dnd_period() - print(f" Сейчас в DND: {is_dnd}") - - return True - - except Exception as e: - print(f"❌ Ошибка: {e}") - import traceback - traceback.print_exc() - return False - - -def test_queryset_methods(): - """Тест методов QuerySet""" - print("\n" + "="*80) - print("ТЕСТ 3: QuerySet методы") - print("="*80) - - try: - user = User.objects.first() - if not user: - print("❌ Нет пользователей в БД") - return False - - print(f"👤 Пользователь: {user.username}") - - # Получаем статистику - total = Notification.objects.filter(recipient=user).count() - unread = Notification.objects.filter(recipient=user).unread().count() - read_count = Notification.objects.filter(recipient=user).read().count() - - print(f"📊 Статистика:") - print(f" Всего: {total}") - print(f" Непрочитанных: {unread}") - print(f" Прочитанных: {read_count}") - - # Последние 5 уведомлений - recent = Notification.objects.filter( - recipient=user - ).order_by('-timestamp')[:5] - - if recent: - print(f"\n📬 Последние {recent.count()} уведомлений:") - for n in recent: - status = "🔵" if n.unread else "⚪" - print(f" {status} [{n.verb}] {n.description[:50]}... ({n.timesince()} ago)") - - return True - - except Exception as e: - print(f"❌ Ошибка: {e}") - import traceback - traceback.print_exc() - return False - - -def test_mark_as_read(): - """Тест отметки как прочитанного""" - print("\n" + "="*80) - print("ТЕСТ 4: Отметка как прочитанного") - print("="*80) - - try: - user = User.objects.first() - if not user: - print("❌ Нет пользователей в БД") - return False - - # Находим непрочитанное уведомление - notification = Notification.objects.filter( - recipient=user, - unread=True - ).first() - - if not notification: - print("⚠️ Нет непрочитанных уведомлений") - return True - - print(f"📬 Уведомление ID={notification.id}: unread={notification.unread}") - - # Отмечаем как прочитанное - notification.mark_as_read() - - # Перезагружаем из БД - notification.refresh_from_db() - - print(f"✅ После mark_as_read(): unread={notification.unread}") - print(f" timestamp_read: {notification.timestamp_read}") - - return True - - except Exception as e: - print(f"❌ Ошибка: {e}") - import traceback - traceback.print_exc() - return False - - -def main(): - """Запуск всех тестов""" - print("\n" * 2) - print("╔" + "="*78 + "╗") - print("║" + " "*20 + "ТЕСТИРОВАНИЕ NOTIFICATIONS V2.0" + " "*25 + "║") - print("╚" + "="*78 + "╝") - - results = [] - - # Запускаем тесты - results.append(("notify.send()", test_notify_send())) - results.append(("UserChannelPreferences", test_channel_preferences())) - results.append(("QuerySet методы", test_queryset_methods())) - results.append(("mark_as_read()", test_mark_as_read())) - - # Итоги - print("\n" + "="*80) - print("ИТОГИ") - print("="*80) - - for test_name, result in results: - status = "✅ PASSED" if result else "❌ FAILED" - print(f"{status}: {test_name}") - - passed = sum(1 for _, r in results if r) - total = len(results) - - print(f"\n{'='*80}") - print(f"Пройдено: {passed}/{total} тестов") - print(f"{'='*80}\n") - - return all(r for _, r in results) - - -if __name__ == "__main__": - success = main() - exit(0 if success else 1)