From 331d7d3d6328a025c0137044772f4d540d3aff37 Mon Sep 17 00:00:00 2001 From: Amirhesam Adibinia Date: Wed, 6 Dec 2023 02:37:03 +0330 Subject: [PATCH] feat(backend): add sending login credentials email (#64) * feat(backend): add sending login credentials email * feat(backend): add retry policy on sending emails --- backend/aaiss_backend/settings.py | 4 +- backend/backend_api/admin.py | 97 ++++++++----------- backend/backend_api/email.py | 16 ++- backend/backend_api/models.py | 1 - backend/templates/html/login_credentials.html | 17 ++++ 5 files changed, 77 insertions(+), 58 deletions(-) create mode 100644 backend/templates/html/login_credentials.html diff --git a/backend/aaiss_backend/settings.py b/backend/aaiss_backend/settings.py index d8e0152..5be44db 100644 --- a/backend/aaiss_backend/settings.py +++ b/backend/aaiss_backend/settings.py @@ -191,4 +191,6 @@ PAYWALL = env.str("PAYWALL", "ZIFY") #ZIFY -ZIFY_AUTH = env.str("ZIFY_AUTH", "") \ No newline at end of file +ZIFY_AUTH = env.str("ZIFY_AUTH", "") + +SKYROOM_URL = env.str("SKYROOM_URL", "https://www.skyroom.online/ch/aut_ceit_ssc/aaiss") \ No newline at end of file diff --git a/backend/backend_api/admin.py b/backend/backend_api/admin.py index eaead7a..7d1a527 100644 --- a/backend/backend_api/admin.py +++ b/backend/backend_api/admin.py @@ -1,6 +1,8 @@ from django import forms from django.contrib import admin +from django.template.loader import render_to_string +from aaiss_backend.settings import SKYROOM_URL from backend_api import models from backend_api.email import MailerThread from backend_api.models import Discount, Presentation, PresentationParticipation, WorkshopRegistration @@ -37,52 +39,6 @@ class UserAdmin(admin.ModelAdmin): list_display = ('account',) -class MailerForm(forms.ModelForm): - HTML_body = forms.CharField(widget=forms.Textarea) - - class Meta: - model = models.Mailer - fields = '__all__' - - -class MailerAdmin(admin.ModelAdmin): - model = models.Mailer - form = MailerForm - - def execute_mailer(self, request, obj): - for mailer in obj: - if mailer.target_mode == 1: - targets = models.User.objects.all() - mails = [] - for target in targets: - mails.append(target.account.email) - MailerThread(mailer.subject, mails, mailer.HTML_body).start() - elif mailer.target_mode == 2: - mails = [] - for workshop in mailer.workshop_selection.all(): - for user in models.User.objects.all(): - if workshop in user.registered_workshops.all(): - if __name__ == '__main__': - mails.append(user.account.email) - MailerThread(mailer.subject, mails, mailer.HTML_body).start() - elif mailer.target_mode == 3: - mails = [] - for user in models.User.objects.all(): - if user.registered_for_presentations: - mails.append(user.account.email) - MailerThread(mailer.subject, mails, mailer.HTML_body).start() - elif mailer.target_mode == 4: - targets = mailer.user_selection.all() - mails = [] - for target in targets: - mails.append(target.account.email) - MailerThread(mailer.subject, mails, mailer.HTML_body).start() - - execute_mailer.short_description = 'Send selected mails' - execute_mailer.allow_tags = True - actions = ['execute_mailer'] - - class DiscountAdmin(admin.ModelAdmin): list_display = ('__str__', 'is_active', 'discount_percent', 'capacity', 'remaining_capacity', 'expiration_date') readonly_fields = ('participants',) @@ -113,7 +69,7 @@ class Meta: class PresentationAdmin(admin.ModelAdmin): list_display = ('__str__', 'level', 'no_of_participants', 'year') readonly_fields = ('participants',) - actions = ['export_login_credentials'] + actions = ['export_login_credentials', 'send_registration_emails'] class Meta: model = Presentation @@ -123,18 +79,34 @@ class Meta: def export_login_credentials(self, request, obj): user_credentials: list[SkyroomCredentials] = [] for presentation in obj: - for presentation_registration in presentation.presentationparticipation_set.filter( + for registration in presentation.presentationparticipation_set.filter( status=PresentationParticipation.StatusChoices.PURCHASED): user_credentials.append( - SkyroomCredentials(presentation_registration.username, presentation_registration.password, - presentation_registration.user.account.email)) + SkyroomCredentials(registration.username, registration.password, + registration.user.account.email)) return convert_credentials_to_csv_response(user_credentials) + @admin.action(description='Send registration emails') + def send_registration_emails(self, request, obj): + for presentation in obj: + for registration in presentation.presentationparticipation_set.filter( + status=PresentationParticipation.StatusChoices.PURCHASED): + MailerThread(f"AAISS login credentials for {presentation.name}", + [registration.user.account.email], + render_to_string('login_credentials.html', + { + 'username': registration.username, + 'password': registration.password, + 'meeting_url': SKYROOM_URL, + 'meeting_type': 'presentation', + 'meeting_title': presentation.name, + })).start() + class WorkshopAdmin(admin.ModelAdmin): list_display = ('__str__', 'capacity', 'cost', 'has_project', 'level', 'no_of_participants', 'year') readonly_fields = ('participants',) - actions = ['export_login_credentials'] + actions = ['export_login_credentials', 'send_registration_emails'] class Meta: model = models.Workshop @@ -144,13 +116,29 @@ class Meta: def export_login_credentials(self, request, obj): user_credentials: list[SkyroomCredentials] = [] for workshop in obj: - for workshop_registration in workshop.workshopregistration_set.filter( + for registration in workshop.workshopregistration_set.filter( status=WorkshopRegistration.StatusChoices.PURCHASED): user_credentials.append( - SkyroomCredentials(workshop_registration.username, workshop_registration.password, - workshop_registration.user.account.email)) + SkyroomCredentials(registration.username, registration.password, + registration.user.account.email)) return convert_credentials_to_csv_response(user_credentials) + @admin.action(description='Send registration emails') + def send_registration_emails(self, request, obj): + for workshop in obj: + for registration in workshop.workshopregistration_set.filter( + status=WorkshopRegistration.StatusChoices.PURCHASED): + MailerThread(f"AAISS login credentials for {workshop.name}", + [registration.user.account.email], + render_to_string('login_credentials.html', + { + 'username': registration.username, + 'password': registration.password, + 'meeting_url': SKYROOM_URL, + 'meeting_type': 'workshop', + 'meeting_title': workshop.name, + })).start() + class MiscAdmin(admin.ModelAdmin): list_display = ('__str__', 'year') @@ -163,7 +151,6 @@ class Meta: admin.site.register(models.Teacher, TeacherAdmin) admin.site.register(models.Presenter, PresenterAdmin) admin.site.register(models.User, UserAdmin) -admin.site.register(models.Mailer, MailerAdmin) admin.site.register(models.Discount, DiscountAdmin) admin.site.register(models.Payment, PaymentAdmin) admin.site.register(models.WorkshopRegistration) diff --git a/backend/backend_api/email.py b/backend/backend_api/email.py index c177a46..06b7d10 100644 --- a/backend/backend_api/email.py +++ b/backend/backend_api/email.py @@ -1,4 +1,5 @@ import threading +from time import sleep from django.conf import settings from django.core.mail import EmailMessage @@ -8,6 +9,9 @@ class MailerThread(threading.Thread): + _MAXIMUM_RETRIES = 5 + _SLEEP_INTERVAL_SECONDS = 5 + def __init__(self, subject: str, targets: list[str], html_body: str): self.subject = subject self.targets = targets @@ -28,7 +32,17 @@ def run(self): ) email.content_subtype = "html" if ENABLE_SENDING_EMAIL: - email.send(fail_silently=False) + has_send = False + for i in range(self._MAXIMUM_RETRIES): + try: + email.send(fail_silently=True) + has_send = True + break + except Exception as e: + print(f"Exception raised while sending email: {e}") + sleep(self._SLEEP_INTERVAL_SECONDS) + if not has_send: + print(f"Failed to send email to {self.targets} after {self._MAXIMUM_RETRIES} retries") else: print(f"Sent the following email (change ENABLE_SENDING_EMAIL to True to actually send the email):") print(f"To: {email.bcc}\nSubject: {email.subject}\n\nBody: {email.body}\n\n") diff --git a/backend/backend_api/models.py b/backend/backend_api/models.py index 8a81238..d998ca4 100644 --- a/backend/backend_api/models.py +++ b/backend/backend_api/models.py @@ -235,7 +235,6 @@ def __str__(self): name = "" for presenter in self.presenters.all(): name += presenter.name + " " - print(name) return f"{name}: {self.name}" diff --git a/backend/templates/html/login_credentials.html b/backend/templates/html/login_credentials.html new file mode 100644 index 0000000..95a0b02 --- /dev/null +++ b/backend/templates/html/login_credentials.html @@ -0,0 +1,17 @@ + + + + + AAISS 2023 Login Credentials + + +
You've registered to {{ meeting_title }}, to enter the {{ meeting_type }} use the following credentials: +
+ +
+Meeting link +
+
Username: {{ username }}
+
Password: {{ password }}
+ + \ No newline at end of file