Merge branch 'develop' into feature/VBV-621-teilnehmer-profil

This commit is contained in:
Reto Aebersold 2024-01-23 08:38:58 +01:00
commit 0e487cd7d0
20 changed files with 165 additions and 53 deletions

View File

@ -17,7 +17,7 @@ const documents = {
"\n mutation UpsertAssignmentCompletion(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n $completionStatus: AssignmentCompletionStatus!\n $completionDataString: String!\n $evaluationPoints: Float\n $initializeCompletion: Boolean\n $evaluationUserId: ID\n ) {\n upsert_assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n assignment_user_id: $assignmentUserId\n completion_status: $completionStatus\n completion_data_string: $completionDataString\n evaluation_points: $evaluationPoints\n initialize_completion: $initializeCompletion\n evaluation_user_id: $evaluationUserId\n ) {\n assignment_completion {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_points\n completion_data\n task_completion_data\n }\n }\n }\n": types.UpsertAssignmentCompletionDocument,
"\n fragment CoursePageFields on CoursePageInterface {\n title\n id\n slug\n content_type\n frontend_url\n }\n": types.CoursePageFieldsFragmentDoc,
"\n query attendanceCheckQuery($courseSessionId: ID!) {\n course_session_attendance_course(id: $courseSessionId) {\n id\n attendance_user_list {\n user_id\n status\n }\n }\n }\n": types.AttendanceCheckQueryDocument,
"\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n solution_sample {\n id\n url\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n }\n assignment_user {\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n }\n }\n": types.AssignmentCompletionQueryDocument,
"\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n solution_sample {\n id\n url\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n first_name\n last_name\n }\n assignment_user {\n avatar_url\n first_name\n last_name\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n }\n }\n": types.AssignmentCompletionQueryDocument,
"\n query competenceCertificateQuery($courseSlug: String!, $courseSessionId: ID!) {\n competence_certificate_list(course_slug: $courseSlug) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n": types.CompetenceCertificateQueryDocument,
"\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n enable_circle_documents\n circle_contact_type\n }\n users {\n id\n user_id\n first_name\n last_name\n email\n avatar_url\n role\n circles {\n id\n title\n slug\n }\n }\n attendance_courses {\n id\n location\n trainer\n due_date {\n id\n start\n end\n }\n learning_content_id\n learning_content {\n id\n title\n circle {\n id\n title\n slug\n }\n }\n }\n assignments {\n id\n submission_deadline {\n id\n start\n }\n evaluation_deadline {\n id\n start\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n edoniq_tests {\n id\n deadline {\n id\n start\n end\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n }\n }\n": types.CourseSessionDetailDocument,
"\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n enable_circle_documents\n circle_contact_type\n action_competences {\n competence_id\n ...CoursePageFields\n performance_criteria {\n competence_id\n learning_unit {\n id\n slug\n evaluate_url\n }\n ...CoursePageFields\n }\n }\n learning_path {\n ...CoursePageFields\n topics {\n is_visible\n ...CoursePageFields\n circles {\n description\n goals\n ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n performance_criteria {\n ...CoursePageFields\n }\n learning_contents {\n can_user_self_toggle_course_completion\n content_url\n minutes\n description\n ...CoursePageFields\n ... on LearningContentAssignmentObjectType {\n assignment_type\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentEdoniqTestObjectType {\n checkbox_text\n has_extended_time_test\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentRichTextObjectType {\n text\n }\n }\n }\n }\n }\n }\n }\n }\n }\n": types.CourseQueryDocument,
@ -60,7 +60,7 @@ export function graphql(source: "\n query attendanceCheckQuery($courseSessionId
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n solution_sample {\n id\n url\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n }\n assignment_user {\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n }\n }\n"): (typeof documents)["\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n solution_sample {\n id\n url\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n }\n assignment_user {\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n }\n }\n"];
export function graphql(source: "\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n solution_sample {\n id\n url\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n first_name\n last_name\n }\n assignment_user {\n avatar_url\n first_name\n last_name\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n }\n }\n"): (typeof documents)["\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n solution_sample {\n id\n url\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n first_name\n last_name\n }\n assignment_user {\n avatar_url\n first_name\n last_name\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/

File diff suppressed because one or more lines are too long

View File

@ -66,8 +66,13 @@ export const ASSIGNMENT_COMPLETION_QUERY = graphql(`
evaluation_submitted_at
evaluation_user {
id
first_name
last_name
}
assignment_user {
avatar_url
first_name
last_name
id
}
evaluation_points

View File

@ -1,16 +1,15 @@
<script setup lang="ts">
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
import { useCurrentCourseSession } from "@/composables";
import { ASSIGNMENT_COMPLETION_QUERY } from "@/graphql/queries";
import EvaluationContainer from "@/pages/cockpit/assignmentEvaluationPage/EvaluationContainer.vue";
import AssignmentSubmissionResponses from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionResponses.vue";
import type { Assignment, AssignmentCompletion } from "@/types";
import type { Assignment, AssignmentCompletion, CourseSessionUser } from "@/types";
import { useQuery } from "@urql/vue";
import log from "loglevel";
import { computed, onMounted } from "vue";
import { useRouter } from "vue-router";
import { getPreviousRoute } from "@/router/history";
import { getAssignmentTypeTitle } from "../../../utils/utils";
import { useExpertCockpitPageData } from "@/pages/cockpit/cockpitPage/composables";
const props = defineProps<{
courseSlug: string;
@ -20,7 +19,6 @@ const props = defineProps<{
log.debug("AssignmentEvaluationPage created", props.assignmentId, props.userId);
const { loading } = useExpertCockpitPageData(props.courseSlug);
const courseSession = useCurrentCourseSession();
const router = useRouter();
@ -38,9 +36,6 @@ onMounted(async () => {
log.debug("AssignmentView mounted", props.assignmentId, props.userId);
});
const courseSessionDetailResult = useCourseSessionDetailQuery();
const assignmentUser = computed(() => courseSessionDetailResult.findUser(props.userId));
const previousRoute = getPreviousRoute();
function close() {
@ -58,13 +53,17 @@ const assignmentCompletion = computed(
queryResult.data.value?.assignment_completion as AssignmentCompletion | undefined
);
const assignmentUser = computed(
() => assignmentCompletion.value?.assignment_user as CourseSessionUser | undefined
);
const assignment = computed(
() => queryResult.data.value?.assignment as Assignment | undefined
);
</script>
<template>
<div v-if="!loading" class="absolute bottom-0 top-0 z-10 w-full bg-white">
<div class="absolute bottom-0 top-0 z-10 w-full bg-white">
<div v-if="queryResult.fetching.value"></div>
<div v-else-if="queryResult.error.value">{{ queryResult.error.value }}</div>
<div v-else>
@ -101,12 +100,12 @@ const assignment = computed(
<div class="my-6 flex items-center">
<img
:src="assignmentUser?.avatar_url"
:src="assignmentUser.avatar_url"
class="mr-4 h-11 w-11 rounded-full"
/>
<div class="font-bold">
{{ assignmentUser?.first_name }}
{{ assignmentUser?.last_name }}
{{ assignmentUser.first_name }}
{{ assignmentUser.last_name }}
</div>
</div>
<AssignmentSubmissionResponses

View File

@ -58,7 +58,7 @@ async function startEvaluation() {
upsertAssignmentCompletionMutation.executeMutation({
assignmentId: props.assignment.id,
courseSessionId: courseSession.value.id,
assignmentUserId: props.assignmentUser.user_id,
assignmentUserId: props.assignmentUser.id,
completionStatus: "EVALUATION_IN_PROGRESS",
completionDataString: JSON.stringify({}),
// next line used for urql

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import ItSuccessAlert from "@/components/ui/ItSuccessAlert.vue";
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
import { useCurrentCourseSession } from "@/composables";
import { UPSERT_ASSIGNMENT_COMPLETION_MUTATION } from "@/graphql/mutations";
import {
maxAssignmentPoints,
@ -75,7 +75,7 @@ async function submitEvaluation() {
upsertAssignmentCompletionMutation.executeMutation({
assignmentId: props.assignment.id,
courseSessionId: courseSession.value.id,
assignmentUserId: props.assignmentUser.user_id,
assignmentUserId: props.assignmentUser.id,
completionStatus: "EVALUATION_SUBMITTED",
completionDataString: JSON.stringify({}),
evaluationPoints: userPoints.value,
@ -110,26 +110,19 @@ const maxPoints = computed(() => maxAssignmentPoints(props.assignment));
const userPoints = computed(() =>
userAssignmentPoints(props.assignment, props.assignmentCompletion)
);
const courseSessionDetailResult = useCourseSessionDetailQuery();
const evaluationUser = computed(() => {
if (props.assignmentCompletion.evaluation_user) {
return courseSessionDetailResult.findUser(
props.assignmentCompletion.evaluation_user?.id
);
}
return undefined;
});
</script>
<template>
<!-- eslint-disable vue/no-v-html -->
<div>
<h3 v-if="evaluationUser && props.showEvaluationUser" class="mb-6">
<h3
v-if="assignmentCompletion.evaluation_user && props.showEvaluationUser"
class="mb-6"
>
{{
$t(text.evaluationFromUser, {
x: evaluationUser?.first_name,
y: evaluationUser?.last_name,
x: assignmentCompletion.evaluation_user.first_name,
y: assignmentCompletion.evaluation_user.last_name,
})
}}
</h3>

View File

@ -91,7 +91,7 @@ async function evaluateAssignmentCompletion(completionData: AssignmentCompletion
upsertAssignmentCompletionMutation.executeMutation({
assignmentId: props.assignment.id,
courseSessionId: courseSession.value.id,
assignmentUserId: props.assignmentUser.user_id,
assignmentUserId: props.assignmentUser.id,
completionStatus: "EVALUATION_IN_PROGRESS",
completionDataString: JSON.stringify(completionData),
// eslint-disable-next-line @typescript-eslint/ban-ts-comment

View File

@ -1,8 +1,9 @@
<script setup lang="ts">
import type { Participant, PraxisAssignment } from "@/services/mentorCockpit";
import { useMentorCockpit } from "@/services/mentorCockpit";
import { computed, type Ref } from "vue";
import { computed, onMounted, type Ref } from "vue";
import { useCurrentCourseSession } from "@/composables";
import log from "loglevel";
const props = defineProps<{
praxisAssignmentId: string;
@ -11,19 +12,19 @@ const props = defineProps<{
const courseSession = useCurrentCourseSession();
const mentorCockpitStore = useMentorCockpit(courseSession.value.id);
const participants = computed(() => mentorCockpitStore.summary.value?.participants);
const praxisAssignment: Ref<PraxisAssignment | null> = computed(() =>
mentorCockpitStore.getPraxisAssignmentById(props.praxisAssignmentId)
);
const getParticipantById = (id: string): Participant | null => {
if (mentorCockpitStore.summary.value?.participants) {
const found = mentorCockpitStore.summary.value.participants.find(
(item) => item.id === id
);
return found || null;
}
return null;
return participants.value?.find((participant) => participant.id === id) || null;
};
onMounted(() => {
log.debug("MentorPraxisAssignment mounted");
mentorCockpitStore.fetchData();
});
</script>
<template>
@ -96,7 +97,22 @@ const getParticipantById = (id: string): Participant | null => {
</template>
</div>
<!-- Right -->
<div></div>
<div>
<router-link
v-if="item.status == 'SUBMITTED'"
class="btn-primary"
:to="item.url"
>
{{ $t("a.Ergebnis bewerten") }}
</router-link>
<router-link
v-else-if="item.status == 'EVALUATED'"
class="underline"
:to="item.url"
>
{{ $t("a.Bewertung ansehen") }}
</router-link>
</div>
</div>
</div>
</div>

View File

@ -18,9 +18,9 @@ import { useRouteQuery } from "@vueuse/router";
import * as log from "loglevel";
import { computed, onMounted, ref, watchEffect } from "vue";
import { useTranslation } from "i18next-vue";
import { bustItGetCache } from "@/fetchHelpers";
import { learningContentTypeData } from "@/utils/typeMaps";
import EvaluationSummary from "@/pages/cockpit/assignmentEvaluationPage/EvaluationSummary.vue";
import { bustItGetCache } from "@/fetchHelpers";
const { t } = useTranslation();
const courseSession = useCurrentCourseSession();

View File

@ -1,4 +1,4 @@
import { itGetCached } from "@/fetchHelpers";
import { itGet } from "@/fetchHelpers";
import type { Ref } from "vue";
import { ref, watchEffect } from "vue";
@ -27,6 +27,7 @@ interface Completion {
status: CompletionStatus;
user_id: string;
last_name: string;
url: string;
}
export interface PraxisAssignment {
@ -73,7 +74,7 @@ export const useMentorCockpit = (
summary.value = null;
error.value = null;
itGetCached(`/api/mentor/${courseSessionId}/summary`)
itGet(`/api/mentor/${courseSessionId}/summary`)
.then((response) => {
summary.value = response;
})
@ -93,5 +94,6 @@ export const useMentorCockpit = (
error,
getCircleTitleById,
getPraxisAssignmentById,
fetchData,
};
};

View File

@ -99,7 +99,11 @@ class AssignmentCompletionMutation(graphene.Mutation):
AssignmentCompletionStatus.EVALUATION_SUBMITTED,
AssignmentCompletionStatus.EVALUATION_IN_PROGRESS,
):
if not can_evaluate_assignments(info.context.user, course_session_id):
if not can_evaluate_assignments(
evaluation_user=info.context.user,
assignment_user_id=assignment_user_id,
course_session_id=course_session_id,
):
raise PermissionDenied()
evaluation_data = {

View File

@ -109,7 +109,9 @@ def resolve_assignment_completion(
assignment_user_id = info.context.user.id
if str(assignment_user_id) == str(info.context.user.id) or can_evaluate_assignments(
info.context.user, course_session_id
evaluation_user=info.context.user,
assignment_user_id=assignment_user_id,
course_session_id=course_session_id,
):
course_id = CourseSession.objects.get(id=course_session_id).course_id
if has_course_access(info.context.user, course_id):

View File

@ -41,7 +41,7 @@ class Course(models.Model):
return f"/course/{self.slug}"
def get_cockpit_url(self):
return f"/{self.get_course_url()}/cockpit"
return f"{self.get_course_url()}/cockpit"
def get_learning_path(self):
from vbv_lernwelt.learnpath.models import LearningPath

View File

@ -18,6 +18,9 @@ def has_course_access(user, course_id):
).exists():
return True
if LearningMentor.objects.filter(course_id=course_id, mentor=user).exists():
return True
return CourseSessionUser.objects.filter(
course_session__course_id=course_id, user=user
).exists()
@ -32,6 +35,19 @@ def has_course_session_access(user, course_session_id: int):
).exists()
def is_user_mentor(mentor: User, participant_user_id: str, course_session_id: int):
csu = CourseSessionUser.objects.filter(
course_session_id=course_session_id, user_id=participant_user_id
).first()
if csu is None:
return False
return LearningMentor.objects.filter(
course_id=csu.course_session.course_id, mentor=mentor, participants=csu
).exists()
def is_course_session_expert(user, course_session_id: int):
if user.is_superuser:
return True
@ -67,21 +83,29 @@ def is_course_session_member(user, course_session_id: int | None = None):
).exists()
def can_evaluate_assignments(user, course_session_id: int):
if user.is_superuser:
def can_evaluate_assignments(
evaluation_user: User, course_session_id: int, assignment_user_id: str | None = None
):
if evaluation_user.is_superuser:
return True
is_supervisor = CourseSessionGroup.objects.filter(
supervisor=user, course_session__id=course_session_id
supervisor=evaluation_user, course_session__id=course_session_id
).exists()
is_expert = CourseSessionUser.objects.filter(
course_session_id=course_session_id,
user=user,
user=evaluation_user,
role=CourseSessionUser.Role.EXPERT,
).exists()
return is_supervisor or is_expert
is_mentor = is_user_mentor(
mentor=evaluation_user,
participant_user_id=assignment_user_id,
course_session_id=course_session_id,
)
return is_supervisor or is_expert or is_mentor
def course_sessions_for_user_qs(user):

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_session_group.models import CourseSessionGroup
from vbv_lernwelt.iam.permissions import has_role_in_course
from vbv_lernwelt.iam.permissions import has_role_in_course, is_user_mentor
from vbv_lernwelt.learning_mentor.models import LearningMentor
@ -59,6 +59,62 @@ class RoleTestCase(TestCase):
# THEN
self.assertTrue(has_role)
def test_is_user_mentor(self):
# GIVEN
member = create_user("member")
mentor = create_user("mentor")
course, course_page = create_course("Test Course")
course_session = create_course_session(course=course, title=":)")
member_csu = add_course_session_user(
course_session=course_session,
user=member,
role=CourseSessionUser.Role.MEMBER,
)
learning_mentor = LearningMentor.objects.create(
mentor=mentor,
course=course,
)
learning_mentor.participants.add(member_csu)
learning_mentor.save()
# WHEN
is_mentor = is_user_mentor(
mentor=mentor,
participant_user_id=member.id,
course_session_id=course_session.id,
)
# THEN
self.assertTrue(is_mentor)
def test_not_is_user_mentor(self):
# GIVEN
member = create_user("member")
wanna_be_mentor = create_user("wanna_be_mentor")
course, course_page = create_course("Test Course")
course_session = create_course_session(course=course, title=":)")
add_course_session_user(
course_session=course_session,
user=member,
role=CourseSessionUser.Role.MEMBER,
)
# WHEN
is_mentor = is_user_mentor(
mentor=wanna_be_mentor,
participant_user_id=member.id,
course_session_id=course_session.id,
)
# THEN
self.assertFalse(is_mentor)
def test_no_role(self):
# GIVEN
other_course, _ = create_course("Other Test Course")

View File

@ -71,6 +71,7 @@ def get_assignment_completions(
)[0],
user_id=user.id,
last_name=user.last_name,
url=f"/course/{course_session.course.slug}/cockpit/assignment/{assignment.id}/{user.id}",
)
for user in sorted_participants
]

View File

@ -14,6 +14,7 @@ class PraxisAssignmentCompletion:
status: CompletionStatus
user_id: str
last_name: str
url: str
@dataclass

View File

@ -8,6 +8,7 @@ class PraxisAssignmentCompletionSerializer(serializers.Serializer):
status = serializers.SerializerMethodField()
user_id = serializers.CharField()
last_name = serializers.CharField()
url = serializers.CharField()
@staticmethod
def get_status(obj):

View File

@ -84,6 +84,10 @@ class AttendanceServicesTestCase(TestCase):
for i, result in enumerate(results):
self.assertEqual(result.last_name, expected_order[i])
self.assertEqual(result.status, expected_statuses[result.last_name])
self.assertEqual(
result.url,
f"/course/test-lehrgang/cockpit/assignment/{self.assignment.id}/{result.user_id}",
)
def test_praxis_assignment_status(self):
# GIVEN

View File

@ -9,6 +9,7 @@ from vbv_lernwelt.course_session.models import (
CourseSessionAssignment,
CourseSessionEdoniqTest,
)
from vbv_lernwelt.learnpath.models import Circle
from vbv_lernwelt.notify.services import NotificationService
logger = structlog.get_logger(__name__)
@ -63,9 +64,12 @@ def send_assignment_reminder_notifications():
AssignmentType.CASEWORK.value,
],
):
circle_page = assignment.learning_content.get_parent_circle()
circle = Circle.objects.get(page_ptr=circle_page.id)
for expert in CourseSessionUser.objects.filter(
course_session_id=assignment.course_session.id,
role=CourseSessionUser.Role.EXPERT,
expert=circle,
):
sent.append(
NotificationService.send_casework_expert_evaluation_reminder(