Introduce notifications app and integrate emails
Add a new notifications app (models, serializers, views, admin, tasks, consumers, routing, urls, tests) with Notification.notify for in-app websocket pushes and optional email delivery. Centralize email sending in notifications.tasks.send_email_with_context and re-export it from account.tasks for compatibility. Update account and commerce and advertisement tasks to use Notification.notify/_notify_order and the new email helper; adjust order/notification tasks to consolidate logic. Wire notifications into ASGI routing, settings and URL conf. Misc: handle OSError when importing weasyprint, add tmp/ to .gitignore, add local .claude PowerShell checks, add social.blog skeleton, and remove legacy ews-component test files.
This commit is contained in:
119
backend/notifications/tasks.py
Normal file
119
backend/notifications/tasks.py
Normal file
@@ -0,0 +1,119 @@
|
||||
from datetime import datetime
|
||||
|
||||
from celery import shared_task
|
||||
from celery.utils.log import get_task_logger
|
||||
|
||||
from django.core.mail import send_mail, EmailMultiAlternatives
|
||||
|
||||
logger = get_task_logger(__name__)
|
||||
|
||||
from account.models import CustomUser
|
||||
from django.conf import settings
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
|
||||
@shared_task
|
||||
def send_notification_email_task(recipient_email, subject, template_path, context=None, attachments=None):
|
||||
send_email_with_context(
|
||||
recipients=recipient_email,
|
||||
subject=subject,
|
||||
template_path=template_path,
|
||||
context=context or {},
|
||||
attachments=attachments,
|
||||
)
|
||||
|
||||
|
||||
|
||||
def send_email_with_context(recipients, subject, template_path=None, context=None, message: str | None = None, attachments=None):
|
||||
"""
|
||||
Send email using component-based template system.
|
||||
Uses base.html with header/footer components and includes the specified content template.
|
||||
"""
|
||||
if isinstance(recipients, str):
|
||||
recipients = [recipients]
|
||||
|
||||
if not recipients:
|
||||
logger.warning("No recipients provided for email. Skipping sending email.")
|
||||
return "No recipients provided for email."
|
||||
|
||||
ctx = dict(context or {})
|
||||
|
||||
# Add current_year to context if not present
|
||||
if "current_year" not in ctx:
|
||||
ctx["current_year"] = datetime.now().year
|
||||
|
||||
# Add AppConfig data for footer if not already present
|
||||
if "company_name" not in ctx:
|
||||
try:
|
||||
from configuration.models import AppConfig
|
||||
config = AppConfig.objects.first()
|
||||
if config:
|
||||
ctx["company_name"] = config.company_name
|
||||
ctx["company_address"] = config.company_address
|
||||
ctx["company_ico"] = config.company_ico
|
||||
ctx["company_dic"] = config.company_dic
|
||||
ctx["contact_email"] = config.contact_email
|
||||
ctx["contact_phone"] = config.contact_phone
|
||||
except Exception:
|
||||
pass # Gracefully skip if AppConfig not available
|
||||
|
||||
# Sanitize user if someone passes the model by mistake
|
||||
if "user" in ctx and not isinstance(ctx["user"], dict):
|
||||
try:
|
||||
ctx["user"] = _build_user_template_ctx(ctx["user"])
|
||||
except Exception:
|
||||
ctx["user"] = {}
|
||||
|
||||
# Render HTML using base template with content include
|
||||
html_message = None
|
||||
if template_path:
|
||||
ctx["content_template"] = template_path
|
||||
html_message = render_to_string("email/components/base.html", ctx)
|
||||
|
||||
try:
|
||||
email = EmailMultiAlternatives(
|
||||
subject=subject,
|
||||
body=message or "Tento e-mail vyžaduje HTML podporu.",
|
||||
from_email=None,
|
||||
to=recipients if len(recipients) == 1 else [],
|
||||
bcc=recipients if len(recipients) > 1 else [],
|
||||
)
|
||||
|
||||
if html_message:
|
||||
email.attach_alternative(html_message, "text/html")
|
||||
|
||||
if attachments:
|
||||
for filename, content, mimetype in attachments:
|
||||
email.attach(filename, content, mimetype)
|
||||
|
||||
email.send(fail_silently=False)
|
||||
|
||||
logger.info(f"Sent email to {recipients} with subject '{subject}'")
|
||||
|
||||
if settings.EMAIL_BACKEND == 'django.core.mail.backends.console.EmailBackend':
|
||||
logger.debug(f"\nEMAIL HTML:\n{html_message}\nKONEC OBSAHU")
|
||||
return "Successfully sent email."
|
||||
except Exception as e:
|
||||
logger.error(f"E-mail se neodeslal: {e}")
|
||||
raise # Re-raise so Celery marks the task as FAILED and can retry
|
||||
|
||||
|
||||
def _build_user_template_ctx(user: CustomUser) -> dict:
|
||||
"""
|
||||
Return a plain dict for templates instead of passing the DB model.
|
||||
Provides aliases to avoid template errors (firstname vs first_name).
|
||||
Adds a backward-compatible key 'get_full_name' for templates using user.get_full_name.
|
||||
"""
|
||||
first_name = getattr(user, "first_name", "") or ""
|
||||
last_name = getattr(user, "last_name", "") or ""
|
||||
full_name = f"{first_name} {last_name}".strip()
|
||||
return {
|
||||
"id": user.pk,
|
||||
"email": getattr(user, "email", "") or "",
|
||||
"first_name": first_name,
|
||||
"firstname": first_name, # alias for templates using `firstname`
|
||||
"last_name": last_name,
|
||||
"lastname": last_name, # alias for templates using `lastname`
|
||||
"full_name": full_name,
|
||||
"get_full_name": full_name, # compatibility for templates using method-style access
|
||||
}
|
||||
Reference in New Issue
Block a user