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

163 lines
6.9 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
from django.utils.translation import gettext as _
from common.models import UserConfig
from notifications.models import NotificationRecipient
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)
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
# Construire l'e-mail
count = len(valid_recipients)
subject = f"[StreetUp] Rappel : {count} notification(s) non lue(s)"
# Host URL pour les liens
domain = getattr(settings, 'SITE_URL', 'http://localhost:8003')
lines_text = [
f"Bonjour {user.get_full_name() or user.username},\n",
f"Vous avez {count} notification(s) non lue(s) reçue(s) il y a plus de {delay_hours} heure(s) :\n",
]
html_items = []
for r in valid_recipients:
n = r.notification
created_str = n.created_at.strftime('%d/%m/%Y à %H:%M')
sender_str = n.sender.get_full_name() if n.sender else _("Système")
link = f"{domain}{n.action_url}" if n.action_url else f"{domain}/notifications/"
lines_text.append(f"- [{n.get_notification_type_display()}] {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> ({n.get_notification_type_display()})<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"\nAccédez à votre boîte de réception : {domain}/notifications/")
text_body = "\n".join(lines_text)
html_body = f"""
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; color: #333;">
<h2 style="color: #2f3e8b;">Notifications non lues</h2>
<p>Bonjour {user.get_full_name() or user.username},</p>
<p>Vous avez <strong>{count}</strong> notification(s) non lue(s) qui attendent votre attention :</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;">
Voir mes notifications
</a>
</p>
</div>
"""
if dry_run:
self.stdout.write(f"[DRY-RUN] E-mail simulé pour {user.username} ({recipient_emails}) - {count} notification(s).")
else:
try:
msg = EmailMultiAlternatives(
subject=subject,
body=text_body,
from_email=getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@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})."))
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)."))