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",
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/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 () => {
diff --git a/client/src/pages/learningPath/learningContentPage/assignment/AssignmentTaskView.vue b/client/src/pages/learningPath/learningContentPage/assignment/AssignmentTaskView.vue
index 3d25b001..37108815 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,12 @@ 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..7ed068be
--- /dev/null
+++ b/client/src/pages/learningPath/learningContentPage/assignment/AttachmentSection.vue
@@ -0,0 +1,101 @@
+
+
+
+
+
{{ $t("a.Datei hochladen") }}
+
+
+ {{ $t("a.Laden...") }}
+
+
+
+
+
+
+
+ {{ $t("a.Datei auswählen") }}
+
+
+ {{ $t("a.Mögliche Formate") }}: .JPG, .PNG, .PDF, .DOC, .MOV, .PPT
+
+
+ {{ $t("a.Maximale Dateigrösse") }}: 20 MB
+
+
+
+
+
+
{{ 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..59544756 100644
--- a/server/config/urls.py
+++ b/server/config/urls.py
@@ -47,6 +47,7 @@ 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,
@@ -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/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/assignment/graphql/types.py b/server/vbv_lernwelt/assignment/graphql/types.py
index d98e40b0..33687377 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",
)
diff --git a/server/vbv_lernwelt/assignment/models.py b/server/vbv_lernwelt/assignment/models.py
index d5ab6d74..e0de99d1 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):
@@ -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..72280d5f 100644
--- a/server/vbv_lernwelt/assignment/services.py
+++ b/server/vbv_lernwelt/assignment/services.py
@@ -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..023ff374 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,6 +101,18 @@ 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},
+ }
},
)
@@ -93,6 +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}},
},
)
@@ -136,6 +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}},
},
)
@@ -151,6 +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}},
},
)
diff --git a/server/vbv_lernwelt/course_session/models.py b/server/vbv_lernwelt/course_session/models.py
index 9c094de1..a436fd95 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)
"""
diff --git a/server/vbv_lernwelt/files/integrations.py b/server/vbv_lernwelt/files/integrations.py
index 7e7b5d5f..a47899f2 100644
--- a/server/vbv_lernwelt/files/integrations.py
+++ b/server/vbv_lernwelt/files/integrations.py
@@ -47,11 +47,16 @@ 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,
)
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..de882bc0 100644
--- a/server/vbv_lernwelt/files/services.py
+++ b/server/vbv_lernwelt/files/services.py
@@ -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..50c94a73
--- /dev/null
+++ b/server/vbv_lernwelt/files/views.py
@@ -0,0 +1,42 @@
+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):
+ 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)
+
+ 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,
+ },
+ }
+ )