Refactor Notification model
This commit is contained in:
parent
b26ec64edb
commit
da56f2a346
|
|
@ -56,7 +56,7 @@ function onNotificationClick(notification: Notification) {
|
|||
>
|
||||
<div class="flex flex-row">
|
||||
<img
|
||||
v-if="notification.notification_type === 'USER_INTERACTION'"
|
||||
v-if="notification.notification_category === 'USER_INTERACTION'"
|
||||
alt="Notification icon"
|
||||
class="mr-2 h-[45px] min-w-[45px] rounded-full"
|
||||
:src="notification.actor_avatar_url ?? undefined"
|
||||
|
|
|
|||
|
|
@ -491,7 +491,7 @@ export interface DocumentUploadData {
|
|||
|
||||
// notifications
|
||||
|
||||
export type NotificationType = "USER_INTERACTION" | "PROGRESS" | "INFORMATION";
|
||||
export type NotificationCategory = "USER_INTERACTION" | "PROGRESS" | "INFORMATION";
|
||||
|
||||
export interface Notification {
|
||||
// given by AbstractNotification model
|
||||
|
|
@ -503,7 +503,7 @@ export interface Notification {
|
|||
target: string | null;
|
||||
action_object: string | null;
|
||||
// given by Notification model
|
||||
notification_type: NotificationType;
|
||||
notification_category: NotificationCategory;
|
||||
target_url: string | null;
|
||||
actor_avatar_url: string | null;
|
||||
course: string | null;
|
||||
|
|
|
|||
|
|
@ -133,38 +133,21 @@ def update_assignment_completion(
|
|||
if completion_status == AssignmentCompletionStatus.SUBMITTED:
|
||||
ac.submitted_at = timezone.now()
|
||||
if evaluation_user:
|
||||
verb = gettext(
|
||||
"%(sender)s hat die geleitete Fallarbeit «%(assignment_title)s» abgegeben."
|
||||
) % {
|
||||
"sender": assignment_user.get_full_name(),
|
||||
"assignment_title": assignment.title,
|
||||
}
|
||||
NotificationService.send_user_interaction_notification(
|
||||
NotificationService.send_assignment_submitted_notification(
|
||||
recipient=evaluation_user,
|
||||
verb=verb,
|
||||
sender=ac.assignment_user,
|
||||
course=course_session.course.title,
|
||||
target_url=ac.get_assignment_evaluation_frontend_url(),
|
||||
email_template=EmailTemplate.CASEWORK_SUBMITTED,
|
||||
assignment_completion=ac,
|
||||
)
|
||||
elif completion_status == AssignmentCompletionStatus.EVALUATION_SUBMITTED:
|
||||
ac.evaluation_submitted_at = timezone.now()
|
||||
learning_content_assignment = assignment.learningcontentassignment_set.first()
|
||||
if learning_content_assignment:
|
||||
assignment_frontend_url = learning_content_assignment.get_frontend_url()
|
||||
verb = gettext(
|
||||
"%(sender)s hat die geleitete Fallarbeit «%(assignment_title)s» bewertet."
|
||||
) % {
|
||||
"sender": evaluation_user.get_full_name(),
|
||||
"assignment_title": assignment.title,
|
||||
}
|
||||
NotificationService.send_user_interaction_notification(
|
||||
NotificationService.send_assignment_evaluated_notification(
|
||||
recipient=ac.assignment_user,
|
||||
verb=verb,
|
||||
sender=evaluation_user,
|
||||
course=course_session.course.title,
|
||||
assignment_completion=ac,
|
||||
target_url=assignment_frontend_url,
|
||||
email_template=EmailTemplate.CASEWORK_EVALUATED,
|
||||
)
|
||||
|
||||
ac.completion_status = completion_status.value
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from vbv_lernwelt.core.models import User
|
|||
from vbv_lernwelt.core.utils import find_first
|
||||
from vbv_lernwelt.course.creators.test_course import create_test_course
|
||||
from vbv_lernwelt.course.models import CourseSession
|
||||
from vbv_lernwelt.notify.models import Notification
|
||||
|
||||
|
||||
class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
|
||||
|
|
@ -153,6 +154,41 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
|
|||
},
|
||||
)
|
||||
|
||||
# check notification
|
||||
self.assertEqual(Notification.objects.count(), 1)
|
||||
notification = Notification.objects.first()
|
||||
self.assertEquals(
|
||||
"Test Student1 hat die geleitete Fallarbeit «Überprüfen einer Motorfahrzeugs-Versicherungspolice» abgegeben.",
|
||||
notification.verb,
|
||||
)
|
||||
self.assertEquals(
|
||||
"test-trainer1@example.com",
|
||||
notification.recipient.email,
|
||||
)
|
||||
self.assertEquals(
|
||||
"test-student1@example.com",
|
||||
notification.actor.email,
|
||||
)
|
||||
self.assertEquals(
|
||||
"USER_INTERACTION",
|
||||
notification.notification_category,
|
||||
)
|
||||
self.assertEquals(
|
||||
"CASEWORK_SUBMITTED",
|
||||
notification.notification_trigger,
|
||||
)
|
||||
self.assertEquals(
|
||||
notification.action_object,
|
||||
db_entry,
|
||||
)
|
||||
self.assertEquals(
|
||||
notification.course_session,
|
||||
self.course_session,
|
||||
)
|
||||
self.assertTrue(
|
||||
f"/course/test-lehrgang/cockpit/assignment" in notification.target_url
|
||||
)
|
||||
|
||||
# second submit will fail
|
||||
completion_data_string = json.dumps(
|
||||
{
|
||||
|
|
@ -346,6 +382,42 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
|
|||
},
|
||||
)
|
||||
|
||||
# check notification
|
||||
self.assertEqual(Notification.objects.count(), 1)
|
||||
notification = Notification.objects.first()
|
||||
self.assertEquals(
|
||||
"Test Trainer1 hat die geleitete Fallarbeit «Überprüfen einer Motorfahrzeugs-Versicherungspolice» bewertet.",
|
||||
notification.verb,
|
||||
)
|
||||
self.assertEquals(
|
||||
"test-student1@example.com",
|
||||
notification.recipient.email,
|
||||
)
|
||||
self.assertEquals(
|
||||
"test-trainer1@example.com",
|
||||
notification.actor.email,
|
||||
)
|
||||
self.assertEquals(
|
||||
"USER_INTERACTION",
|
||||
notification.notification_category,
|
||||
)
|
||||
self.assertEquals(
|
||||
"CASEWORK_EVALUATED",
|
||||
notification.notification_trigger,
|
||||
)
|
||||
self.assertEquals(
|
||||
notification.action_object,
|
||||
db_entry,
|
||||
)
|
||||
self.assertEquals(
|
||||
notification.course_session,
|
||||
self.course_session,
|
||||
)
|
||||
self.assertEquals(
|
||||
notification.target_url,
|
||||
"/course/test-lehrgang/learn/fahrzeug/überprüfen-einer-motorfahrzeug-versicherungspolice",
|
||||
)
|
||||
|
||||
# `EVALUATION_SUBMITTED` will create a new AssignmentCompletionAuditLog
|
||||
acl = AssignmentCompletionAuditLog.objects.get(
|
||||
assignment_user=self.student,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import uuid
|
||||
|
||||
import structlog
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from vbv_lernwelt.core.models import User
|
||||
from vbv_lernwelt.course.models import CourseSessionUser
|
||||
from vbv_lernwelt.notify.email.email_services import EmailTemplate
|
||||
from vbv_lernwelt.notify.services import NotificationService
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class FeedbackIntegerField(models.IntegerField):
|
||||
def __init__(self, **kwargs):
|
||||
|
|
@ -46,20 +48,31 @@ class FeedbackResponse(models.Model):
|
|||
HUNDRED = 100, "100%"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self._state.adding:
|
||||
# with `id=UUIDField` it is always set...
|
||||
course_session_users = CourseSessionUser.objects.filter(
|
||||
role="EXPERT", course_session=self.course_session, expert=self.circle
|
||||
)
|
||||
for csu in course_session_users:
|
||||
NotificationService.send_information_notification(
|
||||
recipient=csu.user,
|
||||
verb=f"{_('New feedback for circle')} {self.circle.title}",
|
||||
target_url=f"/course/{self.course_session.course.slug}/cockpit/feedback/{self.circle_id}/",
|
||||
email_template=EmailTemplate.NEW_FEEDBACK,
|
||||
)
|
||||
# with `id=UUIDField` it is always set...
|
||||
create_new = self._state.adding
|
||||
|
||||
super(FeedbackResponse, self).save(*args, **kwargs)
|
||||
|
||||
try:
|
||||
if create_new:
|
||||
# with `id=UUIDField` it is always set...
|
||||
course_session_users = CourseSessionUser.objects.filter(
|
||||
role="EXPERT",
|
||||
course_session=self.course_session,
|
||||
expert=self.circle,
|
||||
)
|
||||
for csu in course_session_users:
|
||||
NotificationService.send_new_feedback_notification(
|
||||
recipient=csu.user,
|
||||
feedback_response=self,
|
||||
)
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"Failed to send feedback notification",
|
||||
exc_info=True,
|
||||
label="feedback_notification",
|
||||
)
|
||||
|
||||
data = models.JSONField(default=dict)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@ from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
|
|||
from vbv_lernwelt.feedback.factories import FeedbackResponseFactory
|
||||
from vbv_lernwelt.feedback.models import FeedbackResponse
|
||||
from vbv_lernwelt.learnpath.models import Circle
|
||||
from vbv_lernwelt.notify.models import Notification
|
||||
from vbv_lernwelt.notify.models import (
|
||||
Notification,
|
||||
NotificationCategory,
|
||||
NotificationTrigger,
|
||||
)
|
||||
|
||||
|
||||
class FeedbackApiBaseTestCase(APITestCase):
|
||||
|
|
@ -62,20 +66,28 @@ class FeedbackSummaryApiTestCase(FeedbackApiBaseTestCase):
|
|||
basis_circle = Circle.objects.get(slug="test-lehrgang-lp-circle-reisen")
|
||||
csu.expert.add(basis_circle)
|
||||
|
||||
FeedbackResponse.objects.create(
|
||||
feedback = FeedbackResponse.objects.create(
|
||||
circle=basis_circle, course_session=csu.course_session
|
||||
)
|
||||
|
||||
notifications = Notification.objects.all()
|
||||
self.assertEqual(len(notifications), 1)
|
||||
self.assertEqual(notifications[0].recipient, expert)
|
||||
self.assertEqual(Notification.objects.count(), 1)
|
||||
notification = Notification.objects.first()
|
||||
self.assertEqual(notification.recipient, expert)
|
||||
self.assertEqual(
|
||||
notifications[0].verb, f"New feedback for circle {basis_circle.title}"
|
||||
notification.verb, f"New feedback for circle {basis_circle.title}"
|
||||
)
|
||||
self.assertEqual(
|
||||
notifications[0].target_url,
|
||||
notification.target_url,
|
||||
f"/course/{self.course_session.course.slug}/cockpit/feedback/{basis_circle.id}/",
|
||||
)
|
||||
self.assertEqual(
|
||||
notification.notification_category, NotificationCategory.INFORMATION
|
||||
)
|
||||
self.assertEqual(
|
||||
notification.notification_trigger, NotificationTrigger.NEW_FEEDBACK
|
||||
)
|
||||
self.assertEqual(notification.action_object, feedback)
|
||||
self.assertEqual(notification.course_session, csu.course_session)
|
||||
|
||||
def test_triggers_notification_only_on_create(self):
|
||||
expert = User.objects.get(username="patrizia.huggel@eiger-versicherungen.ch")
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ from vbv_lernwelt.learnpath.models import (
|
|||
LearningContentAttendanceCourse,
|
||||
LearningContentEdoniqTest,
|
||||
)
|
||||
from vbv_lernwelt.notify.models import NotificationType
|
||||
from vbv_lernwelt.notify.models import NotificationCategory
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
|
@ -713,7 +713,10 @@ def sync_students_from_t2l(data):
|
|||
def update_user_json_data(user: User, data: Dict[str, Any]):
|
||||
# Set E-Mail notification settings for new users
|
||||
user.additional_json_data = user.additional_json_data | sanitize_json_data_input(
|
||||
{**data, "email_notification_types": [str(NotificationType.INFORMATION)]}
|
||||
{
|
||||
**data,
|
||||
"email_notification_categories": [str(NotificationCategory.INFORMATION)],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class CreateOrUpdateStudentTestCase(TestCase):
|
|||
"Lehrvertragsnummer": "1234",
|
||||
"Tel. Privat": "079 593 83 43",
|
||||
"Geburtsdatum": "01.01.2000",
|
||||
"email_notification_types": ["INFORMATION"],
|
||||
"email_notification_categories": ["INFORMATION"],
|
||||
}
|
||||
|
||||
def test_create_student(self):
|
||||
|
|
|
|||
|
|
@ -5,20 +5,26 @@ from vbv_lernwelt.core.admin import LogAdmin
|
|||
class CustomNotificationAdmin(LogAdmin):
|
||||
date_hierarchy = "timestamp"
|
||||
raw_id_fields = ("recipient",)
|
||||
search_fields = (("recipient__username"),)
|
||||
list_display = (
|
||||
"timestamp",
|
||||
"recipient",
|
||||
"actor",
|
||||
"notification_type",
|
||||
"notification_category",
|
||||
"notification_trigger",
|
||||
"course_session",
|
||||
"emailed",
|
||||
"unread",
|
||||
)
|
||||
list_filter = (
|
||||
"notification_type",
|
||||
"notification_category",
|
||||
"notification_trigger",
|
||||
"emailed",
|
||||
"unread",
|
||||
"timestamp",
|
||||
"level",
|
||||
"public",
|
||||
# "level",
|
||||
# "public",
|
||||
"course_session",
|
||||
)
|
||||
|
||||
def get_queryset(self, request):
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ class NotifyConfig(AppConfig):
|
|||
name = "vbv_lernwelt.notify"
|
||||
|
||||
def ready(self):
|
||||
# Move the admin import here to avoid early imports
|
||||
from .admin import CustomNotificationAdmin
|
||||
|
||||
# Unregister the default Notification admin if it exists
|
||||
from vbv_lernwelt.notify.models import Notification
|
||||
|
||||
# Move the admin import here to avoid early imports
|
||||
from .admin import CustomNotificationAdmin
|
||||
|
||||
if admin.site.is_registered(Notification):
|
||||
admin.site.unregister(Notification)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from datetime import timedelta
|
|||
from django.utils import timezone
|
||||
|
||||
from vbv_lernwelt.core.admin import User
|
||||
from vbv_lernwelt.notify.models import NotificationType
|
||||
from vbv_lernwelt.notify.models import NotificationCategory
|
||||
from vbv_lernwelt.notify.tests.factories import NotificationFactory
|
||||
|
||||
|
||||
|
|
@ -28,8 +28,7 @@ def create_default_notifications() -> int:
|
|||
verb="Alexandra hat einen neuen Beitrag erfasst",
|
||||
actor_avatar_url=avatar_urls[0],
|
||||
target_url="/",
|
||||
notification_type=NotificationType.USER_INTERACTION,
|
||||
course="Versicherungsvermittler/-in",
|
||||
notification_category=NotificationCategory.USER_INTERACTION,
|
||||
timestamp=timestamps[0],
|
||||
),
|
||||
NotificationFactory(
|
||||
|
|
@ -38,8 +37,7 @@ def create_default_notifications() -> int:
|
|||
verb="Alexandra hat einen neuen Beitrag erfasst",
|
||||
actor_avatar_url=avatar_urls[0],
|
||||
target_url="/",
|
||||
notification_type=NotificationType.USER_INTERACTION,
|
||||
course="Versicherungsvermittler/-in",
|
||||
notification_category=NotificationCategory.USER_INTERACTION,
|
||||
timestamp=timestamps[1],
|
||||
),
|
||||
NotificationFactory(
|
||||
|
|
@ -48,8 +46,7 @@ def create_default_notifications() -> int:
|
|||
verb="Alexandra hat einen neuen Beitrag erfasst",
|
||||
actor_avatar_url=avatar_urls[0],
|
||||
target_url="/",
|
||||
notification_type=NotificationType.USER_INTERACTION,
|
||||
course="Versicherungsvermittler/-in",
|
||||
notification_category=NotificationCategory.USER_INTERACTION,
|
||||
timestamp=timestamps[2],
|
||||
),
|
||||
NotificationFactory(
|
||||
|
|
@ -58,8 +55,7 @@ def create_default_notifications() -> int:
|
|||
verb="Alexandra hat einen neuen Beitrag erfasst",
|
||||
actor_avatar_url=avatar_urls[0],
|
||||
target_url="/",
|
||||
notification_type=NotificationType.USER_INTERACTION,
|
||||
course="Versicherungsvermittler/-in",
|
||||
notification_category=NotificationCategory.USER_INTERACTION,
|
||||
timestamp=timestamps[3],
|
||||
),
|
||||
NotificationFactory(
|
||||
|
|
@ -68,8 +64,7 @@ def create_default_notifications() -> int:
|
|||
verb="Bianca hat für den Auftrag Autoversicherung 3 eine Lösung abgegeben",
|
||||
actor_avatar_url=avatar_urls[1],
|
||||
target_url="/",
|
||||
notification_type=NotificationType.USER_INTERACTION,
|
||||
course="Versicherungsvermittler/-in",
|
||||
notification_category=NotificationCategory.USER_INTERACTION,
|
||||
timestamp=timestamps[4],
|
||||
),
|
||||
NotificationFactory(
|
||||
|
|
@ -78,8 +73,7 @@ def create_default_notifications() -> int:
|
|||
verb="Bianca hat für den Auftrag Autoversicherung 1 eine Lösung abgegeben",
|
||||
actor_avatar_url=avatar_urls[1],
|
||||
target_url="/",
|
||||
notification_type=NotificationType.USER_INTERACTION,
|
||||
course="Versicherungsvermittler/-in",
|
||||
notification_category=NotificationCategory.USER_INTERACTION,
|
||||
timestamp=timestamps[5],
|
||||
),
|
||||
NotificationFactory(
|
||||
|
|
@ -88,8 +82,7 @@ def create_default_notifications() -> int:
|
|||
verb="Bianca hat für den Auftrag Autoversicherung 2 eine Lösung abgegeben",
|
||||
actor_avatar_url=avatar_urls[1],
|
||||
target_url="/",
|
||||
notification_type=NotificationType.USER_INTERACTION,
|
||||
course="Versicherungsvermittler/-in",
|
||||
notification_category=NotificationCategory.USER_INTERACTION,
|
||||
timestamp=timestamps[6],
|
||||
),
|
||||
NotificationFactory(
|
||||
|
|
@ -98,8 +91,7 @@ def create_default_notifications() -> int:
|
|||
verb="Bianca hat für den Auftrag Autoversicherung 4 eine Lösung abgegeben",
|
||||
actor_avatar_url=avatar_urls[1],
|
||||
target_url="/",
|
||||
notification_type=NotificationType.USER_INTERACTION,
|
||||
course="Versicherungsvermittler/-in",
|
||||
notification_category=NotificationCategory.USER_INTERACTION,
|
||||
timestamp=timestamps[7],
|
||||
),
|
||||
NotificationFactory(
|
||||
|
|
@ -108,8 +100,7 @@ def create_default_notifications() -> int:
|
|||
verb="Chantal hat eine Bewertung für den Transferauftrag 3 eingegeben",
|
||||
target_url="/",
|
||||
actor_avatar_url=avatar_urls[2],
|
||||
notification_type=NotificationType.USER_INTERACTION,
|
||||
course="Versicherungsvermittler/-in",
|
||||
notification_category=NotificationCategory.USER_INTERACTION,
|
||||
timestamp=timestamps[8],
|
||||
),
|
||||
NotificationFactory(
|
||||
|
|
@ -118,8 +109,7 @@ def create_default_notifications() -> int:
|
|||
verb="Chantal hat eine Bewertung für den Transferauftrag 4 eingegeben",
|
||||
target_url="/",
|
||||
actor_avatar_url=avatar_urls[2],
|
||||
notification_type=NotificationType.USER_INTERACTION,
|
||||
course="Versicherungsvermittler/-in",
|
||||
notification_category=NotificationCategory.USER_INTERACTION,
|
||||
timestamp=timestamps[9],
|
||||
),
|
||||
NotificationFactory(
|
||||
|
|
@ -127,22 +117,21 @@ def create_default_notifications() -> int:
|
|||
actor=user,
|
||||
verb="Super, du kommst in deinem Lernpfad gut voran. Schaue dir jetzt die verfügbaren Prüfungstermine an.",
|
||||
target_url="/",
|
||||
notification_type=NotificationType.PROGRESS,
|
||||
course="Versicherungsvermittler/-in",
|
||||
notification_category=NotificationCategory.PROGRESS,
|
||||
timestamp=timestamps[10],
|
||||
),
|
||||
NotificationFactory(
|
||||
recipient=user,
|
||||
actor=user,
|
||||
verb="Wartungsarbeiten: 20.12.2022 08:00 - 12:00",
|
||||
notification_type=NotificationType.INFORMATION,
|
||||
notification_category=NotificationCategory.INFORMATION,
|
||||
timestamp=timestamps[11],
|
||||
),
|
||||
NotificationFactory(
|
||||
recipient=user,
|
||||
actor=user,
|
||||
verb="Wartungsarbeiten: 31.01.2023 08:00 - 12:00",
|
||||
notification_type=NotificationType.INFORMATION,
|
||||
notification_category=NotificationCategory.INFORMATION,
|
||||
timestamp=timestamps[12],
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,16 +3,10 @@ from datetime import timedelta
|
|||
|
||||
import structlog
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from vbv_lernwelt.core.base import LoggedCommand
|
||||
from vbv_lernwelt.core.models import User
|
||||
from vbv_lernwelt.course.models import CourseSessionUser
|
||||
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
||||
from vbv_lernwelt.notify.email.email_services import (
|
||||
create_template_data_from_course_session_attendance_course,
|
||||
EmailTemplate,
|
||||
)
|
||||
from vbv_lernwelt.notify.services import NotificationService
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
|
@ -20,20 +14,6 @@ logger = structlog.get_logger(__name__)
|
|||
PRESENCE_COURSE_REMINDER_LEAD_TIME = timedelta(weeks=2)
|
||||
|
||||
|
||||
def send_attendance_course_reminder_notification(
|
||||
recipient: User, attendance_course: CourseSessionAttendanceCourse
|
||||
) -> str:
|
||||
return NotificationService.send_information_notification(
|
||||
recipient=recipient,
|
||||
verb=_("Erinnerung: Bald findet ein Präsenzkurs statt"),
|
||||
target_url=attendance_course.learning_content.get_frontend_url(),
|
||||
email_template=EmailTemplate.ATTENDANCE_COURSE_REMINDER,
|
||||
template_data=create_template_data_from_course_session_attendance_course(
|
||||
attendance_course=attendance_course
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def attendance_course_reminder_notification_job():
|
||||
"""Checks if an attendance course is coming up and sends a reminder to the participants"""
|
||||
start = timezone.now()
|
||||
|
|
@ -63,7 +43,7 @@ def attendance_course_reminder_notification_job():
|
|||
course_session_id=cs_id,
|
||||
)
|
||||
for user in csu:
|
||||
result = send_attendance_course_reminder_notification(
|
||||
result = NotificationService.send_attendance_course_reminder_notification(
|
||||
user.user, attendance_course
|
||||
)
|
||||
results_counter[result] += 1
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
# Generated by Django 3.2.20 on 2023-08-30 14:06
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("course", "0004_auto_20230823_1744"),
|
||||
("notify", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="notification",
|
||||
name="course",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="notification",
|
||||
name="notification_type",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="notification",
|
||||
name="course_session",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="course.coursesession",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="notification",
|
||||
name="notification_category",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("USER_INTERACTION", "User Interaction"),
|
||||
("PROGRESS", "Progress"),
|
||||
("INFORMATION", "Information"),
|
||||
],
|
||||
default="INFORMATION",
|
||||
max_length=255,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="notification",
|
||||
name="notification_trigger",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("ATTENDANCE_COURSE_REMINDER", "Attendance Course Reminder"),
|
||||
("CASEWORK_SUBMITTED", "Casework Submitted"),
|
||||
("CASEWORK_EVALUATED", "Casework Evaluated"),
|
||||
("NEW_FEEDBACK", "New Feedback"),
|
||||
],
|
||||
default="ATTENDANCE_COURSE_REMINDER",
|
||||
max_length=255,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="actor_avatar_url",
|
||||
field=models.CharField(blank=True, default="", max_length=2048),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="target_url",
|
||||
field=models.CharField(blank=True, default="", max_length=2048),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# Generated by Django 3.2.20 on 2023-08-30 14:10
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def init_user_notification_emails(apps=None, schema_editor=None):
|
||||
User = apps.get_model("core", "User")
|
||||
for u in User.objects.all():
|
||||
u.additional_json_data["email_notification_categories"] = ["NOTIFICATION"]
|
||||
u.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("notify", "0002_auto_20230830_1606"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunSQL("truncate table notify_notification cascade;"),
|
||||
migrations.RunPython(init_user_notification_emails),
|
||||
]
|
||||
|
|
@ -2,25 +2,43 @@ from django.db import models
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from notifications.base.models import AbstractNotification
|
||||
|
||||
from vbv_lernwelt.course.models import CourseSession
|
||||
|
||||
class NotificationType(models.TextChoices):
|
||||
|
||||
class NotificationCategory(models.TextChoices):
|
||||
USER_INTERACTION = "USER_INTERACTION", _("User Interaction")
|
||||
PROGRESS = "PROGRESS", _("Progress")
|
||||
INFORMATION = "INFORMATION", _("Information")
|
||||
|
||||
|
||||
class NotificationTrigger(models.TextChoices):
|
||||
ATTENDANCE_COURSE_REMINDER = "ATTENDANCE_COURSE_REMINDER", _(
|
||||
"Attendance Course Reminder"
|
||||
)
|
||||
CASEWORK_SUBMITTED = "CASEWORK_SUBMITTED", _("Casework Submitted")
|
||||
CASEWORK_EVALUATED = "CASEWORK_EVALUATED", _("Casework Evaluated")
|
||||
NEW_FEEDBACK = "NEW_FEEDBACK", _("New Feedback")
|
||||
|
||||
|
||||
class Notification(AbstractNotification):
|
||||
# UUIDs are not supported by the notifications app...
|
||||
# id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
|
||||
notification_type = models.CharField(
|
||||
max_length=32,
|
||||
choices=NotificationType.choices,
|
||||
default=NotificationType.INFORMATION,
|
||||
notification_category = models.CharField(
|
||||
max_length=255,
|
||||
choices=NotificationCategory.choices,
|
||||
default=NotificationCategory.INFORMATION,
|
||||
)
|
||||
notification_trigger = models.CharField(
|
||||
max_length=255,
|
||||
choices=NotificationTrigger.choices,
|
||||
default="",
|
||||
)
|
||||
target_url = models.CharField(max_length=2048, default="", blank=True)
|
||||
actor_avatar_url = models.CharField(max_length=2048, default="", blank=True)
|
||||
course_session = models.ForeignKey(
|
||||
CourseSession, blank=True, null=True, on_delete=models.SET_NULL
|
||||
)
|
||||
target_url = models.URLField(blank=True, null=True)
|
||||
actor_avatar_url = models.URLField(blank=True, null=True)
|
||||
course = models.CharField(max_length=32, blank=True, null=True)
|
||||
|
||||
class Meta(AbstractNotification.Meta):
|
||||
abstract = False
|
||||
|
|
|
|||
|
|
@ -1,74 +1,121 @@
|
|||
from typing import Optional
|
||||
from __future__ import annotations
|
||||
|
||||
from gettext import gettext
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import structlog
|
||||
from django.db.models import Model
|
||||
from notifications.signals import notify
|
||||
|
||||
from vbv_lernwelt.core.models import User
|
||||
from vbv_lernwelt.notify.email.email_services import EmailTemplate, send_email
|
||||
from vbv_lernwelt.notify.models import Notification, NotificationType
|
||||
from vbv_lernwelt.course.models import CourseSession
|
||||
from vbv_lernwelt.notify.email.email_services import (
|
||||
create_template_data_from_course_session_attendance_course,
|
||||
EmailTemplate,
|
||||
send_email,
|
||||
)
|
||||
from vbv_lernwelt.notify.models import (
|
||||
Notification,
|
||||
NotificationCategory,
|
||||
NotificationTrigger,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from vbv_lernwelt.assignment.models import AssignmentCompletion
|
||||
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
||||
from vbv_lernwelt.feedback.models import FeedbackResponse
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class NotificationService:
|
||||
@classmethod
|
||||
def send_user_interaction_notification(
|
||||
cls,
|
||||
recipient: User,
|
||||
verb: str,
|
||||
sender: User,
|
||||
course: str,
|
||||
target_url: str,
|
||||
email_template: EmailTemplate,
|
||||
template_data: dict = None,
|
||||
) -> str:
|
||||
def send_assignment_submitted_notification(
|
||||
cls, recipient: User, sender: User, assignment_completion: AssignmentCompletion
|
||||
):
|
||||
verb = gettext(
|
||||
"%(sender)s hat die geleitete Fallarbeit «%(assignment_title)s» abgegeben."
|
||||
) % {
|
||||
"sender": sender.get_full_name(),
|
||||
"assignment_title": assignment_completion.assignment.title,
|
||||
}
|
||||
|
||||
return cls._send_notification(
|
||||
recipient=recipient,
|
||||
verb=verb,
|
||||
notification_category=NotificationCategory.USER_INTERACTION,
|
||||
notification_trigger=NotificationTrigger.CASEWORK_SUBMITTED,
|
||||
sender=sender,
|
||||
course=course,
|
||||
target_url=target_url,
|
||||
notification_type=NotificationType.USER_INTERACTION,
|
||||
email_template=email_template,
|
||||
template_data=template_data,
|
||||
target_url=assignment_completion.get_assignment_evaluation_frontend_url(),
|
||||
course_session=assignment_completion.course_session,
|
||||
action_object=assignment_completion,
|
||||
email_template=EmailTemplate.CASEWORK_SUBMITTED,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def send_progress_notification(
|
||||
def send_assignment_evaluated_notification(
|
||||
cls,
|
||||
recipient: User,
|
||||
verb: str,
|
||||
course: str,
|
||||
sender: User,
|
||||
assignment_completion: AssignmentCompletion,
|
||||
target_url: str,
|
||||
email_template: EmailTemplate,
|
||||
template_data: dict = None,
|
||||
) -> str:
|
||||
):
|
||||
verb = gettext(
|
||||
"%(sender)s hat die geleitete Fallarbeit «%(assignment_title)s» bewertet."
|
||||
) % {
|
||||
"sender": sender.get_full_name(),
|
||||
"assignment_title": assignment_completion.assignment.title,
|
||||
}
|
||||
|
||||
return cls._send_notification(
|
||||
recipient=recipient,
|
||||
verb=verb,
|
||||
course=course,
|
||||
notification_category=NotificationCategory.USER_INTERACTION,
|
||||
notification_trigger=NotificationTrigger.CASEWORK_EVALUATED,
|
||||
sender=sender,
|
||||
target_url=target_url,
|
||||
notification_type=NotificationType.PROGRESS,
|
||||
email_template=email_template,
|
||||
template_data=template_data,
|
||||
course_session=assignment_completion.course_session,
|
||||
action_object=assignment_completion,
|
||||
email_template=EmailTemplate.CASEWORK_EVALUATED,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def send_information_notification(
|
||||
def send_new_feedback_notification(
|
||||
cls,
|
||||
recipient: User,
|
||||
verb: str,
|
||||
target_url: str,
|
||||
email_template: EmailTemplate,
|
||||
template_data: dict = None,
|
||||
) -> str:
|
||||
feedback_response: FeedbackResponse,
|
||||
):
|
||||
verb = f"New feedback for circle {feedback_response.circle.title}"
|
||||
|
||||
return cls._send_notification(
|
||||
recipient=recipient,
|
||||
verb=verb,
|
||||
target_url=target_url,
|
||||
notification_type=NotificationType.INFORMATION,
|
||||
email_template=email_template,
|
||||
template_data=template_data,
|
||||
notification_category=NotificationCategory.INFORMATION,
|
||||
notification_trigger=NotificationTrigger.NEW_FEEDBACK,
|
||||
target_url=f"/course/{feedback_response.course_session.course.slug}/cockpit/feedback/{feedback_response.circle_id}/",
|
||||
course_session=feedback_response.course_session,
|
||||
action_object=feedback_response,
|
||||
email_template=EmailTemplate.NEW_FEEDBACK,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def send_attendance_course_reminder_notification(
|
||||
cls,
|
||||
recipient: User,
|
||||
attendance_course: CourseSessionAttendanceCourse,
|
||||
):
|
||||
return cls._send_notification(
|
||||
recipient=recipient,
|
||||
verb="Erinnerung: Bald findet ein Präsenzkurs statt",
|
||||
notification_category=NotificationCategory.INFORMATION,
|
||||
notification_trigger=NotificationTrigger.ATTENDANCE_COURSE_REMINDER,
|
||||
target_url=attendance_course.learning_content.get_frontend_url(),
|
||||
action_object=attendance_course,
|
||||
course_session=attendance_course.course_session,
|
||||
email_template=EmailTemplate.ATTENDANCE_COURSE_REMINDER,
|
||||
template_data=create_template_data_from_course_session_attendance_course(
|
||||
attendance_course=attendance_course
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
|
@ -76,20 +123,24 @@ class NotificationService:
|
|||
cls,
|
||||
recipient: User,
|
||||
verb: str,
|
||||
notification_type: NotificationType,
|
||||
email_template: EmailTemplate,
|
||||
template_data: dict | None = None,
|
||||
notification_category: NotificationCategory,
|
||||
notification_trigger: NotificationTrigger,
|
||||
sender: User | None = None,
|
||||
course: str | None = None,
|
||||
action_object: Model | None = None,
|
||||
target_url: str | None = None,
|
||||
course_session: CourseSession | None = None,
|
||||
email_template: EmailTemplate | None = None,
|
||||
template_data: dict | None = None,
|
||||
fail_silently: bool = True,
|
||||
) -> str:
|
||||
if template_data is None:
|
||||
template_data = {}
|
||||
|
||||
notification_identifier = f"{notification_type.name}_{email_template.name}"
|
||||
notification_identifier = (
|
||||
f"{notification_category.name}_{notification_trigger.name}"
|
||||
)
|
||||
|
||||
actor_avatar_url: Optional[str] = None
|
||||
actor_avatar_url = ""
|
||||
if not sender:
|
||||
sender = User.objects.get(email="admin")
|
||||
else:
|
||||
|
|
@ -98,8 +149,9 @@ class NotificationService:
|
|||
recipient=recipient.email,
|
||||
sender=sender.email,
|
||||
verb=verb,
|
||||
notification_type=notification_type,
|
||||
course=course,
|
||||
notification_category=notification_category,
|
||||
notification_trigger=notification_trigger,
|
||||
course_session=course_session.title if course_session else "",
|
||||
target_url=target_url,
|
||||
template_data=template_data,
|
||||
)
|
||||
|
|
@ -108,16 +160,20 @@ class NotificationService:
|
|||
notification = NotificationService._find_duplicate_notification(
|
||||
recipient=recipient,
|
||||
verb=verb,
|
||||
notification_type=notification_type,
|
||||
notification_category=notification_category,
|
||||
notification_trigger=notification_trigger,
|
||||
target_url=target_url,
|
||||
template_name=email_template.name,
|
||||
template_data=template_data,
|
||||
course_session=course_session,
|
||||
)
|
||||
emailed = False
|
||||
if notification and notification.emailed:
|
||||
emailed = True
|
||||
|
||||
if cls._should_send_email(notification_type, recipient) and not emailed:
|
||||
if (
|
||||
email_template
|
||||
and cls._should_send_email(notification_category, recipient)
|
||||
and not emailed
|
||||
):
|
||||
log.debug("Try to send email")
|
||||
try:
|
||||
emailed = cls._send_email(
|
||||
|
|
@ -153,16 +209,18 @@ class NotificationService:
|
|||
sender=sender,
|
||||
recipient=recipient,
|
||||
verb=verb,
|
||||
action_object=action_object,
|
||||
emailed=emailed,
|
||||
# The metadata is saved in the 'data' member of the AbstractNotification model
|
||||
email_template=email_template.name,
|
||||
# The extra arguments are saved in the 'data' member
|
||||
email_template=email_template.name if email_template else "",
|
||||
template_data=template_data,
|
||||
)
|
||||
|
||||
sent_notification: Notification = response[0][1][0] # 🫨
|
||||
sent_notification.target_url = target_url
|
||||
sent_notification.notification_type = notification_type
|
||||
sent_notification.course = course
|
||||
sent_notification.notification_category = notification_category
|
||||
sent_notification.notification_trigger = notification_trigger
|
||||
sent_notification.course_session = course_session
|
||||
sent_notification.actor_avatar_url = actor_avatar_url
|
||||
sent_notification.save()
|
||||
log.info("Notification sent successfully", emailed=emailed)
|
||||
|
|
@ -181,10 +239,10 @@ class NotificationService:
|
|||
|
||||
@staticmethod
|
||||
def _should_send_email(
|
||||
notification_type: NotificationType, recipient: User
|
||||
notification_category: NotificationCategory, recipient: User
|
||||
) -> bool:
|
||||
return str(notification_type) in recipient.additional_json_data.get(
|
||||
"email_notification_types", []
|
||||
return str(notification_category) in recipient.additional_json_data.get(
|
||||
"email_notification_categories", []
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -206,10 +264,10 @@ class NotificationService:
|
|||
def _find_duplicate_notification(
|
||||
recipient: User,
|
||||
verb: str,
|
||||
notification_type: NotificationType,
|
||||
template_name: str,
|
||||
template_data: dict,
|
||||
notification_category: NotificationCategory,
|
||||
notification_trigger: NotificationTrigger,
|
||||
target_url: str | None,
|
||||
course_session: CourseSession | None,
|
||||
) -> Notification | None:
|
||||
"""Check if a notification with the same parameters has already been sent to the recipient.
|
||||
This is to prevent duplicate notifications from being sent and to protect against potential programming errors.
|
||||
|
|
@ -217,10 +275,8 @@ class NotificationService:
|
|||
return Notification.objects.filter(
|
||||
recipient=recipient,
|
||||
verb=verb,
|
||||
notification_type=notification_type,
|
||||
notification_category=notification_category,
|
||||
notification_trigger=notification_trigger,
|
||||
target_url=target_url,
|
||||
data={
|
||||
"email_template": template_name,
|
||||
"template_data": template_data,
|
||||
},
|
||||
course_session=course_session,
|
||||
).first()
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from rest_framework.test import APITestCase
|
|||
|
||||
from vbv_lernwelt.core.admin import User
|
||||
from vbv_lernwelt.core.tests.factories import UserFactory
|
||||
from vbv_lernwelt.notify.models import Notification, NotificationType
|
||||
from vbv_lernwelt.notify.models import Notification, NotificationCategory
|
||||
from vbv_lernwelt.notify.tests.factories import NotificationFactory
|
||||
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ class TestNotificationSettingsApi(APITestCase):
|
|||
|
||||
def test_store_retrieve_settings(self):
|
||||
notification_settings = json.dumps(
|
||||
[NotificationType.INFORMATION, NotificationType.PROGRESS]
|
||||
[NotificationCategory.INFORMATION, NotificationCategory.PROGRESS]
|
||||
)
|
||||
|
||||
api_path = "/api/notify/email_notification_settings/"
|
||||
|
|
@ -128,7 +128,7 @@ class TestNotificationSettingsApi(APITestCase):
|
|||
self.assertEqual(response.json(), notification_settings)
|
||||
self.user.refresh_from_db()
|
||||
self.assertEqual(
|
||||
self.user.additional_json_data["email_notification_types"],
|
||||
self.user.additional_json_data["email_notification_categories"],
|
||||
notification_settings,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,45 +1,21 @@
|
|||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from freezegun import freeze_time
|
||||
|
||||
from vbv_lernwelt.core.constants import TEST_COURSE_SESSION_BERN_ID
|
||||
from vbv_lernwelt.core.create_default_users import create_default_users
|
||||
from vbv_lernwelt.course.creators.test_course import create_test_course
|
||||
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
|
||||
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.email.email_services import EmailTemplate
|
||||
from vbv_lernwelt.notify.management.commands.send_attendance_course_reminders import (
|
||||
attendance_course_reminder_notification_job,
|
||||
)
|
||||
from vbv_lernwelt.notify.models import Notification
|
||||
|
||||
|
||||
@dataclass
|
||||
class SentNotification:
|
||||
recipient: User
|
||||
verb: str
|
||||
target_url: str
|
||||
email_template: EmailTemplate
|
||||
template_data: dict
|
||||
|
||||
|
||||
sent_notifications: list[SentNotification] = []
|
||||
|
||||
|
||||
def on_send_notification(**kwargs) -> None:
|
||||
sent_notifications.append(SentNotification(**kwargs))
|
||||
|
||||
|
||||
@patch(
|
||||
"vbv_lernwelt.notify.services.NotificationService.send_information_notification",
|
||||
on_send_notification,
|
||||
)
|
||||
class TestAttendanceCourseReminders(TestCase):
|
||||
def setUp(self):
|
||||
create_default_users()
|
||||
|
|
@ -57,13 +33,12 @@ class TestAttendanceCourseReminders(TestCase):
|
|||
location="Handelsschule KV Bern, Zimmer 123, Eigerstrasse 16, 3012 Bern",
|
||||
trainer="Roland Grossenbacher",
|
||||
)
|
||||
in_one_week = datetime.now() + timedelta(weeks=1)
|
||||
self.csac.due_date.start = timezone.make_aware(
|
||||
in_one_week.replace(hour=7, minute=30, second=0, microsecond=0)
|
||||
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 = timezone.make_aware(
|
||||
in_one_week.replace(hour=16, minute=15, 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()
|
||||
|
||||
|
|
@ -89,41 +64,53 @@ class TestAttendanceCourseReminders(TestCase):
|
|||
self.csac_future.due_date.save()
|
||||
|
||||
attendance_course_reminder_notification_job()
|
||||
self.assertEquals(3, len(sent_notifications))
|
||||
recipients = CourseSessionUser.objects.filter(
|
||||
course_session_id=self.csac.course_session.id
|
||||
)
|
||||
self.assertEquals(
|
||||
set(map(lambda n: n.recipient, sent_notifications)),
|
||||
set(map(lambda csu: csu.user, recipients)),
|
||||
|
||||
self.assertEquals(3, len(Notification.objects.all()))
|
||||
notification = Notification.objects.get(
|
||||
recipient__username="test-student1@example.com"
|
||||
)
|
||||
|
||||
for notification in sent_notifications:
|
||||
self.assertEquals(
|
||||
_("Erinnerung: Bald findet ein Präsenzkurs statt"),
|
||||
notification.verb,
|
||||
)
|
||||
self.assertEquals(
|
||||
"/course/test-lehrgang/learn/fahrzeug/präsenzkurs-fahrzeug",
|
||||
notification.target_url,
|
||||
)
|
||||
self.assertEquals(
|
||||
self.csac.learning_content.title,
|
||||
notification.template_data["attendance_course"],
|
||||
)
|
||||
self.assertEquals(
|
||||
self.csac.location,
|
||||
notification.template_data["location"],
|
||||
)
|
||||
self.assertEquals(
|
||||
self.csac.trainer,
|
||||
notification.template_data["trainer"],
|
||||
)
|
||||
self.assertEquals(
|
||||
self.csac.due_date.start.strftime("%d.%m.%Y %H:%M"),
|
||||
notification.template_data["start"],
|
||||
)
|
||||
self.assertEquals(
|
||||
self.csac.due_date.end.strftime("%d.%m.%Y %H:%M"),
|
||||
notification.template_data["end"],
|
||||
)
|
||||
self.assertEquals(
|
||||
"Erinnerung: Bald findet ein Präsenzkurs statt",
|
||||
notification.verb,
|
||||
)
|
||||
self.assertEquals(
|
||||
"INFORMATION",
|
||||
notification.notification_category,
|
||||
)
|
||||
self.assertEquals(
|
||||
"ATTENDANCE_COURSE_REMINDER",
|
||||
notification.notification_trigger,
|
||||
)
|
||||
self.assertEquals(
|
||||
self.csac,
|
||||
notification.action_object,
|
||||
)
|
||||
self.assertEquals(
|
||||
self.csac.course_session,
|
||||
notification.course_session,
|
||||
)
|
||||
self.assertEquals(
|
||||
"/course/test-lehrgang/learn/fahrzeug/präsenzkurs-fahrzeug",
|
||||
notification.target_url,
|
||||
)
|
||||
self.assertEquals(
|
||||
self.csac.learning_content.title,
|
||||
notification.data["template_data"]["attendance_course"],
|
||||
)
|
||||
self.assertEquals(
|
||||
self.csac.location,
|
||||
notification.data["template_data"]["location"],
|
||||
)
|
||||
self.assertEquals(
|
||||
self.csac.trainer,
|
||||
notification.data["template_data"]["trainer"],
|
||||
)
|
||||
self.assertEquals(
|
||||
self.csac.due_date.start.strftime("%d.%m.%Y %H:%M"),
|
||||
notification.data["template_data"]["start"],
|
||||
)
|
||||
self.assertEquals(
|
||||
self.csac.due_date.end.strftime("%d.%m.%Y %H:%M"),
|
||||
notification.data["template_data"]["end"],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@ from django.test import TestCase
|
|||
from vbv_lernwelt.core.models import User
|
||||
from vbv_lernwelt.core.tests.factories import UserFactory
|
||||
from vbv_lernwelt.notify.email.email_services import EmailTemplate
|
||||
from vbv_lernwelt.notify.models import Notification, NotificationType
|
||||
from vbv_lernwelt.notify.models import (
|
||||
Notification,
|
||||
NotificationCategory,
|
||||
NotificationTrigger,
|
||||
)
|
||||
from vbv_lernwelt.notify.services import NotificationService
|
||||
|
||||
|
||||
|
|
@ -24,142 +28,178 @@ class TestNotificationService(TestCase):
|
|||
|
||||
self.admin = UserFactory(username="admin", email="admin")
|
||||
self.sender_username = "Bob"
|
||||
UserFactory(username=self.sender_username, email="bob@gmail.com")
|
||||
UserFactory(username=self.sender_username, email="bob@example.com")
|
||||
self.sender = User.objects.get(username=self.sender_username)
|
||||
|
||||
self.recipient_username = "Alice"
|
||||
UserFactory(username=self.recipient_username, email="alice@gmail.com")
|
||||
UserFactory(username=self.recipient_username, email="alice@example.com")
|
||||
self.recipient = User.objects.get(username=self.recipient_username)
|
||||
self.recipient.additional_json_data["email_notification_types"] = json.dumps(
|
||||
["USER_INTERACTION", "INFORMATION"]
|
||||
)
|
||||
self.recipient.save()
|
||||
|
||||
self.client.login(username=self.recipient, password="pw")
|
||||
|
||||
def test_send_information_notification(self):
|
||||
verb = "Wartungsarbeiten: 13.12 10:00 - 13:00 Uhr"
|
||||
target_url = "https://www.vbv.ch"
|
||||
self.notification_service.send_information_notification(
|
||||
recipient=self.recipient,
|
||||
verb=verb,
|
||||
target_url=target_url,
|
||||
email_template=EmailTemplate.ATTENDANCE_COURSE_REMINDER,
|
||||
)
|
||||
self.assertEqual(1, Notification.objects.count())
|
||||
notification: Notification = Notification.objects.first()
|
||||
self.assertEqual(self.admin, notification.actor)
|
||||
self.assertEqual(verb, notification.verb)
|
||||
self.assertEqual(target_url, notification.target_url)
|
||||
self.assertEqual(
|
||||
str(NotificationType.INFORMATION), notification.notification_type
|
||||
)
|
||||
self.assertTrue(notification.emailed)
|
||||
|
||||
def test_send_progress_notification(self):
|
||||
verb = "Super Fortschritt! Melde dich jetzt an."
|
||||
target_url = "https://www.vbv.ch"
|
||||
course = "Versicherungsvermittler/in"
|
||||
self.notification_service.send_progress_notification(
|
||||
recipient=self.recipient,
|
||||
verb=verb,
|
||||
target_url=target_url,
|
||||
course=course,
|
||||
email_template=EmailTemplate.ATTENDANCE_COURSE_REMINDER,
|
||||
)
|
||||
self.assertEqual(1, Notification.objects.count())
|
||||
notification: Notification = Notification.objects.first()
|
||||
self.assertEqual(self.admin, notification.actor)
|
||||
self.assertEqual(verb, notification.verb)
|
||||
self.assertEqual(target_url, notification.target_url)
|
||||
self.assertEqual(course, notification.course)
|
||||
self.assertEqual(str(NotificationType.PROGRESS), notification.notification_type)
|
||||
self.assertFalse(notification.emailed)
|
||||
|
||||
def test_send_user_interaction_notification(self):
|
||||
def test_send_notification_without_email(self):
|
||||
verb = "Anne hat deinen Auftrag bewertet"
|
||||
target_url = "https://www.vbv.ch"
|
||||
course = "Versicherungsvermittler/in"
|
||||
self.notification_service.send_user_interaction_notification(
|
||||
result = self.notification_service._send_notification(
|
||||
sender=self.sender,
|
||||
recipient=self.recipient,
|
||||
verb=verb,
|
||||
target_url=target_url,
|
||||
course=course,
|
||||
email_template=EmailTemplate.ATTENDANCE_COURSE_REMINDER,
|
||||
notification_category=NotificationCategory.USER_INTERACTION,
|
||||
notification_trigger=NotificationTrigger.CASEWORK_SUBMITTED,
|
||||
fail_silently=False,
|
||||
)
|
||||
|
||||
self.assertEqual(result, "USER_INTERACTION_CASEWORK_SUBMITTED_success")
|
||||
|
||||
self.assertEqual(1, Notification.objects.count())
|
||||
notification: Notification = Notification.objects.first()
|
||||
self.assertEqual(self.sender, notification.actor)
|
||||
self.assertEqual(verb, notification.verb)
|
||||
self.assertEqual(target_url, notification.target_url)
|
||||
self.assertEqual(course, notification.course)
|
||||
self.assertEqual(
|
||||
str(NotificationType.USER_INTERACTION), notification.notification_type
|
||||
str(NotificationCategory.USER_INTERACTION),
|
||||
notification.notification_category,
|
||||
)
|
||||
self.assertFalse(notification.emailed)
|
||||
|
||||
def test_send_notification_with_email(self):
|
||||
self.recipient.additional_json_data[
|
||||
"email_notification_categories"
|
||||
] = json.dumps(["USER_INTERACTION"])
|
||||
self.recipient.save()
|
||||
|
||||
verb = "Anne hat deinen Auftrag bewertet"
|
||||
target_url = "https://www.vbv.ch"
|
||||
result = self.notification_service._send_notification(
|
||||
sender=self.sender,
|
||||
recipient=self.recipient,
|
||||
verb=verb,
|
||||
target_url=target_url,
|
||||
notification_category=NotificationCategory.USER_INTERACTION,
|
||||
notification_trigger=NotificationTrigger.CASEWORK_SUBMITTED,
|
||||
email_template=EmailTemplate.CASEWORK_SUBMITTED,
|
||||
fail_silently=False,
|
||||
)
|
||||
|
||||
self.assertEqual(result, "USER_INTERACTION_CASEWORK_SUBMITTED_emailed_success")
|
||||
|
||||
self.assertEqual(1, Notification.objects.count())
|
||||
notification: Notification = Notification.objects.first()
|
||||
self.assertEqual(self.sender, notification.actor)
|
||||
self.assertEqual(verb, notification.verb)
|
||||
self.assertEqual(target_url, notification.target_url)
|
||||
self.assertEqual(
|
||||
str(NotificationCategory.USER_INTERACTION),
|
||||
notification.notification_category,
|
||||
)
|
||||
self.assertTrue(notification.emailed)
|
||||
|
||||
def test_does_not_send_duplicate_notification(self):
|
||||
verb = "Anne hat deinen Auftrag bewertet"
|
||||
target_url = "https://www.vbv.ch"
|
||||
course = "Versicherungsvermittler/in"
|
||||
|
||||
for i in range(2):
|
||||
self.notification_service.send_user_interaction_notification(
|
||||
sender=self.sender,
|
||||
recipient=self.recipient,
|
||||
verb=verb,
|
||||
target_url=target_url,
|
||||
course=course,
|
||||
email_template=EmailTemplate.ATTENDANCE_COURSE_REMINDER,
|
||||
template_data={
|
||||
"blah": 123,
|
||||
"foo": "ich habe hunger",
|
||||
},
|
||||
)
|
||||
result = self.notification_service._send_notification(
|
||||
sender=self.sender,
|
||||
recipient=self.recipient,
|
||||
verb=verb,
|
||||
target_url=target_url,
|
||||
notification_category=NotificationCategory.USER_INTERACTION,
|
||||
notification_trigger=NotificationTrigger.CASEWORK_SUBMITTED,
|
||||
email_template=EmailTemplate.CASEWORK_SUBMITTED,
|
||||
template_data={
|
||||
"blah": 123,
|
||||
"foo": "ich habe hunger",
|
||||
},
|
||||
)
|
||||
self.assertEqual(result, "USER_INTERACTION_CASEWORK_SUBMITTED_success")
|
||||
|
||||
self.assertEqual(1, Notification.objects.count())
|
||||
notification: Notification = Notification.objects.first()
|
||||
self.assertEqual(self.sender, notification.actor)
|
||||
self.assertEqual(verb, notification.verb)
|
||||
self.assertEqual(target_url, notification.target_url)
|
||||
self.assertEqual(course, notification.course)
|
||||
self.assertEqual(
|
||||
str(NotificationType.USER_INTERACTION),
|
||||
notification.notification_type,
|
||||
)
|
||||
self.assertTrue(notification.emailed)
|
||||
result = self.notification_service._send_notification(
|
||||
sender=self.sender,
|
||||
recipient=self.recipient,
|
||||
verb=verb,
|
||||
target_url=target_url,
|
||||
notification_category=NotificationCategory.USER_INTERACTION,
|
||||
notification_trigger=NotificationTrigger.CASEWORK_SUBMITTED,
|
||||
email_template=EmailTemplate.CASEWORK_SUBMITTED,
|
||||
template_data={
|
||||
"blah": 123,
|
||||
"foo": "ich habe hunger",
|
||||
},
|
||||
)
|
||||
self.assertEqual(result, "USER_INTERACTION_CASEWORK_SUBMITTED_duplicate")
|
||||
|
||||
self.assertEqual(1, Notification.objects.count())
|
||||
notification: Notification = Notification.objects.first()
|
||||
self.assertEqual(self.sender, notification.actor)
|
||||
self.assertEqual(verb, notification.verb)
|
||||
self.assertEqual(target_url, notification.target_url)
|
||||
self.assertEqual(
|
||||
str(NotificationCategory.USER_INTERACTION),
|
||||
notification.notification_category,
|
||||
)
|
||||
self.assertEqual(
|
||||
str(NotificationTrigger.CASEWORK_SUBMITTED),
|
||||
notification.notification_trigger,
|
||||
)
|
||||
self.assertFalse(notification.emailed)
|
||||
|
||||
# when the email was not sent, yet it will still send it afterwards...
|
||||
self.recipient.additional_json_data[
|
||||
"email_notification_categories"
|
||||
] = json.dumps(["USER_INTERACTION"])
|
||||
self.recipient.save()
|
||||
|
||||
result = self.notification_service._send_notification(
|
||||
sender=self.sender,
|
||||
recipient=self.recipient,
|
||||
verb=verb,
|
||||
target_url=target_url,
|
||||
notification_category=NotificationCategory.USER_INTERACTION,
|
||||
notification_trigger=NotificationTrigger.CASEWORK_SUBMITTED,
|
||||
email_template=EmailTemplate.CASEWORK_SUBMITTED,
|
||||
template_data={
|
||||
"blah": 123,
|
||||
"foo": "ich habe hunger",
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
result, "USER_INTERACTION_CASEWORK_SUBMITTED_emailed_duplicate"
|
||||
)
|
||||
|
||||
self.assertEqual(1, Notification.objects.count())
|
||||
notification: Notification = Notification.objects.first()
|
||||
self.assertTrue(notification.emailed)
|
||||
|
||||
def test_only_sends_email_if_enabled(self):
|
||||
# Assert no mail is sent if corresponding email notification type is not enabled
|
||||
self.recipient.additional_json_data["email_notification_types"] = json.dumps(
|
||||
["INFORMATION"]
|
||||
)
|
||||
self.recipient.save()
|
||||
self.notification_service.send_user_interaction_notification(
|
||||
self.notification_service._send_notification(
|
||||
sender=self.sender,
|
||||
recipient=self.recipient,
|
||||
verb="should not be sent",
|
||||
target_url="",
|
||||
course="",
|
||||
email_template=EmailTemplate.CASEWORK_EVALUATED,
|
||||
notification_category=NotificationCategory.USER_INTERACTION,
|
||||
notification_trigger=NotificationTrigger.CASEWORK_SUBMITTED,
|
||||
email_template=EmailTemplate.CASEWORK_SUBMITTED,
|
||||
template_data={},
|
||||
)
|
||||
self.assertEqual(1, Notification.objects.count())
|
||||
self.assertFalse(self._has_sent_emails())
|
||||
|
||||
# Assert mail is sent if corresponding email notification type is enabled
|
||||
self.recipient.additional_json_data["email_notification_types"] = json.dumps(
|
||||
["USER_INTERACTION"]
|
||||
)
|
||||
self.recipient.additional_json_data[
|
||||
"email_notification_categories"
|
||||
] = json.dumps(["USER_INTERACTION"])
|
||||
self.recipient.save()
|
||||
self.notification_service.send_user_interaction_notification(
|
||||
self.notification_service._send_notification(
|
||||
sender=self.sender,
|
||||
recipient=self.recipient,
|
||||
verb="should be sent",
|
||||
target_url="",
|
||||
course="",
|
||||
email_template=EmailTemplate.CASEWORK_EVALUATED,
|
||||
notification_category=NotificationCategory.USER_INTERACTION,
|
||||
notification_trigger=NotificationTrigger.CASEWORK_SUBMITTED,
|
||||
email_template=EmailTemplate.CASEWORK_SUBMITTED,
|
||||
template_data={},
|
||||
)
|
||||
self.assertEqual(2, Notification.objects.count())
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ from rest_framework.response import Response
|
|||
|
||||
@api_view(["POST", "GET"])
|
||||
def email_notification_settings(request):
|
||||
EMAIL_NOTIFICATION_TYPES = "email_notification_types"
|
||||
EMAIL_NOTIFICATION_CATEGORIES = "email_notification_categories"
|
||||
if request.method == "POST":
|
||||
request.user.additional_json_data[EMAIL_NOTIFICATION_TYPES] = request.data
|
||||
request.user.additional_json_data[EMAIL_NOTIFICATION_CATEGORIES] = request.data
|
||||
request.user.save()
|
||||
return Response(
|
||||
status=200,
|
||||
data=request.user.additional_json_data.get(EMAIL_NOTIFICATION_TYPES, []),
|
||||
data=request.user.additional_json_data.get(EMAIL_NOTIFICATION_CATEGORIES, []),
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue