Compare commits
2 Commits
b3b04fe14a
...
45ccce5b04
| Author | SHA1 | Date | |
|---|---|---|---|
| 45ccce5b04 | |||
|
|
99140bf8ca |
@ -4,6 +4,8 @@ from django.db import models
|
||||
from django_ckeditor_5.widgets import CKEditor5Widget
|
||||
from django.utils.html import format_html
|
||||
from django.urls import reverse
|
||||
from programmer.tasks import send_new_article_notification
|
||||
from django.contrib import messages
|
||||
|
||||
|
||||
@admin.register(Category)
|
||||
@ -25,7 +27,7 @@ class ArticleAdmin(admin.ModelAdmin):
|
||||
# filter_horizontal = ('tags',) # для удобного выбора тегов
|
||||
raw_id_fields = ('author',) # удобно при большом количестве пользователей
|
||||
date_hierarchy = 'time_create'
|
||||
actions = ['make_published', 'make_unpublished']
|
||||
actions = ['make_published', 'make_unpublished', 'send_notification']
|
||||
|
||||
fieldsets = (
|
||||
(None, {
|
||||
@ -63,6 +65,20 @@ class ArticleAdmin(admin.ModelAdmin):
|
||||
queryset.update(is_published=False)
|
||||
make_unpublished.short_description = "Снять с публикации"
|
||||
|
||||
def send_notification(self, request, queryset):
|
||||
"""Отправить уведомления подписчикам для выбранных статей."""
|
||||
published = queryset.filter(is_published=True)
|
||||
count = published.count()
|
||||
if count == 0:
|
||||
self.message_user(request, "Нет опубликованных статей для отправки уведомлений.", level=messages.WARNING)
|
||||
return
|
||||
|
||||
for article in published:
|
||||
send_new_article_notification.delay(article.id)
|
||||
|
||||
self.message_user(request, f"Уведомления отправлены для {count} статей.")
|
||||
send_notification.short_description = "Отправить уведомление подписчикам о новых статьях"
|
||||
|
||||
# Добавляем кнопку предпросмотра для черновиков
|
||||
def view_on_site(self, obj):
|
||||
if obj.is_published:
|
||||
|
||||
@ -4,3 +4,6 @@ from django.apps import AppConfig
|
||||
class BlogConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'blog'
|
||||
|
||||
def ready(self):
|
||||
import blog.signals
|
||||
@ -3,10 +3,12 @@ from django.dispatch import receiver
|
||||
from .models import Article
|
||||
from programmer.models import Profile
|
||||
from django.core.mail import send_mail
|
||||
from programmer.tasks import send_new_article_notification
|
||||
|
||||
|
||||
@receiver(post_save, sender=Article)
|
||||
def notify_about_new_article(sender, instance, created, **kwargs):
|
||||
if created and instance.is_published:
|
||||
profiles = Profile.objects.filter(notify_new_articles=True, user__is_active=True)
|
||||
emails = [p.user.email for p in profiles if p.user.email]
|
||||
# отправка письма (можно асинхронно)
|
||||
send_new_article_notification.delay(instance.id)
|
||||
@ -5,6 +5,8 @@ from django_ckeditor_5.widgets import CKEditor5Widget
|
||||
from .models import ProductCategory, Configuration, Product, Order
|
||||
from programmer.utils.email_notifications import send_multiple_order_notifications, send_multiple_order_notifications_async
|
||||
from django.urls import reverse
|
||||
from programmer.tasks import send_new_product_notification
|
||||
from django.contrib import messages
|
||||
|
||||
|
||||
@admin.register(ProductCategory)
|
||||
@ -35,7 +37,7 @@ class ProductAdmin(admin.ModelAdmin):
|
||||
prepopulated_fields = {'slug': ('title',)}
|
||||
filter_horizontal = ('configurations',)
|
||||
date_hierarchy = 'time_create'
|
||||
actions = ['make_published', 'make_unpublished']
|
||||
actions = ['make_published', 'make_unpublished', 'send_notification']
|
||||
|
||||
fieldsets = (
|
||||
(None, {
|
||||
@ -72,6 +74,20 @@ class ProductAdmin(admin.ModelAdmin):
|
||||
queryset.update(is_published=False)
|
||||
make_unpublished.short_description = "Снять с публикации"
|
||||
|
||||
def send_notification(self, request, queryset):
|
||||
"""Отправить уведомления подписчикам для выбранных продуктов."""
|
||||
published = queryset.filter(is_published=True)
|
||||
count = published.count()
|
||||
if count == 0:
|
||||
self.message_user(request, "Нет опубликованных продуктов для отправки уведомлений.", level=messages.WARNING)
|
||||
return
|
||||
|
||||
for product in published:
|
||||
send_new_product_notification.delay(product.id)
|
||||
|
||||
self.message_user(request, f"Уведомления отправлены для {count} продуктов.")
|
||||
send_notification.short_description = "Отправить уведомление подписчикам о новых продуктах"
|
||||
|
||||
|
||||
@admin.register(Order)
|
||||
class OrderAdmin(admin.ModelAdmin):
|
||||
|
||||
@ -4,3 +4,6 @@ from django.apps import AppConfig
|
||||
class ProductsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'products'
|
||||
|
||||
def ready(self):
|
||||
import products.signals
|
||||
13
products/signals.py
Normal file
13
products/signals.py
Normal file
@ -0,0 +1,13 @@
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from .models import Product
|
||||
from programmer.tasks import send_new_product_notification
|
||||
|
||||
|
||||
@receiver(post_save, sender=Product)
|
||||
def notify_about_new_product(sender, instance, created, **kwargs):
|
||||
"""
|
||||
Отправляет уведомления подписчикам, когда создаётся и публикуется новый продукт.
|
||||
"""
|
||||
if created and instance.is_published:
|
||||
send_new_product_notification.delay(instance.id)
|
||||
@ -1,8 +1,15 @@
|
||||
from background_task import background
|
||||
from django.conf import settings
|
||||
from celery import shared_task
|
||||
from django.core.mail import send_mail
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
from django.contrib.auth import get_user_model
|
||||
import logging
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
User = get_user_model()
|
||||
|
||||
@background(schedule=0)
|
||||
def send_order_notification_background(order_id):
|
||||
@ -53,4 +60,89 @@ if shared_task is not None:
|
||||
from .utils.email_notifications import send_multiple_order_notifications
|
||||
from products.models import Order
|
||||
orders = Order.objects.filter(id__in=order_ids)
|
||||
return send_multiple_order_notifications(orders)
|
||||
return send_multiple_order_notifications(orders)
|
||||
|
||||
|
||||
@shared_task
|
||||
def send_new_article_notification(article_id):
|
||||
"""Отправка уведомлений подписчикам о новой статье."""
|
||||
from blog.models import Article
|
||||
from programmer.models import Profile
|
||||
|
||||
try:
|
||||
article = Article.objects.select_related('category', 'author').get(id=article_id, is_published=True)
|
||||
except Article.DoesNotExist:
|
||||
logger.error(f"Article #{article_id} not found or not published")
|
||||
return
|
||||
|
||||
# Получаем email-адреса пользователей, подписанных на новые статьи
|
||||
profiles = Profile.objects.filter(notify_new_articles=True, user__is_active=True)
|
||||
emails = [p.user.email for p in profiles if p.user.email]
|
||||
|
||||
if not emails:
|
||||
logger.info(f"No subscribers for new articles, skipping notification for article #{article_id}")
|
||||
return
|
||||
|
||||
subject = f'📰 Новая статья: {article.title}'
|
||||
context = {
|
||||
'article': article,
|
||||
'site_url': settings.ALLOWED_HOSTS[0] if settings.ALLOWED_HOSTS else 'localhost',
|
||||
'site_name': 'С.Н.А. Технологии',
|
||||
}
|
||||
html_message = render_to_string('emails/new_article_notification.html', context)
|
||||
plain_message = strip_tags(html_message)
|
||||
|
||||
try:
|
||||
send_mail(
|
||||
subject=subject,
|
||||
message=plain_message,
|
||||
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
recipient_list=emails,
|
||||
html_message=html_message,
|
||||
fail_silently=False,
|
||||
)
|
||||
logger.info(f"New article notification sent to {len(emails)} subscribers for article #{article_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send new article notification for article #{article_id}: {e}")
|
||||
|
||||
|
||||
@shared_task
|
||||
def send_new_product_notification(product_id):
|
||||
"""Отправка уведомлений подписчикам о новом продукте."""
|
||||
from products.models import Product
|
||||
from programmer.models import Profile
|
||||
|
||||
try:
|
||||
product = Product.objects.select_related('category').get(id=product_id, is_published=True)
|
||||
except Product.DoesNotExist:
|
||||
logger.error(f"Product #{product_id} not found or not published")
|
||||
return
|
||||
|
||||
profiles = Profile.objects.filter(notify_new_products=True, user__is_active=True)
|
||||
emails = [p.user.email for p in profiles if p.user.email]
|
||||
|
||||
if not emails:
|
||||
logger.info(f"No subscribers for new products, skipping notification for product #{product_id}")
|
||||
return
|
||||
|
||||
subject = f'🛒 Новый продукт: {product.title}'
|
||||
context = {
|
||||
'product': product,
|
||||
'site_url': settings.ALLOWED_HOSTS[0] if settings.ALLOWED_HOSTS else 'localhost',
|
||||
'site_name': 'С.Н.А. Технологии',
|
||||
}
|
||||
html_message = render_to_string('emails/new_product_notification.html', context)
|
||||
plain_message = strip_tags(html_message)
|
||||
|
||||
try:
|
||||
send_mail(
|
||||
subject=subject,
|
||||
message=plain_message,
|
||||
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
recipient_list=emails,
|
||||
html_message=html_message,
|
||||
fail_silently=False,
|
||||
)
|
||||
logger.info(f"New product notification sent to {len(emails)} subscribers for product #{product_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send new product notification for product #{product_id}: {e}")
|
||||
|
||||
16
programmer/templates/emails/new_article_notification.html
Normal file
16
programmer/templates/emails/new_article_notification.html
Normal file
@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<h2>Здравствуйте!</h2>
|
||||
<p>На сайте <strong>{{ site_name }}</strong> вышла новая статья:</p>
|
||||
<h3><a href="{{ site_url }}{{ article.get_absolute_url }}">{{ article.title }}</a></h3>
|
||||
<p>{{ article.content|truncatewords:50|safe }}</p>
|
||||
<p>Читать полностью: <a href="{{ site_url }}{{ article.get_absolute_url }}">{{ site_url }}{{ article.get_absolute_url }}</a></p>
|
||||
<hr>
|
||||
<p style="color: #666; font-size: 12px;">Вы получили это письмо, потому что подписаны на уведомления о новых статьях.</p>
|
||||
<p style="color: #666; font-size: 12px;">Отписаться можно в <a href="{{ site_url }}{% url 'profile' %}">личном кабинете</a>.</p>
|
||||
</body>
|
||||
</html>
|
||||
16
programmer/templates/emails/new_product_notification.html
Normal file
16
programmer/templates/emails/new_product_notification.html
Normal file
@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<h2>Здравствуйте!</h2>
|
||||
<p>На сайте <strong>{{ site_name }}</strong> появился новый продукт:</p>
|
||||
<h3><a href="{{ site_url }}{{ product.get_absolute_url }}">{{ product.title }}</a></h3>
|
||||
<p>{{ product.short_description|truncatewords:40 }}</p>
|
||||
<p>Подробнее: <a href="{{ site_url }}{{ product.get_absolute_url }}">{{ site_url }}{{ product.get_absolute_url }}</a></p>
|
||||
<hr>
|
||||
<p style="color: #666; font-size: 12px;">Вы получили это письмо, потому что подписаны на уведомления о новых продуктах.</p>
|
||||
<p style="color: #666; font-size: 12px;">Отписаться можно в <a href="{{ site_url }}{% url 'profile' %}">личном кабинете</a>.</p>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user