Refactor `LearningMentor` model to flat `AgentParticipantRelation` model

This commit is contained in:
Daniel Egger 2024-07-21 15:44:58 +02:00
parent 3f02fd254a
commit cdfb9d2c5b
45 changed files with 761 additions and 483 deletions

View File

@ -8,9 +8,9 @@ import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
const courseSession = useCurrentCourseSession(); const courseSession = useCurrentCourseSession();
const { isLoading, summary, fetchData } = useLearningMentees(courseSession.value.id); const { isLoading, summary, fetchData } = useLearningMentees(courseSession.value.id);
const removeMyMentee = async (menteeId: string) => { const removeMyMentee = async (relationId: string) => {
await useCSRFFetch( await useCSRFFetch(
`/api/mentor/${courseSession.value.id}/mentors/${summary.value?.mentor_id}/remove/${menteeId}` `/api/mentor/${courseSession.value.id}/mentors/${relationId}/delete`
).delete(); ).delete();
fetchData(); fetchData();
}; };
@ -28,25 +28,31 @@ const noMenteesText = computed(() =>
</div> </div>
<div v-else> <div v-else>
<h2 class="heading-2 py-6">{{ $t("a.Personen, die du begleitest") }}</h2> <h2 class="heading-2 py-6">{{ $t("a.Personen, die du begleitest") }}</h2>
<div v-if="(summary?.participants?.length ?? 0) > 0" class="bg-white px-4 py-2"> <div
v-if="(summary?.participant_relations.length ?? 0) > 0"
class="bg-white px-4 py-2"
>
<div <div
v-for="participant in summary?.participants ?? []" v-for="relation in summary?.participant_relations ?? []"
:key="participant.id" :key="relation.id"
data-cy="lm-my-mentee-list-item" data-cy="lm-my-mentee-list-item"
class="flex flex-col items-start justify-between gap-4 border-b py-2 last:border-b-0 md:flex-row md:items-center md:gap-16" class="flex flex-col items-start justify-between gap-4 border-b py-2 last:border-b-0 md:flex-row md:items-center md:gap-16"
> >
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<img <img
:alt="participant.last_name" :alt="relation.participant_user.last_name"
class="h-11 w-11 rounded-full" class="h-11 w-11 rounded-full"
:src="participant.avatar_url || '/static/avatars/myvbv-default-avatar.png'" :src="
relation.participant_user.avatar_url ||
'/static/avatars/myvbv-default-avatar.png'
"
/> />
<div> <div>
<div class="text-bold"> <div class="text-bold">
{{ participant.first_name }} {{ relation.participant_user.first_name }}
{{ participant.last_name }} {{ relation.participant_user.last_name }}
</div> </div>
{{ participant.email }} {{ relation.participant_user.email }}
</div> </div>
</div> </div>
<div class="space-x-5"> <div class="space-x-5">
@ -55,7 +61,7 @@ const noMenteesText = computed(() =>
:to="{ :to="{
name: 'profileLearningPath', name: 'profileLearningPath',
params: { params: {
userId: participant.id, userId: relation.participant_user.id,
courseSlug: courseSession.course.slug, courseSlug: courseSession.course.slug,
}, },
}" }"
@ -66,7 +72,7 @@ const noMenteesText = computed(() =>
<button <button
class="underline" class="underline"
data-cy="lm-my-mentee-remove" data-cy="lm-my-mentee-remove"
@click="removeMyMentee(participant.id)" @click="removeMyMentee(relation.id)"
> >
{{ $t("a.Entfernen") }} {{ $t("a.Entfernen") }}
</button> </button>

View File

@ -51,11 +51,9 @@ const removeInvitation = async (invitationId: string) => {
await refreshInvitations(); await refreshInvitations();
}; };
const userStore = useUserStore(); const removeMyMentor = async (relationId: string) => {
const removeMyMentor = async (mentorId: string) => {
await useCSRFFetch( await useCSRFFetch(
`/api/mentor/${courseSession.value.id}/mentors/${mentorId}/remove/${userStore.id}` `/api/mentor/${courseSession.value.id}/mentors/${relationId}/delete`
).delete(); ).delete();
await refreshMentors(); await refreshMentors();
}; };
@ -138,16 +136,16 @@ const noLearningMentors = computed(() =>
> >
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<img <img
:alt="learningMentor.mentor.last_name" :alt="learningMentor.agent.last_name"
class="h-11 w-11 rounded-full" class="h-11 w-11 rounded-full"
:src="learningMentor.mentor.avatar_url" :src="learningMentor.agent.avatar_url"
/> />
<div> <div>
<div class="text-bold"> <div class="text-bold">
{{ learningMentor.mentor.first_name }} {{ learningMentor.agent.first_name }}
{{ learningMentor.mentor.last_name }} {{ learningMentor.agent.last_name }}
</div> </div>
{{ learningMentor.mentor.email }} {{ learningMentor.agent.email }}
</div> </div>
</div> </div>
<button <button

View File

@ -74,9 +74,9 @@ const onSubmit = async () => {
<option <option
v-for="learningMentor in learningMentors" v-for="learningMentor in learningMentors"
:key="learningMentor.id" :key="learningMentor.id"
:value="learningMentor.mentor.id" :value="learningMentor.agent.id"
> >
{{ learningMentor.mentor.first_name }} {{ learningMentor.mentor.last_name }} {{ learningMentor.agent.first_name }} {{ learningMentor.agent.last_name }}
</option> </option>
</select> </select>
</div> </div>

View File

@ -39,7 +39,7 @@ import type {
CourseSessionDetail, CourseSessionDetail,
DashboardPersonsPageMode, DashboardPersonsPageMode,
LearningContentWithCompletion, LearningContentWithCompletion,
LearningMentor, AgentParticipantRelation,
LearningPathType, LearningPathType,
LearningUnitPerformanceCriteria, LearningUnitPerformanceCriteria,
PerformanceCriteria, PerformanceCriteria,
@ -489,7 +489,7 @@ export function useFileUpload() {
} }
export function useMyLearningMentors() { export function useMyLearningMentors() {
const learningMentors = ref<LearningMentor[]>([]); const learningMentors = ref<AgentParticipantRelation[]>([]);
const currentCourseSessionId = useCurrentCourseSession().value.id; const currentCourseSessionId = useCurrentCourseSession().value.id;
const loading = ref(false); const loading = ref(false);

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Assignment, Participant } from "@/services/learningMentees"; import type { Assignment, UserShort } from "@/services/learningMentees";
import { useLearningMentees } from "@/services/learningMentees"; import { useLearningMentees } from "@/services/learningMentees";
import { computed, onMounted, type Ref } from "vue"; import { computed, onMounted, type Ref } from "vue";
import { useCurrentCourseSession } from "@/composables"; import { useCurrentCourseSession } from "@/composables";
@ -11,13 +11,16 @@ const props = defineProps<{
const courseSession = useCurrentCourseSession(); const courseSession = useCurrentCourseSession();
const learningMentees = useLearningMentees(courseSession.value.id); const learningMentees = useLearningMentees(courseSession.value.id);
const participants = computed(() => learningMentees.summary.value?.participants);
const praxisAssignment: Ref<Assignment | null> = computed(() => const praxisAssignment: Ref<Assignment | null> = computed(() =>
learningMentees.getAssignmentById(props.praxisAssignmentId) learningMentees.getAssignmentById(props.praxisAssignmentId)
); );
const getParticipantById = (id: string): Participant | null => { const getParticipantById = (id: string): UserShort | undefined => {
return participants.value?.find((participant) => participant.id === id) || null; return (learningMentees.summary.value?.participant_relations ?? [])
.map((rel) => {
return rel.participant_user;
})
.find((user) => user.id === id);
}; };
onMounted(() => { onMounted(() => {

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Assignment, Participant } from "@/services/learningMentees"; import type { Assignment, UserShort } from "@/services/learningMentees";
import { useLearningMentees } from "@/services/learningMentees"; import { useLearningMentees } from "@/services/learningMentees";
import { computed, type Ref } from "vue"; import { computed, type Ref } from "vue";
import { useCurrentCourseSession } from "@/composables"; import { useCurrentCourseSession } from "@/composables";
@ -15,14 +15,12 @@ const selfEvaluationFeedback: Ref<Assignment | null> = computed(() =>
learningMentees.getAssignmentById(props.learningUnitId) learningMentees.getAssignmentById(props.learningUnitId)
); );
const getParticipantById = (id: string): Participant | null => { const getParticipantById = (id: string): UserShort | undefined => {
if (learningMentees.summary.value?.participants) { return (learningMentees.summary.value?.participant_relations ?? [])
const found = learningMentees.summary.value.participants.find( .map((rel) => {
(item) => item.id === id return rel.participant_user;
); })
return found || null; .find((user) => user.id === id);
}
return null;
}; };
</script> </script>

View File

@ -29,8 +29,8 @@ const isMentorsLoading = computed(() => learningMentors.loading.value);
const mentors = computed(() => { const mentors = computed(() => {
return learningMentors.learningMentors.value.map((mentor) => ({ return learningMentors.learningMentors.value.map((mentor) => ({
id: mentor.mentor.id, id: mentor.agent.id,
name: `${mentor.mentor.first_name} ${mentor.mentor.last_name}`, name: `${mentor.agent.first_name} ${mentor.agent.last_name}`,
})); }));
}); });

View File

@ -2,7 +2,7 @@ import { itGet } from "@/fetchHelpers";
import type { Ref } from "vue"; import type { Ref } from "vue";
import { ref, watchEffect } from "vue"; import { ref, watchEffect } from "vue";
export interface Participant { export interface UserShort {
id: string; id: string;
first_name: string; first_name: string;
last_name: string; last_name: string;
@ -12,6 +12,14 @@ export interface Participant {
language: string; language: string;
} }
export interface AgentParticipantRelation {
id: string;
role: "LEARNING_MENTOR";
course_session_id: number;
agent: UserShort;
participant_user: UserShort;
}
interface Circle { interface Circle {
id: number; id: number;
title: string; title: string;
@ -40,9 +48,8 @@ export interface Assignment {
type: string; type: string;
} }
export interface Summary { export interface LearningMentorSummary {
mentor_id: string; participant_relations: AgentParticipantRelation[];
participants: Participant[];
circles: Circle[]; circles: Circle[];
assignments: Assignment[]; assignments: Assignment[];
} }
@ -51,7 +58,7 @@ export const useLearningMentees = (
courseSessionId: string | Ref<string> | (() => string) courseSessionId: string | Ref<string> | (() => string)
) => { ) => {
const isLoading = ref(false); const isLoading = ref(false);
const summary: Ref<Summary | null> = ref(null); const summary: Ref<LearningMentorSummary | null> = ref(null);
const error = ref(null); const error = ref(null);
const getAssignmentById = (id: string): Assignment | null => { const getAssignmentById = (id: string): Assignment | null => {

View File

@ -468,15 +468,16 @@ export interface ExpertSessionUser extends CourseSessionUser {
role: "EXPERT"; role: "EXPERT";
} }
export interface Mentor { export interface Agent {
id: number; id: number;
first_name: string; first_name: string;
last_name: string; last_name: string;
} }
export interface LearningMentor { export interface AgentParticipantRelation {
id: number; id: number;
mentor: Mentor; role: "LEARNING_MENTOR";
agent: Agent;
} }
export type CourseSessionDetail = CourseSessionObjectType; export type CourseSessionDetail = CourseSessionObjectType;

View File

@ -39,9 +39,9 @@ def send_learning_mentor_invitation():
recipient_email="daniel.egger+sendgrid@gmail.com", recipient_email="daniel.egger+sendgrid@gmail.com",
template=EmailTemplate.LEARNING_MENTOR_INVITATION, template=EmailTemplate.LEARNING_MENTOR_INVITATION,
template_data={ template_data={
"inviter_name": f"Daniel Egger", "inviter_name": "Daniel Egger",
"inviter_email": "daniel.egger@example.com", "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", template_language="de",
fail_silently=True, fail_silently=True,

View File

@ -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.models import CourseSessionAttendanceCourse
from vbv_lernwelt.course_session.services.attendance import AttendanceUserStatus from vbv_lernwelt.course_session.services.attendance import AttendanceUserStatus
from vbv_lernwelt.feedback.models import FeedbackResponse 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 ( from vbv_lernwelt.learnpath.models import (
LearningContentAttendanceCourse, LearningContentAttendanceCourse,
LearningContentFeedbackUK, LearningContentFeedbackUK,
@ -144,7 +147,8 @@ def command(
SelfEvaluationFeedback.objects.all().delete() SelfEvaluationFeedback.objects.all().delete()
CourseCompletionFeedback.objects.all().delete() CourseCompletionFeedback.objects.all().delete()
LearningMentor.objects.all().delete() AgentParticipantRelation.objects.all().delete()
# LearningMentor.objects.all().delete()
MentorInvitation.objects.all().delete() MentorInvitation.objects.all().delete()
User.objects.all().update(organisation=Organisation.objects.first()) User.objects.all().update(organisation=Organisation.objects.first())
User.objects.all().update(language="de") User.objects.all().update(language="de")
@ -414,48 +418,40 @@ def command(
if create_learning_mentor: if create_learning_mentor:
cs_bern = CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID) cs_bern = CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID)
AgentParticipantRelation.objects.create(
uk_mentor = LearningMentor.objects.create( agent=User.objects.get(id=TEST_MENTOR1_USER_ID),
mentor=User.objects.get(id=TEST_MENTOR1_USER_ID), participant=CourseSessionUser.objects.get(
course_session=cs_bern, user__id=TEST_STUDENT1_USER_ID, course_session=cs_bern
) ),
uk_mentor.participants.add( role="LEARNING_MENTOR",
CourseSessionUser.objects.get(
user__id=TEST_STUDENT1_USER_ID,
course_session=cs_bern,
)
) )
vv_course = Course.objects.get(id=COURSE_VERSICHERUNGSVERMITTLERIN_ID) vv_course = Course.objects.get(id=COURSE_VERSICHERUNGSVERMITTLERIN_ID)
vv_course_session = CourseSession.objects.get(course=vv_course) 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( AgentParticipantRelation.objects.create(
CourseSessionUser.objects.get( agent=User.objects.get(id=TEST_MENTOR1_USER_ID),
user__id=TEST_STUDENT1_VV_USER_ID, course_session=vv_course_session participant=CourseSessionUser.objects.get(
)
)
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(
user__id=TEST_STUDENT1_VV_USER_ID, user__id=TEST_STUDENT1_VV_USER_ID,
course_session=vv_course_session, 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) course = Course.objects.get(id=COURSE_TEST_ID)

View File

@ -24,7 +24,10 @@ from vbv_lernwelt.course_session.models import (
) )
from vbv_lernwelt.course_session_group.models import CourseSessionGroup from vbv_lernwelt.course_session_group.models import CourseSessionGroup
from vbv_lernwelt.feedback.models import FeedbackResponse 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.learnpath.models import Circle
from vbv_lernwelt.notify.models import Notification 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"]) cs = CourseSession.objects.get(import_id=data["ID"])
members, trainer, regionenleiter = get_or_create_users_uk() 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_to_course_session(cs, members)
add_trainers_to_course_session(cs, [trainer], uk_circle_keys, language) 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) create_or_update_assignment_course_session(cs)
members, member_with_mentor, mentor = get_or_create_users_vv() 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_to_course_session(cs, members + [member_with_mentor])
add_mentor_to_course_session(cs, [(mentor, 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: if cs:
CourseCompletion.objects.filter(course_session=cs).delete() CourseCompletion.objects.filter(course_session=cs).delete()
Notification.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() CourseSessionEdoniqTest.objects.filter(course_session=cs).delete()
CourseSessionUser.objects.filter(course_session=cs).delete() CourseSessionUser.objects.filter(course_session=cs).delete()
learning_mentor_ids = (
LearningMentor.objects.filter(participants__course_session=cs) AgentParticipantRelation.objects.filter(course_session=cs).delete()
.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()
else: else:
logger.info("no_course_session_found", import_id=cs.import_id) 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]] course_session: CourseSession, mentor_mentee_pairs: list[tuple[User, User]]
): ):
for mentor, mentee in mentor_mentee_pairs: for mentor, mentee in mentor_mentee_pairs:
lm = LearningMentor.objects.create( AgentParticipantRelation.objects.create(
course_session=course_session, agent=mentor,
mentor=mentor, participant=CourseSessionUser.objects.get(
) user__id=mentee.id, course_session=course_session
lm.participants.add( ),
CourseSessionUser.objects.get( role=AgentParticipantRoleType.LEARNING_MENTOR.value,
user__id=mentee.id,
course_session=course_session,
)
) )

View File

@ -131,6 +131,20 @@ class UserSerializer(serializers.ModelSerializer):
return instance 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 CypressUserSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = User model = User

View File

@ -41,7 +41,10 @@ from vbv_lernwelt.course_session.models import (
) )
from vbv_lernwelt.course_session_group.models import CourseSessionGroup from vbv_lernwelt.course_session_group.models import CourseSessionGroup
from vbv_lernwelt.duedate.models import DueDate 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 ( from vbv_lernwelt.learnpath.models import (
Circle, Circle,
LearningContentAssignment, LearningContentAssignment,
@ -101,13 +104,13 @@ def create_course_session(
def add_learning_mentor( def add_learning_mentor(
course_session: CourseSession, mentor: User, mentee: CourseSessionUser mentor: User, mentee: CourseSessionUser
) -> LearningMentor: ) -> AgentParticipantRelation:
learning_mentor = LearningMentor.objects.create( return AgentParticipantRelation.objects.create(
course_session=course_session, mentor=mentor agent=mentor,
participant=mentee,
role=AgentParticipantRoleType.LEARNING_MENTOR.value,
) )
learning_mentor.participants.add(mentee)
return learning_mentor
def add_course_session_user( def add_course_session_user(

View File

@ -45,10 +45,7 @@ from vbv_lernwelt.competence.create_vv_new_competence_profile import (
create_vv_new_competence_profile, create_vv_new_competence_profile,
) )
from vbv_lernwelt.competence.models import PerformanceCriteria from vbv_lernwelt.competence.models import PerformanceCriteria
from vbv_lernwelt.core.constants import ( from vbv_lernwelt.core.constants import TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID
TEST_MENTOR1_USER_ID,
TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID,
)
from vbv_lernwelt.core.create_default_users import default_users from vbv_lernwelt.core.create_default_users import default_users
from vbv_lernwelt.core.models import User from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.consts import ( from vbv_lernwelt.course.consts import (
@ -95,7 +92,6 @@ from vbv_lernwelt.importer.services import (
import_students_from_excel, import_students_from_excel,
import_trainers_from_excel_for_training, import_trainers_from_excel_for_training,
) )
from vbv_lernwelt.learning_mentor.models import LearningMentor
from vbv_lernwelt.learnpath.create_vv_new_learning_path import ( from vbv_lernwelt.learnpath.create_vv_new_learning_path import (
create_vv_motorfahrzeug_pruefung_learning_path, create_vv_motorfahrzeug_pruefung_learning_path,
create_vv_new_learning_path, create_vv_new_learning_path,
@ -243,6 +239,11 @@ def create_versicherungsvermittlerin_course(
course_session=cs, course_session=cs,
user=User.objects.get(username="student-vv@eiger-versicherungen.ch"), 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( CourseSessionUser.objects.create(
course_session=cs, course_session=cs,
@ -262,30 +263,6 @@ def create_versicherungsvermittlerin_course(
role=CourseSessionUser.Role.EXPERT, 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: for admin_email in ADMIN_EMAILS:
CourseSessionUser.objects.create( CourseSessionUser.objects.create(
course_session=cs, course_session=cs,

View File

@ -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,
),
),
]

View File

@ -27,7 +27,7 @@ from vbv_lernwelt.iam.permissions import (
has_course_access_by_page_request, has_course_access_by_page_request,
is_circle_expert, is_circle_expert,
) )
from vbv_lernwelt.learning_mentor.models import LearningMentor from vbv_lernwelt.learning_mentor.models import AgentParticipantRelation
logger = structlog.get_logger(__name__) logger = structlog.get_logger(__name__)
@ -155,9 +155,10 @@ def get_course_sessions(request):
# enrich with mentor course sessions # enrich with mentor course sessions
mentor_course_sessions = CourseSession.objects.filter( mentor_course_sessions = CourseSession.objects.filter(
id__in=LearningMentor.objects.filter(mentor=request.user).values_list( id__in=[
"course_session", flat=True rel.participant.course_session_id
) for rel in AgentParticipantRelation.objects.filter(agent=request.user)
]
).prefetch_related("course") ).prefetch_related("course")
all_to_serialize = ( all_to_serialize = (

View File

@ -25,7 +25,10 @@ from vbv_lernwelt.iam.permissions import (
can_view_course_session_group_statistics, can_view_course_session_group_statistics,
can_view_course_session_progress, 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 from vbv_lernwelt.learnpath.models import Circle
@ -95,12 +98,15 @@ class DashboardQuery(graphene.ObjectType):
mentees_ids = set() mentees_ids = set()
course_session_ids = set() course_session_ids = set()
mentees = CourseSessionUser.objects.filter( relations_qs = AgentParticipantRelation.objects.filter(
participants__mentor=user, course_session__course=course agent=user,
).values_list("user", "course_session") role=AgentParticipantRoleType.LEARNING_MENTOR.value,
for user_id, course_session_id in mentees: participant__course_session__course=course,
mentees_ids.add(user_id) )
course_session_ids.add(course_session_id)
for relation in relations_qs:
mentees_ids.add(relation.participant.user_id)
course_session_ids.add(relation.participant.course_session_id)
return CourseStatisticsType( return CourseStatisticsType(
_id=f"mentor:{course.id}", # noqa _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( def get_learning_mentor_dashboards(
user: User, exclude_course_ids: Set[int] user: User, exclude_course_ids: Set[int]
) -> Tuple[List[Dict[str, str]], Set[int]]: ) -> Tuple[List[Dict[str, str]], Set[int]]:
learning_mentor = LearningMentor.objects.filter(mentor=user).exclude( learning_mentor_relation_qs = AgentParticipantRelation.objects.filter(
course_session__course__id__in=exclude_course_ids agent=user, role=AgentParticipantRoleType.LEARNING_MENTOR.value
) ).exclude(participant__course_session__course__id__in=exclude_course_ids)
dashboards = [] dashboards = []
course_ids = set() course_ids = set()
for mentor in learning_mentor: for rel in learning_mentor_relation_qs:
course = mentor.course_session.course course = rel.participant.course_session.course
course_ids.add(course.id)
if course.id in UK_COURSE_IDS: if course.id in UK_COURSE_IDS:
dashboard_type = DashboardType.PRAXISBILDNER_DASHBOARD dashboard_type = DashboardType.PRAXISBILDNER_DASHBOARD
else: else:
dashboard_type = DashboardType.MENTOR_DASHBOARD dashboard_type = DashboardType.MENTOR_DASHBOARD
dashboards.append(
{ if course.id not in course_ids:
"id": str(course.id), course_ids.add(course.id)
"name": course.title, dashboards.append(
"slug": course.slug, {
"dashboard_type": dashboard_type, "id": str(course.id),
"course_configuration": course.configuration, "name": course.title,
} "slug": course.slug,
) "dashboard_type": dashboard_type,
"course_configuration": course.configuration,
}
)
return dashboards, course_ids return dashboards, course_ids

View File

@ -110,22 +110,22 @@ class DashboardTestCase(GraphQLTestCase):
self.client.force_login(member) self.client.force_login(member)
query = f"""query($course_id: ID!) {{ query = """query($course_id: ID!) {
course_progress(course_id: $course_id) {{ course_progress(course_id: $course_id) {
course_id course_id
session_to_continue_id session_to_continue_id
competence {{ competence {
total_count total_count
success_count success_count
fail_count fail_count
}} }
assignment {{ assignment {
total_count total_count
points_max_count points_max_count
points_achieved_count points_achieved_count
}} }
}} }
}} }
""" """
variables = {"course_id": str(course.id)} variables = {"course_id": str(course.id)}
@ -268,7 +268,7 @@ class DashboardTestCase(GraphQLTestCase):
role=CourseSessionUser.Role.MEMBER, 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) self.client.force_login(mentor)
@ -287,6 +287,7 @@ class DashboardTestCase(GraphQLTestCase):
# THEN # THEN
self.assertResponseNoErrors(response) self.assertResponseNoErrors(response)
print(response.json())
self.assertEqual(len(response.json()["data"]["dashboard_config"]), 1) self.assertEqual(len(response.json()["data"]["dashboard_config"]), 1)
self.assertEqual( self.assertEqual(
@ -314,7 +315,7 @@ class DashboardTestCase(GraphQLTestCase):
role=CourseSessionUser.Role.MEMBER, 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 # WHEN
self.client.force_login(mentor_and_member) self.client.force_login(mentor_and_member)
@ -350,11 +351,11 @@ class DashboardTestCase(GraphQLTestCase):
self.client.force_login(disallowed_user) self.client.force_login(disallowed_user)
query = f"""query($course_id: ID!) {{ query = """query($course_id: ID!) {
course_statistics(course_id: $course_id) {{ course_statistics(course_id: $course_id) {
course_id course_id
}} }
}} }
""" """
variables = {"course_id": str(course.id)} variables = {"course_id": str(course.id)}
@ -384,13 +385,13 @@ class DashboardTestCase(GraphQLTestCase):
self.client.force_login(supervisor) self.client.force_login(supervisor)
query = f"""query($course_id: ID!) {{ query = """query($course_id: ID!) {
course_statistics(course_id: $course_id) {{ course_statistics(course_id: $course_id) {
course_id course_id
course_title course_title
course_slug course_slug
}} }
}} }
""" """
variables = {"course_id": str(course_2.id)} variables = {"course_id": str(course_2.id)}

View File

@ -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.creators.test_utils import add_course_session_user, create_user
from vbv_lernwelt.course.models import CourseSessionUser from vbv_lernwelt.course.models import CourseSessionUser
from vbv_lernwelt.dashboard.tests.test_views import BaseMentorAssignmentTestCase 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): class MentorStatisticsTestCase(BaseMentorAssignmentTestCase, GraphQLTestCase):
@ -19,15 +19,13 @@ class MentorStatisticsTestCase(BaseMentorAssignmentTestCase, GraphQLTestCase):
self.course.configuration.save() self.course.configuration.save()
self.mentor = create_user("mentor") 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)] self.participants = [create_user(f"participant{i}") for i in range(4)]
def test_assignment_statistics(self): def test_assignment_statistics(self):
# WHEN # WHEN
has_lb = [True, True, True, False] has_lb = [True, True, True, False]
has_passed = [True, False, True, False] has_passed = [True, False, True, False]
for i in range(4): for i in range(4):
csu = add_course_session_user( csu = add_course_session_user(
self.course_session, self.course_session,
@ -35,7 +33,9 @@ class MentorStatisticsTestCase(BaseMentorAssignmentTestCase, GraphQLTestCase):
role=CourseSessionUser.Role.MEMBER, role=CourseSessionUser.Role.MEMBER,
) )
if has_lb[i]: if has_lb[i]:
self.lm.participants.add(csu) AgentParticipantRelation.objects.create(
agent=self.mentor, participant=csu, role="LEARNING_MENTOR"
)
AssignmentCompletion.objects.create( AssignmentCompletion.objects.create(
course_session=self.course_session, course_session=self.course_session,
@ -47,22 +47,22 @@ class MentorStatisticsTestCase(BaseMentorAssignmentTestCase, GraphQLTestCase):
) )
# THEN # THEN
# WHEN # WHEN
query = f"""query ($courseId: ID!) {{ query = """query ($courseId: ID!) {
mentor_course_statistics(course_id: $courseId) {{ mentor_course_statistics(course_id: $courseId) {
course_session_selection_ids course_session_selection_ids
user_selection_ids user_selection_ids
assignments {{ assignments {
_id _id
summary {{ summary {
_id _id
completed_count completed_count
average_passed average_passed
total_passed total_passed
total_failed total_failed
}} }
}} }
}} }
}}""" }"""
# THEN # THEN
variables = {"courseId": str(self.course.id)} variables = {"courseId": str(self.course.id)}

View File

@ -37,7 +37,7 @@ from vbv_lernwelt.dashboard.views import (
get_course_config, get_course_config,
get_course_sessions_with_roles_for_user, 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.learnpath.models import Circle, LearningUnit
from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback
@ -98,14 +98,16 @@ class GetCourseSessionsForUserTestCase(TestCase):
def test_learning_mentor_get_sessions(self): def test_learning_mentor_get_sessions(self):
mentor = create_user("mentor") mentor = create_user("mentor")
LearningMentor.objects.create(mentor=mentor, course_session=self.course_session)
participant = create_user("participant") participant = create_user("participant")
add_course_session_user( csu = add_course_session_user(
self.course_session, self.course_session,
participant, participant,
role=CourseSessionUser.Role.MEMBER, role=CourseSessionUser.Role.MEMBER,
) )
AgentParticipantRelation.objects.create(
agent=mentor, participant=csu, role="LEARNING_MENTOR"
)
sessions = get_course_sessions_with_roles_for_user(mentor) sessions = get_course_sessions_with_roles_for_user(mentor)
@ -187,7 +189,16 @@ class GetDashboardConfig(TestCase):
def test_mentor_uk_get_config(self): def test_mentor_uk_get_config(self):
# GIVEN # GIVEN
mentor = create_user("mentor") 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.is_uk = True
self.course.configuration.save() self.course.configuration.save()
@ -205,7 +216,15 @@ class GetDashboardConfig(TestCase):
def test_mentor_vv_get_config(self): def test_mentor_vv_get_config(self):
# GIVEN # GIVEN
mentor = create_user("mentor") 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.is_vv = True
self.course.configuration.save() self.course.configuration.save()
@ -228,7 +247,16 @@ class GetDashboardConfig(TestCase):
mentor, mentor,
role=CourseSessionUser.Role.MEMBER, 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.is_vv = True
self.course.configuration.save() self.course.configuration.save()
@ -264,9 +292,6 @@ class GetMenteeCountTestCase(TestCase):
participants_with_mentor = [create_user(f"participant{i}") for i in range(2)] participants_with_mentor = [create_user(f"participant{i}") for i in range(2)]
participant = create_user("participant") participant = create_user("participant")
mentor = create_user("mentor") mentor = create_user("mentor")
lm = LearningMentor.objects.create(
mentor=mentor, course_session=self.course_session
)
# WHEN # WHEN
for p in participants_with_mentor: for p in participants_with_mentor:
@ -275,7 +300,9 @@ class GetMenteeCountTestCase(TestCase):
p, p,
role=CourseSessionUser.Role.MEMBER, role=CourseSessionUser.Role.MEMBER,
) )
lm.participants.add(csu) AgentParticipantRelation.objects.create(
agent=mentor, participant=csu, role="LEARNING_MENTOR"
)
add_course_session_user( add_course_session_user(
self.course_session, self.course_session,
@ -305,9 +332,6 @@ class GetMentorOpenTasksTestCase(BaseMentorAssignmentTestCase):
self.course.configuration.save() self.course.configuration.save()
self.mentor = create_user("mentor") 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)] self.participants = [create_user(f"participant{i}") for i in range(2)]
def create_and_test_count( def create_and_test_count(
@ -337,7 +361,10 @@ class GetMentorOpenTasksTestCase(BaseMentorAssignmentTestCase):
self.participants[0], self.participants[0],
role=CourseSessionUser.Role.MEMBER, role=CourseSessionUser.Role.MEMBER,
) )
self.lm.participants.add(csu)
AgentParticipantRelation.objects.create(
agent=self.mentor, participant=csu, role="LEARNING_MENTOR"
)
add_course_session_user( add_course_session_user(
self.course_session, self.course_session,
@ -367,7 +394,9 @@ class GetMentorOpenTasksTestCase(BaseMentorAssignmentTestCase):
self.participants[0], self.participants[0],
role=CourseSessionUser.Role.MEMBER, role=CourseSessionUser.Role.MEMBER,
) )
self.lm.participants.add(csu) AgentParticipantRelation.objects.create(
agent=self.mentor, participant=csu, role="LEARNING_MENTOR"
)
add_course_session_user( add_course_session_user(
self.course_session, self.course_session,
@ -389,8 +418,9 @@ class GetMentorOpenTasksTestCase(BaseMentorAssignmentTestCase):
self.participants[0], self.participants[0],
role=CourseSessionUser.Role.MEMBER, role=CourseSessionUser.Role.MEMBER,
) )
self.lm.participants.add(csu) AgentParticipantRelation.objects.create(
agent=self.mentor, participant=csu, role="LEARNING_MENTOR"
)
SelfEvaluationFeedback.objects.create( SelfEvaluationFeedback.objects.create(
feedback_submitted=False, feedback_submitted=False,
feedback_requester_user=self.participants[0], feedback_requester_user=self.participants[0],
@ -411,8 +441,9 @@ class GetMentorOpenTasksTestCase(BaseMentorAssignmentTestCase):
self.participants[0], self.participants[0],
role=CourseSessionUser.Role.MEMBER, role=CourseSessionUser.Role.MEMBER,
) )
self.lm.participants.add(csu) AgentParticipantRelation.objects.create(
agent=self.mentor, participant=csu, role="LEARNING_MENTOR"
)
SelfEvaluationFeedback.objects.create( SelfEvaluationFeedback.objects.create(
feedback_submitted=True, feedback_submitted=True,
feedback_requester_user=self.participants[0], feedback_requester_user=self.participants[0],

View File

@ -42,7 +42,10 @@ from vbv_lernwelt.feedback.export import (
export_feedback_with_circle_restriction, export_feedback_with_circle_restriction,
FEEDBACK_EXPORT_FILE_NAME, 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.learnpath.models import Circle
from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback 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 result_course_sessions[cs.id] = cs
# enrich with mentor course sessions # enrich with mentor course sessions
lm_qs = LearningMentor.objects.filter(mentor=user).prefetch_related( lm_qs = AgentParticipantRelation.objects.filter(agent=user).prefetch_related(
"course_session", "course_session__course" "participant__course_session", "participant__course_session__course"
) )
for lm in lm_qs: for lm in lm_qs:
cs = lm.course_session cs = lm.participant.course_session
cs.roles = set() cs.roles = set()
cs = result_course_sessions.get(cs.id, cs) cs = result_course_sessions.get(cs.id, cs)
cs.roles.add("LEARNING_MENTOR") cs.roles.add(lm.role)
result_course_sessions[cs.id] = cs result_course_sessions[cs.id] = cs
return [ return [
@ -197,46 +200,46 @@ def _create_person_list_with_roles(user):
# add persons where request.user is mentor # add persons where request.user is mentor
for cs in course_sessions: for cs in course_sessions:
if "LEARNING_MENTOR" in cs.roles: if "LEARNING_MENTOR" in cs.roles:
lm = LearningMentor.objects.filter( for relation in AgentParticipantRelation.objects.filter(
mentor=user, course_session=cs.id agent=user, participant__course_session_id=cs.id
).first() ):
for participant in lm.participants.all():
course_session_entry = _create_course_session_dict( course_session_entry = _create_course_session_dict(
cs, cs,
"LEARNING_MENTOR", "LEARNING_MENTOR",
"LEARNING_MENTEE", "LEARNING_MENTEE",
) )
participant_user = relation.participant.user
if participant.user.id not in result_persons: if participant_user.id not in result_persons:
person_data = create_user_dict(participant.user) person_data = create_user_dict(participant_user)
person_data["course_sessions"] = [course_session_entry] person_data["course_sessions"] = [course_session_entry]
result_persons[participant.user.id] = person_data result_persons[participant_user] = person_data
else: else:
# user is already in result_persons # user is already in result_persons
result_persons[participant.user.id]["course_sessions"].append( result_persons[participant_user]["course_sessions"].append(
course_session_entry course_session_entry
) )
# add persons where request.user is mentee # add persons where request.user is mentee
mentor_relation_qs = LearningMentor.objects.filter( mentor_relation_qs = AgentParticipantRelation.objects.filter(
participants__user=user participant__user=user,
).prefetch_related("mentor", "course_session") role=AgentParticipantRoleType.LEARNING_MENTOR.value,
).prefetch_related("agent")
for mentor_relation in mentor_relation_qs: 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( course_session_entry = _create_course_session_dict(
cs, cs,
"LEARNING_MENTEE", "LEARNING_MENTEE",
"LEARNING_MENTOR", "LEARNING_MENTOR",
) )
if mentor_relation.mentor.id not in result_persons: if mentor_relation.agent.id not in result_persons:
person_data = create_user_dict(mentor_relation.mentor) person_data = create_user_dict(mentor_relation.agent)
person_data["course_sessions"] = [course_session_entry] person_data["course_sessions"] = [course_session_entry]
result_persons[mentor_relation.mentor.id] = person_data result_persons[mentor_relation.agent.id] = person_data
else: else:
# user is already in result_persons # 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 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: def _get_mentee_count(course_id: str, mentor: User) -> int:
return CourseSessionUser.objects.filter( return AgentParticipantRelation.objects.filter(
participants__mentor=mentor, course_session__course__id=course_id agent=mentor,
participant__course_session__course_id=course_id,
role=AgentParticipantRoleType.LEARNING_MENTOR.value,
).count() ).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) 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: if course_configuration.is_vv:
open_assigment_count = AssignmentCompletion.objects.filter( open_assigment_count = AssignmentCompletion.objects.filter(
course_session__course__id=course_id, course_session__course__id=course_id,
completion_status=AssignmentCompletionStatus.SUBMITTED.value, completion_status=AssignmentCompletionStatus.SUBMITTED.value,
evaluation_user=mentor, # noqa evaluation_user=mentor, # noqa
assignment_user__coursesessionuser__participants__mentor=mentor, assignment_user_id__in=learning_meentee_ids,
).count() ).count()
open_feedback_qs = SelfEvaluationFeedback.objects.filter( open_feedback_qs = SelfEvaluationFeedback.objects.filter(
feedback_provider_user=mentor, # noqa feedback_provider_user=mentor, # noqa
feedback_requester_user__coursesessionuser__participants__mentor=mentor, feedback_requester_user_id__in=learning_meentee_ids,
feedback_submitted=False, feedback_submitted=False,
) )
# filter open feedbacks for course_id (-> not possible with queryset) # filter open feedbacks for course_id (-> not possible with queryset)

View File

@ -1,7 +1,10 @@
from vbv_lernwelt.core.models import User from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.models import Course, CourseSession, CourseSessionUser from vbv_lernwelt.course.models import Course, CourseSession, CourseSessionUser
from vbv_lernwelt.course_session_group.models import CourseSessionGroup 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 from vbv_lernwelt.learnpath.models import LearningSequence
@ -22,8 +25,8 @@ def has_course_access(user, course_id):
).exists(): ).exists():
return True return True
if LearningMentor.objects.filter( if AgentParticipantRelation.objects.filter(
course_session__course_id=course_id, mentor=user agent=user, participant__course_session__course_id=course_id
).exists(): ).exists():
return True return True
@ -72,8 +75,9 @@ def is_course_session_learning_mentor(mentor: User, course_session_id: int):
if course_session is None: if course_session is None:
return False return False
return LearningMentor.objects.filter( return AgentParticipantRelation.objects.filter(
mentor=mentor, course_session=course_session agent=mentor,
role=AgentParticipantRoleType.LEARNING_MENTOR.value,
).exists() ).exists()
@ -87,9 +91,11 @@ def is_learning_mentor_for_user(
if csu is None: if csu is None:
return False return False
return LearningMentor.objects.filter( return AgentParticipantRelation.objects.filter(
course_session_id=course_session_id, mentor=mentor, participants=csu agent=mentor,
).exists() participant=csu,
role=AgentParticipantRoleType.LEARNING_MENTOR.value,
)
def is_course_session_supervisor(user, course_session_id: int): 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(): ).exists():
return True return True
if LearningMentor.objects.filter( if AgentParticipantRelation.objects.filter(
course_session__course=course, mentor=user agent=user, participant__course_session__course=course
).exists(): ).exists():
return True return True

View File

@ -8,7 +8,7 @@ from vbv_lernwelt.course.creators.test_utils import (
) )
from vbv_lernwelt.course.models import CourseSessionUser from vbv_lernwelt.course.models import CourseSessionUser
from vbv_lernwelt.iam.permissions import course_session_permissions 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): class ActionTestCase(TestCase):
@ -21,14 +21,16 @@ class ActionTestCase(TestCase):
def test_course_session_permissions(self): def test_course_session_permissions(self):
# GIVEN # GIVEN
lm = create_user("mentor") lm = create_user("mentor")
LearningMentor.objects.create(mentor=lm, course_session=self.course_session)
participant = create_user("participant") participant = create_user("participant")
add_course_session_user( csu = add_course_session_user(
self.course_session, self.course_session,
participant, participant,
role=CourseSessionUser.Role.MEMBER, role=CourseSessionUser.Role.MEMBER,
) )
AgentParticipantRelation.objects.create(
agent=lm, participant=csu, role="LEARNING_MENTOR"
)
trainer = create_user("trainer") trainer = create_user("trainer")
add_course_session_user( add_course_session_user(

View File

@ -9,7 +9,7 @@ from vbv_lernwelt.course.creators.test_utils import (
from vbv_lernwelt.course.models import CourseSessionUser from vbv_lernwelt.course.models import CourseSessionUser
from vbv_lernwelt.course_session_group.models import CourseSessionGroup 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.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): class RoleTestCase(TestCase):
@ -48,9 +48,14 @@ class RoleTestCase(TestCase):
def test_has_role_mentor(self): def test_has_role_mentor(self):
# GIVEN # GIVEN
LearningMentor.objects.create( participant = create_user("participant")
mentor=self.user, csu = add_course_session_user(
course_session=self.course_session, self.course_session,
participant,
role=CourseSessionUser.Role.MEMBER,
)
AgentParticipantRelation.objects.create(
agent=self.user, participant=csu, role="LEARNING_MENTOR"
) )
# WHEN # WHEN
@ -73,14 +78,10 @@ class RoleTestCase(TestCase):
role=CourseSessionUser.Role.MEMBER, role=CourseSessionUser.Role.MEMBER,
) )
learning_mentor = LearningMentor.objects.create( AgentParticipantRelation.objects.create(
mentor=mentor, agent=mentor, participant=member_csu, role="LEARNING_MENTOR"
course_session=course_session,
) )
learning_mentor.participants.add(member_csu)
learning_mentor.save()
# WHEN # WHEN
is_mentor = is_learning_mentor_for_user( is_mentor = is_learning_mentor_for_user(
mentor=mentor, mentor=mentor,

View File

@ -101,7 +101,7 @@ class CreateOrUpdateCourseSessionTestCase(TestCase):
attendance_course.due_date.end.isoformat(), "2023-06-06T13:00:00+00:00" attendance_course.due_date.end.isoformat(), "2023-06-06T13:00:00+00:00"
) )
self.assertEqual( self.assertEqual(
f"E64, HKV Aarau, Bahnhofstrasse 460, 5001, Aarau", "E64, HKV Aarau, Bahnhofstrasse 460, 5001, Aarau",
attendance_course.location, attendance_course.location,
) )
self.assertEqual("", attendance_course.trainer) 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" attendance_course.due_date.end.isoformat(), "2023-06-06T15:00:00+00:00"
) )
self.assertEqual( self.assertEqual(
f"E666, HKV Aarau2, Bahnhofstrasse 460, 5002, Aarau", "E666, HKV Aarau2, Bahnhofstrasse 460, 5002, Aarau",
attendance_course.location, attendance_course.location,
) )
self.assertEqual( self.assertEqual(

View File

@ -1,34 +1,29 @@
from django.contrib import admin from django.contrib import admin
from vbv_lernwelt.course.models import CourseSessionUser from vbv_lernwelt.learning_mentor.models import (
from vbv_lernwelt.learning_mentor.models import LearningMentor, MentorInvitation AgentParticipantRelation,
MentorInvitation,
)
@admin.register(LearningMentor) @admin.register(AgentParticipantRelation)
class LearningMentorAdmin(admin.ModelAdmin): class TrainerParticipantRelationAdmin(admin.ModelAdmin):
def participant_count(self, obj): list_display = ["agent", "participant", "role"]
return obj.participants.count()
participant_count.short_description = "Participants" search_fields = [
"agent__email",
list_display = ["mentor", "course_session", "participant_count"] "agent__first_name",
"agent__last_name",
search_fields = ["mentor__email"] "participant__user__email",
"participant__user__first_name",
raw_id_fields = [ "participant__user__last_name",
"mentor",
"course_session",
] ]
def formfield_for_manytomany(self, db_field, request, **kwargs): raw_id_fields = [
if db_field.name == "participants": "agent",
if request.resolver_match.kwargs.get("object_id"): "participant",
object_id = str(request.resolver_match.kwargs.get("object_id")) # "course_session",
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)
@admin.register(MentorInvitation) @admin.register(MentorInvitation)

View File

@ -6,4 +6,8 @@ class LearningMentorConfig(AppConfig):
name = "vbv_lernwelt.learning_mentor" name = "vbv_lernwelt.learning_mentor"
def ready(self): 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

View File

@ -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")},
},
),
]

View File

@ -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)]

View File

@ -1,4 +1,5 @@
import uuid import uuid
from enum import Enum
from django.db import models from django.db import models
from django_extensions.db.models import TimeStampedModel 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() 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): class MentorInvitation(TimeStampedModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.EmailField() email = models.EmailField()

View File

@ -1,7 +1,10 @@
from rest_framework import serializers from rest_framework import serializers
from vbv_lernwelt.core.serializers import UserSerializer from vbv_lernwelt.core.serializers import UserShortSerializer
from vbv_lernwelt.learning_mentor.models import LearningMentor, MentorInvitation from vbv_lernwelt.learning_mentor.models import (
AgentParticipantRelation,
MentorInvitation,
)
class MentorAssignmentCompletionSerializer(serializers.Serializer): class MentorAssignmentCompletionSerializer(serializers.Serializer):
@ -39,10 +42,24 @@ class InvitationSerializer(serializers.ModelSerializer):
return invitation return invitation
class MentorSerializer(serializers.ModelSerializer): class AgentParticipantRelationSerializer(serializers.ModelSerializer):
mentor = UserSerializer(read_only=True) 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: class Meta:
model = LearningMentor model = AgentParticipantRelation
fields = ["id", "mentor"] fields = [
"id",
"role",
"course_session_id",
"agent",
"participant_user",
]
read_only_fields = ["id"] read_only_fields = ["id"]

View File

@ -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.")

View File

@ -11,7 +11,11 @@ from vbv_lernwelt.course.creators.test_utils import (
create_user, create_user,
) )
from vbv_lernwelt.course.models import CourseSessionUser 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 from vbv_lernwelt.notify.email.email_services import EmailTemplate
@ -254,13 +258,14 @@ class LearningMentorInvitationTest(APITestCase):
# THEN # THEN
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertFalse(MentorInvitation.objects.filter(id=invitation.id).exists()) self.assertFalse(MentorInvitation.objects.filter(id=invitation.id).exists())
self.assertTrue(
LearningMentor.objects.filter( relation_qs = AgentParticipantRelation.objects.all()
mentor=invitee, self.assertEqual(relation_qs.count(), 1)
course_session=self.course_session, relation = relation_qs.first()
participants=participant_cs_user, self.assertEqual(relation.agent, invitee)
).exists() 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"] user = response.data["user"]
self.assertEqual(user["id"], str(self.participant.id)) self.assertEqual(user["id"], str(self.participant.id))

View File

@ -22,7 +22,10 @@ from vbv_lernwelt.course.creators.test_utils import (
create_user, create_user,
) )
from vbv_lernwelt.course.models import CourseSessionUser 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 from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback
@ -71,63 +74,56 @@ class LearningMentorAPITest(APITestCase):
response = self.client.get(self.url) response = self.client.get(self.url)
# THEN # THEN
print(response.data)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_api_no_participants(self) -> None: def test_api_no_participants(self) -> None:
# GIVEN # GIVEN
self.client.force_login(self.mentor) self.client.force_login(self.mentor)
LearningMentor.objects.create(
mentor=self.mentor, course_session=self.course_session
)
# WHEN # WHEN
response = self.client.get(self.url) response = self.client.get(self.url)
# THEN # THEN
self.assertEqual(response.status_code, status.HTTP_200_OK) print(response.data)
self.assertEqual(response.data["participants"], []) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
self.assertEqual(response.data["assignments"], [])
def test_api_participants(self) -> None: def test_api_participants(self) -> None:
# GIVEN # GIVEN
participants = [self.participant_1, self.participant_2, self.participant_3] participants = [self.participant_1, self.participant_2, self.participant_3]
self.client.force_login(self.mentor)
mentor = LearningMentor.objects.create( for participant in participants:
mentor=self.mentor, course_session=self.course_session AgentParticipantRelation.objects.create(
) agent=self.mentor,
mentor.participants.set(participants) participant=participant,
role=AgentParticipantRoleType.LEARNING_MENTOR.value,
)
# WHEN # WHEN
self.client.force_login(self.mentor)
response = self.client.get(self.url) response = self.client.get(self.url)
# THEN # THEN
print(response.data)
self.assertEqual(response.status_code, status.HTTP_200_OK) 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 = [ rel1 = AgentParticipantRelation.objects.get(participant=self.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")
self.assertEqual( self.assertEqual(rel1.participant.user.email, "participant_1@example.com")
response.data["mentor_id"], self.assertEqual(rel1.participant.user.first_name, "Test")
mentor.id, self.assertEqual(rel1.participant.user.last_name, "Participant_1")
)
def test_api_self_evaluation_feedback(self) -> None: def test_api_self_evaluation_feedback(self) -> None:
# GIVEN # GIVEN
participants = [self.participant_1, self.participant_2, self.participant_3] participants = [self.participant_1, self.participant_2, self.participant_3]
self.client.force_login(self.mentor)
mentor = LearningMentor.objects.create( for participant in participants:
mentor=self.mentor, course_session=self.course_session AgentParticipantRelation.objects.create(
) agent=self.mentor,
participant=participant,
mentor.participants.set(participants) role=AgentParticipantRoleType.LEARNING_MENTOR.value,
)
learning_unit = create_learning_unit( learning_unit = create_learning_unit(
circle=self.circle, circle=self.circle,
@ -154,6 +150,7 @@ class LearningMentorAPITest(APITestCase):
# ... # ...
# WHEN # WHEN
self.client.force_login(self.mentor)
response = self.client.get(self.url) response = self.client.get(self.url)
# THEN # THEN
@ -194,7 +191,6 @@ class LearningMentorAPITest(APITestCase):
def test_api_praxis_assignments(self) -> None: def test_api_praxis_assignments(self) -> None:
# GIVEN # GIVEN
self.client.force_login(self.mentor)
assignment = create_assignment( assignment = create_assignment(
course=self.course, assignment_type=AssignmentType.PRAXIS_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 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] 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( AssignmentCompletion.objects.create(
assignment_user=self.participant_1.user, assignment_user=self.participant_1.user,
@ -230,6 +226,7 @@ class LearningMentorAPITest(APITestCase):
) )
# WHEN # WHEN
self.client.force_login(self.mentor)
response = self.client.get(self.url) response = self.client.get(self.url)
# THEN # THEN
@ -267,12 +264,12 @@ class LearningMentorAPITest(APITestCase):
role=CourseSessionUser.Role.MEMBER, role=CourseSessionUser.Role.MEMBER,
) )
learning_mentor = LearningMentor.objects.create( db_relation = AgentParticipantRelation.objects.create(
mentor=self.mentor, course_session=self.course_session agent=self.mentor,
participant=participant_cs_user,
role=AgentParticipantRoleType.LEARNING_MENTOR.value,
) )
learning_mentor.participants.add(participant_cs_user)
list_url = reverse( list_url = reverse(
"list_user_mentors", kwargs={"course_session_id": self.course_session.id} "list_user_mentors", kwargs={"course_session_id": self.course_session.id}
) )
@ -281,12 +278,13 @@ class LearningMentorAPITest(APITestCase):
response = self.client.get(list_url) response = self.client.get(list_url)
# THEN # THEN
print(response.data)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
mentor = response.data[0] mentor_relation = response.data[0]
self.assertEqual(mentor["id"], learning_mentor.id) 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["email"], self.mentor.email)
self.assertEqual(mentor_user["id"], str(self.mentor.id)) self.assertEqual(mentor_user["id"], str(self.mentor.id))
@ -300,18 +298,17 @@ class LearningMentorAPITest(APITestCase):
role=CourseSessionUser.Role.MEMBER, role=CourseSessionUser.Role.MEMBER,
) )
learning_mentor = LearningMentor.objects.create( relation = AgentParticipantRelation.objects.create(
mentor=self.mentor, course_session=self.course_session agent=self.mentor,
participant=participant_cs_user,
role=AgentParticipantRoleType.LEARNING_MENTOR.value,
) )
learning_mentor.participants.add(participant_cs_user)
remove_url = reverse( remove_url = reverse(
"remove_participant_from_mentor", "delete_agent_participant_relation",
kwargs={ kwargs={
"course_session_id": self.course_session.id, "course_session_id": self.course_session.id,
"mentor_id": learning_mentor.id, "relation_id": relation.id,
"participant_user_id": participant_cs_user.user.id,
}, },
) )
@ -320,9 +317,7 @@ class LearningMentorAPITest(APITestCase):
# THEN # THEN
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertFalse( self.assertEqual(AgentParticipantRelation.objects.count(), 0)
LearningMentor.objects.filter(participants=participant_cs_user).exists()
)
def test_remove_myself_from_mentor(self) -> None: def test_remove_myself_from_mentor(self) -> None:
# GIVEN # GIVEN
@ -335,18 +330,17 @@ class LearningMentorAPITest(APITestCase):
role=CourseSessionUser.Role.MEMBER, role=CourseSessionUser.Role.MEMBER,
) )
learning_mentor = LearningMentor.objects.create( relation = AgentParticipantRelation.objects.create(
mentor=self.mentor, course_session=self.course_session agent=self.mentor,
participant=participant_cs_user,
role=AgentParticipantRoleType.LEARNING_MENTOR.value,
) )
learning_mentor.participants.add(participant_cs_user)
remove_url = reverse( remove_url = reverse(
"remove_participant_from_mentor", "delete_agent_participant_relation",
kwargs={ kwargs={
"course_session_id": self.course_session.id, "course_session_id": self.course_session.id,
"mentor_id": learning_mentor.id, "relation_id": relation.id,
"participant_user_id": participant_cs_user.user.id,
}, },
) )
@ -355,26 +349,54 @@ class LearningMentorAPITest(APITestCase):
# THEN # THEN
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertFalse( self.assertEqual(AgentParticipantRelation.objects.count(), 0)
LearningMentor.objects.filter(participants=participant_cs_user).exists()
)
def test_mentor_multiple_courses(self) -> None: def test_mentor_multiple_courses(self) -> None:
# GIVEN # GIVEN
course_a, _ = create_course("Course A") course_a, _ = create_course("Course A")
course_session_a = create_course_session(course=course_a, title="Test 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_b, _ = create_course("Course B")
course_session_b = create_course_session(course=course_b, title="Test 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 # WHEN
LearningMentor.objects.create( relation_a = AgentParticipantRelation.objects.create(
mentor=self.mentor, course_session=course_session_a 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( self.client.force_login(self.mentor)
mentor=self.mentor, course_session=course_session_b
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 response_b = self.client.get(
self.assertEqual(LearningMentor.objects.count(), 2) 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)
)

View File

@ -6,9 +6,9 @@ urlpatterns = [
path("summary", views.mentor_summary, name="mentor_summary"), path("summary", views.mentor_summary, name="mentor_summary"),
path("mentors", views.list_user_mentors, name="list_user_mentors"), path("mentors", views.list_user_mentors, name="list_user_mentors"),
path( path(
"mentors/<int:mentor_id>/remove/<uuid:participant_user_id>", "mentors/<uuid:relation_id>/delete",
views.remove_participant_from_mentor, views.delete_agent_participant_relation,
name="remove_participant_from_mentor", name="delete_agent_participant_relation",
), ),
path("invitations", views.list_invitations, name="list_invitations"), path("invitations", views.list_invitations, name="list_invitations"),
path("invitations/create", views.create_invitation, name="create_invitation"), path("invitations/create", views.create_invitation, name="create_invitation"),

View File

@ -16,11 +16,15 @@ from vbv_lernwelt.learning_mentor.content.praxis_assignment import (
from vbv_lernwelt.learning_mentor.content.self_evaluation_feedback import ( from vbv_lernwelt.learning_mentor.content.self_evaluation_feedback import (
get_self_feedback_evaluation, 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 ( from vbv_lernwelt.learning_mentor.serializers import (
AgentParticipantRelationSerializer,
InvitationSerializer, InvitationSerializer,
MentorAssignmentStatusSerializer, MentorAssignmentStatusSerializer,
MentorSerializer,
) )
from vbv_lernwelt.learnpath.models import Circle from vbv_lernwelt.learnpath.models import Circle
from vbv_lernwelt.notify.email.email_services import EmailTemplate, send_email 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): def mentor_summary(request, course_session_id: int):
course_session = CourseSession.objects.get(id=course_session_id) course_session = CourseSession.objects.get(id=course_session_id)
mentor = get_object_or_404( participant_qs = AgentParticipantRelation.objects.filter(
LearningMentor, mentor=request.user, course_session=course_session agent=request.user,
participant__course_session=course_session,
role=AgentParticipantRoleType.LEARNING_MENTOR.value,
) )
participants = mentor.participants.filter(course_session=course_session) if participant_qs.count() == 0:
users = [p.user for p in participants] return Response(status=status.HTTP_404_NOT_FOUND)
users = [p.participant.user for p in participant_qs]
assignments = [] assignments = []
circle_ids = set() circle_ids = set()
@ -71,8 +79,9 @@ def mentor_summary(request, course_session_id: int):
) )
return Response( return Response(
{ {
"mentor_id": mentor.id, "participant_relations": AgentParticipantRelationSerializer(
"participants": [UserSerializer(user).data for user in users], participant_qs, many=True
).data,
"circles": list( "circles": list(
Circle.objects.filter(id__in=circle_ids).values("id", "title") 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 CourseSessionUser, user=request.user, course_session=course_session
) )
mentors = LearningMentor.objects.filter( mentors = AgentParticipantRelation.objects.filter(
course_session=course_session, participants=course_session_user 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"]) @api_view(["DELETE"])
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
def remove_participant_from_mentor( def delete_agent_participant_relation(
request, course_session_id: int, mentor_id: int, participant_user_id: uuid.UUID request, course_session_id: int, relation_id: uuid.UUID
): ):
requester_user = request.user requester_user = request.user
mentor = get_object_or_404( relation = get_object_or_404(AgentParticipantRelation, id=relation_id)
LearningMentor, id=mentor_id, course_session_id=course_session_id
)
is_requester_mentor = requester_user.id == mentor.mentor_id is_requester_mentor = requester_user.id == relation.agent.id
is_requester_participant = mentor.participants.filter( is_requester_participant = requester_user.id == relation.participant.user.id
user=requester_user, course_session_id=course_session_id
).exists()
if not is_requester_mentor and not is_requester_participant: if not is_requester_mentor and not is_requester_participant:
return Response( return Response(
@ -204,12 +210,7 @@ def remove_participant_from_mentor(
status=status.HTTP_403_FORBIDDEN, status=status.HTTP_403_FORBIDDEN,
) )
course_session = get_object_or_404(CourseSession, id=course_session_id) relation.delete()
course_session_user = get_object_or_404(
CourseSessionUser, user=participant_user_id, course_session=course_session
)
mentor.participants.remove(course_session_user)
return Response(status=status.HTTP_204_NO_CONTENT) 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, status=status.HTTP_400_BAD_REQUEST,
) )
mentor, _ = LearningMentor.objects.get_or_create( AgentParticipantRelation.objects.get_or_create(
mentor=request.user, course_session=course_session agent=request.user,
participant=invitation.participant,
role=AgentParticipantRoleType.LEARNING_MENTOR.value,
) )
mentor.participants.add(invitation.participant)
invitation.delete() invitation.delete()
return Response( return Response(

View File

@ -2,6 +2,7 @@ import imghdr
from wsgiref.util import FileWrapper from wsgiref.util import FileWrapper
import structlog import structlog
from django.conf import settings
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, StreamingHttpResponse from django.http import HttpResponse, StreamingHttpResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
@ -21,6 +22,16 @@ def user_image(request, image_id):
try: try:
rendition = image.get_rendition(filter_spec) 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: except SourceImageIOError:
return HttpResponse( return HttpResponse(
"Source image file not found", content_type="text/plain", status=410 "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", content_type="text/plain",
status=400, status=400,
) )
except Exception:
rendition.file.open("rb") if settings.APP_ENVIRONMENT.startswith("local"):
image_format = imghdr.what(rendition.file) # do not spam the console
logger.warning("Error while serving image")
return StreamingHttpResponse( else:
FileWrapper(rendition.file), logger.exception(
content_type=f"image/{image_format}" if image_format else "binary/octet-stream", "Error while serving image", exc_info=True, label="s3_mediafiles"
) )
return HttpResponse(
"Error while serving image", content_type="text/plain", status=400
)

View File

@ -14,7 +14,7 @@ from vbv_lernwelt.course.creators.test_utils import (
) )
from vbv_lernwelt.course.models import CourseCompletionStatus, CourseSessionUser from vbv_lernwelt.course.models import CourseCompletionStatus, CourseSessionUser
from vbv_lernwelt.course.services import mark_course_completion 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 ( from vbv_lernwelt.self_evaluation_feedback.models import (
CourseCompletionFeedback, CourseCompletionFeedback,
SelfEvaluationFeedback, SelfEvaluationFeedback,
@ -51,13 +51,10 @@ class SelfEvaluationFeedbackAPI(APITestCase):
title="Test Circle", course_page=self.course_page title="Test Circle", course_page=self.course_page
) )
learning_mentor = LearningMentor.objects.create( AgentParticipantRelation.objects.create(
mentor=self.mentor, agent=self.mentor, participant=member_csu, role="LEARNING_MENTOR"
course_session=self.course_session,
) )
learning_mentor.participants.add(member_csu)
@patch( @patch(
"vbv_lernwelt.notify.services.NotificationService.send_self_evaluation_feedback_request_feedback_notification" "vbv_lernwelt.notify.services.NotificationService.send_self_evaluation_feedback_request_feedback_notification"
) )

View File

@ -11,7 +11,7 @@ from vbv_lernwelt.core.models import User
from vbv_lernwelt.core.serializers import UserSerializer from vbv_lernwelt.core.serializers import UserSerializer
from vbv_lernwelt.course.models import CourseCompletion, CourseSession from vbv_lernwelt.course.models import CourseCompletion, CourseSession
from vbv_lernwelt.iam.permissions import can_view_course_completions 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.learnpath.models import Circle, LearningUnit
from vbv_lernwelt.notify.services import NotificationService from vbv_lernwelt.notify.services import NotificationService
from vbv_lernwelt.self_evaluation_feedback.models import ( 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) learning_unit = get_object_or_404(LearningUnit, id=learning_unit_id)
feedback_provider_user = get_object_or_404(User, id=feedback_provider_user_id) feedback_provider_user = get_object_or_404(User, id=feedback_provider_user_id)
if not LearningMentor.objects.filter( if not AgentParticipantRelation.objects.filter(
course_session__course=learning_unit.get_course(), agent=feedback_provider_user,
mentor=feedback_provider_user, participant__user=request.user,
participants__user=request.user,
).exists(): ).exists():
raise PermissionDenied() raise PermissionDenied()

View File

@ -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),
),
]

View File

@ -5,7 +5,10 @@ from keycloak.exceptions import KeycloakDeleteError, KeycloakPostError
from vbv_lernwelt.course.models import CourseSessionUser from vbv_lernwelt.course.models import CourseSessionUser
from vbv_lernwelt.course_session_group.models import CourseSessionGroup 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.models import SsoSyncError, SsoUser
from vbv_lernwelt.sso.role_sync.services import ( from vbv_lernwelt.sso.role_sync.services import (
create_and_update_user, create_and_update_user,
@ -20,7 +23,7 @@ def create_sso_user_from_admin(user: User, request):
create_and_update_user(user) # noqa create_and_update_user(user) # noqa
user.save() user.save()
messages.add_message( 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: except KeycloakPostError as e:
messages.add_message( 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): def sync_sso_roles_from_admin(user: User, request):
course_roles = [ course_roles = {
(csu.course_session.course.slug, csu.role) (csu.course_session.course.slug, csu.role)
for csu in CourseSessionUser.objects.filter(user=user) for csu in CourseSessionUser.objects.filter(user=user)
] }
course_roles += [ course_roles += {
(lm.course_session.course.slug, "LEARNING_MENTOR") (relation.participant.course_session.course.slug, "LEARNING_MENTOR")
for lm in LearningMentor.objects.filter(mentor=user) for relation in AgentParticipantRelation.objects.filter(
] agent=user, role=AgentParticipantRoleType.LEARNING_MENTOR.value
)
}
for csg in CourseSessionGroup.objects.filter(supervisor=user): for csg in CourseSessionGroup.objects.filter(supervisor=user):
for course_session in csg.course_session.all(): 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: try:
sync_roles_for_user(user, course_roles) sync_roles_for_user(user, course_roles)
messages.add_message( 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: except KeycloakDeleteError as e:
messages.add_message( messages.add_message(

View File

@ -1,5 +1,5 @@
import unicodedata import unicodedata
from typing import Dict, List, Tuple from typing import Dict, List, Set, Tuple
import structlog import structlog
from django.conf import settings 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__) logger = structlog.get_logger(__name__)
CourseRolesType = List[Tuple[str, str]] CourseRolesType = Set[Tuple[str, str]]
KeyCloakRolesType = List[Dict[str, str]] KeyCloakRolesType = List[Dict[str, str]]
keycloak_admin = None # Needed for pytest keycloak_admin = None # Needed for pytest

View File

@ -6,7 +6,10 @@ from keycloak.exceptions import KeycloakDeleteError, KeycloakError, KeycloakPost
from vbv_lernwelt.core.models import User from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.models import CourseSessionUser from vbv_lernwelt.course.models import CourseSessionUser
from vbv_lernwelt.course_session_group.models import CourseSessionGroup 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 ( from vbv_lernwelt.sso.role_sync.services import (
add_roles_to_user, add_roles_to_user,
remove_roles_from_user, remove_roles_from_user,
@ -78,21 +81,39 @@ def update_sso_roles_in_csg(sender, instance, action, reverse, model, pk_set, **
# LearningMentor # LearningMentor
@receiver(post_delete, sender=LearningMentor, dispatch_uid="delete_sso_roles_in_lm") @receiver(
def remove_sso_roles_in_lm(sender, instance: LearningMentor, **kwargs): post_delete, sender=AgentParticipantRelation, dispatch_uid="delete_sso_roles_in_lm"
if not LearningMentor.objects.filter( )
mentor=instance.mentor, course_session__course=instance.course_session.course 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(): ).exists():
_remove_sso_role( _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") @receiver(
def update_sso_roles_in_lm(sender, instance: LearningMentor, **kwargs): pre_save, sender=AgentParticipantRelation, dispatch_uid="update_sso_roles_in_lm"
if not instance.pk: )
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( _add_sso_role(
instance.mentor, instance.course_session.course.slug, "LEARNING_MENTOR" instance.agent,
instance.participant.course_session.course.slug,
"LEARNING_MENTOR",
) )

View File

@ -24,7 +24,7 @@ from vbv_lernwelt.course.creators.test_utils import (
) )
from vbv_lernwelt.course.models import Course, CourseSessionUser from vbv_lernwelt.course.models import Course, CourseSessionUser
from vbv_lernwelt.course_session_group.models import CourseSessionGroup 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 from vbv_lernwelt.sso.signals import update_sso_roles_in_cs
@ -217,10 +217,16 @@ class LearningMentorTests(TestCase):
def setUp(self): def setUp(self):
self.course, self.course_page = create_course("Test Course") self.course, self.course_page = create_course("Test Course")
self.course_session = create_course_session(course=self.course, title="Test VV") self.course_session = create_course_session(course=self.course, title="Test VV")
self.user = create_user("mentor") 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") @patch("vbv_lernwelt.sso.signals.remove_roles_from_user")
@ -229,7 +235,7 @@ class LearningMentorTests(TestCase):
): ):
mock_remove_roles_from_user.return_value = None mock_remove_roles_from_user.return_value = None
self.mentor.delete() self.relation.delete()
self.assertEqual(mock_remove_roles_from_user.call_count, 1) 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") @patch("vbv_lernwelt.sso.signals.add_roles_to_user")
def test_add_roles_for_learning_mentor_on_create(self, mock_add_roles_from_user): def test_add_roles_for_learning_mentor_on_create(self, mock_add_roles_from_user):
mock_add_roles_from_user.return_value = None mock_add_roles_from_user.return_value = None
self.mentor.delete() self.relation.delete()
LearningMentor.objects.create( AgentParticipantRelation.objects.create(
mentor=self.user, course_session=self.course_session agent=self.user, participant=self.csu, role="LEARNING_MENTOR"
) )
self.assertEqual(mock_add_roles_from_user.call_count, 1) self.assertEqual(mock_add_roles_from_user.call_count, 1)
@ -263,5 +269,8 @@ class LearningMentorTests(TestCase):
) )
mock_add_roles_from_user.reset_mock() 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) self.assertEqual(mock_add_roles_from_user.call_count, 0)