diff --git a/backend/account/models.py b/backend/account/models.py index c4fcc24..2d6fecc 100644 --- a/backend/account/models.py +++ b/backend/account/models.py @@ -63,6 +63,7 @@ class CustomUser(SoftDeleteModel, AbstractUser): email_verification_token = models.CharField(max_length=128, null=True, blank=True, db_index=True) email_verification_sent_at = models.DateTimeField(null=True, blank=True) + newsletter = models.BooleanField(default=True) #misc gdpr = models.BooleanField(default=False) diff --git a/backend/account/views.py b/backend/account/views.py index 6905591..16a0fef 100644 --- a/backend/account/views.py +++ b/backend/account/views.py @@ -405,4 +405,7 @@ class PasswordResetConfirmView(APIView): user.set_password(serializer.validated_data['password']) user.save() return Response({"detail": "Heslo bylo úspěšně změněno."}) - return Response(serializer.errors, status=400) \ No newline at end of file + return Response(serializer.errors, status=400) + + + diff --git a/backend/advertisement/tasks.py b/backend/advertisement/tasks.py index d9f6874..a62019d 100644 --- a/backend/advertisement/tasks.py +++ b/backend/advertisement/tasks.py @@ -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, } - ) \ No newline at end of file + ) + diff --git a/backend/advertisement/templates/email/advertisement/commerce/new_items_added_this_week.html b/backend/advertisement/templates/email/advertisement/commerce/new_items_added_this_week.html new file mode 100644 index 0000000..98eea1a --- /dev/null +++ b/backend/advertisement/templates/email/advertisement/commerce/new_items_added_this_week.html @@ -0,0 +1,83 @@ + + +

🆕 Nové produkty v obchodě

+

Týdenní přehled nově přidaných produktů

+ +
+

📊 Celkem nových produktů: {{ products_of_week|length }}

+

Přehled produktů přidaných za posledních 7 dní

+
+ +{% if products_of_week %} + {% for product in products_of_week %} +
+
{{ product.name }}
+ + {% if product.price %} +
+ {{ product.price|floatformat:0 }} {{ product.currency|default:"Kč" }} +
+ {% endif %} + + {% if product.short_description %} +
+ {{ product.short_description|truncatewords:20 }} +
+ {% endif %} + +
+ Přidáno: {{ product.created_at|date:"d.m.Y H:i" }} +
+
+ {% endfor %} +{% else %} +
+

🤷‍♂️ Žádné nové produkty

+

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.

+
+{% endif %} \ No newline at end of file diff --git a/backend/advertisement/urls.py b/backend/advertisement/urls.py index ad96ccc..6160f80 100644 --- a/backend/advertisement/urls.py +++ b/backend/advertisement/urls.py @@ -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"), ] diff --git a/backend/advertisement/views.py b/backend/advertisement/views.py index 2de85f7..ec0c1b5 100644 --- a/backend/advertisement/views.py +++ b/backend/advertisement/views.py @@ -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) diff --git a/backend/commerce/models.py b/backend/commerce/models.py index 8aad58b..169c437 100644 --- a/backend/commerce/models.py +++ b/backend/commerce/models.py @@ -94,6 +94,8 @@ class Product(models.Model): "Carrier", on_delete=models.SET_NULL, null=True, blank=True, related_name="default_for_products" ) + include_in_week_summary_email = models.BooleanField(default=False) + created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) diff --git a/backend/templates/email/email_verification.html b/backend/templates/email/email_verification.html new file mode 100644 index 0000000..475cd0d --- /dev/null +++ b/backend/templates/email/email_verification.html @@ -0,0 +1,78 @@ + + +
+

✉️ Ověření e-mailové adresy

+ +

+ Vítejte {{ user.first_name|default:user.username }}!

+ Děkujeme za registraci. Pro dokončení vytvoření účtu je nutné + ověřit vaši e-mailovou adresu kliknutím na tlačítko níže. +

+ + {{ cta_label }} + +
+ 🎉 Těšíme se na vás!
+ Po ověření e-mailu budete moci využívat všechny funkce naší platformy + a začít nakupovat nebo prodávat. +
+ +
+ ℹ️ Co dělat, když tlačítko nefunguje?
+ Zkopírujte a vložte následující odkaz do adresního řádku prohlížeče: +

+ {{ action_url }} +
+ +

+ Odkaz pro ověření je platný po omezenou dobu. + Pokud jste se neregistrovali na našich stránkách, ignorujte tento e-mail. +

+
\ No newline at end of file diff --git a/backend/templates/email/password_reset.html b/backend/templates/email/password_reset.html new file mode 100644 index 0000000..c99236d --- /dev/null +++ b/backend/templates/email/password_reset.html @@ -0,0 +1,75 @@ + + +
+

🔐 Obnova hesla

+ +

+ Dobrý den {{ user.first_name|default:user.username }},

+ Obdrželi jsme požadavek na obnovení hesla k vašemu účtu. + Klikněte na tlačítko níže pro vytvoření nového hesla. +

+ + {{ cta_label }} + +
+ ⚠️ Bezpečnostní upozornění:
+ Pokud jste o obnovu hesla nepožádali, ignorujte tento e-mail. + Váše heslo zůstane nezměněno. +
+ +

+ Odkaz pro obnovu hesla je platný pouze po omezenou dobu. + Pokud odkaz nefunguje, zkopírujte a vložte následující adresu do prohlížeče: +

+ +

+ {{ action_url }} +

+ +

+ Tento odkaz je určen pouze pro vás a nelze ho sdílet s ostatními. +

+
\ No newline at end of file diff --git a/backend/vontor_cz/settings.py b/backend/vontor_cz/settings.py index 233ffb2..54c266b 100644 --- a/backend/vontor_cz/settings.py +++ b/backend/vontor_cz/settings.py @@ -19,6 +19,7 @@ from django.db import OperationalError, connections from datetime import timedelta import json +from celery.schedules import crontab from dotenv import load_dotenv load_dotenv() # Pouze načte proměnné lokálně, pokud nejsou dostupné @@ -508,6 +509,14 @@ CELERY_TASK_SERIALIZER = os.getenv("CELERY_TASK_SERIALIZER", "json") CELERY_RESULT_SERIALIZER = os.getenv("CELERY_RESULT_SERIALIZER", "json") CELERY_TIMEZONE = os.getenv("CELERY_TIMEZONE", TIME_ZONE) CELERY_BEAT_SCHEDULER = os.getenv("CELERY_BEAT_SCHEDULER") + +# Celery Beat Schedule for periodic tasks +CELERY_BEAT_SCHEDULE = { + 'send-weekly-new-items-email': { + 'task': 'advertisement.tasks.send_newly_added_items_to_store_email_task_last_week', + 'schedule': crontab(hour=9, minute=0, day_of_week=1), # Every Monday at 9:00 AM + }, +} #-------------------------------------END CELERY 📅------------------------------------