Add weekly new products email and related features

Introduces a weekly summary email for newly added products, including a new email template and Celery periodic task. Adds 'include_in_week_summary_email' to Product and 'newsletter' to CustomUser. Provides an admin endpoint to manually trigger the weekly email, updates Celery Beat schedule, and adds email templates for verification and password reset.
This commit is contained in:
2026-01-22 00:22:21 +01:00
parent c0bd24ee5e
commit 963ba6b824
10 changed files with 308 additions and 7 deletions

View File

@@ -1,9 +1,13 @@
from venv import create
from account.tasks import send_email_with_context
from configuration.models import SiteConfiguration
from celery import shared_task
from celery.schedules import crontab
from commerce.models import Product
import datetime
@shared_task
def send_contact_me_email_task(client_email, message_content):
context = {
@@ -18,13 +22,28 @@ def send_contact_me_email_task(client_email, message_content):
)
def send_newly_added_items_to_store_email_task_last_week(item_id):
@shared_task
def send_newly_added_items_to_store_email_task_last_week():
last_week_date = datetime.datetime.now() - datetime.timedelta(days=7)
"""
__lte -> Less than or equal
__gte -> Greater than or equal
__lt -> Less than
__gt -> Greater than
"""
products_of_week = Product.objects.filter(
include_in_week_summary_email=True,
created_at__gte=last_week_date
)
send_email_with_context(
recipients=SiteConfiguration.get_solo().contact_email,
subject="Nový produkt přidán do obchodu",
template_path="email/new_item_added.html",
template_path="email/advertisement/commerce/new_items_added_this_week.html",
context={
"item": item,
"products_of_week": products_of_week,
}
)
)

View File

@@ -0,0 +1,83 @@
<style>
.summary {
background-color: #e3f2fd;
padding: 15px;
border-radius: 6px;
margin-bottom: 20px;
text-align: center;
font-family: Arial, sans-serif;
}
.product-item {
border-bottom: 1px solid #eee;
padding: 15px 0;
font-family: Arial, sans-serif;
}
.product-item:last-child {
border-bottom: none;
}
.product-name {
font-weight: bold;
font-size: 16px;
color: #333;
margin-bottom: 8px;
}
.product-price {
font-size: 14px;
color: #007bff;
font-weight: bold;
margin-bottom: 5px;
}
.product-description {
color: #666;
font-size: 13px;
margin-bottom: 8px;
}
.product-date {
color: #999;
font-size: 12px;
}
.no-products {
text-align: center;
color: #666;
font-style: italic;
padding: 30px;
font-family: Arial, sans-serif;
}
</style>
<h2 style="color: #007bff; margin: 0 0 20px 0;">🆕 Nové produkty v obchodě</h2>
<p style="margin: 0 0 20px 0;">Týdenní přehled nově přidaných produktů</p>
<div class="summary">
<h3 style="margin: 0 0 10px 0;">📊 Celkem nových produktů: {{ products_of_week|length }}</h3>
<p style="margin: 0;">Přehled produktů přidaných za posledních 7 dní</p>
</div>
{% if products_of_week %}
{% for product in products_of_week %}
<div class="product-item">
<div class="product-name">{{ product.name }}</div>
{% if product.price %}
<div class="product-price">
{{ product.price|floatformat:0 }} {{ product.currency|default:"Kč" }}
</div>
{% endif %}
{% if product.short_description %}
<div class="product-description">
{{ product.short_description|truncatewords:20 }}
</div>
{% endif %}
<div class="product-date">
Přidáno: {{ product.created_at|date:"d.m.Y H:i" }}
</div>
</div>
{% endfor %}
{% else %}
<div class="no-products">
<h3 style="margin: 0 0 15px 0;">🤷‍♂️ Žádné nové produkty</h3>
<p style="margin: 0;">Za posledních 7 dní nebyly přidány žádné nové produkty, které by měly být zahrnuty do týdenního přehledu.</p>
</div>
{% endif %}

View File

@@ -1,7 +1,7 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ContactMePublicView, ContactMeAdminViewSet
from .views import ContactMePublicView, ContactMeAdminViewSet, trigger_weekly_email
router = DefaultRouter()
router.register(r"contact-messages", ContactMeAdminViewSet, basename="contactme")
@@ -12,4 +12,5 @@ urlpatterns = [
# Admin endpoints
path("", include(router.urls)),
path("trigger-weekly-email/", trigger_weekly_email, name="trigger-weekly-email"),
]

View File

@@ -3,11 +3,12 @@ from rest_framework.response import Response
from rest_framework import status, viewsets
from rest_framework.permissions import AllowAny, IsAdminUser
from rest_framework.authentication import SessionAuthentication
from rest_framework.decorators import api_view, permission_classes
from drf_spectacular.utils import extend_schema, extend_schema_view
from .models import ContactMe
from .serializer import ContactMeSerializer
from .tasks import send_contact_me_email_task
from .tasks import send_contact_me_email_task, send_newly_added_items_to_store_email_task_last_week
@extend_schema(tags=["advertisement", "public"])
@@ -54,3 +55,32 @@ class ContactMeAdminViewSet(viewsets.ModelViewSet):
serializer_class = ContactMeSerializer
permission_classes = [IsAdminUser]
@extend_schema(
tags=["advertisement"],
summary="Manually trigger weekly new items email",
description="Triggers the weekly email task that sends a summary of newly added products from the last week. Only accessible by admin users.",
methods=["POST"]
)
@api_view(['POST'])
@permission_classes([IsAdminUser])
def trigger_weekly_email(request):
"""
Manually trigger the weekly new items email task.
Only accessible by admin users.
"""
try:
# Trigger the task asynchronously
task = send_newly_added_items_to_store_email_task_last_week.delay()
return Response({
'success': True,
'message': 'Weekly email task triggered successfully',
'task_id': task.id
}, status=status.HTTP_200_OK)
except Exception as e:
return Response({
'success': False,
'message': f'Failed to trigger weekly email task: {str(e)}'
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)