wip: reminder for assignments
This commit is contained in:
parent
99508fec09
commit
3cd764ee76
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from sendgrid import Mail, SendGridAPIClient
|
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__)
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
@ -29,6 +31,24 @@ class EmailTemplate(Enum):
|
||||||
"fr": "d-f88d9912e5484e55a879571463e4a166",
|
"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": "<template_id>",
|
||||||
|
"it": "<template_id>",
|
||||||
|
"fr": "<template_id>",
|
||||||
|
}
|
||||||
|
|
||||||
|
# FIXME: Same as above...
|
||||||
|
CASEWORK_EXPERT_EVALUATION_REMINDER = {
|
||||||
|
"de": "<template_id>",
|
||||||
|
"it": "<template_id>",
|
||||||
|
"fr": "<template_id>",
|
||||||
|
}
|
||||||
|
|
||||||
# VBV - Geleitete Fallarbeit abgegeben
|
# VBV - Geleitete Fallarbeit abgegeben
|
||||||
CASEWORK_SUBMITTED = {"de": "d-599f0b35ddcd4fac99314cdf8f5446a2"}
|
CASEWORK_SUBMITTED = {"de": "d-599f0b35ddcd4fac99314cdf8f5446a2"}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, Union
|
||||||
|
|
||||||
import structlog
|
import structlog
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
|
|
@ -21,7 +21,11 @@ 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,
|
||||||
|
CourseSessionAssignment,
|
||||||
|
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 +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
|
@classmethod
|
||||||
def _send_notification(
|
def _send_notification(
|
||||||
cls,
|
cls,
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
Loading…
Reference in New Issue