From 3cd764ee7617b1e26c852a756ba13a8ee645b8e7 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Fri, 6 Oct 2023 15:33:40 +0200 Subject: [PATCH 1/6] wip: reminder for assignments --- compose/django/supercronic_crontab | 2 +- .../notify/email/email_services.py | 22 ++++- .../send_assigment_course_reminders.py | 92 +++++++++++++++++++ ...alter_notification_notification_trigger.py | 18 ++++ server/vbv_lernwelt/notify/models.py | 5 + server/vbv_lernwelt/notify/services.py | 64 ++++++++++++- .../test_send_assigment_course_reminders.py | 53 +++++++++++ 7 files changed, 252 insertions(+), 4 deletions(-) create mode 100644 server/vbv_lernwelt/notify/management/commands/send_assigment_course_reminders.py create mode 100644 server/vbv_lernwelt/notify/migrations/0005_alter_notification_notification_trigger.py create mode 100644 server/vbv_lernwelt/notify/tests/test_send_assigment_course_reminders.py diff --git a/compose/django/supercronic_crontab b/compose/django/supercronic_crontab index daeff803..ca3a264c 100644 --- a/compose/django/supercronic_crontab +++ b/compose/django/supercronic_crontab @@ -8,4 +8,4 @@ 0 */1 * * * /usr/local/bin/python /app/manage.py edoniq_import_results # every day at 19:30 -30 19 * * * /usr/local/bin/python /app/manage.py send_attendance_course_reminders +30 19 * * * /usr/local/bin/python /app/manage.py send_email_reminders --type=all diff --git a/server/vbv_lernwelt/notify/email/email_services.py b/server/vbv_lernwelt/notify/email/email_services.py index d62d3421..9df03411 100644 --- a/server/vbv_lernwelt/notify/email/email_services.py +++ b/server/vbv_lernwelt/notify/email/email_services.py @@ -6,7 +6,9 @@ from django.conf import settings from django.utils import timezone from sendgrid import Mail, SendGridAPIClient -from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse +from vbv_lernwelt.course_session.models import ( + CourseSessionAttendanceCourse, +) logger = structlog.get_logger(__name__) @@ -29,6 +31,24 @@ class EmailTemplate(Enum): "fr": "d-f88d9912e5484e55a879571463e4a166", } + # FIXME: Create Sendgrid templates + # Assumption: Edonqic, CaseWork and PrepAssignment share + # the same template for now. Once the templates are created + # update the template ids here and make sure the needed template + # data is passed to _send_notification! + ASSIGNMENT_REMINDER = { + "de": "", + "it": "", + "fr": "", + } + + # FIXME: Same as above... + CASEWORK_EXPERT_EVALUATION_REMINDER = { + "de": "", + "it": "", + "fr": "", + } + # VBV - Geleitete Fallarbeit abgegeben CASEWORK_SUBMITTED = {"de": "d-599f0b35ddcd4fac99314cdf8f5446a2"} diff --git a/server/vbv_lernwelt/notify/management/commands/send_assigment_course_reminders.py b/server/vbv_lernwelt/notify/management/commands/send_assigment_course_reminders.py new file mode 100644 index 00000000..cc79214b --- /dev/null +++ b/server/vbv_lernwelt/notify/management/commands/send_assigment_course_reminders.py @@ -0,0 +1,92 @@ +from datetime import timedelta + +import structlog +from django.utils import timezone + +from vbv_lernwelt.assignment.models import AssignmentType +from vbv_lernwelt.core.base import LoggedCommand +from vbv_lernwelt.course.models import CourseSessionUser +from vbv_lernwelt.course_session.models import ( + CourseSessionAssignment, + CourseSessionEdoniqTest, +) +from vbv_lernwelt.notify.services import NotificationService + +logger = structlog.get_logger(__name__) + +PRESENCE_COURSE_REMINDER_LEAD_TIME = timedelta(weeks=2) +ASSIGNMENT_REMINDER_LEAD_TIME = timedelta(days=2) + + +def assignment_reminder_members_notification_job(): + start = timezone.now() + end = timezone.now() + ASSIGNMENT_REMINDER_LEAD_TIME + sent = [] + + # member notifications (CASEWORK and PREP_ASSIGNMENT) + for assignment in CourseSessionAssignment.objects.filter( + submission_deadline__start__lte=end, + submission_deadline__start__gte=start, + learning_content__assignment_type__in=[ + AssignmentType.CASEWORK.value, + AssignmentType.PREP_ASSIGNMENT.value, + ], + ): + for member in CourseSessionUser.objects.filter( + course_session_id=assignment.course_session.id, + role=CourseSessionUser.Role.MEMBER, + ): + sent.append( + NotificationService.send_assignment_reminder_notification( + member.user, assignment + ) + ) + + # member notifications (EDONIQ) + for assignment in CourseSessionEdoniqTest.objects.filter( + submission_deadline__start__lte=end, + submission_deadline__start__gte=start, + ): + for member in CourseSessionUser.objects.filter( + course_session_id=assignment.course_session.id, + role=CourseSessionUser.Role.MEMBER, + ): + sent.append( + NotificationService.send_assignment_reminder_notification( + member.user, assignment + ) + ) + + # expert notifications (CASEWORK) + for assignment in CourseSessionAssignment.objects.filter( + evaluation_deadline__start__lte=end, + evaluation_deadline__start__gte=start, + learning_content__assignment_type__in=[ + AssignmentType.CASEWORK.value, + ], + ): + for expert in CourseSessionUser.objects.filter( + course_session_id=assignment.course_session.id, + role=CourseSessionUser.Role.EXPERT, + ): + sent.append( + NotificationService.send_casework_expert_evaluation_reminder( + expert.user, assignment + ) + ) + + return {"sent": sent} + + +class Command(LoggedCommand): + help = "Sends assigments course reminder notifications to participants (submission deadline) AND experts (evaluation deadline)" + + def handle(self, *args, **options): + results = assignment_reminder_members_notification_job() + self.job_log.json_data = results + self.job_log.save() + logger.info( + "Assignment course reminder notification job finished", + label="assignment_course_reminder_notification_job", + results=results, + ) diff --git a/server/vbv_lernwelt/notify/migrations/0005_alter_notification_notification_trigger.py b/server/vbv_lernwelt/notify/migrations/0005_alter_notification_notification_trigger.py new file mode 100644 index 00000000..9d0b9565 --- /dev/null +++ b/server/vbv_lernwelt/notify/migrations/0005_alter_notification_notification_trigger.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.20 on 2023-10-06 13:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notify', '0004_alter_notification_notification_trigger'), + ] + + operations = [ + migrations.AlterField( + model_name='notification', + name='notification_trigger', + field=models.CharField(choices=[('ATTENDANCE_COURSE_REMINDER', 'Attendance Course Reminder'), ('ASSIGNMENT_REMINDER', 'Assignment Reminder'), ('CASEWORK_EXPERT_EVALUATION_REMINDER', 'Casework Expert Evaluation Reminder'), ('CASEWORK_SUBMITTED', 'Casework Submitted'), ('CASEWORK_EVALUATED', 'Casework Evaluated'), ('NEW_FEEDBACK', 'New Feedback')], default='', max_length=255), + ), + ] diff --git a/server/vbv_lernwelt/notify/models.py b/server/vbv_lernwelt/notify/models.py index dfe6df74..4e87ffea 100644 --- a/server/vbv_lernwelt/notify/models.py +++ b/server/vbv_lernwelt/notify/models.py @@ -15,6 +15,11 @@ class NotificationTrigger(models.TextChoices): ATTENDANCE_COURSE_REMINDER = "ATTENDANCE_COURSE_REMINDER", _( "Attendance Course Reminder" ) + ASSIGNMENT_REMINDER = "ASSIGNMENT_REMINDER", _("Assignment Reminder") + CASEWORK_EXPERT_EVALUATION_REMINDER = ( + "CASEWORK_EXPERT_EVALUATION_REMINDER", + _("Casework Expert Evaluation Reminder"), + ) CASEWORK_SUBMITTED = "CASEWORK_SUBMITTED", _("Casework Submitted") CASEWORK_EVALUATED = "CASEWORK_EVALUATED", _("Casework Evaluated") NEW_FEEDBACK = "NEW_FEEDBACK", _("New Feedback") diff --git a/server/vbv_lernwelt/notify/services.py b/server/vbv_lernwelt/notify/services.py index fa2956b8..e30b993f 100644 --- a/server/vbv_lernwelt/notify/services.py +++ b/server/vbv_lernwelt/notify/services.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Union import structlog from django.db.models import Model @@ -21,7 +21,11 @@ from vbv_lernwelt.notify.models import ( if TYPE_CHECKING: from vbv_lernwelt.assignment.models import AssignmentCompletion - from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse + from vbv_lernwelt.course_session.models import ( + CourseSessionAttendanceCourse, + CourseSessionAssignment, + CourseSessionEdoniqTest, + ) from vbv_lernwelt.feedback.models import FeedbackResponse logger = structlog.get_logger(__name__) @@ -137,6 +141,62 @@ class NotificationService: ), ) + @classmethod + def send_assignment_reminder_notification( + cls, + recipient: User, + assignment: Union[CourseSessionAssignment, CourseSessionEdoniqTest], + ): + texts = { + "de": "Erinnerung: Bald ist ein Abgabetermin", + "fr": "Rappel: Une date limite approche", + "it": "Promemoria: Una scadenza si avvicina", + } + verb = texts.get(recipient.language, "de") + + return cls._send_notification( + recipient=recipient, + verb=verb, + notification_category=NotificationCategory.INFORMATION, + notification_trigger=NotificationTrigger.ASSIGNMENT_REMINDER, + target_url=assignment.learning_content.get_frontend_url(), + action_object=assignment, + course_session=assignment.course_session, + email_template=EmailTemplate.ASSIGNMENT_REMINDER, + template_data={ + # FIXME: Add template data as needed + # for the sendgrid email template + }, + ) + + @classmethod + def send_casework_expert_evaluation_reminder( + cls, + recipient: User, + assignment: CourseSessionAssignment, + ): + texts = { + "de": "Erinnerung: Bald ist ein Bewertungstermin", + "fr": "Rappel: Une date limite approche", + "it": "Promemoria: Una scadenza si avvicina", + } + verb = texts.get(recipient.language, "de") + + return cls._send_notification( + recipient=recipient, + verb=verb, + notification_category=NotificationCategory.INFORMATION, + notification_trigger=NotificationTrigger.CASEWORK_EXPERT_EVALUATION_REMINDER, + target_url=assignment.evaluation_deadline.url_expert, + action_object=assignment, + course_session=assignment.course_session, + email_template=EmailTemplate.CASEWORK_EXPERT_EVALUATION_REMINDER, + template_data={ + # FIXME: Add template data as needed + # for the sendgrid email template + }, + ) + @classmethod def _send_notification( cls, diff --git a/server/vbv_lernwelt/notify/tests/test_send_assigment_course_reminders.py b/server/vbv_lernwelt/notify/tests/test_send_assigment_course_reminders.py new file mode 100644 index 00000000..3e4f182b --- /dev/null +++ b/server/vbv_lernwelt/notify/tests/test_send_assigment_course_reminders.py @@ -0,0 +1,53 @@ +from datetime import datetime + +from django.test import TestCase +from django.utils import timezone +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 +from vbv_lernwelt.course.creators.test_course import create_test_course +from vbv_lernwelt.course.models import CourseSession +from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse +from vbv_lernwelt.learnpath.models import LearningContentAttendanceCourse + + +class TestAttendanceCourseReminders(TestCase): + def setUp(self): + create_default_users() + create_test_course(with_sessions=True) + CourseSessionAttendanceCourse.objects.all().delete() + + cs_bern = CourseSession.objects.get( + id=TEST_COURSE_SESSION_BERN_ID, + ) + self.csac = CourseSessionAttendanceCourse.objects.create( + course_session=cs_bern, + learning_content=LearningContentAttendanceCourse.objects.get( + slug="test-lehrgang-lp-circle-fahrzeug-lc-präsenzkurs-fahrzeug" + ), + location="Handelsschule KV Bern, Zimmer 123, Eigerstrasse 16, 3012 Bern", + trainer="Roland Grossenbacher", + ) + ref_date_time = timezone.make_aware(datetime(2023, 8, 29)) + self.csac.due_date.start = ref_date_time.replace( + hour=7, minute=30, second=0, microsecond=0 + ) + self.csac.due_date.end = ref_date_time.replace( + hour=16, minute=15, second=0, microsecond=0 + ) + self.csac.due_date.save() + + # Attendance course more than two weeks in the future + self.csac_future = CourseSessionAttendanceCourse.objects.create( + course_session=cs_bern, + learning_content=LearningContentAttendanceCourse.objects.get( + slug="test-lehrgang-lp-circle-fahrzeug-lc-präsenzkurs-fahrzeug" + ), + location="Handelsschule BV Bern, Zimmer 122", + trainer="Thomas Berger", + ) + + @freeze_time("2023-08-25 13:02:01") + def test_happy_day(self): + assert False, "TODO" From e44dc5e31d720abf7b170321e203e16ef5a61648 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Mon, 9 Oct 2023 14:10:04 +0200 Subject: [PATCH 2/6] feat: assignment reminder mails --- .../notify/email/email_services.py | 4 +- .../send_assigment_course_reminders.py | 9 +- ...alter_notification_notification_trigger.py | 23 +- server/vbv_lernwelt/notify/services.py | 2 +- .../test_send_assigment_course_reminders.py | 256 +++++++++++++++--- 5 files changed, 246 insertions(+), 48 deletions(-) diff --git a/server/vbv_lernwelt/notify/email/email_services.py b/server/vbv_lernwelt/notify/email/email_services.py index 9df03411..525133d5 100644 --- a/server/vbv_lernwelt/notify/email/email_services.py +++ b/server/vbv_lernwelt/notify/email/email_services.py @@ -6,9 +6,7 @@ from django.conf import settings from django.utils import timezone from sendgrid import Mail, SendGridAPIClient -from vbv_lernwelt.course_session.models import ( - CourseSessionAttendanceCourse, -) +from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse logger = structlog.get_logger(__name__) diff --git a/server/vbv_lernwelt/notify/management/commands/send_assigment_course_reminders.py b/server/vbv_lernwelt/notify/management/commands/send_assigment_course_reminders.py index cc79214b..5cb5c59f 100644 --- a/server/vbv_lernwelt/notify/management/commands/send_assigment_course_reminders.py +++ b/server/vbv_lernwelt/notify/management/commands/send_assigment_course_reminders.py @@ -14,7 +14,6 @@ from vbv_lernwelt.notify.services import NotificationService logger = structlog.get_logger(__name__) -PRESENCE_COURSE_REMINDER_LEAD_TIME = timedelta(weeks=2) ASSIGNMENT_REMINDER_LEAD_TIME = timedelta(days=2) @@ -42,10 +41,10 @@ def assignment_reminder_members_notification_job(): ) ) - # member notifications (EDONIQ) + # member notifications (EDONIQ_TEST) for assignment in CourseSessionEdoniqTest.objects.filter( - submission_deadline__start__lte=end, - submission_deadline__start__gte=start, + deadline__start__lte=end, + deadline__start__gte=start, ): for member in CourseSessionUser.objects.filter( course_session_id=assignment.course_session.id, @@ -79,7 +78,7 @@ def assignment_reminder_members_notification_job(): class Command(LoggedCommand): - help = "Sends assigments course reminder notifications to participants (submission deadline) AND experts (evaluation deadline)" + help = "Sends assignments course reminder notifications to participants and experts" def handle(self, *args, **options): results = assignment_reminder_members_notification_job() diff --git a/server/vbv_lernwelt/notify/migrations/0005_alter_notification_notification_trigger.py b/server/vbv_lernwelt/notify/migrations/0005_alter_notification_notification_trigger.py index 9d0b9565..807f65e8 100644 --- a/server/vbv_lernwelt/notify/migrations/0005_alter_notification_notification_trigger.py +++ b/server/vbv_lernwelt/notify/migrations/0005_alter_notification_notification_trigger.py @@ -4,15 +4,28 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('notify', '0004_alter_notification_notification_trigger'), + ("notify", "0004_alter_notification_notification_trigger"), ] operations = [ migrations.AlterField( - model_name='notification', - name='notification_trigger', - field=models.CharField(choices=[('ATTENDANCE_COURSE_REMINDER', 'Attendance Course Reminder'), ('ASSIGNMENT_REMINDER', 'Assignment Reminder'), ('CASEWORK_EXPERT_EVALUATION_REMINDER', 'Casework Expert Evaluation Reminder'), ('CASEWORK_SUBMITTED', 'Casework Submitted'), ('CASEWORK_EVALUATED', 'Casework Evaluated'), ('NEW_FEEDBACK', 'New Feedback')], default='', max_length=255), + model_name="notification", + name="notification_trigger", + field=models.CharField( + choices=[ + ("ATTENDANCE_COURSE_REMINDER", "Attendance Course Reminder"), + ("ASSIGNMENT_REMINDER", "Assignment Reminder"), + ( + "CASEWORK_EXPERT_EVALUATION_REMINDER", + "Casework Expert Evaluation Reminder", + ), + ("CASEWORK_SUBMITTED", "Casework Submitted"), + ("CASEWORK_EVALUATED", "Casework Evaluated"), + ("NEW_FEEDBACK", "New Feedback"), + ], + default="", + max_length=255, + ), ), ] diff --git a/server/vbv_lernwelt/notify/services.py b/server/vbv_lernwelt/notify/services.py index e30b993f..3239754e 100644 --- a/server/vbv_lernwelt/notify/services.py +++ b/server/vbv_lernwelt/notify/services.py @@ -22,8 +22,8 @@ from vbv_lernwelt.notify.models import ( if TYPE_CHECKING: from vbv_lernwelt.assignment.models import AssignmentCompletion from vbv_lernwelt.course_session.models import ( - CourseSessionAttendanceCourse, CourseSessionAssignment, + CourseSessionAttendanceCourse, CourseSessionEdoniqTest, ) from vbv_lernwelt.feedback.models import FeedbackResponse diff --git a/server/vbv_lernwelt/notify/tests/test_send_assigment_course_reminders.py b/server/vbv_lernwelt/notify/tests/test_send_assigment_course_reminders.py index 3e4f182b..c8fef4b1 100644 --- a/server/vbv_lernwelt/notify/tests/test_send_assigment_course_reminders.py +++ b/server/vbv_lernwelt/notify/tests/test_send_assigment_course_reminders.py @@ -1,53 +1,241 @@ from datetime import datetime +from typing import Dict from django.test import TestCase from django.utils import timezone from freezegun import freeze_time +from vbv_lernwelt.assignment.models import AssignmentType from vbv_lernwelt.core.constants import TEST_COURSE_SESSION_BERN_ID from vbv_lernwelt.core.create_default_users import create_default_users from vbv_lernwelt.course.creators.test_course import create_test_course from vbv_lernwelt.course.models import CourseSession -from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse -from vbv_lernwelt.learnpath.models import LearningContentAttendanceCourse +from vbv_lernwelt.course_session.models import ( + CourseSessionAssignment, + CourseSessionEdoniqTest, +) +from vbv_lernwelt.learnpath.models import ( + LearningContentAssignment, + LearningContentEdoniqTest, +) +from vbv_lernwelt.notify.management.commands.send_assigment_course_reminders import ( + assignment_reminder_members_notification_job, +) +from vbv_lernwelt.notify.models import Notification + +EXPECTED_MEMBER_VERB = "Erinnerung: Bald ist ein Abgabetermin" +EXPECTED_EXPERT_VERB = "Erinnerung: Bald ist ein Bewertungstermin" + +RECIPIENT_TRAINER = "test-trainer1@example.com" +RECIPIENT_STUDENTS = [ + "test-student1@example.com", + "test-student2@example.com", + "test-student3@example.com", +] -class TestAttendanceCourseReminders(TestCase): +ASSIGNMENT_TYPE_LEARNING_CONTENT_LOOKUP: Dict[AssignmentType, str] = { + AssignmentType.CONDITION_ACCEPTANCE: "test-lehrgang-lp-circle-fahrzeug-lc-redlichkeitserklärung", + AssignmentType.PREP_ASSIGNMENT: "test-lehrgang-lp-circle-fahrzeug-lc-fahrzeug-mein-erstes-auto", + AssignmentType.REFLECTION: "test-lehrgang-lp-circle-fahrzeug-lc-reflexion", + AssignmentType.CASEWORK: "test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice", +} + + +def create_assignment( + assignment_type: AssignmentType, + submission_deadline=None, + evaluation_deadline=None, +): + assignment = CourseSessionAssignment.objects.create( + course_session=CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID), + learning_content=LearningContentAssignment.objects.get( + slug=ASSIGNMENT_TYPE_LEARNING_CONTENT_LOOKUP[assignment_type] + ), + ) + + assert ( + AssignmentType(assignment.learning_content.assignment_type) == assignment_type + ) + + if submission_deadline: + assignment.submission_deadline.start = submission_deadline + assignment.submission_deadline.end = None + assignment.submission_deadline.save() + + if evaluation_deadline: + assignment.evaluation_deadline.start = evaluation_deadline + assignment.evaluation_deadline.end = None + assignment.evaluation_deadline.save() + + return assignment + + +def create_edoniq_test_assignment(deadline_start): + edoniq_test = CourseSessionEdoniqTest.objects.create( + course_session=CourseSession.objects.get( + id=TEST_COURSE_SESSION_BERN_ID, + ), + learning_content=LearningContentEdoniqTest.objects.get( + slug="test-lehrgang-lp-circle-fahrzeug-lc-wissens-und-verständnisfragen" + ), + ) + + edoniq_test.deadline.start = deadline_start + edoniq_test.deadline.end = None + edoniq_test.deadline.save() + + return edoniq_test + + +class TestAssignmentCourseRemindersTest(TestCase): def setUp(self): create_default_users() create_test_course(with_sessions=True) - CourseSessionAttendanceCourse.objects.all().delete() - cs_bern = CourseSession.objects.get( - id=TEST_COURSE_SESSION_BERN_ID, - ) - self.csac = CourseSessionAttendanceCourse.objects.create( - course_session=cs_bern, - learning_content=LearningContentAttendanceCourse.objects.get( - slug="test-lehrgang-lp-circle-fahrzeug-lc-präsenzkurs-fahrzeug" - ), - location="Handelsschule KV Bern, Zimmer 123, Eigerstrasse 16, 3012 Bern", - trainer="Roland Grossenbacher", - ) - ref_date_time = timezone.make_aware(datetime(2023, 8, 29)) - self.csac.due_date.start = ref_date_time.replace( - hour=7, minute=30, second=0, microsecond=0 - ) - self.csac.due_date.end = ref_date_time.replace( - hour=16, minute=15, second=0, microsecond=0 - ) - self.csac.due_date.save() + CourseSessionAssignment.objects.all().delete() + CourseSessionEdoniqTest.objects.all().delete() - # Attendance course more than two weeks in the future - self.csac_future = CourseSessionAttendanceCourse.objects.create( - course_session=cs_bern, - learning_content=LearningContentAttendanceCourse.objects.get( - slug="test-lehrgang-lp-circle-fahrzeug-lc-präsenzkurs-fahrzeug" - ), - location="Handelsschule BV Bern, Zimmer 122", - trainer="Thomas Berger", + Notification.objects.all().delete() + + def _assert_member_assignment_notifications( + self, action_object, expected_recipients + ): + for expected_recipient in expected_recipients: + notification = Notification.objects.get( + recipient__username=expected_recipient + ) + self.assertEquals(action_object, notification.action_object) + self.assertEquals("ASSIGNMENT_REMINDER", notification.notification_trigger) + self.assertEquals("INFORMATION", notification.notification_category) + self.assertEquals(EXPECTED_MEMBER_VERB, notification.verb) + + @freeze_time("2023-01-01") + def test_notification_edoniq(self): + # GIVEN + should_be_sent = create_edoniq_test_assignment( + deadline_start=timezone.make_aware(datetime(2023, 1, 2)) ) - @freeze_time("2023-08-25 13:02:01") - def test_happy_day(self): - assert False, "TODO" + # ...too early + create_edoniq_test_assignment( + deadline_start=timezone.make_aware(datetime(2023, 1, 4)) + ) + + # ...too late + create_edoniq_test_assignment( + deadline_start=timezone.make_aware(datetime(2022, 1, 1)) + ) + + # WHEN + assignment_reminder_members_notification_job() + + # THEN + self.assertEquals(3, len(Notification.objects.all())) + self._assert_member_assignment_notifications( + action_object=should_be_sent, + expected_recipients=RECIPIENT_STUDENTS, + ) + + with self.assertRaises(Notification.DoesNotExist): + Notification.objects.get(recipient__username=RECIPIENT_TRAINER) + + @freeze_time("2023-01-01") + def test_notification_casework_for_members(self): + # GIVEN + casework = create_assignment( + assignment_type=AssignmentType.CASEWORK, + # has a submission deadline within range -> member notification + submission_deadline=timezone.make_aware(datetime(2023, 1, 2)), + # but no evaluation deadline within range -> no expert notification + evaluation_deadline=timezone.make_aware(datetime(2023, 2, 2)), + ) + + # ...too early + create_assignment( + assignment_type=AssignmentType.CASEWORK, + submission_deadline=timezone.make_aware(datetime(2023, 1, 4)), + evaluation_deadline=timezone.make_aware(datetime(2023, 2, 2)), + ) + + # ...too late + create_assignment( + assignment_type=AssignmentType.CASEWORK, + submission_deadline=timezone.make_aware(datetime(2022, 1, 1)), + evaluation_deadline=timezone.make_aware(datetime(2022, 2, 2)), + ) + + # WHEN + assignment_reminder_members_notification_job() + + # THEN + self.assertEquals(3, len(Notification.objects.all())) + self._assert_member_assignment_notifications( + action_object=casework, + expected_recipients=RECIPIENT_STUDENTS, + ) + + with self.assertRaises(Notification.DoesNotExist): + Notification.objects.get(recipient__username=RECIPIENT_TRAINER) + + @freeze_time("2023-01-01") + def test_notification_casework_for_experts(self): + # GIVEN + casework = create_assignment( + assignment_type=AssignmentType.CASEWORK, + submission_deadline=timezone.make_aware(datetime(2022, 12, 12)), + evaluation_deadline=timezone.make_aware(datetime(2023, 1, 2)), + ) + + # WHEN + assignment_reminder_members_notification_job() + + # THEN + self.assertEquals(1, len(Notification.objects.all())) + + notification = Notification.objects.get(recipient__username=RECIPIENT_TRAINER) + self.assertEquals(casework, notification.action_object) + self.assertEquals("INFORMATION", notification.notification_category) + self.assertEquals(EXPECTED_EXPERT_VERB, notification.verb) + self.assertEquals( + casework.evaluation_deadline.url_expert, notification.target_url + ) + self.assertEquals( + "CASEWORK_EXPERT_EVALUATION_REMINDER", notification.notification_trigger + ) + + @freeze_time("2023-01-01") + def test_notification_prep_assignment(self): + # GIVEN + prep_assignment = create_assignment( + assignment_type=AssignmentType.PREP_ASSIGNMENT, + submission_deadline=timezone.make_aware(datetime(2023, 1, 2)), + evaluation_deadline=None, + ) + + # ...too early + create_assignment( + assignment_type=AssignmentType.PREP_ASSIGNMENT, + submission_deadline=timezone.make_aware(datetime(2023, 1, 4)), + evaluation_deadline=None, + ) + + # ...too late + create_assignment( + assignment_type=AssignmentType.PREP_ASSIGNMENT, + submission_deadline=timezone.make_aware(datetime(2022, 1, 1)), + evaluation_deadline=None, + ) + + # WHEN + assignment_reminder_members_notification_job() + + # THEN + self.assertEquals(3, len(Notification.objects.all())) + self._assert_member_assignment_notifications( + action_object=prep_assignment, + expected_recipients=RECIPIENT_STUDENTS, + ) + + with self.assertRaises(Notification.DoesNotExist): + Notification.objects.get(recipient__username=RECIPIENT_TRAINER) From 4e7f7b9da8ad19ce94530c25f7250cdf3dbb1566 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Thu, 12 Oct 2023 12:19:24 +0200 Subject: [PATCH 3/6] fix: use correct template, split edoniq & casework/prepass --- .../notify/email/email_services.py | 34 ++++++---- .../send_assigment_course_reminders.py | 14 ++-- server/vbv_lernwelt/notify/services.py | 68 ++++++++++++++++--- .../test_send_assigment_course_reminders.py | 46 +++++++++++++ 4 files changed, 131 insertions(+), 31 deletions(-) diff --git a/server/vbv_lernwelt/notify/email/email_services.py b/server/vbv_lernwelt/notify/email/email_services.py index 525133d5..66cb7b97 100644 --- a/server/vbv_lernwelt/notify/email/email_services.py +++ b/server/vbv_lernwelt/notify/email/email_services.py @@ -29,22 +29,28 @@ class EmailTemplate(Enum): "fr": "d-f88d9912e5484e55a879571463e4a166", } - # FIXME: Create Sendgrid templates - # Assumption: Edonqic, CaseWork and PrepAssignment share - # the same template for now. Once the templates are created - # update the template ids here and make sure the needed template - # data is passed to _send_notification! - ASSIGNMENT_REMINDER = { - "de": "", - "it": "", - "fr": "", + ASSIGNMENT_REMINDER_CASEWORK_MEMBER = { + "de": "d-8b84fd96213540a796c40d4344bc606f", + "fr": "d-ae6cd87b6b574643b4fff209148c7620", + "it": "d-1bf8b12a70324e1b91050d5fa01ed81f", } - # FIXME: Same as above... - CASEWORK_EXPERT_EVALUATION_REMINDER = { - "de": "", - "it": "", - "fr": "", + ASSIGNMENT_REMINDER_PREP_ASSIGNMENT_MEMBER = { + "de": "d-cb866d8c538f4ffaab923022ef7209fa", + "fr": "d-fdc84ae0e1b7417a8ede8db4e07ee7a8", + "it": "d-39d16586341b4559b3a3df71db3d04fb", + } + + ASSIGNMENT_REMINDER_EDONIQ_MEMBER = { + "de": "d-4b26911d04834079a64ab1758ca470cc", + "fr": "d-b9f27e3e13e44f20aa5d1a40c93da00d", + "it": "d-1d3d854c5b3e4012ac3d33eeb3d6e7d1", + } + + EVALUATION_REMINDER_CASEWORK_EXPERT = { + "de": "d-6e3dd4acc7fc4ce7a2776f5147bd32fd", + "fr": "d-0104add90a354d7fb1fc9fecfa132d06", + "it": "d-630e9316960647768c0a657e175436aa", } # VBV - Geleitete Fallarbeit abgegeben diff --git a/server/vbv_lernwelt/notify/management/commands/send_assigment_course_reminders.py b/server/vbv_lernwelt/notify/management/commands/send_assigment_course_reminders.py index 5cb5c59f..468c49d2 100644 --- a/server/vbv_lernwelt/notify/management/commands/send_assigment_course_reminders.py +++ b/server/vbv_lernwelt/notify/management/commands/send_assigment_course_reminders.py @@ -36,23 +36,23 @@ def assignment_reminder_members_notification_job(): role=CourseSessionUser.Role.MEMBER, ): sent.append( - NotificationService.send_assignment_reminder_notification( - member.user, assignment + NotificationService.send_assignment_reminder_notification_member( + recipient=member.user, assignment=assignment ) ) # member notifications (EDONIQ_TEST) - for assignment in CourseSessionEdoniqTest.objects.filter( + for edoniq_test in CourseSessionEdoniqTest.objects.filter( deadline__start__lte=end, deadline__start__gte=start, ): for member in CourseSessionUser.objects.filter( - course_session_id=assignment.course_session.id, + course_session_id=edoniq_test.course_session.id, role=CourseSessionUser.Role.MEMBER, ): sent.append( - NotificationService.send_assignment_reminder_notification( - member.user, assignment + NotificationService.send_edoniq_test_reminder_notification_member( + recipient=member.user, edoniq_test=edoniq_test ) ) @@ -70,7 +70,7 @@ def assignment_reminder_members_notification_job(): ): sent.append( NotificationService.send_casework_expert_evaluation_reminder( - expert.user, assignment + recipient=expert.user, assignment=assignment ) ) diff --git a/server/vbv_lernwelt/notify/services.py b/server/vbv_lernwelt/notify/services.py index 3239754e..48649c72 100644 --- a/server/vbv_lernwelt/notify/services.py +++ b/server/vbv_lernwelt/notify/services.py @@ -1,17 +1,22 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING import structlog from django.db.models import Model from notifications.signals import notify +from vbv_lernwelt.assignment.models import AssignmentType from vbv_lernwelt.core.models import User from vbv_lernwelt.course.models import CourseSession +from vbv_lernwelt.course_session.models import ( + CourseSessionAssignment, +) from vbv_lernwelt.notify.email.email_services import ( create_template_data_from_course_session_attendance_course, EmailTemplate, send_email, + format_swiss_datetime, ) from vbv_lernwelt.notify.models import ( Notification, @@ -22,7 +27,6 @@ from vbv_lernwelt.notify.models import ( if TYPE_CHECKING: from vbv_lernwelt.assignment.models import AssignmentCompletion from vbv_lernwelt.course_session.models import ( - CourseSessionAssignment, CourseSessionAttendanceCourse, CourseSessionEdoniqTest, ) @@ -142,17 +146,25 @@ class NotificationService: ) @classmethod - def send_assignment_reminder_notification( + def send_assignment_reminder_notification_member( cls, recipient: User, - assignment: Union[CourseSessionAssignment, CourseSessionEdoniqTest], + assignment: CourseSessionAssignment, ): texts = { "de": "Erinnerung: Bald ist ein Abgabetermin", "fr": "Rappel: Une date limite approche", "it": "Promemoria: Una scadenza si avvicina", } + + templates = { + AssignmentType.CASEWORK: EmailTemplate.ASSIGNMENT_REMINDER_CASEWORK_MEMBER, + AssignmentType.PREP_ASSIGNMENT: EmailTemplate.ASSIGNMENT_REMINDER_PREP_ASSIGNMENT_MEMBER, + } + verb = texts.get(recipient.language, "de") + circle = assignment.learning_content.get_parent_circle().title + due_at = assignment.submission_deadline.start return cls._send_notification( recipient=recipient, @@ -162,10 +174,43 @@ class NotificationService: target_url=assignment.learning_content.get_frontend_url(), action_object=assignment, course_session=assignment.course_session, - email_template=EmailTemplate.ASSIGNMENT_REMINDER, + email_template=templates[ + AssignmentType(assignment.learning_content.assignment_type) + ], template_data={ - # FIXME: Add template data as needed - # for the sendgrid email template + "circle": circle, + "due_date": format_swiss_datetime(due_at), + }, + ) + + @classmethod + def send_edoniq_test_reminder_notification_member( + cls, + recipient: User, + edoniq_test: CourseSessionEdoniqTest, + ): + texts = { + "de": "Erinnerung: Bald ist ein Abgabetermin", + "fr": "Rappel: Une date limite approche", + "it": "Promemoria: Una scadenza si avvicina", + } + + verb = texts.get(recipient.language, "de") + circle = edoniq_test.learning_content.get_parent_circle().title + due_at = edoniq_test.deadline.start + + return cls._send_notification( + recipient=recipient, + verb=verb, + notification_category=NotificationCategory.INFORMATION, + notification_trigger=NotificationTrigger.ASSIGNMENT_REMINDER, + target_url=edoniq_test.learning_content.get_frontend_url(), + action_object=edoniq_test, + course_session=edoniq_test.course_session, + email_template=EmailTemplate.ASSIGNMENT_REMINDER_EDONIQ_MEMBER, + template_data={ + "circle": circle, + "due_date": format_swiss_datetime(due_at), }, ) @@ -180,7 +225,10 @@ class NotificationService: "fr": "Rappel: Une date limite approche", "it": "Promemoria: Una scadenza si avvicina", } + verb = texts.get(recipient.language, "de") + circle = assignment.learning_content.get_parent_circle().title + due_at = assignment.evaluation_deadline.start return cls._send_notification( recipient=recipient, @@ -190,10 +238,10 @@ class NotificationService: target_url=assignment.evaluation_deadline.url_expert, action_object=assignment, course_session=assignment.course_session, - email_template=EmailTemplate.CASEWORK_EXPERT_EVALUATION_REMINDER, + email_template=EmailTemplate.EVALUATION_REMINDER_CASEWORK_EXPERT, template_data={ - # FIXME: Add template data as needed - # for the sendgrid email template + "circle": circle, + "due_date": format_swiss_datetime(due_at), }, ) diff --git a/server/vbv_lernwelt/notify/tests/test_send_assigment_course_reminders.py b/server/vbv_lernwelt/notify/tests/test_send_assigment_course_reminders.py index c8fef4b1..de5e2736 100644 --- a/server/vbv_lernwelt/notify/tests/test_send_assigment_course_reminders.py +++ b/server/vbv_lernwelt/notify/tests/test_send_assigment_course_reminders.py @@ -1,3 +1,4 @@ +import re from datetime import datetime from typing import Dict @@ -18,6 +19,7 @@ from vbv_lernwelt.learnpath.models import ( LearningContentAssignment, LearningContentEdoniqTest, ) +from vbv_lernwelt.notify.email.email_services import EmailTemplate from vbv_lernwelt.notify.management.commands.send_assigment_course_reminders import ( assignment_reminder_members_notification_job, ) @@ -110,6 +112,50 @@ class TestAssignmentCourseRemindersTest(TestCase): self.assertEquals("INFORMATION", notification.notification_category) self.assertEquals(EXPECTED_MEMBER_VERB, notification.verb) + template_data = notification.data["template_data"] + + self.assertEquals( + action_object.learning_content.get_parent_circle().title, + template_data["circle"], + ) + + self.assertEquals( + action_object.learning_content.get_frontend_url(), + notification.target_url, + ) + + match = re.fullmatch( + r"\d{2}\.\d{2}\.\d{4} \d{2}:\d{2}", template_data["due_date"] + ) + + self.assertIsNotNone( + match, f"due_date format is incorrect: {template_data['due_date']}" + ) + + email_template = notification.data["email_template"] + + # make sure we have the correct email template + if type(action_object) == CourseSessionAssignment: + assignment_type = AssignmentType( + action_object.learning_content.assignment_type + ) + + if assignment_type == AssignmentType.CASEWORK: + self.assertEquals( + EmailTemplate.ASSIGNMENT_REMINDER_CASEWORK_MEMBER.name, + email_template, + ) + elif assignment_type == AssignmentType.PREP_ASSIGNMENT: + self.assertEquals( + EmailTemplate.ASSIGNMENT_REMINDER_PREP_ASSIGNMENT_MEMBER.name, + email_template, + ) + elif type(action_object) == CourseSessionEdoniqTest: + self.assertEquals( + EmailTemplate.ASSIGNMENT_REMINDER_EDONIQ_MEMBER.name, + email_template, + ) + @freeze_time("2023-01-01") def test_notification_edoniq(self): # GIVEN From d93a563880b043ec3a8770219669b9f2f9b49d67 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Thu, 12 Oct 2023 12:20:53 +0200 Subject: [PATCH 4/6] fix: format --- server/vbv_lernwelt/notify/services.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/server/vbv_lernwelt/notify/services.py b/server/vbv_lernwelt/notify/services.py index 48649c72..bf792511 100644 --- a/server/vbv_lernwelt/notify/services.py +++ b/server/vbv_lernwelt/notify/services.py @@ -9,14 +9,12 @@ from notifications.signals import notify from vbv_lernwelt.assignment.models import AssignmentType from vbv_lernwelt.core.models import User from vbv_lernwelt.course.models import CourseSession -from vbv_lernwelt.course_session.models import ( - CourseSessionAssignment, -) +from vbv_lernwelt.course_session.models import CourseSessionAssignment from vbv_lernwelt.notify.email.email_services import ( create_template_data_from_course_session_attendance_course, EmailTemplate, - send_email, format_swiss_datetime, + send_email, ) from vbv_lernwelt.notify.models import ( Notification, From 7b610fa742ae6e9e6417076be6e31419fa879376 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Thu, 12 Oct 2023 12:33:13 +0200 Subject: [PATCH 5/6] fix: trufflehog file with Sendgrid template IDs --- trufflehog-exclude-patterns.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/trufflehog-exclude-patterns.txt b/trufflehog-exclude-patterns.txt index e15192fe..bee94b37 100644 --- a/trufflehog-exclude-patterns.txt +++ b/trufflehog-exclude-patterns.txt @@ -3,6 +3,7 @@ env_secrets/ env/bitbucket/Dockerfile env/docker_local.env server/vbv_lernwelt/assignment/creators/create_assignments.py +server/vbv_lernwelt/notify/email/email_services.py server/vbv_lernwelt/static/ server/vbv_lernwelt/media/ server/vbv_lernwelt/edoniq_test/certificates/test.key From 98ad158913013e2bba40220b992bc9b2cced5c5c Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Thu, 12 Oct 2023 13:26:55 +0200 Subject: [PATCH 6/6] refactor: unify different reminders (manage.py) --- server/vbv_lernwelt/notify/email/__init__.py | 0 .../notify/email/reminders/__init__.py | 0 .../reminders/assigment.py} | 25 +++------ .../reminders/attendance.py} | 25 ++------- .../commands/send_email_reminders.py | 56 +++++++++++++++++++ ...minders.py => test_assigment_reminders.py} | 12 ++-- ...inders.py => test_attendance_reminders.py} | 6 +- 7 files changed, 79 insertions(+), 45 deletions(-) create mode 100644 server/vbv_lernwelt/notify/email/__init__.py create mode 100644 server/vbv_lernwelt/notify/email/reminders/__init__.py rename server/vbv_lernwelt/notify/{management/commands/send_assigment_course_reminders.py => email/reminders/assigment.py} (81%) rename server/vbv_lernwelt/notify/{management/commands/send_attendance_course_reminders.py => email/reminders/attendance.py} (72%) create mode 100644 server/vbv_lernwelt/notify/management/commands/send_email_reminders.py rename server/vbv_lernwelt/notify/tests/{test_send_assigment_course_reminders.py => test_assigment_reminders.py} (96%) rename server/vbv_lernwelt/notify/tests/{test_send_attendance_course_reminders.py => test_attendance_reminders.py} (95%) diff --git a/server/vbv_lernwelt/notify/email/__init__.py b/server/vbv_lernwelt/notify/email/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/vbv_lernwelt/notify/email/reminders/__init__.py b/server/vbv_lernwelt/notify/email/reminders/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/vbv_lernwelt/notify/management/commands/send_assigment_course_reminders.py b/server/vbv_lernwelt/notify/email/reminders/assigment.py similarity index 81% rename from server/vbv_lernwelt/notify/management/commands/send_assigment_course_reminders.py rename to server/vbv_lernwelt/notify/email/reminders/assigment.py index 468c49d2..50a030c3 100644 --- a/server/vbv_lernwelt/notify/management/commands/send_assigment_course_reminders.py +++ b/server/vbv_lernwelt/notify/email/reminders/assigment.py @@ -4,7 +4,6 @@ import structlog from django.utils import timezone from vbv_lernwelt.assignment.models import AssignmentType -from vbv_lernwelt.core.base import LoggedCommand from vbv_lernwelt.course.models import CourseSessionUser from vbv_lernwelt.course_session.models import ( CourseSessionAssignment, @@ -17,7 +16,7 @@ logger = structlog.get_logger(__name__) ASSIGNMENT_REMINDER_LEAD_TIME = timedelta(days=2) -def assignment_reminder_members_notification_job(): +def send_assignment_reminder_notifications(): start = timezone.now() end = timezone.now() + ASSIGNMENT_REMINDER_LEAD_TIME sent = [] @@ -74,18 +73,12 @@ def assignment_reminder_members_notification_job(): ) ) + logger.debug( + "Sent assigment reminders", + start_time=start.isoformat(), + end_time=end.isoformat(), + label="assigment_reminders", + sent=sent, + ) + return {"sent": sent} - - -class Command(LoggedCommand): - help = "Sends assignments course reminder notifications to participants and experts" - - def handle(self, *args, **options): - results = assignment_reminder_members_notification_job() - self.job_log.json_data = results - self.job_log.save() - logger.info( - "Assignment course reminder notification job finished", - label="assignment_course_reminder_notification_job", - results=results, - ) diff --git a/server/vbv_lernwelt/notify/management/commands/send_attendance_course_reminders.py b/server/vbv_lernwelt/notify/email/reminders/attendance.py similarity index 72% rename from server/vbv_lernwelt/notify/management/commands/send_attendance_course_reminders.py rename to server/vbv_lernwelt/notify/email/reminders/attendance.py index 81e89677..aa97081d 100644 --- a/server/vbv_lernwelt/notify/management/commands/send_attendance_course_reminders.py +++ b/server/vbv_lernwelt/notify/email/reminders/attendance.py @@ -4,7 +4,6 @@ from datetime import timedelta import structlog from django.utils import timezone -from vbv_lernwelt.core.base import LoggedCommand from vbv_lernwelt.course.models import CourseSessionUser from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse from vbv_lernwelt.notify.services import NotificationService @@ -14,7 +13,7 @@ logger = structlog.get_logger(__name__) PRESENCE_COURSE_REMINDER_LEAD_TIME = timedelta(weeks=2) -def attendance_course_reminder_notification_job(): +def send_attendance_reminder_notifications(): """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 @@ -26,8 +25,8 @@ def attendance_course_reminder_notification_job(): logger.info( "Querying for attendance courses in specified time range", - start_time=start, - end_time=end, + start_time=start.isoformat(), + end_time=end.isoformat(), label="attendance_course_reminder_notification_job", num_attendance_courses=len(attendance_courses), ) @@ -36,8 +35,8 @@ def attendance_course_reminder_notification_job(): csu = CourseSessionUser.objects.filter(course_session_id=cs_id) logger.info( "Sending attendance course reminder notification", - start_time=start, - end_time=end, + start_time=start.isoformat(), + end_time=end.isoformat(), label="attendance_course_reminder_notification_job", num_users=len(csu), course_session_id=cs_id, @@ -53,17 +52,3 @@ def attendance_course_reminder_notification_job(): label="attendance_course_reminder_notification_job", ) return dict(results_counter) - - -class Command(LoggedCommand): - help = "Sends attendance course reminder notifications to participants" - - def handle(self, *args, **options): - results = attendance_course_reminder_notification_job() - self.job_log.json_data = results - self.job_log.save() - logger.info( - "Attendance course reminder notification job finished", - label="attendance_course_reminder_notification_job", - results=results, - ) diff --git a/server/vbv_lernwelt/notify/management/commands/send_email_reminders.py b/server/vbv_lernwelt/notify/management/commands/send_email_reminders.py new file mode 100644 index 00000000..eec415a5 --- /dev/null +++ b/server/vbv_lernwelt/notify/management/commands/send_email_reminders.py @@ -0,0 +1,56 @@ +from enum import Enum + +import structlog + +from vbv_lernwelt.core.base import LoggedCommand +from vbv_lernwelt.notify.email.reminders.assigment import ( + send_assignment_reminder_notifications, +) +from vbv_lernwelt.notify.email.reminders.attendance import ( + send_attendance_reminder_notifications, +) + +logger = structlog.get_logger(__name__) + + +class ReminderType(Enum): + ASSIGNMENT = "assignment" + ATTENDANCE = "attendance" + + +ACTIONS = { + ReminderType.ASSIGNMENT: send_assignment_reminder_notifications, + ReminderType.ATTENDANCE: send_attendance_reminder_notifications, +} + + +class Command(LoggedCommand): + help = "Sends Email Reminder Notifications" + + def add_arguments(self, parser): + parser.add_argument( + "--type", + choices=[t.value for t in ReminderType] + ["all"], + required=True, + help="Type of reminder", + ) + + def handle(self, *args, **options): + reminder_type = options["type"] + + if reminder_type == "all": + types = [ReminderType.ASSIGNMENT, ReminderType.ATTENDANCE] + else: + types = [ReminderType(reminder_type)] + + results = {t.value: None for t in types} + + for reminder in types: + logger.info(f"Starting {reminder.name} reminder notification job") + results[reminder.value] = ACTIONS[reminder]() + logger.info(f"{reminder.name} reminder notification job finished") + + self.job_log.json_data = results + self.job_log.save() + + logger.info("Reminder notification job finished") diff --git a/server/vbv_lernwelt/notify/tests/test_send_assigment_course_reminders.py b/server/vbv_lernwelt/notify/tests/test_assigment_reminders.py similarity index 96% rename from server/vbv_lernwelt/notify/tests/test_send_assigment_course_reminders.py rename to server/vbv_lernwelt/notify/tests/test_assigment_reminders.py index de5e2736..020baf08 100644 --- a/server/vbv_lernwelt/notify/tests/test_send_assigment_course_reminders.py +++ b/server/vbv_lernwelt/notify/tests/test_assigment_reminders.py @@ -20,8 +20,8 @@ from vbv_lernwelt.learnpath.models import ( LearningContentEdoniqTest, ) from vbv_lernwelt.notify.email.email_services import EmailTemplate -from vbv_lernwelt.notify.management.commands.send_assigment_course_reminders import ( - assignment_reminder_members_notification_job, +from vbv_lernwelt.notify.email.reminders.assigment import ( + send_assignment_reminder_notifications, ) from vbv_lernwelt.notify.models import Notification @@ -174,7 +174,7 @@ class TestAssignmentCourseRemindersTest(TestCase): ) # WHEN - assignment_reminder_members_notification_job() + send_assignment_reminder_notifications() # THEN self.assertEquals(3, len(Notification.objects.all())) @@ -212,7 +212,7 @@ class TestAssignmentCourseRemindersTest(TestCase): ) # WHEN - assignment_reminder_members_notification_job() + send_assignment_reminder_notifications() # THEN self.assertEquals(3, len(Notification.objects.all())) @@ -234,7 +234,7 @@ class TestAssignmentCourseRemindersTest(TestCase): ) # WHEN - assignment_reminder_members_notification_job() + send_assignment_reminder_notifications() # THEN self.assertEquals(1, len(Notification.objects.all())) @@ -274,7 +274,7 @@ class TestAssignmentCourseRemindersTest(TestCase): ) # WHEN - assignment_reminder_members_notification_job() + send_assignment_reminder_notifications() # THEN self.assertEquals(3, len(Notification.objects.all())) diff --git a/server/vbv_lernwelt/notify/tests/test_send_attendance_course_reminders.py b/server/vbv_lernwelt/notify/tests/test_attendance_reminders.py similarity index 95% rename from server/vbv_lernwelt/notify/tests/test_send_attendance_course_reminders.py rename to server/vbv_lernwelt/notify/tests/test_attendance_reminders.py index 81b3c9b3..3a0a032c 100644 --- a/server/vbv_lernwelt/notify/tests/test_send_attendance_course_reminders.py +++ b/server/vbv_lernwelt/notify/tests/test_attendance_reminders.py @@ -10,8 +10,8 @@ from vbv_lernwelt.course.creators.test_course import create_test_course from vbv_lernwelt.course.models import CourseSession from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse from vbv_lernwelt.learnpath.models import LearningContentAttendanceCourse -from vbv_lernwelt.notify.management.commands.send_attendance_course_reminders import ( - attendance_course_reminder_notification_job, +from vbv_lernwelt.notify.email.reminders.attendance import ( + send_attendance_reminder_notifications, ) from vbv_lernwelt.notify.models import Notification @@ -63,7 +63,7 @@ class TestAttendanceCourseReminders(TestCase): ) self.csac_future.due_date.save() - attendance_course_reminder_notification_job() + send_attendance_reminder_notifications() self.assertEquals(4, len(Notification.objects.all())) notification = Notification.objects.get(