diff --git a/OneCprogsite/OneCprogsite/__pycache__/settings.cpython-310.pyc b/OneCprogsite/OneCprogsite/__pycache__/settings.cpython-310.pyc index b93c66d..f260f30 100644 Binary files a/OneCprogsite/OneCprogsite/__pycache__/settings.cpython-310.pyc and b/OneCprogsite/OneCprogsite/__pycache__/settings.cpython-310.pyc differ diff --git a/OneCprogsite/OneCprogsite/settings.py b/OneCprogsite/OneCprogsite/settings.py index 65ac19e..2d73e10 100644 --- a/OneCprogsite/OneCprogsite/settings.py +++ b/OneCprogsite/OneCprogsite/settings.py @@ -137,3 +137,25 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = '/media/' + +# Настройки email +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +# EMAIL_HOST = 'smtp.yandex.ru' # или smtp.gmail.com, smtp.mail.ru +# EMAIL_PORT = 587 +# EMAIL_USE_TLS = True +# EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', 'it@yandex.ru') +# EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', 'tifdctkrcjcqwxyc') +# DEFAULT_FROM_EMAIL = EMAIL_HOST_USER +# SERVER_EMAIL = EMAIL_HOST_USER + +EMAIL_HOST = 'smtp.gmail.com' # или smtp.gmail.com, smtp.mail.ru +EMAIL_PORT = 587 +EMAIL_USE_TLS = True +EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', 'nikdizell@gmail.com') +EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', 'qvmw yccb msqv mmpj') +DEFAULT_FROM_EMAIL = EMAIL_HOST_USER +SERVER_EMAIL = EMAIL_HOST_USER + +# Email для уведомлений (можно указать несколько через запятую) +# ADMIN_EMAILS = os.getenv('ADMIN_EMAILS', 'nikdizell@gmail.com').split(',') +ADMIN_EMAILS = os.getenv('ADMIN_EMAILS', 'it@nserdyuk.ru').split(',') diff --git a/OneCprogsite/programmer/__pycache__/admin.cpython-310.pyc b/OneCprogsite/programmer/__pycache__/admin.cpython-310.pyc index e52d92c..38271a2 100644 Binary files a/OneCprogsite/programmer/__pycache__/admin.cpython-310.pyc and b/OneCprogsite/programmer/__pycache__/admin.cpython-310.pyc differ diff --git a/OneCprogsite/programmer/__pycache__/models.cpython-310.pyc b/OneCprogsite/programmer/__pycache__/models.cpython-310.pyc index e776b38..d708069 100644 Binary files a/OneCprogsite/programmer/__pycache__/models.cpython-310.pyc and b/OneCprogsite/programmer/__pycache__/models.cpython-310.pyc differ diff --git a/OneCprogsite/programmer/admin.py b/OneCprogsite/programmer/admin.py index 18a9935..5962719 100644 --- a/OneCprogsite/programmer/admin.py +++ b/OneCprogsite/programmer/admin.py @@ -49,6 +49,18 @@ class CallbackAdmin(admin.ModelAdmin): search_fields = ('name', 'phone', 'email') readonly_fields = ('time_create',) actions = ['mark_as_read', 'mark_as_unread', 'mark_as_processed'] + actions = ['mark_as_read', 'mark_as_unread', 'mark_as_processed', 'resend_notification'] + + def resend_notification(self, request, queryset): + from .utils.email_notifications import send_callback_notification + count = 0 + for callback in queryset: + success = send_callback_notification(callback) + if success: + count += 1 + self.message_user(request, f'Уведомления отправлены для {count} заявок') + + resend_notification.short_description = "Переотправить email уведомления" def new_badge(self, obj): if not obj.is_read: diff --git a/OneCprogsite/programmer/management/commands/__pycache__/test_email.cpython-310.pyc b/OneCprogsite/programmer/management/commands/__pycache__/test_email.cpython-310.pyc new file mode 100644 index 0000000..20b9687 Binary files /dev/null and b/OneCprogsite/programmer/management/commands/__pycache__/test_email.cpython-310.pyc differ diff --git a/OneCprogsite/programmer/management/commands/send_daily_summary.py b/OneCprogsite/programmer/management/commands/send_daily_summary.py new file mode 100644 index 0000000..fadba37 --- /dev/null +++ b/OneCprogsite/programmer/management/commands/send_daily_summary.py @@ -0,0 +1,33 @@ +# programmer/management/commands/send_daily_summary.py +from django.core.management.base import BaseCommand +from django.utils import timezone +from programmer.utils.email_notifications import send_daily_summary + + +class Command(BaseCommand): + help = 'Отправляет ежедневную сводку по заявкам на email' + + def add_arguments(self, parser): + parser.add_argument( + '--test', + action='store_true', + help='Тестовая отправка (не учитывает реальные данные)', + ) + + def handle(self, *args, **options): + if options['test']: + self.stdout.write(self.style.WARNING('Тестовая отправка ежедневной сводки...')) + # Здесь можно добавить тестовые данные + else: + self.stdout.write('Отправка ежедневной сводки...') + + success = send_daily_summary() + + if success: + self.stdout.write( + self.style.SUCCESS(f'Ежедневная сводка отправлена успешно! Время: {timezone.now()}') + ) + else: + self.stdout.write( + self.style.WARNING('Ежедневная сводка не отправлена (нет данных или ошибка)') + ) \ No newline at end of file diff --git a/OneCprogsite/programmer/management/commands/test_email.py b/OneCprogsite/programmer/management/commands/test_email.py new file mode 100644 index 0000000..b2ccccf --- /dev/null +++ b/OneCprogsite/programmer/management/commands/test_email.py @@ -0,0 +1,29 @@ +# programmer/management/commands/test_email.py +from django.core.management.base import BaseCommand +from django.conf import settings +from programmer.utils.email_notifications import send_test_email + + +class Command(BaseCommand): + help = 'Test email configuration' + + def handle(self, *args, **options): + self.stdout.write("Testing email configuration...") + + # Проверяем настройки + self.stdout.write(f"EMAIL_HOST: {settings.EMAIL_HOST}") + self.stdout.write(f"EMAIL_PORT: {settings.EMAIL_PORT}") + self.stdout.write(f"EMAIL_HOST_USER: {settings.EMAIL_HOST_USER}") + self.stdout.write(f"ADMIN_EMAILS: {settings.ADMIN_EMAILS}") + + # Тестируем отправку + success = send_test_email() + + if success: + self.stdout.write( + self.style.SUCCESS('✅ Test email sent successfully!') + ) + else: + self.stdout.write( + self.style.ERROR('❌ Failed to send test email. Check your email settings.') + ) \ No newline at end of file diff --git a/OneCprogsite/programmer/migrations/0013_callbackrequest_notification_sent.py b/OneCprogsite/programmer/migrations/0013_callbackrequest_notification_sent.py new file mode 100644 index 0000000..7308578 --- /dev/null +++ b/OneCprogsite/programmer/migrations/0013_callbackrequest_notification_sent.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.7 on 2025-11-14 10:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('programmer', '0012_callbackrequest_is_read'), + ] + + operations = [ + migrations.AddField( + model_name='callbackrequest', + name='notification_sent', + field=models.BooleanField(default=False, verbose_name='Уведомление отправлено'), + ), + ] diff --git a/OneCprogsite/programmer/migrations/__pycache__/0013_callbackrequest_notification_sent.cpython-310.pyc b/OneCprogsite/programmer/migrations/__pycache__/0013_callbackrequest_notification_sent.cpython-310.pyc new file mode 100644 index 0000000..1bbd4b6 Binary files /dev/null and b/OneCprogsite/programmer/migrations/__pycache__/0013_callbackrequest_notification_sent.cpython-310.pyc differ diff --git a/OneCprogsite/programmer/models.py b/OneCprogsite/programmer/models.py index 4b08fa7..4f3165e 100644 --- a/OneCprogsite/programmer/models.py +++ b/OneCprogsite/programmer/models.py @@ -4,6 +4,7 @@ from django.utils import timezone from django.db.models.signals import post_save, post_delete from django.dispatch import receiver from django.core.cache import cache +from .utils.email_notifications import send_callback_notification class Recall(models.Model): @@ -95,6 +96,7 @@ class CallbackRequest(models.Model): time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания') is_processed = models.BooleanField(default=False, verbose_name='Обработано') is_read = models.BooleanField(default=False, verbose_name='Прочитано') + notification_sent = models.BooleanField(default=False, verbose_name='Уведомление отправлено') def __str__(self): return f"{self.name} - {self.phone}" @@ -105,6 +107,18 @@ class CallbackRequest(models.Model): ordering = ['-time_create'] +# Сигнал для отправки уведомления при создании заявки +@receiver(post_save, sender=CallbackRequest) +def send_callback_email_notification(sender, instance, created, **kwargs): + if created and not instance.notification_sent: + # Отправляем email уведомление + success = send_callback_notification(instance) + if success: + instance.notification_sent = True + # Сохраняем без повторного вызова сигнала + sender.objects.filter(pk=instance.pk).update(notification_sent=True) + + class PageView(models.Model): url = models.CharField(max_length=500) timestamp = models.DateTimeField(default=timezone.now) diff --git a/OneCprogsite/programmer/templates/emails/callback_notification.html b/OneCprogsite/programmer/templates/emails/callback_notification.html new file mode 100644 index 0000000..4e7cdd8 --- /dev/null +++ b/OneCprogsite/programmer/templates/emails/callback_notification.html @@ -0,0 +1,57 @@ + + + + + + + + +
+
+

🚨 Новая заявка на сайте

+

Требуется ваше внимание!

+
+ +
+
+ ⚠️ Срочно! Пользователь оставил заявку на обратный звонок. +
+ +
+

📋 Информация о заявке:

+

👤 Имя: {{ callback.name }}

+

📞 Телефон: {{ callback.phone }}

+ {% if callback.email %} +

📧 Email: {{ callback.email }}

+ {% endif %} + {% if callback.question %} +

❓ Вопрос:
{{ callback.question }}

+ {% endif %} +

🕒 Время отправки: {{ callback.time_create|date:"d.m.Y H:i" }}

+
+ +
+ + 📋 Перейти к заявкам в админке + +
+ +

Не забудьте отметить заявку как обработанную после связи с клиентом!

+
+ + +
+ + \ No newline at end of file diff --git a/OneCprogsite/programmer/templates/emails/daily_summary.html b/OneCprogsite/programmer/templates/emails/daily_summary.html new file mode 100644 index 0000000..7886975 --- /dev/null +++ b/OneCprogsite/programmer/templates/emails/daily_summary.html @@ -0,0 +1,60 @@ + + + + + + + + +
+
+

📊 Ежедневная сводка

+

Статистика заявок за {{ yesterday|date:"d.m.Y" }}

+
+ +
+
+
+

📅 Вчерашние заявки

+

{{ yesterday_callbacks }}

+
+ +
+

⏳ Ожидают обработки

+

{{ unprocessed_callbacks }}

+
+
+ + {% if unprocessed_callbacks > 0 %} +
+ ⚠️ Внимание! У вас есть {{ unprocessed_callbacks }} необработанных заявок. +
+ {% endif %} + +
+ + 📋 Управление заявками + +
+ +

Не забудьте обработать все pending заявки!

+
+ + +
+ + \ No newline at end of file diff --git a/OneCprogsite/programmer/utils/__pycache__/email_notifications.cpython-310.pyc b/OneCprogsite/programmer/utils/__pycache__/email_notifications.cpython-310.pyc new file mode 100644 index 0000000..12f89e7 Binary files /dev/null and b/OneCprogsite/programmer/utils/__pycache__/email_notifications.cpython-310.pyc differ diff --git a/OneCprogsite/programmer/utils/email_notifications.py b/OneCprogsite/programmer/utils/email_notifications.py new file mode 100644 index 0000000..fa902a8 --- /dev/null +++ b/OneCprogsite/programmer/utils/email_notifications.py @@ -0,0 +1,65 @@ +# programmer/utils/email_notifications.py +from django.core.mail import send_mail, EmailMultiAlternatives +from django.template.loader import render_to_string +from django.conf import settings +from django.utils.html import strip_tags +import logging + +logger = logging.getLogger(__name__) + + +def send_callback_notification(callback_request): + """ + Отправляет уведомление о новой заявке на обратный звонок + """ + try: + subject = f'🚨 Новая заявка на обратный звонок от {callback_request.name}' + + # HTML версия письма + html_message = render_to_string('emails/callback_notification.html', { + 'callback': callback_request, + 'site_url': settings.ALLOWED_HOSTS[0] if settings.ALLOWED_HOSTS else 'localhost', + }) + + # Текстовая версия письма + plain_message = strip_tags(html_message) + + # Проверяем настройки email + if not all([settings.EMAIL_HOST_USER, settings.EMAIL_HOST_PASSWORD]): + logger.error("Email settings are not configured properly") + return False + + # Отправляем email + send_mail( + subject=subject, + message=plain_message, + from_email=settings.DEFAULT_FROM_EMAIL, + recipient_list=settings.ADMIN_EMAILS, + html_message=html_message, + fail_silently=False, + ) + + logger.info(f"Email notification sent successfully for callback #{callback_request.id}") + return True + + except Exception as e: + logger.error(f"Error sending email notification: {e}") + return False + + +def send_test_email(): + """ + Функция для тестирования отправки email + """ + try: + send_mail( + subject='📧 Test Email from Django', + message='This is a test email from your Django application.', + from_email=settings.DEFAULT_FROM_EMAIL, + recipient_list=settings.ADMIN_EMAILS, + fail_silently=False, + ) + return True + except Exception as e: + logger.error(f"Test email failed: {e}") + return False