loko/streetup/notifications/management/commands/send_unread_notification_reminders.py

183 lines
8.2 KiB
Python

import logging
import re
from datetime import timedelta
from django.conf import settings
from django.core.mail import EmailMultiAlternatives
from django.core.management.base import BaseCommand
from django.utils import timezone, translation
from django.utils.translation import gettext as _
from common.models import UserConfig
from notifications.models import NotificationRecipient
from notifications.views import _translate_notification_data
logger = logging.getLogger(__name__)
class Command(BaseCommand):
help = "Envoie un e-mail de rappel aux utilisateurs ayant des notifications non lues depuis un certain délai."
def add_arguments(self, parser):
parser.add_argument(
'--user',
type=str,
help="Username spécifique à cibler pour le test.",
)
parser.add_argument(
'--dry-run',
action='store_true',
help="Simule l'envoi sans envoyer d'e-mails réels ni mettre à jour la base de données.",
)
def handle(self, *args, **options):
target_username = options.get('user')
dry_run = options.get('dry_run', False) or options.get('dry-run', False)
now = timezone.now()
configs = UserConfig.objects.filter(
notification_reminder_enabled=True,
user__is_active=True,
).select_related('user')
if target_username:
configs = configs.filter(user__username=target_username)
sent_count = 0
user_count = 0
for config in configs:
user = config.user
delay_hours = config.notification_reminder_delay_hours or 24
cutoff_time = now - timedelta(hours=delay_hours)
# Recueillir les notifications non lues > délai
recipients = (
NotificationRecipient.objects.filter(
user=user,
read_at__isnull=True,
archived_at__isnull=True,
reminder_sent_at__isnull=True,
notification__created_at__lte=cutoff_time,
)
.select_related('notification', 'notification__sender')
.order_by('-notification__created_at')
)
# Filtrer les notifications expirées
valid_recipients = [r for r in recipients if not r.notification.is_expired]
if not valid_recipients:
continue
# Déterminer les adresses e-mail de destination
email_override = getattr(settings, 'EMAIL_OVERRIDE', None)
if email_override:
recipient_emails = [email_override]
else:
emails = set()
raw_emails = config.notification_reminder_emails or user.email or getattr(config, 'generic_email', '')
if raw_emails:
for email in re.split(r'[;,]', str(raw_emails)):
e = email.strip()
if e:
emails.add(e)
recipient_emails = list(emails)
if not recipient_emails:
logger.warning("Aucune adresse e-mail trouvée pour l'utilisateur %s.", user.username)
continue
user_lang = getattr(config, 'preferred_language', 'fr') or 'fr'
with translation.override(user_lang):
# Construire l'e-mail dans la langue de l'utilisateur
count = len(valid_recipients)
subject = _("[StreetUp] Rappel : %(count)d notification(s) non lue(s)") % {'count': count}
# Host URL pour les liens
domain = (getattr(settings, 'ROOT_URL', '') or getattr(settings, 'SITE_URL', '') or 'http://localhost:8000').rstrip('/')
user_display_name = user.get_full_name() or user.username
greeting = _("Bonjour %(name)s,") % {'name': user_display_name}
intro_text = _("Vous avez %(count)d notification(s) non lue(s) reçue(s) il y a plus de %(delay)d heure(s) :") % {
'count': count,
'delay': delay_hours,
}
lines_text = [
f"{greeting}\n",
f"{intro_text}\n",
]
html_items = []
for r in valid_recipients:
n = r.notification
_translate_notification_data(n)
created_str = n.created_at.strftime('%d/%m/%Y %H:%M')
sender_str = n.sender.get_full_name() if n.sender else _("Système")
if n.action_url:
action_path = n.action_url if n.action_url.startswith('/') else f"/{n.action_url}"
link = f"{domain}{action_path}"
else:
link = f"{domain}/notifications/"
type_label = n.get_notification_type_display()
lines_text.append(f"- [{type_label}] {n.title} ({_('reçue le')} {created_str} {_('par')} {sender_str})")
lines_text.append(f" {_('Lien')}: {link}\n")
html_items.append(
f"<li>"
f"<strong>{n.title}</strong> ({type_label})<br>"
f"<small style='color:#6c757d;'>{_('Reçue le')} {created_str} • {sender_str}</small><br>"
f"<a href='{link}' style='color:#2f3e8b; font-weight:bold;'>{_('Consulter')}</a>"
f"</li>"
)
lines_text.append(f"\n{_('Accédez à votre boîte de réception :')} {domain}/notifications/")
text_body = "\n".join(lines_text)
html_title = _("Notifications non lues")
html_intro = _("Vous avez <strong>%(count)d</strong> notification(s) non lue(s) qui attendent votre attention :") % {'count': count}
btn_label = _("Voir mes notifications")
html_body = f"""
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; color: #333;">
<h2 style="color: #2f3e8b;">{html_title}</h2>
<p>{greeting}</p>
<p>{html_intro}</p>
<ul style="line-height: 1.6; padding-left: 20px;">
{"".join(html_items)}
</ul>
<p style="margin-top: 20px;">
<a href="{domain}/notifications/" style="background-color: #2f3e8b; color: #fff; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block;">
{btn_label}
</a>
</p>
</div>
"""
if dry_run:
self.stdout.write(f"[DRY-RUN] E-mail simulé [{user_lang}] pour {user.username} ({recipient_emails}) - {count} notification(s).")
else:
try:
msg = EmailMultiAlternatives(
subject=subject,
body=text_body,
from_email=getattr(settings, 'DEFAULT_FROM_EMAIL', 'StreetUp no-reply <no-reply@streetup.be>'),
to=recipient_emails,
)
msg.attach_alternative(html_body, "text/html")
msg.send()
# Marquer le rappel envoyé
NotificationRecipient.objects.filter(pk__in=[r.pk for r in valid_recipients]).update(
reminder_sent_at=now
)
sent_count += 1
user_count += 1
self.stdout.write(self.style.SUCCESS(f"Rappel envoyé avec succès à {user.username} ({recipient_emails}) en [{user_lang}]."))
except Exception as exc:
logger.exception("Erreur d'envoi du rappel pour %s: %s", user.username, exc)
self.stderr.write(self.style.ERROR(f"Erreur d'envoi pour {user.username}: {exc}"))
self.stdout.write(self.style.SUCCESS(f"Traitement terminé : {sent_count} e-mail(s) envoyé(s) pour {user_count} utilisateur(s)."))