- {{ 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)