Change email function to use email address directly
This commit is contained in:
parent
31af4e933f
commit
d83f660918
Binary file not shown.
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
@ -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"],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Reference in New Issue