Merged in feat/course-feature-toggles (pull request #295)

Introduce Course Configuration

Approved-by: Daniel Egger
This commit is contained in:
Livio Bieri 2024-03-06 19:24:53 +00:00
commit 709931c888
42 changed files with 501 additions and 334 deletions

View File

@ -23,7 +23,6 @@ import {
getMediaCenterUrl, getMediaCenterUrl,
} from "@/utils/utils"; } from "@/utils/utils";
import { useCockpitStore } from "@/stores/cockpit"; import { useCockpitStore } from "@/stores/cockpit";
import { VV_COURSE_IDS } from "@/constants";
log.debug("MainNavigationBar created"); log.debug("MainNavigationBar created");
@ -100,13 +99,13 @@ const hasNotificationsMenu = computed(() => {
}); });
const hasMentorManagementMenu = computed(() => { const hasMentorManagementMenu = computed(() => {
if (courseSessionsStore.currentCourseSessionHasCockpit) { if (courseSessionsStore.currentCourseSessionHasCockpit || !inCourse()) {
return false; return false;
} }
return (
// learning mentor management is only available for VV courses courseSessionsStore.currentCourseSession?.course.configuration
const currentCourseId = courseSessionsStore.currentCourseSession?.course.id || ""; .enable_learning_mentor ?? false
return inCourse() && VV_COURSE_IDS.includes(currentCourseId); );
}); });
</script> </script>

View File

@ -37,7 +37,7 @@ const questionIndex = useRouteQuery("step", "0", { transform: Number, mode: "pus
const previousRoute = getPreviousRoute(); const previousRoute = getPreviousRoute();
const learningUnitHasFeedbackPage = computed( const learningUnitHasFeedbackPage = computed(
() => props.learningUnit?.feedback_user !== "NO_FEEDBACK" () => courseSession.value.course.configuration.enable_learning_mentor
); );
const currentQuestion = computed(() => questions.value[questionIndex.value]); const currentQuestion = computed(() => questions.value[questionIndex.value]);
@ -163,7 +163,7 @@ onUnmounted(() => {
</div> </div>
</div> </div>
<SelfEvaluationRequestFeedbackPage <SelfEvaluationRequestFeedbackPage
v-else-if="isLastStep && learningUnit.feedback_user == 'MENTOR_FEEDBACK'" v-else-if="isLastStep && learningUnitHasFeedbackPage"
:learning-unit="props.learningUnit" :learning-unit="props.learningUnit"
:criteria="questions" :criteria="questions"
/> />

View File

@ -3,9 +3,3 @@ export const itCheckboxDefaultIconCheckedTailwindClass =
export const itCheckboxDefaultIconUncheckedTailwindClass = export const itCheckboxDefaultIconUncheckedTailwindClass =
"bg-[url(/static/icons/icon-checkbox-unchecked.svg)] hover:bg-[url(/static/icons/icon-checkbox-unchecked-hover.svg)]"; "bg-[url(/static/icons/icon-checkbox-unchecked.svg)] hover:bg-[url(/static/icons/icon-checkbox-unchecked-hover.svg)]";
export const VV_COURSE_IDS = [
"-4", // vv-de
"-10", // vv-fr
"-11", // vv-it
];

View File

@ -19,9 +19,9 @@ const documents = {
"\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 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 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 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 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 courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\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 feedback_user\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, "\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\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,
"\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n }\n }\n": types.DashboardConfigDocument, "\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n course_configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n }\n }\n": types.DashboardConfigDocument,
"\n query dashboardProgress($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n competence {\n _id\n total_count\n success_count\n fail_count\n }\n assignment {\n _id\n total_count\n points_max_count\n points_achieved_count\n }\n }\n }\n": types.DashboardProgressDocument, "\n query dashboardProgress($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n competence {\n _id\n total_count\n success_count\n fail_count\n }\n assignment {\n _id\n total_count\n points_max_count\n points_achieved_count\n }\n }\n }\n": types.DashboardProgressDocument,
"\n query courseStatistics($courseId: ID!) {\n course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n }\n }\n course_session_selection_ids\n course_session_selection_metrics {\n _id\n session_count\n participant_count\n expert_count\n }\n attendance_day_presences {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n due_date\n participants_present\n participants_total\n details_url\n }\n summary {\n _id\n days_completed\n participants_present\n }\n }\n feedback_responses {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n experts\n satisfaction_average\n satisfaction_max\n details_url\n }\n summary {\n _id\n satisfaction_average\n satisfaction_max\n total_responses\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_passed\n }\n }\n }\n competences {\n _id\n summary {\n _id\n success_total\n fail_total\n }\n records {\n _id\n course_session_id\n generation\n circle_id\n title\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n": types.CourseStatisticsDocument, "\n query courseStatistics($courseId: ID!) {\n course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n }\n }\n course_session_selection_ids\n course_session_selection_metrics {\n _id\n session_count\n participant_count\n expert_count\n }\n attendance_day_presences {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n due_date\n participants_present\n participants_total\n details_url\n }\n summary {\n _id\n days_completed\n participants_present\n }\n }\n feedback_responses {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n experts\n satisfaction_average\n satisfaction_max\n details_url\n }\n summary {\n _id\n satisfaction_average\n satisfaction_max\n total_responses\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_passed\n }\n }\n }\n competences {\n _id\n summary {\n _id\n success_total\n fail_total\n }\n records {\n _id\n course_session_id\n generation\n circle_id\n title\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n": types.CourseStatisticsDocument,
"\n mutation SendFeedbackMutation(\n $courseSessionId: ID!\n $learningContentId: ID!\n $learningContentType: String!\n $data: GenericScalar!\n $submitted: Boolean\n ) {\n send_feedback(\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n learning_content_type: $learningContentType\n data: $data\n submitted: $submitted\n ) {\n feedback_response {\n id\n data\n submitted\n }\n errors {\n field\n messages\n }\n }\n }\n": types.SendFeedbackMutationDocument, "\n mutation SendFeedbackMutation(\n $courseSessionId: ID!\n $learningContentId: ID!\n $learningContentType: String!\n $data: GenericScalar!\n $submitted: Boolean\n ) {\n send_feedback(\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n learning_content_type: $learningContentType\n data: $data\n submitted: $submitted\n ) {\n feedback_response {\n id\n data\n submitted\n }\n errors {\n field\n messages\n }\n }\n }\n": types.SendFeedbackMutationDocument,
@ -68,15 +68,15 @@ export function graphql(source: "\n query competenceCertificateQuery($courseSlu
/** /**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * 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 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"): (typeof documents)["\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"]; export function graphql(source: "\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\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"): (typeof documents)["\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\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"];
/** /**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * 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 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 feedback_user\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"): (typeof documents)["\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 feedback_user\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"]; export function graphql(source: "\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\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"): (typeof documents)["\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\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"];
/** /**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * 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 dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n }\n }\n"): (typeof documents)["\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n }\n }\n"]; export function graphql(source: "\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n course_configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n }\n }\n"): (typeof documents)["\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n course_configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n }\n }\n"];
/** /**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * 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

@ -200,6 +200,7 @@ type DashboardConfigType {
name: String! name: String!
slug: String! slug: String!
dashboard_type: DashboardType! dashboard_type: DashboardType!
course_configuration: CourseConfigurationObjectType!
} }
enum DashboardType { enum DashboardType {
@ -208,6 +209,13 @@ enum DashboardType {
SIMPLE_DASHBOARD SIMPLE_DASHBOARD
} }
type CourseConfigurationObjectType {
id: ID!
enable_circle_documents: Boolean!
enable_learning_mentor: Boolean!
enable_competence_certificates: Boolean!
}
type LearningPathObjectType implements CoursePageInterface { type LearningPathObjectType implements CoursePageInterface {
id: ID! id: ID!
title: String! title: String!
@ -236,21 +244,11 @@ type CourseObjectType {
title: String! title: String!
category_name: String! category_name: String!
slug: String! slug: String!
enable_circle_documents: Boolean! configuration: CourseConfigurationObjectType!
circle_contact_type: CourseCourseCircleContactTypeChoices!
learning_path: LearningPathObjectType! learning_path: LearningPathObjectType!
action_competences: [ActionCompetenceObjectType!]! action_competences: [ActionCompetenceObjectType!]!
} }
"""An enumeration."""
enum CourseCourseCircleContactTypeChoices {
"""EXPERT"""
EXPERT
"""LEARNING_MENTOR"""
LEARNING_MENTOR
}
type ActionCompetenceObjectType implements CoursePageInterface { type ActionCompetenceObjectType implements CoursePageInterface {
competence_id: String! competence_id: String!
id: ID! id: ID!
@ -279,7 +277,6 @@ type PerformanceCriteriaObjectType implements CoursePageInterface {
type LearningUnitObjectType implements CoursePageInterface { type LearningUnitObjectType implements CoursePageInterface {
title_hidden: Boolean! title_hidden: Boolean!
feedback_user: LearnpathLearningUnitFeedbackUserChoices!
id: ID! id: ID!
title: String! title: String!
slug: String! slug: String!
@ -293,15 +290,6 @@ type LearningUnitObjectType implements CoursePageInterface {
evaluate_url: String! evaluate_url: String!
} }
"""An enumeration."""
enum LearnpathLearningUnitFeedbackUserChoices {
"""NO_FEEDBACK"""
NO_FEEDBACK
"""MENTOR_FEEDBACK"""
MENTOR_FEEDBACK
}
interface LearningContentInterface { interface LearningContentInterface {
id: ID! id: ID!
title: String! title: String!

View File

@ -25,7 +25,7 @@ export const CompetenceRecordStatisticsType = "CompetenceRecordStatisticsType";
export const CompetencesStatisticsType = "CompetencesStatisticsType"; export const CompetencesStatisticsType = "CompetencesStatisticsType";
export const ContentDocumentObjectType = "ContentDocumentObjectType"; export const ContentDocumentObjectType = "ContentDocumentObjectType";
export const CoreUserLanguageChoices = "CoreUserLanguageChoices"; export const CoreUserLanguageChoices = "CoreUserLanguageChoices";
export const CourseCourseCircleContactTypeChoices = "CourseCourseCircleContactTypeChoices"; export const CourseConfigurationObjectType = "CourseConfigurationObjectType";
export const CourseObjectType = "CourseObjectType"; export const CourseObjectType = "CourseObjectType";
export const CoursePageInterface = "CoursePageInterface"; export const CoursePageInterface = "CoursePageInterface";
export const CourseProgressType = "CourseProgressType"; export const CourseProgressType = "CourseProgressType";
@ -69,7 +69,6 @@ export const LearningPathObjectType = "LearningPathObjectType";
export const LearningSequenceObjectType = "LearningSequenceObjectType"; export const LearningSequenceObjectType = "LearningSequenceObjectType";
export const LearningUnitObjectType = "LearningUnitObjectType"; export const LearningUnitObjectType = "LearningUnitObjectType";
export const LearnpathLearningContentAssignmentAssignmentTypeChoices = "LearnpathLearningContentAssignmentAssignmentTypeChoices"; export const LearnpathLearningContentAssignmentAssignmentTypeChoices = "LearnpathLearningContentAssignmentAssignmentTypeChoices";
export const LearnpathLearningUnitFeedbackUserChoices = "LearnpathLearningUnitFeedbackUserChoices";
export const Mutation = "Mutation"; export const Mutation = "Mutation";
export const PerformanceCriteriaObjectType = "PerformanceCriteriaObjectType"; export const PerformanceCriteriaObjectType = "PerformanceCriteriaObjectType";
export const PresenceRecordStatisticsType = "PresenceRecordStatisticsType"; export const PresenceRecordStatisticsType = "PresenceRecordStatisticsType";

View File

@ -126,8 +126,12 @@ export const COURSE_SESSION_DETAIL_QUERY = graphql(`
id id
title title
slug slug
enable_circle_documents configuration {
circle_contact_type id
enable_circle_documents
enable_learning_mentor
enable_competence_certificates
}
} }
users { users {
id id
@ -211,8 +215,12 @@ export const COURSE_QUERY = graphql(`
title title
slug slug
category_name category_name
enable_circle_documents configuration {
circle_contact_type id
enable_circle_documents
enable_learning_mentor
enable_competence_certificates
}
action_competences { action_competences {
competence_id competence_id
...CoursePageFields ...CoursePageFields
@ -239,7 +247,6 @@ export const COURSE_QUERY = graphql(`
icon icon
...CoursePageFields ...CoursePageFields
learning_units { learning_units {
feedback_user
evaluate_url evaluate_url
...CoursePageFields ...CoursePageFields
performance_criteria { performance_criteria {
@ -292,6 +299,12 @@ export const DASHBOARD_CONFIG = graphql(`
slug slug
name name
dashboard_type dashboard_type
course_configuration {
id
enable_circle_documents
enable_learning_mentor
enable_competence_certificates
}
} }
} }
`); `);

View File

@ -59,7 +59,7 @@ const courseSessionDetailResult = useCourseSessionDetailQuery();
</div> </div>
</div> </div>
<div <div
v-if="courseSession.course.enable_circle_documents" v-if="courseSession.course.configuration.enable_circle_documents"
class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0" class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0"
data-cy="circle-documents" data-cy="circle-documents"
> >
@ -70,13 +70,6 @@ const courseSessionDetailResult = useCourseSessionDetailQuery();
<div class="mb-4"> <div class="mb-4">
{{ $t("a.Stelle deinen Lernenden zusätzliche Inhalte zur Verfügung.") }} {{ $t("a.Stelle deinen Lernenden zusätzliche Inhalte zur Verfügung.") }}
</div> </div>
<!-- <div-->
<!-- v-if="courseSessionsStore.circleDocuments.length"-->
<!-- class="mb-4 flex items-center gap-x-2"-->
<!-- >-->
<!-- <it-icon-document />-->
<!-- {{ courseSessionsStore.circleDocuments.length }} {{ $t("a.Unterlagen") }}-->
<!-- </div>-->
</div> </div>
<div> <div>
<router-link <router-link

View File

@ -12,7 +12,6 @@ import {
} from "@/pages/competence/utils"; } from "@/pages/competence/utils";
import { useSelfEvaluationFeedbackSummaries } from "@/services/selfEvaluationFeedback"; import { useSelfEvaluationFeedbackSummaries } from "@/services/selfEvaluationFeedback";
import ItProgress from "@/components/ui/ItProgress.vue"; import ItProgress from "@/components/ui/ItProgress.vue";
import { VV_COURSE_IDS } from "@/constants";
const props = defineProps<{ const props = defineProps<{
courseSlug: string; courseSlug: string;
@ -61,18 +60,7 @@ const feedbackEvaluationCounts = computed(
() => selfEvaluationFeedbackSummaries.aggregates.value?.feedback_assessment () => selfEvaluationFeedbackSummaries.aggregates.value?.feedback_assessment
); );
const isFeedbackEvaluationVisible = computed(
() =>
selfEvaluationFeedbackSummaries.aggregates.value?.feedback_assessment_visible ??
false
);
// FIXME 22.02.24: To-be-tackled NEXT in a separate PR (shippable member comp.navi)
// -> Do not use the VV_COURSE_ID anymore (discuss with @chrigu) -> We do this next.
const currentCourseSession = useCurrentCourseSession(); const currentCourseSession = useCurrentCourseSession();
const hasCompetenceCertificates = computed(() => {
return !VV_COURSE_IDS.includes(currentCourseSession.value.course.id);
});
const isLoaded = computed( const isLoaded = computed(
() => () =>
@ -83,7 +71,10 @@ const isLoaded = computed(
<template> <template>
<div v-if="isLoaded" class="container-large lg:mt-4"> <div v-if="isLoaded" class="container-large lg:mt-4">
<!-- Competence certificates --> <!-- Competence certificates -->
<section v-if="hasCompetenceCertificates" class="mb-4 bg-white p-8"> <section
v-if="currentCourseSession.course.configuration.enable_competence_certificates"
class="mb-4 bg-white p-8"
>
<div class="flex items-center"> <div class="flex items-center">
<h3>{{ $t("a.Kompetenznachweise") }}</h3> <h3>{{ $t("a.Kompetenznachweise") }}</h3>
</div> </div>
@ -204,7 +195,10 @@ const isLoaded = computed(
</div> </div>
<!-- Feedback evaluation --> <!-- Feedback evaluation -->
<div v-if="isFeedbackEvaluationVisible" class="mb-8 border-t pt-8"> <div
v-if="courseSession.course.configuration.enable_learning_mentor"
class="mb-8 border-t pt-8"
>
<h3 class="mb-4 pb-4 lg:pb-0"> <h3 class="mb-4 pb-4 lg:pb-0">
{{ $t("a.Fremdeinschätzungen") }} {{ $t("a.Fremdeinschätzungen") }}
</h3> </h3>

View File

@ -1,8 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import * as log from "loglevel"; import * as log from "loglevel";
import { computed, onMounted } from "vue"; import { onMounted } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { VV_COURSE_IDS } from "@/constants";
import { useCurrentCourseSession } from "@/composables"; import { useCurrentCourseSession } from "@/composables";
log.debug("CompetenceParentPage created"); log.debug("CompetenceParentPage created");
@ -29,12 +28,7 @@ function routeInSelfEvaluationAndFeedback() {
return route.path.endsWith("/self-evaluation-and-feedback"); return route.path.endsWith("/self-evaluation-and-feedback");
} }
// FIXME 22.02.24: To-be-tackled NEXT in a separate PR (shippable member comp.navi)
// -> Do not use the VV_COURSE_ID anymore (discuss with @chrigu) -> We do this next.
const currentCourseSession = useCurrentCourseSession(); const currentCourseSession = useCurrentCourseSession();
const isVVCourse = computed(() => {
return VV_COURSE_IDS.includes(currentCourseSession.value.course.id);
});
onMounted(async () => { onMounted(async () => {
log.debug("CompetenceParentPage mounted", props.courseSlug); log.debug("CompetenceParentPage mounted", props.courseSlug);
@ -54,7 +48,9 @@ onMounted(async () => {
</router-link> </router-link>
</li> </li>
<li <li
v-if="!isVVCourse" v-if="
currentCourseSession.course.configuration.enable_competence_certificates
"
class="border-t-2 border-t-transparent lg:ml-12" class="border-t-2 border-t-transparent lg:ml-12"
:class="{ 'border-b-2 border-b-blue-900': routeInCompetenceCertificate() }" :class="{ 'border-b-2 border-b-blue-900': routeInCompetenceCertificate() }"
> >
@ -76,7 +72,7 @@ onMounted(async () => {
class="block py-3" class="block py-3"
> >
{{ {{
isVVCourse currentCourseSession.course.configuration.enable_learning_mentor
? $t("a.Selbst- und Fremdeinschätzungen") ? $t("a.Selbst- und Fremdeinschätzungen")
: $t("a.Selbsteinschätzungen") : $t("a.Selbsteinschätzungen")
}} }}

View File

@ -10,6 +10,9 @@ const selfEvaluationFeedbackSummaries = useSelfEvaluationFeedbackSummaries(
useCurrentCourseSession().value.id useCurrentCourseSession().value.id
); );
const courseSession = useCurrentCourseSession();
const course = computed(() => courseSession.value.course);
const isLoaded = computed(() => !selfEvaluationFeedbackSummaries.loading.value); const isLoaded = computed(() => !selfEvaluationFeedbackSummaries.loading.value);
const selectedCircle = ref({ name: t("a.AlleCircle"), id: "_all" }); const selectedCircle = ref({ name: t("a.AlleCircle"), id: "_all" });
@ -32,10 +35,7 @@ const summaries = computed(() => {
}); });
const headerTitle = computed(() => { const headerTitle = computed(() => {
const canHaveFeedback = if (course.value.configuration.enable_learning_mentor) {
selfEvaluationFeedbackSummaries.aggregates.value?.feedback_assessment_visible ??
false;
if (canHaveFeedback) {
return t("a.Selbst- und Fremdeinschätzungen"); return t("a.Selbst- und Fremdeinschätzungen");
} else { } else {
return t("a.Selbsteinschätzungen"); return t("a.Selbsteinschätzungen");

View File

@ -10,7 +10,6 @@ import type {
} from "@/gql/graphql"; } from "@/gql/graphql";
import CompetenceSummaryBox from "@/components/dashboard/CompetenceSummaryBox.vue"; import CompetenceSummaryBox from "@/components/dashboard/CompetenceSummaryBox.vue";
import AssignmentProgressSummaryBox from "@/components/dashboard/AssignmentProgressSummaryBox.vue"; import AssignmentProgressSummaryBox from "@/components/dashboard/AssignmentProgressSummaryBox.vue";
import { VV_COURSE_IDS } from "@/constants";
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
@ -48,11 +47,12 @@ const competenceCriteriaUrl = computed(() => {
return `/course/${courseSlug.value}/competence/self-evaluation-and-feedback?courseSessionId=${courseSessionProgress.value?.session_to_continue_id}`; return `/course/${courseSlug.value}/competence/self-evaluation-and-feedback?courseSessionId=${courseSessionProgress.value?.session_to_continue_id}`;
}); });
const isVVCourse = computed(() => { const showCompetenceCertificates = computed(() => {
if (!dashboardStore.currentDashboardConfig) { if (!dashboardStore.currentDashboardConfig) {
return false; return false;
} }
return VV_COURSE_IDS.includes(dashboardStore.currentDashboardConfig.id); return dashboardStore.currentDashboardConfig.course_configuration
.enable_competence_certificates;
}); });
</script> </script>
@ -82,7 +82,7 @@ const isVVCourse = computed(() => {
</div> </div>
<div class="grid auto-rows-fr grid-cols-1 gap-8 xl:grid-cols-2"> <div class="grid auto-rows-fr grid-cols-1 gap-8 xl:grid-cols-2">
<AssignmentProgressSummaryBox <AssignmentProgressSummaryBox
v-if="!isVVCourse" v-if="showCompetenceCertificates"
:total-assignments="assignment.total_count" :total-assignments="assignment.total_count"
:achieved-points-count="assignment.points_achieved_count" :achieved-points-count="assignment.points_achieved_count"
:max-points-count="assignment.points_max_count" :max-points-count="assignment.points_max_count"

View File

@ -57,33 +57,14 @@ const learningContentReadonly = computed(() => {
return props.readonly || !actions.includes("complete-learning-content"); return props.readonly || !actions.includes("complete-learning-content");
}); });
const duration = computed(() => {
// if (circleStore.circle) {
// const minutes = sumBy(circleStore.circle.learningSequences, "minutes");
// return humanizeDuration(minutes);
// }
return "";
});
const showDuration = computed(() => {
// return (
// circleStore.circle && sumBy(circleStore.circle.learningSequences, "minutes") > 0
// );
return false;
});
const showDocumentSection = computed(() => { const showDocumentSection = computed(() => {
return lpQueryResult.course.value?.enable_circle_documents && !props.readonly; return (
lpQueryResult.course.value?.configuration.enable_circle_documents && !props.readonly
);
}); });
const courseConfig = computed(() => { const courseConfig = computed(() => {
if (lpQueryResult.course.value?.circle_contact_type === "EXPERT") { if (lpQueryResult.course.value?.configuration.enable_learning_mentor) {
return {
contactDescription: "circlePage.contactExpertDescription",
contactButton: "circlePage.contactExpertButton",
showContact: true,
};
} else if (lpQueryResult.course.value?.circle_contact_type === "LEARNING_MENTOR") {
return { return {
contactDescription: "circlePage.contactLearningMentorDescription", contactDescription: "circlePage.contactLearningMentorDescription",
contactButton: "circlePage.contactLearningMentorButton", contactButton: "circlePage.contactLearningMentorButton",
@ -91,8 +72,8 @@ const courseConfig = computed(() => {
}; };
} else { } else {
return { return {
contactDescription: "", contactDescription: "circlePage.contactExpertDescription",
contactButton: "", contactButton: "circlePage.contactExpertButton",
showContact: true, showContact: true,
}; };
} }
@ -117,12 +98,12 @@ interface Mentor {
const experts = computed<Expert[] | null>(() => { const experts = computed<Expert[] | null>(() => {
if (courseConfig.value.showContact) { if (courseConfig.value.showContact) {
if (lpQueryResult.course.value?.circle_contact_type === "EXPERT") { if (lpQueryResult.course.value?.configuration.enable_learning_mentor) {
return circleExperts.value;
} else if (lpQueryResult.course.value?.circle_contact_type === "LEARNING_MENTOR") {
if (mentors.value?.length > 0) { if (mentors.value?.length > 0) {
return mentors.value.map((m: Mentor) => m.mentor); return mentors.value.map((m: Mentor) => m.mentor);
} }
} else {
return circleExperts.value;
} }
} }
return null; return null;
@ -216,11 +197,6 @@ watch(
{{ circle?.title }} {{ circle?.title }}
</h1> </h1>
<div v-if="showDuration" class="mt-2">
{{ $t("circlePage.duration") }}:
{{ duration }}
</div>
<div class="mt-8 w-full"> <div class="mt-8 w-full">
<CircleDiagram v-if="circle" :circle="circle"></CircleDiagram> <CircleDiagram v-if="circle" :circle="circle"></CircleDiagram>
</div> </div>

View File

@ -35,8 +35,6 @@ export interface FeedbackSummaryAggregates {
// totals across all learning units in the course session // totals across all learning units in the course session
self_assessment: FeedbackSummaryCounts; self_assessment: FeedbackSummaryCounts;
feedback_assessment: FeedbackSummaryCounts; feedback_assessment: FeedbackSummaryCounts;
// does this course have any feedback?
feedback_assessment_visible: boolean;
} }
interface FeedbackAssessmentSummary { interface FeedbackAssessmentSummary {

View File

@ -5,7 +5,6 @@ import type {
AssignmentCompletionStatus as AssignmentCompletionStatusGenerated, AssignmentCompletionStatus as AssignmentCompletionStatusGenerated,
AssignmentObjectType, AssignmentObjectType,
CircleObjectType, CircleObjectType,
CourseCourseCircleContactTypeChoices,
CourseSessionObjectType, CourseSessionObjectType,
CourseSessionUserObjectsType, CourseSessionUserObjectsType,
LearningContentAssignmentObjectType, LearningContentAssignmentObjectType,
@ -190,13 +189,18 @@ export interface CourseCompletion {
additional_json_data: unknown; additional_json_data: unknown;
} }
export interface CourseConfiguration {
enable_circle_documents: boolean;
enable_learning_mentor: boolean;
enable_competence_certificates: boolean;
}
export interface Course { export interface Course {
id: string; id: string;
title: string; title: string;
category_name: string; category_name: string;
slug: string; slug: string;
enable_circle_documents: boolean; configuration: CourseConfiguration;
circle_contact_type: CourseCourseCircleContactTypeChoices;
} }
export interface CourseCategory { export interface CourseCategory {

View File

@ -369,5 +369,5 @@ def command(
) )
course = Course.objects.get(id=COURSE_TEST_ID) course = Course.objects.get(id=COURSE_TEST_ID)
course.enable_circle_documents = enable_circle_documents course.configuration.enable_circle_documents = enable_circle_documents
course.save() course.configuration.save()

View File

@ -1,6 +1,11 @@
from django.contrib import admin from django.contrib import admin
from vbv_lernwelt.course.models import Course, CourseSession, CourseSessionUser from vbv_lernwelt.course.models import (
Course,
CourseConfiguration,
CourseSession,
CourseSessionUser,
)
from vbv_lernwelt.feedback.services import ( from vbv_lernwelt.feedback.services import (
get_feedbacks_for_course_sessions, get_feedbacks_for_course_sessions,
get_feedbacks_for_courses, get_feedbacks_for_courses,
@ -11,6 +16,16 @@ get_feedbacks_for_course_sessions.short_description = "Feedback export"
get_feedbacks_for_courses.short_description = "Feedback export" get_feedbacks_for_courses.short_description = "Feedback export"
@admin.register(CourseConfiguration)
class CourseConfigurationAdmin(admin.ModelAdmin):
list_display = [
"course",
"enable_circle_documents",
"enable_learning_mentor",
"enable_competence_certificates",
]
@admin.register(Course) @admin.register(Course)
class CourseAdmin(admin.ModelAdmin): class CourseAdmin(admin.ModelAdmin):
list_display = [ list_display = [

View File

@ -10,3 +10,20 @@ COURSE_VERSICHERUNGSVERMITTLERIN_FR_ID = -10
COURSE_VERSICHERUNGSVERMITTLERIN_IT_ID = -11 COURSE_VERSICHERUNGSVERMITTLERIN_IT_ID = -11
COURSE_VERSICHERUNGSVERMITTLERIN_PRUEFUNG_ID = -12 COURSE_VERSICHERUNGSVERMITTLERIN_PRUEFUNG_ID = -12
COURSE_MOTORFAHRZEUG_PRUEFUNG_ID = -13 COURSE_MOTORFAHRZEUG_PRUEFUNG_ID = -13
VV_COURSE_IDS = [
COURSE_VERSICHERUNGSVERMITTLERIN_ID,
COURSE_VERSICHERUNGSVERMITTLERIN_FR_ID,
COURSE_VERSICHERUNGSVERMITTLERIN_IT_ID,
COURSE_VERSICHERUNGSVERMITTLERIN_PRUEFUNG_ID,
COURSE_MOTORFAHRZEUG_PRUEFUNG_ID,
]
UK_COURSE_IDS = [
COURSE_UK,
COURSE_UK_FR,
COURSE_UK_IT,
COURSE_UK_TRAINING,
COURSE_UK_TRAINING_FR,
COURSE_UK_TRAINING_IT,
]

View File

@ -104,7 +104,10 @@ def create_test_course(include_uk=True, include_vv=True, with_sessions=False):
if UserImage.objects.count() == 0 and ContentImage.objects.count() == 0: if UserImage.objects.count() == 0 and ContentImage.objects.count() == 0:
create_default_images() create_default_images()
course = create_test_course_with_categories() course: Course = create_test_course_with_categories()
course.configuration.enable_learning_mentor = False
course.configuration.save()
competence_certificate = create_test_competence_navi() competence_certificate = create_test_competence_navi()
# assignments create assignments parent page # assignments create assignments parent page

View File

@ -24,6 +24,7 @@ from vbv_lernwelt.competence.factories import (
) )
from vbv_lernwelt.competence.models import CompetenceCertificate, PerformanceCriteria from vbv_lernwelt.competence.models import CompetenceCertificate, PerformanceCriteria
from vbv_lernwelt.core.models import User from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.consts import COURSE_TEST_ID, UK_COURSE_IDS, VV_COURSE_IDS
from vbv_lernwelt.course.factories import CoursePageFactory from vbv_lernwelt.course.factories import CoursePageFactory
from vbv_lernwelt.course.models import ( from vbv_lernwelt.course.models import (
Course, Course,
@ -47,7 +48,6 @@ from vbv_lernwelt.learnpath.models import (
LearningContentEdoniqTest, LearningContentEdoniqTest,
LearningPath, LearningPath,
LearningUnit, LearningUnit,
LearningUnitPerformanceFeedbackType,
) )
from vbv_lernwelt.learnpath.tests.learning_path_factories import ( from vbv_lernwelt.learnpath.tests.learning_path_factories import (
CircleFactory, CircleFactory,
@ -276,7 +276,6 @@ def create_learning_unit(
circle: Circle, circle: Circle,
course: Course, course: Course,
course_category_title: str = "Course Category", course_category_title: str = "Course Category",
feedback_user: LearningUnitPerformanceFeedbackType = LearningUnitPerformanceFeedbackType.NO_FEEDBACK,
) -> LearningUnit: ) -> LearningUnit:
cat, _ = CourseCategory.objects.get_or_create( cat, _ = CourseCategory.objects.get_or_create(
course=course, course=course,
@ -287,7 +286,6 @@ def create_learning_unit(
title="Learning Unit", title="Learning Unit",
parent=circle, parent=circle,
course_category=cat, course_category=cat,
feedback_user=feedback_user.value,
) )
@ -337,3 +335,68 @@ def create_circle_expert(
course_session_expert_user.expert.add(circle) course_session_expert_user.expert.add(circle)
return course_session_expert_user return course_session_expert_user
def apply_course_configuration(course: Course):
"""
Apply course configuration based on course id:
By default everything is enabled, disable unnecessary features
"""
if course.id == COURSE_TEST_ID:
pass # all features are enabled
elif course.id in VV_COURSE_IDS:
course.configuration.enable_competence_certificates = False
elif course.id in UK_COURSE_IDS:
course.configuration.enable_learning_mentor = False
else:
raise ValueError(f"Unknown course id {course.id}")
course.configuration.save()
def create_course_with_categories(
course_id,
title,
apps=None,
):
if apps is not None:
Course = apps.get_model("course", "Course")
CourseCategory = apps.get_model("course", "CourseCategory")
else:
# pylint: disable=import-outside-toplevel
from vbv_lernwelt.course.models import Course, CourseCategory
course, _ = Course.objects.get_or_create(
id=course_id,
title=title,
category_name="Handlungsfeld",
)
apply_course_configuration(course)
CourseCategory.objects.get_or_create(course=course, title="Allgemein", general=True)
for cat in [
"Fahrzeug",
"Reisen",
"Einkommenssicherung",
"Gesundheit",
"Haushalt",
"Sparen",
"Pensionierung",
"KMU",
"Wohneigentum",
"Rechtsstreitigkeiten",
"Erben / Vererben",
"Selbstständigkeit",
]:
CourseCategory.objects.get_or_create(course=course, title=cat)
course_page = CoursePageFactory(
title=title,
parent=get_wagtail_default_site().root_page,
course=course,
)
course.slug = course_page.slug
course.save()
return course

View File

@ -1,54 +0,0 @@
from vbv_lernwelt.course.consts import COURSE_VERSICHERUNGSVERMITTLERIN_ID
from vbv_lernwelt.course.factories import CoursePageFactory
from vbv_lernwelt.course.models import CircleContactType
from vbv_lernwelt.course.utils import get_wagtail_default_site
def create_versicherungsvermittlerin_with_categories(
apps=None,
schema_editor=None,
course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID,
title="Versicherungsvermittler/-in",
):
if apps is not None:
Course = apps.get_model("course", "Course")
CourseCategory = apps.get_model("course", "CourseCategory")
else:
# pylint: disable=import-outside-toplevel
from vbv_lernwelt.course.models import Course, CourseCategory
course, _ = Course.objects.get_or_create(
id=course_id,
title=title,
category_name="Handlungsfeld",
enable_circle_documents=False,
circle_contact_type=CircleContactType.LEARNING_MENTOR.value,
)
CourseCategory.objects.get_or_create(course=course, title="Allgemein", general=True)
for cat in [
"Fahrzeug",
"Reisen",
"Einkommenssicherung",
"Gesundheit",
"Haushalt",
"Sparen",
"Pensionierung",
"KMU",
"Wohneigentum",
"Rechtsstreitigkeiten",
"Erben / Vererben",
"Selbstständigkeit",
]:
CourseCategory.objects.get_or_create(course=course, title=cat)
course_page = CoursePageFactory(
title=title,
parent=get_wagtail_default_site().root_page,
course=course,
)
course.slug = course_page.slug
course.save()
return course

View File

@ -12,6 +12,7 @@ from vbv_lernwelt.course.models import (
CircleDocument, CircleDocument,
Course, Course,
CourseBasePage, CourseBasePage,
CourseConfiguration,
CoursePage, CoursePage,
CourseSession, CourseSession,
CourseSessionUser, CourseSessionUser,
@ -86,11 +87,23 @@ def resolve_course_page(
raise e raise e
class CourseConfigurationObjectType(DjangoObjectType):
class Meta:
model = CourseConfiguration
fields = (
"id",
"enable_circle_documents",
"enable_learning_mentor",
"enable_competence_certificates",
)
class CourseObjectType(DjangoObjectType): class CourseObjectType(DjangoObjectType):
learning_path = graphene.Field(LearningPathObjectType, required=True) learning_path = graphene.Field(LearningPathObjectType, required=True)
action_competences = graphene.List( action_competences = graphene.List(
graphene.NonNull(ActionCompetenceObjectType), required=True graphene.NonNull(ActionCompetenceObjectType), required=True
) )
configuration = graphene.Field(CourseConfigurationObjectType, required=True)
class Meta: class Meta:
model = Course model = Course
@ -99,8 +112,7 @@ class CourseObjectType(DjangoObjectType):
"title", "title",
"category_name", "category_name",
"slug", "slug",
"enable_circle_documents", "configuration",
"circle_contact_type",
) )
@staticmethod @staticmethod

View File

@ -66,6 +66,7 @@ from vbv_lernwelt.course.creators.test_course import (
create_edoniq_test_assignment, create_edoniq_test_assignment,
create_test_course, create_test_course,
) )
from vbv_lernwelt.course.creators.test_utils import create_course_with_categories
from vbv_lernwelt.course.creators.uk_course import ( from vbv_lernwelt.course.creators.uk_course import (
create_competence_navi, create_competence_navi,
create_uk_fr_learning_path, create_uk_fr_learning_path,
@ -75,9 +76,6 @@ from vbv_lernwelt.course.creators.uk_course import (
from vbv_lernwelt.course.creators.uk_training_course import ( from vbv_lernwelt.course.creators.uk_training_course import (
create_uk_training_learning_path, create_uk_training_learning_path,
) )
from vbv_lernwelt.course.creators.versicherungsvermittlerin import (
create_versicherungsvermittlerin_with_categories,
)
from vbv_lernwelt.course.models import ( from vbv_lernwelt.course.models import (
Course, Course,
CoursePage, CoursePage,
@ -202,7 +200,7 @@ def create_versicherungsvermittlerin_course(
"it": "Intermediario/a assicurativo/a", "it": "Intermediario/a assicurativo/a",
} }
# Versicherungsvermittler/in mit neuen Circles # Versicherungsvermittler/in mit neuen Circles
course = create_versicherungsvermittlerin_with_categories( course = create_course_with_categories(
course_id=course_id, course_id=course_id,
title=names[language], title=names[language],
) )
@ -240,19 +238,20 @@ 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"),
) )
expert1 = CourseSessionUser.objects.create(
CourseSessionUser.objects.create(
course_session=cs, course_session=cs,
user=User.objects.get(username="expert-vv.expert1@eiger-versicherungen.ch"), user=User.objects.get(username="expert-vv.expert1@eiger-versicherungen.ch"),
role=CourseSessionUser.Role.EXPERT, role=CourseSessionUser.Role.EXPERT,
) )
expert2 = CourseSessionUser.objects.create( CourseSessionUser.objects.create(
course_session=cs, course_session=cs,
user=User.objects.get(username="expert-vv.expert2@eiger-versicherungen.ch"), user=User.objects.get(username="expert-vv.expert2@eiger-versicherungen.ch"),
role=CourseSessionUser.Role.EXPERT, role=CourseSessionUser.Role.EXPERT,
) )
expert3 = CourseSessionUser.objects.create( CourseSessionUser.objects.create(
course_session=cs, course_session=cs,
user=User.objects.get(username="expert-vv.expert3@eiger-versicherungen.ch"), user=User.objects.get(username="expert-vv.expert3@eiger-versicherungen.ch"),
role=CourseSessionUser.Role.EXPERT, role=CourseSessionUser.Role.EXPERT,
@ -265,17 +264,6 @@ def create_versicherungsvermittlerin_course(
lemme.participants.add(csu) lemme.participants.add(csu)
experts = [expert1, expert2, expert3]
circles = Circle.objects.filter(
slug__startswith="versicherungsvermittler-in-lp"
)
# for i, circle in enumerate(circles):
# expert = experts[i % len(experts)]
# expert.expert.add(circle)
# create_feedback(circle, cs, 3)
for admin_email in ADMIN_EMAILS: for admin_email in ADMIN_EMAILS:
CourseSessionUser.objects.create( CourseSessionUser.objects.create(
course_session=cs, course_session=cs,
@ -292,7 +280,7 @@ def create_versicherungsvermittlerin_pruefung_course(
"it": "Intermediario/a assicurativo/a AFA Esame", "it": "Intermediario/a assicurativo/a AFA Esame",
} }
# Versicherungsvermittler/in mit neuen Circles # Versicherungsvermittler/in mit neuen Circles
course = create_versicherungsvermittlerin_with_categories( course = create_course_with_categories( # noqa
course_id=course_id, course_id=course_id,
title=names[language], title=names[language],
) )
@ -320,7 +308,7 @@ def create_motorfahrzeug_pruefung_course(
"it": "Veicolo a motore Intermediario/a assicurativo/a AFA Esame", "it": "Veicolo a motore Intermediario/a assicurativo/a AFA Esame",
} }
# Versicherungsvermittler/in mit neuen Circles # Versicherungsvermittler/in mit neuen Circles
course = create_versicherungsvermittlerin_with_categories( course = create_course_with_categories( # noqa
course_id=course_id, course_id=course_id,
title=names[language], title=names[language],
) )
@ -345,9 +333,7 @@ def create_course_uk_de(course_id=COURSE_UK, lang="de"):
"fr": "Cours interentreprises", "fr": "Cours interentreprises",
"it": "Corsi interaziendali", "it": "Corsi interaziendali",
} }
course = create_versicherungsvermittlerin_with_categories( course = create_course_with_categories(course_id=course_id, title=names[lang])
course_id=course_id, title=names[lang]
)
# assignments create assignments parent page # assignments create assignments parent page
_assignment_list_page = AssignmentListPageFactory( _assignment_list_page = AssignmentListPageFactory(
@ -398,7 +384,7 @@ def create_course_uk_de_course_sessions():
tuesday_in_two_weeks = ( tuesday_in_two_weeks = (
datetime.now() + relativedelta(weekday=TU(2)) + relativedelta(weeks=2) datetime.now() + relativedelta(weekday=TU(2)) + relativedelta(weeks=2)
) )
csac.due_date.start = timezone.make_aware( csac.due_date.start = timezone.make_aware( # noqa
tuesday_in_two_weeks.replace(hour=8, minute=30, second=0, microsecond=0) tuesday_in_two_weeks.replace(hour=8, minute=30, second=0, microsecond=0)
) )
csac.due_date.end = timezone.make_aware( csac.due_date.end = timezone.make_aware(
@ -488,25 +474,10 @@ def create_course_uk_de_course_sessions():
user=User.objects.get(username="patrick.muster@eiger-versicherungen.ch"), user=User.objects.get(username="patrick.muster@eiger-versicherungen.ch"),
) )
# TODO: feedback must now contain a `feedback_user`
# create_feedback(
# Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-kickoff"),
# cs,
# 3,
# )
# create_feedback(
# Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-haushalt-teil-2"),
# cs,
# 14,
# )
# create_feedback(
# Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-basis"), cs, 4
# )
def create_course_uk_fr(): def create_course_uk_fr():
# Überbetriebliche Kurse FR # Überbetriebliche Kurse FR
course = create_versicherungsvermittlerin_with_categories( course = create_course_with_categories(
course_id=COURSE_UK_FR, title="Cours interentreprises" course_id=COURSE_UK_FR, title="Cours interentreprises"
) )
@ -556,7 +527,7 @@ def create_course_uk_fr():
def create_course_uk_it(): def create_course_uk_it():
# Überbetriebliche Kurse FR # Überbetriebliche Kurse FR
course = create_versicherungsvermittlerin_with_categories( course = create_course_with_categories(
course_id=COURSE_UK_IT, title="Corso interaziendale" course_id=COURSE_UK_IT, title="Corso interaziendale"
) )
@ -657,7 +628,7 @@ def create_course_uk_de_completion_data(course_session):
def create_course_training_de(): def create_course_training_de():
# Test Lehrgang für üK Trainer # Test Lehrgang für üK Trainer
course = create_versicherungsvermittlerin_with_categories( course = create_course_with_categories(
course_id=COURSE_UK_TRAINING, title="myVBV Training" course_id=COURSE_UK_TRAINING, title="myVBV Training"
) )
@ -708,7 +679,7 @@ def create_course_training_de():
cs = CourseSession.objects.get(course_id=COURSE_UK, title="Demo üK 2023 DE") cs = CourseSession.objects.get(course_id=COURSE_UK, title="Demo üK 2023 DE")
for user in users: for user in users: # noqa
csu, _created = CourseSessionUser.objects.get_or_create( csu, _created = CourseSessionUser.objects.get_or_create(
course_session_id=cs.id, user_id=user.id course_session_id=cs.id, user_id=user.id
) )
@ -747,7 +718,7 @@ def create_course_session_assignments(course_session, assignment_slug, i=1):
def create_course_training_fr(): def create_course_training_fr():
# Test Lehrgang für üK Trainer FR # Test Lehrgang für üK Trainer FR
course = create_versicherungsvermittlerin_with_categories( course = create_course_with_categories(
course_id=COURSE_UK_TRAINING_FR, title="myVBV Training (FR)" course_id=COURSE_UK_TRAINING_FR, title="myVBV Training (FR)"
) )
@ -814,7 +785,7 @@ def create_course_training_fr():
title="Demo ci 2023 FR", title="Demo ci 2023 FR",
) )
for user in users: for user in users: # noqa
csu, _created = CourseSessionUser.objects.get_or_create( csu, _created = CourseSessionUser.objects.get_or_create(
course_session_id=cs.id, user_id=user.id course_session_id=cs.id, user_id=user.id
) )
@ -834,7 +805,7 @@ def create_course_training_fr():
def create_course_training_it(): def create_course_training_it():
# Test Lehrgang für üK Trainer FR # Test Lehrgang für üK Trainer FR
course = create_versicherungsvermittlerin_with_categories( course = create_course_with_categories(
course_id=COURSE_UK_TRAINING_IT, title="myVBV Training (IT)" course_id=COURSE_UK_TRAINING_IT, title="myVBV Training (IT)"
) )
@ -901,7 +872,7 @@ def create_course_training_it():
title="Demo ci 2023 IT", title="Demo ci 2023 IT",
) )
for user in users: for user in users: # noqa
csu, _created = CourseSessionUser.objects.get_or_create( csu, _created = CourseSessionUser.objects.get_or_create(
course_session_id=cs.id, user_id=user.id course_session_id=cs.id, user_id=user.id
) )

View File

@ -0,0 +1,111 @@
import django.db.models.deletion
from django.db import migrations, models
TEST_COURSE_ID = -1
UK_COURSE_IDS = [
-3, # uk-de
-6, # uk-training-de
-5, # uk-fr
-7, # uk-training-fr
-8, # uk-it
-9, # uk-training-it
]
VV_COURSE_IDS = [
-4, # vv-de
-10, # vv-fr
-11, # vv-it
-12, # vv-prüfung
]
def forward_migration(apps, schema_editor):
Course = apps.get_model("course", "Course")
CourseConfiguration = apps.get_model("course", "CourseConfiguration")
for course in Course.objects.all():
config, created = CourseConfiguration.objects.get_or_create(
course=course,
)
if created:
# moved -> use existing value from course
config.enable_circle_documents = course.enable_circle_documents
# by default everything is enabled
# -> disable unnecessary features
if course.id in UK_COURSE_IDS:
config.enable_learning_mentor = False
elif course.id in VV_COURSE_IDS:
config.enable_competence_certificates = False
config.save()
def backward_migration(apps, schema_editor):
CourseConfiguration = apps.get_model("course", "CourseConfiguration")
for config in CourseConfiguration.objects.all():
course = config.course
course.enable_circle_documents = config.enable_circle_documents
course.save()
class Migration(migrations.Migration):
dependencies = [
("course", "0006_auto_20231221_1411"),
]
operations = [
migrations.CreateModel(
name="CourseConfiguration",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"enable_circle_documents",
models.BooleanField(
default=True, verbose_name="Dokumente im Circle ein/aus"
),
),
(
"enable_learning_mentor",
models.BooleanField(
default=True, verbose_name="Lernmentor-Funktion ein/aus"
),
),
(
"enable_competence_certificates",
models.BooleanField(
default=True, verbose_name="Kompetenzweise ein/aus"
),
),
(
"course",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="configuration",
to="course.course",
),
),
],
),
migrations.RunPython(forward_migration, backward_migration),
migrations.RemoveField(
model_name="course",
name="circle_contact_type",
),
migrations.RemoveField(
model_name="course",
name="enable_circle_documents",
),
]

View File

@ -28,14 +28,6 @@ class Course(models.Model):
slug = models.SlugField( slug = models.SlugField(
_("Slug"), max_length=255, unique=True, blank=True, allow_unicode=True _("Slug"), max_length=255, unique=True, blank=True, allow_unicode=True
) )
enable_circle_documents = models.BooleanField(
_("Trainer Dokumente in Circles"), default=True
)
circle_contact_type = models.CharField(
max_length=50,
choices=[(cct.value, cct.value) for cct in CircleContactType],
default=CircleContactType.EXPERT.value,
)
def get_course_url(self): def get_course_url(self):
return f"/course/{self.slug}" return f"/course/{self.slug}"
@ -329,3 +321,24 @@ class CircleDocument(models.Model):
self.file.upload_finished_at = None self.file.upload_finished_at = None
self.file.save() self.file.save()
return super().delete(*args, **kwargs) return super().delete(*args, **kwargs)
class CourseConfiguration(models.Model):
course = models.OneToOneField(
Course, on_delete=models.CASCADE, related_name="configuration"
)
enable_circle_documents = models.BooleanField(
_("Dokumente im Circle ein/aus"), default=True
)
enable_learning_mentor = models.BooleanField(
_("Lernmentor-Funktion ein/aus"), default=True
)
enable_competence_certificates = models.BooleanField(
_("Kompetenzweise ein/aus"), default=True
)
def __str__(self):
return f"Course Configuration for '{self.course.title}'"

View File

@ -7,6 +7,7 @@ from vbv_lernwelt.course.models import (
Course, Course,
CourseCategory, CourseCategory,
CourseCompletion, CourseCompletion,
CourseConfiguration,
CourseSession, CourseSession,
) )
from vbv_lernwelt.duedate.models import DueDate from vbv_lernwelt.duedate.models import DueDate
@ -14,12 +15,27 @@ from vbv_lernwelt.duedate.serializers import DueDateSerializer
from vbv_lernwelt.iam.permissions import course_session_permissions from vbv_lernwelt.iam.permissions import course_session_permissions
class CourseConfigurationSerializer(serializers.ModelSerializer):
class Meta:
model = CourseConfiguration
fields = [
"id",
"course",
"enable_circle_documents",
"enable_learning_mentor",
"enable_competence_certificates",
]
class CourseSerializer(serializers.ModelSerializer): class CourseSerializer(serializers.ModelSerializer):
id = StringIDField() id = StringIDField()
configuration = CourseConfigurationSerializer(
read_only=True,
)
class Meta: class Meta:
model = Course model = Course
fields = ["id", "title", "category_name", "slug", "enable_circle_documents"] fields = ["id", "title", "category_name", "slug", "configuration"]
class CourseCategorySerializer(serializers.ModelSerializer): class CourseCategorySerializer(serializers.ModelSerializer):

View File

@ -0,0 +1,10 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
from vbv_lernwelt.course.models import Course, CourseConfiguration
@receiver(post_save, sender=Course)
def create_course_configuration(sender, instance, created, **kwargs):
if created:
CourseConfiguration.objects.create(course=instance)

View File

@ -66,16 +66,16 @@ class DashboardQuery(graphene.ObjectType):
user = info.context.user user = info.context.user
if user.is_superuser: if user.is_superuser:
courses = Course.objects.all().values("id", "title", "slug")
return [ return [
{ {
"id": c["id"], "id": c.id,
"course_id": c["id"], "course_id": c.id,
"name": c["title"], "name": c.title,
"slug": c["slug"], "slug": c.slug,
"dashboard_type": DashboardType.SIMPLE_DASHBOARD, "dashboard_type": DashboardType.SIMPLE_DASHBOARD,
"course_configuration": c.configuration,
} }
for c in courses for c in Course.objects.all()
] ]
( (
@ -176,6 +176,7 @@ def get_user_statistics_dashboards(user: User) -> Tuple[List[Dict[str, str]], Se
"name": course.title, "name": course.title,
"slug": course.slug, "slug": course.slug,
"dashboard_type": DashboardType.STATISTICS_DASHBOARD, "dashboard_type": DashboardType.STATISTICS_DASHBOARD,
"course_configuration": course.configuration,
} }
) )
@ -194,6 +195,7 @@ def get_learning_mentor_dashboards(user: User) -> List[Dict[str, str]]:
"name": course.title, "name": course.title,
"slug": course.slug, "slug": course.slug,
"dashboard_type": DashboardType.SIMPLE_DASHBOARD, "dashboard_type": DashboardType.SIMPLE_DASHBOARD,
"course_configuration": course.configuration,
} }
) )
@ -241,6 +243,7 @@ def get_user_course_session_dashboards(
"name": course.title, "name": course.title,
"slug": course.slug, "slug": course.slug,
"dashboard_type": resolved_dashboard_type, "dashboard_type": resolved_dashboard_type,
"course_configuration": course.configuration,
} }
) )

View File

@ -1,6 +1,7 @@
import graphene import graphene
from graphene import Enum from graphene import Enum
from vbv_lernwelt.course.graphql.types import CourseConfigurationObjectType
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
from vbv_lernwelt.dashboard.graphql.types.assignment import ( from vbv_lernwelt.dashboard.graphql.types.assignment import (
assignments, assignments,
@ -59,6 +60,7 @@ class DashboardConfigType(graphene.ObjectType):
name = graphene.String(required=True) name = graphene.String(required=True)
slug = graphene.String(required=True) slug = graphene.String(required=True)
dashboard_type = graphene.Field(DashboardType, required=True) dashboard_type = graphene.Field(DashboardType, required=True)
course_configuration = graphene.Field(CourseConfigurationObjectType, required=True)
class ProgressDashboardCompetenceType(graphene.ObjectType): class ProgressDashboardCompetenceType(graphene.ObjectType):

View File

@ -153,6 +153,11 @@ class DashboardTestCase(GraphQLTestCase):
def test_dashboard_config(self): def test_dashboard_config(self):
# GIVEN # GIVEN
course_1, _ = create_course("Test Course 1") course_1, _ = create_course("Test Course 1")
course_1.configuration.enable_learning_mentor = False
course_1.configuration.enable_competence_certificates = False
course_1.configuration.enable_circle_documents = False
course_1.configuration.save()
course_2, _ = create_course("Test Course 2") course_2, _ = create_course("Test Course 2")
course_3, _ = create_course("Test Course 3") course_3, _ = create_course("Test Course 3")
@ -193,6 +198,11 @@ class DashboardTestCase(GraphQLTestCase):
name name
slug slug
dashboard_type dashboard_type
course_configuration {
enable_circle_documents
enable_learning_mentor
enable_competence_certificates
}
} }
} }
""" """
@ -213,6 +223,13 @@ class DashboardTestCase(GraphQLTestCase):
self.assertEqual(course_1_config["slug"], course_1.slug) self.assertEqual(course_1_config["slug"], course_1.slug)
self.assertEqual(course_1_config["dashboard_type"], "PROGRESS_DASHBOARD") self.assertEqual(course_1_config["dashboard_type"], "PROGRESS_DASHBOARD")
course_1_course_configuration = course_1_config["course_configuration"]
self.assertFalse(course_1_course_configuration["enable_circle_documents"])
self.assertFalse(course_1_course_configuration["enable_learning_mentor"])
self.assertFalse(
course_1_course_configuration["enable_competence_certificates"]
)
course_2_config = find_dashboard_config_by_course_id( course_2_config = find_dashboard_config_by_course_id(
dashboard_config, course_2.id dashboard_config, course_2.id
) )
@ -221,6 +238,11 @@ class DashboardTestCase(GraphQLTestCase):
self.assertEqual(course_2_config["slug"], course_2.slug) self.assertEqual(course_2_config["slug"], course_2.slug)
self.assertEqual(course_2_config["dashboard_type"], "STATISTICS_DASHBOARD") self.assertEqual(course_2_config["dashboard_type"], "STATISTICS_DASHBOARD")
course_2_course_configuration = course_2_config["course_configuration"]
self.assertTrue(course_2_course_configuration["enable_circle_documents"])
self.assertTrue(course_2_course_configuration["enable_learning_mentor"])
self.assertTrue(course_2_course_configuration["enable_competence_certificates"])
course_3_config = find_dashboard_config_by_course_id( course_3_config = find_dashboard_config_by_course_id(
dashboard_config, course_3.id dashboard_config, course_3.id
) )
@ -229,6 +251,11 @@ class DashboardTestCase(GraphQLTestCase):
self.assertEqual(course_3_config["slug"], course_3.slug) self.assertEqual(course_3_config["slug"], course_3.slug)
self.assertEqual(course_3_config["dashboard_type"], "SIMPLE_DASHBOARD") self.assertEqual(course_3_config["dashboard_type"], "SIMPLE_DASHBOARD")
course_3_course_configuration = course_3_config["course_configuration"]
self.assertTrue(course_3_course_configuration["enable_circle_documents"])
self.assertTrue(course_3_course_configuration["enable_learning_mentor"])
self.assertTrue(course_3_course_configuration["enable_competence_certificates"])
def test_dashboard_config_mentor(self): def test_dashboard_config_mentor(self):
# GIVEN # GIVEN
course_1, _ = create_course("Test Course 1") course_1, _ = create_course("Test Course 1")

View File

@ -178,6 +178,16 @@ def has_role_in_course(user: User, course: Course) -> bool:
return False return False
def can_view_course(user: User, course: Course) -> bool:
if user.is_superuser:
return True
if has_role_in_course(user, course):
return True
return False
def can_view_profile(user: User, profile_user: CourseSessionUser) -> bool: def can_view_profile(user: User, profile_user: CourseSessionUser) -> bool:
if user.is_superuser: if user.is_superuser:
return True return True

View File

@ -10,10 +10,7 @@ from vbv_lernwelt.learning_mentor.entities import (
MentorAssignmentStatusType, MentorAssignmentStatusType,
MentorCompletionStatus, MentorCompletionStatus,
) )
from vbv_lernwelt.learnpath.models import ( from vbv_lernwelt.learnpath.models import LearningUnit
LearningUnit,
LearningUnitPerformanceFeedbackType,
)
from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback
logger = structlog.get_logger(__name__) logger = structlog.get_logger(__name__)
@ -49,17 +46,16 @@ def get_self_feedback_evaluation(
evaluation_user: User, evaluation_user: User,
course: Course, course: Course,
) -> Tuple[List[MentorAssignmentStatus], Set[int]]: ) -> Tuple[List[MentorAssignmentStatus], Set[int]]:
if not participants or not course.configuration.enable_learning_mentor:
return [], set()
records: List[MentorAssignmentStatus] = [] records: List[MentorAssignmentStatus] = []
circle_ids: Set[int] = set() circle_ids: Set[int] = set()
if not participants:
return records, circle_ids
# very unfortunate: we can't simply get all SelfEvaluationFeedback objects since then # very unfortunate: we can't simply get all SelfEvaluationFeedback objects since then
# we would miss the one where no feedback was requested -> so we get all learning units # we would miss the one where no feedback was requested -> so we get all learning units
# and check if we have to take them into account (course, feedback type, etc.) # and check if we have to take them into account (course, feedback type, etc.)
for learning_unit in LearningUnit.objects.filter( for learning_unit in LearningUnit.objects.filter(
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK.value,
course_category__course_id=course.id, course_category__course_id=course.id,
): ):
feedbacks = SelfEvaluationFeedback.objects.filter( feedbacks = SelfEvaluationFeedback.objects.filter(

View File

@ -23,7 +23,6 @@ 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.learning_mentor.models import LearningMentor from vbv_lernwelt.learning_mentor.models import LearningMentor
from vbv_lernwelt.learnpath.models import LearningUnitPerformanceFeedbackType
from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback
@ -132,13 +131,6 @@ class LearningMentorAPITest(APITestCase):
course=self.course, course=self.course,
) )
# performance criteria under this learning unit shall be evaluated by the mentor
learning_unit.feedback_user = (
LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK.name
)
learning_unit.save()
# 1: we already evaluated # 1: we already evaluated
SelfEvaluationFeedback.objects.create( SelfEvaluationFeedback.objects.create(
feedback_requester_user=self.participant_1.user, feedback_requester_user=self.participant_1.user,

View File

@ -18,7 +18,6 @@ from vbv_lernwelt.course.consts import (
COURSE_VERSICHERUNGSVERMITTLERIN_PRUEFUNG_ID, COURSE_VERSICHERUNGSVERMITTLERIN_PRUEFUNG_ID,
) )
from vbv_lernwelt.course.models import CourseCategory, CoursePage from vbv_lernwelt.course.models import CourseCategory, CoursePage
from vbv_lernwelt.learnpath.models import LearningUnitPerformanceFeedbackType
from vbv_lernwelt.learnpath.tests.learning_path_factories import ( from vbv_lernwelt.learnpath.tests.learning_path_factories import (
CircleFactory, CircleFactory,
LearningContentAssignmentFactory, LearningContentAssignmentFactory,
@ -175,7 +174,6 @@ def create_circle_basis(lp, title="Basis", course_page=None):
LearningSequenceFactory(title="Arbeitsalltag", parent=circle) LearningSequenceFactory(title="Arbeitsalltag", parent=circle)
lu = LearningUnitFactory( lu = LearningUnitFactory(
title="Mein neuer Job, Arbeitstechnik, Soziale Medien, Datenschutz und Beratungspflichten", title="Mein neuer Job, Arbeitstechnik, Soziale Medien, Datenschutz und Beratungspflichten",
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK.name,
parent=circle, parent=circle,
) )
@ -432,7 +430,6 @@ def create_circle_fahrzeug(lp, title="Fahrzeug", course_page=None):
title="Transfer", title="Transfer",
title_hidden=True, title_hidden=True,
parent=circle, parent=circle,
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK.name,
) )
LearningContentPlaceholderFactory( LearningContentPlaceholderFactory(

View File

@ -234,7 +234,7 @@ class LearningUnitObjectType(DjangoObjectType):
class Meta: class Meta:
model = LearningUnit model = LearningUnit
interfaces = (CoursePageInterface,) interfaces = (CoursePageInterface,)
fields = ["evaluate_url", "title_hidden", "feedback_user"] fields = ["evaluate_url", "title_hidden"]
def resolve_evaluate_url(self: LearningUnit, info, **kwargs): def resolve_evaluate_url(self: LearningUnit, info, **kwargs):
return self.get_evaluate_url() return self.get_evaluate_url()

View File

@ -0,0 +1,16 @@
# Generated by Django 3.2.20 on 2024-02-29 10:22
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("learnpath", "0015_set_feedback_user_mentor_for_vv"),
]
operations = [
migrations.RemoveField(
model_name="learningunit",
name="feedback_user",
),
]

View File

@ -1,10 +1,9 @@
import re import re
from enum import Enum
from typing import Tuple from typing import Tuple
from django.db import models from django.db import models
from django.utils.text import slugify from django.utils.text import slugify
from wagtail.admin.panels import FieldPanel, HelpPanel, PageChooserPanel from wagtail.admin.panels import FieldPanel, PageChooserPanel
from wagtail.fields import RichTextField, StreamField from wagtail.fields import RichTextField, StreamField
from wagtail.models import Page from wagtail.models import Page
@ -119,13 +118,6 @@ class Circle(CourseBasePage):
return f"{self.title}" return f"{self.title}"
class LearningUnitPerformanceFeedbackType(Enum):
"""Defines how feedback on the performance criteria (n) of a learning unit are given."""
NO_FEEDBACK = "NO_FEEDBACK"
MENTOR_FEEDBACK = "MENTOR_FEEDBACK"
class LearningSequence(CourseBasePage): class LearningSequence(CourseBasePage):
serialize_field_names = ["icon"] serialize_field_names = ["icon"]
@ -178,21 +170,10 @@ class LearningUnit(CourseBasePage):
"course.CourseCategory", on_delete=models.SET_NULL, null=True, blank=True "course.CourseCategory", on_delete=models.SET_NULL, null=True, blank=True
) )
title_hidden = models.BooleanField(default=False) title_hidden = models.BooleanField(default=False)
feedback_user = models.CharField(
max_length=255,
choices=[(tag.name, tag.name) for tag in LearningUnitPerformanceFeedbackType],
default=LearningUnitPerformanceFeedbackType.NO_FEEDBACK.name,
)
content_panels = Page.content_panels + [ content_panels = Page.content_panels + [
FieldPanel("course_category"), FieldPanel("course_category"),
FieldPanel("title_hidden"), FieldPanel("title_hidden"),
FieldPanel("feedback_user"),
HelpPanel(
content="👆 Feedback zur Selbsteinschätzung: Normalerweise <code>NO_FEEDBACK</code>, "
"ausser bei den Lerninhalten Selbsteinschätzungen, die eine Bewertung haben von einer "
"Lernbegleitung haben sollen (z.B. VV)."
),
] ]
class Meta: class Meta:

View File

@ -20,7 +20,6 @@ class LearningUnitSerializer(
"course_category", "course_category",
"children", "children",
"title_hidden", "title_hidden",
"feedback_user",
], ],
) )
): ):

View File

@ -0,0 +1,39 @@
# Generated by Django 3.2.20 on 2024-02-26 15:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("notify", "0005_alter_notification_notification_trigger"),
]
operations = [
migrations.AlterField(
model_name="notification",
name="notification_trigger",
field=models.CharField(
choices=[
("ATTENDANCE_COURSE_REMINDER", "Attendance Course Reminder"),
("ASSIGNMENT_REMINDER", "Assignment Reminder"),
(
"CASEWORK_EXPERT_EVALUATION_REMINDER",
"Casework Expert Evaluation Reminder",
),
("CASEWORK_SUBMITTED", "Casework Submitted"),
("CASEWORK_EVALUATED", "Casework Evaluated"),
("NEW_FEEDBACK", "New Feedback"),
(
"SELF_EVALUATION_FEEDBACK_REQUESTED",
"Self Evaluation Feedback Requested",
),
(
"SELF_EVALUATION_FEEDBACK_PROVIDED",
"Self Evaluation Feedback Provided",
),
],
default="",
max_length=255,
),
),
]

View File

@ -15,7 +15,6 @@ 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 LearningMentor
from vbv_lernwelt.learnpath.models import LearningUnitPerformanceFeedbackType
from vbv_lernwelt.self_evaluation_feedback.models import ( from vbv_lernwelt.self_evaluation_feedback.models import (
CourseCompletionFeedback, CourseCompletionFeedback,
SelfEvaluationFeedback, SelfEvaluationFeedback,
@ -253,7 +252,6 @@ class SelfEvaluationFeedbackAPI(APITestCase):
learning_unit = create_learning_unit( learning_unit = create_learning_unit(
course=self.course, course=self.course,
circle=self.circle, circle=self.circle,
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK,
) )
feedback = create_self_evaluation_feedback( feedback = create_self_evaluation_feedback(
@ -337,7 +335,6 @@ class SelfEvaluationFeedbackAPI(APITestCase):
learning_unit_with_success_feedback = create_learning_unit( learning_unit_with_success_feedback = create_learning_unit(
course=self.course, course=self.course,
circle=self.circle, circle=self.circle,
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK,
) )
performance_criteria_page = create_performance_criteria_page( performance_criteria_page = create_performance_criteria_page(
@ -382,7 +379,6 @@ class SelfEvaluationFeedbackAPI(APITestCase):
learning_unit = create_learning_unit( # noqa learning_unit = create_learning_unit( # noqa
course=self.course, course=self.course,
circle=self.circle, circle=self.circle,
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK,
) )
create_performance_criteria_page( create_performance_criteria_page(
@ -415,7 +411,6 @@ class SelfEvaluationFeedbackAPI(APITestCase):
learning_unit = create_learning_unit( # noqa learning_unit = create_learning_unit( # noqa
course=self.course, course=self.course,
circle=self.circle, circle=self.circle,
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK,
) )
create_performance_criteria_page( create_performance_criteria_page(

View File

@ -9,11 +9,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.learning_mentor.models import LearningMentor from vbv_lernwelt.learning_mentor.models import LearningMentor
from vbv_lernwelt.learnpath.models import ( from vbv_lernwelt.learnpath.models import Circle, LearningUnit
Circle,
LearningUnit,
LearningUnitPerformanceFeedbackType,
)
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 (
CourseCompletionFeedback, CourseCompletionFeedback,
@ -181,15 +177,6 @@ def get_self_evaluation_feedbacks_as_requester(request, course_session_id: int):
+ received_feedback_counts_aggregate.unknown_count, + received_feedback_counts_aggregate.unknown_count,
) )
# check if there are any learning units with mentor feedback
feedback_assessment_visible = (
LearningUnit.objects.filter(
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK.value,
course_category__course=course_session.course,
).count()
> 0
)
return Response( return Response(
{ {
"results": results, "results": results,
@ -197,7 +184,6 @@ def get_self_evaluation_feedbacks_as_requester(request, course_session_id: int):
Circle.objects.filter(id__in=circle_ids).values("id", "title") Circle.objects.filter(id__in=circle_ids).values("id", "title")
), ),
"aggregates": { "aggregates": {
"feedback_assessment_visible": feedback_assessment_visible,
"feedback_assessment": { "feedback_assessment": {
"pass": feedback_assessment_counts_aggregate.pass_count, "pass": feedback_assessment_counts_aggregate.pass_count,
"fail": feedback_assessment_counts_aggregate.fail_count, "fail": feedback_assessment_counts_aggregate.fail_count,