mirror of
				https://github.com/ae-utbm/sith.git
				synced 2025-11-04 02:53:06 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			122 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			122 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import logging
 | 
						|
import time
 | 
						|
from smtplib import SMTPException
 | 
						|
 | 
						|
from django.conf import settings
 | 
						|
from django.core.mail import send_mail
 | 
						|
from django.core.management.base import BaseCommand
 | 
						|
from django.db.models import Exists, OuterRef, Subquery
 | 
						|
from django.template.loader import render_to_string
 | 
						|
from django.utils.timezone import localdate, now
 | 
						|
from django.utils.translation import gettext as _
 | 
						|
 | 
						|
from core.models import User, UserQuerySet
 | 
						|
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",
 | 
						|
        )
 | 
						|
 | 
						|
    def handle(self, *args, **options):
 | 
						|
        users: list[User] = list(self._get_users())
 | 
						|
        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
 | 
						|
        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"])
 | 
						|
 | 
						|
        AccountDump.objects.bulk_create(dumps)
 | 
						|
        self.stdout.write("Finished !")
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def _get_users() -> UserQuerySet:
 | 
						|
        ongoing_dump_operation = AccountDump.objects.ongoing().filter(
 | 
						|
            customer__user=OuterRef("pk")
 | 
						|
        )
 | 
						|
        return (
 | 
						|
            User.objects.filter_inactive()
 | 
						|
            .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(
 | 
						|
            "counter/mails/account_dump_warning.jinja",
 | 
						|
            {
 | 
						|
                "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
 |