From 052ddfba8dc7abf896b985f2412d574401290fef Mon Sep 17 00:00:00 2001 From: Christian Cueni Date: Tue, 31 Oct 2023 08:14:19 +0100 Subject: [PATCH 1/4] Send reminders only to related experts --- .../course_session/tests/test_attendance.py | 46 ++++++++++++++++++- .../notify/email/reminders/attendance.py | 17 ++++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/server/vbv_lernwelt/course_session/tests/test_attendance.py b/server/vbv_lernwelt/course_session/tests/test_attendance.py index 347d1795..b9782167 100644 --- a/server/vbv_lernwelt/course_session/tests/test_attendance.py +++ b/server/vbv_lernwelt/course_session/tests/test_attendance.py @@ -3,14 +3,23 @@ from django.test import TestCase from vbv_lernwelt.core.constants import TEST_STUDENT1_USER_ID from vbv_lernwelt.core.create_default_users import create_default_users from vbv_lernwelt.core.models import User -from vbv_lernwelt.course.creators.test_course import create_test_course -from vbv_lernwelt.course.models import CourseCompletion, CourseSession +from vbv_lernwelt.course.creators.test_course import ( + create_test_course, + create_test_uk_circle_fahrzeug, +) +from vbv_lernwelt.course.models import ( + CourseCompletion, + CourseSession, + CourseSessionUser, +) from vbv_lernwelt.course.services import mark_course_completion from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse from vbv_lernwelt.course_session.services.attendance import ( AttendanceUserStatus, update_attendance_list, ) +from vbv_lernwelt.learnpath.models import Circle, LearningPath +from vbv_lernwelt.notify.email.reminders.attendance import get_recipients class AttendanceServicesTestCase(TestCase): @@ -88,3 +97,36 @@ class AttendanceServicesTestCase(TestCase): self.assertEqual(cc.user, student) self.assertEqual(cc.completion_status, "FAIL") self.assertEqual(cc.page_id, self.attendance_course.learning_content.id) + + +class AttendanceReminderTestCase(TestCase): + def setUp(self): + create_default_users() + create_test_course(include_vv=False, with_sessions=True) + self.course_session = CourseSession.objects.get(title="Test Bern 2022 a") + self.attendance_course = ( + self.course_session.coursesessionattendancecourse_set.first() + ) + self.trainer = User.objects.get(username="test-trainer1@example.com") + self.other_circle_title = "Something different" + lp = LearningPath.objects.get(title="Test Lernpfad") + create_test_uk_circle_fahrzeug(lp, title=self.other_circle_title) + csu = CourseSessionUser.objects.get(user=self.trainer) + fahrzeug = Circle.objects.get(title="Fahrzeug") + csu.expert.add(fahrzeug) + + def test_reminderOnlySendsToMembersAndRelevantExperts(self): + # promote user to expert, but in another circle + csu = CourseSessionUser.objects.get(user__email="test-student3@example.com") + other_circle = Circle.objects.get(title=self.other_circle_title) + csu.role = CourseSessionUser.Role.EXPERT + csu.save() + csu.expert.add(other_circle) + + expected_csu = { + "test-student1@example.com", + "test-student2@example.com", + "test-trainer1@example.com", + } + csus = get_recipients(self.attendance_course) + self.assertEqual(set([u.user.email for u in csus]), expected_csu) diff --git a/server/vbv_lernwelt/notify/email/reminders/attendance.py b/server/vbv_lernwelt/notify/email/reminders/attendance.py index aa97081d..e76f3ba5 100644 --- a/server/vbv_lernwelt/notify/email/reminders/attendance.py +++ b/server/vbv_lernwelt/notify/email/reminders/attendance.py @@ -2,10 +2,12 @@ from collections import Counter from datetime import timedelta import structlog +from django.db.models import QuerySet from django.utils import timezone from vbv_lernwelt.course.models import CourseSessionUser from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse +from vbv_lernwelt.learnpath.models import Circle from vbv_lernwelt.notify.services import NotificationService logger = structlog.get_logger(__name__) @@ -32,7 +34,7 @@ def send_attendance_reminder_notifications(): ) for attendance_course in attendance_courses: cs_id = attendance_course.course_session.id - csu = CourseSessionUser.objects.filter(course_session_id=cs_id) + csu = get_recipients(attendance_course) logger.info( "Sending attendance course reminder notification", start_time=start.isoformat(), @@ -52,3 +54,16 @@ def send_attendance_reminder_notifications(): label="attendance_course_reminder_notification_job", ) return dict(results_counter) + + +def get_recipients( + attendance_course: CourseSessionAttendanceCourse, +) -> QuerySet["CourseSessionUser"]: + cs_id = attendance_course.course_session.id + circle_page = attendance_course.learning_content.get_parent_circle() + circle = Circle.objects.get(page_ptr=circle_page.id) + experts = circle.expert.all() + members = CourseSessionUser.objects.filter( + course_session_id=cs_id, role=CourseSessionUser.Role.MEMBER + ) + return members | experts From 4776206bb84b4f00a473591fe7ff2c7d78576b4b Mon Sep 17 00:00:00 2001 From: Reto Aebersold Date: Wed, 1 Nov 2023 14:27:53 +0100 Subject: [PATCH 2/4] feat: Rich-text component for ext. link handling --- .../components/mediaLibrary/OverviewCard.vue | 5 +-- client/src/components/ui/RichText.vue | 32 +++++++++++++++++++ .../EvaluationSummary.vue | 20 +++++++----- .../EvaluationTask.vue | 8 ++--- .../circlePage/CircleOverview.vue | 9 ++---- .../LearningContentPage.vue | 20 +----------- .../assignment/AssignmentIntroductionView.vue | 20 ++++++------ .../AssignmentSubmissionResponses.vue | 11 ++++--- .../assignment/AssignmentTaskView.vue | 7 +++- .../blocks/PlaceholderBlock.vue | 10 +++--- .../blocks/RichTextBlock.vue | 15 ++++----- .../mediaLibrary/MediaLibraryContentPage.vue | 21 ++++++------ client/src/utils/dom.ts | 14 ++++++++ 13 files changed, 113 insertions(+), 79 deletions(-) create mode 100644 client/src/components/ui/RichText.vue create mode 100644 client/src/utils/dom.ts diff --git a/client/src/components/mediaLibrary/OverviewCard.vue b/client/src/components/mediaLibrary/OverviewCard.vue index fe592f57..0edda822 100644 --- a/client/src/components/mediaLibrary/OverviewCard.vue +++ b/client/src/components/mediaLibrary/OverviewCard.vue @@ -1,4 +1,6 @@ + + diff --git a/client/src/pages/cockpit/assignmentEvaluationPage/EvaluationSummary.vue b/client/src/pages/cockpit/assignmentEvaluationPage/EvaluationSummary.vue index e0de6449..52e20e80 100644 --- a/client/src/pages/cockpit/assignmentEvaluationPage/EvaluationSummary.vue +++ b/client/src/pages/cockpit/assignmentEvaluationPage/EvaluationSummary.vue @@ -16,6 +16,7 @@ import { useMutation } from "@urql/vue"; import dayjs, { Dayjs } from "dayjs"; import * as log from "loglevel"; import { computed, reactive } from "vue"; +import RichText from "@/components/ui/RichText.vue"; const props = defineProps<{ assignmentUser: CourseSessionUser; @@ -188,10 +189,11 @@ const evaluationUser = computed(() => { -
+
{ subTaskByPoints(task, evaluationForTask(task).points)?.value.title " >
-

+ open-links-in-new-tab + /> +
{{ evaluationForTask(task).points }} Punkte
diff --git a/client/src/pages/cockpit/assignmentEvaluationPage/EvaluationTask.vue b/client/src/pages/cockpit/assignmentEvaluationPage/EvaluationTask.vue index f2c273dc..7beda376 100644 --- a/client/src/pages/cockpit/assignmentEvaluationPage/EvaluationTask.vue +++ b/client/src/pages/cockpit/assignmentEvaluationPage/EvaluationTask.vue @@ -13,6 +13,7 @@ import { useMutation } from "@urql/vue"; import { useDebounceFn } from "@vueuse/core"; import * as log from "loglevel"; import { computed } from "vue"; +import RichText from "@/components/ui/RichText.vue"; const props = defineProps<{ assignmentUser: CourseSessionUser; @@ -114,11 +115,10 @@ const evaluateAssignmentCompletionDebounced = useDebounceFn( /> diff --git a/client/src/pages/learningPath/circlePage/CircleOverview.vue b/client/src/pages/learningPath/circlePage/CircleOverview.vue index fe468913..b822194e 100644 --- a/client/src/pages/learningPath/circlePage/CircleOverview.vue +++ b/client/src/pages/learningPath/circlePage/CircleOverview.vue @@ -1,6 +1,7 @@