diff --git a/client/src/components/learningMentor/MyMentees.vue b/client/src/components/learningMentor/MyMentees.vue index 5f22d2cc..c90a57ce 100644 --- a/client/src/components/learningMentor/MyMentees.vue +++ b/client/src/components/learningMentor/MyMentees.vue @@ -8,9 +8,9 @@ import LoadingSpinner from "@/components/ui/LoadingSpinner.vue"; const courseSession = useCurrentCourseSession(); const { isLoading, summary, fetchData } = useLearningMentees(courseSession.value.id); -const removeMyMentee = async (menteeId: string) => { +const removeMyMentee = async (relationId: string) => { await useCSRFFetch( - `/api/mentor/${courseSession.value.id}/mentors/${summary.value?.mentor_id}/remove/${menteeId}` + `/api/mentor/${courseSession.value.id}/mentors/${relationId}/delete` ).delete(); fetchData(); }; @@ -28,25 +28,31 @@ const noMenteesText = computed(() =>

{{ $t("a.Personen, die du begleitest") }}

-
+
- {{ participant.first_name }} - {{ participant.last_name }} + {{ relation.participant_user.first_name }} + {{ relation.participant_user.last_name }}
- {{ participant.email }} + {{ relation.participant_user.email }}
@@ -55,7 +61,7 @@ const noMenteesText = computed(() => :to="{ name: 'profileLearningPath', params: { - userId: participant.id, + userId: relation.participant_user.id, courseSlug: courseSession.course.slug, }, }" @@ -66,7 +72,7 @@ const noMenteesText = computed(() => diff --git a/client/src/components/learningMentor/MyMentors.vue b/client/src/components/learningMentor/MyMentors.vue index 5988653b..85941cc6 100644 --- a/client/src/components/learningMentor/MyMentors.vue +++ b/client/src/components/learningMentor/MyMentors.vue @@ -51,11 +51,9 @@ const removeInvitation = async (invitationId: string) => { await refreshInvitations(); }; -const userStore = useUserStore(); - -const removeMyMentor = async (mentorId: string) => { +const removeMyMentor = async (relationId: string) => { await useCSRFFetch( - `/api/mentor/${courseSession.value.id}/mentors/${mentorId}/remove/${userStore.id}` + `/api/mentor/${courseSession.value.id}/mentors/${relationId}/delete` ).delete(); await refreshMentors(); }; @@ -138,16 +136,16 @@ const noLearningMentors = computed(() => >
- {{ learningMentor.mentor.first_name }} - {{ learningMentor.mentor.last_name }} + {{ learningMentor.agent.first_name }} + {{ learningMentor.agent.last_name }}
- {{ learningMentor.mentor.email }} + {{ learningMentor.agent.email }}
diff --git a/client/src/composables.ts b/client/src/composables.ts index 15ecb645..284ed4fa 100644 --- a/client/src/composables.ts +++ b/client/src/composables.ts @@ -39,7 +39,7 @@ import type { CourseSessionDetail, DashboardPersonsPageMode, LearningContentWithCompletion, - LearningMentor, + AgentParticipantRelation, LearningPathType, LearningUnitPerformanceCriteria, PerformanceCriteria, @@ -489,7 +489,7 @@ export function useFileUpload() { } export function useMyLearningMentors() { - const learningMentors = ref([]); + const learningMentors = ref([]); const currentCourseSessionId = useCurrentCourseSession().value.id; const loading = ref(false); diff --git a/client/src/pages/learningMentor/mentor/MentorPraxisAssignmentPage.vue b/client/src/pages/learningMentor/mentor/MentorPraxisAssignmentPage.vue index 2203454f..306b351c 100644 --- a/client/src/pages/learningMentor/mentor/MentorPraxisAssignmentPage.vue +++ b/client/src/pages/learningMentor/mentor/MentorPraxisAssignmentPage.vue @@ -1,5 +1,5 @@ diff --git a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationRequestFeedbackPage.vue b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationRequestFeedbackPage.vue index 9b8407cb..1c05a0df 100644 --- a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationRequestFeedbackPage.vue +++ b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationRequestFeedbackPage.vue @@ -29,8 +29,8 @@ const isMentorsLoading = computed(() => learningMentors.loading.value); const mentors = computed(() => { return learningMentors.learningMentors.value.map((mentor) => ({ - id: mentor.mentor.id, - name: `${mentor.mentor.first_name} ${mentor.mentor.last_name}`, + id: mentor.agent.id, + name: `${mentor.agent.first_name} ${mentor.agent.last_name}`, })); }); diff --git a/client/src/services/learningMentees.ts b/client/src/services/learningMentees.ts index 6550056f..0809fdba 100644 --- a/client/src/services/learningMentees.ts +++ b/client/src/services/learningMentees.ts @@ -2,7 +2,7 @@ import { itGet } from "@/fetchHelpers"; import type { Ref } from "vue"; import { ref, watchEffect } from "vue"; -export interface Participant { +export interface UserShort { id: string; first_name: string; last_name: string; @@ -12,6 +12,14 @@ export interface Participant { language: string; } +export interface AgentParticipantRelation { + id: string; + role: "LEARNING_MENTOR"; + course_session_id: number; + agent: UserShort; + participant_user: UserShort; +} + interface Circle { id: number; title: string; @@ -40,9 +48,8 @@ export interface Assignment { type: string; } -export interface Summary { - mentor_id: string; - participants: Participant[]; +export interface LearningMentorSummary { + participant_relations: AgentParticipantRelation[]; circles: Circle[]; assignments: Assignment[]; } @@ -51,7 +58,7 @@ export const useLearningMentees = ( courseSessionId: string | Ref | (() => string) ) => { const isLoading = ref(false); - const summary: Ref = ref(null); + const summary: Ref = ref(null); const error = ref(null); const getAssignmentById = (id: string): Assignment | null => { diff --git a/client/src/types.ts b/client/src/types.ts index afdf8556..f37d93c7 100644 --- a/client/src/types.ts +++ b/client/src/types.ts @@ -468,15 +468,16 @@ export interface ExpertSessionUser extends CourseSessionUser { role: "EXPERT"; } -export interface Mentor { +export interface Agent { id: number; first_name: string; last_name: string; } -export interface LearningMentor { +export interface AgentParticipantRelation { id: number; - mentor: Mentor; + role: "LEARNING_MENTOR"; + agent: Agent; } export type CourseSessionDetail = CourseSessionObjectType; diff --git a/scripts/send_sendgrid_email.py b/scripts/send_sendgrid_email.py index 012ea5f8..07c5dbc9 100644 --- a/scripts/send_sendgrid_email.py +++ b/scripts/send_sendgrid_email.py @@ -39,9 +39,9 @@ def send_learning_mentor_invitation(): recipient_email="daniel.egger+sendgrid@gmail.com", template=EmailTemplate.LEARNING_MENTOR_INVITATION, template_data={ - "inviter_name": f"Daniel Egger", + "inviter_name": "Daniel Egger", "inviter_email": "daniel.egger@example.com", - "target_url": f"https://stage.vbv-afa.ch/foobar", + "target_url": "https://stage.vbv-afa.ch/foobar", }, template_language="de", fail_silently=True, diff --git a/server/vbv_lernwelt/core/management/commands/cypress_reset.py b/server/vbv_lernwelt/core/management/commands/cypress_reset.py index eb46bc5c..0c47aba5 100644 --- a/server/vbv_lernwelt/core/management/commands/cypress_reset.py +++ b/server/vbv_lernwelt/core/management/commands/cypress_reset.py @@ -43,7 +43,10 @@ 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 from vbv_lernwelt.feedback.models import FeedbackResponse -from vbv_lernwelt.learning_mentor.models import LearningMentor, MentorInvitation +from vbv_lernwelt.learning_mentor.models import ( + AgentParticipantRelation, + MentorInvitation, +) from vbv_lernwelt.learnpath.models import ( LearningContentAttendanceCourse, LearningContentFeedbackUK, @@ -144,7 +147,8 @@ def command( SelfEvaluationFeedback.objects.all().delete() CourseCompletionFeedback.objects.all().delete() - LearningMentor.objects.all().delete() + AgentParticipantRelation.objects.all().delete() + # LearningMentor.objects.all().delete() MentorInvitation.objects.all().delete() User.objects.all().update(organisation=Organisation.objects.first()) User.objects.all().update(language="de") @@ -414,48 +418,40 @@ def command( if create_learning_mentor: cs_bern = CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID) - - uk_mentor = LearningMentor.objects.create( - mentor=User.objects.get(id=TEST_MENTOR1_USER_ID), - course_session=cs_bern, - ) - uk_mentor.participants.add( - CourseSessionUser.objects.get( - user__id=TEST_STUDENT1_USER_ID, - course_session=cs_bern, - ) + AgentParticipantRelation.objects.create( + agent=User.objects.get(id=TEST_MENTOR1_USER_ID), + participant=CourseSessionUser.objects.get( + user__id=TEST_STUDENT1_USER_ID, course_session=cs_bern + ), + role="LEARNING_MENTOR", ) vv_course = Course.objects.get(id=COURSE_VERSICHERUNGSVERMITTLERIN_ID) vv_course_session = CourseSession.objects.get(course=vv_course) - vv_mentor = LearningMentor.objects.create( - mentor=User.objects.get(id=TEST_MENTOR1_USER_ID), - course_session=vv_course_session, - ) - vv_mentor.participants.add( - CourseSessionUser.objects.get( - user__id=TEST_STUDENT1_VV_USER_ID, course_session=vv_course_session - ) - ) - - vv_mentor.participants.add( - CourseSessionUser.objects.get( - user__id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID, - course_session=vv_course_session, - ) - ) - - vv_student_and_mentor = LearningMentor.objects.create( - mentor=User.objects.get(id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID), - course_session=vv_course_session, - ) - - vv_student_and_mentor.participants.add( - CourseSessionUser.objects.get( + AgentParticipantRelation.objects.create( + agent=User.objects.get(id=TEST_MENTOR1_USER_ID), + participant=CourseSessionUser.objects.get( user__id=TEST_STUDENT1_VV_USER_ID, course_session=vv_course_session, - ) + ), + role="LEARNING_MENTOR", + ) + AgentParticipantRelation.objects.create( + agent=User.objects.get(id=TEST_MENTOR1_USER_ID), + participant=CourseSessionUser.objects.get( + user__id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID, + course_session=vv_course_session, + ), + role="LEARNING_MENTOR", + ) + AgentParticipantRelation.objects.create( + agent=User.objects.get(id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID), + participant=CourseSessionUser.objects.get( + user__id=TEST_STUDENT1_VV_USER_ID, + course_session=vv_course_session, + ), + role="LEARNING_MENTOR", ) course = Course.objects.get(id=COURSE_TEST_ID) diff --git a/server/vbv_lernwelt/core/management/commands/reset_iterativ_test_sessions.py b/server/vbv_lernwelt/core/management/commands/reset_iterativ_test_sessions.py index d9afab70..ce007a79 100644 --- a/server/vbv_lernwelt/core/management/commands/reset_iterativ_test_sessions.py +++ b/server/vbv_lernwelt/core/management/commands/reset_iterativ_test_sessions.py @@ -24,7 +24,10 @@ from vbv_lernwelt.course_session.models import ( ) from vbv_lernwelt.course_session_group.models import CourseSessionGroup from vbv_lernwelt.feedback.models import FeedbackResponse -from vbv_lernwelt.learning_mentor.models import LearningMentor +from vbv_lernwelt.learning_mentor.models import ( + AgentParticipantRelation, + AgentParticipantRoleType, +) from vbv_lernwelt.learnpath.models import Circle from vbv_lernwelt.notify.models import Notification @@ -71,7 +74,7 @@ def create_or_update_uk(language="de"): cs = CourseSession.objects.get(import_id=data["ID"]) members, trainer, regionenleiter = get_or_create_users_uk() - delete_cs_data(cs, members + [trainer, regionenleiter]) + delete_cs_data(cs) add_to_course_session(cs, members) add_trainers_to_course_session(cs, [trainer], uk_circle_keys, language) @@ -89,13 +92,13 @@ def create_or_update_vv(language="de"): create_or_update_assignment_course_session(cs) members, member_with_mentor, mentor = get_or_create_users_vv() - delete_cs_data(cs, members + [member_with_mentor, mentor]) + delete_cs_data(cs) add_to_course_session(cs, members + [member_with_mentor]) add_mentor_to_course_session(cs, [(mentor, member_with_mentor)]) -def delete_cs_data(cs: CourseSession, users: list[User]): +def delete_cs_data(cs: CourseSession): if cs: CourseCompletion.objects.filter(course_session=cs).delete() Notification.objects.filter(course_session=cs).delete() @@ -105,16 +108,8 @@ def delete_cs_data(cs: CourseSession, users: list[User]): ) CourseSessionEdoniqTest.objects.filter(course_session=cs).delete() CourseSessionUser.objects.filter(course_session=cs).delete() - learning_mentor_ids = ( - LearningMentor.objects.filter(participants__course_session=cs) - .values_list("id", flat=True) - .distinct() - | LearningMentor.objects.filter(mentor__in=users) - .values_list("id", flat=True) - .distinct() - ) - # cannot call delete on distinct objects - LearningMentor.objects.filter(id__in=list(learning_mentor_ids)).delete() + + AgentParticipantRelation.objects.filter(course_session=cs).delete() else: logger.info("no_course_session_found", import_id=cs.import_id) @@ -138,15 +133,12 @@ def add_mentor_to_course_session( course_session: CourseSession, mentor_mentee_pairs: list[tuple[User, User]] ): for mentor, mentee in mentor_mentee_pairs: - lm = LearningMentor.objects.create( - course_session=course_session, - mentor=mentor, - ) - lm.participants.add( - CourseSessionUser.objects.get( - user__id=mentee.id, - course_session=course_session, - ) + AgentParticipantRelation.objects.create( + agent=mentor, + participant=CourseSessionUser.objects.get( + user__id=mentee.id, course_session=course_session + ), + role=AgentParticipantRoleType.LEARNING_MENTOR.value, ) diff --git a/server/vbv_lernwelt/core/serializers.py b/server/vbv_lernwelt/core/serializers.py index 31965017..5aea54d8 100644 --- a/server/vbv_lernwelt/core/serializers.py +++ b/server/vbv_lernwelt/core/serializers.py @@ -131,6 +131,20 @@ class UserSerializer(serializers.ModelSerializer): return instance +class UserShortSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = [ + "id", + "first_name", + "last_name", + "email", + "username", + "avatar_url", + "language", + ] + + class CypressUserSerializer(serializers.ModelSerializer): class Meta: model = User diff --git a/server/vbv_lernwelt/course/creators/test_utils.py b/server/vbv_lernwelt/course/creators/test_utils.py index d5c65682..6b2e6238 100644 --- a/server/vbv_lernwelt/course/creators/test_utils.py +++ b/server/vbv_lernwelt/course/creators/test_utils.py @@ -41,7 +41,10 @@ from vbv_lernwelt.course_session.models import ( ) from vbv_lernwelt.course_session_group.models import CourseSessionGroup from vbv_lernwelt.duedate.models import DueDate -from vbv_lernwelt.learning_mentor.models import LearningMentor +from vbv_lernwelt.learning_mentor.models import ( + AgentParticipantRelation, + AgentParticipantRoleType, +) from vbv_lernwelt.learnpath.models import ( Circle, LearningContentAssignment, @@ -101,13 +104,13 @@ def create_course_session( def add_learning_mentor( - course_session: CourseSession, mentor: User, mentee: CourseSessionUser -) -> LearningMentor: - learning_mentor = LearningMentor.objects.create( - course_session=course_session, mentor=mentor + mentor: User, mentee: CourseSessionUser +) -> AgentParticipantRelation: + return AgentParticipantRelation.objects.create( + agent=mentor, + participant=mentee, + role=AgentParticipantRoleType.LEARNING_MENTOR.value, ) - learning_mentor.participants.add(mentee) - return learning_mentor def add_course_session_user( diff --git a/server/vbv_lernwelt/course/management/commands/create_default_courses.py b/server/vbv_lernwelt/course/management/commands/create_default_courses.py index 2798f21e..8ebfc82d 100644 --- a/server/vbv_lernwelt/course/management/commands/create_default_courses.py +++ b/server/vbv_lernwelt/course/management/commands/create_default_courses.py @@ -45,10 +45,7 @@ from vbv_lernwelt.competence.create_vv_new_competence_profile import ( create_vv_new_competence_profile, ) from vbv_lernwelt.competence.models import PerformanceCriteria -from vbv_lernwelt.core.constants import ( - TEST_MENTOR1_USER_ID, - TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID, -) +from vbv_lernwelt.core.constants import TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID from vbv_lernwelt.core.create_default_users import default_users from vbv_lernwelt.core.models import User from vbv_lernwelt.course.consts import ( @@ -95,7 +92,6 @@ from vbv_lernwelt.importer.services import ( import_students_from_excel, import_trainers_from_excel_for_training, ) -from vbv_lernwelt.learning_mentor.models import LearningMentor from vbv_lernwelt.learnpath.create_vv_new_learning_path import ( create_vv_motorfahrzeug_pruefung_learning_path, create_vv_new_learning_path, @@ -243,6 +239,11 @@ def create_versicherungsvermittlerin_course( course_session=cs, user=User.objects.get(username="student-vv@eiger-versicherungen.ch"), ) + mentor_and_student_2_learning_csu = CourseSessionUser.objects.create( + course_session=cs, + user=User.objects.get(id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID), + role=CourseSessionUser.Role.MEMBER, + ) CourseSessionUser.objects.create( course_session=cs, @@ -262,30 +263,6 @@ def create_versicherungsvermittlerin_course( role=CourseSessionUser.Role.EXPERT, ) - mentor_and_student_2_learning_csu = CourseSessionUser.objects.create( - course_session=cs, - user=User.objects.get(id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID), - role=CourseSessionUser.Role.MEMBER, - ) - - # TEST_MENTOR1_USER_ID is only mentor - just_mentor = LearningMentor.objects.create( - mentor=User.objects.get(id=TEST_MENTOR1_USER_ID), - course_session=cs, - ) - - just_mentor.participants.add(student_1_csu) - just_mentor.participants.add(mentor_and_student_2_learning_csu) - - # TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID is both student and mentor - - mentor_and_student_learning_mentor = LearningMentor.objects.create( - mentor=User.objects.get(id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID), - course_session=cs, - ) - - mentor_and_student_learning_mentor.participants.add(student_1_csu) - for admin_email in ADMIN_EMAILS: CourseSessionUser.objects.create( course_session=cs, diff --git a/server/vbv_lernwelt/course/migrations/0009_alter_coursecompletion_completion_status.py b/server/vbv_lernwelt/course/migrations/0009_alter_coursecompletion_completion_status.py new file mode 100644 index 00000000..2c3fcb8f --- /dev/null +++ b/server/vbv_lernwelt/course/migrations/0009_alter_coursecompletion_completion_status.py @@ -0,0 +1,27 @@ +# Generated by Django 3.2.25 on 2024-07-17 14:53 + +from django.db import migrations, models + +import vbv_lernwelt.course.models + + +class Migration(migrations.Migration): + dependencies = [ + ("course", "0008_auto_20240403_1132"), + ] + + operations = [ + migrations.AlterField( + model_name="coursecompletion", + name="completion_status", + field=models.CharField( + choices=[ + ("SUCCESS", "Success"), + ("FAIL", "Fail"), + ("UNKNOWN", "Unknown"), + ], + default=vbv_lernwelt.course.models.CourseCompletionStatus["UNKNOWN"], + max_length=255, + ), + ), + ] diff --git a/server/vbv_lernwelt/course/views.py b/server/vbv_lernwelt/course/views.py index 6d1a9791..090b5c1e 100644 --- a/server/vbv_lernwelt/course/views.py +++ b/server/vbv_lernwelt/course/views.py @@ -27,7 +27,7 @@ from vbv_lernwelt.iam.permissions import ( has_course_access_by_page_request, is_circle_expert, ) -from vbv_lernwelt.learning_mentor.models import LearningMentor +from vbv_lernwelt.learning_mentor.models import AgentParticipantRelation logger = structlog.get_logger(__name__) @@ -155,9 +155,10 @@ def get_course_sessions(request): # enrich with mentor course sessions mentor_course_sessions = CourseSession.objects.filter( - id__in=LearningMentor.objects.filter(mentor=request.user).values_list( - "course_session", flat=True - ) + id__in=[ + rel.participant.course_session_id + for rel in AgentParticipantRelation.objects.filter(agent=request.user) + ] ).prefetch_related("course") all_to_serialize = ( diff --git a/server/vbv_lernwelt/dashboard/graphql/queries.py b/server/vbv_lernwelt/dashboard/graphql/queries.py index e508fb8c..330c39a8 100644 --- a/server/vbv_lernwelt/dashboard/graphql/queries.py +++ b/server/vbv_lernwelt/dashboard/graphql/queries.py @@ -25,7 +25,10 @@ from vbv_lernwelt.iam.permissions import ( can_view_course_session_group_statistics, can_view_course_session_progress, ) -from vbv_lernwelt.learning_mentor.models import LearningMentor +from vbv_lernwelt.learning_mentor.models import ( + AgentParticipantRelation, + AgentParticipantRoleType, +) from vbv_lernwelt.learnpath.models import Circle @@ -95,12 +98,15 @@ class DashboardQuery(graphene.ObjectType): mentees_ids = set() course_session_ids = set() - mentees = CourseSessionUser.objects.filter( - participants__mentor=user, course_session__course=course - ).values_list("user", "course_session") - for user_id, course_session_id in mentees: - mentees_ids.add(user_id) - course_session_ids.add(course_session_id) + relations_qs = AgentParticipantRelation.objects.filter( + agent=user, + role=AgentParticipantRoleType.LEARNING_MENTOR.value, + participant__course_session__course=course, + ) + + for relation in relations_qs: + mentees_ids.add(relation.participant.user_id) + course_session_ids.add(relation.participant.course_session_id) return CourseStatisticsType( _id=f"mentor:{course.id}", # noqa @@ -249,29 +255,31 @@ def get_user_statistics_dashboards(user: User) -> Tuple[List[Dict[str, str]], Se def get_learning_mentor_dashboards( user: User, exclude_course_ids: Set[int] ) -> Tuple[List[Dict[str, str]], Set[int]]: - learning_mentor = LearningMentor.objects.filter(mentor=user).exclude( - course_session__course__id__in=exclude_course_ids - ) + learning_mentor_relation_qs = AgentParticipantRelation.objects.filter( + agent=user, role=AgentParticipantRoleType.LEARNING_MENTOR.value + ).exclude(participant__course_session__course__id__in=exclude_course_ids) dashboards = [] course_ids = set() - for mentor in learning_mentor: - course = mentor.course_session.course - course_ids.add(course.id) + for rel in learning_mentor_relation_qs: + course = rel.participant.course_session.course if course.id in UK_COURSE_IDS: dashboard_type = DashboardType.PRAXISBILDNER_DASHBOARD else: dashboard_type = DashboardType.MENTOR_DASHBOARD - dashboards.append( - { - "id": str(course.id), - "name": course.title, - "slug": course.slug, - "dashboard_type": dashboard_type, - "course_configuration": course.configuration, - } - ) + + if course.id not in course_ids: + course_ids.add(course.id) + dashboards.append( + { + "id": str(course.id), + "name": course.title, + "slug": course.slug, + "dashboard_type": dashboard_type, + "course_configuration": course.configuration, + } + ) return dashboards, course_ids diff --git a/server/vbv_lernwelt/dashboard/tests/graphql/test_dashboard.py b/server/vbv_lernwelt/dashboard/tests/graphql/test_dashboard.py index 6373eea3..2f9631d5 100644 --- a/server/vbv_lernwelt/dashboard/tests/graphql/test_dashboard.py +++ b/server/vbv_lernwelt/dashboard/tests/graphql/test_dashboard.py @@ -110,22 +110,22 @@ class DashboardTestCase(GraphQLTestCase): self.client.force_login(member) - query = f"""query($course_id: ID!) {{ - course_progress(course_id: $course_id) {{ + query = """query($course_id: ID!) { + course_progress(course_id: $course_id) { course_id session_to_continue_id - competence {{ + competence { total_count success_count fail_count - }} - assignment {{ + } + assignment { total_count points_max_count points_achieved_count - }} - }} - }} + } + } + } """ variables = {"course_id": str(course.id)} @@ -268,7 +268,7 @@ class DashboardTestCase(GraphQLTestCase): role=CourseSessionUser.Role.MEMBER, ) - add_learning_mentor(course_session=cs_1, mentor=mentor, mentee=csu) + add_learning_mentor(mentor=mentor, mentee=csu) self.client.force_login(mentor) @@ -287,6 +287,7 @@ class DashboardTestCase(GraphQLTestCase): # THEN self.assertResponseNoErrors(response) + print(response.json()) self.assertEqual(len(response.json()["data"]["dashboard_config"]), 1) self.assertEqual( @@ -314,7 +315,7 @@ class DashboardTestCase(GraphQLTestCase): role=CourseSessionUser.Role.MEMBER, ) - add_learning_mentor(course_session=cs, mentor=mentor_and_member, mentee=mentee) + add_learning_mentor(mentor=mentor_and_member, mentee=mentee) # WHEN self.client.force_login(mentor_and_member) @@ -350,11 +351,11 @@ class DashboardTestCase(GraphQLTestCase): self.client.force_login(disallowed_user) - query = f"""query($course_id: ID!) {{ - course_statistics(course_id: $course_id) {{ + query = """query($course_id: ID!) { + course_statistics(course_id: $course_id) { course_id - }} - }} + } + } """ variables = {"course_id": str(course.id)} @@ -384,13 +385,13 @@ class DashboardTestCase(GraphQLTestCase): self.client.force_login(supervisor) - query = f"""query($course_id: ID!) {{ - course_statistics(course_id: $course_id) {{ + query = """query($course_id: ID!) { + course_statistics(course_id: $course_id) { course_id course_title course_slug - }} - }} + } + } """ variables = {"course_id": str(course_2.id)} diff --git a/server/vbv_lernwelt/dashboard/tests/graphql/test_mentor_statistics.py b/server/vbv_lernwelt/dashboard/tests/graphql/test_mentor_statistics.py index 823fdd09..31947f5b 100644 --- a/server/vbv_lernwelt/dashboard/tests/graphql/test_mentor_statistics.py +++ b/server/vbv_lernwelt/dashboard/tests/graphql/test_mentor_statistics.py @@ -7,7 +7,7 @@ from vbv_lernwelt.assignment.models import ( from vbv_lernwelt.course.creators.test_utils import add_course_session_user, create_user from vbv_lernwelt.course.models import CourseSessionUser from vbv_lernwelt.dashboard.tests.test_views import BaseMentorAssignmentTestCase -from vbv_lernwelt.learning_mentor.models import LearningMentor +from vbv_lernwelt.learning_mentor.models import AgentParticipantRelation class MentorStatisticsTestCase(BaseMentorAssignmentTestCase, GraphQLTestCase): @@ -19,15 +19,13 @@ class MentorStatisticsTestCase(BaseMentorAssignmentTestCase, GraphQLTestCase): self.course.configuration.save() self.mentor = create_user("mentor") - self.lm = LearningMentor.objects.create( - mentor=self.mentor, course_session=self.course_session - ) self.participants = [create_user(f"participant{i}") for i in range(4)] def test_assignment_statistics(self): # WHEN has_lb = [True, True, True, False] has_passed = [True, False, True, False] + for i in range(4): csu = add_course_session_user( self.course_session, @@ -35,7 +33,9 @@ class MentorStatisticsTestCase(BaseMentorAssignmentTestCase, GraphQLTestCase): role=CourseSessionUser.Role.MEMBER, ) if has_lb[i]: - self.lm.participants.add(csu) + AgentParticipantRelation.objects.create( + agent=self.mentor, participant=csu, role="LEARNING_MENTOR" + ) AssignmentCompletion.objects.create( course_session=self.course_session, @@ -47,22 +47,22 @@ class MentorStatisticsTestCase(BaseMentorAssignmentTestCase, GraphQLTestCase): ) # THEN # WHEN - query = f"""query ($courseId: ID!) {{ - mentor_course_statistics(course_id: $courseId) {{ + query = """query ($courseId: ID!) { + mentor_course_statistics(course_id: $courseId) { course_session_selection_ids user_selection_ids - assignments {{ + assignments { _id - summary {{ + summary { _id completed_count average_passed total_passed total_failed - }} - }} - }} - }}""" + } + } + } + }""" # THEN variables = {"courseId": str(self.course.id)} diff --git a/server/vbv_lernwelt/dashboard/tests/test_views.py b/server/vbv_lernwelt/dashboard/tests/test_views.py index 277bbf5b..629b5b6d 100644 --- a/server/vbv_lernwelt/dashboard/tests/test_views.py +++ b/server/vbv_lernwelt/dashboard/tests/test_views.py @@ -37,7 +37,7 @@ from vbv_lernwelt.dashboard.views import ( get_course_config, get_course_sessions_with_roles_for_user, ) -from vbv_lernwelt.learning_mentor.models import LearningMentor +from vbv_lernwelt.learning_mentor.models import AgentParticipantRelation from vbv_lernwelt.learnpath.models import Circle, LearningUnit from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback @@ -98,14 +98,16 @@ class GetCourseSessionsForUserTestCase(TestCase): def test_learning_mentor_get_sessions(self): mentor = create_user("mentor") - LearningMentor.objects.create(mentor=mentor, course_session=self.course_session) participant = create_user("participant") - add_course_session_user( + csu = add_course_session_user( self.course_session, participant, role=CourseSessionUser.Role.MEMBER, ) + AgentParticipantRelation.objects.create( + agent=mentor, participant=csu, role="LEARNING_MENTOR" + ) sessions = get_course_sessions_with_roles_for_user(mentor) @@ -187,7 +189,16 @@ class GetDashboardConfig(TestCase): def test_mentor_uk_get_config(self): # GIVEN mentor = create_user("mentor") - LearningMentor.objects.create(mentor=mentor, course_session=self.course_session) + + participant = create_user("participant") + csu = add_course_session_user( + self.course_session, + participant, + role=CourseSessionUser.Role.MEMBER, + ) + AgentParticipantRelation.objects.create( + agent=mentor, participant=csu, role="LEARNING_MENTOR" + ) self.course.configuration.is_uk = True self.course.configuration.save() @@ -205,7 +216,15 @@ class GetDashboardConfig(TestCase): def test_mentor_vv_get_config(self): # GIVEN mentor = create_user("mentor") - LearningMentor.objects.create(mentor=mentor, course_session=self.course_session) + participant = create_user("participant") + csu = add_course_session_user( + self.course_session, + participant, + role=CourseSessionUser.Role.MEMBER, + ) + AgentParticipantRelation.objects.create( + agent=mentor, participant=csu, role="LEARNING_MENTOR" + ) self.course.configuration.is_vv = True self.course.configuration.save() @@ -228,7 +247,16 @@ class GetDashboardConfig(TestCase): mentor, role=CourseSessionUser.Role.MEMBER, ) - LearningMentor.objects.create(mentor=mentor, course_session=self.course_session) + + participant = create_user("participant") + csu = add_course_session_user( + self.course_session, + participant, + role=CourseSessionUser.Role.MEMBER, + ) + AgentParticipantRelation.objects.create( + agent=mentor, participant=csu, role="LEARNING_MENTOR" + ) self.course.configuration.is_vv = True self.course.configuration.save() @@ -264,9 +292,6 @@ class GetMenteeCountTestCase(TestCase): participants_with_mentor = [create_user(f"participant{i}") for i in range(2)] participant = create_user("participant") mentor = create_user("mentor") - lm = LearningMentor.objects.create( - mentor=mentor, course_session=self.course_session - ) # WHEN for p in participants_with_mentor: @@ -275,7 +300,9 @@ class GetMenteeCountTestCase(TestCase): p, role=CourseSessionUser.Role.MEMBER, ) - lm.participants.add(csu) + AgentParticipantRelation.objects.create( + agent=mentor, participant=csu, role="LEARNING_MENTOR" + ) add_course_session_user( self.course_session, @@ -305,9 +332,6 @@ class GetMentorOpenTasksTestCase(BaseMentorAssignmentTestCase): self.course.configuration.save() self.mentor = create_user("mentor") - self.lm = LearningMentor.objects.create( - mentor=self.mentor, course_session=self.course_session - ) self.participants = [create_user(f"participant{i}") for i in range(2)] def create_and_test_count( @@ -337,7 +361,10 @@ class GetMentorOpenTasksTestCase(BaseMentorAssignmentTestCase): self.participants[0], role=CourseSessionUser.Role.MEMBER, ) - self.lm.participants.add(csu) + + AgentParticipantRelation.objects.create( + agent=self.mentor, participant=csu, role="LEARNING_MENTOR" + ) add_course_session_user( self.course_session, @@ -367,7 +394,9 @@ class GetMentorOpenTasksTestCase(BaseMentorAssignmentTestCase): self.participants[0], role=CourseSessionUser.Role.MEMBER, ) - self.lm.participants.add(csu) + AgentParticipantRelation.objects.create( + agent=self.mentor, participant=csu, role="LEARNING_MENTOR" + ) add_course_session_user( self.course_session, @@ -389,8 +418,9 @@ class GetMentorOpenTasksTestCase(BaseMentorAssignmentTestCase): self.participants[0], role=CourseSessionUser.Role.MEMBER, ) - self.lm.participants.add(csu) - + AgentParticipantRelation.objects.create( + agent=self.mentor, participant=csu, role="LEARNING_MENTOR" + ) SelfEvaluationFeedback.objects.create( feedback_submitted=False, feedback_requester_user=self.participants[0], @@ -411,8 +441,9 @@ class GetMentorOpenTasksTestCase(BaseMentorAssignmentTestCase): self.participants[0], role=CourseSessionUser.Role.MEMBER, ) - self.lm.participants.add(csu) - + AgentParticipantRelation.objects.create( + agent=self.mentor, participant=csu, role="LEARNING_MENTOR" + ) SelfEvaluationFeedback.objects.create( feedback_submitted=True, feedback_requester_user=self.participants[0], diff --git a/server/vbv_lernwelt/dashboard/views.py b/server/vbv_lernwelt/dashboard/views.py index 1cdf533a..186da2ae 100644 --- a/server/vbv_lernwelt/dashboard/views.py +++ b/server/vbv_lernwelt/dashboard/views.py @@ -42,7 +42,10 @@ from vbv_lernwelt.feedback.export import ( export_feedback_with_circle_restriction, FEEDBACK_EXPORT_FILE_NAME, ) -from vbv_lernwelt.learning_mentor.models import LearningMentor +from vbv_lernwelt.learning_mentor.models import ( + AgentParticipantRelation, + AgentParticipantRoleType, +) from vbv_lernwelt.learnpath.models import Circle from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback @@ -118,15 +121,15 @@ def get_course_sessions_with_roles_for_user(user: User) -> List[CourseSessionWit result_course_sessions[cs.id] = cs # enrich with mentor course sessions - lm_qs = LearningMentor.objects.filter(mentor=user).prefetch_related( - "course_session", "course_session__course" + lm_qs = AgentParticipantRelation.objects.filter(agent=user).prefetch_related( + "participant__course_session", "participant__course_session__course" ) for lm in lm_qs: - cs = lm.course_session + cs = lm.participant.course_session cs.roles = set() cs = result_course_sessions.get(cs.id, cs) - cs.roles.add("LEARNING_MENTOR") + cs.roles.add(lm.role) result_course_sessions[cs.id] = cs return [ @@ -197,46 +200,46 @@ def _create_person_list_with_roles(user): # add persons where request.user is mentor for cs in course_sessions: if "LEARNING_MENTOR" in cs.roles: - lm = LearningMentor.objects.filter( - mentor=user, course_session=cs.id - ).first() - - for participant in lm.participants.all(): + for relation in AgentParticipantRelation.objects.filter( + agent=user, participant__course_session_id=cs.id + ): course_session_entry = _create_course_session_dict( cs, "LEARNING_MENTOR", "LEARNING_MENTEE", ) + participant_user = relation.participant.user - if participant.user.id not in result_persons: - person_data = create_user_dict(participant.user) + if participant_user.id not in result_persons: + person_data = create_user_dict(participant_user) person_data["course_sessions"] = [course_session_entry] - result_persons[participant.user.id] = person_data + result_persons[participant_user] = person_data else: # user is already in result_persons - result_persons[participant.user.id]["course_sessions"].append( + result_persons[participant_user]["course_sessions"].append( course_session_entry ) # add persons where request.user is mentee - mentor_relation_qs = LearningMentor.objects.filter( - participants__user=user - ).prefetch_related("mentor", "course_session") + mentor_relation_qs = AgentParticipantRelation.objects.filter( + participant__user=user, + role=AgentParticipantRoleType.LEARNING_MENTOR.value, + ).prefetch_related("agent") for mentor_relation in mentor_relation_qs: - cs = mentor_relation.course_session + cs = mentor_relation.participant.course_session course_session_entry = _create_course_session_dict( cs, "LEARNING_MENTEE", "LEARNING_MENTOR", ) - if mentor_relation.mentor.id not in result_persons: - person_data = create_user_dict(mentor_relation.mentor) + if mentor_relation.agent.id not in result_persons: + person_data = create_user_dict(mentor_relation.agent) person_data["course_sessions"] = [course_session_entry] - result_persons[mentor_relation.mentor.id] = person_data + result_persons[mentor_relation.agent.id] = person_data else: # user is already in result_persons - result_persons[mentor_relation.mentor.id]["course_sessions"].append( + result_persons[mentor_relation.agent.id]["course_sessions"].append( course_session_entry ) @@ -495,8 +498,10 @@ def get_mentee_count(request, course_id: str): def _get_mentee_count(course_id: str, mentor: User) -> int: - return CourseSessionUser.objects.filter( - participants__mentor=mentor, course_session__course__id=course_id + return AgentParticipantRelation.objects.filter( + agent=mentor, + participant__course_session__course_id=course_id, + role=AgentParticipantRoleType.LEARNING_MENTOR.value, ).count() @@ -524,17 +529,26 @@ def _get_mentor_open_tasks_count(course_id: str, mentor: User) -> int: course_configuration = CourseConfiguration.objects.get(course_id=course_id) + learning_meentee_ids = [ + str(relation.participant.user_id) + for relation in AgentParticipantRelation.objects.filter( + agent=mentor, + participant__course_session__course_id=course_id, + role=AgentParticipantRoleType.LEARNING_MENTOR.value, + ).prefetch_related("participant") + ] + if course_configuration.is_vv: open_assigment_count = AssignmentCompletion.objects.filter( course_session__course__id=course_id, completion_status=AssignmentCompletionStatus.SUBMITTED.value, evaluation_user=mentor, # noqa - assignment_user__coursesessionuser__participants__mentor=mentor, + assignment_user_id__in=learning_meentee_ids, ).count() open_feedback_qs = SelfEvaluationFeedback.objects.filter( feedback_provider_user=mentor, # noqa - feedback_requester_user__coursesessionuser__participants__mentor=mentor, + feedback_requester_user_id__in=learning_meentee_ids, feedback_submitted=False, ) # filter open feedbacks for course_id (-> not possible with queryset) diff --git a/server/vbv_lernwelt/iam/permissions.py b/server/vbv_lernwelt/iam/permissions.py index b9b141b0..1820b9ad 100644 --- a/server/vbv_lernwelt/iam/permissions.py +++ b/server/vbv_lernwelt/iam/permissions.py @@ -1,7 +1,10 @@ from vbv_lernwelt.core.models import User from vbv_lernwelt.course.models import Course, CourseSession, CourseSessionUser from vbv_lernwelt.course_session_group.models import CourseSessionGroup -from vbv_lernwelt.learning_mentor.models import LearningMentor +from vbv_lernwelt.learning_mentor.models import ( + AgentParticipantRelation, + AgentParticipantRoleType, +) from vbv_lernwelt.learnpath.models import LearningSequence @@ -22,8 +25,8 @@ def has_course_access(user, course_id): ).exists(): return True - if LearningMentor.objects.filter( - course_session__course_id=course_id, mentor=user + if AgentParticipantRelation.objects.filter( + agent=user, participant__course_session__course_id=course_id ).exists(): return True @@ -72,8 +75,9 @@ def is_course_session_learning_mentor(mentor: User, course_session_id: int): if course_session is None: return False - return LearningMentor.objects.filter( - mentor=mentor, course_session=course_session + return AgentParticipantRelation.objects.filter( + agent=mentor, + role=AgentParticipantRoleType.LEARNING_MENTOR.value, ).exists() @@ -87,9 +91,11 @@ def is_learning_mentor_for_user( if csu is None: return False - return LearningMentor.objects.filter( - course_session_id=course_session_id, mentor=mentor, participants=csu - ).exists() + return AgentParticipantRelation.objects.filter( + agent=mentor, + participant=csu, + role=AgentParticipantRoleType.LEARNING_MENTOR.value, + ) def is_course_session_supervisor(user, course_session_id: int): @@ -222,8 +228,8 @@ def has_role_in_course(user: User, course: Course) -> bool: ).exists(): return True - if LearningMentor.objects.filter( - course_session__course=course, mentor=user + if AgentParticipantRelation.objects.filter( + agent=user, participant__course_session__course=course ).exists(): return True diff --git a/server/vbv_lernwelt/iam/tests/test_actions.py b/server/vbv_lernwelt/iam/tests/test_actions.py index 84d70e5e..c2991ec0 100644 --- a/server/vbv_lernwelt/iam/tests/test_actions.py +++ b/server/vbv_lernwelt/iam/tests/test_actions.py @@ -8,7 +8,7 @@ from vbv_lernwelt.course.creators.test_utils import ( ) from vbv_lernwelt.course.models import CourseSessionUser from vbv_lernwelt.iam.permissions import course_session_permissions -from vbv_lernwelt.learning_mentor.models import LearningMentor +from vbv_lernwelt.learning_mentor.models import AgentParticipantRelation class ActionTestCase(TestCase): @@ -21,14 +21,16 @@ class ActionTestCase(TestCase): def test_course_session_permissions(self): # GIVEN lm = create_user("mentor") - LearningMentor.objects.create(mentor=lm, course_session=self.course_session) participant = create_user("participant") - add_course_session_user( + csu = add_course_session_user( self.course_session, participant, role=CourseSessionUser.Role.MEMBER, ) + AgentParticipantRelation.objects.create( + agent=lm, participant=csu, role="LEARNING_MENTOR" + ) trainer = create_user("trainer") add_course_session_user( diff --git a/server/vbv_lernwelt/iam/tests/test_roles.py b/server/vbv_lernwelt/iam/tests/test_roles.py index 92ae9b20..94e48dbe 100644 --- a/server/vbv_lernwelt/iam/tests/test_roles.py +++ b/server/vbv_lernwelt/iam/tests/test_roles.py @@ -9,7 +9,7 @@ from vbv_lernwelt.course.creators.test_utils import ( from vbv_lernwelt.course.models import CourseSessionUser from vbv_lernwelt.course_session_group.models import CourseSessionGroup from vbv_lernwelt.iam.permissions import has_role_in_course, is_learning_mentor_for_user -from vbv_lernwelt.learning_mentor.models import LearningMentor +from vbv_lernwelt.learning_mentor.models import AgentParticipantRelation class RoleTestCase(TestCase): @@ -48,9 +48,14 @@ class RoleTestCase(TestCase): def test_has_role_mentor(self): # GIVEN - LearningMentor.objects.create( - mentor=self.user, - course_session=self.course_session, + participant = create_user("participant") + csu = add_course_session_user( + self.course_session, + participant, + role=CourseSessionUser.Role.MEMBER, + ) + AgentParticipantRelation.objects.create( + agent=self.user, participant=csu, role="LEARNING_MENTOR" ) # WHEN @@ -73,14 +78,10 @@ class RoleTestCase(TestCase): role=CourseSessionUser.Role.MEMBER, ) - learning_mentor = LearningMentor.objects.create( - mentor=mentor, - course_session=course_session, + AgentParticipantRelation.objects.create( + agent=mentor, participant=member_csu, role="LEARNING_MENTOR" ) - learning_mentor.participants.add(member_csu) - learning_mentor.save() - # WHEN is_mentor = is_learning_mentor_for_user( mentor=mentor, diff --git a/server/vbv_lernwelt/importer/tests/test_import_course_sessions.py b/server/vbv_lernwelt/importer/tests/test_import_course_sessions.py index bda1ac8b..9b6f1fda 100644 --- a/server/vbv_lernwelt/importer/tests/test_import_course_sessions.py +++ b/server/vbv_lernwelt/importer/tests/test_import_course_sessions.py @@ -101,7 +101,7 @@ class CreateOrUpdateCourseSessionTestCase(TestCase): attendance_course.due_date.end.isoformat(), "2023-06-06T13:00:00+00:00" ) self.assertEqual( - f"E64, HKV Aarau, Bahnhofstrasse 460, 5001, Aarau", + "E64, HKV Aarau, Bahnhofstrasse 460, 5001, Aarau", attendance_course.location, ) self.assertEqual("", attendance_course.trainer) @@ -183,7 +183,7 @@ class CreateOrUpdateCourseSessionTestCase(TestCase): attendance_course.due_date.end.isoformat(), "2023-06-06T15:00:00+00:00" ) self.assertEqual( - f"E666, HKV Aarau2, Bahnhofstrasse 460, 5002, Aarau", + "E666, HKV Aarau2, Bahnhofstrasse 460, 5002, Aarau", attendance_course.location, ) self.assertEqual( diff --git a/server/vbv_lernwelt/learning_mentor/admin.py b/server/vbv_lernwelt/learning_mentor/admin.py index 8834dd95..dc55ffd1 100644 --- a/server/vbv_lernwelt/learning_mentor/admin.py +++ b/server/vbv_lernwelt/learning_mentor/admin.py @@ -1,34 +1,29 @@ from django.contrib import admin -from vbv_lernwelt.course.models import CourseSessionUser -from vbv_lernwelt.learning_mentor.models import LearningMentor, MentorInvitation +from vbv_lernwelt.learning_mentor.models import ( + AgentParticipantRelation, + MentorInvitation, +) -@admin.register(LearningMentor) -class LearningMentorAdmin(admin.ModelAdmin): - def participant_count(self, obj): - return obj.participants.count() +@admin.register(AgentParticipantRelation) +class TrainerParticipantRelationAdmin(admin.ModelAdmin): + list_display = ["agent", "participant", "role"] - participant_count.short_description = "Participants" - - list_display = ["mentor", "course_session", "participant_count"] - - search_fields = ["mentor__email"] - - raw_id_fields = [ - "mentor", - "course_session", + search_fields = [ + "agent__email", + "agent__first_name", + "agent__last_name", + "participant__user__email", + "participant__user__first_name", + "participant__user__last_name", ] - def formfield_for_manytomany(self, db_field, request, **kwargs): - if db_field.name == "participants": - if request.resolver_match.kwargs.get("object_id"): - object_id = str(request.resolver_match.kwargs.get("object_id")) - lm = LearningMentor.objects.get(id=object_id) - kwargs["queryset"] = CourseSessionUser.objects.filter( - course_session=lm.course_session - ) - return super().formfield_for_manytomany(db_field, request, **kwargs) + raw_id_fields = [ + "agent", + "participant", + # "course_session", + ] @admin.register(MentorInvitation) diff --git a/server/vbv_lernwelt/learning_mentor/apps.py b/server/vbv_lernwelt/learning_mentor/apps.py index 1580676b..ec958470 100644 --- a/server/vbv_lernwelt/learning_mentor/apps.py +++ b/server/vbv_lernwelt/learning_mentor/apps.py @@ -6,4 +6,8 @@ class LearningMentorConfig(AppConfig): name = "vbv_lernwelt.learning_mentor" def ready(self): - import vbv_lernwelt.learning_mentor.signals # noqa F401 + try: + # pylint: disable=unused-import,import-outside-toplevel + import vbv_lernwelt.learning_mentor.signals # noqa F401 + except ImportError: + pass diff --git a/server/vbv_lernwelt/learning_mentor/migrations/0008_agentparticipantrelation.py b/server/vbv_lernwelt/learning_mentor/migrations/0008_agentparticipantrelation.py new file mode 100644 index 00000000..d340f670 --- /dev/null +++ b/server/vbv_lernwelt/learning_mentor/migrations/0008_agentparticipantrelation.py @@ -0,0 +1,57 @@ +# Generated by Django 3.2.20 on 2024-07-18 13:33 + +import uuid + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("course", "0009_alter_coursecompletion_completion_status"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("learning_mentor", "0007_mentorinvitation_target_url"), + ] + + operations = [ + migrations.CreateModel( + name="AgentParticipantRelation", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "role", + models.CharField( + choices=[("LEARNING_MENTOR", "LEARNING_MENTOR")], + default="LEARNING_MENTOR", + max_length=255, + ), + ), + ( + "agent", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "participant", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="course.coursesessionuser", + ), + ), + ], + options={ + "unique_together": {("agent", "participant")}, + }, + ), + ] diff --git a/server/vbv_lernwelt/learning_mentor/migrations/0009_auto_20240718_1533.py b/server/vbv_lernwelt/learning_mentor/migrations/0009_auto_20240718_1533.py new file mode 100644 index 00000000..7265067a --- /dev/null +++ b/server/vbv_lernwelt/learning_mentor/migrations/0009_auto_20240718_1533.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.20 on 2024-07-18 13:33 +from django.db import migrations +from django.db.migrations import RunPython + + +def refactor_mentor_to_agent_participant(apps=None, schema_editor=None): + LearningMentor = apps.get_model("learning_mentor", "LearningMentor") + AgentParticipantRelation = apps.get_model( + "learning_mentor", "AgentParticipantRelation" + ) + + for mentor in LearningMentor.objects.all(): + for participant in mentor.participants.all(): + AgentParticipantRelation.objects.create( + agent=mentor.mentor, + participant=participant, + ) + + +class Migration(migrations.Migration): + dependencies = [ + ("learning_mentor", "0008_agentparticipantrelation"), + ] + + operations = [RunPython(refactor_mentor_to_agent_participant)] diff --git a/server/vbv_lernwelt/learning_mentor/models.py b/server/vbv_lernwelt/learning_mentor/models.py index fa84b108..1db96576 100644 --- a/server/vbv_lernwelt/learning_mentor/models.py +++ b/server/vbv_lernwelt/learning_mentor/models.py @@ -1,4 +1,5 @@ import uuid +from enum import Enum from django.db import models from django_extensions.db.models import TimeStampedModel @@ -30,6 +31,26 @@ class LearningMentor(models.Model): return self.participants.values_list("course_session", flat=True).distinct() +class AgentParticipantRoleType(Enum): + LEARNING_MENTOR = "LEARNING_MENTOR" # Lernbegleiter + + +class AgentParticipantRelation(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + + agent = models.ForeignKey(User, on_delete=models.CASCADE) + participant = models.ForeignKey(CourseSessionUser, on_delete=models.CASCADE) + + role = models.CharField( + max_length=255, + choices=[(t.value, t.value) for t in AgentParticipantRoleType], + default=AgentParticipantRoleType.LEARNING_MENTOR.value, + ) + + class Meta: + unique_together = [("agent", "participant")] + + class MentorInvitation(TimeStampedModel): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) email = models.EmailField() diff --git a/server/vbv_lernwelt/learning_mentor/serializers.py b/server/vbv_lernwelt/learning_mentor/serializers.py index a644d3cd..35d4bb23 100644 --- a/server/vbv_lernwelt/learning_mentor/serializers.py +++ b/server/vbv_lernwelt/learning_mentor/serializers.py @@ -1,7 +1,10 @@ from rest_framework import serializers -from vbv_lernwelt.core.serializers import UserSerializer -from vbv_lernwelt.learning_mentor.models import LearningMentor, MentorInvitation +from vbv_lernwelt.core.serializers import UserShortSerializer +from vbv_lernwelt.learning_mentor.models import ( + AgentParticipantRelation, + MentorInvitation, +) class MentorAssignmentCompletionSerializer(serializers.Serializer): @@ -39,10 +42,24 @@ class InvitationSerializer(serializers.ModelSerializer): return invitation -class MentorSerializer(serializers.ModelSerializer): - mentor = UserSerializer(read_only=True) +class AgentParticipantRelationSerializer(serializers.ModelSerializer): + agent = UserShortSerializer(read_only=True) + participant_user = serializers.SerializerMethodField() + course_session_id = serializers.SerializerMethodField() + + def get_participant_user(self, obj): + return UserShortSerializer(obj.participant.user).data + + def get_course_session_id(self, obj): + return obj.participant.course_session_id class Meta: - model = LearningMentor - fields = ["id", "mentor"] + model = AgentParticipantRelation + fields = [ + "id", + "role", + "course_session_id", + "agent", + "participant_user", + ] read_only_fields = ["id"] diff --git a/server/vbv_lernwelt/learning_mentor/signals.py b/server/vbv_lernwelt/learning_mentor/signals.py deleted file mode 100644 index 32cb5c72..00000000 --- a/server/vbv_lernwelt/learning_mentor/signals.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.core.exceptions import ValidationError -from django.db.models.signals import m2m_changed -from django.dispatch import receiver - -from .models import LearningMentor - - -@receiver(m2m_changed, sender=LearningMentor.participants.through) -def validate_student(sender, instance, action, reverse, model, pk_set, **kwargs): - if action == "pre_add": - participants = model.objects.filter(pk__in=pk_set) - for participant in participants: - if participant.course_session != instance.course_session: - raise ValidationError( - "Participant (CourseSessionUser) does not match the course for this mentor." - ) - if participant.user == instance.mentor: - raise ValidationError("You cannot mentor yourself.") diff --git a/server/vbv_lernwelt/learning_mentor/tests/test_invitation.py b/server/vbv_lernwelt/learning_mentor/tests/test_invitation.py index 6c56c29a..0584c2ee 100644 --- a/server/vbv_lernwelt/learning_mentor/tests/test_invitation.py +++ b/server/vbv_lernwelt/learning_mentor/tests/test_invitation.py @@ -11,7 +11,11 @@ from vbv_lernwelt.course.creators.test_utils import ( create_user, ) from vbv_lernwelt.course.models import CourseSessionUser -from vbv_lernwelt.learning_mentor.models import LearningMentor, MentorInvitation +from vbv_lernwelt.learning_mentor.models import ( + AgentParticipantRelation, + AgentParticipantRoleType, + MentorInvitation, +) from vbv_lernwelt.notify.email.email_services import EmailTemplate @@ -254,13 +258,14 @@ class LearningMentorInvitationTest(APITestCase): # THEN self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertFalse(MentorInvitation.objects.filter(id=invitation.id).exists()) - self.assertTrue( - LearningMentor.objects.filter( - mentor=invitee, - course_session=self.course_session, - participants=participant_cs_user, - ).exists() - ) + + relation_qs = AgentParticipantRelation.objects.all() + self.assertEqual(relation_qs.count(), 1) + relation = relation_qs.first() + self.assertEqual(relation.agent, invitee) + self.assertEqual(relation.participant, participant_cs_user) + self.assertEqual(relation.participant.course_session, self.course_session) + self.assertEqual(relation.role, AgentParticipantRoleType.LEARNING_MENTOR.value) user = response.data["user"] self.assertEqual(user["id"], str(self.participant.id)) diff --git a/server/vbv_lernwelt/learning_mentor/tests/test_mentor.py b/server/vbv_lernwelt/learning_mentor/tests/test_mentor.py index 41ce64b6..e32f0b84 100644 --- a/server/vbv_lernwelt/learning_mentor/tests/test_mentor.py +++ b/server/vbv_lernwelt/learning_mentor/tests/test_mentor.py @@ -22,7 +22,10 @@ from vbv_lernwelt.course.creators.test_utils import ( create_user, ) from vbv_lernwelt.course.models import CourseSessionUser -from vbv_lernwelt.learning_mentor.models import LearningMentor +from vbv_lernwelt.learning_mentor.models import ( + AgentParticipantRelation, + AgentParticipantRoleType, +) from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback @@ -71,63 +74,56 @@ class LearningMentorAPITest(APITestCase): response = self.client.get(self.url) # THEN + print(response.data) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) def test_api_no_participants(self) -> None: # GIVEN self.client.force_login(self.mentor) - LearningMentor.objects.create( - mentor=self.mentor, course_session=self.course_session - ) # WHEN response = self.client.get(self.url) # THEN - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data["participants"], []) - self.assertEqual(response.data["assignments"], []) + print(response.data) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) def test_api_participants(self) -> None: # GIVEN participants = [self.participant_1, self.participant_2, self.participant_3] - self.client.force_login(self.mentor) - mentor = LearningMentor.objects.create( - mentor=self.mentor, course_session=self.course_session - ) - mentor.participants.set(participants) + + for participant in participants: + AgentParticipantRelation.objects.create( + agent=self.mentor, + participant=participant, + role=AgentParticipantRoleType.LEARNING_MENTOR.value, + ) # WHEN + self.client.force_login(self.mentor) response = self.client.get(self.url) # THEN + print(response.data) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.data["participants"]), len(participants)) + self.assertEqual(len(response.data["participant_relations"]), len(participants)) - participant_1 = [ - p - for p in response.data["participants"] - if p["id"] == str(self.participant_1.user.id) - ][0] - self.assertEqual(participant_1["email"], "participant_1@example.com") - self.assertEqual(participant_1["first_name"], "Test") - self.assertEqual(participant_1["last_name"], "Participant_1") + rel1 = AgentParticipantRelation.objects.get(participant=self.participant_1) - self.assertEqual( - response.data["mentor_id"], - mentor.id, - ) + self.assertEqual(rel1.participant.user.email, "participant_1@example.com") + self.assertEqual(rel1.participant.user.first_name, "Test") + self.assertEqual(rel1.participant.user.last_name, "Participant_1") def test_api_self_evaluation_feedback(self) -> None: # GIVEN participants = [self.participant_1, self.participant_2, self.participant_3] - self.client.force_login(self.mentor) - mentor = LearningMentor.objects.create( - mentor=self.mentor, course_session=self.course_session - ) - - mentor.participants.set(participants) + for participant in participants: + AgentParticipantRelation.objects.create( + agent=self.mentor, + participant=participant, + role=AgentParticipantRoleType.LEARNING_MENTOR.value, + ) learning_unit = create_learning_unit( circle=self.circle, @@ -154,6 +150,7 @@ class LearningMentorAPITest(APITestCase): # ... # WHEN + self.client.force_login(self.mentor) response = self.client.get(self.url) # THEN @@ -194,7 +191,6 @@ class LearningMentorAPITest(APITestCase): def test_api_praxis_assignments(self) -> None: # GIVEN - self.client.force_login(self.mentor) assignment = create_assignment( course=self.course, assignment_type=AssignmentType.PRAXIS_ASSIGNMENT @@ -205,13 +201,13 @@ class LearningMentorAPITest(APITestCase): course_session=self.course_session, learning_content_assignment=lca ) - mentor = LearningMentor.objects.create( - mentor=self.mentor, - course_session=self.course_session, - ) - participants = [self.participant_1, self.participant_2, self.participant_3] - mentor.participants.set(participants) + for participant in participants: + AgentParticipantRelation.objects.create( + agent=self.mentor, + participant=participant, + role=AgentParticipantRoleType.LEARNING_MENTOR.value, + ) AssignmentCompletion.objects.create( assignment_user=self.participant_1.user, @@ -230,6 +226,7 @@ class LearningMentorAPITest(APITestCase): ) # WHEN + self.client.force_login(self.mentor) response = self.client.get(self.url) # THEN @@ -267,12 +264,12 @@ class LearningMentorAPITest(APITestCase): role=CourseSessionUser.Role.MEMBER, ) - learning_mentor = LearningMentor.objects.create( - mentor=self.mentor, course_session=self.course_session + db_relation = AgentParticipantRelation.objects.create( + agent=self.mentor, + participant=participant_cs_user, + role=AgentParticipantRoleType.LEARNING_MENTOR.value, ) - learning_mentor.participants.add(participant_cs_user) - list_url = reverse( "list_user_mentors", kwargs={"course_session_id": self.course_session.id} ) @@ -281,12 +278,13 @@ class LearningMentorAPITest(APITestCase): response = self.client.get(list_url) # THEN + print(response.data) self.assertEqual(response.status_code, status.HTTP_200_OK) - mentor = response.data[0] - self.assertEqual(mentor["id"], learning_mentor.id) + mentor_relation = response.data[0] + self.assertEqual(mentor_relation["id"], str(db_relation.id)) - mentor_user = mentor["mentor"] + mentor_user = mentor_relation["agent"] self.assertEqual(mentor_user["email"], self.mentor.email) self.assertEqual(mentor_user["id"], str(self.mentor.id)) @@ -300,18 +298,17 @@ class LearningMentorAPITest(APITestCase): role=CourseSessionUser.Role.MEMBER, ) - learning_mentor = LearningMentor.objects.create( - mentor=self.mentor, course_session=self.course_session + relation = AgentParticipantRelation.objects.create( + agent=self.mentor, + participant=participant_cs_user, + role=AgentParticipantRoleType.LEARNING_MENTOR.value, ) - learning_mentor.participants.add(participant_cs_user) - remove_url = reverse( - "remove_participant_from_mentor", + "delete_agent_participant_relation", kwargs={ "course_session_id": self.course_session.id, - "mentor_id": learning_mentor.id, - "participant_user_id": participant_cs_user.user.id, + "relation_id": relation.id, }, ) @@ -320,9 +317,7 @@ class LearningMentorAPITest(APITestCase): # THEN self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - self.assertFalse( - LearningMentor.objects.filter(participants=participant_cs_user).exists() - ) + self.assertEqual(AgentParticipantRelation.objects.count(), 0) def test_remove_myself_from_mentor(self) -> None: # GIVEN @@ -335,18 +330,17 @@ class LearningMentorAPITest(APITestCase): role=CourseSessionUser.Role.MEMBER, ) - learning_mentor = LearningMentor.objects.create( - mentor=self.mentor, course_session=self.course_session + relation = AgentParticipantRelation.objects.create( + agent=self.mentor, + participant=participant_cs_user, + role=AgentParticipantRoleType.LEARNING_MENTOR.value, ) - learning_mentor.participants.add(participant_cs_user) - remove_url = reverse( - "remove_participant_from_mentor", + "delete_agent_participant_relation", kwargs={ "course_session_id": self.course_session.id, - "mentor_id": learning_mentor.id, - "participant_user_id": participant_cs_user.user.id, + "relation_id": relation.id, }, ) @@ -355,26 +349,54 @@ class LearningMentorAPITest(APITestCase): # THEN self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - self.assertFalse( - LearningMentor.objects.filter(participants=participant_cs_user).exists() - ) + self.assertEqual(AgentParticipantRelation.objects.count(), 0) def test_mentor_multiple_courses(self) -> None: # GIVEN course_a, _ = create_course("Course A") course_session_a = create_course_session(course=course_a, title="Test A") + participant_a = create_user("participant_a") + participant_course_session_a = add_course_session_user( + course_session_a, + participant_a, + role=CourseSessionUser.Role.MEMBER, + ) course_b, _ = create_course("Course B") course_session_b = create_course_session(course=course_b, title="Test B") + participant_b = create_user("participant_b") + participant_course_session_b = add_course_session_user( + course_session_b, + participant_b, + role=CourseSessionUser.Role.MEMBER, + ) # WHEN - LearningMentor.objects.create( - mentor=self.mentor, course_session=course_session_a + relation_a = AgentParticipantRelation.objects.create( + agent=self.mentor, + participant=participant_course_session_a, + role=AgentParticipantRoleType.LEARNING_MENTOR.value, + ) + relation_b = AgentParticipantRelation.objects.create( + agent=self.mentor, + participant=participant_course_session_b, + role=AgentParticipantRoleType.LEARNING_MENTOR.value, ) - LearningMentor.objects.create( - mentor=self.mentor, course_session=course_session_b + self.client.force_login(self.mentor) + + response_a = self.client.get( + reverse("mentor_summary", kwargs={"course_session_id": course_session_a.id}) + ) + self.assertEqual(len(response_a.data["participant_relations"]), 1) + self.assertEqual( + response_a.data["participant_relations"][0]["id"], str(relation_a.id) ) - # THEN - self.assertEqual(LearningMentor.objects.count(), 2) + response_b = self.client.get( + reverse("mentor_summary", kwargs={"course_session_id": course_session_b.id}) + ) + self.assertEqual(len(response_b.data["participant_relations"]), 1) + self.assertEqual( + response_b.data["participant_relations"][0]["id"], str(relation_b.id) + ) diff --git a/server/vbv_lernwelt/learning_mentor/urls.py b/server/vbv_lernwelt/learning_mentor/urls.py index b64ccd4e..47fd479d 100644 --- a/server/vbv_lernwelt/learning_mentor/urls.py +++ b/server/vbv_lernwelt/learning_mentor/urls.py @@ -6,9 +6,9 @@ urlpatterns = [ path("summary", views.mentor_summary, name="mentor_summary"), path("mentors", views.list_user_mentors, name="list_user_mentors"), path( - "mentors//remove/", - views.remove_participant_from_mentor, - name="remove_participant_from_mentor", + "mentors//delete", + views.delete_agent_participant_relation, + name="delete_agent_participant_relation", ), path("invitations", views.list_invitations, name="list_invitations"), path("invitations/create", views.create_invitation, name="create_invitation"), diff --git a/server/vbv_lernwelt/learning_mentor/views.py b/server/vbv_lernwelt/learning_mentor/views.py index 4eaf517c..f2c27705 100644 --- a/server/vbv_lernwelt/learning_mentor/views.py +++ b/server/vbv_lernwelt/learning_mentor/views.py @@ -16,11 +16,15 @@ from vbv_lernwelt.learning_mentor.content.praxis_assignment import ( from vbv_lernwelt.learning_mentor.content.self_evaluation_feedback import ( get_self_feedback_evaluation, ) -from vbv_lernwelt.learning_mentor.models import LearningMentor, MentorInvitation +from vbv_lernwelt.learning_mentor.models import ( + AgentParticipantRelation, + AgentParticipantRoleType, + MentorInvitation, +) from vbv_lernwelt.learning_mentor.serializers import ( + AgentParticipantRelationSerializer, InvitationSerializer, MentorAssignmentStatusSerializer, - MentorSerializer, ) from vbv_lernwelt.learnpath.models import Circle from vbv_lernwelt.notify.email.email_services import EmailTemplate, send_email @@ -31,12 +35,16 @@ from vbv_lernwelt.notify.email.email_services import EmailTemplate, send_email def mentor_summary(request, course_session_id: int): course_session = CourseSession.objects.get(id=course_session_id) - mentor = get_object_or_404( - LearningMentor, mentor=request.user, course_session=course_session + participant_qs = AgentParticipantRelation.objects.filter( + agent=request.user, + participant__course_session=course_session, + role=AgentParticipantRoleType.LEARNING_MENTOR.value, ) - participants = mentor.participants.filter(course_session=course_session) - users = [p.user for p in participants] + if participant_qs.count() == 0: + return Response(status=status.HTTP_404_NOT_FOUND) + + users = [p.participant.user for p in participant_qs] assignments = [] circle_ids = set() @@ -71,8 +79,9 @@ def mentor_summary(request, course_session_id: int): ) return Response( { - "mentor_id": mentor.id, - "participants": [UserSerializer(user).data for user in users], + "participant_relations": AgentParticipantRelationSerializer( + participant_qs, many=True + ).data, "circles": list( Circle.objects.filter(id__in=circle_ids).values("id", "title") ), @@ -176,27 +185,24 @@ def list_user_mentors(request, course_session_id: int): CourseSessionUser, user=request.user, course_session=course_session ) - mentors = LearningMentor.objects.filter( - course_session=course_session, participants=course_session_user + mentors = AgentParticipantRelation.objects.filter( + participant=course_session_user, + role=AgentParticipantRoleType.LEARNING_MENTOR.value, ) - return Response(MentorSerializer(mentors, many=True).data) + return Response(AgentParticipantRelationSerializer(mentors, many=True).data) @api_view(["DELETE"]) @permission_classes([IsAuthenticated]) -def remove_participant_from_mentor( - request, course_session_id: int, mentor_id: int, participant_user_id: uuid.UUID +def delete_agent_participant_relation( + request, course_session_id: int, relation_id: uuid.UUID ): requester_user = request.user - mentor = get_object_or_404( - LearningMentor, id=mentor_id, course_session_id=course_session_id - ) + relation = get_object_or_404(AgentParticipantRelation, id=relation_id) - is_requester_mentor = requester_user.id == mentor.mentor_id - is_requester_participant = mentor.participants.filter( - user=requester_user, course_session_id=course_session_id - ).exists() + is_requester_mentor = requester_user.id == relation.agent.id + is_requester_participant = requester_user.id == relation.participant.user.id if not is_requester_mentor and not is_requester_participant: return Response( @@ -204,12 +210,7 @@ def remove_participant_from_mentor( status=status.HTTP_403_FORBIDDEN, ) - course_session = get_object_or_404(CourseSession, id=course_session_id) - course_session_user = get_object_or_404( - CourseSessionUser, user=participant_user_id, course_session=course_session - ) - - mentor.participants.remove(course_session_user) + relation.delete() return Response(status=status.HTTP_204_NO_CONTENT) @@ -228,11 +229,12 @@ def accept_invitation(request, course_session_id: int): status=status.HTTP_400_BAD_REQUEST, ) - mentor, _ = LearningMentor.objects.get_or_create( - mentor=request.user, course_session=course_session + AgentParticipantRelation.objects.get_or_create( + agent=request.user, + participant=invitation.participant, + role=AgentParticipantRoleType.LEARNING_MENTOR.value, ) - mentor.participants.add(invitation.participant) invitation.delete() return Response( diff --git a/server/vbv_lernwelt/media_files/views.py b/server/vbv_lernwelt/media_files/views.py index 55f37610..1d9abb95 100644 --- a/server/vbv_lernwelt/media_files/views.py +++ b/server/vbv_lernwelt/media_files/views.py @@ -2,6 +2,7 @@ import imghdr from wsgiref.util import FileWrapper import structlog +from django.conf import settings from django.contrib.auth.decorators import login_required from django.http import HttpResponse, StreamingHttpResponse from django.shortcuts import get_object_or_404 @@ -21,6 +22,16 @@ def user_image(request, image_id): try: rendition = image.get_rendition(filter_spec) + + rendition.file.open("rb") + image_format = imghdr.what(rendition.file) + + return StreamingHttpResponse( + FileWrapper(rendition.file), + content_type=( + f"image/{image_format}" if image_format else "binary/octet-stream" + ), + ) except SourceImageIOError: return HttpResponse( "Source image file not found", content_type="text/plain", status=410 @@ -31,11 +42,14 @@ def user_image(request, image_id): content_type="text/plain", status=400, ) - - rendition.file.open("rb") - image_format = imghdr.what(rendition.file) - - return StreamingHttpResponse( - FileWrapper(rendition.file), - content_type=f"image/{image_format}" if image_format else "binary/octet-stream", - ) + except Exception: + if settings.APP_ENVIRONMENT.startswith("local"): + # do not spam the console + logger.warning("Error while serving image") + else: + logger.exception( + "Error while serving image", exc_info=True, label="s3_mediafiles" + ) + return HttpResponse( + "Error while serving image", content_type="text/plain", status=400 + ) diff --git a/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py b/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py index adb22c0b..d95de0c8 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py @@ -14,7 +14,7 @@ from vbv_lernwelt.course.creators.test_utils import ( ) from vbv_lernwelt.course.models import CourseCompletionStatus, CourseSessionUser from vbv_lernwelt.course.services import mark_course_completion -from vbv_lernwelt.learning_mentor.models import LearningMentor +from vbv_lernwelt.learning_mentor.models import AgentParticipantRelation from vbv_lernwelt.self_evaluation_feedback.models import ( CourseCompletionFeedback, SelfEvaluationFeedback, @@ -51,13 +51,10 @@ class SelfEvaluationFeedbackAPI(APITestCase): title="Test Circle", course_page=self.course_page ) - learning_mentor = LearningMentor.objects.create( - mentor=self.mentor, - course_session=self.course_session, + AgentParticipantRelation.objects.create( + agent=self.mentor, participant=member_csu, role="LEARNING_MENTOR" ) - learning_mentor.participants.add(member_csu) - @patch( "vbv_lernwelt.notify.services.NotificationService.send_self_evaluation_feedback_request_feedback_notification" ) diff --git a/server/vbv_lernwelt/self_evaluation_feedback/views.py b/server/vbv_lernwelt/self_evaluation_feedback/views.py index f725273c..bb75d116 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/views.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/views.py @@ -11,7 +11,7 @@ from vbv_lernwelt.core.models import User from vbv_lernwelt.core.serializers import UserSerializer from vbv_lernwelt.course.models import CourseCompletion, CourseSession from vbv_lernwelt.iam.permissions import can_view_course_completions -from vbv_lernwelt.learning_mentor.models import LearningMentor +from vbv_lernwelt.learning_mentor.models import AgentParticipantRelation from vbv_lernwelt.learnpath.models import Circle, LearningUnit from vbv_lernwelt.notify.services import NotificationService from vbv_lernwelt.self_evaluation_feedback.models import ( @@ -38,10 +38,9 @@ def start_self_evaluation_feedback(request, learning_unit_id): learning_unit = get_object_or_404(LearningUnit, id=learning_unit_id) feedback_provider_user = get_object_or_404(User, id=feedback_provider_user_id) - if not LearningMentor.objects.filter( - course_session__course=learning_unit.get_course(), - mentor=feedback_provider_user, - participants__user=request.user, + if not AgentParticipantRelation.objects.filter( + agent=feedback_provider_user, + participant__user=request.user, ).exists(): raise PermissionDenied() diff --git a/server/vbv_lernwelt/shop/migrations/0016_alter_checkoutinformation_refno2.py b/server/vbv_lernwelt/shop/migrations/0016_alter_checkoutinformation_refno2.py new file mode 100644 index 00000000..23520b90 --- /dev/null +++ b/server/vbv_lernwelt/shop/migrations/0016_alter_checkoutinformation_refno2.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.25 on 2024-07-17 14:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("shop", "0015_cembra_fields"), + ] + + operations = [ + migrations.AlterField( + model_name="checkoutinformation", + name="refno2", + field=models.CharField(max_length=255), + ), + ] diff --git a/server/vbv_lernwelt/sso/admin.py b/server/vbv_lernwelt/sso/admin.py index afd9b3aa..42dabe9c 100644 --- a/server/vbv_lernwelt/sso/admin.py +++ b/server/vbv_lernwelt/sso/admin.py @@ -5,7 +5,10 @@ from keycloak.exceptions import KeycloakDeleteError, KeycloakPostError from vbv_lernwelt.course.models import CourseSessionUser from vbv_lernwelt.course_session_group.models import CourseSessionGroup -from vbv_lernwelt.learning_mentor.models import LearningMentor +from vbv_lernwelt.learning_mentor.models import ( + AgentParticipantRelation, + AgentParticipantRoleType, +) from vbv_lernwelt.sso.models import SsoSyncError, SsoUser from vbv_lernwelt.sso.role_sync.services import ( create_and_update_user, @@ -20,7 +23,7 @@ def create_sso_user_from_admin(user: User, request): create_and_update_user(user) # noqa user.save() messages.add_message( - request, messages.SUCCESS, f"Der Bentuzer wurde in Keycloak erstellt." + request, messages.SUCCESS, "Der Bentuzer wurde in Keycloak erstellt." ) except KeycloakPostError as e: messages.add_message( @@ -31,24 +34,26 @@ def create_sso_user_from_admin(user: User, request): def sync_sso_roles_from_admin(user: User, request): - course_roles = [ + course_roles = { (csu.course_session.course.slug, csu.role) for csu in CourseSessionUser.objects.filter(user=user) - ] + } - course_roles += [ - (lm.course_session.course.slug, "LEARNING_MENTOR") - for lm in LearningMentor.objects.filter(mentor=user) - ] + course_roles += { + (relation.participant.course_session.course.slug, "LEARNING_MENTOR") + for relation in AgentParticipantRelation.objects.filter( + agent=user, role=AgentParticipantRoleType.LEARNING_MENTOR.value + ) + } for csg in CourseSessionGroup.objects.filter(supervisor=user): for course_session in csg.course_session.all(): - course_roles.append((course_session.course.slug, "SUPERVISOR")) + course_roles.add((course_session.course.slug, "SUPERVISOR")) try: sync_roles_for_user(user, course_roles) messages.add_message( - request, messages.SUCCESS, f"Die Daten wurden mit Keycloak synchronisiert." + request, messages.SUCCESS, "Die Daten wurden mit Keycloak synchronisiert." ) except KeycloakDeleteError as e: messages.add_message( diff --git a/server/vbv_lernwelt/sso/role_sync/services.py b/server/vbv_lernwelt/sso/role_sync/services.py index f3c2c563..706c63ba 100644 --- a/server/vbv_lernwelt/sso/role_sync/services.py +++ b/server/vbv_lernwelt/sso/role_sync/services.py @@ -1,5 +1,5 @@ import unicodedata -from typing import Dict, List, Tuple +from typing import Dict, List, Set, Tuple import structlog from django.conf import settings @@ -12,7 +12,7 @@ from vbv_lernwelt.sso.role_sync.roles import ROLE_IDS, SSO_ROLES logger = structlog.get_logger(__name__) -CourseRolesType = List[Tuple[str, str]] +CourseRolesType = Set[Tuple[str, str]] KeyCloakRolesType = List[Dict[str, str]] keycloak_admin = None # Needed for pytest diff --git a/server/vbv_lernwelt/sso/signals.py b/server/vbv_lernwelt/sso/signals.py index 56f4f83e..ec44ee59 100644 --- a/server/vbv_lernwelt/sso/signals.py +++ b/server/vbv_lernwelt/sso/signals.py @@ -6,7 +6,10 @@ from keycloak.exceptions import KeycloakDeleteError, KeycloakError, KeycloakPost from vbv_lernwelt.core.models import User from vbv_lernwelt.course.models import CourseSessionUser from vbv_lernwelt.course_session_group.models import CourseSessionGroup -from vbv_lernwelt.learning_mentor.models import LearningMentor +from vbv_lernwelt.learning_mentor.models import ( + AgentParticipantRelation, + AgentParticipantRoleType, +) from vbv_lernwelt.sso.role_sync.services import ( add_roles_to_user, remove_roles_from_user, @@ -78,21 +81,39 @@ def update_sso_roles_in_csg(sender, instance, action, reverse, model, pk_set, ** # LearningMentor -@receiver(post_delete, sender=LearningMentor, dispatch_uid="delete_sso_roles_in_lm") -def remove_sso_roles_in_lm(sender, instance: LearningMentor, **kwargs): - if not LearningMentor.objects.filter( - mentor=instance.mentor, course_session__course=instance.course_session.course +@receiver( + post_delete, sender=AgentParticipantRelation, dispatch_uid="delete_sso_roles_in_lm" +) +def remove_sso_roles_in_lm(sender, instance: AgentParticipantRelation, **kwargs): + if not AgentParticipantRelation.objects.filter( + agent=instance.agent, + participant__course_session__course=instance.participant.course_session.course, + role=AgentParticipantRoleType.LEARNING_MENTOR.value, ).exists(): _remove_sso_role( - instance.mentor, instance.course_session.course.slug, "LEARNING_MENTOR" + instance.agent, + instance.participant.course_session.course.slug, + "LEARNING_MENTOR", ) -@receiver(pre_save, sender=LearningMentor, dispatch_uid="update_sso_roles_in_lm") -def update_sso_roles_in_lm(sender, instance: LearningMentor, **kwargs): - if not instance.pk: +@receiver( + pre_save, sender=AgentParticipantRelation, dispatch_uid="update_sso_roles_in_lm" +) +def update_sso_roles_in_lm(sender, instance: AgentParticipantRelation, **kwargs): + if ( + instance.role == AgentParticipantRoleType.LEARNING_MENTOR.value + and AgentParticipantRelation.objects.filter( + agent=instance.agent, + participant__course_session__course=instance.participant.course_session.course, + role=AgentParticipantRoleType.LEARNING_MENTOR.value, + ).count() + == 0 + ): _add_sso_role( - instance.mentor, instance.course_session.course.slug, "LEARNING_MENTOR" + instance.agent, + instance.participant.course_session.course.slug, + "LEARNING_MENTOR", ) diff --git a/server/vbv_lernwelt/sso/tests/test_signals.py b/server/vbv_lernwelt/sso/tests/test_signals.py index 70697ee7..dfa30a1c 100644 --- a/server/vbv_lernwelt/sso/tests/test_signals.py +++ b/server/vbv_lernwelt/sso/tests/test_signals.py @@ -24,7 +24,7 @@ from vbv_lernwelt.course.creators.test_utils import ( ) from vbv_lernwelt.course.models import Course, CourseSessionUser from vbv_lernwelt.course_session_group.models import CourseSessionGroup -from vbv_lernwelt.learning_mentor.models import LearningMentor +from vbv_lernwelt.learning_mentor.models import AgentParticipantRelation from vbv_lernwelt.sso.signals import update_sso_roles_in_cs @@ -217,10 +217,16 @@ class LearningMentorTests(TestCase): def setUp(self): self.course, self.course_page = create_course("Test Course") self.course_session = create_course_session(course=self.course, title="Test VV") - self.user = create_user("mentor") - self.mentor = LearningMentor.objects.create( - mentor=self.user, course_session=self.course_session + + participant = create_user("participant") + self.csu = add_course_session_user( + self.course_session, + participant, + role=CourseSessionUser.Role.MEMBER, + ) + self.relation = AgentParticipantRelation.objects.create( + agent=self.user, participant=self.csu, role="LEARNING_MENTOR" ) @patch("vbv_lernwelt.sso.signals.remove_roles_from_user") @@ -229,7 +235,7 @@ class LearningMentorTests(TestCase): ): mock_remove_roles_from_user.return_value = None - self.mentor.delete() + self.relation.delete() self.assertEqual(mock_remove_roles_from_user.call_count, 1) @@ -240,10 +246,10 @@ class LearningMentorTests(TestCase): @patch("vbv_lernwelt.sso.signals.add_roles_to_user") def test_add_roles_for_learning_mentor_on_create(self, mock_add_roles_from_user): mock_add_roles_from_user.return_value = None - self.mentor.delete() + self.relation.delete() - LearningMentor.objects.create( - mentor=self.user, course_session=self.course_session + AgentParticipantRelation.objects.create( + agent=self.user, participant=self.csu, role="LEARNING_MENTOR" ) self.assertEqual(mock_add_roles_from_user.call_count, 1) @@ -263,5 +269,8 @@ class LearningMentorTests(TestCase): ) mock_add_roles_from_user.reset_mock() - self.mentor.participants.set([participant_1]) + + AgentParticipantRelation.objects.create( + agent=self.user, participant=participant_1, role="LEARNING_MENTOR" + ) self.assertEqual(mock_add_roles_from_user.call_count, 0)