From d4cb978de3a59de529f4f34aa10d84b099ce9c91 Mon Sep 17 00:00:00 2001
From: Reto Aebersold
Date: Wed, 4 Oct 2023 14:14:56 +0200
Subject: [PATCH 1/7] Add assigment task file upload
---
client/src/gql/gql.ts | 8 +-
client/src/gql/graphql.ts | 9 +-
client/src/gql/schema.graphql | 1 +
client/src/graphql/mutations.ts | 1 +
client/src/graphql/queries.ts | 1 +
.../assignment/AssignmentTaskView.vue | 36 ++++++--
.../assignment/AttachmentSection.vue | 85 +++++++++++++++++++
client/src/services/files.ts | 24 ++++--
client/src/types.ts | 28 +++++-
server/config/urls.py | 11 ++-
server/requirements/requirements.in | 1 -
server/requirements/requirements.txt | 3 +-
.../vbv_lernwelt/assignment/graphql/types.py | 24 +++---
server/vbv_lernwelt/assignment/models.py | 32 ++++++-
server/vbv_lernwelt/assignment/serializers.py | 1 +
server/vbv_lernwelt/assignment/services.py | 16 +++-
.../assignment/tests/test_graphql.py | 43 +++++++++-
server/vbv_lernwelt/course_session/models.py | 6 +-
server/vbv_lernwelt/files/integrations.py | 8 +-
server/vbv_lernwelt/files/serializers.py | 6 ++
server/vbv_lernwelt/files/services.py | 14 ++-
server/vbv_lernwelt/files/views.py | 33 +++++++
22 files changed, 330 insertions(+), 61 deletions(-)
create mode 100644 client/src/pages/learningPath/learningContentPage/assignment/AttachmentSection.vue
create mode 100644 server/vbv_lernwelt/files/serializers.py
create mode 100644 server/vbv_lernwelt/files/views.py
diff --git a/client/src/gql/gql.ts b/client/src/gql/gql.ts
index fdde1523..d971bb1d 100644
--- a/client/src/gql/gql.ts
+++ b/client/src/gql/gql.ts
@@ -14,10 +14,10 @@ import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-
*/
const documents = {
"\n mutation AttendanceCheckMutation(\n $attendanceCourseId: ID!\n $attendanceUserList: [AttendanceUserInputType]!\n ) {\n update_course_session_attendance_course_users(\n id: $attendanceCourseId\n attendance_user_list: $attendanceUserList\n ) {\n course_session_attendance_course {\n id\n attendance_user_list {\n user_id\n first_name\n last_name\n email\n status\n }\n }\n }\n }\n": types.AttendanceCheckMutationDocument,
- "\n mutation UpsertAssignmentCompletion(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n $completionStatus: AssignmentCompletionStatus!\n $completionDataString: String!\n $evaluationPoints: Float\n $initializeCompletion: Boolean\n ) {\n upsert_assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n assignment_user_id: $assignmentUserId\n completion_status: $completionStatus\n completion_data_string: $completionDataString\n evaluation_points: $evaluationPoints\n initialize_completion: $initializeCompletion\n ) {\n assignment_completion {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_points\n completion_data\n }\n }\n }\n": types.UpsertAssignmentCompletionDocument,
+ "\n mutation UpsertAssignmentCompletion(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n $completionStatus: AssignmentCompletionStatus!\n $completionDataString: String!\n $evaluationPoints: Float\n $initializeCompletion: Boolean\n ) {\n upsert_assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n assignment_user_id: $assignmentUserId\n completion_status: $completionStatus\n completion_data_string: $completionDataString\n evaluation_points: $evaluationPoints\n initialize_completion: $initializeCompletion\n ) {\n assignment_completion {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_points\n completion_data\n task_completion_data\n }\n }\n }\n": types.UpsertAssignmentCompletionDocument,
"\n fragment CoursePageFields on CoursePageInterface {\n title\n id\n slug\n content_type\n frontend_url\n }\n": types.CoursePageFieldsFragmentDoc,
"\n query attendanceCheckQuery($courseSessionId: ID!) {\n course_session_attendance_course(id: $courseSessionId) {\n id\n attendance_user_list {\n user_id\n status\n }\n }\n }\n": types.AttendanceCheckQueryDocument,
- "\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n 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 }\n }\n": types.AssignmentCompletionQueryDocument,
+ "\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n 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 courseQuery($courseId: ID!) {\n course(id: $courseId) {\n id\n slug\n title\n category_name\n learning_path {\n id\n }\n }\n }\n": types.CourseQueryDocument,
"\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 title\n id\n slug\n content_type\n frontend_url\n circle {\n ...CoursePageFields\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 }\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,
@@ -45,7 +45,7 @@ export function graphql(source: "\n mutation AttendanceCheckMutation(\n $att
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
-export function graphql(source: "\n mutation UpsertAssignmentCompletion(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n $completionStatus: AssignmentCompletionStatus!\n $completionDataString: String!\n $evaluationPoints: Float\n $initializeCompletion: Boolean\n ) {\n upsert_assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n assignment_user_id: $assignmentUserId\n completion_status: $completionStatus\n completion_data_string: $completionDataString\n evaluation_points: $evaluationPoints\n initialize_completion: $initializeCompletion\n ) {\n assignment_completion {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_points\n completion_data\n }\n }\n }\n"): (typeof documents)["\n mutation UpsertAssignmentCompletion(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n $completionStatus: AssignmentCompletionStatus!\n $completionDataString: String!\n $evaluationPoints: Float\n $initializeCompletion: Boolean\n ) {\n upsert_assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n assignment_user_id: $assignmentUserId\n completion_status: $completionStatus\n completion_data_string: $completionDataString\n evaluation_points: $evaluationPoints\n initialize_completion: $initializeCompletion\n ) {\n assignment_completion {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_points\n completion_data\n }\n }\n }\n"];
+export function graphql(source: "\n mutation UpsertAssignmentCompletion(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n $completionStatus: AssignmentCompletionStatus!\n $completionDataString: String!\n $evaluationPoints: Float\n $initializeCompletion: Boolean\n ) {\n upsert_assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n assignment_user_id: $assignmentUserId\n completion_status: $completionStatus\n completion_data_string: $completionDataString\n evaluation_points: $evaluationPoints\n initialize_completion: $initializeCompletion\n ) {\n assignment_completion {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_points\n completion_data\n task_completion_data\n }\n }\n }\n"): (typeof documents)["\n mutation UpsertAssignmentCompletion(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n $completionStatus: AssignmentCompletionStatus!\n $completionDataString: String!\n $evaluationPoints: Float\n $initializeCompletion: Boolean\n ) {\n upsert_assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n assignment_user_id: $assignmentUserId\n completion_status: $completionStatus\n completion_data_string: $completionDataString\n evaluation_points: $evaluationPoints\n initialize_completion: $initializeCompletion\n ) {\n assignment_completion {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_points\n completion_data\n task_completion_data\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -57,7 +57,7 @@ export function graphql(source: "\n query attendanceCheckQuery($courseSessionId
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
-export function graphql(source: "\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n 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 }\n }\n"): (typeof documents)["\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n 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 }\n }\n"];
+export function graphql(source: "\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n }\n assignment_user {\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n }\n }\n"): (typeof documents)["\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n 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"];
/**
* 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 0f417008..9a81624b 100644
--- a/client/src/gql/graphql.ts
+++ b/client/src/gql/graphql.ts
@@ -94,6 +94,7 @@ export type AssignmentCompletionObjectType = {
id: Scalars['UUID']['output'];
learning_content_page_id?: Maybe;
submitted_at?: Maybe;
+ task_completion_data?: Maybe;
updated_at: Scalars['DateTime']['output'];
};
@@ -798,7 +799,7 @@ export type UpsertAssignmentCompletionMutationVariables = Exact<{
}>;
-export type UpsertAssignmentCompletionMutation = { __typename?: 'Mutation', upsert_assignment_completion?: { __typename?: 'AssignmentCompletionMutation', assignment_completion?: { __typename?: 'AssignmentCompletionObjectType', id: any, completion_status: AssignmentAssignmentCompletionCompletionStatusChoices, submitted_at?: any | null, evaluation_submitted_at?: any | null, evaluation_points?: number | null, completion_data?: any | null } | null } | null };
+export type UpsertAssignmentCompletionMutation = { __typename?: 'Mutation', upsert_assignment_completion?: { __typename?: 'AssignmentCompletionMutation', assignment_completion?: { __typename?: 'AssignmentCompletionObjectType', id: any, completion_status: AssignmentAssignmentCompletionCompletionStatusChoices, submitted_at?: any | null, evaluation_submitted_at?: any | null, evaluation_points?: number | null, completion_data?: any | null, task_completion_data?: any | null } | null } | null };
type CoursePageFieldsAssignmentObjectTypeFragment = { __typename?: 'AssignmentObjectType', title?: string | null, id?: string | null, slug?: string | null, content_type?: string | null, frontend_url?: string | null } & { ' $fragmentName'?: 'CoursePageFieldsAssignmentObjectTypeFragment' };
@@ -836,7 +837,7 @@ export type AssignmentCompletionQueryQueryVariables = Exact<{
export type AssignmentCompletionQueryQuery = { __typename?: 'Query', assignment?: { __typename?: 'AssignmentObjectType', assignment_type: AssignmentAssignmentAssignmentTypeChoices, needs_expert_evaluation: boolean, max_points?: number | null, content_type?: string | null, effort_required: string, evaluation_description: string, evaluation_document_url: string, evaluation_tasks?: any | null, id?: string | null, intro_text: string, performance_objectives?: any | null, slug?: string | null, tasks?: any | null, title?: string | null, translation_key?: string | null, competence_certificate?: (
{ __typename?: 'CompetenceCertificateObjectType' }
& { ' $fragmentRefs'?: { 'CoursePageFieldsCompetenceCertificateObjectTypeFragment': CoursePageFieldsCompetenceCertificateObjectTypeFragment } }
- ) | null } | null, assignment_completion?: { __typename?: 'AssignmentCompletionObjectType', id: any, completion_status: AssignmentAssignmentCompletionCompletionStatusChoices, submitted_at?: any | null, evaluation_submitted_at?: any | null, evaluation_points?: number | null, evaluation_max_points?: number | null, evaluation_passed?: boolean | null, edoniq_extended_time_flag: boolean, completion_data?: any | null, evaluation_user?: { __typename?: 'UserType', id: any } | null, assignment_user: { __typename?: 'UserType', id: any } } | null };
+ ) | null } | null, assignment_completion?: { __typename?: 'AssignmentCompletionObjectType', id: any, completion_status: AssignmentAssignmentCompletionCompletionStatusChoices, submitted_at?: any | null, evaluation_submitted_at?: any | null, evaluation_points?: number | null, evaluation_max_points?: number | null, evaluation_passed?: boolean | null, edoniq_extended_time_flag: boolean, completion_data?: any | null, task_completion_data?: any | null, evaluation_user?: { __typename?: 'UserType', id: any } | null, assignment_user: { __typename?: 'UserType', id: any } } | null };
export type CourseQueryQueryVariables = Exact<{
courseId: Scalars['ID']['input'];
@@ -911,9 +912,9 @@ export type SendFeedbackMutationMutation = { __typename?: 'Mutation', send_feedb
export const CoursePageFieldsFragmentDoc = {"kind":"Document","definitions":[{"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 AttendanceCheckMutationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AttendanceCheckMutation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"attendanceCourseId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"attendanceUserList"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NamedType","name":{"kind":"Name","value":"AttendanceUserInputType"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"update_course_session_attendance_course_users"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"attendanceCourseId"}}},{"kind":"Argument","name":{"kind":"Name","value":"attendance_user_list"},"value":{"kind":"Variable","name":{"kind":"Name","value":"attendanceUserList"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"course_session_attendance_course"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"attendance_user_list"},"selectionSet":{"kind":"SelectionSet","selections":[{"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":"status"}}]}}]}}]}}]}}]} as unknown as DocumentNode;
-export const UpsertAssignmentCompletionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpsertAssignmentCompletion"},"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"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"completionStatus"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"AssignmentCompletionStatus"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"completionDataString"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"evaluationPoints"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"initializeCompletion"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"upsert_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":"learning_content_page_id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"learningContentId"}}},{"kind":"Argument","name":{"kind":"Name","value":"assignment_user_id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"assignmentUserId"}}},{"kind":"Argument","name":{"kind":"Name","value":"completion_status"},"value":{"kind":"Variable","name":{"kind":"Name","value":"completionStatus"}}},{"kind":"Argument","name":{"kind":"Name","value":"completion_data_string"},"value":{"kind":"Variable","name":{"kind":"Name","value":"completionDataString"}}},{"kind":"Argument","name":{"kind":"Name","value":"evaluation_points"},"value":{"kind":"Variable","name":{"kind":"Name","value":"evaluationPoints"}}},{"kind":"Argument","name":{"kind":"Name","value":"initialize_completion"},"value":{"kind":"Variable","name":{"kind":"Name","value":"initializeCompletion"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"assignment_completion"},"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_points"}},{"kind":"Field","name":{"kind":"Name","value":"completion_data"}}]}}]}}]}}]} as unknown as DocumentNode;
+export const UpsertAssignmentCompletionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpsertAssignmentCompletion"},"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"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"completionStatus"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"AssignmentCompletionStatus"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"completionDataString"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"evaluationPoints"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"initializeCompletion"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"upsert_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":"learning_content_page_id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"learningContentId"}}},{"kind":"Argument","name":{"kind":"Name","value":"assignment_user_id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"assignmentUserId"}}},{"kind":"Argument","name":{"kind":"Name","value":"completion_status"},"value":{"kind":"Variable","name":{"kind":"Name","value":"completionStatus"}}},{"kind":"Argument","name":{"kind":"Name","value":"completion_data_string"},"value":{"kind":"Variable","name":{"kind":"Name","value":"completionDataString"}}},{"kind":"Argument","name":{"kind":"Name","value":"evaluation_points"},"value":{"kind":"Variable","name":{"kind":"Name","value":"evaluationPoints"}}},{"kind":"Argument","name":{"kind":"Name","value":"initialize_completion"},"value":{"kind":"Variable","name":{"kind":"Name","value":"initializeCompletion"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"assignment_completion"},"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_points"}},{"kind":"Field","name":{"kind":"Name","value":"completion_data"}},{"kind":"Field","name":{"kind":"Name","value":"task_completion_data"}}]}}]}}]}}]} as unknown as DocumentNode;
export const AttendanceCheckQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"attendanceCheckQuery"},"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_attendance_course"},"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":"attendance_user_list"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user_id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]}}]} as unknown as DocumentNode;
-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":"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":"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 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":"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 CourseQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"courseQuery"},"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"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"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":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"category_name"}},{"kind":"Field","name":{"kind":"Name","value":"learning_path"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} 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":"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"}},{"kind":"Field","name":{"kind":"Name","value":"circle"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CoursePageFields"}}]}}]}}]}}]}}]}}]}},{"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":"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;
diff --git a/client/src/gql/schema.graphql b/client/src/gql/schema.graphql
index 87b8817d..8120f07c 100644
--- a/client/src/gql/schema.graphql
+++ b/client/src/gql/schema.graphql
@@ -312,6 +312,7 @@ type AssignmentCompletionObjectType {
completion_status: AssignmentAssignmentCompletionCompletionStatusChoices!
completion_data: GenericScalar
additional_json_data: JSONString!
+ task_completion_data: GenericScalar
learning_content_page_id: ID
}
diff --git a/client/src/graphql/mutations.ts b/client/src/graphql/mutations.ts
index 19ee3349..f2cf586a 100644
--- a/client/src/graphql/mutations.ts
+++ b/client/src/graphql/mutations.ts
@@ -51,6 +51,7 @@ export const UPSERT_ASSIGNMENT_COMPLETION_MUTATION = graphql(`
evaluation_submitted_at
evaluation_points
completion_data
+ task_completion_data
}
}
}
diff --git a/client/src/graphql/queries.ts b/client/src/graphql/queries.ts
index 68268a6d..91bfd7c7 100644
--- a/client/src/graphql/queries.ts
+++ b/client/src/graphql/queries.ts
@@ -71,6 +71,7 @@ export const ASSIGNMENT_COMPLETION_QUERY = graphql(`
evaluation_passed
edoniq_extended_time_flag
completion_data
+ task_completion_data
}
}
`);
diff --git a/client/src/pages/learningPath/learningContentPage/assignment/AssignmentTaskView.vue b/client/src/pages/learningPath/learningContentPage/assignment/AssignmentTaskView.vue
index 3d25b001..0fb5bcb8 100644
--- a/client/src/pages/learningPath/learningContentPage/assignment/AssignmentTaskView.vue
+++ b/client/src/pages/learningPath/learningContentPage/assignment/AssignmentTaskView.vue
@@ -8,12 +8,14 @@ import type {
AssignmentCompletionData,
AssignmentTask,
UserDataConfirmation,
+ UserDataFile,
UserDataText,
} from "@/types";
import { useMutation } from "@urql/vue";
import { useDebounceFn } from "@vueuse/core";
import log from "loglevel";
import { computed, reactive } from "vue";
+import AttachmentSection from "@/pages/learningPath/learningContentPage/assignment/AttachmentSection.vue";
const props = defineProps<{
assignmentId: string;
@@ -77,6 +79,16 @@ const onUpdateConfirmation = (id: string, value: boolean) => {
upsertAssignmentCompletion(data);
};
+const onUpdateFile = (taskId: string, value: string | null) => {
+ const data: AssignmentCompletionData = {};
+ data[taskId] = {
+ user_data: {
+ fileId: value,
+ } as UserDataFile,
+ };
+ upsertAssignmentCompletion(data);
+};
+
const getBlockData = (id: string) => {
const userData = getCompletionDataForUserInput(id)?.user_data;
if (userData && "text" in userData) {
@@ -95,6 +107,15 @@ const onToggleCheckbox = (id: string) => {
const completionStatus = computed(() => {
return props.assignmentCompletion?.completion_status ?? "IN_PROGRESS";
});
+
+const taskFileInfo = computed(() => {
+ if (!props.assignmentCompletion) {
+ return null;
+ }
+ const taskUserData =
+ props.assignmentCompletion.task_completion_data[props.task.id]?.user_data;
+ return taskUserData?.fileInfo ?? null;
+});
@@ -133,12 +154,11 @@ const completionStatus = computed(() => {
-
-
-
Datei hochladen
-
-
- Mögliche Formate: .JPG, .PNG, .PDF, .DOC, .MOV, .PPT
-
-
+
diff --git a/client/src/pages/learningPath/learningContentPage/assignment/AttachmentSection.vue b/client/src/pages/learningPath/learningContentPage/assignment/AttachmentSection.vue
new file mode 100644
index 00000000..5d59db4b
--- /dev/null
+++ b/client/src/pages/learningPath/learningContentPage/assignment/AttachmentSection.vue
@@ -0,0 +1,85 @@
+
+
+
+
+
{{ $t("a.Datei hochladen") }}
+
+
+ {{ $t("a.Laden...") }}
+
+
+
+
+
+
+
+ {{ $t("a.Datei auswählen") }}
+
+
+ {{ $t("a.Mögliche Formate") }}: .JPG, .PNG, .PDF, .DOC, .MOV, .PPT
+
+
+
+
+
+
{{ selectedFile.name }}
+
+
+
+
+
+
+
+
+
+
+ {{ $t("a.Datei kann nicht gespeichert werden.") }}
+
+
+
+
diff --git a/client/src/services/files.ts b/client/src/services/files.ts
index c2ada811..3a6ae62d 100644
--- a/client/src/services/files.ts
+++ b/client/src/services/files.ts
@@ -20,7 +20,7 @@ async function startFileUpload(fileData: DocumentUploadData, courseSessionId: st
});
}
-function uploadFile(fileData: FileData, file: File) {
+export async function uploadFile(fileData: FileData, file: File) {
if (fileData.fields) {
return s3Upload(fileData, file);
} else {
@@ -44,7 +44,7 @@ function directUpload(fileData: FileData, file: File) {
// @ts-ignore
options.headers["X-CSRFToken"] = getCookieValue("csrftoken");
- handleUpload(fileData.url, options);
+ return handleUpload(fileData.url, options);
}
function s3Upload(fileData: FileData, file: File) {
@@ -63,12 +63,13 @@ function s3Upload(fileData: FileData, file: File) {
return handleUpload(fileData.url, options);
}
-function handleUpload(url: string, options: RequestInit) {
- return itFetch(url, options).then((response) => {
- return response.json().catch(() => {
- return Promise.resolve(null);
- });
- });
+async function handleUpload(url: string, options: RequestInit) {
+ const response = await itFetch(url, options);
+ try {
+ return await response.json();
+ } catch (e) {
+ return Promise.resolve(null);
+ }
}
export async function uploadCircleDocument(
@@ -105,3 +106,10 @@ export async function deleteCircleDocument(documentId: string, bustCacheUrlKey =
export async function fetchCourseSessionDocuments(courseSessionId: string) {
return itGetCached(`/api/core/document/list/${courseSessionId}/`);
}
+
+export async function presignUpload(file: File) {
+ return await itPost(`/api/core/storage/presign/`, {
+ file_type: file.type,
+ file_name: file.name,
+ });
+}
diff --git a/client/src/types.ts b/client/src/types.ts
index d8b92282..391184e5 100644
--- a/client/src/types.ts
+++ b/client/src/types.ts
@@ -395,7 +395,9 @@ export interface PerformanceCriteria extends BaseCourseWagtailPage {
readonly competence_id: string;
readonly circle: CircleLight;
readonly course_category: CourseCategory;
- readonly learning_unit: BaseCourseWagtailPage & { evaluate_url: string };
+ readonly learning_unit: BaseCourseWagtailPage & {
+ evaluate_url: string;
+ };
}
export interface CompetencePage extends BaseCourseWagtailPage {
@@ -602,6 +604,17 @@ export interface UserDataConfirmation {
confirmation: boolean;
}
+export interface UserDataFile {
+ fileId?: string;
+ fileInfo?: UserDataFileInfo;
+}
+
+export interface UserDataFileInfo {
+ id: string;
+ name: string;
+ url: string;
+}
+
export interface ExpertData {
points?: number;
text?: string;
@@ -613,11 +626,21 @@ export interface AssignmentCompletionData {
// "": {"user_data": {"confirmation": true}},
// }
[key: string]: {
- user_data?: UserDataText | UserDataConfirmation;
+ user_data?: UserDataText | UserDataConfirmation | UserDataFile;
expert_data?: ExpertData;
};
}
+export interface AssignmentTaskCompletionData {
+ // {
+ // "": {"user_data": {"text": "some text from user"}},
+ // "": {"user_data": {"confirmation": true}},
+ // }
+ [key: string]: {
+ user_data?: UserDataFile;
+ };
+}
+
export interface AssignmentCompletion {
id: string;
created_at: string;
@@ -630,6 +653,7 @@ export interface AssignmentCompletion {
completion_status: AssignmentCompletionStatus;
evaluation_user: string | null;
completion_data: AssignmentCompletionData;
+ task_completion_data: AssignmentTaskCompletionData;
edoniq_extended_time_flag: boolean;
evaluation_points: number | null;
evaluation_max_points: number | null;
diff --git a/server/config/urls.py b/server/config/urls.py
index 44c43545..6bb8ec03 100644
--- a/server/config/urls.py
+++ b/server/config/urls.py
@@ -10,6 +10,9 @@ from django.views import defaults as default_views
from django.views.decorators.csrf import csrf_exempt
from django_ratelimit.exceptions import Ratelimited
from graphene_django.views import GraphQLView
+from wagtail import urls as wagtail_urls
+from wagtail.admin import urls as wagtailadmin_urls
+from wagtail.documents import urls as wagtaildocs_urls
from vbv_lernwelt.assignment.views import request_assignment_completion_status
from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
@@ -47,15 +50,13 @@ from vbv_lernwelt.feedback.views import (
get_expert_feedbacks_for_course,
get_feedback_for_circle,
)
+from vbv_lernwelt.files.views import presign
from vbv_lernwelt.importer.views import (
coursesessions_students_import,
coursesessions_trainers_import,
t2l_sync,
)
from vbv_lernwelt.notify.views import email_notification_settings
-from wagtail import urls as wagtail_urls
-from wagtail.admin import urls as wagtailadmin_urls
-from wagtail.documents import urls as wagtaildocs_urls
class SignedIntConverter(IntConverter):
@@ -143,6 +144,10 @@ urlpatterns = [
get_course_session_documents,
name='get_course_session_documents'),
+ # file storage
+ path(r'api/core/storage/presign/', presign,
+ name='storage_presign'),
+
# feedback
path(r'api/core/feedback//summary/',
get_expert_feedbacks_for_course,
diff --git a/server/requirements/requirements.in b/server/requirements/requirements.in
index 76388ad2..3b5ae91e 100644
--- a/server/requirements/requirements.in
+++ b/server/requirements/requirements.in
@@ -25,7 +25,6 @@ django-ratelimit
django-ipware
django-csp
django-storages
-django-storages[azure]
django-notifications-hq
django-jsonform
django-constance
diff --git a/server/requirements/requirements.txt b/server/requirements/requirements.txt
index a0818296..d526fed6 100644
--- a/server/requirements/requirements.txt
+++ b/server/requirements/requirements.txt
@@ -33,7 +33,6 @@ azure-identity==1.14.0
azure-storage-blob==12.17.0
# via
# -r requirements.in
- # django-storages
bcrypt==4.0.1
# via paramiko
beautifulsoup4==4.11.2
@@ -129,7 +128,7 @@ django-ratelimit==4.1.0
# via -r requirements.in
django-redis==5.3.0
# via -r requirements.in
-django-storages[azure]==1.13.2
+django-storages==1.13.2
# via -r requirements.in
django-taggit==4.0.0
# via wagtail
diff --git a/server/vbv_lernwelt/assignment/graphql/types.py b/server/vbv_lernwelt/assignment/graphql/types.py
index d98e40b0..d1681bdd 100644
--- a/server/vbv_lernwelt/assignment/graphql/types.py
+++ b/server/vbv_lernwelt/assignment/graphql/types.py
@@ -13,6 +13,7 @@ from vbv_lernwelt.learnpath.graphql.types import LearningContentInterface
class AssignmentCompletionObjectType(DjangoObjectType):
completion_data = GenericScalar()
+ task_completion_data = GenericScalar()
learning_content_page_id = graphene.ID(source="learning_content_page_id")
class Meta:
@@ -35,6 +36,7 @@ class AssignmentCompletionObjectType(DjangoObjectType):
"evaluation_points",
"evaluation_passed",
"evaluation_max_points",
+ "task_completion_data"
)
@@ -72,11 +74,11 @@ class AssignmentObjectType(DjangoObjectType):
return self.find_attached_learning_content()
def resolve_completion(
- self,
- info,
- course_session_id,
- learning_content_page_id=None,
- assignment_user_id=None,
+ self,
+ info,
+ course_session_id,
+ learning_content_page_id=None,
+ assignment_user_id=None,
):
if learning_content_page_id is None:
lp = self.find_attached_learning_content()
@@ -92,17 +94,17 @@ class AssignmentObjectType(DjangoObjectType):
def resolve_assignment_completion(
- info,
- assignment_id,
- course_session_id,
- learning_content_page_id=None,
- assignment_user_id=None,
+ info,
+ assignment_id,
+ course_session_id,
+ learning_content_page_id=None,
+ assignment_user_id=None,
):
if assignment_user_id is None:
assignment_user_id = info.context.user.id
if str(assignment_user_id) == str(info.context.user.id) or is_course_session_expert(
- info.context.user, course_session_id
+ info.context.user, course_session_id
):
course_id = CourseSession.objects.get(id=course_session_id).course_id
if has_course_access(info.context.user, course_id):
diff --git a/server/vbv_lernwelt/assignment/models.py b/server/vbv_lernwelt/assignment/models.py
index d5ab6d74..8d4abd0a 100644
--- a/server/vbv_lernwelt/assignment/models.py
+++ b/server/vbv_lernwelt/assignment/models.py
@@ -1,3 +1,4 @@
+import copy
import uuid
from enum import Enum
@@ -16,6 +17,7 @@ from vbv_lernwelt.core.constants import (
from vbv_lernwelt.core.model_utils import find_available_slug
from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.models import CourseBasePage
+from vbv_lernwelt.files.models import UploadFile
class AssignmentListPage(CourseBasePage):
@@ -302,7 +304,7 @@ class AssignmentCompletionStatus(Enum):
def is_valid_assignment_completion_status(
- completion_status: AssignmentCompletionStatus,
+ completion_status: AssignmentCompletionStatus,
):
return completion_status.value in AssignmentCompletionStatus.__members__
@@ -370,6 +372,34 @@ class AssignmentCompletion(models.Model):
"""
return f"{self.course_session.course.get_cockpit_url()}/assignment/{self.assignment.id}/{self.assignment_user.id}"
+ @property
+ def task_completion_data(self):
+ data = {}
+ for task in self.assignment.tasks:
+ data[task.id] = get_task_data(task, self.completion_data)
+ return data
+
+
+def get_file_info(file_id):
+ file_info = UploadFile.objects.filter(id=file_id).first()
+ if file_info:
+ return {
+ "id": str(file_info.id),
+ "name": file_info.original_file_name,
+ "url": file_info.url,
+ }
+
+
+def get_task_data(task, completion_data):
+ task_data = copy.deepcopy(completion_data.get(task.id, {}))
+ user_data = task_data.get("user_data", {})
+ file_id = user_data.get("fileId")
+
+ if file_id:
+ user_data["fileInfo"] = get_file_info(file_id)
+
+ return task_data
+
class AssignmentCompletionAuditLog(models.Model):
"""
diff --git a/server/vbv_lernwelt/assignment/serializers.py b/server/vbv_lernwelt/assignment/serializers.py
index 78791487..746aa6c2 100644
--- a/server/vbv_lernwelt/assignment/serializers.py
+++ b/server/vbv_lernwelt/assignment/serializers.py
@@ -17,6 +17,7 @@ class AssignmentCompletionSerializer(serializers.ModelSerializer):
"course_session",
"completion_status",
"completion_data",
+ "task_completion_data",
"evaluation_user",
"additional_json_data",
"evaluation_points",
diff --git a/server/vbv_lernwelt/assignment/services.py b/server/vbv_lernwelt/assignment/services.py
index 87d31695..6f30df5e 100644
--- a/server/vbv_lernwelt/assignment/services.py
+++ b/server/vbv_lernwelt/assignment/services.py
@@ -108,8 +108,8 @@ def update_assignment_completion(
)
if (
- completion_status == AssignmentCompletionStatus.IN_PROGRESS
- and ac.completion_status != "IN_PROGRESS"
+ completion_status == AssignmentCompletionStatus.IN_PROGRESS
+ and ac.completion_status != "IN_PROGRESS"
):
raise serializers.ValidationError(
{
@@ -179,6 +179,14 @@ def update_assignment_completion(
ac.edoniq_extended_time_flag = edoniq_extended_time_flag
ac.additional_json_data = ac.additional_json_data | additional_json_data
+ task_ids = [task.id for task in assignment.tasks]
+
+ for key, value in completion_data.items():
+ if key in task_ids:
+ stored_entry = ac.completion_data.get(key, {})
+ stored_entry.update(value)
+ ac.completion_data[key] = stored_entry
+
# TODO: make more validation of the provided input -> maybe with graphql
completion_data = _remove_unknown_entries(assignment, completion_data)
for key, value in completion_data.items():
@@ -189,9 +197,9 @@ def update_assignment_completion(
if copy_task_data:
# copy over the question data, so that we don't lose the context
- substasks = assignment.get_input_tasks()
+ sub_tasks = assignment.get_input_tasks()
for key, value in ac.completion_data.items():
- task_data = find_first(substasks, pred=lambda x: x["id"] == key)
+ task_data = find_first(sub_tasks, pred=lambda x: x["id"] == key)
if task_data:
ac.completion_data[key].update(task_data)
diff --git a/server/vbv_lernwelt/assignment/tests/test_graphql.py b/server/vbv_lernwelt/assignment/tests/test_graphql.py
index debebc04..dea42c5f 100644
--- a/server/vbv_lernwelt/assignment/tests/test_graphql.py
+++ b/server/vbv_lernwelt/assignment/tests/test_graphql.py
@@ -1,6 +1,7 @@
import json
from datetime import date
+from django.core.files.uploadedfile import SimpleUploadedFile
from django.utils import timezone
from graphene_django.utils import GraphQLTestCase
@@ -14,6 +15,7 @@ from vbv_lernwelt.core.models import User
from vbv_lernwelt.core.utils import find_first
from vbv_lernwelt.course.creators.test_course import create_test_course
from vbv_lernwelt.course.models import CourseSession
+from vbv_lernwelt.files.models import UploadFile
from vbv_lernwelt.notify.models import Notification
@@ -31,6 +33,7 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
slug="test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"
)
self.assignment_subtasks = self.assignment.filter_user_subtasks()
+ self.assignment_task_ids = [t.id for t in self.assignment.tasks]
# self.client.force_login(self.trainer)
@@ -40,9 +43,27 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
self.assignment_subtasks, pred=lambda x: x["type"] == "user_text_input"
)
+ task_id = self.assignment_task_ids[0]
+
+ # Create file
+ uploaded_file = SimpleUploadedFile("file.txt", b"file_content")
+ file = UploadFile(
+ original_file_name="file.txt",
+ file_name="file.txt",
+ file_type="text/plain",
+ uploaded_by=self.student,
+ file=uploaded_file,
+ )
+ file.full_clean()
+ file.save()
+
+ file_id = str(file.id)
+ file_url = file.url
+
completion_data_string = json.dumps(
{
user_text_input["id"]: {"user_data": {"text": "Hallo via API"}},
+ task_id: {"user_data": {"fileId": file_id}},
}
).replace('"', '\\"')
@@ -58,6 +79,7 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
id
completion_status
completion_data
+ task_completion_data
assignment_user {{ id }}
assignment {{ id }}
}}
@@ -79,9 +101,25 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
data["completion_data"],
{
user_text_input["id"]: {"user_data": {"text": "Hallo via API"}},
+ task_id: {"user_data": {"fileId": file_id}}
},
)
+ task_data = data["task_completion_data"][task_id]
+ self.assertDictEqual(
+ task_data,
+ {
+ "user_data":
+ {
+ "fileId": file_id,
+ "fileInfo": {
+ "id": file_id,
+ "name": 'file.txt',
+ "url": file_url}
+ }
+ }
+ )
+
# check DB data
db_entry = AssignmentCompletion.objects.get(
assignment_user=self.student,
@@ -93,6 +131,7 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
db_entry.completion_data,
{
user_text_input["id"]: {"user_data": {"text": "Hallo via API"}},
+ task_id: {"user_data": {"fileId": file_id}}
},
)
@@ -136,6 +175,7 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
data["completion_data"],
{
user_text_input["id"]: {"user_data": {"text": "Hallo via API 2"}},
+ task_id: {"user_data": {"fileId": file_id}}
},
)
@@ -151,6 +191,7 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
db_entry.completion_data,
{
user_text_input["id"]: {"user_data": {"text": "Hallo via API 2"}},
+ task_id: {"user_data": {"fileId": file_id}}
},
)
@@ -227,7 +268,7 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
user_text_input = find_first(
subtasks,
pred=lambda x: (value := x.get("value"))
- and value.get("text", "").startswith(
+ and value.get("text", "").startswith(
"Gibt es zusätzliche Deckungen, die du der Person empfehlen würdest?"
),
)
diff --git a/server/vbv_lernwelt/course_session/models.py b/server/vbv_lernwelt/course_session/models.py
index 9c094de1..576599b5 100644
--- a/server/vbv_lernwelt/course_session/models.py
+++ b/server/vbv_lernwelt/course_session/models.py
@@ -92,7 +92,7 @@ class CourseSessionAttendanceCourse(models.Model):
class CourseSessionAssignment(models.Model):
"""
Auftrag
- - Geletitete Fallarbeit ist eine speziefische ausprägung eines Auftrags (assignment_type)
+ - Geleitete Fallarbeit ist eine spezifische Ausprägung eines Auftrags (assignment_type)
"""
@@ -138,8 +138,8 @@ class CourseSessionAssignment(models.Model):
url_expert = f"/course/{self.course_session.course.slug}/cockpit/assignment/{self.learning_content_id}?courseSessionId={self.course_session.id}"
if assignment_type in (
- AssignmentType.CASEWORK.value,
- AssignmentType.PREP_ASSIGNMENT.value,
+ AssignmentType.CASEWORK.value,
+ AssignmentType.PREP_ASSIGNMENT.value,
):
if not self.submission_deadline_id:
self.submission_deadline = DueDate.objects.create(
diff --git a/server/vbv_lernwelt/files/integrations.py b/server/vbv_lernwelt/files/integrations.py
index 7e7b5d5f..53f2a8c3 100644
--- a/server/vbv_lernwelt/files/integrations.py
+++ b/server/vbv_lernwelt/files/integrations.py
@@ -47,16 +47,21 @@ def s3_get_credentials() -> S3Credentials:
def s3_get_client():
credentials = s3_get_credentials()
+ # This is needed until https://github.com/boto/boto3/issues/3015 is fixed
+ s3 = boto3.client('s3', region_name=credentials.region_name)
+ endpoint_url = s3.meta.endpoint_url
+
return boto3.client(
service_name="s3",
aws_access_key_id=credentials.access_key_id,
aws_secret_access_key=credentials.secret_access_key,
region_name=credentials.region_name,
+ endpoint_url=endpoint_url
)
def s3_generate_presigned_post(
- *, file_path: str, file_type: str, file_name: str
+ *, file_path: str, file_type: str, file_name: str
) -> Dict[str, Any]:
credentials = s3_get_credentials()
s3_client = s3_get_client()
@@ -99,6 +104,7 @@ def s3_generate_presigned_post(
["starts-with", "$Content-Disposition", ""],
],
ExpiresIn=expires_in,
+
)
return presigned_data
diff --git a/server/vbv_lernwelt/files/serializers.py b/server/vbv_lernwelt/files/serializers.py
new file mode 100644
index 00000000..4d6fd14d
--- /dev/null
+++ b/server/vbv_lernwelt/files/serializers.py
@@ -0,0 +1,6 @@
+from rest_framework import serializers
+
+
+class PresignInputSerializer(serializers.Serializer):
+ file_name = serializers.CharField()
+ file_type = serializers.CharField()
diff --git a/server/vbv_lernwelt/files/services.py b/server/vbv_lernwelt/files/services.py
index 36b8ccac..51de24ff 100644
--- a/server/vbv_lernwelt/files/services.py
+++ b/server/vbv_lernwelt/files/services.py
@@ -43,7 +43,7 @@ class FileStandardUploadService:
self.file_obj = file_obj
def _infer_file_name_and_type(
- self, file_name: str = "", file_type: str = ""
+ self, file_name: str = "", file_type: str = ""
) -> Tuple[str, str]:
if not file_name:
file_name = self.file_obj.name
@@ -80,7 +80,7 @@ class FileStandardUploadService:
@transaction.atomic
def update(
- self, file: UploadFile, file_name: str = "", file_type: str = ""
+ self, file: UploadFile, file_name: str = "", file_type: str = ""
) -> UploadFile:
_validate_file_size(self.file_obj)
@@ -114,7 +114,7 @@ class FileDirectUploadService:
@transaction.atomic
def start(
- self, file_name: str, file_type: str
+ self, file_name: str, file_type: str
) -> Tuple[UploadFile, Dict[str, Any]]:
file = UploadFile(
original_file_name=file_name,
@@ -134,19 +134,17 @@ class FileDirectUploadService:
file.file = file.file.field.attr_class(file, file.file.field, upload_path)
file.save()
- presigned_data: Dict[str, Any] = {}
-
if settings.FILE_UPLOAD_STORAGE == FileUploadStorage.S3.value:
- presigned_data = s3_generate_presigned_post(
+ pre_signed_data = s3_generate_presigned_post(
file_path=upload_path, file_type=file.file_type, file_name=file_name
)
else:
- presigned_data = {
+ pre_signed_data = {
"url": file_generate_local_upload_url(file_id=str(file.id)),
}
- return file, presigned_data
+ return file, pre_signed_data
@transaction.atomic
def finish(self, *, file: UploadFile) -> UploadFile:
diff --git a/server/vbv_lernwelt/files/views.py b/server/vbv_lernwelt/files/views.py
new file mode 100644
index 00000000..5afc101e
--- /dev/null
+++ b/server/vbv_lernwelt/files/views.py
@@ -0,0 +1,33 @@
+from rest_framework.decorators import api_view
+from rest_framework.response import Response
+
+from vbv_lernwelt.files.serializers import PresignInputSerializer
+from vbv_lernwelt.files.services import FileDirectUploadService
+
+
+@api_view(["POST"])
+def presign(request):
+ print("presign", request.data)
+
+ if not request.user.is_authenticated:
+ return Response(status=401)
+
+ serializer = PresignInputSerializer(data=request.data)
+ serializer.is_valid(raise_exception=True)
+
+ service = FileDirectUploadService(request.user)
+
+ upload_file, pre_signed_data = service.start(
+ file_name=serializer.validated_data["file_name"], file_type=serializer.validated_data["file_type"]
+ )
+
+ return Response(
+ data={
+ "pre_sign": pre_signed_data,
+ "file_info": {
+ "id": upload_file.id,
+ "name": upload_file.original_file_name,
+ "url": upload_file.url,
+ }
+ }
+ )
From 75351b9986b19c0cf79eee2cebba9cb610f60936 Mon Sep 17 00:00:00 2001
From: Reto Aebersold
Date: Wed, 4 Oct 2023 14:26:09 +0200
Subject: [PATCH 2/7] chore: format
---
server/config/urls.py | 6 ++---
.../vbv_lernwelt/assignment/graphql/types.py | 22 ++++++++---------
server/vbv_lernwelt/assignment/models.py | 2 +-
server/vbv_lernwelt/assignment/services.py | 4 ++--
.../assignment/tests/test_graphql.py | 24 ++++++++-----------
server/vbv_lernwelt/course_session/models.py | 4 ++--
server/vbv_lernwelt/files/integrations.py | 7 +++---
server/vbv_lernwelt/files/services.py | 6 ++---
server/vbv_lernwelt/files/views.py | 5 ++--
9 files changed, 38 insertions(+), 42 deletions(-)
diff --git a/server/config/urls.py b/server/config/urls.py
index 6bb8ec03..59544756 100644
--- a/server/config/urls.py
+++ b/server/config/urls.py
@@ -10,9 +10,6 @@ from django.views import defaults as default_views
from django.views.decorators.csrf import csrf_exempt
from django_ratelimit.exceptions import Ratelimited
from graphene_django.views import GraphQLView
-from wagtail import urls as wagtail_urls
-from wagtail.admin import urls as wagtailadmin_urls
-from wagtail.documents import urls as wagtaildocs_urls
from vbv_lernwelt.assignment.views import request_assignment_completion_status
from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
@@ -57,6 +54,9 @@ from vbv_lernwelt.importer.views import (
t2l_sync,
)
from vbv_lernwelt.notify.views import email_notification_settings
+from wagtail import urls as wagtail_urls
+from wagtail.admin import urls as wagtailadmin_urls
+from wagtail.documents import urls as wagtaildocs_urls
class SignedIntConverter(IntConverter):
diff --git a/server/vbv_lernwelt/assignment/graphql/types.py b/server/vbv_lernwelt/assignment/graphql/types.py
index d1681bdd..b66614a9 100644
--- a/server/vbv_lernwelt/assignment/graphql/types.py
+++ b/server/vbv_lernwelt/assignment/graphql/types.py
@@ -74,11 +74,11 @@ class AssignmentObjectType(DjangoObjectType):
return self.find_attached_learning_content()
def resolve_completion(
- self,
- info,
- course_session_id,
- learning_content_page_id=None,
- assignment_user_id=None,
+ self,
+ info,
+ course_session_id,
+ learning_content_page_id=None,
+ assignment_user_id=None,
):
if learning_content_page_id is None:
lp = self.find_attached_learning_content()
@@ -94,17 +94,17 @@ class AssignmentObjectType(DjangoObjectType):
def resolve_assignment_completion(
- info,
- assignment_id,
- course_session_id,
- learning_content_page_id=None,
- assignment_user_id=None,
+ info,
+ assignment_id,
+ course_session_id,
+ learning_content_page_id=None,
+ assignment_user_id=None,
):
if assignment_user_id is None:
assignment_user_id = info.context.user.id
if str(assignment_user_id) == str(info.context.user.id) or is_course_session_expert(
- info.context.user, course_session_id
+ info.context.user, course_session_id
):
course_id = CourseSession.objects.get(id=course_session_id).course_id
if has_course_access(info.context.user, course_id):
diff --git a/server/vbv_lernwelt/assignment/models.py b/server/vbv_lernwelt/assignment/models.py
index 8d4abd0a..e0de99d1 100644
--- a/server/vbv_lernwelt/assignment/models.py
+++ b/server/vbv_lernwelt/assignment/models.py
@@ -304,7 +304,7 @@ class AssignmentCompletionStatus(Enum):
def is_valid_assignment_completion_status(
- completion_status: AssignmentCompletionStatus,
+ completion_status: AssignmentCompletionStatus,
):
return completion_status.value in AssignmentCompletionStatus.__members__
diff --git a/server/vbv_lernwelt/assignment/services.py b/server/vbv_lernwelt/assignment/services.py
index 6f30df5e..72280d5f 100644
--- a/server/vbv_lernwelt/assignment/services.py
+++ b/server/vbv_lernwelt/assignment/services.py
@@ -108,8 +108,8 @@ def update_assignment_completion(
)
if (
- completion_status == AssignmentCompletionStatus.IN_PROGRESS
- and ac.completion_status != "IN_PROGRESS"
+ completion_status == AssignmentCompletionStatus.IN_PROGRESS
+ and ac.completion_status != "IN_PROGRESS"
):
raise serializers.ValidationError(
{
diff --git a/server/vbv_lernwelt/assignment/tests/test_graphql.py b/server/vbv_lernwelt/assignment/tests/test_graphql.py
index dea42c5f..023ff374 100644
--- a/server/vbv_lernwelt/assignment/tests/test_graphql.py
+++ b/server/vbv_lernwelt/assignment/tests/test_graphql.py
@@ -101,7 +101,7 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
data["completion_data"],
{
user_text_input["id"]: {"user_data": {"text": "Hallo via API"}},
- task_id: {"user_data": {"fileId": file_id}}
+ task_id: {"user_data": {"fileId": file_id}},
},
)
@@ -109,15 +109,11 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
self.assertDictEqual(
task_data,
{
- "user_data":
- {
- "fileId": file_id,
- "fileInfo": {
- "id": file_id,
- "name": 'file.txt',
- "url": file_url}
- }
- }
+ "user_data": {
+ "fileId": file_id,
+ "fileInfo": {"id": file_id, "name": "file.txt", "url": file_url},
+ }
+ },
)
# check DB data
@@ -131,7 +127,7 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
db_entry.completion_data,
{
user_text_input["id"]: {"user_data": {"text": "Hallo via API"}},
- task_id: {"user_data": {"fileId": file_id}}
+ task_id: {"user_data": {"fileId": file_id}},
},
)
@@ -175,7 +171,7 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
data["completion_data"],
{
user_text_input["id"]: {"user_data": {"text": "Hallo via API 2"}},
- task_id: {"user_data": {"fileId": file_id}}
+ task_id: {"user_data": {"fileId": file_id}},
},
)
@@ -191,7 +187,7 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
db_entry.completion_data,
{
user_text_input["id"]: {"user_data": {"text": "Hallo via API 2"}},
- task_id: {"user_data": {"fileId": file_id}}
+ task_id: {"user_data": {"fileId": file_id}},
},
)
@@ -268,7 +264,7 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
user_text_input = find_first(
subtasks,
pred=lambda x: (value := x.get("value"))
- and value.get("text", "").startswith(
+ and value.get("text", "").startswith(
"Gibt es zusätzliche Deckungen, die du der Person empfehlen würdest?"
),
)
diff --git a/server/vbv_lernwelt/course_session/models.py b/server/vbv_lernwelt/course_session/models.py
index 576599b5..a436fd95 100644
--- a/server/vbv_lernwelt/course_session/models.py
+++ b/server/vbv_lernwelt/course_session/models.py
@@ -138,8 +138,8 @@ class CourseSessionAssignment(models.Model):
url_expert = f"/course/{self.course_session.course.slug}/cockpit/assignment/{self.learning_content_id}?courseSessionId={self.course_session.id}"
if assignment_type in (
- AssignmentType.CASEWORK.value,
- AssignmentType.PREP_ASSIGNMENT.value,
+ AssignmentType.CASEWORK.value,
+ AssignmentType.PREP_ASSIGNMENT.value,
):
if not self.submission_deadline_id:
self.submission_deadline = DueDate.objects.create(
diff --git a/server/vbv_lernwelt/files/integrations.py b/server/vbv_lernwelt/files/integrations.py
index 53f2a8c3..a47899f2 100644
--- a/server/vbv_lernwelt/files/integrations.py
+++ b/server/vbv_lernwelt/files/integrations.py
@@ -48,7 +48,7 @@ def s3_get_client():
credentials = s3_get_credentials()
# This is needed until https://github.com/boto/boto3/issues/3015 is fixed
- s3 = boto3.client('s3', region_name=credentials.region_name)
+ s3 = boto3.client("s3", region_name=credentials.region_name)
endpoint_url = s3.meta.endpoint_url
return boto3.client(
@@ -56,12 +56,12 @@ def s3_get_client():
aws_access_key_id=credentials.access_key_id,
aws_secret_access_key=credentials.secret_access_key,
region_name=credentials.region_name,
- endpoint_url=endpoint_url
+ endpoint_url=endpoint_url,
)
def s3_generate_presigned_post(
- *, file_path: str, file_type: str, file_name: str
+ *, file_path: str, file_type: str, file_name: str
) -> Dict[str, Any]:
credentials = s3_get_credentials()
s3_client = s3_get_client()
@@ -104,7 +104,6 @@ def s3_generate_presigned_post(
["starts-with", "$Content-Disposition", ""],
],
ExpiresIn=expires_in,
-
)
return presigned_data
diff --git a/server/vbv_lernwelt/files/services.py b/server/vbv_lernwelt/files/services.py
index 51de24ff..de882bc0 100644
--- a/server/vbv_lernwelt/files/services.py
+++ b/server/vbv_lernwelt/files/services.py
@@ -43,7 +43,7 @@ class FileStandardUploadService:
self.file_obj = file_obj
def _infer_file_name_and_type(
- self, file_name: str = "", file_type: str = ""
+ self, file_name: str = "", file_type: str = ""
) -> Tuple[str, str]:
if not file_name:
file_name = self.file_obj.name
@@ -80,7 +80,7 @@ class FileStandardUploadService:
@transaction.atomic
def update(
- self, file: UploadFile, file_name: str = "", file_type: str = ""
+ self, file: UploadFile, file_name: str = "", file_type: str = ""
) -> UploadFile:
_validate_file_size(self.file_obj)
@@ -114,7 +114,7 @@ class FileDirectUploadService:
@transaction.atomic
def start(
- self, file_name: str, file_type: str
+ self, file_name: str, file_type: str
) -> Tuple[UploadFile, Dict[str, Any]]:
file = UploadFile(
original_file_name=file_name,
diff --git a/server/vbv_lernwelt/files/views.py b/server/vbv_lernwelt/files/views.py
index 5afc101e..30791398 100644
--- a/server/vbv_lernwelt/files/views.py
+++ b/server/vbv_lernwelt/files/views.py
@@ -18,7 +18,8 @@ def presign(request):
service = FileDirectUploadService(request.user)
upload_file, pre_signed_data = service.start(
- file_name=serializer.validated_data["file_name"], file_type=serializer.validated_data["file_type"]
+ file_name=serializer.validated_data["file_name"],
+ file_type=serializer.validated_data["file_type"],
)
return Response(
@@ -28,6 +29,6 @@ def presign(request):
"id": upload_file.id,
"name": upload_file.original_file_name,
"url": upload_file.url,
- }
+ },
}
)
From 7a9cf339f919b17c9ff41918da7017aca2b26561 Mon Sep 17 00:00:00 2001
From: Reto Aebersold
Date: Wed, 4 Oct 2023 15:19:30 +0200
Subject: [PATCH 3/7] add files to overview
---
.../AssignmentEvaluationPage.vue | 3 +++
.../assignment/AssignmentSubmissionResponses.vue | 14 ++++++++++++++
.../assignment/AssignmentSubmissionView.vue | 5 +++++
3 files changed, 22 insertions(+)
diff --git a/client/src/pages/cockpit/assignmentEvaluationPage/AssignmentEvaluationPage.vue b/client/src/pages/cockpit/assignmentEvaluationPage/AssignmentEvaluationPage.vue
index fd42b7c8..bb3fd1b0 100644
--- a/client/src/pages/cockpit/assignmentEvaluationPage/AssignmentEvaluationPage.vue
+++ b/client/src/pages/cockpit/assignmentEvaluationPage/AssignmentEvaluationPage.vue
@@ -110,6 +110,9 @@ const assignment = computed(
diff --git a/client/src/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionResponses.vue b/client/src/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionResponses.vue
index 32965cbc..da9fc215 100644
--- a/client/src/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionResponses.vue
+++ b/client/src/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionResponses.vue
@@ -3,12 +3,14 @@ import type {
Assignment,
AssignmentCompletionData,
AssignmentTask,
+ AssignmentTaskCompletionData,
UserDataText,
} from "@/types";
const props = defineProps<{
assignment: Assignment;
assignmentCompletionData: AssignmentCompletionData;
+ assignmentTaskCompletionData: AssignmentTaskCompletionData;
allowEdit: boolean;
}>();
@@ -48,5 +50,17 @@ const emit = defineEmits<{
{{ (assignmentCompletionData[taskBlock.id].user_data as UserDataText).text }}
+
+
diff --git a/client/src/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionView.vue b/client/src/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionView.vue
index d132b180..6a4163eb 100644
--- a/client/src/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionView.vue
+++ b/client/src/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionView.vue
@@ -63,6 +63,10 @@ const completionData = computed(() => {
return props.assignmentCompletion?.completion_data ?? {};
});
+const completionTaskData = computed(() => {
+ return props.assignmentCompletion?.task_completion_data ?? {};
+});
+
const canSubmit = computed(() => {
return (
!state.confirmInput ||
@@ -179,6 +183,7 @@ const onSubmit = async () => {
From de1949407b135f523d7905146de882ae59ca7484 Mon Sep 17 00:00:00 2001
From: Reto Aebersold
Date: Tue, 10 Oct 2023 17:03:59 +0200
Subject: [PATCH 4/7] disable doc updates if assigment not in progress
---
.../assignment/AssignmentTaskView.vue | 1 +
.../assignment/AttachmentSection.vue | 24 +++++++++++++++----
2 files changed, 21 insertions(+), 4 deletions(-)
diff --git a/client/src/pages/learningPath/learningContentPage/assignment/AssignmentTaskView.vue b/client/src/pages/learningPath/learningContentPage/assignment/AssignmentTaskView.vue
index 0fb5bcb8..37108815 100644
--- a/client/src/pages/learningPath/learningContentPage/assignment/AssignmentTaskView.vue
+++ b/client/src/pages/learningPath/learningContentPage/assignment/AssignmentTaskView.vue
@@ -157,6 +157,7 @@ const taskFileInfo = computed(() => {
-import { ref } from "vue";
+import { ref, watch } from "vue";
import { presignUpload, uploadFile } from "@/services/files";
import type { UserDataFileInfo } from "@/types";
const props = defineProps<{
fileInfo: UserDataFileInfo | null;
+ readOnly: boolean;
}>();
const emit = defineEmits(["fileUploaded", "fileDeleted"]);
-const selectedFile = ref(props.fileInfo);
+const selectedFile = ref();
+
+watch(
+ () => props.fileInfo,
+ (newVal) => {
+ selectedFile.value = newVal;
+ },
+ { immediate: true }
+);
const loading = ref(false);
const uploadError = ref(false);
@@ -49,8 +58,12 @@ function handleDelete() {
-
+
{{ $t("a.Mögliche Formate") }}: .JPG, .PNG, .PDF, .DOC, .MOV, .PPT
+
+ {{ $t("a.Maximale Dateigrösse") }}: 20 MB
+
{{ selectedFile.name }}
-
+
From 9d64c27b04d5b205b441e71eb6156d05b5b86034 Mon Sep 17 00:00:00 2001
From: Reto Aebersold
Date: Tue, 10 Oct 2023 17:14:02 +0200
Subject: [PATCH 5/7] caprover app uses S3 storage
---
caprover_create_app.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/caprover_create_app.py b/caprover_create_app.py
index 00688bf5..50a55ccd 100644
--- a/caprover_create_app.py
+++ b/caprover_create_app.py
@@ -87,6 +87,11 @@ def main(app_name, image_name, environment_file):
"IT_DJANGO_SECRET_KEY": env.str(
"IT_DJANGO_SECRET_KEY", generate_random_string(63)
),
+ "AWS_S3_ACCESS_KEY_ID": env.str("AWS_S3_ACCESS_KEY_ID", ""),
+ "AWS_S3_SECRET_ACCESS_KEY": env.str("AWS_S3_SECRET_ACCESS_KEY", ""),
+ "AWS_S3_REGION_NAME": "eu-central-1",
+ "AWS_STORAGE_BUCKET_NAME": "myvbv-dev.iterativ.ch",
+ "FILE_UPLOAD_STORAGE": "s3",
"IT_DJANGO_DEBUG": "false",
"IT_SERVE_VUE": "false",
"IT_ALLOW_LOCAL_LOGIN": "true",
From 4cc942a1ee6fc97d29305a2ad22157c5c4d03049 Mon Sep 17 00:00:00 2001
From: Reto Aebersold
Date: Wed, 11 Oct 2023 10:01:04 +0200
Subject: [PATCH 6/7] chore: format
---
server/vbv_lernwelt/assignment/graphql/types.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/server/vbv_lernwelt/assignment/graphql/types.py b/server/vbv_lernwelt/assignment/graphql/types.py
index b66614a9..33687377 100644
--- a/server/vbv_lernwelt/assignment/graphql/types.py
+++ b/server/vbv_lernwelt/assignment/graphql/types.py
@@ -36,7 +36,7 @@ class AssignmentCompletionObjectType(DjangoObjectType):
"evaluation_points",
"evaluation_passed",
"evaluation_max_points",
- "task_completion_data"
+ "task_completion_data",
)
From 5a3753f510f51ea005294f29e9439af1f90bc6ef Mon Sep 17 00:00:00 2001
From: Daniel Egger
Date: Wed, 11 Oct 2023 16:33:54 +0200
Subject: [PATCH 7/7] Add `file_submission_required=True` to test data creation
script
---
.../assignment/creators/create_assignments.py | 2 +-
server/vbv_lernwelt/files/views.py | 10 +++++++++-
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/server/vbv_lernwelt/assignment/creators/create_assignments.py b/server/vbv_lernwelt/assignment/creators/create_assignments.py
index fc21f8af..d51c9c7f 100644
--- a/server/vbv_lernwelt/assignment/creators/create_assignments.py
+++ b/server/vbv_lernwelt/assignment/creators/create_assignments.py
@@ -254,6 +254,7 @@ def create_uk_fahrzeug_casework(course_id=COURSE_UK, competence_certificate=None
"task",
TaskBlockFactory(
title="Teilaufgabe 1: Beispiel einer Versicherungspolice finden",
+ file_submission_required=True,
# it is hard to create a StreamValue programmatically, we have to
# create a `StreamValue` manually. Ask Daniel and/or Ramon
content=StreamValue(
@@ -317,7 +318,6 @@ def create_uk_fahrzeug_casework(course_id=COURSE_UK, competence_certificate=None
"task",
TaskBlockFactory(
title="Teilaufgabe 3: Aktuelle Versicherung",
- # TODO: add document upload
content=StreamValue(
TaskContentStreamBlock(),
stream_data=[
diff --git a/server/vbv_lernwelt/files/views.py b/server/vbv_lernwelt/files/views.py
index 30791398..50c94a73 100644
--- a/server/vbv_lernwelt/files/views.py
+++ b/server/vbv_lernwelt/files/views.py
@@ -1,13 +1,21 @@
+import structlog
from rest_framework.decorators import api_view
from rest_framework.response import Response
from vbv_lernwelt.files.serializers import PresignInputSerializer
from vbv_lernwelt.files.services import FileDirectUploadService
+logger = structlog.get_logger(__name__)
+
@api_view(["POST"])
def presign(request):
- print("presign", request.data)
+ logger.debug(
+ "presign request",
+ file_type=request.data.get("file_type"),
+ file_name=request.data.get("file_name"),
+ label="file_upload",
+ )
if not request.user.is_authenticated:
return Response(status=401)