Squash merge of code from Elia

This commit is contained in:
Elia Bieri 2023-08-25 09:39:14 +02:00 committed by Daniel Egger
parent e96c21f623
commit 56e454cc8b
15 changed files with 469 additions and 68 deletions

View File

@ -121,12 +121,12 @@ There are multiple ways on how to add new translations to Locize:
### Process one: Let Locize add missing keys automatically
When running the app, it will automatically add the missing translation
keys to Locize.
keys to Locize.
There you can translate them, and also add the German translation.
### Process two: Add keys manually
You can add the new keys manually to the German locale file in
You can add the new keys manually to the German locale file in
./client/locales/de/translation.json
Then you can run the following command to add the keys to Locize:
@ -149,7 +149,6 @@ The command will add the keys and the German translation to Locize.
Bonus: Use the "i18n ally" plugin in VSCode or IntelliJ to get extract untranslated
texts directly from the code to the translation.json file.
### "_many" plural form in French and Italian
See https://github.com/i18next/i18next/issues/1691#issuecomment-968063348
@ -296,7 +295,6 @@ npm run dev
If you run `npm run dev`, the codegen command will be run automatically in watch mode.
For the `ObjectTypes` on the server, please use the postfix `ObjectType` for types,
like `LearningContentAttendanceCourseObjectType`.

View File

@ -18,13 +18,28 @@ COPY ./server ${APP_HOME}
# define an alias for the specfic python version used in this file.
FROM python:${PYTHON_VERSION} as python
# Setup Supersonic (Cron scheduler for containers)
# https://github.com/aptible/supercronic
ENV SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/v0.2.26/supercronic-linux-amd64 \
SUPERCRONIC=supercronic-linux-amd64 \
SUPERCRONIC_SHA1SUM=7a79496cf8ad899b99a719355d4db27422396735
RUN apt-get update && apt-get install --no-install-recommends -y curl
RUN curl -fsSLO "$SUPERCRONIC_URL" \
&& echo "${SUPERCRONIC_SHA1SUM} ${SUPERCRONIC}" | sha1sum -c - \
&& chmod +x "$SUPERCRONIC" \
&& mv "$SUPERCRONIC" "/usr/local/bin/${SUPERCRONIC}" \
&& ln -s "/usr/local/bin/${SUPERCRONIC}" /usr/local/bin/supercronic
COPY ./compose/django/send_attendance_course_reminders /app/send_attendance_course_reminders
# Python build stage
FROM python as python-build-stage
ARG BUILD_ENVIRONMENT=production
# Install apt packages
RUN apt-get update && apt-get install --no-install-recommends -y \
RUN apt-get install --no-install-recommends -y \
# dependencies for building Python packages
build-essential \
# psycopg2 dependencies
@ -56,7 +71,7 @@ RUN addgroup --system django \
# Install required system dependencies
RUN apt-get update && apt-get install --no-install-recommends -y \
RUN apt-get install --no-install-recommends -y \
# psycopg2 dependencies
libpq-dev \
# Translations dependencies

View File

@ -15,4 +15,9 @@ else
python /app/manage.py migrate
fi
if [[ $IT_APP_ENVIRONMENT != dev* ]]; then
# Start periodic tasks
/usr/local/bin/supercronic -sentry-dsn "$IT_SENTRY_DSN" -split-logs /app/send_attendance_course_reminders &
fi
newrelic-admin run-program gunicorn config.asgi --bind 0.0.0.0:7555 --chdir=/app -k uvicorn.workers.UvicornWorker

View File

@ -0,0 +1 @@
@daily /usr/local/bin/python /app/manage.py send_attendance_course_reminders

View File

@ -636,7 +636,7 @@ EDONIQ_CERTIFICATE = env("IT_EDONIQ_CERTIFICATE", default="")
# Notifications
# django-notifications
DJANGO_NOTIFICATIONS_CONFIG = {"SOFT_DELETE": True}
DJANGO_NOTIFICATIONS_CONFIG = {"SOFT_DELETE": True, "USE_JSONFIELD": True}
NOTIFICATIONS_NOTIFICATION_MODEL = "notify.Notification"
# sendgrid (email notifications)
SENDGRID_API_KEY = env("IT_SENDGRID_API_KEY", default="")

View File

@ -16,7 +16,7 @@ from vbv_lernwelt.core.models import User
from vbv_lernwelt.core.utils import find_first
from vbv_lernwelt.course.models import CourseCompletionStatus, CourseSession
from vbv_lernwelt.course.services import mark_course_completion
from vbv_lernwelt.notify.service import NotificationService
from vbv_lernwelt.notify.service import EmailTemplate, NotificationService
def update_assignment_completion(
@ -144,6 +144,7 @@ def update_assignment_completion(
sender=ac.assignment_user,
course=course_session.course.title,
target_url=ac.get_assignment_evaluation_frontend_url(),
email_template=EmailTemplate.CASEWORK_SUBMITTED,
)
elif completion_status == AssignmentCompletionStatus.EVALUATION_SUBMITTED:
ac.evaluation_submitted_at = timezone.now()
@ -162,6 +163,7 @@ def update_assignment_completion(
sender=evaluation_user,
course=course_session.course.title,
target_url=assignment_frontend_url,
email_template=EmailTemplate.CASEWORK_EVALUATED,
)
ac.completion_status = completion_status.value

View File

@ -105,14 +105,14 @@ def create_test_course(include_uk=True, include_vv=True, with_sessions=False):
location="Handelsschule KV Bern, Zimmer 123, Eigerstrasse 16, 3012 Bern",
trainer="Roland Grossenbacher, roland.grossenbacher@helvetia.ch",
)
tuesday_in_two_weeks = (
datetime.now() + relativedelta(weekday=TU(2)) + relativedelta(weeks=2)
tuesday_in_one_week = (
datetime.now() + relativedelta(weekday=TU) + relativedelta(weeks=1)
)
csac.due_date.start = timezone.make_aware(
tuesday_in_two_weeks.replace(hour=8, minute=30, second=0, microsecond=0)
tuesday_in_one_week.replace(hour=8, minute=30, second=0, microsecond=0)
)
csac.due_date.end = timezone.make_aware(
tuesday_in_two_weeks.replace(hour=17, minute=0, second=0, microsecond=0)
tuesday_in_one_week.replace(hour=17, minute=0, second=0, microsecond=0)
)
csac.due_date.save()

View File

@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _
from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.models import CourseSessionUser
from vbv_lernwelt.notify.service import NotificationService
from vbv_lernwelt.notify.service import EmailTemplate, NotificationService
class FeedbackIntegerField(models.IntegerField):
@ -55,6 +55,7 @@ class FeedbackResponse(models.Model):
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,
)
super(FeedbackResponse, self).save(*args, **kwargs)

View File

@ -25,6 +25,7 @@ from vbv_lernwelt.learnpath.models import (
LearningContentAttendanceCourse,
LearningContentEdoniqTest,
)
from vbv_lernwelt.notify.models import NotificationType
logger = structlog.get_logger(__name__)
@ -248,7 +249,11 @@ def create_or_update_user(
if not user:
# create user
user = User(sso_id=sso_id, email=email, username=email)
user = User(
sso_id=sso_id,
email=email,
username=email,
)
user.email = email
user.sso_id = user.sso_id or sso_id
@ -699,8 +704,9 @@ 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
{**data, "email_notification_types": [str(NotificationType.INFORMATION)]}
)

View File

@ -52,6 +52,7 @@ class CreateOrUpdateStudentTestCase(TestCase):
"Lehrvertragsnummer": "1234",
"Tel. Privat": "079 593 83 43",
"Geburtsdatum": "01.01.2000",
"email_notification_types": ["INFORMATION"],
}
def test_create_student(self):

View File

@ -0,0 +1,65 @@
from datetime import timedelta
import djclick as click
import structlog
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.models import CourseSessionUser
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
from vbv_lernwelt.notify.service import EmailTemplate, NotificationService
logger = structlog.get_logger(__name__)
PRESENCE_COURSE_REMINDER_LEAD_TIME = timedelta(weeks=2)
DATETIME_FORMAT_STR = "%H:%M %d.%m.%Y"
def format_datetime(dt: timezone.datetime) -> str:
return dt.astimezone(timezone.get_current_timezone()).strftime(DATETIME_FORMAT_STR)
def send_notification(
recipient: User, attendance_course: CourseSessionAttendanceCourse
):
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={
"attendance_course": attendance_course.learning_content.title,
"location": attendance_course.location,
"trainer": attendance_course.trainer,
"start": format_datetime(attendance_course.due_date.start),
"end": format_datetime(attendance_course.due_date.end),
},
)
def check_attendance_course():
"""Checks if an attendance course is coming up and sends a reminder to the participants"""
start = timezone.now()
end = timezone.now() + PRESENCE_COURSE_REMINDER_LEAD_TIME
logger.info(
"Querying for attendance courses in specified time range",
start_time=start.strftime(DATETIME_FORMAT_STR),
end_time=end.strftime(DATETIME_FORMAT_STR),
)
attendance_courses = CourseSessionAttendanceCourse.objects.filter(
due_date__start__lte=end,
due_date__start__gte=start,
)
for attendance_course in attendance_courses:
cs_id = attendance_course.course_session.id
csu = CourseSessionUser.objects.filter(course_session_id=cs_id)
for user in csu:
send_notification(user.user, attendance_course)
if not attendance_courses:
logger.info("No attendance courses found")
@click.command()
def command():
check_attendance_course()

View File

@ -1,3 +1,4 @@
from enum import Enum
from typing import Optional
import structlog
@ -11,35 +12,60 @@ from vbv_lernwelt.notify.models import Notification, NotificationType
logger = structlog.get_logger(__name__)
class EmailTemplate(Enum):
"""Enum for the different Sendgrid email templates."""
# VBV - Erinnerung Präsenzkurse
ATTENDANCE_COURSE_REMINDER = {
"de": "d-9af079f98f524d85ac6e4166de3480da",
"it": "d-ab78ddca8a7a46b8afe50aaba3efee81",
"fr": "d-f88d9912e5484e55a879571463e4a166",
}
# VBV - Geleitete Fallarbeit abgegeben
CASEWORK_SUBMITTED = {"de": "d-599f0b35ddcd4fac99314cdf8f5446a2"}
# VBV - Geleitete Fallarbeit bewertet
CASEWORK_EVALUATED = {"de": "d-8c57fa13116b47be8eec95dfaf2aa030"}
# VBV - Neues Feedback für Circle
NEW_FEEDBACK = {"de": "d-40fb94d5149949e7b8e7ddfcf0fcfdde"}
class EmailService:
"""Email service class implemented using the Sendgrid API"""
_sendgrid_client = SendGridAPIClient(setting("SENDGRID_API_KEY"))
@classmethod
def send_email(cls, recipient: User, verb: str, target_url) -> bool:
def send_email(
cls,
recipient: User,
template: EmailTemplate,
template_data: dict,
) -> None:
message = Mail(
from_email="info@iterativ.ch",
from_email="noreply@my.vbv-afa.ch",
to_emails=recipient.email,
subject=f"myVBV - {verb}",
## TODO: Add HTML content.
html_content=f"{verb}: <a href='{target_url}'>Link</a>",
)
try:
cls._sendgrid_client.send(message)
logger.info(f"Successfully sent email to {recipient}", label="email")
return True
except Exception as e:
logger.error(
f"Failed to send email to {recipient}: {e}",
exc_info=True,
label="email",
)
return False
message.template_id = template.value.get(
recipient.language, template.value["de"]
)
message.dynamic_template_data = template_data
cls._sendgrid_client.send(message)
class NotificationService:
@classmethod
def send_user_interaction_notification(
cls, recipient: User, verb: str, sender: User, course: str, target_url: str
cls,
recipient: User,
verb: str,
sender: User,
course: str,
target_url: str,
email_template: EmailTemplate,
template_data: dict = {},
) -> None:
cls._send_notification(
recipient=recipient,
@ -48,11 +74,19 @@ class NotificationService:
course=course,
target_url=target_url,
notification_type=NotificationType.USER_INTERACTION,
email_template=email_template,
template_data=template_data,
)
@classmethod
def send_progress_notification(
cls, recipient: User, verb: str, course: str, target_url: str
cls,
recipient: User,
verb: str,
course: str,
target_url: str,
email_template: EmailTemplate,
template_data: dict = {},
) -> None:
cls._send_notification(
recipient=recipient,
@ -60,17 +94,26 @@ class NotificationService:
course=course,
target_url=target_url,
notification_type=NotificationType.PROGRESS,
email_template=email_template,
template_data=template_data,
)
@classmethod
def send_information_notification(
cls, recipient: User, verb: str, target_url: str
cls,
recipient: User,
verb: str,
target_url: str,
email_template: EmailTemplate,
template_data: dict = {},
) -> None:
cls._send_notification(
recipient=recipient,
verb=verb,
target_url=target_url,
notification_type=NotificationType.INFORMATION,
email_template=email_template,
template_data=template_data,
)
@classmethod
@ -79,42 +122,66 @@ class NotificationService:
recipient: User,
verb: str,
notification_type: NotificationType,
sender: Optional[User] = None,
course: Optional[str] = None,
target_url: Optional[str] = None,
email_template: EmailTemplate,
template_data: dict,
sender: User | None = None,
course: str | None = None,
target_url: str | None = None,
) -> None:
actor_avatar_url: Optional[str] = None
if not sender:
sender = User.objects.get(email="admin")
else:
actor_avatar_url = sender.avatar_url
emailed = False
if cls._should_send_email(notification_type, recipient):
emailed = cls._send_email(recipient, verb, target_url)
log = logger.bind(
recipient=recipient.get_full_name(),
sender=sender.get_full_name(),
verb=verb,
notification_type=notification_type,
sender=sender.get_full_name(),
recipient=recipient.get_full_name(),
course=course,
target_url=target_url,
template_data=template_data,
)
if NotificationService._is_duplicate_notification(
recipient=recipient,
verb=verb,
notification_type=notification_type,
target_url=target_url,
template_name=email_template.name,
template_data=template_data,
):
log.warn("A duplicate notification was omitted from being sent")
return
emailed = False
if cls._should_send_email(notification_type, recipient):
emailed = cls._send_email(
recipient=recipient,
template=email_template,
template_data={
"target_url": f"https://my.vbv-afa.ch{target_url}",
**template_data,
},
)
try:
response = notify.send(
sender=sender,
recipient=recipient,
verb=verb,
emailed=emailed,
# The metadata is saved in the 'data' member of the AbstractNotification model
email_template=email_template.name,
template_data=template_data,
)
sent_notification: Notification = response[0][1][0]
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.actor_avatar_url = actor_avatar_url
sent_notification.emailed = emailed
sent_notification.save()
except Exception as e:
log.bind(exception=e)
log.error("Failed to send notification")
log.error("Failed to send notification", exception=str(e))
else:
log.info("Notification sent successfully")
@ -127,13 +194,49 @@ class NotificationService:
)
@staticmethod
def _send_email(recipient: User, verb: str, target_url: Optional[str]) -> bool:
def _send_email(
recipient: User,
template: EmailTemplate,
template_data: dict | None,
) -> bool:
log = logger.bind(
recipient=recipient.username,
template=template.name,
template_data=template_data,
)
try:
return EmailService.send_email(
EmailService.send_email(
recipient=recipient,
verb=verb,
target_url=target_url,
template=template,
template_data=template_data,
)
log.info("Email sent successfully")
return True
except Exception as e:
logger.error(f"Failed to send email to {recipient}: {e}")
log.error(
"Failed to send Email", exception=str(e), exc_info=True, stack_info=True
)
return False
@staticmethod
def _is_duplicate_notification(
recipient: User,
verb: str,
notification_type: NotificationType,
template_name: str,
template_data: dict,
target_url: str | None,
) -> bool:
"""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.
"""
return Notification.objects.filter(
recipient=recipient,
verb=verb,
notification_type=notification_type,
target_url=target_url,
data={
"email_template": template_name,
"template_data": template_data,
},
).exists()

View File

@ -0,0 +1,125 @@
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 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_session.models import CourseSessionAttendanceCourse
from vbv_lernwelt.learnpath.models import LearningContentAttendanceCourse
from vbv_lernwelt.notify.management.commands.send_attendance_course_reminders import (
check_attendance_course,
)
from vbv_lernwelt.notify.service import EmailTemplate
@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.service.NotificationService.send_information_notification",
on_send_notification,
)
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",
)
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)
)
self.csac.due_date.end = timezone.make_aware(
in_one_week.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",
)
in_two_weeks = datetime.now() + timedelta(weeks=2, days=1)
self.csac_future.due_date.start = timezone.make_aware(
in_two_weeks.replace(hour=5, minute=20, second=0, microsecond=0)
)
self.csac_future.due_date.end = timezone.make_aware(
in_two_weeks.replace(hour=15, minute=20, second=0, microsecond=0)
)
self.csac_future.due_date.save()
def test_happy_day(self):
check_attendance_course()
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)),
)
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("%H:%M %d.%m.%Y"),
notification.template_data["start"],
)
self.assertEquals(
self.csac.due_date.end.strftime("%H:%M %d.%m.%Y"),
notification.template_data["end"],
)

View File

@ -1,18 +1,25 @@
import json
from typing import List
from django.test import TestCase
from vbv_lernwelt.core.models import User
from vbv_lernwelt.core.tests.factories import UserFactory
from vbv_lernwelt.notify.models import Notification, NotificationType
from vbv_lernwelt.notify.service import NotificationService
from vbv_lernwelt.notify.service import EmailTemplate, NotificationService
class TestNotificationService(TestCase):
def _on_send_email(self, *_, **__) -> bool:
self._emails_sent += 1
return True
def _has_sent_emails(self) -> bool:
return self._emails_sent != 0
def setUp(self) -> None:
self._emails_sent = 0
self.notification_service = NotificationService
self.notification_service._send_email = lambda a, b, c: True
self.notification_service._send_email = self._on_send_email
self.admin = UserFactory(username="admin", email="admin")
self.sender_username = "Bob"
@ -36,11 +43,10 @@ class TestNotificationService(TestCase):
recipient=self.recipient,
verb=verb,
target_url=target_url,
email_template=EmailTemplate.ATTENDANCE_COURSE_REMINDER,
)
notifications: List[Notification] = Notification.objects.all()
self.assertEqual(1, len(notifications))
notification = notifications[0]
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)
@ -54,12 +60,14 @@ class TestNotificationService(TestCase):
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
recipient=self.recipient,
verb=verb,
target_url=target_url,
course=course,
email_template=EmailTemplate.ATTENDANCE_COURSE_REMINDER,
)
notifications: List[Notification] = Notification.objects.all()
self.assertEqual(1, len(notifications))
notification = notifications[0]
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)
@ -77,11 +85,10 @@ class TestNotificationService(TestCase):
verb=verb,
target_url=target_url,
course=course,
email_template=EmailTemplate.ATTENDANCE_COURSE_REMINDER,
)
notifications: List[Notification] = Notification.objects.all()
self.assertEqual(1, len(notifications))
notification = notifications[0]
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)
@ -90,3 +97,68 @@ class TestNotificationService(TestCase):
str(NotificationType.USER_INTERACTION), notification.notification_type
)
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",
},
)
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)
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(
sender=self.sender,
recipient=self.recipient,
verb="should not be sent",
target_url="",
course="",
email_template=EmailTemplate.CASEWORK_EVALUATED,
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.save()
self.notification_service.send_user_interaction_notification(
sender=self.sender,
recipient=self.recipient,
verb="should be sent",
target_url="",
course="",
email_template=EmailTemplate.CASEWORK_EVALUATED,
template_data={},
)
self.assertEqual(2, Notification.objects.count())
self.assertTrue(self._has_sent_emails())

View File

@ -11,5 +11,12 @@
"img base64 content": "regex:data:image/png;base64,.*",
"sentry url": "https://2df6096a4fd94bd6b4802124d10e4b8d@o8544.ingest.sentry.io/4504157846372352",
"git commit": "bdadf52b849bb5fa47854a3094f4da6fe9d54d02",
"customDomainVerificationId": "A2AB57353045150ADA4488FAA8AA9DFBBEDDD311934653F55243B336C2F3358E"
"customDomainVerificationId": "A2AB57353045150ADA4488FAA8AA9DFBBEDDD311934653F55243B336C2F3358E",
"SENDGRID_TEMPLATE_ATTENDANCE_COURSE_REMINDER_DE": "d-9af079f98f524d85ac6e4166de3480da",
"SENDGRID_TEMPLATE_ATTENDANCE_COURSE_REMINDER_FR": "d-f88d9912e5484e55a879571463e4a166",
"SENDGRID_TEMPLATE_ATTENDANCE_COURSE_REMINDER_IT": "d-ab78ddca8a7a46b8afe50aaba3efee81",
"SENDGRID_TEMPLATE_CASEWORK_SUBMITTED_DE": "d-599f0b35ddcd4fac99314cdf8f5446a2",
"SENDGRID_TEMPLATE_CASEWORK_EVALUATED_DE": "d-8c57fa13116b47be8eec95dfaf2aa030",
"SENDGRID_TEMPLATE_NEW_FEEDBACK_DE": "d-40fb94d5149949e7b8e7ddfcf0fcfdde",
"SUPERCRONIC_SHA1SUM": "7a79496cf8ad899b99a719355d4db27422396735"
}