183 lines
8.2 KiB
Python
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)."))
|