From 5890e908f2b4e28f7b4e026014e2b963fcd83329 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 11 Jul 2023 17:39:50 +0200 Subject: [PATCH] Add CourseSessionAssignment to serializer --- client/src/gql/fragment-masking.ts | 48 ++-- client/src/gql/gql.ts | 4 +- client/src/gql/graphql.ts | 214 +++++++++--------- .../AssignmentEvaluationPage.vue | 6 +- .../EvaluationContainer.vue | 4 +- .../assignmentsPage/AssignmentDetails.vue | 4 +- .../assignment/AssignmentIntroductionView.vue | 3 + .../assignment/AssignmentView.vue | 10 +- client/src/services/assignmentService.ts | 2 +- client/src/stores/courseSessions.ts | 18 +- client/src/types.ts | 20 +- server/vbv_lernwelt/course/serializers.py | 14 +- .../course_session/serializers.py | 36 ++- 13 files changed, 223 insertions(+), 160 deletions(-) diff --git a/client/src/gql/fragment-masking.ts b/client/src/gql/fragment-masking.ts index a94b5c77..c000279a 100644 --- a/client/src/gql/fragment-masking.ts +++ b/client/src/gql/fragment-masking.ts @@ -1,11 +1,13 @@ -import type { ResultOf, TypedDocumentNode as DocumentNode, } from '@graphql-typed-document-node/core'; +import type { ResultOf, DocumentTypeDecoration, TypedDocumentNode } from '@graphql-typed-document-node/core'; +import type { FragmentDefinitionNode } from 'graphql'; +import type { Incremental } from './graphql'; -export type FragmentType> = TDocumentType extends DocumentNode< +export type FragmentType> = TDocumentType extends DocumentTypeDecoration< infer TType, any > - ? TType extends { ' $fragmentName'?: infer TKey } + ? [TType] extends [{ ' $fragmentName'?: infer TKey }] ? TKey extends string ? { ' $fragmentRefs'?: { [key in TKey]: TType } } : never @@ -14,35 +16,51 @@ export type FragmentType> = TDocume // return non-nullable if `fragmentType` is non-nullable export function useFragment( - _documentNode: DocumentNode, - fragmentType: FragmentType> + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> ): TType; // return nullable if `fragmentType` is nullable export function useFragment( - _documentNode: DocumentNode, - fragmentType: FragmentType> | null | undefined + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null | undefined ): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable export function useFragment( - _documentNode: DocumentNode, - fragmentType: ReadonlyArray>> + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> ): ReadonlyArray; // return array of nullable if `fragmentType` is array of nullable export function useFragment( - _documentNode: DocumentNode, - fragmentType: ReadonlyArray>> | null | undefined + _documentNode: DocumentTypeDecoration, + fragmentType: ReadonlyArray>> | null | undefined ): ReadonlyArray | null | undefined; export function useFragment( - _documentNode: DocumentNode, - fragmentType: FragmentType> | ReadonlyArray>> | null | undefined + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | ReadonlyArray>> | null | undefined ): TType | ReadonlyArray | null | undefined { return fragmentType as any; } export function makeFragmentData< - F extends DocumentNode, + F extends DocumentTypeDecoration, FT extends ResultOf >(data: FT, _fragment: F): FragmentType { return data as FragmentType; -} \ No newline at end of file +} +export function isFragmentReady( + queryNode: DocumentTypeDecoration, + fragmentNode: TypedDocumentNode, + data: FragmentType, any>> | null | undefined +): data is FragmentType { + const deferredFields = (queryNode as { __meta__?: { deferredFields: Record } }).__meta__ + ?.deferredFields; + + if (!deferredFields) return true; + + const fragDef = fragmentNode.definitions[0] as FragmentDefinitionNode | undefined; + const fragName = fragDef?.name?.value; + + const fields = (fragName && deferredFields[fragName]) || []; + return fields.length > 0 && fields.every(field => data && field in data); +} diff --git a/client/src/gql/gql.ts b/client/src/gql/gql.ts index f47da5f3..c0a9d025 100644 --- a/client/src/gql/gql.ts +++ b/client/src/gql/gql.ts @@ -10,7 +10,7 @@ import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document- * 2. It is not minifiable, so the string of a GraphQL query will be multiple times inside the bundle. * 3. It does not support dead code elimination, so it will add unused operations. * - * Therefore it is highly recommended to use the babel-plugin for production. + * Therefore it is highly recommended to use the babel or swc plugin for production. */ const documents = { "\n mutation SendFeedbackMutation($input: SendFeedbackInput!) {\n send_feedback(input: $input) {\n feedback_response {\n id\n }\n errors {\n field\n messages\n }\n }\n }\n": types.SendFeedbackMutationDocument, @@ -25,7 +25,7 @@ const documents = { * * @example * ```ts - * const query = gql(`query GetUser($id: ID!) { user(id: $id) { name } }`); + * const query = graphql(`query GetUser($id: ID!) { user(id: $id) { name } }`); * ``` * * The query argument is unknown! diff --git a/client/src/gql/graphql.ts b/client/src/gql/graphql.ts index 6f9da652..158e17c7 100644 --- a/client/src/gql/graphql.ts +++ b/client/src/gql/graphql.ts @@ -5,33 +5,35 @@ export type InputMaybe = Maybe; export type Exact = { [K in keyof T]: T[K] }; export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; +export type MakeEmpty = { [_ in K]?: never }; +export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; /** All built-in and custom scalars, mapped to their actual values */ export type Scalars = { - ID: string; - String: string; - Boolean: boolean; - Int: number; - Float: number; + ID: { input: string; output: string; } + String: { input: string; output: string; } + Boolean: { input: boolean; output: boolean; } + Int: { input: number; output: number; } + Float: { input: number; output: number; } /** * The `DateTime` scalar type represents a DateTime * value as specified by * [iso8601](https://en.wikipedia.org/wiki/ISO_8601). */ - DateTime: any; + DateTime: { input: any; output: any; } /** * The `GenericScalar` scalar type represents a generic * GraphQL scalar value that could be: * String, Boolean, Int, Float, List or Object. */ - GenericScalar: any; - JSONStreamField: any; + GenericScalar: { input: any; output: any; } + JSONStreamField: { input: any; output: any; } /** * Allows use of a JSON String for input / output from the GraphQL schema. * * Use of this type is *not recommended* as you lose the benefits of having a defined, static * schema (one of the key benefits of GraphQL). */ - JSONString: any; + JSONString: { input: any; output: any; } }; /** An enumeration. */ @@ -61,19 +63,19 @@ export type AssignmentCompletionMutation = { export type AssignmentCompletionObjectType = { __typename?: 'AssignmentCompletionObjectType'; - additional_json_data: Scalars['JSONString']; + additional_json_data: Scalars['JSONString']['output']; assignment: AssignmentObjectType; assignment_user: UserType; - completion_data?: Maybe; + completion_data?: Maybe; completion_status: AssignmentAssignmentCompletionCompletionStatusChoices; - created_at: Scalars['DateTime']; - evaluation_grade?: Maybe; - evaluation_points?: Maybe; - evaluation_submitted_at?: Maybe; + created_at: Scalars['DateTime']['output']; + evaluation_grade?: Maybe; + evaluation_points?: Maybe; + evaluation_submitted_at?: Maybe; evaluation_user?: Maybe; - id: Scalars['ID']; - submitted_at?: Maybe; - updated_at: Scalars['DateTime']; + id: Scalars['ID']['output']; + submitted_at?: Maybe; + updated_at: Scalars['DateTime']['output']; }; /** An enumeration. */ @@ -86,24 +88,24 @@ export type AssignmentCompletionStatus = export type AssignmentObjectType = CoursePageInterface & { __typename?: 'AssignmentObjectType'; assignment_type: AssignmentAssignmentAssignmentTypeChoices; - content_type?: Maybe; + content_type?: Maybe; /** Zeitaufwand als Text */ - effort_required: Scalars['String']; + effort_required: Scalars['String']['output']; /** Beschreibung der Bewertung */ - evaluation_description: Scalars['String']; + evaluation_description: Scalars['String']['output']; /** URL zum Beurteilungsinstrument */ - evaluation_document_url: Scalars['String']; - evaluation_tasks?: Maybe; - frontend_url?: Maybe; - id?: Maybe; + evaluation_document_url: Scalars['String']['output']; + evaluation_tasks?: Maybe; + frontend_url?: Maybe; + id?: Maybe; /** Erläuterung der Ausgangslage */ - intro_text: Scalars['String']; - live?: Maybe; - performance_objectives?: Maybe; - slug?: Maybe; - tasks?: Maybe; - title?: Maybe; - translation_key?: Maybe; + intro_text: Scalars['String']['output']; + live?: Maybe; + performance_objectives?: Maybe; + slug?: Maybe; + tasks?: Maybe; + title?: Maybe; + translation_key?: Maybe; }; /** An enumeration. */ @@ -116,69 +118,69 @@ export type CoreUserLanguageChoices = | 'IT'; export type CoursePageInterface = { - content_type?: Maybe; - frontend_url?: Maybe; - id?: Maybe; - live?: Maybe; - slug?: Maybe; - title?: Maybe; - translation_key?: Maybe; + content_type?: Maybe; + frontend_url?: Maybe; + id?: Maybe; + live?: Maybe; + slug?: Maybe; + title?: Maybe; + translation_key?: Maybe; }; export type CourseType = { __typename?: 'CourseType'; - category_name: Scalars['String']; - id: Scalars['ID']; + category_name: Scalars['String']['output']; + id: Scalars['ID']['output']; learning_path?: Maybe; - slug: Scalars['String']; - title: Scalars['String']; + slug: Scalars['String']['output']; + title: Scalars['String']['output']; }; export type ErrorType = { __typename?: 'ErrorType'; - field: Scalars['String']; - messages: Array; + field: Scalars['String']['output']; + messages: Array; }; export type FeedbackResponse = Node & { __typename?: 'FeedbackResponse'; - created_at: Scalars['DateTime']; - data?: Maybe; + created_at: Scalars['DateTime']['output']; + data?: Maybe; /** The ID of the object */ - id: Scalars['ID']; + id: Scalars['ID']['output']; }; export type LearningPathType = CoursePageInterface & { __typename?: 'LearningPathType'; - content_type?: Maybe; - depth: Scalars['Int']; - draft_title: Scalars['String']; - expire_at?: Maybe; - expired: Scalars['Boolean']; - first_published_at?: Maybe; - frontend_url?: Maybe; - go_live_at?: Maybe; - has_unpublished_changes: Scalars['Boolean']; - id?: Maybe; - last_published_at?: Maybe; - latest_revision_created_at?: Maybe; - live?: Maybe; - locked: Scalars['Boolean']; - locked_at?: Maybe; + content_type?: Maybe; + depth: Scalars['Int']['output']; + draft_title: Scalars['String']['output']; + expire_at?: Maybe; + expired: Scalars['Boolean']['output']; + first_published_at?: Maybe; + frontend_url?: Maybe; + go_live_at?: Maybe; + has_unpublished_changes: Scalars['Boolean']['output']; + id?: Maybe; + last_published_at?: Maybe; + latest_revision_created_at?: Maybe; + live?: Maybe; + locked: Scalars['Boolean']['output']; + locked_at?: Maybe; locked_by?: Maybe; - numchild: Scalars['Int']; + numchild: Scalars['Int']['output']; owner?: Maybe; - path: Scalars['String']; + path: Scalars['String']['output']; /** Die informative Beschreibung, dargestellt in Suchmaschinen-Ergebnissen unter der Überschrift. */ - search_description: Scalars['String']; + search_description: Scalars['String']['output']; /** Der Titel der Seite, dargestellt in Suchmaschinen-Ergebnissen als die verlinkte Überschrift. */ - seo_title: Scalars['String']; + seo_title: Scalars['String']['output']; /** Ob ein Link zu dieser Seite in automatisch generierten Menüs auftaucht. */ - show_in_menus: Scalars['Boolean']; - slug?: Maybe; - title?: Maybe; - translation_key?: Maybe; - url_path: Scalars['String']; + show_in_menus: Scalars['Boolean']['output']; + slug?: Maybe; + title?: Maybe; + translation_key?: Maybe; + url_path: Scalars['String']['output']; }; export type Mutation = { @@ -194,19 +196,19 @@ export type MutationSendFeedbackArgs = { export type MutationUpsertAssignmentCompletionArgs = { - assignment_id: Scalars['ID']; - assignment_user_id?: InputMaybe; - completion_data_string?: InputMaybe; + assignment_id: Scalars['ID']['input']; + assignment_user_id?: InputMaybe; + completion_data_string?: InputMaybe; completion_status?: InputMaybe; - course_session_id: Scalars['ID']; - evaluation_grade?: InputMaybe; - evaluation_points?: InputMaybe; + course_session_id: Scalars['ID']['input']; + evaluation_grade?: InputMaybe; + evaluation_points?: InputMaybe; }; /** An object with an ID */ export type Node = { /** The ID of the object */ - id: Scalars['ID']; + id: Scalars['ID']['output']; }; export type Query = { @@ -218,32 +220,32 @@ export type Query = { export type QueryAssignmentArgs = { - id?: InputMaybe; - slug?: InputMaybe; + id?: InputMaybe; + slug?: InputMaybe; }; export type QueryAssignmentCompletionArgs = { - assignment_id: Scalars['ID']; - assignment_user_id?: InputMaybe; - course_session_id: Scalars['ID']; + assignment_id: Scalars['ID']['input']; + assignment_user_id?: InputMaybe; + course_session_id: Scalars['ID']['input']; }; export type QueryCourseArgs = { - id?: InputMaybe; + id?: InputMaybe; }; export type SendFeedbackInput = { - clientMutationId?: InputMaybe; - course_session: Scalars['Int']; - data?: InputMaybe; - page: Scalars['String']; + clientMutationId?: InputMaybe; + course_session: Scalars['Int']['input']; + data?: InputMaybe; + page: Scalars['String']['input']; }; export type SendFeedbackPayload = { __typename?: 'SendFeedbackPayload'; - clientMutationId?: Maybe; + clientMutationId?: Maybe; /** May contain more than one error for same field. */ errors?: Maybe>>; feedback_response?: Maybe; @@ -251,14 +253,14 @@ export type SendFeedbackPayload = { export type UserType = { __typename?: 'UserType'; - avatar_url: Scalars['String']; - email: Scalars['String']; - first_name: Scalars['String']; - id: Scalars['ID']; + avatar_url: Scalars['String']['output']; + email: Scalars['String']['output']; + first_name: Scalars['String']['output']; + id: Scalars['ID']['output']; language: CoreUserLanguageChoices; - last_name: Scalars['String']; + last_name: Scalars['String']['output']; /** Erforderlich. 150 Zeichen oder weniger. Nur Buchstaben, Ziffern und @/./+/-/_. */ - username: Scalars['String']; + username: Scalars['String']['output']; }; export type SendFeedbackMutationMutationVariables = Exact<{ @@ -269,29 +271,29 @@ export type SendFeedbackMutationMutationVariables = Exact<{ export type SendFeedbackMutationMutation = { __typename?: 'Mutation', send_feedback?: { __typename?: 'SendFeedbackPayload', feedback_response?: { __typename?: 'FeedbackResponse', id: string } | null, errors?: Array<{ __typename?: 'ErrorType', field: string, messages: Array } | null> | null } | null }; export type UpsertAssignmentCompletionMutationVariables = Exact<{ - assignmentId: Scalars['ID']; - courseSessionId: Scalars['ID']; - assignmentUserId?: InputMaybe; + assignmentId: Scalars['ID']['input']; + courseSessionId: Scalars['ID']['input']; + assignmentUserId?: InputMaybe; completionStatus: AssignmentCompletionStatus; - completionDataString: Scalars['String']; - evaluationGrade?: InputMaybe; - evaluationPoints?: InputMaybe; + completionDataString: Scalars['String']['input']; + evaluationGrade?: InputMaybe; + evaluationPoints?: InputMaybe; }>; export type UpsertAssignmentCompletionMutation = { __typename?: 'Mutation', upsert_assignment_completion?: { __typename?: 'AssignmentCompletionMutation', assignment_completion?: { __typename?: 'AssignmentCompletionObjectType', id: string, completion_status: AssignmentAssignmentCompletionCompletionStatusChoices, submitted_at?: any | null, evaluation_submitted_at?: any | null, evaluation_grade?: number | null, evaluation_points?: number | null, completion_data?: any | null } | null } | null }; export type AssignmentCompletionQueryQueryVariables = Exact<{ - assignmentId: Scalars['ID']; - courseSessionId: Scalars['ID']; - assignmentUserId?: InputMaybe; + assignmentId: Scalars['ID']['input']; + courseSessionId: Scalars['ID']['input']; + assignmentUserId?: InputMaybe; }>; export type AssignmentCompletionQueryQuery = { __typename?: 'Query', assignment?: { __typename?: 'AssignmentObjectType', assignment_type: AssignmentAssignmentAssignmentTypeChoices, 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 } | null, assignment_completion?: { __typename?: 'AssignmentCompletionObjectType', id: string, completion_status: AssignmentAssignmentCompletionCompletionStatusChoices, submitted_at?: any | null, evaluation_submitted_at?: any | null, evaluation_grade?: number | null, evaluation_points?: number | null, completion_data?: any | null, evaluation_user?: { __typename?: 'UserType', id: string } | null, assignment_user: { __typename?: 'UserType', id: string } } | null }; export type CourseQueryQueryVariables = Exact<{ - courseId: Scalars['Int']; + courseId: Scalars['Int']['input']; }>; diff --git a/client/src/pages/cockpit/assignmentEvaluationPage/AssignmentEvaluationPage.vue b/client/src/pages/cockpit/assignmentEvaluationPage/AssignmentEvaluationPage.vue index 9b45fd5e..6872cb56 100644 --- a/client/src/pages/cockpit/assignmentEvaluationPage/AssignmentEvaluationPage.vue +++ b/client/src/pages/cockpit/assignmentEvaluationPage/AssignmentEvaluationPage.vue @@ -6,7 +6,7 @@ import AssignmentSubmissionResponses from "@/pages/learningPath/learningContentP import type { Assignment, AssignmentCompletion, - CourseSessionAssignmentDetails, + CourseSessionAssignment, CourseSessionUser, } from "@/types"; import { useQuery } from "@urql/vue"; @@ -23,12 +23,12 @@ const props = defineProps<{ log.debug("AssignmentEvaluationPage created", props.assignmentId, props.userId); interface StateInterface { - courseSessionAssignmentDetails: CourseSessionAssignmentDetails | undefined; + courseSessionAssignment: CourseSessionAssignment | undefined; assignmentUser: CourseSessionUser | undefined; } const state: StateInterface = reactive({ - courseSessionAssignmentDetails: undefined, + courseSessionAssignment: undefined, assignmentUser: undefined, }); diff --git a/client/src/pages/cockpit/assignmentEvaluationPage/EvaluationContainer.vue b/client/src/pages/cockpit/assignmentEvaluationPage/EvaluationContainer.vue index 0adbe94f..1b4a872f 100644 --- a/client/src/pages/cockpit/assignmentEvaluationPage/EvaluationContainer.vue +++ b/client/src/pages/cockpit/assignmentEvaluationPage/EvaluationContainer.vue @@ -60,9 +60,7 @@ function editTask(task: AssignmentEvaluationTask) { const assignmentDetail = computed(() => findAssignmentDetail(props.assignment.id)); -const dueDate = computed(() => - dayjs(assignmentDetail.value?.evaluationDeadlineDateTimeUtc) -); +const dueDate = computed(() => dayjs(assignmentDetail.value?.evaluation_deadline_end)); const inEvaluationTask = computed( () => stepIndex.value >= 1 && stepIndex.value <= numTasks.value diff --git a/client/src/pages/cockpit/assignmentsPage/AssignmentDetails.vue b/client/src/pages/cockpit/assignmentsPage/AssignmentDetails.vue index 4410ae42..eaae98e0 100644 --- a/client/src/pages/cockpit/assignmentsPage/AssignmentDetails.vue +++ b/client/src/pages/cockpit/assignmentsPage/AssignmentDetails.vue @@ -56,12 +56,12 @@ const assignmentDetail = computed(() =>
Abgabetermin: - {{ dayjs(assignmentDetail.submissionDeadlineDateTimeUtc).format("DD.MM.YYYY") }} + {{ dayjs(assignmentDetail.submission_deadline_end).format("DD.MM.YYYY") }} - Freigabetermin: - {{ dayjs(assignmentDetail.evaluationDeadlineDateTimeUtc).format("DD.MM.YYYY") }} + {{ dayjs(assignmentDetail.evaluation_deadline_end).format("DD.MM.YYYY") }}
diff --git a/client/src/pages/learningPath/learningContentPage/assignment/AssignmentIntroductionView.vue b/client/src/pages/learningPath/learningContentPage/assignment/AssignmentIntroductionView.vue index c48ed595..0d3381a8 100644 --- a/client/src/pages/learningPath/learningContentPage/assignment/AssignmentIntroductionView.vue +++ b/client/src/pages/learningPath/learningContentPage/assignment/AssignmentIntroductionView.vue @@ -3,6 +3,7 @@ import DateEmbedding from "@/components/dueDates/DateEmbedding.vue"; import type { Assignment } from "@/types"; import { useRouteQuery } from "@vueuse/router"; import type { Dayjs } from "dayjs"; +import log from "loglevel"; interface Props { assignment: Assignment; @@ -13,6 +14,8 @@ const props = withDefaults(defineProps(), { dueDate: undefined, }); +log.debug("AssignmentIntroductionView created", props.assignment, props.dueDate); + // TODO: Test if submission deadline is set correctly, and evaluation_deadline is set. const step = useRouteQuery("step"); diff --git a/client/src/pages/learningPath/learningContentPage/assignment/AssignmentView.vue b/client/src/pages/learningPath/learningContentPage/assignment/AssignmentView.vue index 0fec60c3..a310c78f 100644 --- a/client/src/pages/learningPath/learningContentPage/assignment/AssignmentView.vue +++ b/client/src/pages/learningPath/learningContentPage/assignment/AssignmentView.vue @@ -13,7 +13,7 @@ import type { Assignment, AssignmentCompletion, AssignmentTask, - CourseSessionAssignmentDetails, + CourseSessionAssignment, CourseSessionUser, LearningContentAssignment, } from "@/types"; @@ -29,11 +29,11 @@ const courseSession = useCurrentCourseSession(); const userStore = useUserStore(); interface State { - courseSessionAssignmentDetails: CourseSessionAssignmentDetails | undefined; + courseSessionAssignment: CourseSessionAssignment | undefined; } const state: State = reactive({ - courseSessionAssignmentDetails: undefined, + courseSessionAssignment: undefined, }); const props = defineProps<{ @@ -80,7 +80,7 @@ onMounted(async () => { props.learningContent ); - state.courseSessionAssignmentDetails = useCourseSessionsStore().findAssignmentDetails( + state.courseSessionAssignment = useCourseSessionsStore().findCourseSessionAssignment( props.learningContent.id ); @@ -123,7 +123,7 @@ const showPreviousButton = computed(() => stepIndex.value != 0); const showNextButton = computed(() => stepIndex.value + 1 < numPages.value); const showExitButton = computed(() => numPages.value === stepIndex.value + 1); const dueDate = computed(() => - dayjs(state.courseSessionAssignmentDetails?.submissionDeadlineDateTimeUtc) + dayjs(state.courseSessionAssignment?.submission_deadline_end) ); const currentTask = computed(() => { if (stepIndex.value > 0 && stepIndex.value <= numTasks.value) { diff --git a/client/src/services/assignmentService.ts b/client/src/services/assignmentService.ts index fa44b688..3db79596 100644 --- a/client/src/services/assignmentService.ts +++ b/client/src/services/assignmentService.ts @@ -109,7 +109,7 @@ export function findAssignmentDetail(assignmentId: number) { (lc) => lc.assignmentId === assignmentId ); - return courseSessionsStore.findAssignmentDetails(learningContent?.id); + return courseSessionsStore.findCourseSessionAssignment(learningContent?.id); } export function maxAssignmentPoints(assignment: Assignment) { diff --git a/client/src/stores/courseSessions.ts b/client/src/stores/courseSessions.ts index 95734fea..d1cb8d8f 100644 --- a/client/src/stores/courseSessions.ts +++ b/client/src/stores/courseSessions.ts @@ -3,7 +3,7 @@ import { deleteCircleDocument } from "@/services/files"; import type { CircleDocument, CourseSession, - CourseSessionAssignmentDetails, + CourseSessionAssignment, CourseSessionAttendanceCourse, CourseSessionUser, DueDate, @@ -230,20 +230,18 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => { ): CourseSessionAttendanceCourse | undefined { if (currentCourseSession.value) { return currentCourseSession.value.attendance_courses.find( - (attendanceCourse) => attendanceCourse.learning_content === contentId + (attendanceCourse) => attendanceCourse.learning_content_id === contentId ); } } - function findAssignmentDetails( + function findCourseSessionAssignment( contentId?: number - ): CourseSessionAssignmentDetails | undefined { + ): CourseSessionAssignment | undefined { if (contentId && currentCourseSession.value) { - return; - // TODO: Commented out because DueDate replaced assignment_details_list, not shure if other iformation is needed - // currentCourseSession.value.assignment_details_list.find( - // (assignmentDetails) => assignmentDetails.learningContentId === contentId - // ); + return currentCourseSession.value.assignments.find( + (a) => a.learning_content_id === contentId + ); } } @@ -261,7 +259,7 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => { startUpload, removeDocument, findAttendanceCourse, - findAssignmentDetails, + findCourseSessionAssignment, allDueDates, // use `useCurrentCourseSession` whenever possible diff --git a/client/src/types.ts b/client/src/types.ts index fbfaf876..bcb7763f 100644 --- a/client/src/types.ts +++ b/client/src/types.ts @@ -413,18 +413,24 @@ export interface CircleDocument { } export interface CourseSessionAttendanceCourse { - learning_content: number; + id: number; + course_session_id: number; + learning_content_id: number; start: string; end: string; location: string; trainer: string; - due_date: DueDate; + due_date_id: number; } -export interface CourseSessionAssignmentDetails { - learningContentId: number; - submissionDeadlineDateTimeUtc: string; - evaluationDeadlineDateTimeUtc: string; +export interface CourseSessionAssignment { + id: number; + course_session_id: number; + learning_content_id: number; + submission_deadline_id: number; + submission_deadline_end: string; + evaluation_deadline_id: number; + evaluation_deadline_end: string; } export interface CourseSession { @@ -441,7 +447,7 @@ export interface CourseSession { course_url: string; media_library_url: string; attendance_courses: CourseSessionAttendanceCourse[]; - assignment_details_list: CourseSessionAssignmentDetails[]; + assignments: CourseSessionAssignment[]; documents: CircleDocument[]; users: CourseSessionUser[]; duedates: DueDate[]; diff --git a/server/vbv_lernwelt/course/serializers.py b/server/vbv_lernwelt/course/serializers.py index 718366bd..a30eea17 100644 --- a/server/vbv_lernwelt/course/serializers.py +++ b/server/vbv_lernwelt/course/serializers.py @@ -7,8 +7,12 @@ from vbv_lernwelt.course.models import ( CourseCompletion, CourseSession, ) -from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse +from vbv_lernwelt.course_session.models import ( + CourseSessionAssignment, + CourseSessionAttendanceCourse, +) from vbv_lernwelt.course_session.serializers import ( + CourseSessionAssignmentSerializer, CourseSessionAttendanceCourseSerializer, ) from vbv_lernwelt.duedate.models import DueDate @@ -57,6 +61,7 @@ class CourseSessionSerializer(serializers.ModelSerializer): media_library_url = serializers.SerializerMethodField() documents = serializers.SerializerMethodField() attendance_courses = serializers.SerializerMethodField() + assignments = serializers.SerializerMethodField() duedates = serializers.SerializerMethodField() def get_course(self, obj): @@ -88,6 +93,11 @@ class CourseSessionSerializer(serializers.ModelSerializer): CourseSessionAttendanceCourse.objects.filter(course_session=obj), many=True ).data + def get_assignments(self, obj): + return CourseSessionAssignmentSerializer( + CourseSessionAssignment.objects.filter(course_session=obj), many=True + ).data + def get_duedates(self, obj): # TODO: Filter by user / userrole duedates = DueDate.objects.filter(course_session=obj) @@ -105,7 +115,7 @@ class CourseSessionSerializer(serializers.ModelSerializer): "end_date", "additional_json_data", "attendance_courses", - # "assignment_details_list", + "assignments", "learning_path_url", "cockpit_url", "competence_url", diff --git a/server/vbv_lernwelt/course_session/serializers.py b/server/vbv_lernwelt/course_session/serializers.py index 41dbc6fb..9d524ccd 100644 --- a/server/vbv_lernwelt/course_session/serializers.py +++ b/server/vbv_lernwelt/course_session/serializers.py @@ -1,6 +1,9 @@ from rest_framework import serializers -from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse +from vbv_lernwelt.course_session.models import ( + CourseSessionAssignment, + CourseSessionAttendanceCourse, +) class CourseSessionAttendanceCourseSerializer(serializers.ModelSerializer): @@ -11,9 +14,9 @@ class CourseSessionAttendanceCourseSerializer(serializers.ModelSerializer): model = CourseSessionAttendanceCourse fields = [ "id", - "course_session", - "learning_content", - "due_date", + "course_session_id", + "learning_content_id", + "due_date_id", "location", "trainer", "start", @@ -25,3 +28,28 @@ class CourseSessionAttendanceCourseSerializer(serializers.ModelSerializer): def get_end(self, obj): return obj.due_date.end + + +class CourseSessionAssignmentSerializer(serializers.ModelSerializer): + submission_deadline_end = serializers.SerializerMethodField() + evaluation_deadline_end = serializers.SerializerMethodField() + + class Meta: + model = CourseSessionAssignment + fields = [ + "id", + "course_session_id", + "learning_content_id", + "submission_deadline_id", + "submission_deadline_end", + "evaluation_deadline_id", + "evaluation_deadline_end", + ] + + def get_evaluation_deadline_end(self, obj): + if obj.evaluation_deadline: + return obj.evaluation_deadline.end + + def get_submission_deadline_end(self, obj): + if obj.submission_deadline: + return obj.submission_deadline.end