Compare commits
No commits in common. "45ccce5b045162f73688c79eec5da55362c8efc8" and "b3b04fe14a785c7bbba800ab187e2fb206f2e4a3" have entirely different histories.
45ccce5b04
...
b3b04fe14a
@ -4,8 +4,6 @@ from django.db import models
|
|||||||
from django_ckeditor_5.widgets import CKEditor5Widget
|
from django_ckeditor_5.widgets import CKEditor5Widget
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from programmer.tasks import send_new_article_notification
|
|
||||||
from django.contrib import messages
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Category)
|
@admin.register(Category)
|
||||||
@ -27,7 +25,7 @@ class ArticleAdmin(admin.ModelAdmin):
|
|||||||
# filter_horizontal = ('tags',) # для удобного выбора тегов
|
# filter_horizontal = ('tags',) # для удобного выбора тегов
|
||||||
raw_id_fields = ('author',) # удобно при большом количестве пользователей
|
raw_id_fields = ('author',) # удобно при большом количестве пользователей
|
||||||
date_hierarchy = 'time_create'
|
date_hierarchy = 'time_create'
|
||||||
actions = ['make_published', 'make_unpublished', 'send_notification']
|
actions = ['make_published', 'make_unpublished']
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {
|
(None, {
|
||||||
@ -65,20 +63,6 @@ class ArticleAdmin(admin.ModelAdmin):
|
|||||||
queryset.update(is_published=False)
|
queryset.update(is_published=False)
|
||||||
make_unpublished.short_description = "Снять с публикации"
|
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):
|
def view_on_site(self, obj):
|
||||||
if obj.is_published:
|
if obj.is_published:
|
||||||
|
|||||||
@ -4,6 +4,3 @@ from django.apps import AppConfig
|
|||||||
class BlogConfig(AppConfig):
|
class BlogConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'blog'
|
name = 'blog'
|
||||||
|
|
||||||
def ready(self):
|
|
||||||
import blog.signals
|
|
||||||
@ -3,12 +3,10 @@ from django.dispatch import receiver
|
|||||||
from .models import Article
|
from .models import Article
|
||||||
from programmer.models import Profile
|
from programmer.models import Profile
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from programmer.tasks import send_new_article_notification
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Article)
|
@receiver(post_save, sender=Article)
|
||||||
def notify_about_new_article(sender, instance, created, **kwargs):
|
def notify_about_new_article(sender, instance, created, **kwargs):
|
||||||
if created and instance.is_published:
|
if created and instance.is_published:
|
||||||
profiles = Profile.objects.filter(notify_new_articles=True, user__is_active=True)
|
profiles = Profile.objects.filter(notify_new_articles=True, user__is_active=True)
|
||||||
emails = [p.user.email for p in profiles if p.user.email]
|
emails = [p.user.email for p in profiles if p.user.email]
|
||||||
send_new_article_notification.delay(instance.id)
|
# отправка письма (можно асинхронно)
|
||||||
@ -5,8 +5,6 @@ from django_ckeditor_5.widgets import CKEditor5Widget
|
|||||||
from .models import ProductCategory, Configuration, Product, Order
|
from .models import ProductCategory, Configuration, Product, Order
|
||||||
from programmer.utils.email_notifications import send_multiple_order_notifications, send_multiple_order_notifications_async
|
from programmer.utils.email_notifications import send_multiple_order_notifications, send_multiple_order_notifications_async
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from programmer.tasks import send_new_product_notification
|
|
||||||
from django.contrib import messages
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ProductCategory)
|
@admin.register(ProductCategory)
|
||||||
@ -37,7 +35,7 @@ class ProductAdmin(admin.ModelAdmin):
|
|||||||
prepopulated_fields = {'slug': ('title',)}
|
prepopulated_fields = {'slug': ('title',)}
|
||||||
filter_horizontal = ('configurations',)
|
filter_horizontal = ('configurations',)
|
||||||
date_hierarchy = 'time_create'
|
date_hierarchy = 'time_create'
|
||||||
actions = ['make_published', 'make_unpublished', 'send_notification']
|
actions = ['make_published', 'make_unpublished']
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {
|
(None, {
|
||||||
@ -74,20 +72,6 @@ class ProductAdmin(admin.ModelAdmin):
|
|||||||
queryset.update(is_published=False)
|
queryset.update(is_published=False)
|
||||||
make_unpublished.short_description = "Снять с публикации"
|
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)
|
@admin.register(Order)
|
||||||
class OrderAdmin(admin.ModelAdmin):
|
class OrderAdmin(admin.ModelAdmin):
|
||||||
|
|||||||
@ -4,6 +4,3 @@ from django.apps import AppConfig
|
|||||||
class ProductsConfig(AppConfig):
|
class ProductsConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'products'
|
name = 'products'
|
||||||
|
|
||||||
def ready(self):
|
|
||||||
import products.signals
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
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,15 +1,8 @@
|
|||||||
from background_task import background
|
from background_task import background
|
||||||
from django.conf import settings
|
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
|
import logging
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
@background(schedule=0)
|
@background(schedule=0)
|
||||||
def send_order_notification_background(order_id):
|
def send_order_notification_background(order_id):
|
||||||
@ -60,89 +53,4 @@ if shared_task is not None:
|
|||||||
from .utils.email_notifications import send_multiple_order_notifications
|
from .utils.email_notifications import send_multiple_order_notifications
|
||||||
from products.models import Order
|
from products.models import Order
|
||||||
orders = Order.objects.filter(id__in=order_ids)
|
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}")
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
<!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>
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
<!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