Добавил оповещение о новой заявке на почту
This commit is contained in:
parent
c3228a8baa
commit
fae45abcee
Binary file not shown.
@ -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(',')
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -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:
|
||||
|
||||
Binary file not shown.
@ -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('Ежедневная сводка не отправлена (нет данных или ошибка)')
|
||||
)
|
||||
29
OneCprogsite/programmer/management/commands/test_email.py
Normal file
29
OneCprogsite/programmer/management/commands/test_email.py
Normal file
@ -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.')
|
||||
)
|
||||
@ -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='Уведомление отправлено'),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
@ -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)
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
<!-- templates/emails/callback_notification.html -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
|
||||
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
||||
.header { background: linear-gradient(135deg, #FF6B00 0%, #0055A5 100%); color: white; padding: 20px; text-align: center; border-radius: 10px 10px 0 0; }
|
||||
.content { background: #f9f9f9; padding: 20px; border-radius: 0 0 10px 10px; }
|
||||
.alert { background: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; margin: 15px 0; }
|
||||
.info-box { background: white; padding: 15px; border-radius: 5px; margin: 10px 0; border-left: 4px solid #007bff; }
|
||||
.btn { display: inline-block; background: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; margin: 10px 0; }
|
||||
.footer { text-align: center; margin-top: 20px; padding-top: 20px; border-top: 1px solid #ddd; color: #666; font-size: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🚨 Новая заявка на сайте</h1>
|
||||
<p>Требуется ваше внимание!</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="alert">
|
||||
<strong>⚠️ Срочно!</strong> Пользователь оставил заявку на обратный звонок.
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<h3>📋 Информация о заявке:</h3>
|
||||
<p><strong>👤 Имя:</strong> {{ callback.name }}</p>
|
||||
<p><strong>📞 Телефон:</strong> {{ callback.phone }}</p>
|
||||
{% if callback.email %}
|
||||
<p><strong>📧 Email:</strong> {{ callback.email }}</p>
|
||||
{% endif %}
|
||||
{% if callback.question %}
|
||||
<p><strong>❓ Вопрос:</strong><br>{{ callback.question }}</p>
|
||||
{% endif %}
|
||||
<p><strong>🕒 Время отправки:</strong> {{ callback.time_create|date:"d.m.Y H:i" }}</p>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; margin: 20px 0;">
|
||||
<a href="http://{{ site_url }}/admin/programmer/callbackrequest/" class="btn">
|
||||
📋 Перейти к заявкам в админке
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p><em>Не забудьте отметить заявку как обработанную после связи с клиентом!</em></p>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>Это автоматическое уведомление от системы сайта.</p>
|
||||
<p>Если вы получили это письмо по ошибке, пожалуйста, свяжитесь с администратором.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
60
OneCprogsite/programmer/templates/emails/daily_summary.html
Normal file
60
OneCprogsite/programmer/templates/emails/daily_summary.html
Normal file
@ -0,0 +1,60 @@
|
||||
<!-- templates/emails/daily_summary.html -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
|
||||
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
||||
.header { background: linear-gradient(135deg, #28a745 0%, #20c997 100%); color: white; padding: 20px; text-align: center; border-radius: 10px 10px 0 0; }
|
||||
.content { background: #f9f9f9; padding: 20px; border-radius: 0 0 10px 10px; }
|
||||
.stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin: 20px 0; }
|
||||
.stat-card { background: white; padding: 15px; border-radius: 8px; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
||||
.stat-number { font-size: 2rem; font-weight: bold; margin: 0; }
|
||||
.alert { background: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; margin: 15px 0; }
|
||||
.btn { display: inline-block; background: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; margin: 10px 0; }
|
||||
.footer { text-align: center; margin-top: 20px; padding-top: 20px; border-top: 1px solid #ddd; color: #666; font-size: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>📊 Ежедневная сводка</h1>
|
||||
<p>Статистика заявок за {{ yesterday|date:"d.m.Y" }}</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<h3>📅 Вчерашние заявки</h3>
|
||||
<p class="stat-number" style="color: #28a745;">{{ yesterday_callbacks }}</p>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<h3>⏳ Ожидают обработки</h3>
|
||||
<p class="stat-number" style="color: #ffc107;">{{ unprocessed_callbacks }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if unprocessed_callbacks > 0 %}
|
||||
<div class="alert">
|
||||
<strong>⚠️ Внимание!</strong> У вас есть {{ unprocessed_callbacks }} необработанных заявок.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div style="text-align: center; margin: 20px 0;">
|
||||
<a href="http://{{ site_url }}/admin/programmer/callbackrequest/" class="btn">
|
||||
📋 Управление заявками
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p><em>Не забудьте обработать все pending заявки!</em></p>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>Ежедневная автоматическая сводка от системы сайта.</p>
|
||||
<p>Дата формирования: {{ today|date:"d.m.Y H:i" }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
65
OneCprogsite/programmer/utils/email_notifications.py
Normal file
65
OneCprogsite/programmer/utils/email_notifications.py
Normal file
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user