Sith/counter/management/commands/dump_warning_mail.py

122 lines
4.1 KiB
Python
Raw Permalink Normal View History

2024-10-06 20:24:20 +00:00
import logging
import time
2024-10-06 20:24:20 +00:00
from smtplib import SMTPException
from django.conf import settings
from django.core.mail import send_mail
from django.core.management.base import BaseCommand
2024-11-10 01:55:18 +00:00
from django.db.models import Exists, OuterRef, Subquery
2024-10-06 20:24:20 +00:00
from django.template.loader import render_to_string
from django.utils.timezone import localdate, now
from django.utils.translation import gettext as _
2024-11-10 01:55:18 +00:00
from core.models import User, UserQuerySet
2024-10-06 20:24:20 +00:00
from counter.models import AccountDump
from subscription.models import Subscription
class Command(BaseCommand):
"""Send mail to inactive users, warning them that their account is about to be dumped.
This command should be automated with a cron task.
"""
def __init__(self, *args, **kwargs):
self.logger = logging.getLogger("account_dump_mail")
self.logger.setLevel(logging.INFO)
super().__init__(*args, **kwargs)
def add_arguments(self, parser):
parser.add_argument(
"--dry-run",
action="store_true",
help="Do not send the mails, just display the number of users concerned",
)
parser.add_argument(
"-d",
"--delay",
type=float,
default=0,
help="Delay in seconds between each mail sent",
)
2024-10-06 20:24:20 +00:00
def handle(self, *args, **options):
users: list[User] = list(self._get_users())
2024-10-09 11:12:09 +00:00
self.stdout.write(f"{len(users)} users will be warned of their account dump")
if options["verbosity"] > 1:
self.stdout.write("Users concerned:\n")
self.stdout.write(
"\n".join(
f" - {user.get_display_name()} ({user.email}) : "
f"{user.customer.amount}"
for user in users
)
)
if options["dry_run"]:
return
2024-10-09 11:12:09 +00:00
dumps = []
for user in users:
is_success = self._send_mail(user)
dumps.append(
AccountDump(
customer_id=user.id,
warning_mail_sent_at=now(),
warning_mail_error=not is_success,
)
)
if options["delay"]:
# avoid spamming the mail server too much
time.sleep(options["delay"])
2024-10-09 11:12:09 +00:00
AccountDump.objects.bulk_create(dumps)
self.stdout.write("Finished !")
@staticmethod
2024-11-10 01:55:18 +00:00
def _get_users() -> UserQuerySet:
2024-10-06 20:24:20 +00:00
ongoing_dump_operation = AccountDump.objects.ongoing().filter(
customer__user=OuterRef("pk")
)
2024-10-09 11:12:09 +00:00
return (
User.objects.filter_inactive()
2024-10-06 20:24:20 +00:00
.filter(customer__amount__gt=0)
.exclude(Exists(ongoing_dump_operation))
.annotate(
last_subscription_date=Subquery(
Subscription.objects.filter(member=OuterRef("pk"))
.order_by("-subscription_end")
.values("subscription_end")[:1]
),
)
.select_related("customer")
)
def _send_mail(self, user: User) -> bool:
"""Send the warning email to the given user.
Returns:
True if the mail was successfully sent, else False
"""
message = render_to_string(
2024-11-10 01:55:18 +00:00
"counter/mails/account_dump_warning.jinja",
2024-10-06 20:24:20 +00:00
{
"balance": user.customer.amount,
"last_subscription_date": user.last_subscription_date,
"dump_date": localdate() + settings.SITH_ACCOUNT_DUMP_DELTA,
},
)
try:
# sending mails one by one is long and ineffective,
# but it makes easier to know which emails failed (and how).
# Also, there won't be that much mails sent (except on the first run)
send_mail(
_("Clearing of your AE account"),
message,
settings.DEFAULT_FROM_EMAIL,
[user.email],
)
self.logger.info(f"Mail successfully sent to {user.email}")
return True
except SMTPException as e:
self.logger.error(f"failed mail to {user.email} :\n{e}")
return False