From 780be68a58c9b3234129a430750095d55345ab81 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Wed, 17 Jan 2024 14:18:34 +0100 Subject: [PATCH 01/57] feat: adds feedback_user to LearningUnit model --- server/vbv_lernwelt/course/models.py | 22 ++++++++++++++++++ .../migrations/0013_auto_20240117_1400.py | 23 +++++++++++++++++++ server/vbv_lernwelt/learnpath/models.py | 22 +++++++++++++++++- 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 server/vbv_lernwelt/learnpath/migrations/0013_auto_20240117_1400.py diff --git a/server/vbv_lernwelt/course/models.py b/server/vbv_lernwelt/course/models.py index cbd52a55..a6c8fd80 100644 --- a/server/vbv_lernwelt/course/models.py +++ b/server/vbv_lernwelt/course/models.py @@ -329,3 +329,25 @@ class CircleDocument(models.Model): self.file.upload_finished_at = None self.file.save() return super().delete(*args, **kwargs) + + +# TODO: Model something like this: +# class LearningUnitCompletionFeedback(models.Model): +# assignment_user = models.ForeignKey(User, on_delete=models.CASCADE) +# feedback_user = models.ForeignKey(User, on_delete=models.CASCADE) +# feedback_submitted = models.BooleanField(default=False) +# learning_unit = models.ForeignKey( +# "learnpath.LearningUnit", on_delete=models.CASCADE +# ) +# +# +# class CourseCompletionFeedback(models.Model): +# learning_unit_completion_feedback = models.ForeignKey( +# LearningUnitCompletionFeedback, on_delete=models.CASCADE +# ) +# course_completion = models.ForeignKey(CourseCompletion, on_delete=models.CASCADE) +# feedback_status = models.CharField( +# max_length=255, +# choices=[(status, status.value) for status in CourseCompletionStatus], +# default=CourseCompletionStatus.UNKNOWN.value, +# ) diff --git a/server/vbv_lernwelt/learnpath/migrations/0013_auto_20240117_1400.py b/server/vbv_lernwelt/learnpath/migrations/0013_auto_20240117_1400.py new file mode 100644 index 00000000..3b78bfbc --- /dev/null +++ b/server/vbv_lernwelt/learnpath/migrations/0013_auto_20240117_1400.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.20 on 2024-01-17 13:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('learnpath', '0012_auto_20231129_0827'), + ] + + operations = [ + migrations.AddField( + model_name='learningunit', + name='feedback_user', + field=models.CharField(blank=True, choices=[('NO_FEEDBACK', 'NO_FEEDBACK'), ('MENTOR_FEEDBACK', 'MENTOR_FEEDBACK')], default='NO_FEEDBACK', max_length=255), + ), + migrations.AlterField( + model_name='learningcontentassignment', + name='assignment_type', + field=models.CharField(choices=[('PRAXIS_ASSIGNMENT', 'PRAXIS_ASSIGNMENT'), ('CASEWORK', 'CASEWORK'), ('PREP_ASSIGNMENT', 'PREP_ASSIGNMENT'), ('REFLECTION', 'REFLECTION'), ('CONDITION_ACCEPTANCE', 'CONDITION_ACCEPTANCE'), ('EDONIQ_TEST', 'EDONIQ_TEST')], default='CASEWORK', max_length=50), + ), + ] diff --git a/server/vbv_lernwelt/learnpath/models.py b/server/vbv_lernwelt/learnpath/models.py index 64d260cc..983f4dd7 100644 --- a/server/vbv_lernwelt/learnpath/models.py +++ b/server/vbv_lernwelt/learnpath/models.py @@ -1,8 +1,9 @@ import re +from enum import Enum from django.db import models from django.utils.text import slugify -from wagtail.admin.panels import FieldPanel, PageChooserPanel +from wagtail.admin.panels import FieldPanel, PageChooserPanel, HelpPanel from wagtail.fields import RichTextField, StreamField from wagtail.models import Page @@ -117,6 +118,13 @@ class Circle(CourseBasePage): 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): serialize_field_names = ["icon"] @@ -169,10 +177,22 @@ class LearningUnit(CourseBasePage): "course.CourseCategory", on_delete=models.SET_NULL, null=True, blank=True ) title_hidden = models.BooleanField(default=False) + feedback_user = models.CharField( + max_length=255, + blank=True, + choices=[(tag.name, tag.name) for tag in LearningUnitPerformanceFeedbackType], + default=LearningUnitPerformanceFeedbackType.NO_FEEDBACK.name, + ) content_panels = Page.content_panels + [ FieldPanel("course_category"), FieldPanel("title_hidden"), + FieldPanel("feedback_user"), + HelpPanel( + content="👆 Feedback zur Selbsteinschätzung: Normalerweise NO_FEEDBACK, " + "ausser bei den Lerninhalten Selbsteinschätzungen, die eine Bewertung haben von einer " + "Lernbegleitung haben sollen (z.B. VV)." + ), ] class Meta: From 756a0740e09c5f0a065a526bc302bb91f6c951e4 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Wed, 17 Jan 2024 15:08:48 +0100 Subject: [PATCH 02/57] chore: adds one self assessment competence to vv --- .../learnpath/create_vv_new_learning_path.py | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py b/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py index 915b2239..8c2f2891 100644 --- a/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py +++ b/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py @@ -5,7 +5,11 @@ from wagtail.rich_text import RichText from wagtail_localize.models import LocaleSynchronization from vbv_lernwelt.assignment.models import Assignment -from vbv_lernwelt.competence.factories import PerformanceCriteriaFactory +from vbv_lernwelt.competence.factories import ( + PerformanceCriteriaFactory, + ActionCompetenceFactory, + ActionCompetenceListPageFactory, +) from vbv_lernwelt.competence.models import ActionCompetence from vbv_lernwelt.core.admin import User from vbv_lernwelt.course.consts import COURSE_VERSICHERUNGSVERMITTLERIN_ID @@ -43,7 +47,7 @@ def create_vv_new_learning_path( ) TopicFactory(title="Basis", is_visible=False, parent=lp) - create_circle_basis(lp) + create_circle_basis(lp, course_page=course_page) TopicFactory(title="Gewinnen von Kunden", parent=lp) create_circle_gewinnen(lp) @@ -83,7 +87,7 @@ def create_vv_new_learning_path( Page.objects.update(owner=user) -def create_circle_basis(lp, title="Basis"): +def create_circle_basis(lp, title="Basis", course_page=None): circle = CircleFactory( title=title, parent=lp, @@ -125,10 +129,41 @@ def create_circle_basis(lp, title="Basis"): ) LearningSequenceFactory(title="Arbeitsalltag", parent=circle) - LearningUnitFactory( + lu = LearningUnitFactory( title="Mein neuer Job, Arbeitstechnik, Soziale Medien, Datenschutz und Beratungspflichten", parent=circle, ) + + competence_profile_page = ActionCompetenceListPageFactory( + title="KompetenzNavi", + parent=course_page, + ) + + ace = ActionCompetenceFactory( + parent=competence_profile_page, + ) + + PerformanceCriteriaFactory( + parent=ace, + competence_id="VV-Arbeitsalltag-A", + title="Ich kenne die wichtigsten Aspekte des Arbeitsalltags als Versicherungsvermittler/-in.", + learning_unit=lu, + ) + + PerformanceCriteriaFactory( + parent=ace, + competence_id="VV-Arbeitsalltag-B", + title="Ich identifiziere und analysiere neue Markttrends im Versicherungssektor.", + learning_unit=lu, + ) + + PerformanceCriteriaFactory( + parent=ace, + competence_id="VV-Arbeitsalltag-C", + title="Ich nutze digitale Tools zur Optimierung der Kundenbetreuung und -beratung im Versicherungswesen.", + learning_unit=lu, + ) + LearningContentPlaceholderFactory( title="Mediathek", parent=circle, From 359d4482a4cf3b528819e9283ee46a16fc76144e Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Wed, 17 Jan 2024 16:40:41 +0100 Subject: [PATCH 03/57] chore: let frontend know if feedback is needed --- client/src/gql/gql.ts | 4 ++-- client/src/gql/graphql.ts | 12 ++++++++++-- client/src/gql/schema.graphql | 10 ++++++++++ client/src/gql/typenames.ts | 1 + client/src/graphql/queries.ts | 1 + .../selfEvaluationPage/SelfEvaluationPage.vue | 8 +++++--- .../learnpath/create_vv_new_learning_path.py | 2 ++ server/vbv_lernwelt/learnpath/graphql/types.py | 2 +- .../0014_alter_learningunit_feedback_user.py | 18 ++++++++++++++++++ server/vbv_lernwelt/learnpath/models.py | 1 - server/vbv_lernwelt/learnpath/serializers.py | 1 + 11 files changed, 51 insertions(+), 9 deletions(-) create mode 100644 server/vbv_lernwelt/learnpath/migrations/0014_alter_learningunit_feedback_user.py diff --git a/client/src/gql/gql.ts b/client/src/gql/gql.ts index 2fbc4386..fe3a0f75 100644 --- a/client/src/gql/gql.ts +++ b/client/src/gql/gql.ts @@ -20,7 +20,7 @@ const 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": 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, + "\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 dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\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 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, @@ -72,7 +72,7 @@ export function graphql(source: "\n query courseSessionDetail($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 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"): (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 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 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"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/client/src/gql/graphql.ts b/client/src/gql/graphql.ts index f2523e64..b4951e76 100644 --- a/client/src/gql/graphql.ts +++ b/client/src/gql/graphql.ts @@ -789,6 +789,7 @@ export type LearningUnitObjectType = CoursePageInterface & { content_type: Scalars['String']['output']; course?: Maybe; evaluate_url: Scalars['String']['output']; + feedback_user: LearnpathLearningUnitFeedbackUserChoices; frontend_url: Scalars['String']['output']; id: Scalars['ID']['output']; learning_contents: Array; @@ -815,6 +816,13 @@ export type LearnpathLearningContentAssignmentAssignmentTypeChoices = /** REFLECTION */ | 'REFLECTION'; +/** An enumeration. */ +export type LearnpathLearningUnitFeedbackUserChoices = + /** MENTOR_FEEDBACK */ + | 'MENTOR_FEEDBACK' + /** NO_FEEDBACK */ + | 'NO_FEEDBACK'; + export type Mutation = { __typename?: 'Mutation'; send_feedback?: Maybe; @@ -1209,7 +1217,7 @@ export type CourseQueryQuery = { __typename?: 'Query', course?: { __typename?: ' { __typename?: 'TopicObjectType', is_visible: boolean, circles: Array<( { __typename?: 'CircleObjectType', description: string, goals: string, learning_sequences: Array<( { __typename?: 'LearningSequenceObjectType', icon: string, learning_units: Array<( - { __typename?: 'LearningUnitObjectType', evaluate_url: string, performance_criteria: Array<( + { __typename?: 'LearningUnitObjectType', feedback_user: LearnpathLearningUnitFeedbackUserChoices, evaluate_url: string, performance_criteria: Array<( { __typename?: 'PerformanceCriteriaObjectType' } & { ' $fragmentRefs'?: { 'CoursePageFieldsPerformanceCriteriaObjectTypeFragment': CoursePageFieldsPerformanceCriteriaObjectTypeFragment } } )>, learning_contents: Array<( @@ -1303,7 +1311,7 @@ export const AttendanceCheckQueryDocument = {"kind":"Document","definitions":[{" export const AssignmentCompletionQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"assignmentCompletionQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"assignmentId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"courseSessionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"learningContentId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"assignmentUserId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"assignment"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"assignmentId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"assignment_type"}},{"kind":"Field","name":{"kind":"Name","value":"needs_expert_evaluation"}},{"kind":"Field","name":{"kind":"Name","value":"max_points"}},{"kind":"Field","name":{"kind":"Name","value":"content_type"}},{"kind":"Field","name":{"kind":"Name","value":"effort_required"}},{"kind":"Field","name":{"kind":"Name","value":"evaluation_description"}},{"kind":"Field","name":{"kind":"Name","value":"evaluation_document_url"}},{"kind":"Field","name":{"kind":"Name","value":"evaluation_tasks"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"intro_text"}},{"kind":"Field","name":{"kind":"Name","value":"performance_objectives"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"tasks"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"translation_key"}},{"kind":"Field","name":{"kind":"Name","value":"solution_sample"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}},{"kind":"Field","name":{"kind":"Name","value":"competence_certificate"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"assignment_completion"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"assignment_id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"assignmentId"}}},{"kind":"Argument","name":{"kind":"Name","value":"course_session_id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"courseSessionId"}}},{"kind":"Argument","name":{"kind":"Name","value":"assignment_user_id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"assignmentUserId"}}},{"kind":"Argument","name":{"kind":"Name","value":"learning_content_page_id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"learningContentId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"completion_status"}},{"kind":"Field","name":{"kind":"Name","value":"submitted_at"}},{"kind":"Field","name":{"kind":"Name","value":"evaluation_submitted_at"}},{"kind":"Field","name":{"kind":"Name","value":"evaluation_user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"assignment_user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"evaluation_points"}},{"kind":"Field","name":{"kind":"Name","value":"evaluation_max_points"}},{"kind":"Field","name":{"kind":"Name","value":"evaluation_passed"}},{"kind":"Field","name":{"kind":"Name","value":"edoniq_extended_time_flag"}},{"kind":"Field","name":{"kind":"Name","value":"completion_data"}},{"kind":"Field","name":{"kind":"Name","value":"task_completion_data"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CoursePageFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CoursePageInterface"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"content_type"}},{"kind":"Field","name":{"kind":"Name","value":"frontend_url"}}]}}]} as unknown as DocumentNode; export const CompetenceCertificateQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"competenceCertificateQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"courseSlug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"courseSessionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"competence_certificate_list"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"course_slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"courseSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}},{"kind":"Field","name":{"kind":"Name","value":"competence_certificates"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}},{"kind":"Field","name":{"kind":"Name","value":"assignments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}},{"kind":"Field","name":{"kind":"Name","value":"assignment_type"}},{"kind":"Field","name":{"kind":"Name","value":"max_points"}},{"kind":"Field","name":{"kind":"Name","value":"completion"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"course_session_id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"courseSessionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"completion_status"}},{"kind":"Field","name":{"kind":"Name","value":"submitted_at"}},{"kind":"Field","name":{"kind":"Name","value":"evaluation_points"}},{"kind":"Field","name":{"kind":"Name","value":"evaluation_max_points"}},{"kind":"Field","name":{"kind":"Name","value":"evaluation_passed"}}]}},{"kind":"Field","name":{"kind":"Name","value":"learning_content"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}},{"kind":"Field","name":{"kind":"Name","value":"circle"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}}]}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CoursePageFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CoursePageInterface"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"content_type"}},{"kind":"Field","name":{"kind":"Name","value":"frontend_url"}}]}}]} as unknown as DocumentNode; export const CourseSessionDetailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"courseSessionDetail"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"courseSessionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"course_session"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"courseSessionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"course"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"enable_circle_documents"}},{"kind":"Field","name":{"kind":"Name","value":"circle_contact_type"}}]}},{"kind":"Field","name":{"kind":"Name","value":"users"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"user_id"}},{"kind":"Field","name":{"kind":"Name","value":"first_name"}},{"kind":"Field","name":{"kind":"Name","value":"last_name"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"avatar_url"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"circles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"attendance_courses"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"trainer"}},{"kind":"Field","name":{"kind":"Name","value":"due_date"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"end"}}]}},{"kind":"Field","name":{"kind":"Name","value":"learning_content_id"}},{"kind":"Field","name":{"kind":"Name","value":"learning_content"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"circle"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"assignments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"submission_deadline"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"start"}}]}},{"kind":"Field","name":{"kind":"Name","value":"evaluation_deadline"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"start"}}]}},{"kind":"Field","name":{"kind":"Name","value":"learning_content"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"content_assignment"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"assignment_type"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"edoniq_tests"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"deadline"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"end"}}]}},{"kind":"Field","name":{"kind":"Name","value":"learning_content"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"content_assignment"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"assignment_type"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; -export const CourseQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"courseQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"slug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"course"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"slug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"category_name"}},{"kind":"Field","name":{"kind":"Name","value":"enable_circle_documents"}},{"kind":"Field","name":{"kind":"Name","value":"circle_contact_type"}},{"kind":"Field","name":{"kind":"Name","value":"action_competences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"competence_id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}},{"kind":"Field","name":{"kind":"Name","value":"performance_criteria"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"competence_id"}},{"kind":"Field","name":{"kind":"Name","value":"learning_unit"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"evaluate_url"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"learning_path"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}},{"kind":"Field","name":{"kind":"Name","value":"topics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"is_visible"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}},{"kind":"Field","name":{"kind":"Name","value":"circles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"goals"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}},{"kind":"Field","name":{"kind":"Name","value":"learning_sequences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}},{"kind":"Field","name":{"kind":"Name","value":"learning_units"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"evaluate_url"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}},{"kind":"Field","name":{"kind":"Name","value":"performance_criteria"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}}]}},{"kind":"Field","name":{"kind":"Name","value":"learning_contents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"can_user_self_toggle_course_completion"}},{"kind":"Field","name":{"kind":"Name","value":"content_url"}},{"kind":"Field","name":{"kind":"Name","value":"minutes"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LearningContentAssignmentObjectType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"assignment_type"}},{"kind":"Field","name":{"kind":"Name","value":"content_assignment"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"assignment_type"}}]}},{"kind":"Field","name":{"kind":"Name","value":"competence_certificate"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LearningContentEdoniqTestObjectType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"checkbox_text"}},{"kind":"Field","name":{"kind":"Name","value":"has_extended_time_test"}},{"kind":"Field","name":{"kind":"Name","value":"content_assignment"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"assignment_type"}}]}},{"kind":"Field","name":{"kind":"Name","value":"competence_certificate"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LearningContentRichTextObjectType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"text"}}]}}]}}]}}]}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CoursePageFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CoursePageInterface"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"content_type"}},{"kind":"Field","name":{"kind":"Name","value":"frontend_url"}}]}}]} as unknown as DocumentNode; +export const CourseQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"courseQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"slug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"course"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"slug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"category_name"}},{"kind":"Field","name":{"kind":"Name","value":"enable_circle_documents"}},{"kind":"Field","name":{"kind":"Name","value":"circle_contact_type"}},{"kind":"Field","name":{"kind":"Name","value":"action_competences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"competence_id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}},{"kind":"Field","name":{"kind":"Name","value":"performance_criteria"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"competence_id"}},{"kind":"Field","name":{"kind":"Name","value":"learning_unit"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"evaluate_url"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"learning_path"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}},{"kind":"Field","name":{"kind":"Name","value":"topics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"is_visible"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}},{"kind":"Field","name":{"kind":"Name","value":"circles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"goals"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}},{"kind":"Field","name":{"kind":"Name","value":"learning_sequences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}},{"kind":"Field","name":{"kind":"Name","value":"learning_units"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"feedback_user"}},{"kind":"Field","name":{"kind":"Name","value":"evaluate_url"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}},{"kind":"Field","name":{"kind":"Name","value":"performance_criteria"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}}]}},{"kind":"Field","name":{"kind":"Name","value":"learning_contents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"can_user_self_toggle_course_completion"}},{"kind":"Field","name":{"kind":"Name","value":"content_url"}},{"kind":"Field","name":{"kind":"Name","value":"minutes"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LearningContentAssignmentObjectType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"assignment_type"}},{"kind":"Field","name":{"kind":"Name","value":"content_assignment"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"assignment_type"}}]}},{"kind":"Field","name":{"kind":"Name","value":"competence_certificate"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LearningContentEdoniqTestObjectType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"checkbox_text"}},{"kind":"Field","name":{"kind":"Name","value":"has_extended_time_test"}},{"kind":"Field","name":{"kind":"Name","value":"content_assignment"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"assignment_type"}}]}},{"kind":"Field","name":{"kind":"Name","value":"competence_certificate"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LearningContentRichTextObjectType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"text"}}]}}]}}]}}]}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CoursePageFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CoursePageInterface"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"content_type"}},{"kind":"Field","name":{"kind":"Name","value":"frontend_url"}}]}}]} as unknown as DocumentNode; export const DashboardConfigDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"dashboardConfig"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"dashboard_config"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"dashboard_type"}}]}}]}}]} as unknown as DocumentNode; export const DashboardProgressDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"dashboardProgress"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"courseId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"course_progress"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"course_id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"courseId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"course_id"}},{"kind":"Field","name":{"kind":"Name","value":"session_to_continue_id"}},{"kind":"Field","name":{"kind":"Name","value":"competence"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"total_count"}},{"kind":"Field","name":{"kind":"Name","value":"success_count"}},{"kind":"Field","name":{"kind":"Name","value":"fail_count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"assignment"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"total_count"}},{"kind":"Field","name":{"kind":"Name","value":"points_max_count"}},{"kind":"Field","name":{"kind":"Name","value":"points_achieved_count"}}]}}]}}]}}]} as unknown as DocumentNode; export const CourseStatisticsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"courseStatistics"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"courseId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"course_statistics"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"course_id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"courseId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"course_id"}},{"kind":"Field","name":{"kind":"Name","value":"course_title"}},{"kind":"Field","name":{"kind":"Name","value":"course_slug"}},{"kind":"Field","name":{"kind":"Name","value":"course_session_properties"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"sessions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"generations"}},{"kind":"Field","name":{"kind":"Name","value":"circles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"course_session_selection_ids"}},{"kind":"Field","name":{"kind":"Name","value":"course_session_selection_metrics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"session_count"}},{"kind":"Field","name":{"kind":"Name","value":"participant_count"}},{"kind":"Field","name":{"kind":"Name","value":"expert_count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"attendance_day_presences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"records"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"course_session_id"}},{"kind":"Field","name":{"kind":"Name","value":"generation"}},{"kind":"Field","name":{"kind":"Name","value":"circle_id"}},{"kind":"Field","name":{"kind":"Name","value":"due_date"}},{"kind":"Field","name":{"kind":"Name","value":"participants_present"}},{"kind":"Field","name":{"kind":"Name","value":"participants_total"}},{"kind":"Field","name":{"kind":"Name","value":"details_url"}}]}},{"kind":"Field","name":{"kind":"Name","value":"summary"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"days_completed"}},{"kind":"Field","name":{"kind":"Name","value":"participants_present"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"feedback_responses"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"records"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"course_session_id"}},{"kind":"Field","name":{"kind":"Name","value":"generation"}},{"kind":"Field","name":{"kind":"Name","value":"circle_id"}},{"kind":"Field","name":{"kind":"Name","value":"experts"}},{"kind":"Field","name":{"kind":"Name","value":"satisfaction_average"}},{"kind":"Field","name":{"kind":"Name","value":"satisfaction_max"}},{"kind":"Field","name":{"kind":"Name","value":"details_url"}}]}},{"kind":"Field","name":{"kind":"Name","value":"summary"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"satisfaction_average"}},{"kind":"Field","name":{"kind":"Name","value":"satisfaction_max"}},{"kind":"Field","name":{"kind":"Name","value":"total_responses"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"assignments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"summary"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"completed_count"}},{"kind":"Field","name":{"kind":"Name","value":"average_passed"}}]}},{"kind":"Field","name":{"kind":"Name","value":"records"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"course_session_id"}},{"kind":"Field","name":{"kind":"Name","value":"course_session_assignment_id"}},{"kind":"Field","name":{"kind":"Name","value":"circle_id"}},{"kind":"Field","name":{"kind":"Name","value":"generation"}},{"kind":"Field","name":{"kind":"Name","value":"assignment_title"}},{"kind":"Field","name":{"kind":"Name","value":"assignment_type_translation_key"}},{"kind":"Field","name":{"kind":"Name","value":"details_url"}},{"kind":"Field","name":{"kind":"Name","value":"deadline"}},{"kind":"Field","name":{"kind":"Name","value":"metrics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"passed_count"}},{"kind":"Field","name":{"kind":"Name","value":"failed_count"}},{"kind":"Field","name":{"kind":"Name","value":"unranked_count"}},{"kind":"Field","name":{"kind":"Name","value":"ranking_completed"}},{"kind":"Field","name":{"kind":"Name","value":"average_passed"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"competences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"summary"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"success_total"}},{"kind":"Field","name":{"kind":"Name","value":"fail_total"}}]}},{"kind":"Field","name":{"kind":"Name","value":"records"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"course_session_id"}},{"kind":"Field","name":{"kind":"Name","value":"generation"}},{"kind":"Field","name":{"kind":"Name","value":"circle_id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"success_count"}},{"kind":"Field","name":{"kind":"Name","value":"fail_count"}},{"kind":"Field","name":{"kind":"Name","value":"details_url"}}]}}]}}]}}]}}]} as unknown as DocumentNode; diff --git a/client/src/gql/schema.graphql b/client/src/gql/schema.graphql index 01a4a5be..989ed785 100644 --- a/client/src/gql/schema.graphql +++ b/client/src/gql/schema.graphql @@ -279,6 +279,7 @@ type PerformanceCriteriaObjectType implements CoursePageInterface { type LearningUnitObjectType implements CoursePageInterface { title_hidden: Boolean! + feedback_user: LearnpathLearningUnitFeedbackUserChoices! id: ID! title: String! slug: String! @@ -292,6 +293,15 @@ type LearningUnitObjectType implements CoursePageInterface { evaluate_url: String! } +"""An enumeration.""" +enum LearnpathLearningUnitFeedbackUserChoices { + """NO_FEEDBACK""" + NO_FEEDBACK + + """MENTOR_FEEDBACK""" + MENTOR_FEEDBACK +} + interface LearningContentInterface { id: ID! title: String! diff --git a/client/src/gql/typenames.ts b/client/src/gql/typenames.ts index 7c337ae9..2f2bcc94 100644 --- a/client/src/gql/typenames.ts +++ b/client/src/gql/typenames.ts @@ -69,6 +69,7 @@ export const LearningPathObjectType = "LearningPathObjectType"; export const LearningSequenceObjectType = "LearningSequenceObjectType"; export const LearningUnitObjectType = "LearningUnitObjectType"; export const LearnpathLearningContentAssignmentAssignmentTypeChoices = "LearnpathLearningContentAssignmentAssignmentTypeChoices"; +export const LearnpathLearningUnitFeedbackUserChoices = "LearnpathLearningUnitFeedbackUserChoices"; export const Mutation = "Mutation"; export const PerformanceCriteriaObjectType = "PerformanceCriteriaObjectType"; export const PresenceRecordStatisticsType = "PresenceRecordStatisticsType"; diff --git a/client/src/graphql/queries.ts b/client/src/graphql/queries.ts index e8b6fc29..bff64cac 100644 --- a/client/src/graphql/queries.ts +++ b/client/src/graphql/queries.ts @@ -234,6 +234,7 @@ export const COURSE_QUERY = graphql(` icon ...CoursePageFields learning_units { + feedback_user evaluate_url ...CoursePageFields performance_criteria { diff --git a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationPage.vue b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationPage.vue index c7b1bc23..30d2f98d 100644 --- a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationPage.vue +++ b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationPage.vue @@ -14,9 +14,11 @@ const props = defineProps<{ }>(); const courseData = useCourseDataWithCompletion(props.courseSlug); -const learningUnit = computed(() => - courseData.findLearningUnit(props.learningUnitSlug, props.circleSlug) -); +const learningUnit = computed(() => { + const lu = courseData.findLearningUnit(props.learningUnitSlug, props.circleSlug); + console.log("FEEDBACK_USER", lu?.feedback_user); + return lu; +}); const circle = computed(() => { return courseData.findCircle(props.circleSlug); }); diff --git a/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py b/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py index 8c2f2891..6a7656ea 100644 --- a/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py +++ b/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py @@ -14,6 +14,7 @@ from vbv_lernwelt.competence.models import ActionCompetence from vbv_lernwelt.core.admin import User from vbv_lernwelt.course.consts import COURSE_VERSICHERUNGSVERMITTLERIN_ID from vbv_lernwelt.course.models import CourseCategory, CoursePage +from vbv_lernwelt.learnpath.models import LearningUnitPerformanceFeedbackType from vbv_lernwelt.learnpath.tests.learning_path_factories import ( CircleFactory, LearningContentAssignmentFactory, @@ -131,6 +132,7 @@ def create_circle_basis(lp, title="Basis", course_page=None): LearningSequenceFactory(title="Arbeitsalltag", parent=circle) lu = LearningUnitFactory( title="Mein neuer Job, Arbeitstechnik, Soziale Medien, Datenschutz und Beratungspflichten", + feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK.name, parent=circle, ) diff --git a/server/vbv_lernwelt/learnpath/graphql/types.py b/server/vbv_lernwelt/learnpath/graphql/types.py index 4e12cc4b..0cd23953 100644 --- a/server/vbv_lernwelt/learnpath/graphql/types.py +++ b/server/vbv_lernwelt/learnpath/graphql/types.py @@ -234,7 +234,7 @@ class LearningUnitObjectType(DjangoObjectType): class Meta: model = LearningUnit interfaces = (CoursePageInterface,) - fields = ["evaluate_url", "title_hidden"] + fields = ["evaluate_url", "title_hidden", "feedback_user"] def resolve_evaluate_url(self: LearningUnit, info, **kwargs): return self.get_evaluate_url() diff --git a/server/vbv_lernwelt/learnpath/migrations/0014_alter_learningunit_feedback_user.py b/server/vbv_lernwelt/learnpath/migrations/0014_alter_learningunit_feedback_user.py new file mode 100644 index 00000000..a75d2090 --- /dev/null +++ b/server/vbv_lernwelt/learnpath/migrations/0014_alter_learningunit_feedback_user.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.20 on 2024-01-17 14:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('learnpath', '0013_auto_20240117_1400'), + ] + + operations = [ + migrations.AlterField( + model_name='learningunit', + name='feedback_user', + field=models.CharField(choices=[('NO_FEEDBACK', 'NO_FEEDBACK'), ('MENTOR_FEEDBACK', 'MENTOR_FEEDBACK')], default='NO_FEEDBACK', max_length=255), + ), + ] diff --git a/server/vbv_lernwelt/learnpath/models.py b/server/vbv_lernwelt/learnpath/models.py index 983f4dd7..26b5b164 100644 --- a/server/vbv_lernwelt/learnpath/models.py +++ b/server/vbv_lernwelt/learnpath/models.py @@ -179,7 +179,6 @@ class LearningUnit(CourseBasePage): title_hidden = models.BooleanField(default=False) feedback_user = models.CharField( max_length=255, - blank=True, choices=[(tag.name, tag.name) for tag in LearningUnitPerformanceFeedbackType], default=LearningUnitPerformanceFeedbackType.NO_FEEDBACK.name, ) diff --git a/server/vbv_lernwelt/learnpath/serializers.py b/server/vbv_lernwelt/learnpath/serializers.py index 3f6a3255..af616d38 100644 --- a/server/vbv_lernwelt/learnpath/serializers.py +++ b/server/vbv_lernwelt/learnpath/serializers.py @@ -20,6 +20,7 @@ class LearningUnitSerializer( "course_category", "children", "title_hidden", + "feedback_user", ], ) ): From 9a601e01693c624f853182085490d630bba6ede7 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Wed, 17 Jan 2024 17:41:29 +0100 Subject: [PATCH 04/57] poc: feedback - abgabe step for if MENTOR_FEEDBACK --- .../selfEvaluationPage/SelfEvaluation.vue | 34 ++++++++++++++----- .../selfEvaluationPage/SelfEvaluationPage.vue | 4 +-- .../learnpath/create_vv_new_learning_path.py | 2 +- .../migrations/0013_auto_20240117_1400.py | 34 ++++++++++++++----- .../0014_alter_learningunit_feedback_user.py | 16 ++++++--- server/vbv_lernwelt/learnpath/models.py | 2 +- 6 files changed, 66 insertions(+), 26 deletions(-) diff --git a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluation.vue b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluation.vue index 4a5b8c9a..e7a62386 100644 --- a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluation.vue +++ b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluation.vue @@ -3,7 +3,7 @@ import { useCircleStore } from "@/stores/circle"; import type { CircleType, LearningUnit } from "@/types"; import * as log from "loglevel"; -import { useCurrentCourseSession, useCourseDataWithCompletion } from "@/composables"; +import { useCourseDataWithCompletion, useCurrentCourseSession } from "@/composables"; import LearningContentContainer from "@/pages/learningPath/learningContentPage/LearningContentContainer.vue"; import LearningContentMultiLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentMultiLayout.vue"; import eventBus from "@/utils/eventBus"; @@ -26,20 +26,32 @@ const props = defineProps<{ circle: CircleType; }>(); +const learningUnitHasFeedbackPage = computed( + () => props.learningUnit?.feedback_user !== "NO_FEEDBACK" +); + const questions = computed(() => props.learningUnit?.performance_criteria ?? []); + +const numPages = computed(() => { + if (learningUnitHasFeedbackPage.value) { + return questions.value.length + 1; + } else { + return questions.value.length; + } +}); + const currentQuestion = computed(() => questions.value[questionIndex.value]); const showPreviousButton = computed(() => questionIndex.value != 0); const showNextButton = computed( - () => questionIndex.value + 1 < questions.value?.length && questions.value?.length > 1 + () => questionIndex.value + 1 < numPages.value && numPages.value > 1 ); const showExitButton = computed( - () => - questions.value?.length === 1 || questions.value?.length === questionIndex.value + 1 + () => questions.value?.length === 1 || numPages.value == questionIndex.value + 1 ); function handleContinue() { log.debug("handleContinue"); - if (questionIndex.value + 1 < questions.value.length) { + if (questionIndex.value + 1 < numPages.value) { log.debug("increment questionIndex", questionIndex.value); questionIndex.value += 1; } else { @@ -50,7 +62,7 @@ function handleContinue() { function handleBack() { log.debug("handleBack"); - if (questionIndex.value > 0 && questionIndex.value < questions.value.length) { + if (questionIndex.value > 0 && questionIndex.value < numPages.value) { questionIndex.value -= 1; } } @@ -78,16 +90,19 @@ onUnmounted(() => { :sub-title="$t('a.Selbsteinschätzung')" :title="`${learningUnit.title}`" icon="it-icon-lc-learning-module" - :steps-count="questions.length" + :steps-count="numPages" :show-next-button="showNextButton" :show-exit-button="showExitButton" :show-start-button="false" :show-previous-button="showPreviousButton" :base-url="props.learningUnit.evaluate_url" + :end-badge-text=" + learningUnitHasFeedbackPage ? $t('general.submission') : undefined + " @previous="handleBack()" @next="handleContinue()" > -
+

{{ currentQuestion.title }} @@ -137,6 +152,9 @@ onUnmounted(() => {

+
+ Sali zeme, Abgooob +
diff --git a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationPage.vue b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationPage.vue index 30d2f98d..2c55f468 100644 --- a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationPage.vue +++ b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationPage.vue @@ -15,9 +15,7 @@ const props = defineProps<{ const courseData = useCourseDataWithCompletion(props.courseSlug); const learningUnit = computed(() => { - const lu = courseData.findLearningUnit(props.learningUnitSlug, props.circleSlug); - console.log("FEEDBACK_USER", lu?.feedback_user); - return lu; + return courseData.findLearningUnit(props.learningUnitSlug, props.circleSlug); }); const circle = computed(() => { return courseData.findCircle(props.circleSlug); diff --git a/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py b/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py index 6a7656ea..0cb82ab4 100644 --- a/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py +++ b/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py @@ -6,9 +6,9 @@ from wagtail_localize.models import LocaleSynchronization from vbv_lernwelt.assignment.models import Assignment from vbv_lernwelt.competence.factories import ( - PerformanceCriteriaFactory, ActionCompetenceFactory, ActionCompetenceListPageFactory, + PerformanceCriteriaFactory, ) from vbv_lernwelt.competence.models import ActionCompetence from vbv_lernwelt.core.admin import User diff --git a/server/vbv_lernwelt/learnpath/migrations/0013_auto_20240117_1400.py b/server/vbv_lernwelt/learnpath/migrations/0013_auto_20240117_1400.py index 3b78bfbc..b8a11cb8 100644 --- a/server/vbv_lernwelt/learnpath/migrations/0013_auto_20240117_1400.py +++ b/server/vbv_lernwelt/learnpath/migrations/0013_auto_20240117_1400.py @@ -4,20 +4,38 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('learnpath', '0012_auto_20231129_0827'), + ("learnpath", "0012_auto_20231129_0827"), ] operations = [ migrations.AddField( - model_name='learningunit', - name='feedback_user', - field=models.CharField(blank=True, choices=[('NO_FEEDBACK', 'NO_FEEDBACK'), ('MENTOR_FEEDBACK', 'MENTOR_FEEDBACK')], default='NO_FEEDBACK', max_length=255), + model_name="learningunit", + name="feedback_user", + field=models.CharField( + blank=True, + choices=[ + ("NO_FEEDBACK", "NO_FEEDBACK"), + ("MENTOR_FEEDBACK", "MENTOR_FEEDBACK"), + ], + default="NO_FEEDBACK", + max_length=255, + ), ), migrations.AlterField( - model_name='learningcontentassignment', - name='assignment_type', - field=models.CharField(choices=[('PRAXIS_ASSIGNMENT', 'PRAXIS_ASSIGNMENT'), ('CASEWORK', 'CASEWORK'), ('PREP_ASSIGNMENT', 'PREP_ASSIGNMENT'), ('REFLECTION', 'REFLECTION'), ('CONDITION_ACCEPTANCE', 'CONDITION_ACCEPTANCE'), ('EDONIQ_TEST', 'EDONIQ_TEST')], default='CASEWORK', max_length=50), + model_name="learningcontentassignment", + name="assignment_type", + field=models.CharField( + choices=[ + ("PRAXIS_ASSIGNMENT", "PRAXIS_ASSIGNMENT"), + ("CASEWORK", "CASEWORK"), + ("PREP_ASSIGNMENT", "PREP_ASSIGNMENT"), + ("REFLECTION", "REFLECTION"), + ("CONDITION_ACCEPTANCE", "CONDITION_ACCEPTANCE"), + ("EDONIQ_TEST", "EDONIQ_TEST"), + ], + default="CASEWORK", + max_length=50, + ), ), ] diff --git a/server/vbv_lernwelt/learnpath/migrations/0014_alter_learningunit_feedback_user.py b/server/vbv_lernwelt/learnpath/migrations/0014_alter_learningunit_feedback_user.py index a75d2090..91dd1d39 100644 --- a/server/vbv_lernwelt/learnpath/migrations/0014_alter_learningunit_feedback_user.py +++ b/server/vbv_lernwelt/learnpath/migrations/0014_alter_learningunit_feedback_user.py @@ -4,15 +4,21 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('learnpath', '0013_auto_20240117_1400'), + ("learnpath", "0013_auto_20240117_1400"), ] operations = [ migrations.AlterField( - model_name='learningunit', - name='feedback_user', - field=models.CharField(choices=[('NO_FEEDBACK', 'NO_FEEDBACK'), ('MENTOR_FEEDBACK', 'MENTOR_FEEDBACK')], default='NO_FEEDBACK', max_length=255), + model_name="learningunit", + name="feedback_user", + field=models.CharField( + choices=[ + ("NO_FEEDBACK", "NO_FEEDBACK"), + ("MENTOR_FEEDBACK", "MENTOR_FEEDBACK"), + ], + default="NO_FEEDBACK", + max_length=255, + ), ), ] diff --git a/server/vbv_lernwelt/learnpath/models.py b/server/vbv_lernwelt/learnpath/models.py index 26b5b164..6f1d95f0 100644 --- a/server/vbv_lernwelt/learnpath/models.py +++ b/server/vbv_lernwelt/learnpath/models.py @@ -3,7 +3,7 @@ from enum import Enum from django.db import models from django.utils.text import slugify -from wagtail.admin.panels import FieldPanel, PageChooserPanel, HelpPanel +from wagtail.admin.panels import FieldPanel, HelpPanel, PageChooserPanel from wagtail.fields import RichTextField, StreamField from wagtail.models import Page From 09b2586262266491b637d6e9b300efb00d4c0485 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Wed, 17 Jan 2024 18:28:02 +0100 Subject: [PATCH 05/57] feat: prepare data for self evaluation submit component --- .../selfEvaluationPage/SelfEvaluation.vue | 9 ++++--- .../SelfEvaluationSubmit.vue | 25 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue diff --git a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluation.vue b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluation.vue index e7a62386..42cd010f 100644 --- a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluation.vue +++ b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluation.vue @@ -11,6 +11,7 @@ import { useRouteQuery } from "@vueuse/router"; import { computed, onUnmounted } from "vue"; import { getPreviousRoute } from "@/router/history"; import { getCompetenceNaviUrl } from "@/utils/utils"; +import SelfEvaluationSubmit from "@/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue"; log.debug("LearningContent.vue setup"); @@ -152,9 +153,11 @@ onUnmounted(() => { -
- Sali zeme, Abgooob -
+ diff --git a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue new file mode 100644 index 00000000..46118b95 --- /dev/null +++ b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue @@ -0,0 +1,25 @@ + + + + + From ab494a1c67a290f869ef00cf7afb0953eaf4cc11 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Thu, 18 Jan 2024 15:05:29 +0100 Subject: [PATCH 06/57] wip: mentor dropdown --- .../mentor/LearningMentorSelector.vue | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 client/src/components/mentor/LearningMentorSelector.vue diff --git a/client/src/components/mentor/LearningMentorSelector.vue b/client/src/components/mentor/LearningMentorSelector.vue new file mode 100644 index 00000000..c4fdc832 --- /dev/null +++ b/client/src/components/mentor/LearningMentorSelector.vue @@ -0,0 +1,23 @@ + + + From 3f9742550f1659001f2362d7d5b042b5ead21d14 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Fri, 19 Jan 2024 13:20:44 +0100 Subject: [PATCH 07/57] wip: self evaluation mentor --- .../assignment/PraxisAssignmentSubmit.vue | 34 +------ .../mentor/LearningMentorSelector.vue | 23 ----- .../mentor/NoMentorInformationPanel.vue | 29 ++++++ client/src/composables.ts | 21 ++++- .../SelfEvaluationSubmit.vue | 93 +++++++++++++++++-- 5 files changed, 137 insertions(+), 63 deletions(-) delete mode 100644 client/src/components/mentor/LearningMentorSelector.vue create mode 100644 client/src/components/mentor/NoMentorInformationPanel.vue diff --git a/client/src/components/learningPath/assignment/PraxisAssignmentSubmit.vue b/client/src/components/learningPath/assignment/PraxisAssignmentSubmit.vue index b1c31c88..fe8bbd27 100644 --- a/client/src/components/learningPath/assignment/PraxisAssignmentSubmit.vue +++ b/client/src/components/learningPath/assignment/PraxisAssignmentSubmit.vue @@ -2,7 +2,7 @@ import ItButton from "@/components/ui/ItButton.vue"; import ItCheckbox from "@/components/ui/ItCheckbox.vue"; import { ref } from "vue"; -import { bustItGetCache, useCSRFFetch } from "@/fetchHelpers"; +import { bustItGetCache } from "@/fetchHelpers"; import { useUserStore } from "@/stores/user"; import eventBus from "@/utils/eventBus"; import log from "loglevel"; @@ -12,9 +12,8 @@ import { useMutation } from "@urql/vue"; import { UPSERT_ASSIGNMENT_COMPLETION_MUTATION } from "@/graphql/mutations"; import type { Assignment } from "@/types"; import DateEmbedding from "@/components/dueDates/DateEmbedding.vue"; -import { useCurrentCourseSession } from "@/composables"; - -const currentCourseSession = useCurrentCourseSession(); +import { useLearningMentors } from "@/composables"; +import NoMentorInformationPanel from "@/components/mentor/NoMentorInformationPanel.vue"; const props = defineProps<{ submissionDeadlineStart?: string | null; @@ -29,10 +28,7 @@ const upsertAssignmentCompletionMutation = useMutation( UPSERT_ASSIGNMENT_COMPLETION_MUTATION ); -const { data: learningMentors } = useCSRFFetch( - `/api/mentor/${props.courseSessionId}/mentors` -).json(); - +const learningMentors = useLearningMentors().learningMentors; const selectedLearningMentor = ref(); const onSubmit = async () => { @@ -85,27 +81,7 @@ const onSubmit = async () => {
-
- -
-
- {{ - $t( - "a.Aktuell hast du noch keine Person als Lernbegleitung eingeladen. Lade jetzt jemanden ein." - ) - }} -
- - {{ $t("a.Lernbegleitung einladen") }} - -
-
+

diff --git a/client/src/components/mentor/LearningMentorSelector.vue b/client/src/components/mentor/LearningMentorSelector.vue deleted file mode 100644 index c4fdc832..00000000 --- a/client/src/components/mentor/LearningMentorSelector.vue +++ /dev/null @@ -1,23 +0,0 @@ - - - diff --git a/client/src/components/mentor/NoMentorInformationPanel.vue b/client/src/components/mentor/NoMentorInformationPanel.vue new file mode 100644 index 00000000..d6170bc3 --- /dev/null +++ b/client/src/components/mentor/NoMentorInformationPanel.vue @@ -0,0 +1,29 @@ + + + diff --git a/client/src/composables.ts b/client/src/composables.ts index af4fda5b..3b8730e2 100644 --- a/client/src/composables.ts +++ b/client/src/composables.ts @@ -1,3 +1,4 @@ +import { useCSRFFetch } from "@/fetchHelpers"; import type { CourseStatisticsType } from "@/gql/graphql"; import { graphqlClient } from "@/graphql/client"; import { COURSE_QUERY, COURSE_SESSION_DETAIL_QUERY } from "@/graphql/queries"; @@ -27,7 +28,7 @@ import { useQuery } from "@urql/vue"; import orderBy from "lodash/orderBy"; import log from "loglevel"; import type { ComputedRef } from "vue"; -import { computed, ref, watchEffect } from "vue"; +import { computed, onMounted, ref, watchEffect } from "vue"; export function useCurrentCourseSession() { /** @@ -463,3 +464,21 @@ export function useFileUpload() { return { upload, error, loading, fileInfo }; } + +export function useLearningMentors() { + const learningMentors = ref([]); + const currentCourseSessionId = useCurrentCourseSession().value.id; + + const fetchMentors = async () => { + const { data } = await useCSRFFetch( + `/api/mentor/${currentCourseSessionId}/mentors` + ).json(); + learningMentors.value = data.value; + }; + + onMounted(fetchMentors); + + return { + learningMentors, + }; +} diff --git a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue index 46118b95..2faea23c 100644 --- a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue +++ b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue @@ -1,24 +1,97 @@ From a7922743fded46d3fc0043f067bca4f5f50783d1 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Sun, 21 Jan 2024 20:02:22 +0100 Subject: [PATCH 08/57] wip: self evaluation mentor api --- client/src/composables.ts | 3 +- client/src/types.ts | 11 ++ server/config/settings/base.py | 1 + server/config/urls.py | 3 + .../course/creators/test_utils.py | 20 ++- server/vbv_lernwelt/course/models.py | 22 ---- .../self_evaluation_feedback/__init__.py | 0 .../self_evaluation_feedback/admin.py | 3 + .../self_evaluation_feedback/apps.py | 6 + .../migrations/0001_initial.py | 113 +++++++++++++++++ .../migrations/__init__.py | 0 .../self_evaluation_feedback/models.py | 33 +++++ .../self_evaluation_feedback/serializers.py | 40 ++++++ .../tests/__init__.py | 0 .../tests/test_api.py | 116 ++++++++++++++++++ .../self_evaluation_feedback/urls.py | 11 ++ .../self_evaluation_feedback/views.py | 49 ++++++++ 17 files changed, 402 insertions(+), 29 deletions(-) create mode 100644 server/vbv_lernwelt/self_evaluation_feedback/__init__.py create mode 100644 server/vbv_lernwelt/self_evaluation_feedback/admin.py create mode 100644 server/vbv_lernwelt/self_evaluation_feedback/apps.py create mode 100644 server/vbv_lernwelt/self_evaluation_feedback/migrations/0001_initial.py create mode 100644 server/vbv_lernwelt/self_evaluation_feedback/migrations/__init__.py create mode 100644 server/vbv_lernwelt/self_evaluation_feedback/models.py create mode 100644 server/vbv_lernwelt/self_evaluation_feedback/serializers.py create mode 100644 server/vbv_lernwelt/self_evaluation_feedback/tests/__init__.py create mode 100644 server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py create mode 100644 server/vbv_lernwelt/self_evaluation_feedback/urls.py create mode 100644 server/vbv_lernwelt/self_evaluation_feedback/views.py diff --git a/client/src/composables.ts b/client/src/composables.ts index 3b8730e2..f9f049e7 100644 --- a/client/src/composables.ts +++ b/client/src/composables.ts @@ -20,6 +20,7 @@ import type { CourseSession, CourseSessionDetail, LearningContentWithCompletion, + LearningMentor, LearningPathType, LearningUnitPerformanceCriteria, PerformanceCriteria, @@ -466,7 +467,7 @@ export function useFileUpload() { } export function useLearningMentors() { - const learningMentors = ref([]); + const learningMentors = ref([]); const currentCourseSessionId = useCurrentCourseSession().value.id; const fetchMentors = async () => { diff --git a/client/src/types.ts b/client/src/types.ts index 5cc139c5..19146357 100644 --- a/client/src/types.ts +++ b/client/src/types.ts @@ -456,6 +456,17 @@ export interface ExpertSessionUser extends CourseSessionUser { role: "EXPERT"; } +export interface Mentor { + id: number; + first_name: string; + last_name: string; +} + +export interface LearningMentor { + id: number; + mentor: Mentor; +} + export type CourseSessionDetail = CourseSessionObjectType; // document upload diff --git a/server/config/settings/base.py b/server/config/settings/base.py index b073746e..81090a32 100644 --- a/server/config/settings/base.py +++ b/server/config/settings/base.py @@ -133,6 +133,7 @@ LOCAL_APPS = [ "vbv_lernwelt.course_session_group", "vbv_lernwelt.shop", "vbv_lernwelt.learning_mentor", + "vbv_lernwelt.self_evaluation_feedback", ] # https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS diff --git a/server/config/urls.py b/server/config/urls.py index f7d27476..d244b960 100644 --- a/server/config/urls.py +++ b/server/config/urls.py @@ -133,6 +133,9 @@ urlpatterns = [ path("api/mentor//", include("vbv_lernwelt.learning_mentor.urls")), + # self evaluation feedback + path("api/self-evaluation-feedback/", include("vbv_lernwelt.self_evaluation_feedback.urls")), + # assignment path( r"api/assignment///status/", diff --git a/server/vbv_lernwelt/course/creators/test_utils.py b/server/vbv_lernwelt/course/creators/test_utils.py index 3a396754..51360fc6 100644 --- a/server/vbv_lernwelt/course/creators/test_utils.py +++ b/server/vbv_lernwelt/course/creators/test_utils.py @@ -268,10 +268,21 @@ def create_course_session_edoniq_test( return cset +def create_learning_unit(circle: Circle, course: Course): + cat, _ = CourseCategory.objects.get_or_create( + course=course, title="Course Category" + ) + + return LearningUnitFactory( + title="Learning Unit", parent=circle, course_category=cat + ) + + def create_performance_criteria_page( course: Course, course_page: CoursePage, circle: Circle, + learning_unit: LearningUnitFactory | None = None, ) -> PerformanceCriteria: competence_navi_page = CompetenceNaviPageFactory( title="Competence Navi", @@ -290,17 +301,14 @@ def create_performance_criteria_page( items=[("item", "Action Competence Item")], ) - cat, _ = CourseCategory.objects.get_or_create( - course=course, title="Course Category" - ) - - lu = LearningUnitFactory(title="Learning Unit", parent=circle, course_category=cat) + if not learning_unit: + learning_unit = create_learning_unit(circle=circle, course=course) return PerformanceCriteriaFactory( parent=action_competence, competence_id="X1.1", title="Performance Criteria", - learning_unit=lu, + learning_unit=learning_unit, ) diff --git a/server/vbv_lernwelt/course/models.py b/server/vbv_lernwelt/course/models.py index a6c8fd80..cbd52a55 100644 --- a/server/vbv_lernwelt/course/models.py +++ b/server/vbv_lernwelt/course/models.py @@ -329,25 +329,3 @@ class CircleDocument(models.Model): self.file.upload_finished_at = None self.file.save() return super().delete(*args, **kwargs) - - -# TODO: Model something like this: -# class LearningUnitCompletionFeedback(models.Model): -# assignment_user = models.ForeignKey(User, on_delete=models.CASCADE) -# feedback_user = models.ForeignKey(User, on_delete=models.CASCADE) -# feedback_submitted = models.BooleanField(default=False) -# learning_unit = models.ForeignKey( -# "learnpath.LearningUnit", on_delete=models.CASCADE -# ) -# -# -# class CourseCompletionFeedback(models.Model): -# learning_unit_completion_feedback = models.ForeignKey( -# LearningUnitCompletionFeedback, on_delete=models.CASCADE -# ) -# course_completion = models.ForeignKey(CourseCompletion, on_delete=models.CASCADE) -# feedback_status = models.CharField( -# max_length=255, -# choices=[(status, status.value) for status in CourseCompletionStatus], -# default=CourseCompletionStatus.UNKNOWN.value, -# ) diff --git a/server/vbv_lernwelt/self_evaluation_feedback/__init__.py b/server/vbv_lernwelt/self_evaluation_feedback/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/vbv_lernwelt/self_evaluation_feedback/admin.py b/server/vbv_lernwelt/self_evaluation_feedback/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/server/vbv_lernwelt/self_evaluation_feedback/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/server/vbv_lernwelt/self_evaluation_feedback/apps.py b/server/vbv_lernwelt/self_evaluation_feedback/apps.py new file mode 100644 index 00000000..d0cec9eb --- /dev/null +++ b/server/vbv_lernwelt/self_evaluation_feedback/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class SelfEvaluationFeedbackConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "vbv_lernwelt.self_evaluation_feedback" diff --git a/server/vbv_lernwelt/self_evaluation_feedback/migrations/0001_initial.py b/server/vbv_lernwelt/self_evaluation_feedback/migrations/0001_initial.py new file mode 100644 index 00000000..faa03afb --- /dev/null +++ b/server/vbv_lernwelt/self_evaluation_feedback/migrations/0001_initial.py @@ -0,0 +1,113 @@ +# Generated by Django 3.2.20 on 2024-01-21 18:42 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + +import vbv_lernwelt.course.models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + ("course", "0006_auto_20231221_1411"), + ("learnpath", "0014_alter_learningunit_feedback_user"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="SelfEvaluationFeedback", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("feedback_submitted", models.BooleanField(default=False)), + ( + "feedback_provider_user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="feedback_provider_user", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "feedback_requester_user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="feedback_requester_user", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "learning_unit", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="learnpath.learningunit", + ), + ), + ], + ), + migrations.CreateModel( + name="CourseCompletionFeedback", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "provider_evaluation_feedback", + models.CharField( + choices=[ + ( + vbv_lernwelt.course.models.CourseCompletionStatus[ + "SUCCESS" + ], + "SUCCESS", + ), + ( + vbv_lernwelt.course.models.CourseCompletionStatus[ + "FAIL" + ], + "FAIL", + ), + ( + vbv_lernwelt.course.models.CourseCompletionStatus[ + "UNKNOWN" + ], + "UNKNOWN", + ), + ], + default="UNKNOWN", + max_length=255, + ), + ), + ( + "feedback", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="self_evaluation_feedback.selfevaluationfeedback", + ), + ), + ( + "requester_evaluation", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="course.coursecompletion", + ), + ), + ], + ), + ] diff --git a/server/vbv_lernwelt/self_evaluation_feedback/migrations/__init__.py b/server/vbv_lernwelt/self_evaluation_feedback/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/vbv_lernwelt/self_evaluation_feedback/models.py b/server/vbv_lernwelt/self_evaluation_feedback/models.py new file mode 100644 index 00000000..cfc7477f --- /dev/null +++ b/server/vbv_lernwelt/self_evaluation_feedback/models.py @@ -0,0 +1,33 @@ +from django.db import models + +from vbv_lernwelt.core.admin import User +from vbv_lernwelt.course.models import CourseCompletion, CourseCompletionStatus + + +class SelfEvaluationFeedback(models.Model): + feedback_submitted = models.BooleanField(default=False) + + feedback_requester_user = models.ForeignKey( + User, on_delete=models.CASCADE, related_name="feedback_requester_user" + ) + + feedback_provider_user = models.ForeignKey( + User, on_delete=models.CASCADE, related_name="feedback_provider_user" + ) + + learning_unit = models.ForeignKey( + "learnpath.LearningUnit", on_delete=models.CASCADE + ) + + +class CourseCompletionFeedback(models.Model): + feedback = models.ForeignKey(SelfEvaluationFeedback, on_delete=models.CASCADE) + + # the course completion has to be evaluated by the feedback provider + requester_evaluation = models.ForeignKey(CourseCompletion, on_delete=models.CASCADE) + + provider_evaluation_feedback = models.CharField( + max_length=255, + choices=[(status, status.value) for status in CourseCompletionStatus], + default=CourseCompletionStatus.UNKNOWN.value, + ) diff --git a/server/vbv_lernwelt/self_evaluation_feedback/serializers.py b/server/vbv_lernwelt/self_evaluation_feedback/serializers.py new file mode 100644 index 00000000..2e1c7d72 --- /dev/null +++ b/server/vbv_lernwelt/self_evaluation_feedback/serializers.py @@ -0,0 +1,40 @@ +from typing import List + +from rest_framework import serializers + +from vbv_lernwelt.competence.models import PerformanceCriteria +from vbv_lernwelt.core.serializers import UserSerializer +from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback + + +class SelfEvaluationFeedbackSerializer(serializers.ModelSerializer): + criteria = serializers.SerializerMethodField() + feedback_requester_user = UserSerializer(read_only=True) + feedback_provider_user = UserSerializer(read_only=True) + learning_unit_id = serializers.PrimaryKeyRelatedField( + read_only=True, source="learning_unit" + ) + + class Meta: + model = SelfEvaluationFeedback + fields = [ + "id", + "learning_unit_id", + "feedback_submitted", + "feedback_requester_user", + "feedback_provider_user", + "criteria", + ] + + def get_criteria(self, obj): + performance_criteria: List[ + PerformanceCriteria + ] = obj.learning_unit.performancecriteria_set.all() + + return [ + { + "id": criteria.id, + "title": criteria.title, + } + for criteria in performance_criteria + ] diff --git a/server/vbv_lernwelt/self_evaluation_feedback/tests/__init__.py b/server/vbv_lernwelt/self_evaluation_feedback/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py b/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py new file mode 100644 index 00000000..ce5febfc --- /dev/null +++ b/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py @@ -0,0 +1,116 @@ +from django.urls import reverse +from rest_framework.test import APITestCase + +from vbv_lernwelt.course.creators.test_utils import ( + add_course_session_user, + create_circle, + create_course, + create_course_session, + create_learning_unit, + create_performance_criteria_page, + create_user, +) +from vbv_lernwelt.course.models import CourseSessionUser +from vbv_lernwelt.course.services import mark_course_completion +from vbv_lernwelt.learning_mentor.models import LearningMentor +from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback + + +def create_self_evaluation_feedback( + learning_unit, feedback_requester_user, feedback_provider_user +): + return SelfEvaluationFeedback.objects.create( + learning_unit=learning_unit, + feedback_requester_user=feedback_requester_user, + feedback_provider_user=feedback_provider_user, + ) + + +class SelfEvaluationFeedbackAPI(APITestCase): + def setUp(self) -> None: + self.member = create_user("member") + self.mentor = create_user("mentor") + + self.course, self.course_page = create_course("Test Course") + self.course_session = create_course_session( + course=self.course, title="Test Bern 2022 a" + ) + + member_csu = add_course_session_user( + course_session=self.course_session, + user=self.member, + role=CourseSessionUser.Role.MEMBER, + ) + + self.circle, _ = create_circle( + title="Test Circle", course_page=self.course_page + ) + + learning_mentor = LearningMentor.objects.create( + mentor=self.mentor, + course=self.course_session.course, + ) + + learning_mentor.participants.add(member_csu) + + self.client.force_login(self.member) + + def test_start_self_evaluation_feedback(self): + # GIVEN + learning_unit = create_learning_unit(course=self.course, circle=self.circle) + + pc = create_performance_criteria_page( + course=self.course, + course_page=self.course_page, + circle=self.circle, + learning_unit=learning_unit, + ) + + mark_course_completion( + page=pc, + user=self.member, + course_session=self.course_session, + completion_status="SUCCESS", + ) + + # WHEN + response = self.client.post( + reverse("start_self_evaluation_feedback"), + { + "learning_unit_id": learning_unit.id, + "feedback_provider_user_id": self.mentor.id, + }, + ) + + # THEN + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data["success"], True) + + self.assertEqual( + SelfEvaluationFeedback.objects.count(), + 1, + ) + + # just get the first one + f = SelfEvaluationFeedback.objects.first() + + self.assertEqual(f.feedback_requester_user, self.member) + self.assertEqual(f.feedback_provider_user, self.mentor) + self.assertEqual(f.learning_unit, learning_unit) + + def test_start_self_evaluation_feedback_not_allowed_user(self): + # GIVEN + learning_unit = create_learning_unit(course=self.course, circle=self.circle) + not_a_mentor = create_user("not_a_mentor") + + # WHEN + response = self.client.post( + reverse("start_self_evaluation_feedback"), + { + "learning_unit_id": learning_unit.id, + "feedback_provider_user_id": not_a_mentor.id, + }, + ) + + # THEN + self.assertEqual(response.status_code, 403) diff --git a/server/vbv_lernwelt/self_evaluation_feedback/urls.py b/server/vbv_lernwelt/self_evaluation_feedback/urls.py new file mode 100644 index 00000000..ec1c26cf --- /dev/null +++ b/server/vbv_lernwelt/self_evaluation_feedback/urls.py @@ -0,0 +1,11 @@ +from django.urls import path + +from .views import start_self_evaluation_feedback + +urlpatterns = [ + path( + "start-feedback", + start_self_evaluation_feedback, + name="start_self_evaluation_feedback", + ), +] diff --git a/server/vbv_lernwelt/self_evaluation_feedback/views.py b/server/vbv_lernwelt/self_evaluation_feedback/views.py new file mode 100644 index 00000000..b6b5c7be --- /dev/null +++ b/server/vbv_lernwelt/self_evaluation_feedback/views.py @@ -0,0 +1,49 @@ +from django.shortcuts import get_object_or_404 +from rest_framework.decorators import api_view, permission_classes +from rest_framework.exceptions import PermissionDenied +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response + +from vbv_lernwelt.core.models import User +from vbv_lernwelt.learning_mentor.models import LearningMentor +from vbv_lernwelt.learnpath.models import LearningUnit +from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback + + +@api_view(["POST"]) +@permission_classes([IsAuthenticated]) +def start_self_evaluation_feedback(request): + learning_unit_id = request.data.get("learning_unit_id") + feedback_provider_user_id = request.data.get("feedback_provider_user_id") + + learning_unit = get_object_or_404(LearningUnit, id=learning_unit_id) + feedback_provider_user = get_object_or_404(User, id=feedback_provider_user_id) + + if not LearningMentor.objects.filter( + course=learning_unit.get_course(), + mentor=feedback_provider_user, + participants__user=request.user, + ).exists(): + raise PermissionDenied() + + SelfEvaluationFeedback.objects.create( + feedback_requester_user=request.user, + feedback_provider_user=feedback_provider_user, + learning_unit=learning_unit, + ) + + # TODO: Create notification for feedback_provider_user + + return Response({"success": True}) + + +@api_view(["GET"]) +@permission_classes([IsAuthenticated]) +def list_self_evaluation_feedback(request): + feedbacks = SelfEvaluationFeedback.objects.filter( + feedback_provider_user=request.user + ) + + # TODO continue here + + return Response({"success": True}) From ab033bb0c9891dd23fa0e907d87b96a0c82b090c Mon Sep 17 00:00:00 2001 From: Reto Aebersold Date: Mon, 22 Jan 2024 09:21:58 +0100 Subject: [PATCH 09/57] feat: submit styling --- .../SelfEvaluationSubmit.vue | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue index 2faea23c..cfbf7a03 100644 --- a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue +++ b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue @@ -22,13 +22,13 @@ const onSubmitForMentorFeedback = async () => { From e107c32734e56412807df0b4352ff3a97162a616 Mon Sep 17 00:00:00 2001 From: Reto Aebersold Date: Mon, 22 Jan 2024 09:23:18 +0100 Subject: [PATCH 10/57] feat: submit styling --- .../learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue index cfbf7a03..4e3d7664 100644 --- a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue +++ b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue @@ -85,13 +85,13 @@ const onSubmitForMentorFeedback = async () => {

{{ $t("selfEvaluation.yes") }}
-
+
{{ $t("selfEvaluation.no") }}
From 8bf762173ae0182108dfd6e52fa4581c952fc62e Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Mon, 22 Jan 2024 15:33:31 +0100 Subject: [PATCH 11/57] feat: API to list self evaluation feedback for fb provider --- ...rsecompletionfeedback_course_completion.py | 17 ++++ .../self_evaluation_feedback/models.py | 4 +- .../self_evaluation_feedback/serializers.py | 54 +++++++++-- .../tests/test_api.py | 97 ++++++++++++++++++- .../self_evaluation_feedback/urls.py | 12 ++- .../self_evaluation_feedback/views.py | 9 +- 6 files changed, 172 insertions(+), 21 deletions(-) create mode 100644 server/vbv_lernwelt/self_evaluation_feedback/migrations/0002_rename_requester_evaluation_coursecompletionfeedback_course_completion.py diff --git a/server/vbv_lernwelt/self_evaluation_feedback/migrations/0002_rename_requester_evaluation_coursecompletionfeedback_course_completion.py b/server/vbv_lernwelt/self_evaluation_feedback/migrations/0002_rename_requester_evaluation_coursecompletionfeedback_course_completion.py new file mode 100644 index 00000000..66eea762 --- /dev/null +++ b/server/vbv_lernwelt/self_evaluation_feedback/migrations/0002_rename_requester_evaluation_coursecompletionfeedback_course_completion.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.20 on 2024-01-22 13:20 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("self_evaluation_feedback", "0001_initial"), + ] + + operations = [ + migrations.RenameField( + model_name="coursecompletionfeedback", + old_name="requester_evaluation", + new_name="course_completion", + ), + ] diff --git a/server/vbv_lernwelt/self_evaluation_feedback/models.py b/server/vbv_lernwelt/self_evaluation_feedback/models.py index cfc7477f..bf3870be 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/models.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/models.py @@ -24,9 +24,9 @@ class CourseCompletionFeedback(models.Model): feedback = models.ForeignKey(SelfEvaluationFeedback, on_delete=models.CASCADE) # the course completion has to be evaluated by the feedback provider - requester_evaluation = models.ForeignKey(CourseCompletion, on_delete=models.CASCADE) + course_completion = models.ForeignKey(CourseCompletion, on_delete=models.CASCADE) - provider_evaluation_feedback = models.CharField( + feedback_assessment = models.CharField( max_length=255, choices=[(status, status.value) for status in CourseCompletionStatus], default=CourseCompletionStatus.UNKNOWN.value, diff --git a/server/vbv_lernwelt/self_evaluation_feedback/serializers.py b/server/vbv_lernwelt/self_evaluation_feedback/serializers.py index 2e1c7d72..037fa5fb 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/serializers.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/serializers.py @@ -4,7 +4,11 @@ from rest_framework import serializers from vbv_lernwelt.competence.models import PerformanceCriteria from vbv_lernwelt.core.serializers import UserSerializer -from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback +from vbv_lernwelt.course.models import CourseCompletion, CourseCompletionStatus +from vbv_lernwelt.self_evaluation_feedback.models import ( + CourseCompletionFeedback, + SelfEvaluationFeedback, +) class SelfEvaluationFeedbackSerializer(serializers.ModelSerializer): @@ -14,11 +18,14 @@ class SelfEvaluationFeedbackSerializer(serializers.ModelSerializer): learning_unit_id = serializers.PrimaryKeyRelatedField( read_only=True, source="learning_unit" ) + feedback_id = serializers.PrimaryKeyRelatedField( + read_only=True, source="course_completion_feedback" + ) class Meta: model = SelfEvaluationFeedback fields = [ - "id", + "feedback_id", "learning_unit_id", "feedback_submitted", "feedback_requester_user", @@ -31,10 +38,39 @@ class SelfEvaluationFeedbackSerializer(serializers.ModelSerializer): PerformanceCriteria ] = obj.learning_unit.performancecriteria_set.all() - return [ - { - "id": criteria.id, - "title": criteria.title, - } - for criteria in performance_criteria - ] + criteria = [] + + for pc in performance_criteria: + # requester self assessment + completion = CourseCompletion.objects.filter( + page_id=pc.id, + user=obj.feedback_requester_user, + ).first() + + self_assessment = ( + completion.completion_status + if completion + else CourseCompletionStatus.UNKNOWN.value + ) + + # provider feedback assessment + feedback = CourseCompletionFeedback.objects.filter( + course_completion=completion + ).first() + + feedback_assessment = ( + feedback.feedback_assessment + if feedback + else CourseCompletionStatus.UNKNOWN.value + ) + + criteria.append( + { + "course_completion_id": completion.id if completion else None, + "title": pc.title, + "self_assessment": self_assessment, + "feedback_assessment": feedback_assessment, + } + ) + + return criteria diff --git a/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py b/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py index ce5febfc..df45b741 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py @@ -10,10 +10,13 @@ from vbv_lernwelt.course.creators.test_utils import ( create_performance_criteria_page, create_user, ) -from vbv_lernwelt.course.models import CourseSessionUser +from vbv_lernwelt.course.models import CourseCompletionStatus, CourseSessionUser from vbv_lernwelt.course.services import mark_course_completion from vbv_lernwelt.learning_mentor.models import LearningMentor -from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback +from vbv_lernwelt.self_evaluation_feedback.models import ( + CourseCompletionFeedback, + SelfEvaluationFeedback, +) def create_self_evaluation_feedback( @@ -53,8 +56,6 @@ class SelfEvaluationFeedbackAPI(APITestCase): learning_mentor.participants.add(member_csu) - self.client.force_login(self.member) - def test_start_self_evaluation_feedback(self): # GIVEN learning_unit = create_learning_unit(course=self.course, circle=self.circle) @@ -73,6 +74,8 @@ class SelfEvaluationFeedbackAPI(APITestCase): completion_status="SUCCESS", ) + self.client.force_login(self.member) + # WHEN response = self.client.post( reverse("start_self_evaluation_feedback"), @@ -103,6 +106,8 @@ class SelfEvaluationFeedbackAPI(APITestCase): learning_unit = create_learning_unit(course=self.course, circle=self.circle) not_a_mentor = create_user("not_a_mentor") + self.client.force_login(self.member) + # WHEN response = self.client.post( reverse("start_self_evaluation_feedback"), @@ -114,3 +119,87 @@ class SelfEvaluationFeedbackAPI(APITestCase): # THEN self.assertEqual(response.status_code, 403) + + def test_list_self_evaluation_feedback_provider(self): + # GIVEN + learning_unit = create_learning_unit(course=self.course, circle=self.circle) + + performance_criteria_1 = create_performance_criteria_page( + course=self.course, + course_page=self.course_page, + circle=self.circle, + learning_unit=learning_unit, + ) + + create_performance_criteria_page( + course=self.course, + course_page=self.course_page, + circle=self.circle, + learning_unit=learning_unit, + ) + + completion = mark_course_completion( + page=performance_criteria_1, + user=self.member, + course_session=self.course_session, + completion_status=CourseCompletionStatus.SUCCESS.value, + ) + + feedback = create_self_evaluation_feedback( + learning_unit=learning_unit, + feedback_requester_user=self.member, + feedback_provider_user=self.mentor, + ) + + CourseCompletionFeedback.objects.create( + feedback=feedback, + course_completion=completion, + feedback_assessment=CourseCompletionStatus.FAIL.value, + ) + + self.client.force_login(self.mentor) + + # WHEN + response = self.client.get(reverse("list_self_evaluation_feedbacks_provider")) + + # THEN + self.assertEqual(response.status_code, 200) + + self.assertEqual(len(response.data), 1) + self.assertEqual(response.data[0]["learning_unit_id"], learning_unit.id) + + provider_user = response.data[0]["feedback_provider_user"] + self.assertEqual(provider_user["id"], str(self.mentor.id)) # noqa + self.assertEqual(provider_user["first_name"], self.mentor.first_name) + self.assertEqual(provider_user["last_name"], self.mentor.last_name) + self.assertEqual(provider_user["avatar_url"], self.mentor.avatar_url) + + requester_user = response.data[0]["feedback_requester_user"] + self.assertEqual(requester_user["id"], str(self.member.id)) # noqa + self.assertEqual(requester_user["first_name"], self.member.first_name) + self.assertEqual(requester_user["last_name"], self.member.last_name) + self.assertEqual(requester_user["avatar_url"], self.member.avatar_url) + + self.assertEqual(len(response.data[0]["criteria"]), 2) + + first_criteria = response.data[0]["criteria"][0] + self.assertEqual(first_criteria["course_completion_id"], completion.id) + self.assertEqual(first_criteria["title"], performance_criteria_1.title) + self.assertEqual( + first_criteria["self_assessment"], + CourseCompletionStatus.SUCCESS.value, + ) + self.assertEqual( + first_criteria["feedback_assessment"], CourseCompletionStatus.FAIL.value + ) + + second_criteria = response.data[0]["criteria"][1] + self.assertEqual(second_criteria["course_completion_id"], None) + self.assertEqual(second_criteria["title"], performance_criteria_1.title) + self.assertEqual( + second_criteria["self_assessment"], CourseCompletionStatus.UNKNOWN.value + ) + self.assertEqual( + second_criteria["feedback_assessment"], + CourseCompletionStatus.UNKNOWN.value, + ) diff --git a/server/vbv_lernwelt/self_evaluation_feedback/urls.py b/server/vbv_lernwelt/self_evaluation_feedback/urls.py index ec1c26cf..6a14536c 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/urls.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/urls.py @@ -1,11 +1,19 @@ from django.urls import path -from .views import start_self_evaluation_feedback +from vbv_lernwelt.self_evaluation_feedback.views import ( + list_self_evaluation_feedbacks_provider, + start_self_evaluation_feedback, +) urlpatterns = [ path( - "start-feedback", + "reqeuster/start-feedback", start_self_evaluation_feedback, name="start_self_evaluation_feedback", ), + path( + "provider/list-feedbacks", + list_self_evaluation_feedbacks_provider, + name="list_self_evaluation_feedbacks_provider", + ), ] diff --git a/server/vbv_lernwelt/self_evaluation_feedback/views.py b/server/vbv_lernwelt/self_evaluation_feedback/views.py index b6b5c7be..b0f85af8 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/views.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/views.py @@ -8,6 +8,9 @@ from vbv_lernwelt.core.models import User from vbv_lernwelt.learning_mentor.models import LearningMentor from vbv_lernwelt.learnpath.models import LearningUnit from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback +from vbv_lernwelt.self_evaluation_feedback.serializers import ( + SelfEvaluationFeedbackSerializer, +) @api_view(["POST"]) @@ -39,11 +42,9 @@ def start_self_evaluation_feedback(request): @api_view(["GET"]) @permission_classes([IsAuthenticated]) -def list_self_evaluation_feedback(request): +def list_self_evaluation_feedbacks_provider(request): feedbacks = SelfEvaluationFeedback.objects.filter( feedback_provider_user=request.user ) - # TODO continue here - - return Response({"success": True}) + return Response(SelfEvaluationFeedbackSerializer(feedbacks, many=True).data) From 8eee03cb69bca22a09d40cd94826aab11f67217a Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Mon, 22 Jan 2024 17:51:15 +0100 Subject: [PATCH 12/57] wip: API for feedback provider to add assessment --- .../self_evaluation_feedback/serializers.py | 5 ++ .../tests/test_api.py | 58 +++++++++++++++---- .../self_evaluation_feedback/urls.py | 21 +++++-- .../self_evaluation_feedback/views.py | 43 ++++++++++++-- 4 files changed, 106 insertions(+), 21 deletions(-) diff --git a/server/vbv_lernwelt/self_evaluation_feedback/serializers.py b/server/vbv_lernwelt/self_evaluation_feedback/serializers.py index 037fa5fb..8fdf4f49 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/serializers.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/serializers.py @@ -21,18 +21,23 @@ class SelfEvaluationFeedbackSerializer(serializers.ModelSerializer): feedback_id = serializers.PrimaryKeyRelatedField( read_only=True, source="course_completion_feedback" ) + circle_name = serializers.SerializerMethodField() class Meta: model = SelfEvaluationFeedback fields = [ "feedback_id", "learning_unit_id", + "circle_name", "feedback_submitted", "feedback_requester_user", "feedback_provider_user", "criteria", ] + def get_circle_name(self, obj): + return obj.learning_unit.get_circle().title + def get_criteria(self, obj): performance_criteria: List[ PerformanceCriteria diff --git a/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py b/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py index df45b741..e3fe6940 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py @@ -120,7 +120,7 @@ class SelfEvaluationFeedbackAPI(APITestCase): # THEN self.assertEqual(response.status_code, 403) - def test_list_self_evaluation_feedback_provider(self): + def test_get_provider_self_evaluation_feedback(self): # GIVEN learning_unit = create_learning_unit(course=self.course, circle=self.circle) @@ -145,14 +145,14 @@ class SelfEvaluationFeedbackAPI(APITestCase): completion_status=CourseCompletionStatus.SUCCESS.value, ) - feedback = create_self_evaluation_feedback( + self_evaluation_feedback = create_self_evaluation_feedback( learning_unit=learning_unit, feedback_requester_user=self.member, feedback_provider_user=self.mentor, ) CourseCompletionFeedback.objects.create( - feedback=feedback, + feedback=self_evaluation_feedback, course_completion=completion, feedback_assessment=CourseCompletionStatus.FAIL.value, ) @@ -160,29 +160,36 @@ class SelfEvaluationFeedbackAPI(APITestCase): self.client.force_login(self.mentor) # WHEN - response = self.client.get(reverse("list_self_evaluation_feedbacks_provider")) + response = self.client.get( + reverse( + "get_provider_self_evaluation_feedback", + args=[self_evaluation_feedback.id], + ) + ) # THEN self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.data), 1) - self.assertEqual(response.data[0]["learning_unit_id"], learning_unit.id) + feedback = response.data + self.assertEqual(feedback["learning_unit_id"], learning_unit.id) + self.assertEqual(feedback["feedback_submitted"], False) + self.assertEqual(feedback["circle_name"], self.circle.title) - provider_user = response.data[0]["feedback_provider_user"] + provider_user = feedback["feedback_provider_user"] self.assertEqual(provider_user["id"], str(self.mentor.id)) # noqa self.assertEqual(provider_user["first_name"], self.mentor.first_name) self.assertEqual(provider_user["last_name"], self.mentor.last_name) self.assertEqual(provider_user["avatar_url"], self.mentor.avatar_url) - requester_user = response.data[0]["feedback_requester_user"] + requester_user = feedback["feedback_requester_user"] self.assertEqual(requester_user["id"], str(self.member.id)) # noqa self.assertEqual(requester_user["first_name"], self.member.first_name) self.assertEqual(requester_user["last_name"], self.member.last_name) self.assertEqual(requester_user["avatar_url"], self.member.avatar_url) - self.assertEqual(len(response.data[0]["criteria"]), 2) + self.assertEqual(len(feedback["criteria"]), 2) - first_criteria = response.data[0]["criteria"][0] + first_criteria = feedback["criteria"][0] self.assertEqual(first_criteria["course_completion_id"], completion.id) self.assertEqual(first_criteria["title"], performance_criteria_1.title) self.assertEqual( @@ -193,7 +200,7 @@ class SelfEvaluationFeedbackAPI(APITestCase): first_criteria["feedback_assessment"], CourseCompletionStatus.FAIL.value ) - second_criteria = response.data[0]["criteria"][1] + second_criteria = feedback["criteria"][1] self.assertEqual(second_criteria["course_completion_id"], None) self.assertEqual(second_criteria["title"], performance_criteria_1.title) self.assertEqual( @@ -203,3 +210,32 @@ class SelfEvaluationFeedbackAPI(APITestCase): second_criteria["feedback_assessment"], CourseCompletionStatus.UNKNOWN.value, ) + + def test_submit_self_evaluation_feedback(self): + # GIVEN + learning_unit = create_learning_unit(course=self.course, circle=self.circle) + self_evaluation_feedback = create_self_evaluation_feedback( + learning_unit=learning_unit, + feedback_requester_user=self.member, + feedback_provider_user=self.mentor, + ) + + self.assertEqual(self_evaluation_feedback.feedback_submitted, False) + self.client.force_login(self.mentor) + + # WHEN + response = self.client.put( + reverse( + "submit_self_evaluation_feedback", args=[self_evaluation_feedback.id] + ), + ) + + # THEN + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data["success"], True) + self.assertEqual( + SelfEvaluationFeedback.objects.get( + id=self_evaluation_feedback.id + ).feedback_submitted, + True, + ) diff --git a/server/vbv_lernwelt/self_evaluation_feedback/urls.py b/server/vbv_lernwelt/self_evaluation_feedback/urls.py index 6a14536c..cc705995 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/urls.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/urls.py @@ -1,19 +1,30 @@ from django.urls import path from vbv_lernwelt.self_evaluation_feedback.views import ( - list_self_evaluation_feedbacks_provider, + get_provider_self_evaluation_feedback, start_self_evaluation_feedback, + submit_self_evaluation_feedback, ) urlpatterns = [ path( - "reqeuster/start-feedback", + "feedback/start", start_self_evaluation_feedback, name="start_self_evaluation_feedback", ), path( - "provider/list-feedbacks", - list_self_evaluation_feedbacks_provider, - name="list_self_evaluation_feedbacks_provider", + "feedback//submit", + submit_self_evaluation_feedback, + name="submit_self_evaluation_feedback", + ), + path( + "feedback//add-feedback-assessment", + add_self_evaluation_feedback_assessment, + name="add_self_evaluation_feedback_assessment", + ), + path( + "provider/feedback/", + get_provider_self_evaluation_feedback, + name="get_provider_self_evaluation_feedback", ), ] diff --git a/server/vbv_lernwelt/self_evaluation_feedback/views.py b/server/vbv_lernwelt/self_evaluation_feedback/views.py index b0f85af8..e60c2cd2 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/views.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/views.py @@ -40,11 +40,44 @@ def start_self_evaluation_feedback(request): return Response({"success": True}) -@api_view(["GET"]) +@api_view(["PUT"]) @permission_classes([IsAuthenticated]) -def list_self_evaluation_feedbacks_provider(request): - feedbacks = SelfEvaluationFeedback.objects.filter( - feedback_provider_user=request.user +def submit_self_evaluation_feedback(request, feedback_id): + feedback = get_object_or_404( + SelfEvaluationFeedback, id=feedback_id, feedback_provider_user=request.user ) - return Response(SelfEvaluationFeedbackSerializer(feedbacks, many=True).data) + feedback.feedback_submitted = True + feedback.save() + + return Response({"success": True}) + + +@api_view(["GET"]) +@permission_classes([IsAuthenticated]) +def get_provider_self_evaluation_feedback(request, feedback_id): + feedback = get_object_or_404( + SelfEvaluationFeedback, id=feedback_id, feedback_provider_user=request.user + ) + + return Response(SelfEvaluationFeedbackSerializer(feedback).data) + + +@api_view(["PUT"]) +@permission_classes([IsAuthenticated]) +def add_self_evaluation_feedback_assessment(request, feedback_id): + feedback = get_object_or_404( + SelfEvaluationFeedback, id=feedback_id, feedback_provider_user=request.user + ) + + feedback_assessment = request.data.get("feedback_assessment") + + # TODO @livioso continue here + + # CourseCompletionFeedback.objects.get_or_create( + # feedback=feedback, + # course_completion=feedback.learning_unit.performancecriteria_set.first(), + # defaults={"feedback_assessment": feedback_assessment}, + # ) + + return Response({"success": True}) From a8b25ec216a6ce30d476a011d29cbce8adc9ca05 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Mon, 22 Jan 2024 17:59:13 +0100 Subject: [PATCH 13/57] wip: missing import --- server/vbv_lernwelt/self_evaluation_feedback/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/vbv_lernwelt/self_evaluation_feedback/urls.py b/server/vbv_lernwelt/self_evaluation_feedback/urls.py index cc705995..3f05b935 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/urls.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/urls.py @@ -3,6 +3,7 @@ from django.urls import path from vbv_lernwelt.self_evaluation_feedback.views import ( get_provider_self_evaluation_feedback, start_self_evaluation_feedback, + add_self_evaluation_feedback_assessment, submit_self_evaluation_feedback, ) From d2352d616665b2c5bcbbc6ec4f59dd0687923862 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Tue, 23 Jan 2024 11:19:20 +0100 Subject: [PATCH 14/57] feat: API for feedback provider to add feedback --- .../tests/test_api.py | 51 +++++++++++++++++++ .../self_evaluation_feedback/urls.py | 14 ++--- .../self_evaluation_feedback/views.py | 35 +++++++++---- 3 files changed, 83 insertions(+), 17 deletions(-) diff --git a/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py b/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py index e3fe6940..5cbbff04 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py @@ -211,6 +211,57 @@ class SelfEvaluationFeedbackAPI(APITestCase): CourseCompletionStatus.UNKNOWN.value, ) + def test_self_evaluation_feedback_assessment(self): + # GIVEN + learning_unit = create_learning_unit(course=self.course, circle=self.circle) + + performance_criteria_1 = create_performance_criteria_page( + course=self.course, + course_page=self.course_page, + circle=self.circle, + learning_unit=learning_unit, + ) + + completion = mark_course_completion( + page=performance_criteria_1, + user=self.member, + course_session=self.course_session, + completion_status=CourseCompletionStatus.SUCCESS.value, + ) + + self_evaluation_feedback = create_self_evaluation_feedback( + learning_unit=learning_unit, + feedback_requester_user=self.member, + feedback_provider_user=self.mentor, + ) + + self.client.force_login(self.mentor) + + # WHEN + response = self.client.put( + reverse( + "add_self_evaluation_feedback_assessment", + args=[self_evaluation_feedback.id], + ), + { + "course_completion_id": completion.id, + "feedback_assessment": CourseCompletionStatus.FAIL.value, + }, + ) + + # THEN + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data["success"], True) + + feedback = CourseCompletionFeedback.objects.get( + feedback=self_evaluation_feedback, + course_completion=completion, + ) + + self.assertEqual( + feedback.feedback_assessment, CourseCompletionStatus.FAIL.value + ) + def test_submit_self_evaluation_feedback(self): # GIVEN learning_unit = create_learning_unit(course=self.course, circle=self.circle) diff --git a/server/vbv_lernwelt/self_evaluation_feedback/urls.py b/server/vbv_lernwelt/self_evaluation_feedback/urls.py index 3f05b935..bf81e809 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/urls.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/urls.py @@ -1,26 +1,26 @@ from django.urls import path from vbv_lernwelt.self_evaluation_feedback.views import ( + add_provider_self_evaluation_feedback, get_provider_self_evaluation_feedback, + submit_provider_self_evaluation_feedback, start_self_evaluation_feedback, - add_self_evaluation_feedback_assessment, - submit_self_evaluation_feedback, ) urlpatterns = [ path( - "feedback/start", + "requester/feedback/start", start_self_evaluation_feedback, name="start_self_evaluation_feedback", ), path( - "feedback//submit", - submit_self_evaluation_feedback, + "provider/feedback//submit", + submit_provider_self_evaluation_feedback, name="submit_self_evaluation_feedback", ), path( - "feedback//add-feedback-assessment", - add_self_evaluation_feedback_assessment, + "provider/feedback//add-assessment", + add_provider_self_evaluation_feedback, name="add_self_evaluation_feedback_assessment", ), path( diff --git a/server/vbv_lernwelt/self_evaluation_feedback/views.py b/server/vbv_lernwelt/self_evaluation_feedback/views.py index e60c2cd2..3f9a352d 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/views.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/views.py @@ -5,9 +5,13 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from vbv_lernwelt.core.models import User +from vbv_lernwelt.course.models import CourseCompletion from vbv_lernwelt.learning_mentor.models import LearningMentor from vbv_lernwelt.learnpath.models import LearningUnit -from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback +from vbv_lernwelt.self_evaluation_feedback.models import ( + SelfEvaluationFeedback, + CourseCompletionFeedback, +) from vbv_lernwelt.self_evaluation_feedback.serializers import ( SelfEvaluationFeedbackSerializer, ) @@ -42,7 +46,7 @@ def start_self_evaluation_feedback(request): @api_view(["PUT"]) @permission_classes([IsAuthenticated]) -def submit_self_evaluation_feedback(request, feedback_id): +def submit_provider_self_evaluation_feedback(request, feedback_id): feedback = get_object_or_404( SelfEvaluationFeedback, id=feedback_id, feedback_provider_user=request.user ) @@ -65,19 +69,30 @@ def get_provider_self_evaluation_feedback(request, feedback_id): @api_view(["PUT"]) @permission_classes([IsAuthenticated]) -def add_self_evaluation_feedback_assessment(request, feedback_id): +def add_provider_self_evaluation_feedback(request, feedback_id): + feedback_assessment = request.data.get("feedback_assessment") + feedback = get_object_or_404( SelfEvaluationFeedback, id=feedback_id, feedback_provider_user=request.user ) - feedback_assessment = request.data.get("feedback_assessment") + course_completion = get_object_or_404( + CourseCompletion, + id=request.data.get("course_completion_id"), + user=feedback.feedback_requester_user, + ) - # TODO @livioso continue here + ( + course_completion_feedback, + created, + ) = CourseCompletionFeedback.objects.get_or_create( + feedback=feedback, + course_completion=course_completion, + defaults={"feedback_assessment": feedback_assessment}, + ) - # CourseCompletionFeedback.objects.get_or_create( - # feedback=feedback, - # course_completion=feedback.learning_unit.performancecriteria_set.first(), - # defaults={"feedback_assessment": feedback_assessment}, - # ) + if not created: + course_completion_feedback.feedback_assessment = feedback_assessment + course_completion_feedback.save() return Response({"success": True}) From 16cd16212ab9b88e9c9b299c950bedf1f53abc43 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Tue, 23 Jan 2024 11:45:55 +0100 Subject: [PATCH 15/57] feat: Feedback member API to get/put lu feedback --- .../tests/test_api.py | 119 +++++++++++++++++- .../self_evaluation_feedback/urls.py | 14 ++- .../self_evaluation_feedback/views.py | 26 +++- 3 files changed, 144 insertions(+), 15 deletions(-) diff --git a/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py b/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py index 5cbbff04..2ecd42b8 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py @@ -78,9 +78,22 @@ class SelfEvaluationFeedbackAPI(APITestCase): # WHEN response = self.client.post( - reverse("start_self_evaluation_feedback"), + reverse( + "start_self_evaluation_feedback", + args=[learning_unit.id], + ), + { + "feedback_provider_user_id": self.mentor.id, + }, + ) + + # make sure re-starting is a no-op + self.client.post( + reverse( + "start_self_evaluation_feedback", + args=[learning_unit.id], + ), { - "learning_unit_id": learning_unit.id, "feedback_provider_user_id": self.mentor.id, }, ) @@ -110,9 +123,8 @@ class SelfEvaluationFeedbackAPI(APITestCase): # WHEN response = self.client.post( - reverse("start_self_evaluation_feedback"), + reverse("start_self_evaluation_feedback", args=[learning_unit.id]), { - "learning_unit_id": learning_unit.id, "feedback_provider_user_id": not_a_mentor.id, }, ) @@ -120,7 +132,102 @@ class SelfEvaluationFeedbackAPI(APITestCase): # THEN self.assertEqual(response.status_code, 403) - def test_get_provider_self_evaluation_feedback(self): + def test_get_self_evaluation_feedback_as_requester(self): + """Tests endpoint of feedback REQUESTER""" + + # GIVEN + learning_unit = create_learning_unit(course=self.course, circle=self.circle) + + performance_criteria_1 = create_performance_criteria_page( + course=self.course, + course_page=self.course_page, + circle=self.circle, + learning_unit=learning_unit, + ) + + create_performance_criteria_page( + course=self.course, + course_page=self.course_page, + circle=self.circle, + learning_unit=learning_unit, + ) + + completion = mark_course_completion( + page=performance_criteria_1, + user=self.member, + course_session=self.course_session, + completion_status=CourseCompletionStatus.SUCCESS.value, + ) + + self_evaluation_feedback = create_self_evaluation_feedback( + learning_unit=learning_unit, + feedback_requester_user=self.member, + feedback_provider_user=self.mentor, + ) + + CourseCompletionFeedback.objects.create( + feedback=self_evaluation_feedback, + course_completion=completion, + feedback_assessment=CourseCompletionStatus.FAIL.value, + ) + + self.client.force_login(self.member) + + # WHEN + response = self.client.get( + reverse( + "get_self_evaluation_feedback_as_requester", + args=[learning_unit.id], + ) + ) + + # THEN + self.assertEqual(response.status_code, 200) + + feedback = response.data + self.assertEqual(feedback["learning_unit_id"], learning_unit.id) + self.assertEqual(feedback["feedback_submitted"], False) + self.assertEqual(feedback["circle_name"], self.circle.title) + + provider_user = feedback["feedback_provider_user"] + self.assertEqual(provider_user["id"], str(self.mentor.id)) # noqa + self.assertEqual(provider_user["first_name"], self.mentor.first_name) + self.assertEqual(provider_user["last_name"], self.mentor.last_name) + self.assertEqual(provider_user["avatar_url"], self.mentor.avatar_url) + + requester_user = feedback["feedback_requester_user"] + self.assertEqual(requester_user["id"], str(self.member.id)) # noqa + self.assertEqual(requester_user["first_name"], self.member.first_name) + self.assertEqual(requester_user["last_name"], self.member.last_name) + self.assertEqual(requester_user["avatar_url"], self.member.avatar_url) + + self.assertEqual(len(feedback["criteria"]), 2) + + first_criteria = feedback["criteria"][0] + self.assertEqual(first_criteria["course_completion_id"], completion.id) + self.assertEqual(first_criteria["title"], performance_criteria_1.title) + self.assertEqual( + first_criteria["self_assessment"], + CourseCompletionStatus.SUCCESS.value, + ) + self.assertEqual( + first_criteria["feedback_assessment"], CourseCompletionStatus.FAIL.value + ) + + second_criteria = feedback["criteria"][1] + self.assertEqual(second_criteria["course_completion_id"], None) + self.assertEqual(second_criteria["title"], performance_criteria_1.title) + self.assertEqual( + second_criteria["self_assessment"], CourseCompletionStatus.UNKNOWN.value + ) + self.assertEqual( + second_criteria["feedback_assessment"], + CourseCompletionStatus.UNKNOWN.value, + ) + + def test_get_self_evaluation_feedback_as_provider(self): + """Tests endpoint of feedback PROVIDER""" + # GIVEN learning_unit = create_learning_unit(course=self.course, circle=self.circle) @@ -162,7 +269,7 @@ class SelfEvaluationFeedbackAPI(APITestCase): # WHEN response = self.client.get( reverse( - "get_provider_self_evaluation_feedback", + "get_self_evaluation_feedback_as_provider", args=[self_evaluation_feedback.id], ) ) diff --git a/server/vbv_lernwelt/self_evaluation_feedback/urls.py b/server/vbv_lernwelt/self_evaluation_feedback/urls.py index bf81e809..d6d14f76 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/urls.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/urls.py @@ -2,17 +2,23 @@ from django.urls import path from vbv_lernwelt.self_evaluation_feedback.views import ( add_provider_self_evaluation_feedback, - get_provider_self_evaluation_feedback, + get_self_evaluation_feedback_as_provider, + get_self_evaluation_feedback_as_requester, submit_provider_self_evaluation_feedback, start_self_evaluation_feedback, ) urlpatterns = [ path( - "requester/feedback/start", + "requester//feedback/start", start_self_evaluation_feedback, name="start_self_evaluation_feedback", ), + path( + "requester//feedback", + get_self_evaluation_feedback_as_requester, + name="get_self_evaluation_feedback_as_requester", + ), path( "provider/feedback//submit", submit_provider_self_evaluation_feedback, @@ -25,7 +31,7 @@ urlpatterns = [ ), path( "provider/feedback/", - get_provider_self_evaluation_feedback, - name="get_provider_self_evaluation_feedback", + get_self_evaluation_feedback_as_provider, + name="get_self_evaluation_feedback_as_provider", ), ] diff --git a/server/vbv_lernwelt/self_evaluation_feedback/views.py b/server/vbv_lernwelt/self_evaluation_feedback/views.py index 3f9a352d..25f78e32 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/views.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/views.py @@ -19,8 +19,7 @@ from vbv_lernwelt.self_evaluation_feedback.serializers import ( @api_view(["POST"]) @permission_classes([IsAuthenticated]) -def start_self_evaluation_feedback(request): - learning_unit_id = request.data.get("learning_unit_id") +def start_self_evaluation_feedback(request, learning_unit_id): feedback_provider_user_id = request.data.get("feedback_provider_user_id") learning_unit = get_object_or_404(LearningUnit, id=learning_unit_id) @@ -33,13 +32,16 @@ def start_self_evaluation_feedback(request): ).exists(): raise PermissionDenied() - SelfEvaluationFeedback.objects.create( + # calling start multiple times shall be a no-op + _, created = SelfEvaluationFeedback.objects.get_or_create( feedback_requester_user=request.user, feedback_provider_user=feedback_provider_user, learning_unit=learning_unit, ) - # TODO: Create notification for feedback_provider_user + if created: + # TODO: Create notification for feedback_provider_user + ... return Response({"success": True}) @@ -59,7 +61,7 @@ def submit_provider_self_evaluation_feedback(request, feedback_id): @api_view(["GET"]) @permission_classes([IsAuthenticated]) -def get_provider_self_evaluation_feedback(request, feedback_id): +def get_self_evaluation_feedback_as_provider(request, feedback_id): feedback = get_object_or_404( SelfEvaluationFeedback, id=feedback_id, feedback_provider_user=request.user ) @@ -67,6 +69,20 @@ def get_provider_self_evaluation_feedback(request, feedback_id): return Response(SelfEvaluationFeedbackSerializer(feedback).data) +@api_view(["GET"]) +@permission_classes([IsAuthenticated]) +def get_self_evaluation_feedback_as_requester(request, learning_unit_id): + learning_unit = get_object_or_404(LearningUnit, id=learning_unit_id) + + feedback = get_object_or_404( + SelfEvaluationFeedback, + learning_unit=learning_unit, + feedback_requester_user=request.user, + ) + + return Response(SelfEvaluationFeedbackSerializer(feedback).data) + + @api_view(["PUT"]) @permission_classes([IsAuthenticated]) def add_provider_self_evaluation_feedback(request, feedback_id): From 3a519e2220539e7c31e1ae14e8a3d5e4b0c6d725 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Tue, 23 Jan 2024 11:45:55 +0100 Subject: [PATCH 16/57] feat: Feedback member API to get/put lu feedback --- server/vbv_lernwelt/self_evaluation_feedback/urls.py | 2 +- server/vbv_lernwelt/self_evaluation_feedback/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/vbv_lernwelt/self_evaluation_feedback/urls.py b/server/vbv_lernwelt/self_evaluation_feedback/urls.py index d6d14f76..652d98a0 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/urls.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/urls.py @@ -4,8 +4,8 @@ from vbv_lernwelt.self_evaluation_feedback.views import ( add_provider_self_evaluation_feedback, get_self_evaluation_feedback_as_provider, get_self_evaluation_feedback_as_requester, - submit_provider_self_evaluation_feedback, start_self_evaluation_feedback, + submit_provider_self_evaluation_feedback, ) urlpatterns = [ diff --git a/server/vbv_lernwelt/self_evaluation_feedback/views.py b/server/vbv_lernwelt/self_evaluation_feedback/views.py index 25f78e32..ac75e44f 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/views.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/views.py @@ -9,8 +9,8 @@ from vbv_lernwelt.course.models import CourseCompletion from vbv_lernwelt.learning_mentor.models import LearningMentor from vbv_lernwelt.learnpath.models import LearningUnit from vbv_lernwelt.self_evaluation_feedback.models import ( - SelfEvaluationFeedback, CourseCompletionFeedback, + SelfEvaluationFeedback, ) from vbv_lernwelt.self_evaluation_feedback.serializers import ( SelfEvaluationFeedbackSerializer, From 412172515f85449ed589dfea2b642c67b2c9dea7 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Tue, 23 Jan 2024 22:34:04 +0100 Subject: [PATCH 17/57] feat: wraps self evaluation feedback request member side --- client/src/composables.ts | 39 +++- .../selfEvaluationPage/SelfEvaluation.vue | 2 +- .../SelfEvaluationRequestFeedback.vue | 170 ++++++++++++++++++ .../SelfEvaluationSubmit.vue | 101 ----------- .../self_evaluation_feedback/admin.py | 48 ++++- 5 files changed, 255 insertions(+), 105 deletions(-) create mode 100644 client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationRequestFeedback.vue delete mode 100644 client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue diff --git a/client/src/composables.ts b/client/src/composables.ts index f9f049e7..094f60c2 100644 --- a/client/src/composables.ts +++ b/client/src/composables.ts @@ -28,8 +28,8 @@ import type { import { useQuery } from "@urql/vue"; import orderBy from "lodash/orderBy"; import log from "loglevel"; -import type { ComputedRef } from "vue"; -import { computed, onMounted, ref, watchEffect } from "vue"; +import type { ComputedRef, Ref } from "vue"; +import { computed, onMounted, ref, toValue, watchEffect } from "vue"; export function useCurrentCourseSession() { /** @@ -469,17 +469,52 @@ export function useFileUpload() { export function useLearningMentors() { const learningMentors = ref([]); const currentCourseSessionId = useCurrentCourseSession().value.id; + const loading = ref(false); const fetchMentors = async () => { + loading.value = true; const { data } = await useCSRFFetch( `/api/mentor/${currentCourseSessionId}/mentors` ).json(); learningMentors.value = data.value; + loading.value = false; }; onMounted(fetchMentors); return { learningMentors, + loading, + }; +} + +export function useSelfEvaluationFeedback(learningUnitId: Ref | string) { + const feedback = ref({}); + const loading = ref(false); + const exists = ref(false); + + const url = `/api/self-evaluation-feedback/requester/${toValue( + learningUnitId + )}/feedback`; + + const fetchSelfEvaluationFeedback = async () => { + loading.value = true; + const { data, statusCode } = await useCSRFFetch(url).json(); + loading.value = false; + + if (statusCode.value === 404) { + exists.value = false; + return; + } else { + exists.value = true; + feedback.value = data.value; + } + }; + onMounted(fetchSelfEvaluationFeedback); + + return { + feedback, + exists, + loading, }; } diff --git a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluation.vue b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluation.vue index 42cd010f..1efbe2f7 100644 --- a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluation.vue +++ b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluation.vue @@ -11,7 +11,7 @@ import { useRouteQuery } from "@vueuse/router"; import { computed, onUnmounted } from "vue"; import { getPreviousRoute } from "@/router/history"; import { getCompetenceNaviUrl } from "@/utils/utils"; -import SelfEvaluationSubmit from "@/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue"; +import SelfEvaluationSubmit from "@/pages/learningPath/selfEvaluationPage/SelfEvaluationRequestFeedback.vue"; log.debug("LearningContent.vue setup"); diff --git a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationRequestFeedback.vue b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationRequestFeedback.vue new file mode 100644 index 00000000..e658e5ca --- /dev/null +++ b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationRequestFeedback.vue @@ -0,0 +1,170 @@ + + + + + diff --git a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue deleted file mode 100644 index 4e3d7664..00000000 --- a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationSubmit.vue +++ /dev/null @@ -1,101 +0,0 @@ - - - - - diff --git a/server/vbv_lernwelt/self_evaluation_feedback/admin.py b/server/vbv_lernwelt/self_evaluation_feedback/admin.py index 8c38f3f3..acabf04e 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/admin.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/admin.py @@ -1,3 +1,49 @@ from django.contrib import admin -# Register your models here. +from vbv_lernwelt.self_evaluation_feedback.models import ( + CourseCompletionFeedback, + SelfEvaluationFeedback, +) + + +@admin.register(SelfEvaluationFeedback) +class CourseSessionAdmin(admin.ModelAdmin): + list_display = ( + "id", + "feedback_submitted", + "feedback_requester_user", + "feedback_provider_user", + "learning_unit", + ) + list_filter = ( + "feedback_submitted", + "feedback_requester_user", + "feedback_provider_user", + "learning_unit", + ) + search_fields = ( + "feedback_submitted", + "feedback_requester_user", + "feedback_provider_user", + "learning_unit", + ) + + +@admin.register(CourseCompletionFeedback) +class CourseSessionAdmin(admin.ModelAdmin): + list_display = ( + "id", + "feedback", + "course_completion", + "feedback_assessment", + ) + list_filter = ( + "feedback", + "course_completion", + "feedback_assessment", + ) + search_fields = ( + "feedback", + "course_completion", + "feedback_assessment", + ) From 40127ed92e0233fcd0a26c338d27429eebf49cb3 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Wed, 24 Jan 2024 11:23:09 +0100 Subject: [PATCH 18/57] fix: missing migrations --- .../migrations/0012_auto_20240124_1004.py | 23 +++++++++++++++++ .../migrations/0006_auto_20240124_1004.py | 25 +++++++++++++++++++ ...ecompletionfeedback_feedback_assessment.py | 20 +++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 server/vbv_lernwelt/assignment/migrations/0012_auto_20240124_1004.py create mode 100644 server/vbv_lernwelt/course_session/migrations/0006_auto_20240124_1004.py create mode 100644 server/vbv_lernwelt/self_evaluation_feedback/migrations/0003_rename_provider_evaluation_feedback_coursecompletionfeedback_feedback_assessment.py diff --git a/server/vbv_lernwelt/assignment/migrations/0012_auto_20240124_1004.py b/server/vbv_lernwelt/assignment/migrations/0012_auto_20240124_1004.py new file mode 100644 index 00000000..3a5a91be --- /dev/null +++ b/server/vbv_lernwelt/assignment/migrations/0012_auto_20240124_1004.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.20 on 2024-01-24 09:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assignment', '0011_assignment_solution_sample'), + ] + + operations = [ + migrations.AlterField( + model_name='assignment', + name='assignment_type', + field=models.CharField(choices=[('PRAXIS_ASSIGNMENT', 'PRAXIS_ASSIGNMENT'), ('CASEWORK', 'CASEWORK'), ('PREP_ASSIGNMENT', 'PREP_ASSIGNMENT'), ('REFLECTION', 'REFLECTION'), ('CONDITION_ACCEPTANCE', 'CONDITION_ACCEPTANCE'), ('EDONIQ_TEST', 'EDONIQ_TEST')], default='CASEWORK', max_length=50), + ), + migrations.AlterField( + model_name='assignment', + name='needs_expert_evaluation', + field=models.BooleanField(default=False, help_text='Muss der Auftrag durch eine/n Experten/in oder eine Lernbegleitung beurteilt werden?'), + ), + ] diff --git a/server/vbv_lernwelt/course_session/migrations/0006_auto_20240124_1004.py b/server/vbv_lernwelt/course_session/migrations/0006_auto_20240124_1004.py new file mode 100644 index 00000000..2921f1da --- /dev/null +++ b/server/vbv_lernwelt/course_session/migrations/0006_auto_20240124_1004.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.20 on 2024-01-24 09:04 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('duedate', '0008_auto_20231108_0747'), + ('course_session', '0005_auto_20230825_1723'), + ] + + operations = [ + migrations.AlterField( + model_name='coursesessionassignment', + name='evaluation_deadline', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='assignment_evaluation_deadline', to='duedate.duedate'), + ), + migrations.AlterField( + model_name='coursesessionassignment', + name='submission_deadline', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='assignment_submission_deadline', to='duedate.duedate'), + ), + ] diff --git a/server/vbv_lernwelt/self_evaluation_feedback/migrations/0003_rename_provider_evaluation_feedback_coursecompletionfeedback_feedback_assessment.py b/server/vbv_lernwelt/self_evaluation_feedback/migrations/0003_rename_provider_evaluation_feedback_coursecompletionfeedback_feedback_assessment.py new file mode 100644 index 00000000..1c803c26 --- /dev/null +++ b/server/vbv_lernwelt/self_evaluation_feedback/migrations/0003_rename_provider_evaluation_feedback_coursecompletionfeedback_feedback_assessment.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.20 on 2024-01-23 15:46 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ( + "self_evaluation_feedback", + "0002_rename_requester_evaluation_coursecompletionfeedback_course_completion", + ), + ] + + operations = [ + migrations.RenameField( + model_name="coursecompletionfeedback", + old_name="provider_evaluation_feedback", + new_name="feedback_assessment", + ), + ] From bc78fe25333fcd3b5617bef29a59716668eb9e59 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Thu, 25 Jan 2024 11:43:14 +0100 Subject: [PATCH 19/57] fix: missing migration dependency --- .../vbv_lernwelt/duedate/migrations/0005_auto_20230925_1648.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/vbv_lernwelt/duedate/migrations/0005_auto_20230925_1648.py b/server/vbv_lernwelt/duedate/migrations/0005_auto_20230925_1648.py index 1f3192a8..9dcd192b 100644 --- a/server/vbv_lernwelt/duedate/migrations/0005_auto_20230925_1648.py +++ b/server/vbv_lernwelt/duedate/migrations/0005_auto_20230925_1648.py @@ -45,6 +45,7 @@ class Migration(migrations.Migration): dependencies = [ ("duedate", "0004_alter_duedate_start"), ("learnpath", "0008_add_edoniq_sequence_id"), + ("course_session", "0005_auto_20230825_1723"), ] operations = [ From 654ccb0d47fa44a2dec0e04edf4abb4828c33a87 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Thu, 25 Jan 2024 13:39:13 +0100 Subject: [PATCH 20/57] feat: feedback received screen --- .../FeedbackReceived.vue | 79 +++++++ .../FeedbackRequested.vue | 41 ++++ .../FeedbackRequestedInformationPanel.vue | 26 +++ client/src/composables.ts | 35 +-- .../selfEvaluationPage/SelfEvaluation.vue | 21 +- .../SelfEvaluationRequestFeedback.vue | 210 ++++++++---------- client/src/services/selfEvaluationFeedback.ts | 60 +++++ client/src/types.ts | 13 ++ .../migrations/0012_auto_20240124_1004.py | 29 ++- .../vbv_lernwelt/core/create_default_users.py | 6 +- .../migrations/0006_auto_20240124_1004.py | 31 ++- 11 files changed, 374 insertions(+), 177 deletions(-) create mode 100644 client/src/components/selfEvaluationFeedback/FeedbackReceived.vue create mode 100644 client/src/components/selfEvaluationFeedback/FeedbackRequested.vue create mode 100644 client/src/components/selfEvaluationFeedback/FeedbackRequestedInformationPanel.vue create mode 100644 client/src/services/selfEvaluationFeedback.ts diff --git a/client/src/components/selfEvaluationFeedback/FeedbackReceived.vue b/client/src/components/selfEvaluationFeedback/FeedbackReceived.vue new file mode 100644 index 00000000..7038efea --- /dev/null +++ b/client/src/components/selfEvaluationFeedback/FeedbackReceived.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/client/src/components/selfEvaluationFeedback/FeedbackRequested.vue b/client/src/components/selfEvaluationFeedback/FeedbackRequested.vue new file mode 100644 index 00000000..07615ef5 --- /dev/null +++ b/client/src/components/selfEvaluationFeedback/FeedbackRequested.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/client/src/components/selfEvaluationFeedback/FeedbackRequestedInformationPanel.vue b/client/src/components/selfEvaluationFeedback/FeedbackRequestedInformationPanel.vue new file mode 100644 index 00000000..9baf746d --- /dev/null +++ b/client/src/components/selfEvaluationFeedback/FeedbackRequestedInformationPanel.vue @@ -0,0 +1,26 @@ + + + diff --git a/client/src/composables.ts b/client/src/composables.ts index 094f60c2..570ad2d1 100644 --- a/client/src/composables.ts +++ b/client/src/composables.ts @@ -28,8 +28,8 @@ import type { import { useQuery } from "@urql/vue"; import orderBy from "lodash/orderBy"; import log from "loglevel"; -import type { ComputedRef, Ref } from "vue"; -import { computed, onMounted, ref, toValue, watchEffect } from "vue"; +import type { ComputedRef } from "vue"; +import { computed, onMounted, ref, watchEffect } from "vue"; export function useCurrentCourseSession() { /** @@ -487,34 +487,3 @@ export function useLearningMentors() { loading, }; } - -export function useSelfEvaluationFeedback(learningUnitId: Ref | string) { - const feedback = ref({}); - const loading = ref(false); - const exists = ref(false); - - const url = `/api/self-evaluation-feedback/requester/${toValue( - learningUnitId - )}/feedback`; - - const fetchSelfEvaluationFeedback = async () => { - loading.value = true; - const { data, statusCode } = await useCSRFFetch(url).json(); - loading.value = false; - - if (statusCode.value === 404) { - exists.value = false; - return; - } else { - exists.value = true; - feedback.value = data.value; - } - }; - onMounted(fetchSelfEvaluationFeedback); - - return { - feedback, - exists, - loading, - }; -} diff --git a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluation.vue b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluation.vue index 1efbe2f7..d9d61d04 100644 --- a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluation.vue +++ b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluation.vue @@ -15,24 +15,16 @@ import SelfEvaluationSubmit from "@/pages/learningPath/selfEvaluationPage/SelfEv log.debug("LearningContent.vue setup"); -const circleStore = useCircleStore(); -const courseSession = useCurrentCourseSession(); -const courseCompletionData = useCourseDataWithCompletion(); - -const questionIndex = useRouteQuery("step", "0", { transform: Number, mode: "push" }); -const previousRoute = getPreviousRoute(); - const props = defineProps<{ learningUnit: LearningUnit; circle: CircleType; }>(); -const learningUnitHasFeedbackPage = computed( - () => props.learningUnit?.feedback_user !== "NO_FEEDBACK" -); +const circleStore = useCircleStore(); +const courseSession = useCurrentCourseSession(); +const courseCompletionData = useCourseDataWithCompletion(); const questions = computed(() => props.learningUnit?.performance_criteria ?? []); - const numPages = computed(() => { if (learningUnitHasFeedbackPage.value) { return questions.value.length + 1; @@ -41,6 +33,13 @@ const numPages = computed(() => { } }); +const questionIndex = useRouteQuery("step", "0", { transform: Number, mode: "push" }); +const previousRoute = getPreviousRoute(); + +const learningUnitHasFeedbackPage = computed( + () => props.learningUnit?.feedback_user !== "NO_FEEDBACK" +); + const currentQuestion = computed(() => questions.value[questionIndex.value]); const showPreviousButton = computed(() => questionIndex.value != 0); const showNextButton = computed( diff --git a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationRequestFeedback.vue b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationRequestFeedback.vue index e658e5ca..d2f174f9 100644 --- a/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationRequestFeedback.vue +++ b/client/src/pages/learningPath/selfEvaluationPage/SelfEvaluationRequestFeedback.vue @@ -1,25 +1,29 @@ diff --git a/client/src/services/selfEvaluationFeedback.ts b/client/src/services/selfEvaluationFeedback.ts new file mode 100644 index 00000000..c80f6f0a --- /dev/null +++ b/client/src/services/selfEvaluationFeedback.ts @@ -0,0 +1,60 @@ +import { useCSRFFetch } from "@/fetchHelpers"; +import type { User } from "@/types"; +import type { Ref } from "vue"; +import { computed, onMounted, ref, toValue } from "vue"; + +export interface FeedbackRequest { + learning_unit_id: number; + circle_name: string; + // submitted => provider submitted (released) his/her feedback + feedback_submitted: boolean; + feedback_requester_user: User; + feedback_provider_user: User; + criteria: Criterion[]; +} + +interface Criterion { + course_completion_id: string; + title: string; + self_assessment: "FAIL" | "SUCCESS" | "UNKNOWN"; + feedback_assessment: "FAIL" | "SUCCESS" | "UNKNOWN"; +} + +export function useSelfEvaluationFeedback(learningUnitId: Ref | string) { + const feedback = ref(); + const loading = ref(false); + const error = ref(); + + const url = computed( + () => `/api/self-evaluation-feedback/requester/${toValue(learningUnitId)}/feedback` + ); + + const fetchSelfEvaluationFeedback = async () => { + feedback.value = undefined; + error.value = undefined; + + loading.value = true; + const { data, statusCode, error: _error } = await useCSRFFetch(url.value).json(); + loading.value = false; + + if (_error.value) { + error.value = _error; + feedback.value = undefined; + return; + } + + if (statusCode.value === 404) { + feedback.value = undefined; + } else { + feedback.value = data.value; + } + }; + + onMounted(fetchSelfEvaluationFeedback); + + return { + feedback, + error, + loading, + }; +} diff --git a/client/src/types.ts b/client/src/types.ts index 19146357..88c08e93 100644 --- a/client/src/types.ts +++ b/client/src/types.ts @@ -589,3 +589,16 @@ export interface FeedbackData { }; feedbackType: FeedbackType; } + +export type User = { + id: string; + first_name: string; + last_name: string; + email: string; + username: string; + avatar_url: string; + organisation: string | null; + is_superuser: boolean; + course_session_experts: any[]; + language: string; +}; diff --git a/server/vbv_lernwelt/assignment/migrations/0012_auto_20240124_1004.py b/server/vbv_lernwelt/assignment/migrations/0012_auto_20240124_1004.py index 3a5a91be..0489bb0b 100644 --- a/server/vbv_lernwelt/assignment/migrations/0012_auto_20240124_1004.py +++ b/server/vbv_lernwelt/assignment/migrations/0012_auto_20240124_1004.py @@ -4,20 +4,33 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('assignment', '0011_assignment_solution_sample'), + ("assignment", "0011_assignment_solution_sample"), ] operations = [ migrations.AlterField( - model_name='assignment', - name='assignment_type', - field=models.CharField(choices=[('PRAXIS_ASSIGNMENT', 'PRAXIS_ASSIGNMENT'), ('CASEWORK', 'CASEWORK'), ('PREP_ASSIGNMENT', 'PREP_ASSIGNMENT'), ('REFLECTION', 'REFLECTION'), ('CONDITION_ACCEPTANCE', 'CONDITION_ACCEPTANCE'), ('EDONIQ_TEST', 'EDONIQ_TEST')], default='CASEWORK', max_length=50), + model_name="assignment", + name="assignment_type", + field=models.CharField( + choices=[ + ("PRAXIS_ASSIGNMENT", "PRAXIS_ASSIGNMENT"), + ("CASEWORK", "CASEWORK"), + ("PREP_ASSIGNMENT", "PREP_ASSIGNMENT"), + ("REFLECTION", "REFLECTION"), + ("CONDITION_ACCEPTANCE", "CONDITION_ACCEPTANCE"), + ("EDONIQ_TEST", "EDONIQ_TEST"), + ], + default="CASEWORK", + max_length=50, + ), ), migrations.AlterField( - model_name='assignment', - name='needs_expert_evaluation', - field=models.BooleanField(default=False, help_text='Muss der Auftrag durch eine/n Experten/in oder eine Lernbegleitung beurteilt werden?'), + model_name="assignment", + name="needs_expert_evaluation", + field=models.BooleanField( + default=False, + help_text="Muss der Auftrag durch eine/n Experten/in oder eine Lernbegleitung beurteilt werden?", + ), ), ] diff --git a/server/vbv_lernwelt/core/create_default_users.py b/server/vbv_lernwelt/core/create_default_users.py index aef8516d..b7d61b37 100644 --- a/server/vbv_lernwelt/core/create_default_users.py +++ b/server/vbv_lernwelt/core/create_default_users.py @@ -346,11 +346,11 @@ def create_default_users(default_password="test"): _create_user( _id=TEST_MENTOR1_USER_ID, email="test-mentor1@example.com", - first_name="[Mentor]", - last_name="Mentor", + first_name="Micheala", + last_name="Weber-Mentor", password=default_password, language="de", - avatar_url="", + avatar_url="/static/avatars/uk1.patrizia.huggel.jpg", ) diff --git a/server/vbv_lernwelt/course_session/migrations/0006_auto_20240124_1004.py b/server/vbv_lernwelt/course_session/migrations/0006_auto_20240124_1004.py index 2921f1da..9aab3304 100644 --- a/server/vbv_lernwelt/course_session/migrations/0006_auto_20240124_1004.py +++ b/server/vbv_lernwelt/course_session/migrations/0006_auto_20240124_1004.py @@ -1,25 +1,36 @@ # Generated by Django 3.2.20 on 2024-01-24 09:04 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('duedate', '0008_auto_20231108_0747'), - ('course_session', '0005_auto_20230825_1723'), + ("duedate", "0008_auto_20231108_0747"), + ("course_session", "0005_auto_20230825_1723"), ] operations = [ migrations.AlterField( - model_name='coursesessionassignment', - name='evaluation_deadline', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='assignment_evaluation_deadline', to='duedate.duedate'), + model_name="coursesessionassignment", + name="evaluation_deadline", + field=models.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="assignment_evaluation_deadline", + to="duedate.duedate", + ), ), migrations.AlterField( - model_name='coursesessionassignment', - name='submission_deadline', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='assignment_submission_deadline', to='duedate.duedate'), + model_name="coursesessionassignment", + name="submission_deadline", + field=models.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="assignment_submission_deadline", + to="duedate.duedate", + ), ), ] From 864a00107efae56460cc6625139a0ded6f243475 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Thu, 25 Jan 2024 18:32:46 +0100 Subject: [PATCH 21/57] feat: mentor cockpit self evaluation feedback --- .../SelfAssignmentFeedbackAssignmentItem.vue | 23 ++++ .../cockpitPage/mentor/MentorOverview.vue | 11 ++ .../content/praxis_assignment.py | 36 +++--- .../content/self_evaluation_feedback.py | 99 +++++++++++++++ .../vbv_lernwelt/learning_mentor/entities.py | 16 ++- .../learning_mentor/serializers.py | 8 +- .../learning_mentor/tests/test_assignments.py | 10 +- .../learning_mentor/tests/test_mentor.py | 114 ++++++++++++++++-- server/vbv_lernwelt/learning_mentor/views.py | 36 ++++-- 9 files changed, 301 insertions(+), 52 deletions(-) create mode 100644 client/src/components/cockpit/mentor/SelfAssignmentFeedbackAssignmentItem.vue create mode 100644 server/vbv_lernwelt/learning_mentor/content/self_evaluation_feedback.py diff --git a/client/src/components/cockpit/mentor/SelfAssignmentFeedbackAssignmentItem.vue b/client/src/components/cockpit/mentor/SelfAssignmentFeedbackAssignmentItem.vue new file mode 100644 index 00000000..3518c42d --- /dev/null +++ b/client/src/components/cockpit/mentor/SelfAssignmentFeedbackAssignmentItem.vue @@ -0,0 +1,23 @@ + + + diff --git a/client/src/pages/cockpit/cockpitPage/mentor/MentorOverview.vue b/client/src/pages/cockpit/cockpitPage/mentor/MentorOverview.vue index 67b0e047..e3f96aa5 100644 --- a/client/src/pages/cockpit/cockpitPage/mentor/MentorOverview.vue +++ b/client/src/pages/cockpit/cockpitPage/mentor/MentorOverview.vue @@ -6,6 +6,7 @@ import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue"; import { computed, type Ref, ref } from "vue"; import PraxisAssignmentItem from "@/components/cockpit/mentor/PraxisAssignmentItem.vue"; import { useTranslation } from "i18next-vue"; +import SelfAssignmentFeedbackAssignmentItem from "@/components/cockpit/mentor/SelfAssignmentFeedbackAssignmentItem.vue"; const { t } = useTranslation(); const courseSession = useCurrentCourseSession(); @@ -80,6 +81,16 @@ const filteredAssignments: Ref = computed(() => { }" :task-title="item.title" /> +
diff --git a/server/vbv_lernwelt/learning_mentor/content/praxis_assignment.py b/server/vbv_lernwelt/learning_mentor/content/praxis_assignment.py index 0e8298a8..b6c07490 100644 --- a/server/vbv_lernwelt/learning_mentor/content/praxis_assignment.py +++ b/server/vbv_lernwelt/learning_mentor/content/praxis_assignment.py @@ -2,17 +2,18 @@ from typing import List, Set, Tuple from vbv_lernwelt.assignment.models import ( Assignment, - AssignmentCompletion, AssignmentCompletionStatus, AssignmentType, + AssignmentCompletion, ) from vbv_lernwelt.core.models import User from vbv_lernwelt.course.models import CourseSession from vbv_lernwelt.course_session.models import CourseSessionAssignment from vbv_lernwelt.learning_mentor.entities import ( - CompletionStatus, - PraxisAssignmentCompletion, - PraxisAssignmentStatus, + MentorCompletionStatus, + MentorAssignmentCompletion, + MentorAssignmentStatus, + MentorAssignmentStatusType, ) @@ -21,7 +22,7 @@ def get_assignment_completions( assignment: Assignment, participants: List[User], evaluation_user: User, -) -> List[PraxisAssignmentCompletion]: +) -> List[MentorAssignmentCompletion]: evaluation_results = AssignmentCompletion.objects.filter( assignment_user__in=participants, course_session=course_session, @@ -34,14 +35,14 @@ def get_assignment_completions( completion_status = result["completion_status"] if completion_status == AssignmentCompletionStatus.EVALUATION_SUBMITTED.value: - status = CompletionStatus.EVALUATED + status = MentorCompletionStatus.EVALUATED elif completion_status in [ AssignmentCompletionStatus.SUBMITTED.value, AssignmentCompletionStatus.EVALUATION_IN_PROGRESS.value, ]: - status = CompletionStatus.SUBMITTED + status = MentorCompletionStatus.SUBMITTED else: - status = CompletionStatus.UNKNOWN + status = MentorCompletionStatus.UNKNOWN user_status_map[result["assignment_user"]] = ( status, @@ -49,25 +50,25 @@ def get_assignment_completions( ) status_priority = { - CompletionStatus.SUBMITTED: 1, - CompletionStatus.EVALUATED: 2, - CompletionStatus.UNKNOWN: 3, + MentorCompletionStatus.SUBMITTED: 1, + MentorCompletionStatus.EVALUATED: 2, + MentorCompletionStatus.UNKNOWN: 3, } sorted_participants = sorted( participants, key=lambda u: ( status_priority.get( - user_status_map.get(u.id, (CompletionStatus.UNKNOWN, ""))[0] + user_status_map.get(u.id, (MentorCompletionStatus.UNKNOWN, ""))[0] ), user_status_map.get(u.id, ("", u.last_name))[1], ), ) return [ - PraxisAssignmentCompletion( + MentorAssignmentCompletion( status=user_status_map.get( - user.id, (CompletionStatus.UNKNOWN, user.last_name) + user.id, (MentorCompletionStatus.UNKNOWN, user.last_name) )[0], user_id=user.id, last_name=user.last_name, @@ -78,7 +79,7 @@ def get_assignment_completions( def get_praxis_assignments( course_session: CourseSession, participants: List[User], evaluation_user: User -) -> Tuple[List[PraxisAssignmentStatus], Set[int]]: +) -> Tuple[List[MentorAssignmentStatus], Set[int]]: records = [] circle_ids = set() @@ -104,19 +105,20 @@ def get_praxis_assignments( [ completion for completion in completions - if completion.status == CompletionStatus.SUBMITTED + if completion.status == MentorCompletionStatus.SUBMITTED ] ) circle_id = learning_content.get_circle().id records.append( - PraxisAssignmentStatus( + MentorAssignmentStatus( id=course_session_assignment.id, title=learning_content.content_assignment.title, circle_id=circle_id, pending_evaluations=submitted_count, completions=completions, + type=MentorAssignmentStatusType.PRAXIS_ASSIGNMENT, ) ) diff --git a/server/vbv_lernwelt/learning_mentor/content/self_evaluation_feedback.py b/server/vbv_lernwelt/learning_mentor/content/self_evaluation_feedback.py new file mode 100644 index 00000000..e2eb2409 --- /dev/null +++ b/server/vbv_lernwelt/learning_mentor/content/self_evaluation_feedback.py @@ -0,0 +1,99 @@ +from typing import List, Set, Tuple + +from django.db.models import Prefetch + +from vbv_lernwelt.core.models import User +from vbv_lernwelt.learning_mentor.entities import ( + MentorCompletionStatus, + MentorAssignmentStatus, + MentorAssignmentCompletion, + MentorAssignmentStatusType, +) +from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback + + +def create_blank_completions_non_requesters( + completions: List[MentorAssignmentCompletion], + participants: List[User], +) -> List[MentorAssignmentCompletion]: + non_requester_completions = [] + + participants_ids = set([str(p.id) for p in participants]) + completion_seen_user_ids = set([str(c.user_id) for c in completions]) + + user_by_id = {str(p.id): p for p in participants} + for non_requester_user_id in participants_ids - completion_seen_user_ids: + non_requester_user = user_by_id[non_requester_user_id] + + non_requester_completions.append( + MentorAssignmentCompletion( + status=MentorCompletionStatus.UNKNOWN, + user_id=non_requester_user.id, + last_name=non_requester_user.last_name, + ) + ) + + return non_requester_completions + + +def get_self_feedback_evaluation( + participants: List[User], evaluation_user: User +) -> Tuple[List[MentorAssignmentStatus], Set[int]]: + records: List[MentorAssignmentStatus] = [] + circle_ids: Set[int] = set() + + if not participants: + return records, circle_ids + + feedbacks = SelfEvaluationFeedback.objects.prefetch_related( + Prefetch("learning_unit") + ).filter( + feedback_requester_user__in=participants, + feedback_provider_user=evaluation_user, + ) + + feedback_by_learning_unit = {} + + for feedback in feedbacks: + feedback_by_learning_unit.setdefault(feedback.learning_unit, []).append( + feedback + ) + + for learning_unit, feedbacks in feedback_by_learning_unit.items(): + circle_id = learning_unit.get_circle().id + circle_ids.add(circle_id) + + pending_evaluations = len([f for f in feedbacks if not f.feedback_submitted]) + + completions = [ + MentorAssignmentCompletion( + # feedback_submitted as seen from the perspective of the evaluation user (feedback provider) + # means that the feedback has been evaluated by the feedback provider, hence the status is EVALUATED + status=MentorCompletionStatus.EVALUATED + if f.feedback_submitted + else MentorCompletionStatus.SUBMITTED, + user_id=f.feedback_requester_user.id, + last_name=f.feedback_requester_user.last_name, + ) + for f in feedbacks + ] + + # requesting feedback is optional, so we need to add blank completions + # for those mentees who did not request a feedback + completions += create_blank_completions_non_requesters( + completions=completions, + participants=participants, + ) + + records.append( + MentorAssignmentStatus( + id=learning_unit.id, + title=learning_unit.title, + circle_id=circle_id, + pending_evaluations=pending_evaluations, + completions=completions, + type=MentorAssignmentStatusType.SELF_EVALUATION_FEEDBACK, + ) + ) + + return records, circle_ids diff --git a/server/vbv_lernwelt/learning_mentor/entities.py b/server/vbv_lernwelt/learning_mentor/entities.py index 6dabcc26..c5b1d0d8 100644 --- a/server/vbv_lernwelt/learning_mentor/entities.py +++ b/server/vbv_lernwelt/learning_mentor/entities.py @@ -3,23 +3,29 @@ from enum import Enum from typing import List -class CompletionStatus(str, Enum): +class MentorCompletionStatus(str, Enum): UNKNOWN = "UNKNOWN" SUBMITTED = "SUBMITTED" EVALUATED = "EVALUATED" +class MentorAssignmentStatusType(str, Enum): + PRAXIS_ASSIGNMENT = "praxis_assignment" + SELF_EVALUATION_FEEDBACK = "self_evaluation_feedback" + + @dataclass -class PraxisAssignmentCompletion: - status: CompletionStatus +class MentorAssignmentCompletion: + status: MentorCompletionStatus user_id: str last_name: str @dataclass -class PraxisAssignmentStatus: +class MentorAssignmentStatus: id: str title: str circle_id: str pending_evaluations: int - completions: List[PraxisAssignmentCompletion] + completions: List[MentorAssignmentCompletion] + type: MentorAssignmentStatusType diff --git a/server/vbv_lernwelt/learning_mentor/serializers.py b/server/vbv_lernwelt/learning_mentor/serializers.py index e9cb7555..64886a9f 100644 --- a/server/vbv_lernwelt/learning_mentor/serializers.py +++ b/server/vbv_lernwelt/learning_mentor/serializers.py @@ -4,7 +4,7 @@ from vbv_lernwelt.core.serializers import UserSerializer from vbv_lernwelt.learning_mentor.models import LearningMentor, MentorInvitation -class PraxisAssignmentCompletionSerializer(serializers.Serializer): +class MentorAssignmentCompletionSerializer(serializers.Serializer): status = serializers.SerializerMethodField() user_id = serializers.CharField() last_name = serializers.CharField() @@ -14,13 +14,13 @@ class PraxisAssignmentCompletionSerializer(serializers.Serializer): return obj.status.value -class PraxisAssignmentStatusSerializer(serializers.Serializer): +class MentorAssignmentStatusSerializer(serializers.Serializer): id = serializers.CharField() title = serializers.CharField() circle_id = serializers.CharField() pending_evaluations = serializers.IntegerField() - completions = PraxisAssignmentCompletionSerializer(many=True) - type = serializers.ReadOnlyField(default="praxis_assignment") + completions = MentorAssignmentCompletionSerializer(many=True) + type = serializers.ReadOnlyField() class InvitationSerializer(serializers.ModelSerializer): diff --git a/server/vbv_lernwelt/learning_mentor/tests/test_assignments.py b/server/vbv_lernwelt/learning_mentor/tests/test_assignments.py index 8b096c64..68eb49f5 100644 --- a/server/vbv_lernwelt/learning_mentor/tests/test_assignments.py +++ b/server/vbv_lernwelt/learning_mentor/tests/test_assignments.py @@ -18,7 +18,7 @@ from vbv_lernwelt.learning_mentor.content.praxis_assignment import ( get_assignment_completions, get_praxis_assignments, ) -from vbv_lernwelt.learning_mentor.entities import CompletionStatus +from vbv_lernwelt.learning_mentor.entities import MentorCompletionStatus class AttendanceServicesTestCase(TestCase): @@ -74,10 +74,10 @@ class AttendanceServicesTestCase(TestCase): # THEN expected_order = ["Beta", "Alpha", "Gamma", "Kappa"] expected_statuses = { - "Alpha": CompletionStatus.EVALUATED, # user1 - "Beta": CompletionStatus.SUBMITTED, # user2 - "Gamma": CompletionStatus.UNKNOWN, # user4 (no AssignmentCompletion) - "Kappa": CompletionStatus.UNKNOWN, # user3 (IN_PROGRESS should be PENDING) + "Alpha": MentorCompletionStatus.EVALUATED, # user1 + "Beta": MentorCompletionStatus.SUBMITTED, # user2 + "Gamma": MentorCompletionStatus.UNKNOWN, # user4 (no AssignmentCompletion) + "Kappa": MentorCompletionStatus.UNKNOWN, # user3 (IN_PROGRESS should be PENDING) } self.assertEqual(len(results), len(participants)) diff --git a/server/vbv_lernwelt/learning_mentor/tests/test_mentor.py b/server/vbv_lernwelt/learning_mentor/tests/test_mentor.py index 13b1748a..f9960017 100644 --- a/server/vbv_lernwelt/learning_mentor/tests/test_mentor.py +++ b/server/vbv_lernwelt/learning_mentor/tests/test_mentor.py @@ -1,3 +1,5 @@ +from typing import List, Optional, Dict + from django.urls import reverse from rest_framework import status from rest_framework.test import APITestCase @@ -7,6 +9,7 @@ from vbv_lernwelt.assignment.models import ( AssignmentCompletionStatus, AssignmentType, ) +from vbv_lernwelt.core.admin import User from vbv_lernwelt.course.creators.test_utils import ( add_course_session_user, create_assignment, @@ -16,9 +19,20 @@ from vbv_lernwelt.course.creators.test_utils import ( create_course_session, create_course_session_assignment, create_user, + create_learning_unit, ) from vbv_lernwelt.course.models import CourseSessionUser from vbv_lernwelt.learning_mentor.models import LearningMentor +from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback + + +def get_completion_for_user( + completions: List[Dict[str, str]], user: User +) -> Optional[Dict[str, str]]: + for completion in completions: + if completion["user_id"] == str(user.id): + return completion + return None class LearningMentorAPITest(APITestCase): @@ -28,15 +42,6 @@ class LearningMentorAPITest(APITestCase): self.circle, _ = create_circle(title="Circle", course_page=self.course_page) - self.assignment = create_assignment( - course=self.course, assignment_type=AssignmentType.PRAXIS_ASSIGNMENT - ) - - lca = create_assignment_learning_content(self.circle, self.assignment) - create_course_session_assignment( - course_session=self.course_session, learning_content_assignment=lca - ) - self.mentor = create_user("mentor") self.participant_1 = add_course_session_user( self.course_session, @@ -109,7 +114,7 @@ class LearningMentorAPITest(APITestCase): self.assertEqual(participant_1["first_name"], "Test") self.assertEqual(participant_1["last_name"], "Participant_1") - def test_api_praxis_assignments(self) -> None: + def test_api_self_evaluation_feedback(self) -> None: # GIVEN participants = [self.participant_1, self.participant_2, self.participant_3] self.client.force_login(self.mentor) @@ -118,12 +123,97 @@ class LearningMentorAPITest(APITestCase): mentor=self.mentor, course=self.course_session.course, ) + + mentor.participants.set(participants) + + learning_unit = create_learning_unit( + circle=self.circle, + course=self.course, + ) + + # 1: we already evaluated + SelfEvaluationFeedback.objects.create( + feedback_requester_user=self.participant_1.user, + feedback_provider_user=self.mentor, + learning_unit=learning_unit, + feedback_submitted=True, + ) + + # 2: we have not evaluated yet + SelfEvaluationFeedback.objects.create( + feedback_requester_user=self.participant_2.user, + feedback_provider_user=self.mentor, + learning_unit=learning_unit, + feedback_submitted=False, + ) + + # 3: did not request feedback + # ... + + # WHEN + response = self.client.get(self.url) + + # THEN + assignments = response.data["assignments"] + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.assertEqual( + response.data["circles"], + [{"id": self.circle.id, "title": self.circle.title}], + ) + + self.assertEqual(len(assignments), 1) + assignment = assignments[0] + + self.assertEqual(assignment["type"], "self_evaluation_feedback") + self.assertEqual(assignment["pending_evaluations"], 1) + + completions = assignment["completions"] + self.assertEqual( + len(completions), + 3, + ) + + completion_1 = get_completion_for_user(completions, self.participant_1.user) + self.assertEqual(completion_1["status"], "EVALUATED") + self.assertEqual(completion_1["last_name"], "Participant_1") + self.assertEqual(completion_1["user_id"], str(self.participant_1.user.id)) + + completion_2 = get_completion_for_user(completions, self.participant_2.user) + self.assertEqual(completion_2["status"], "SUBMITTED") + self.assertEqual(completion_2["last_name"], "Participant_2") + self.assertEqual(completion_2["user_id"], str(self.participant_2.user.id)) + + completion_3 = get_completion_for_user(completions, self.participant_3.user) + self.assertEqual(completion_3["status"], "UNKNOWN") + self.assertEqual(completion_3["last_name"], "Participant_3") + self.assertEqual(completion_3["user_id"], str(self.participant_3.user.id)) + + def test_api_praxis_assignments(self) -> None: + # GIVEN + self.client.force_login(self.mentor) + + assignment = create_assignment( + course=self.course, assignment_type=AssignmentType.PRAXIS_ASSIGNMENT + ) + + lca = create_assignment_learning_content(self.circle, assignment) + create_course_session_assignment( + course_session=self.course_session, learning_content_assignment=lca + ) + + mentor = LearningMentor.objects.create( + mentor=self.mentor, + course=self.course_session.course, + ) + + participants = [self.participant_1, self.participant_2, self.participant_3] mentor.participants.set(participants) AssignmentCompletion.objects.create( assignment_user=self.participant_1.user, course_session=self.course_session, - assignment=self.assignment, + assignment=assignment, completion_status=AssignmentCompletionStatus.EVALUATION_SUBMITTED.value, evaluation_user=self.mentor, ) @@ -131,7 +221,7 @@ class LearningMentorAPITest(APITestCase): AssignmentCompletion.objects.create( assignment_user=self.participant_3.user, course_session=self.course_session, - assignment=self.assignment, + assignment=assignment, completion_status=AssignmentCompletionStatus.SUBMITTED.value, evaluation_user=self.mentor, ) diff --git a/server/vbv_lernwelt/learning_mentor/views.py b/server/vbv_lernwelt/learning_mentor/views.py index 04abb592..a1ee9db9 100644 --- a/server/vbv_lernwelt/learning_mentor/views.py +++ b/server/vbv_lernwelt/learning_mentor/views.py @@ -12,11 +12,14 @@ from vbv_lernwelt.iam.permissions import has_role_in_course, is_course_session_m from vbv_lernwelt.learning_mentor.content.praxis_assignment import ( get_praxis_assignments, ) +from vbv_lernwelt.learning_mentor.content.self_evaluation_feedback import ( + get_self_feedback_evaluation, +) from vbv_lernwelt.learning_mentor.models import LearningMentor, MentorInvitation from vbv_lernwelt.learning_mentor.serializers import ( InvitationSerializer, MentorSerializer, - PraxisAssignmentStatusSerializer, + MentorAssignmentStatusSerializer, ) from vbv_lernwelt.learnpath.models import Circle from vbv_lernwelt.notify.email.email_services import EmailTemplate, send_email @@ -37,24 +40,39 @@ def mentor_summary(request, course_session_id: int): assignments = [] circle_ids = set() - praxis_assignments, _circle_ids = get_praxis_assignments( - course_session=course_session, participants=users, evaluation_user=request.user + praxis_assignments, praxis_assignments_circle_ids = get_praxis_assignments( + course_session=course_session, + participants=users, + evaluation_user=request.user, # noqa + ) + + ( + self_evaluation_feedbacks, + self_evaluation_feedback_circle_ids, + ) = get_self_feedback_evaluation( + participants=users, + evaluation_user=request.user, # noqa + ) + + circle_ids.update(praxis_assignments_circle_ids) + circle_ids.update(self_evaluation_feedback_circle_ids) + + assignments.extend( + MentorAssignmentStatusSerializer(praxis_assignments, many=True).data ) assignments.extend( - PraxisAssignmentStatusSerializer(praxis_assignments, many=True).data + MentorAssignmentStatusSerializer(self_evaluation_feedbacks, many=True).data ) - circle_ids.update(_circle_ids) - - circles = Circle.objects.filter(id__in=circle_ids).values("id", "title") assignments.sort( key=lambda x: (-x.get("pending_evaluations", 0), x.get("title", "").lower()) ) - return Response( { "participants": [UserSerializer(user).data for user in users], - "circles": list(circles), + "circles": list( + Circle.objects.filter(id__in=circle_ids).values("id", "title") + ), "assignments": assignments, } ) From 3f8420ed61c54f1c960821215f570cad65400f9c Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Thu, 25 Jan 2024 18:56:35 +0100 Subject: [PATCH 22/57] fix: format code --- .../vbv_lernwelt/learning_mentor/content/praxis_assignment.py | 4 ++-- .../learning_mentor/content/self_evaluation_feedback.py | 4 ++-- server/vbv_lernwelt/learning_mentor/tests/test_mentor.py | 4 ++-- server/vbv_lernwelt/learning_mentor/views.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/server/vbv_lernwelt/learning_mentor/content/praxis_assignment.py b/server/vbv_lernwelt/learning_mentor/content/praxis_assignment.py index b6c07490..a43f78e0 100644 --- a/server/vbv_lernwelt/learning_mentor/content/praxis_assignment.py +++ b/server/vbv_lernwelt/learning_mentor/content/praxis_assignment.py @@ -2,18 +2,18 @@ from typing import List, Set, Tuple from vbv_lernwelt.assignment.models import ( Assignment, + AssignmentCompletion, AssignmentCompletionStatus, AssignmentType, - AssignmentCompletion, ) from vbv_lernwelt.core.models import User from vbv_lernwelt.course.models import CourseSession from vbv_lernwelt.course_session.models import CourseSessionAssignment from vbv_lernwelt.learning_mentor.entities import ( - MentorCompletionStatus, MentorAssignmentCompletion, MentorAssignmentStatus, MentorAssignmentStatusType, + MentorCompletionStatus, ) diff --git a/server/vbv_lernwelt/learning_mentor/content/self_evaluation_feedback.py b/server/vbv_lernwelt/learning_mentor/content/self_evaluation_feedback.py index e2eb2409..36785f22 100644 --- a/server/vbv_lernwelt/learning_mentor/content/self_evaluation_feedback.py +++ b/server/vbv_lernwelt/learning_mentor/content/self_evaluation_feedback.py @@ -4,10 +4,10 @@ from django.db.models import Prefetch from vbv_lernwelt.core.models import User from vbv_lernwelt.learning_mentor.entities import ( - MentorCompletionStatus, - MentorAssignmentStatus, MentorAssignmentCompletion, + MentorAssignmentStatus, MentorAssignmentStatusType, + MentorCompletionStatus, ) from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback diff --git a/server/vbv_lernwelt/learning_mentor/tests/test_mentor.py b/server/vbv_lernwelt/learning_mentor/tests/test_mentor.py index f9960017..91e2b8d9 100644 --- a/server/vbv_lernwelt/learning_mentor/tests/test_mentor.py +++ b/server/vbv_lernwelt/learning_mentor/tests/test_mentor.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Dict +from typing import Dict, List, Optional from django.urls import reverse from rest_framework import status @@ -18,8 +18,8 @@ from vbv_lernwelt.course.creators.test_utils import ( create_course, create_course_session, create_course_session_assignment, - create_user, create_learning_unit, + create_user, ) from vbv_lernwelt.course.models import CourseSessionUser from vbv_lernwelt.learning_mentor.models import LearningMentor diff --git a/server/vbv_lernwelt/learning_mentor/views.py b/server/vbv_lernwelt/learning_mentor/views.py index a1ee9db9..a1966c8f 100644 --- a/server/vbv_lernwelt/learning_mentor/views.py +++ b/server/vbv_lernwelt/learning_mentor/views.py @@ -18,8 +18,8 @@ from vbv_lernwelt.learning_mentor.content.self_evaluation_feedback import ( from vbv_lernwelt.learning_mentor.models import LearningMentor, MentorInvitation from vbv_lernwelt.learning_mentor.serializers import ( InvitationSerializer, - MentorSerializer, MentorAssignmentStatusSerializer, + MentorSerializer, ) from vbv_lernwelt.learnpath.models import Circle from vbv_lernwelt.notify.email.email_services import EmailTemplate, send_email From 67188a5b73fee309a8fb22b5a3ce8f4599655cea Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Fri, 26 Jan 2024 13:04:13 +0100 Subject: [PATCH 23/57] fix: include learning units without feedback requested --- .../course/creators/test_utils.py | 10 ++++-- .../content/self_evaluation_feedback.py | 36 +++++++++++-------- .../learning_mentor/tests/test_mentor.py | 8 +++++ server/vbv_lernwelt/learning_mentor/views.py | 1 + 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/server/vbv_lernwelt/course/creators/test_utils.py b/server/vbv_lernwelt/course/creators/test_utils.py index 51360fc6..1a76a3ca 100644 --- a/server/vbv_lernwelt/course/creators/test_utils.py +++ b/server/vbv_lernwelt/course/creators/test_utils.py @@ -46,6 +46,7 @@ from vbv_lernwelt.learnpath.models import ( LearningContentAssignment, LearningContentEdoniqTest, LearningPath, + LearningUnit, ) from vbv_lernwelt.learnpath.tests.learning_path_factories import ( CircleFactory, @@ -268,13 +269,18 @@ def create_course_session_edoniq_test( return cset -def create_learning_unit(circle: Circle, course: Course): +def create_learning_unit( + circle: Circle, + course: Course, +) -> LearningUnit: cat, _ = CourseCategory.objects.get_or_create( course=course, title="Course Category" ) return LearningUnitFactory( - title="Learning Unit", parent=circle, course_category=cat + title="Learning Unit", + parent=circle, + course_category=cat, ) diff --git a/server/vbv_lernwelt/learning_mentor/content/self_evaluation_feedback.py b/server/vbv_lernwelt/learning_mentor/content/self_evaluation_feedback.py index 36785f22..bcee4075 100644 --- a/server/vbv_lernwelt/learning_mentor/content/self_evaluation_feedback.py +++ b/server/vbv_lernwelt/learning_mentor/content/self_evaluation_feedback.py @@ -1,14 +1,17 @@ from typing import List, Set, Tuple -from django.db.models import Prefetch - from vbv_lernwelt.core.models import User +from vbv_lernwelt.course.models import Course from vbv_lernwelt.learning_mentor.entities import ( MentorAssignmentCompletion, MentorAssignmentStatus, MentorAssignmentStatusType, MentorCompletionStatus, ) +from vbv_lernwelt.learnpath.models import ( + LearningUnit, + LearningUnitPerformanceFeedbackType, +) from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback @@ -37,7 +40,9 @@ def create_blank_completions_non_requesters( def get_self_feedback_evaluation( - participants: List[User], evaluation_user: User + participants: List[User], + evaluation_user: User, + course: Course, ) -> Tuple[List[MentorAssignmentStatus], Set[int]]: records: List[MentorAssignmentStatus] = [] circle_ids: Set[int] = set() @@ -45,21 +50,24 @@ def get_self_feedback_evaluation( if not participants: return records, circle_ids - feedbacks = SelfEvaluationFeedback.objects.prefetch_related( - Prefetch("learning_unit") - ).filter( - feedback_requester_user__in=participants, - feedback_provider_user=evaluation_user, - ) + # 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 + # and check if we have to take them into account (course, feedback type, etc.) + for learning_unit in LearningUnit.objects.filter( + feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK.value, + ): + circle_page = learning_unit.get_parent() + circle = circle_page.specific - feedback_by_learning_unit = {} + if circle.get_course() != course: + continue - for feedback in feedbacks: - feedback_by_learning_unit.setdefault(feedback.learning_unit, []).append( - feedback + feedbacks = SelfEvaluationFeedback.objects.filter( + learning_unit=learning_unit, + feedback_requester_user__in=participants, + feedback_provider_user=evaluation_user, ) - for learning_unit, feedbacks in feedback_by_learning_unit.items(): circle_id = learning_unit.get_circle().id circle_ids.add(circle_id) diff --git a/server/vbv_lernwelt/learning_mentor/tests/test_mentor.py b/server/vbv_lernwelt/learning_mentor/tests/test_mentor.py index 91e2b8d9..fb27b620 100644 --- a/server/vbv_lernwelt/learning_mentor/tests/test_mentor.py +++ b/server/vbv_lernwelt/learning_mentor/tests/test_mentor.py @@ -23,6 +23,7 @@ from vbv_lernwelt.course.creators.test_utils import ( ) from vbv_lernwelt.course.models import CourseSessionUser from vbv_lernwelt.learning_mentor.models import LearningMentor +from vbv_lernwelt.learnpath.models import LearningUnitPerformanceFeedbackType from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback @@ -131,6 +132,13 @@ class LearningMentorAPITest(APITestCase): 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 SelfEvaluationFeedback.objects.create( feedback_requester_user=self.participant_1.user, diff --git a/server/vbv_lernwelt/learning_mentor/views.py b/server/vbv_lernwelt/learning_mentor/views.py index a1966c8f..e56c33fc 100644 --- a/server/vbv_lernwelt/learning_mentor/views.py +++ b/server/vbv_lernwelt/learning_mentor/views.py @@ -52,6 +52,7 @@ def mentor_summary(request, course_session_id: int): ) = get_self_feedback_evaluation( participants=users, evaluation_user=request.user, # noqa + course=course_session.course, ) circle_ids.update(praxis_assignments_circle_ids) From 123adb7bf1217de1a512e6b6163d09fed96f8841 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Fri, 26 Jan 2024 13:23:32 +0100 Subject: [PATCH 24/57] chore: logs & naming --- .../content/self_evaluation_feedback.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/server/vbv_lernwelt/learning_mentor/content/self_evaluation_feedback.py b/server/vbv_lernwelt/learning_mentor/content/self_evaluation_feedback.py index bcee4075..f434485a 100644 --- a/server/vbv_lernwelt/learning_mentor/content/self_evaluation_feedback.py +++ b/server/vbv_lernwelt/learning_mentor/content/self_evaluation_feedback.py @@ -1,5 +1,7 @@ from typing import List, Set, Tuple +import structlog + from vbv_lernwelt.core.models import User from vbv_lernwelt.course.models import Course from vbv_lernwelt.learning_mentor.entities import ( @@ -14,6 +16,8 @@ from vbv_lernwelt.learnpath.models import ( ) from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback +logger = structlog.get_logger(__name__) + def create_blank_completions_non_requesters( completions: List[MentorAssignmentCompletion], @@ -21,11 +25,11 @@ def create_blank_completions_non_requesters( ) -> List[MentorAssignmentCompletion]: non_requester_completions = [] - participants_ids = set([str(p.id) for p in participants]) + participants_user_ids = set([str(p.id) for p in participants]) completion_seen_user_ids = set([str(c.user_id) for c in completions]) user_by_id = {str(p.id): p for p in participants} - for non_requester_user_id in participants_ids - completion_seen_user_ids: + for non_requester_user_id in participants_user_ids - completion_seen_user_ids: non_requester_user = user_by_id[non_requester_user_id] non_requester_completions.append( @@ -56,6 +60,11 @@ def get_self_feedback_evaluation( for learning_unit in LearningUnit.objects.filter( feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK.value, ): + logger.debug( + "Relevant learning unit found for mentor dashboard", + learning_unit=learning_unit.id, + ) + circle_page = learning_unit.get_parent() circle = circle_page.specific From c5ff3e9fb6377d6f547695828b68c17e69768514 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Fri, 26 Jan 2024 15:53:26 +0100 Subject: [PATCH 25/57] feat: mentor cockpit summary pages wrap up --- .../SelfAssignmentFeedbackAssignmentItem.vue | 2 +- .../cockpitPage/mentor/MentorOverview.vue | 8 +- .../mentor/MentorPraxisAssignment.vue | 6 +- ...MentorSelfEvaluationFeedbackAssignment.vue | 121 ++++++++++++++++++ .../mentor/SelfEvaluationFeedback.vue | 7 + client/src/router/index.ts | 22 ++++ client/src/services/mentorCockpit.ts | 8 +- 7 files changed, 162 insertions(+), 12 deletions(-) create mode 100644 client/src/pages/cockpit/cockpitPage/mentor/MentorSelfEvaluationFeedbackAssignment.vue create mode 100644 client/src/pages/cockpit/cockpitPage/mentor/SelfEvaluationFeedback.vue diff --git a/client/src/components/cockpit/mentor/SelfAssignmentFeedbackAssignmentItem.vue b/client/src/components/cockpit/mentor/SelfAssignmentFeedbackAssignmentItem.vue index 3518c42d..ff74e6b1 100644 --- a/client/src/components/cockpit/mentor/SelfAssignmentFeedbackAssignmentItem.vue +++ b/client/src/components/cockpit/mentor/SelfAssignmentFeedbackAssignmentItem.vue @@ -16,7 +16,7 @@ defineProps<{ :circle-title="circleTitle" :pending-tasks="pendingTasks" :task-link="taskLink" - :pending-tasks-label="$t('a.Selbsteinschätzung geteilt')" + :pending-tasks-label="$t('a.Selbsteinschätzungen geteilt')" :task-link-pending-label="$t('a.Fremdeinschätzung vornehmen')" :task-link-label="$t('a.Selbsteinschätzung anzeigen')" /> diff --git a/client/src/pages/cockpit/cockpitPage/mentor/MentorOverview.vue b/client/src/pages/cockpit/cockpitPage/mentor/MentorOverview.vue index e3f96aa5..434162e6 100644 --- a/client/src/pages/cockpit/cockpitPage/mentor/MentorOverview.vue +++ b/client/src/pages/cockpit/cockpitPage/mentor/MentorOverview.vue @@ -1,5 +1,5 @@ + + diff --git a/client/src/pages/cockpit/cockpitPage/mentor/SelfEvaluationFeedback.vue b/client/src/pages/cockpit/cockpitPage/mentor/SelfEvaluationFeedback.vue new file mode 100644 index 00000000..373f87a6 --- /dev/null +++ b/client/src/pages/cockpit/cockpitPage/mentor/SelfEvaluationFeedback.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/client/src/router/index.ts b/client/src/router/index.ts index 2e0deef5..0be086d8 100644 --- a/client/src/router/index.ts +++ b/client/src/router/index.ts @@ -195,6 +195,16 @@ const router = createRouter({ cockpitType: "mentor", }, }, + { + path: "self-evaluation-feedback/:selfEvaluationFeedbackId", + component: () => + import("@/pages/cockpit/cockpitPage/mentor/SelfEvaluationFeedback.vue"), + name: "mentorSelfEvaluationFeedback", + meta: { + cockpitType: "mentor", + }, + props: true, + }, { path: "details", component: () => @@ -215,6 +225,18 @@ const router = createRouter({ }, props: true, }, + { + path: "self-evaluation-feedback-assignments/:selfEvaluationFeedbackId", + component: () => + import( + "@/pages/cockpit/cockpitPage/mentor/MentorSelfEvaluationFeedbackAssignment.vue" + ), + name: "mentorCockpitSelfEvaluationFeedbackAssignments", + meta: { + cockpitType: "mentor", + }, + props: true, + }, ], }, ], diff --git a/client/src/services/mentorCockpit.ts b/client/src/services/mentorCockpit.ts index 3c6bdc76..bfd34fcf 100644 --- a/client/src/services/mentorCockpit.ts +++ b/client/src/services/mentorCockpit.ts @@ -29,7 +29,7 @@ interface Completion { last_name: string; } -export interface PraxisAssignment { +export interface Assignment { id: string; title: string; circle_id: string; @@ -41,7 +41,7 @@ export interface PraxisAssignment { interface Summary { participants: Participant[]; circles: Circle[]; - assignments: PraxisAssignment[]; + assignments: Assignment[]; } export const useMentorCockpit = ( @@ -59,7 +59,7 @@ export const useMentorCockpit = ( return ""; }; - const getPraxisAssignmentById = (id: string): PraxisAssignment | null => { + const getAssignmentById = (id: string): Assignment | null => { if (summary.value?.assignments) { const found = summary.value.assignments.find( (assignment) => assignment.id === id @@ -92,6 +92,6 @@ export const useMentorCockpit = ( summary, error, getCircleTitleById, - getPraxisAssignmentById, + getAssignmentById, }; }; From f123e2bddfafa8bd008b1cc48bd8ae00c7462fd3 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Mon, 29 Jan 2024 16:34:06 +0100 Subject: [PATCH 26/57] feat: mentor feedback UI --- .../cockpitPage/mentor/MentorOverview.vue | 2 +- ...MentorSelfEvaluationFeedbackAssignment.vue | 6 +- .../mentor/SelfEvaluationFeedback.vue | 94 ++++++++++++++++++- client/src/router/index.ts | 4 +- .../tests/test_api.py | 2 +- .../self_evaluation_feedback/urls.py | 10 +- .../self_evaluation_feedback/views.py | 6 +- 7 files changed, 108 insertions(+), 16 deletions(-) diff --git a/client/src/pages/cockpit/cockpitPage/mentor/MentorOverview.vue b/client/src/pages/cockpit/cockpitPage/mentor/MentorOverview.vue index 434162e6..18641046 100644 --- a/client/src/pages/cockpit/cockpitPage/mentor/MentorOverview.vue +++ b/client/src/pages/cockpit/cockpitPage/mentor/MentorOverview.vue @@ -87,7 +87,7 @@ const filteredAssignments: Ref = computed(() => { :pending-tasks="item.pending_evaluations" :task-link="{ name: 'mentorCockpitSelfEvaluationFeedbackAssignments', - params: { selfEvaluationFeedbackId: item.id }, + params: { learningUnitId: item.id }, }" :task-title="item.title" /> diff --git a/client/src/pages/cockpit/cockpitPage/mentor/MentorSelfEvaluationFeedbackAssignment.vue b/client/src/pages/cockpit/cockpitPage/mentor/MentorSelfEvaluationFeedbackAssignment.vue index 3c51a6e1..97e6d4a9 100644 --- a/client/src/pages/cockpit/cockpitPage/mentor/MentorSelfEvaluationFeedbackAssignment.vue +++ b/client/src/pages/cockpit/cockpitPage/mentor/MentorSelfEvaluationFeedbackAssignment.vue @@ -5,14 +5,14 @@ import { computed, type Ref } from "vue"; import { useCurrentCourseSession } from "@/composables"; const props = defineProps<{ - selfEvaluationFeedbackId: string; + learningUnitId: string; }>(); const courseSession = useCurrentCourseSession(); const mentorCockpitStore = useMentorCockpit(courseSession.value.id); const selfEvaluationFeedback: Ref = computed(() => - mentorCockpitStore.getAssignmentById(props.selfEvaluationFeedbackId) + mentorCockpitStore.getAssignmentById(props.learningUnitId) ); const getParticipantById = (id: string): Participant | null => { @@ -107,7 +107,7 @@ const getParticipantById = (id: string): Participant | null => { :to="{ name: 'mentorSelfEvaluationFeedback', params: { - selfEvaluationFeedbackId: selfEvaluationFeedback.id, + learningUnitId: learningUnitId, }, }" > diff --git a/client/src/pages/cockpit/cockpitPage/mentor/SelfEvaluationFeedback.vue b/client/src/pages/cockpit/cockpitPage/mentor/SelfEvaluationFeedback.vue index 373f87a6..e18d6df5 100644 --- a/client/src/pages/cockpit/cockpitPage/mentor/SelfEvaluationFeedback.vue +++ b/client/src/pages/cockpit/cockpitPage/mentor/SelfEvaluationFeedback.vue @@ -1,7 +1,97 @@ - + diff --git a/client/src/router/index.ts b/client/src/router/index.ts index 0be086d8..45ff9f91 100644 --- a/client/src/router/index.ts +++ b/client/src/router/index.ts @@ -196,7 +196,7 @@ const router = createRouter({ }, }, { - path: "self-evaluation-feedback/:selfEvaluationFeedbackId", + path: "self-evaluation-feedback/:learningUnitId", component: () => import("@/pages/cockpit/cockpitPage/mentor/SelfEvaluationFeedback.vue"), name: "mentorSelfEvaluationFeedback", @@ -226,7 +226,7 @@ const router = createRouter({ props: true, }, { - path: "self-evaluation-feedback-assignments/:selfEvaluationFeedbackId", + path: "self-evaluation-feedback-assignments/:learningUnitId", component: () => import( "@/pages/cockpit/cockpitPage/mentor/MentorSelfEvaluationFeedbackAssignment.vue" diff --git a/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py b/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py index 2ecd42b8..08dea1da 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/tests/test_api.py @@ -270,7 +270,7 @@ class SelfEvaluationFeedbackAPI(APITestCase): response = self.client.get( reverse( "get_self_evaluation_feedback_as_provider", - args=[self_evaluation_feedback.id], + args=[self_evaluation_feedback.learning_unit.id], ) ) diff --git a/server/vbv_lernwelt/self_evaluation_feedback/urls.py b/server/vbv_lernwelt/self_evaluation_feedback/urls.py index 652d98a0..c3a3efe0 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/urls.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/urls.py @@ -19,6 +19,11 @@ urlpatterns = [ get_self_evaluation_feedback_as_requester, name="get_self_evaluation_feedback_as_requester", ), + path( + "provider//feedback", + get_self_evaluation_feedback_as_provider, + name="get_self_evaluation_feedback_as_provider", + ), path( "provider/feedback//submit", submit_provider_self_evaluation_feedback, @@ -29,9 +34,4 @@ urlpatterns = [ add_provider_self_evaluation_feedback, name="add_self_evaluation_feedback_assessment", ), - path( - "provider/feedback/", - get_self_evaluation_feedback_as_provider, - name="get_self_evaluation_feedback_as_provider", - ), ] diff --git a/server/vbv_lernwelt/self_evaluation_feedback/views.py b/server/vbv_lernwelt/self_evaluation_feedback/views.py index ac75e44f..521a6161 100644 --- a/server/vbv_lernwelt/self_evaluation_feedback/views.py +++ b/server/vbv_lernwelt/self_evaluation_feedback/views.py @@ -61,9 +61,11 @@ def submit_provider_self_evaluation_feedback(request, feedback_id): @api_view(["GET"]) @permission_classes([IsAuthenticated]) -def get_self_evaluation_feedback_as_provider(request, feedback_id): +def get_self_evaluation_feedback_as_provider(request, learning_unit_id): feedback = get_object_or_404( - SelfEvaluationFeedback, id=feedback_id, feedback_provider_user=request.user + SelfEvaluationFeedback, + learning_unit_id=learning_unit_id, + feedback_provider_user=request.user, ) return Response(SelfEvaluationFeedbackSerializer(feedback).data) From 6a96ad2d05b014e845442cb47ed894f7d7d16fe9 Mon Sep 17 00:00:00 2001 From: Livio Bieri Date: Mon, 29 Jan 2024 16:34:43 +0100 Subject: [PATCH 27/57] fix: not sure how this worked out it? Needed for SelfEvaluationFeedback.vue --- .../learningContentPage/LearningContentContainer.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/pages/learningPath/learningContentPage/LearningContentContainer.vue b/client/src/pages/learningPath/learningContentPage/LearningContentContainer.vue index 83004cf4..194a9864 100644 --- a/client/src/pages/learningPath/learningContentPage/LearningContentContainer.vue +++ b/client/src/pages/learningPath/learningContentPage/LearningContentContainer.vue @@ -12,7 +12,7 @@ defineEmits(["exit"]); diff --git a/client/src/pages/cockpit/cockpitPage/mentor/SelfEvaluationFeedback.vue b/client/src/pages/cockpit/cockpitPage/mentor/SelfEvaluationFeedback.vue index e18d6df5..c08fe633 100644 --- a/client/src/pages/cockpit/cockpitPage/mentor/SelfEvaluationFeedback.vue +++ b/client/src/pages/cockpit/cockpitPage/mentor/SelfEvaluationFeedback.vue @@ -2,9 +2,14 @@ import LearningContentMultiLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentMultiLayout.vue"; import LearningContentContainer from "@/pages/learningPath/learningContentPage/LearningContentContainer.vue"; import { useRouter } from "vue-router"; -import { computed, onMounted, ref, toValue } from "vue"; -import { useCSRFFetch } from "@/fetchHelpers"; -import type { FeedbackRequest } from "@/services/selfEvaluationFeedback"; +import { computed, ref } from "vue"; +import { + type Criterion, + useSelfEvaluationFeedback, +} from "@/services/selfEvaluationFeedback"; +import { useRouteQuery } from "@vueuse/router"; +import FeedbackProviderRankCriteria from "@/components/selfEvaluationFeedback/FeedbackProviderRankCriteria.vue"; +import FeedbackProviderReleaseOverview from "@/components/selfEvaluationFeedback/FeedbackProviderReleaseOverview.vue"; const router = useRouter(); const props = defineProps<{ @@ -12,47 +17,62 @@ const props = defineProps<{ courseSlug: string; }>(); -const url = computed( - () => - `/api/self-evaluation-feedback/provider/${toValue(props.learningUnitId)}/feedback` -); - -const feedback = ref(); -const isFeedbackLoading = ref(false); - -onMounted(async () => { - feedback.value = null; - isFeedbackLoading.value = true; - const { data, error } = await useCSRFFetch(url.value).json(); - isFeedbackLoading.value = false; - if (error.value) { - console.error(error.value); - return; - } else { - feedback.value = data.value; - } +const currentStepRouteParam = useRouteQuery("step", "0", { + transform: Number, + mode: "push", }); -const stepsCount = computed(() => 10); -const currentStep = 1; +const selfEvaluationFeedback = useSelfEvaluationFeedback( + props.learningUnitId, + "provider" +); -const title = "TITLE " + props.learningUnitId; +const feedback = computed(() => selfEvaluationFeedback?.feedback.value); -const showNextButton = true; -const showExitButton = true; -const showPreviousButton = true; -const base_url = "BASE_URL"; +const title = computed(() => { + if (feedback.value) { + return feedback.value.title; + } + return ""; +}); -const endBadgeText = "END_BADGE_TEXT"; +const currentStep = ref(currentStepRouteParam); + +const stepsCount = computed(() => { + if (feedback.value) { + return feedback.value.criteria.length + 1; + } + return 0; +}); + +const currentCriteria = computed(() => { + if (feedback.value && currentStep.value < stepsCount.value - 1) { + return feedback.value.criteria[currentStep.value]; + } + return null; +}); + +const showNextButton = computed(() => { + if (feedback.value) { + return currentStep.value < stepsCount.value - 1; + } + return false; +}); const handleBack = () => { - console.log("handleBack"); + if (currentStep.value > 0) { + currentStep.value--; + } }; + const handleContinue = () => { - console.log("handleContinue"); + if (currentStep.value < stepsCount.value) { + currentStep.value++; + } }; const clickExit = () => { + console.log("clickExit"); router.push({ name: "mentorCockpitSelfEvaluationFeedbackAssignments", params: { @@ -60,35 +80,60 @@ const clickExit = () => { }, }); }; + +const handleFeedbackEvaluation = async ( + criteria: Criterion, + evaluation: "SUCCESS" | "FAIL" +) => { + if (!feedback.value) { + return; + } + await selfEvaluationFeedback.addFeedbackAssessment( + criteria.course_completion_id, + evaluation + ); +}; + +const handleFeedbackRelease = async () => { + if (!feedback.value) { + return; + } + await selfEvaluationFeedback.releaseFeedback(); +};