Merged in feature/VBV-123 (pull request #216)
Reminder for Assignments Approved-by: Christian Cueni
This commit is contained in:
commit
ab7e879973
|
|
@ -8,4 +8,4 @@
|
||||||
0 */1 * * * /usr/local/bin/python /app/manage.py edoniq_import_results
|
0 */1 * * * /usr/local/bin/python /app/manage.py edoniq_import_results
|
||||||
|
|
||||||
# every day at 19:30
|
# 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
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,30 @@ class EmailTemplate(Enum):
|
||||||
"fr": "d-f88d9912e5484e55a879571463e4a166",
|
"fr": "d-f88d9912e5484e55a879571463e4a166",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ASSIGNMENT_REMINDER_CASEWORK_MEMBER = {
|
||||||
|
"de": "d-8b84fd96213540a796c40d4344bc606f",
|
||||||
|
"fr": "d-ae6cd87b6b574643b4fff209148c7620",
|
||||||
|
"it": "d-1bf8b12a70324e1b91050d5fa01ed81f",
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
# VBV - Geleitete Fallarbeit abgegeben
|
||||||
CASEWORK_SUBMITTED = {"de": "d-599f0b35ddcd4fac99314cdf8f5446a2"}
|
CASEWORK_SUBMITTED = {"de": "d-599f0b35ddcd4fac99314cdf8f5446a2"}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import structlog
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from vbv_lernwelt.assignment.models import AssignmentType
|
||||||
|
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__)
|
||||||
|
|
||||||
|
ASSIGNMENT_REMINDER_LEAD_TIME = timedelta(days=2)
|
||||||
|
|
||||||
|
|
||||||
|
def send_assignment_reminder_notifications():
|
||||||
|
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(
|
||||||
|
recipient=member.user, assignment=assignment
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# member notifications (EDONIQ_TEST)
|
||||||
|
for edoniq_test in CourseSessionEdoniqTest.objects.filter(
|
||||||
|
deadline__start__lte=end,
|
||||||
|
deadline__start__gte=start,
|
||||||
|
):
|
||||||
|
for member in CourseSessionUser.objects.filter(
|
||||||
|
course_session_id=edoniq_test.course_session.id,
|
||||||
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
|
):
|
||||||
|
sent.append(
|
||||||
|
NotificationService.send_edoniq_test_reminder_notification_member(
|
||||||
|
recipient=member.user, edoniq_test=edoniq_test
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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(
|
||||||
|
recipient=expert.user, assignment=assignment
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"Sent assigment reminders",
|
||||||
|
start_time=start.isoformat(),
|
||||||
|
end_time=end.isoformat(),
|
||||||
|
label="assigment_reminders",
|
||||||
|
sent=sent,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"sent": sent}
|
||||||
|
|
@ -4,7 +4,6 @@ from datetime import timedelta
|
||||||
import structlog
|
import structlog
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from vbv_lernwelt.core.base import LoggedCommand
|
|
||||||
from vbv_lernwelt.course.models import CourseSessionUser
|
from vbv_lernwelt.course.models import CourseSessionUser
|
||||||
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
||||||
from vbv_lernwelt.notify.services import NotificationService
|
from vbv_lernwelt.notify.services import NotificationService
|
||||||
|
|
@ -14,7 +13,7 @@ logger = structlog.get_logger(__name__)
|
||||||
PRESENCE_COURSE_REMINDER_LEAD_TIME = timedelta(weeks=2)
|
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"""
|
"""Checks if an attendance course is coming up and sends a reminder to the participants"""
|
||||||
start = timezone.now()
|
start = timezone.now()
|
||||||
end = timezone.now() + PRESENCE_COURSE_REMINDER_LEAD_TIME
|
end = timezone.now() + PRESENCE_COURSE_REMINDER_LEAD_TIME
|
||||||
|
|
@ -26,8 +25,8 @@ def attendance_course_reminder_notification_job():
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"Querying for attendance courses in specified time range",
|
"Querying for attendance courses in specified time range",
|
||||||
start_time=start,
|
start_time=start.isoformat(),
|
||||||
end_time=end,
|
end_time=end.isoformat(),
|
||||||
label="attendance_course_reminder_notification_job",
|
label="attendance_course_reminder_notification_job",
|
||||||
num_attendance_courses=len(attendance_courses),
|
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)
|
csu = CourseSessionUser.objects.filter(course_session_id=cs_id)
|
||||||
logger.info(
|
logger.info(
|
||||||
"Sending attendance course reminder notification",
|
"Sending attendance course reminder notification",
|
||||||
start_time=start,
|
start_time=start.isoformat(),
|
||||||
end_time=end,
|
end_time=end.isoformat(),
|
||||||
label="attendance_course_reminder_notification_job",
|
label="attendance_course_reminder_notification_job",
|
||||||
num_users=len(csu),
|
num_users=len(csu),
|
||||||
course_session_id=cs_id,
|
course_session_id=cs_id,
|
||||||
|
|
@ -53,17 +52,3 @@ def attendance_course_reminder_notification_job():
|
||||||
label="attendance_course_reminder_notification_job",
|
label="attendance_course_reminder_notification_job",
|
||||||
)
|
)
|
||||||
return dict(results_counter)
|
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,
|
|
||||||
)
|
|
||||||
|
|
@ -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")
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
# 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -15,6 +15,11 @@ class NotificationTrigger(models.TextChoices):
|
||||||
ATTENDANCE_COURSE_REMINDER = "ATTENDANCE_COURSE_REMINDER", _(
|
ATTENDANCE_COURSE_REMINDER = "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_SUBMITTED = "CASEWORK_SUBMITTED", _("Casework Submitted")
|
||||||
CASEWORK_EVALUATED = "CASEWORK_EVALUATED", _("Casework Evaluated")
|
CASEWORK_EVALUATED = "CASEWORK_EVALUATED", _("Casework Evaluated")
|
||||||
NEW_FEEDBACK = "NEW_FEEDBACK", _("New Feedback")
|
NEW_FEEDBACK = "NEW_FEEDBACK", _("New Feedback")
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,14 @@ import structlog
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
from notifications.signals import notify
|
from notifications.signals import notify
|
||||||
|
|
||||||
|
from vbv_lernwelt.assignment.models import AssignmentType
|
||||||
from vbv_lernwelt.core.models import User
|
from vbv_lernwelt.core.models import User
|
||||||
from vbv_lernwelt.course.models import CourseSession
|
from vbv_lernwelt.course.models import CourseSession
|
||||||
|
from vbv_lernwelt.course_session.models import CourseSessionAssignment
|
||||||
from vbv_lernwelt.notify.email.email_services import (
|
from vbv_lernwelt.notify.email.email_services import (
|
||||||
create_template_data_from_course_session_attendance_course,
|
create_template_data_from_course_session_attendance_course,
|
||||||
EmailTemplate,
|
EmailTemplate,
|
||||||
|
format_swiss_datetime,
|
||||||
send_email,
|
send_email,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.notify.models import (
|
from vbv_lernwelt.notify.models import (
|
||||||
|
|
@ -21,7 +24,10 @@ from vbv_lernwelt.notify.models import (
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from vbv_lernwelt.assignment.models import AssignmentCompletion
|
from vbv_lernwelt.assignment.models import AssignmentCompletion
|
||||||
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
from vbv_lernwelt.course_session.models import (
|
||||||
|
CourseSessionAttendanceCourse,
|
||||||
|
CourseSessionEdoniqTest,
|
||||||
|
)
|
||||||
from vbv_lernwelt.feedback.models import FeedbackResponse
|
from vbv_lernwelt.feedback.models import FeedbackResponse
|
||||||
|
|
||||||
logger = structlog.get_logger(__name__)
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
@ -137,6 +143,106 @@ class NotificationService:
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def send_assignment_reminder_notification_member(
|
||||||
|
cls,
|
||||||
|
recipient: User,
|
||||||
|
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,
|
||||||
|
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=templates[
|
||||||
|
AssignmentType(assignment.learning_content.assignment_type)
|
||||||
|
],
|
||||||
|
template_data={
|
||||||
|
"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),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
@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")
|
||||||
|
circle = assignment.learning_content.get_parent_circle().title
|
||||||
|
due_at = assignment.evaluation_deadline.start
|
||||||
|
|
||||||
|
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.EVALUATION_REMINDER_CASEWORK_EXPERT,
|
||||||
|
template_data={
|
||||||
|
"circle": circle,
|
||||||
|
"due_date": format_swiss_datetime(due_at),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _send_notification(
|
def _send_notification(
|
||||||
cls,
|
cls,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,287 @@
|
||||||
|
import re
|
||||||
|
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 (
|
||||||
|
CourseSessionAssignment,
|
||||||
|
CourseSessionEdoniqTest,
|
||||||
|
)
|
||||||
|
from vbv_lernwelt.learnpath.models import (
|
||||||
|
LearningContentAssignment,
|
||||||
|
LearningContentEdoniqTest,
|
||||||
|
)
|
||||||
|
from vbv_lernwelt.notify.email.email_services import EmailTemplate
|
||||||
|
from vbv_lernwelt.notify.email.reminders.assigment import (
|
||||||
|
send_assignment_reminder_notifications,
|
||||||
|
)
|
||||||
|
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",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
CourseSessionAssignment.objects.all().delete()
|
||||||
|
CourseSessionEdoniqTest.objects.all().delete()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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
|
||||||
|
should_be_sent = create_edoniq_test_assignment(
|
||||||
|
deadline_start=timezone.make_aware(datetime(2023, 1, 2))
|
||||||
|
)
|
||||||
|
|
||||||
|
# ...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
|
||||||
|
send_assignment_reminder_notifications()
|
||||||
|
|
||||||
|
# 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
|
||||||
|
send_assignment_reminder_notifications()
|
||||||
|
|
||||||
|
# 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
|
||||||
|
send_assignment_reminder_notifications()
|
||||||
|
|
||||||
|
# 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
|
||||||
|
send_assignment_reminder_notifications()
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
@ -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.models import CourseSession
|
||||||
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
||||||
from vbv_lernwelt.learnpath.models import LearningContentAttendanceCourse
|
from vbv_lernwelt.learnpath.models import LearningContentAttendanceCourse
|
||||||
from vbv_lernwelt.notify.management.commands.send_attendance_course_reminders import (
|
from vbv_lernwelt.notify.email.reminders.attendance import (
|
||||||
attendance_course_reminder_notification_job,
|
send_attendance_reminder_notifications,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.notify.models import Notification
|
from vbv_lernwelt.notify.models import Notification
|
||||||
|
|
||||||
|
|
@ -63,7 +63,7 @@ class TestAttendanceCourseReminders(TestCase):
|
||||||
)
|
)
|
||||||
self.csac_future.due_date.save()
|
self.csac_future.due_date.save()
|
||||||
|
|
||||||
attendance_course_reminder_notification_job()
|
send_attendance_reminder_notifications()
|
||||||
|
|
||||||
self.assertEquals(4, len(Notification.objects.all()))
|
self.assertEquals(4, len(Notification.objects.all()))
|
||||||
notification = Notification.objects.get(
|
notification = Notification.objects.get(
|
||||||
|
|
@ -3,6 +3,7 @@ env_secrets/
|
||||||
env/bitbucket/Dockerfile
|
env/bitbucket/Dockerfile
|
||||||
env/docker_local.env
|
env/docker_local.env
|
||||||
server/vbv_lernwelt/assignment/creators/create_assignments.py
|
server/vbv_lernwelt/assignment/creators/create_assignments.py
|
||||||
|
server/vbv_lernwelt/notify/email/email_services.py
|
||||||
server/vbv_lernwelt/static/
|
server/vbv_lernwelt/static/
|
||||||
server/vbv_lernwelt/media/
|
server/vbv_lernwelt/media/
|
||||||
server/vbv_lernwelt/edoniq_test/certificates/test.key
|
server/vbv_lernwelt/edoniq_test/certificates/test.key
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue