Change email function to use email address directly

This commit is contained in:
Daniel Egger 2023-08-25 11:48:50 +02:00
parent 31af4e933f
commit d83f660918
11 changed files with 179 additions and 93 deletions

Binary file not shown.

View File

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
import os
import sys
import django
sys.path.append("../server")
os.environ.setdefault("IT_APP_ENVIRONMENT", "local")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.base")
django.setup()
from vbv_lernwelt.notify.email.email_services import (
EmailTemplate,
send_email,
create_template_data_from_course_session_attendance_course,
)
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
def main():
print("start")
if __name__ == "__main__":
main()
csac = CourseSessionAttendanceCourse.objects.get(pk=1)
print(csac)
print(csac.trainer)
print(csac.due_date)
result = send_email(
to_emails="daniel.egger+sendgrid@gmail.com",
template=EmailTemplate.ATTENDANCE_COURSE_REMINDER,
template_data=create_template_data_from_course_session_attendance_course(csac),
template_language="de",
fail_silently=False,
)
print(result)

View File

@ -30,6 +30,7 @@ django-debug-toolbar # https://github.com/jazzband/django-debug-toolbar
django-extensions # https://github.com/django-extensions/django-extensions
django-coverage-plugin # https://github.com/nedbat/django_coverage_plugin
pytest-django # https://github.com/pytest-dev/pytest-django
freezegun # https://github.com/spulec/freezegun
# django-watchfiles custom PR
https://github.com/q0w/django-watchfiles/archive/issue-1.zip

View File

@ -218,6 +218,8 @@ flake8==6.1.0
# flake8-isort
flake8-isort==6.0.0
# via -r requirements-dev.in
freezegun==1.2.2
# via -r requirements-dev.in
gitdb==4.0.10
# via gitdb2
gitdb2==4.0.2
@ -409,6 +411,7 @@ python-dateutil==2.8.2
# -r requirements.in
# botocore
# faker
# freezegun
python-dotenv==1.0.0
# via
# environs

View File

@ -16,7 +16,8 @@ from vbv_lernwelt.core.models import User
from vbv_lernwelt.core.utils import find_first
from vbv_lernwelt.course.models import CourseCompletionStatus, CourseSession
from vbv_lernwelt.course.services import mark_course_completion
from vbv_lernwelt.notify.service import EmailTemplate, NotificationService
from vbv_lernwelt.notify.email.email_services import EmailTemplate
from vbv_lernwelt.notify.services import NotificationService
def update_assignment_completion(

View File

@ -6,7 +6,8 @@ from django.utils.translation import gettext_lazy as _
from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.models import CourseSessionUser
from vbv_lernwelt.notify.service import EmailTemplate, NotificationService
from vbv_lernwelt.notify.email.email_services import EmailTemplate
from vbv_lernwelt.notify.services import NotificationService
class FeedbackIntegerField(models.IntegerField):

View File

@ -0,0 +1,97 @@
from enum import Enum
import structlog
from django.conf import settings
from django.utils import timezone
from sendgrid import Mail, SendGridAPIClient
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
logger = structlog.get_logger(__name__)
DATETIME_FORMAT_SWISS_STR = "%d.%m.%Y %H:%M"
def format_swiss_datetime(dt: timezone.datetime) -> str:
return dt.astimezone(timezone.get_current_timezone()).strftime(
DATETIME_FORMAT_SWISS_STR
)
class EmailTemplate(Enum):
"""Enum for the different Sendgrid email templates."""
# VBV - Erinnerung Präsenzkurse
ATTENDANCE_COURSE_REMINDER = {
"de": "d-9af079f98f524d85ac6e4166de3480da",
"it": "d-ab78ddca8a7a46b8afe50aaba3efee81",
"fr": "d-f88d9912e5484e55a879571463e4a166",
}
# VBV - Geleitete Fallarbeit abgegeben
CASEWORK_SUBMITTED = {"de": "d-599f0b35ddcd4fac99314cdf8f5446a2"}
# VBV - Geleitete Fallarbeit bewertet
CASEWORK_EVALUATED = {"de": "d-8c57fa13116b47be8eec95dfaf2aa030"}
# VBV - Neues Feedback für Circle
NEW_FEEDBACK = {"de": "d-40fb94d5149949e7b8e7ddfcf0fcfdde"}
def send_email(
to_emails: str | list[str],
template: EmailTemplate,
template_data: dict,
template_language: str = "de",
fail_silently: bool = True,
) -> bool:
log = logger.bind(
recipient_emails=to_emails,
template=template.name,
template_data=template_data,
template_language=template_language,
)
try:
send_sendgrid_email(
to_emails=to_emails,
template=template,
template_data=template_data,
template_language=template_language,
)
log.info("Email sent successfully")
return True
except Exception as e:
log.error(
"Failed to send Email", exception=str(e), exc_info=True, stack_info=True
)
if not fail_silently:
raise e
return False
def send_sendgrid_email(
to_emails: str | list[str],
template: EmailTemplate,
template_data: dict,
template_language: str = "de",
) -> None:
message = Mail(
from_email="noreply@my.vbv-afa.ch",
to_emails=to_emails,
)
message.template_id = template.value.get(template_language, template.value["de"])
message.dynamic_template_data = template_data
SendGridAPIClient(settings.SENDGRID_API_KEY).send(message)
def create_template_data_from_course_session_attendance_course(
attendance_course: CourseSessionAttendanceCourse,
):
return {
"attendance_course": attendance_course.learning_content.title,
"location": attendance_course.location,
"trainer": attendance_course.trainer,
"start": format_swiss_datetime(attendance_course.due_date.start),
"end": format_swiss_datetime(attendance_course.due_date.end),
}

View File

@ -8,19 +8,18 @@ from django.utils.translation import gettext_lazy as _
from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.models import CourseSessionUser
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
from vbv_lernwelt.notify.service import EmailTemplate, NotificationService
from vbv_lernwelt.notify.email.email_services import (
create_template_data_from_course_session_attendance_course,
EmailTemplate,
)
from vbv_lernwelt.notify.services import NotificationService
logger = structlog.get_logger(__name__)
PRESENCE_COURSE_REMINDER_LEAD_TIME = timedelta(weeks=2)
DATETIME_FORMAT_STR = "%H:%M %d.%m.%Y"
def format_datetime(dt: timezone.datetime) -> str:
return dt.astimezone(timezone.get_current_timezone()).strftime(DATETIME_FORMAT_STR)
def send_notification(
def send_attendance_course_reminder_notification(
recipient: User, attendance_course: CourseSessionAttendanceCourse
):
NotificationService.send_information_notification(
@ -28,24 +27,20 @@ def send_notification(
verb=_("Erinnerung: Bald findet ein Präsenzkurs statt"),
target_url=attendance_course.learning_content.get_frontend_url(),
email_template=EmailTemplate.ATTENDANCE_COURSE_REMINDER,
template_data={
"attendance_course": attendance_course.learning_content.title,
"location": attendance_course.location,
"trainer": attendance_course.trainer,
"start": format_datetime(attendance_course.due_date.start),
"end": format_datetime(attendance_course.due_date.end),
},
template_data=create_template_data_from_course_session_attendance_course(
attendance_course=attendance_course
),
)
def check_attendance_course():
def attendance_course_reminder_notification_job():
"""Checks if an attendance course is coming up and sends a reminder to the participants"""
start = timezone.now()
end = timezone.now() + PRESENCE_COURSE_REMINDER_LEAD_TIME
logger.info(
"Querying for attendance courses in specified time range",
start_time=start.strftime(DATETIME_FORMAT_STR),
end_time=end.strftime(DATETIME_FORMAT_STR),
start_time=start,
end_time=end,
)
attendance_courses = CourseSessionAttendanceCourse.objects.filter(
due_date__start__lte=end,
@ -55,11 +50,11 @@ def check_attendance_course():
cs_id = attendance_course.course_session.id
csu = CourseSessionUser.objects.filter(course_session_id=cs_id)
for user in csu:
send_notification(user.user, attendance_course)
send_attendance_course_reminder_notification(user.user, attendance_course)
if not attendance_courses:
logger.info("No attendance courses found")
@click.command()
def command():
check_attendance_course()
attendance_course_reminder_notification_job()

View File

@ -1,60 +1,15 @@
from enum import Enum
from typing import Optional
import structlog
from notifications.signals import notify
from sendgrid import Mail, SendGridAPIClient
from storages.utils import setting
from vbv_lernwelt.core.models import User
from vbv_lernwelt.notify.email.email_services import EmailTemplate, send_email
from vbv_lernwelt.notify.models import Notification, NotificationType
logger = structlog.get_logger(__name__)
class EmailTemplate(Enum):
"""Enum for the different Sendgrid email templates."""
# VBV - Erinnerung Präsenzkurse
ATTENDANCE_COURSE_REMINDER = {
"de": "d-9af079f98f524d85ac6e4166de3480da",
"it": "d-ab78ddca8a7a46b8afe50aaba3efee81",
"fr": "d-f88d9912e5484e55a879571463e4a166",
}
# VBV - Geleitete Fallarbeit abgegeben
CASEWORK_SUBMITTED = {"de": "d-599f0b35ddcd4fac99314cdf8f5446a2"}
# VBV - Geleitete Fallarbeit bewertet
CASEWORK_EVALUATED = {"de": "d-8c57fa13116b47be8eec95dfaf2aa030"}
# VBV - Neues Feedback für Circle
NEW_FEEDBACK = {"de": "d-40fb94d5149949e7b8e7ddfcf0fcfdde"}
class EmailService:
"""Email service class implemented using the Sendgrid API"""
_sendgrid_client = SendGridAPIClient(setting("SENDGRID_API_KEY"))
@classmethod
def send_email(
cls,
recipient: User,
template: EmailTemplate,
template_data: dict,
) -> None:
message = Mail(
from_email="noreply@my.vbv-afa.ch",
to_emails=recipient.email,
)
message.template_id = template.value.get(
recipient.language, template.value["de"]
)
message.dynamic_template_data = template_data
cls._sendgrid_client.send(message)
class NotificationService:
@classmethod
def send_user_interaction_notification(
@ -153,7 +108,7 @@ class NotificationService:
template_name=email_template.name,
template_data=template_data,
):
log.warn("A duplicate notification was omitted from being sent")
log.info("A duplicate notification was omitted from being sent")
return
emailed = False
@ -200,26 +155,14 @@ class NotificationService:
def _send_email(
recipient: User,
template: EmailTemplate,
template_data: dict | None,
template_data: dict,
) -> bool:
log = logger.bind(
recipient=recipient.username,
template=template.name,
return send_email(
to_emails=recipient.email,
template=template,
template_data=template_data,
template_language=recipient.language,
)
try:
EmailService.send_email(
recipient=recipient,
template=template,
template_data=template_data,
)
log.info("Email sent successfully")
return True
except Exception as e:
log.error(
"Failed to send Email", exception=str(e), exc_info=True, stack_info=True
)
return False
@staticmethod
def _is_duplicate_notification(

View File

@ -6,6 +6,7 @@ from django.contrib.auth.models import User
from django.test import TestCase
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from freezegun import freeze_time
from vbv_lernwelt.core.constants import TEST_COURSE_SESSION_BERN_ID
from vbv_lernwelt.core.create_default_users import create_default_users
@ -13,10 +14,10 @@ from vbv_lernwelt.course.creators.test_course import create_test_course
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
from vbv_lernwelt.learnpath.models import LearningContentAttendanceCourse
from vbv_lernwelt.notify.email.email_services import EmailTemplate
from vbv_lernwelt.notify.management.commands.send_attendance_course_reminders import (
check_attendance_course,
attendance_course_reminder_notification_job,
)
from vbv_lernwelt.notify.service import EmailTemplate
@dataclass
@ -36,7 +37,7 @@ def on_send_notification(**kwargs) -> None:
@patch(
"vbv_lernwelt.notify.service.NotificationService.send_information_notification",
"vbv_lernwelt.notify.services.NotificationService.send_information_notification",
on_send_notification,
)
class TestAttendanceCourseReminders(TestCase):
@ -75,6 +76,9 @@ class TestAttendanceCourseReminders(TestCase):
location="Handelsschule BV Bern, Zimmer 122",
trainer="Thomas Berger",
)
@freeze_time("2023-08-25 13:02:01")
def test_happy_day(self):
in_two_weeks = datetime.now() + timedelta(weeks=2, days=1)
self.csac_future.due_date.start = timezone.make_aware(
in_two_weeks.replace(hour=5, minute=20, second=0, microsecond=0)
@ -84,8 +88,7 @@ class TestAttendanceCourseReminders(TestCase):
)
self.csac_future.due_date.save()
def test_happy_day(self):
check_attendance_course()
attendance_course_reminder_notification_job()
self.assertEquals(3, len(sent_notifications))
recipients = CourseSessionUser.objects.filter(
course_session_id=self.csac.course_session.id
@ -94,6 +97,7 @@ class TestAttendanceCourseReminders(TestCase):
set(map(lambda n: n.recipient, sent_notifications)),
set(map(lambda csu: csu.user, recipients)),
)
for notification in sent_notifications:
self.assertEquals(
_("Erinnerung: Bald findet ein Präsenzkurs statt"),
@ -116,10 +120,10 @@ class TestAttendanceCourseReminders(TestCase):
notification.template_data["trainer"],
)
self.assertEquals(
self.csac.due_date.start.strftime("%H:%M %d.%m.%Y"),
self.csac.due_date.start.strftime("%d.%m.%Y %H:%M"),
notification.template_data["start"],
)
self.assertEquals(
self.csac.due_date.end.strftime("%H:%M %d.%m.%Y"),
self.csac.due_date.end.strftime("%d.%m.%Y %H:%M"),
notification.template_data["end"],
)

View File

@ -4,8 +4,9 @@ from django.test import TestCase
from vbv_lernwelt.core.models import User
from vbv_lernwelt.core.tests.factories import UserFactory
from vbv_lernwelt.notify.email.email_services import EmailTemplate
from vbv_lernwelt.notify.models import Notification, NotificationType
from vbv_lernwelt.notify.service import EmailTemplate, NotificationService
from vbv_lernwelt.notify.services import NotificationService
class TestNotificationService(TestCase):