Merged in feature/VBV-354-termine (pull request #155)
Feature/VBV-354 termine Approved-by: Daniel Egger
This commit is contained in:
commit
7e8773cd17
|
|
@ -0,0 +1,15 @@
|
|||
<script lang="ts" setup>
|
||||
import { getDateString, getTimeString } from "@/components/dueDates/dueDatesUtils";
|
||||
import type { Dayjs } from "dayjs";
|
||||
|
||||
const props = defineProps<{
|
||||
singleDate?: Dayjs;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>
|
||||
<it-icon-calendar-light class="it-icon h-5 w-5" />
|
||||
{{ getDateString(props.singleDate) }}, {{ getTimeString(props.singleDate) }}
|
||||
</span>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<script lang="ts" setup>
|
||||
import { formatDate } from "@/components/dueDates/dueDatesUtils";
|
||||
import type { DueDate } from "@/types";
|
||||
|
||||
const props = defineProps<{
|
||||
dueDate: DueDate;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center justify-between py-4">
|
||||
<div class="space-y-1">
|
||||
<a class="text-bold underline" :href="props.dueDate.url" target="_blank">
|
||||
{{ props.dueDate.title }}
|
||||
</a>
|
||||
<p class="text-small text-gray-900">
|
||||
{{ props.dueDate.learning_content_description }}
|
||||
<span v-if="props.dueDate.description !== ''">:</span>
|
||||
{{ props.dueDate.description }}
|
||||
</p>
|
||||
</div>
|
||||
<p>
|
||||
{{ formatDate(props.dueDate.start, props.dueDate.end) }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<div>
|
||||
<ul>
|
||||
<li
|
||||
v-for="dueDate in dueDatesDisplayed"
|
||||
:key="dueDate.id"
|
||||
:class="{ 'first:border-t': props.showTopBorder, 'border-b': true }"
|
||||
>
|
||||
<DueDateSingle :due-date="dueDate"></DueDateSingle>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="allDueDates.length > props.maxCount" class="flex items-center pt-6">
|
||||
<a href="">{{ $t("dueDates.showAllDueDates") }}</a>
|
||||
<it-icon-arrow-right />
|
||||
</div>
|
||||
|
||||
<div v-if="allDueDates.length === 0">{{ $t("dueDates.noDueDatesAvailable") }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import DueDateSingle from "@/components/dueDates/DueDateSingle.vue";
|
||||
import type { DueDate } from "@/types";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
maxCount: number;
|
||||
dueDates: DueDate[];
|
||||
showTopBorder: boolean;
|
||||
}>();
|
||||
|
||||
const allDueDates = computed(() => {
|
||||
return props.dueDates;
|
||||
});
|
||||
|
||||
const dueDatesDisplayed = computed(() => {
|
||||
return props.dueDates.slice(0, props.maxCount);
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<div>
|
||||
<DueDatesList
|
||||
:due-dates="allDueDates"
|
||||
:max-count="props.maxCount"
|
||||
:show-top-border="props.showTopBorder"
|
||||
></DueDatesList>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import DueDatesList from "@/components/dueDates/DueDatesList.vue";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
|
||||
const props = defineProps<{
|
||||
maxCount: number;
|
||||
showTopBorder: boolean;
|
||||
}>();
|
||||
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const allDueDates = courseSession.value.due_dates;
|
||||
</script>
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import dayjs from "dayjs";
|
||||
|
||||
export const dueDatesTestData = () => {
|
||||
return [
|
||||
{
|
||||
id: 1,
|
||||
start: dayjs("2023-06-14T15:00:00+02:00"),
|
||||
end: dayjs("2023-06-14T18:00:00+02:00"),
|
||||
title: "Präsenzkurs Kickoff",
|
||||
url: "/course/überbetriebliche-kurse/learn/kickoff/präsenzkurs-kickoff",
|
||||
course_session: 2,
|
||||
page: 383,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
start: dayjs("2023-06-15T15:00:00+02:00"),
|
||||
end: dayjs("2023-06-15T18:00:00+02:00"),
|
||||
title: "Präsenzkurs Basis",
|
||||
url: "/course/überbetriebliche-kurse/learn/basis/präsenzkurs-basis",
|
||||
course_session: 2,
|
||||
page: 397,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
start: dayjs("2023-06-16T15:00:00+02:00"),
|
||||
end: dayjs("2023-06-16T18:00:00+02:00"),
|
||||
title: "Präsenzkurs Fahrzeug",
|
||||
url: "/course/überbetriebliche-kurse/learn/fahrzeug/präsenzkurs-fahrzeug",
|
||||
course_session: 2,
|
||||
page: 413,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
start: dayjs("2023-06-16T15:00:00+02:00"),
|
||||
end: dayjs("2023-06-16T18:00:00+02:00"),
|
||||
title: "Präsenzkurs Flugzeuge",
|
||||
url: "/course/überbetriebliche-kurse/learn/fahrzeug/präsenzkurs-fahrzeug",
|
||||
course_session: 2,
|
||||
page: 413,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
start: dayjs("2023-07-16T11:00:00+02:00"),
|
||||
end: dayjs("2023-07-16T18:00:00+02:00"),
|
||||
title: "Präsenzkurs Motorräder",
|
||||
url: "/course/überbetriebliche-kurse/learn/fahrzeug/präsenzkurs-fahrzeug",
|
||||
course_session: 2,
|
||||
page: 413,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
start: dayjs("2023-08-09T15:00:00+02:00"),
|
||||
end: dayjs("2023-08-09T19:00:00+02:00"),
|
||||
title: "Präsenzkurs Fahrräder",
|
||||
url: "/course/überbetriebliche-kurse/learn/fahrzeug/präsenzkurs-fahrzeug",
|
||||
course_session: 2,
|
||||
page: 413,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import type { Dayjs } from "dayjs";
|
||||
|
||||
export const formatDate = (start: Dayjs, end: Dayjs) => {
|
||||
const startDateString = getDateString(start);
|
||||
const endDateString = getDateString(end);
|
||||
|
||||
// if start isundefined, dont show the day twice
|
||||
|
||||
if (!start.isValid() && !end.isValid()) {
|
||||
return "Termin nicht festgelegt";
|
||||
}
|
||||
|
||||
if (!start || (!start.isValid() && end.isValid())) {
|
||||
return `${endDateString} ${getTimeString(end)} ${end.format("[Uhr]")}`;
|
||||
}
|
||||
if (!end || (!end.isValid() && start.isValid())) {
|
||||
return `${startDateString} ${getTimeString(start)} ${start.format("[Uhr]")}`;
|
||||
}
|
||||
|
||||
// if start and end are on the same day, dont show the day twice
|
||||
if (startDateString === endDateString) {
|
||||
return `${startDateString} ${getTimeString(start)} - ${getTimeString(
|
||||
end
|
||||
)} ${end.format("[Uhr]")}`;
|
||||
}
|
||||
|
||||
return `${startDateString} ${getTimeString(start)} - ${endDateString} ${getTimeString(
|
||||
end
|
||||
)}`;
|
||||
};
|
||||
|
||||
export const getTimeString = (date?: Dayjs) => {
|
||||
if (date) {
|
||||
return `${date.format("H:mm")}`;
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
export const getDateString = (date?: Dayjs) => {
|
||||
if (date) {
|
||||
return `${date.format("D. MMMM YYYY")}`;
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
export const getWeekday = (date: Dayjs) => {
|
||||
if (date) {
|
||||
return `${date.format("dd")}`;
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
|
@ -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<any, any>> = TDocumentType extends DocumentNode<
|
||||
export type FragmentType<TDocumentType extends DocumentTypeDecoration<any, any>> = 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<TDocumentType extends DocumentNode<any, any>> = TDocume
|
|||
|
||||
// return non-nullable if `fragmentType` is non-nullable
|
||||
export function useFragment<TType>(
|
||||
_documentNode: DocumentNode<TType, any>,
|
||||
fragmentType: FragmentType<DocumentNode<TType, any>>
|
||||
_documentNode: DocumentTypeDecoration<TType, any>,
|
||||
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>>
|
||||
): TType;
|
||||
// return nullable if `fragmentType` is nullable
|
||||
export function useFragment<TType>(
|
||||
_documentNode: DocumentNode<TType, any>,
|
||||
fragmentType: FragmentType<DocumentNode<TType, any>> | null | undefined
|
||||
_documentNode: DocumentTypeDecoration<TType, any>,
|
||||
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>> | null | undefined
|
||||
): TType | null | undefined;
|
||||
// return array of non-nullable if `fragmentType` is array of non-nullable
|
||||
export function useFragment<TType>(
|
||||
_documentNode: DocumentNode<TType, any>,
|
||||
fragmentType: ReadonlyArray<FragmentType<DocumentNode<TType, any>>>
|
||||
_documentNode: DocumentTypeDecoration<TType, any>,
|
||||
fragmentType: ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>>
|
||||
): ReadonlyArray<TType>;
|
||||
// return array of nullable if `fragmentType` is array of nullable
|
||||
export function useFragment<TType>(
|
||||
_documentNode: DocumentNode<TType, any>,
|
||||
fragmentType: ReadonlyArray<FragmentType<DocumentNode<TType, any>>> | null | undefined
|
||||
_documentNode: DocumentTypeDecoration<TType, any>,
|
||||
fragmentType: ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>> | null | undefined
|
||||
): ReadonlyArray<TType> | null | undefined;
|
||||
export function useFragment<TType>(
|
||||
_documentNode: DocumentNode<TType, any>,
|
||||
fragmentType: FragmentType<DocumentNode<TType, any>> | ReadonlyArray<FragmentType<DocumentNode<TType, any>>> | null | undefined
|
||||
_documentNode: DocumentTypeDecoration<TType, any>,
|
||||
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>> | ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>> | null | undefined
|
||||
): TType | ReadonlyArray<TType> | null | undefined {
|
||||
return fragmentType as any;
|
||||
}
|
||||
|
||||
|
||||
export function makeFragmentData<
|
||||
F extends DocumentNode,
|
||||
F extends DocumentTypeDecoration<any, any>,
|
||||
FT extends ResultOf<F>
|
||||
>(data: FT, _fragment: F): FragmentType<F> {
|
||||
return data as FragmentType<F>;
|
||||
}
|
||||
}
|
||||
export function isFragmentReady<TQuery, TFrag>(
|
||||
queryNode: DocumentTypeDecoration<TQuery, any>,
|
||||
fragmentNode: TypedDocumentNode<TFrag>,
|
||||
data: FragmentType<TypedDocumentNode<Incremental<TFrag>, any>> | null | undefined
|
||||
): data is FragmentType<typeof fragmentNode> {
|
||||
const deferredFields = (queryNode as { __meta__?: { deferredFields: Record<string, (keyof TFrag)[]> } }).__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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -5,33 +5,35 @@ export type InputMaybe<T> = Maybe<T>;
|
|||
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
||||
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
|
||||
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
|
||||
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
|
||||
export type Incremental<T> = 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<Scalars['GenericScalar']>;
|
||||
completion_data?: Maybe<Scalars['GenericScalar']['output']>;
|
||||
completion_status: AssignmentAssignmentCompletionCompletionStatusChoices;
|
||||
created_at: Scalars['DateTime'];
|
||||
evaluation_grade?: Maybe<Scalars['Float']>;
|
||||
evaluation_points?: Maybe<Scalars['Float']>;
|
||||
evaluation_submitted_at?: Maybe<Scalars['DateTime']>;
|
||||
created_at: Scalars['DateTime']['output'];
|
||||
evaluation_grade?: Maybe<Scalars['Float']['output']>;
|
||||
evaluation_points?: Maybe<Scalars['Float']['output']>;
|
||||
evaluation_submitted_at?: Maybe<Scalars['DateTime']['output']>;
|
||||
evaluation_user?: Maybe<UserType>;
|
||||
id: Scalars['ID'];
|
||||
submitted_at?: Maybe<Scalars['DateTime']>;
|
||||
updated_at: Scalars['DateTime'];
|
||||
id: Scalars['ID']['output'];
|
||||
submitted_at?: Maybe<Scalars['DateTime']['output']>;
|
||||
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<Scalars['String']>;
|
||||
content_type?: Maybe<Scalars['String']['output']>;
|
||||
/** 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<Scalars['JSONStreamField']>;
|
||||
frontend_url?: Maybe<Scalars['String']>;
|
||||
id?: Maybe<Scalars['ID']>;
|
||||
evaluation_document_url: Scalars['String']['output'];
|
||||
evaluation_tasks?: Maybe<Scalars['JSONStreamField']['output']>;
|
||||
frontend_url?: Maybe<Scalars['String']['output']>;
|
||||
id?: Maybe<Scalars['ID']['output']>;
|
||||
/** Erläuterung der Ausgangslage */
|
||||
intro_text: Scalars['String'];
|
||||
live?: Maybe<Scalars['Boolean']>;
|
||||
performance_objectives?: Maybe<Scalars['JSONStreamField']>;
|
||||
slug?: Maybe<Scalars['String']>;
|
||||
tasks?: Maybe<Scalars['JSONStreamField']>;
|
||||
title?: Maybe<Scalars['String']>;
|
||||
translation_key?: Maybe<Scalars['String']>;
|
||||
intro_text: Scalars['String']['output'];
|
||||
live?: Maybe<Scalars['Boolean']['output']>;
|
||||
performance_objectives?: Maybe<Scalars['JSONStreamField']['output']>;
|
||||
slug?: Maybe<Scalars['String']['output']>;
|
||||
tasks?: Maybe<Scalars['JSONStreamField']['output']>;
|
||||
title?: Maybe<Scalars['String']['output']>;
|
||||
translation_key?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
/** An enumeration. */
|
||||
|
|
@ -116,69 +118,69 @@ export type CoreUserLanguageChoices =
|
|||
| 'IT';
|
||||
|
||||
export type CoursePageInterface = {
|
||||
content_type?: Maybe<Scalars['String']>;
|
||||
frontend_url?: Maybe<Scalars['String']>;
|
||||
id?: Maybe<Scalars['ID']>;
|
||||
live?: Maybe<Scalars['Boolean']>;
|
||||
slug?: Maybe<Scalars['String']>;
|
||||
title?: Maybe<Scalars['String']>;
|
||||
translation_key?: Maybe<Scalars['String']>;
|
||||
content_type?: Maybe<Scalars['String']['output']>;
|
||||
frontend_url?: Maybe<Scalars['String']['output']>;
|
||||
id?: Maybe<Scalars['ID']['output']>;
|
||||
live?: Maybe<Scalars['Boolean']['output']>;
|
||||
slug?: Maybe<Scalars['String']['output']>;
|
||||
title?: Maybe<Scalars['String']['output']>;
|
||||
translation_key?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type CourseType = {
|
||||
__typename?: 'CourseType';
|
||||
category_name: Scalars['String'];
|
||||
id: Scalars['ID'];
|
||||
category_name: Scalars['String']['output'];
|
||||
id: Scalars['ID']['output'];
|
||||
learning_path?: Maybe<LearningPathType>;
|
||||
slug: Scalars['String'];
|
||||
title: Scalars['String'];
|
||||
slug: Scalars['String']['output'];
|
||||
title: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type ErrorType = {
|
||||
__typename?: 'ErrorType';
|
||||
field: Scalars['String'];
|
||||
messages: Array<Scalars['String']>;
|
||||
field: Scalars['String']['output'];
|
||||
messages: Array<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type FeedbackResponse = Node & {
|
||||
__typename?: 'FeedbackResponse';
|
||||
created_at: Scalars['DateTime'];
|
||||
data?: Maybe<Scalars['GenericScalar']>;
|
||||
created_at: Scalars['DateTime']['output'];
|
||||
data?: Maybe<Scalars['GenericScalar']['output']>;
|
||||
/** The ID of the object */
|
||||
id: Scalars['ID'];
|
||||
id: Scalars['ID']['output'];
|
||||
};
|
||||
|
||||
export type LearningPathType = CoursePageInterface & {
|
||||
__typename?: 'LearningPathType';
|
||||
content_type?: Maybe<Scalars['String']>;
|
||||
depth: Scalars['Int'];
|
||||
draft_title: Scalars['String'];
|
||||
expire_at?: Maybe<Scalars['DateTime']>;
|
||||
expired: Scalars['Boolean'];
|
||||
first_published_at?: Maybe<Scalars['DateTime']>;
|
||||
frontend_url?: Maybe<Scalars['String']>;
|
||||
go_live_at?: Maybe<Scalars['DateTime']>;
|
||||
has_unpublished_changes: Scalars['Boolean'];
|
||||
id?: Maybe<Scalars['ID']>;
|
||||
last_published_at?: Maybe<Scalars['DateTime']>;
|
||||
latest_revision_created_at?: Maybe<Scalars['DateTime']>;
|
||||
live?: Maybe<Scalars['Boolean']>;
|
||||
locked: Scalars['Boolean'];
|
||||
locked_at?: Maybe<Scalars['DateTime']>;
|
||||
content_type?: Maybe<Scalars['String']['output']>;
|
||||
depth: Scalars['Int']['output'];
|
||||
draft_title: Scalars['String']['output'];
|
||||
expire_at?: Maybe<Scalars['DateTime']['output']>;
|
||||
expired: Scalars['Boolean']['output'];
|
||||
first_published_at?: Maybe<Scalars['DateTime']['output']>;
|
||||
frontend_url?: Maybe<Scalars['String']['output']>;
|
||||
go_live_at?: Maybe<Scalars['DateTime']['output']>;
|
||||
has_unpublished_changes: Scalars['Boolean']['output'];
|
||||
id?: Maybe<Scalars['ID']['output']>;
|
||||
last_published_at?: Maybe<Scalars['DateTime']['output']>;
|
||||
latest_revision_created_at?: Maybe<Scalars['DateTime']['output']>;
|
||||
live?: Maybe<Scalars['Boolean']['output']>;
|
||||
locked: Scalars['Boolean']['output'];
|
||||
locked_at?: Maybe<Scalars['DateTime']['output']>;
|
||||
locked_by?: Maybe<UserType>;
|
||||
numchild: Scalars['Int'];
|
||||
numchild: Scalars['Int']['output'];
|
||||
owner?: Maybe<UserType>;
|
||||
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<Scalars['String']>;
|
||||
title?: Maybe<Scalars['String']>;
|
||||
translation_key?: Maybe<Scalars['String']>;
|
||||
url_path: Scalars['String'];
|
||||
show_in_menus: Scalars['Boolean']['output'];
|
||||
slug?: Maybe<Scalars['String']['output']>;
|
||||
title?: Maybe<Scalars['String']['output']>;
|
||||
translation_key?: Maybe<Scalars['String']['output']>;
|
||||
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<Scalars['ID']>;
|
||||
completion_data_string?: InputMaybe<Scalars['String']>;
|
||||
assignment_id: Scalars['ID']['input'];
|
||||
assignment_user_id?: InputMaybe<Scalars['ID']['input']>;
|
||||
completion_data_string?: InputMaybe<Scalars['String']['input']>;
|
||||
completion_status?: InputMaybe<AssignmentCompletionStatus>;
|
||||
course_session_id: Scalars['ID'];
|
||||
evaluation_grade?: InputMaybe<Scalars['Float']>;
|
||||
evaluation_points?: InputMaybe<Scalars['Float']>;
|
||||
course_session_id: Scalars['ID']['input'];
|
||||
evaluation_grade?: InputMaybe<Scalars['Float']['input']>;
|
||||
evaluation_points?: InputMaybe<Scalars['Float']['input']>;
|
||||
};
|
||||
|
||||
/** 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<Scalars['ID']>;
|
||||
slug?: InputMaybe<Scalars['String']>;
|
||||
id?: InputMaybe<Scalars['ID']['input']>;
|
||||
slug?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryAssignmentCompletionArgs = {
|
||||
assignment_id: Scalars['ID'];
|
||||
assignment_user_id?: InputMaybe<Scalars['ID']>;
|
||||
course_session_id: Scalars['ID'];
|
||||
assignment_id: Scalars['ID']['input'];
|
||||
assignment_user_id?: InputMaybe<Scalars['ID']['input']>;
|
||||
course_session_id: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryCourseArgs = {
|
||||
id?: InputMaybe<Scalars['Int']>;
|
||||
id?: InputMaybe<Scalars['Int']['input']>;
|
||||
};
|
||||
|
||||
export type SendFeedbackInput = {
|
||||
clientMutationId?: InputMaybe<Scalars['String']>;
|
||||
course_session: Scalars['Int'];
|
||||
data?: InputMaybe<Scalars['GenericScalar']>;
|
||||
page: Scalars['String'];
|
||||
clientMutationId?: InputMaybe<Scalars['String']['input']>;
|
||||
course_session: Scalars['Int']['input'];
|
||||
data?: InputMaybe<Scalars['GenericScalar']['input']>;
|
||||
page: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type SendFeedbackPayload = {
|
||||
__typename?: 'SendFeedbackPayload';
|
||||
clientMutationId?: Maybe<Scalars['String']>;
|
||||
clientMutationId?: Maybe<Scalars['String']['output']>;
|
||||
/** May contain more than one error for same field. */
|
||||
errors?: Maybe<Array<Maybe<ErrorType>>>;
|
||||
feedback_response?: Maybe<FeedbackResponse>;
|
||||
|
|
@ -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<string> } | null> | null } | null };
|
||||
|
||||
export type UpsertAssignmentCompletionMutationVariables = Exact<{
|
||||
assignmentId: Scalars['ID'];
|
||||
courseSessionId: Scalars['ID'];
|
||||
assignmentUserId?: InputMaybe<Scalars['ID']>;
|
||||
assignmentId: Scalars['ID']['input'];
|
||||
courseSessionId: Scalars['ID']['input'];
|
||||
assignmentUserId?: InputMaybe<Scalars['ID']['input']>;
|
||||
completionStatus: AssignmentCompletionStatus;
|
||||
completionDataString: Scalars['String'];
|
||||
evaluationGrade?: InputMaybe<Scalars['Float']>;
|
||||
evaluationPoints?: InputMaybe<Scalars['Float']>;
|
||||
completionDataString: Scalars['String']['input'];
|
||||
evaluationGrade?: InputMaybe<Scalars['Float']['input']>;
|
||||
evaluationPoints?: InputMaybe<Scalars['Float']['input']>;
|
||||
}>;
|
||||
|
||||
|
||||
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<Scalars['ID']>;
|
||||
assignmentId: Scalars['ID']['input'];
|
||||
courseSessionId: Scalars['ID']['input'];
|
||||
assignmentUserId?: InputMaybe<Scalars['ID']['input']>;
|
||||
}>;
|
||||
|
||||
|
||||
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'];
|
||||
}>;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@
|
|||
"assignmentSubmitted": "Du hast deine Ergebnisse erfolgreich abgegeben.",
|
||||
"confirmSubmitPerson": "Hiermit bestätige ich, dass die folgende Person meine Ergebnisse bewerten soll.",
|
||||
"confirmSubmitResults": "Hiermit bestätige ich, dass ich die Zusammenfassung meiner Ergebnisse überprüft habe und so abgeben will.",
|
||||
"dueDateIntroduction": "Reiche deine Ergebnisse pünktlich ein bis am {{date}} um {{time}} Uhr ein.",
|
||||
"dueDateIntroduction": "Reiche deine Ergebnisse pünktlich ein bis am: ",
|
||||
"dueDateNotSet": "Keine Abgabedaten wurden erfasst für diese Durchführung",
|
||||
"dueDateSubmission": "Einreichungstermin: {{date}}",
|
||||
"dueDateSubmission": "Einreichungstermin:",
|
||||
"dueDateTitle": "Abgabetermin",
|
||||
"edit": "Bearbeiten",
|
||||
"effortTitle": "Zeitaufwand",
|
||||
|
|
@ -85,11 +85,14 @@
|
|||
},
|
||||
"dashboard": {
|
||||
"courses": "Lehrgang",
|
||||
"dueDatesTitle": "Termine",
|
||||
"nocourses": "Du wurdest noch keinem Lehrgang zugewiesen.",
|
||||
"welcome": "Willkommen, {{name}}"
|
||||
},
|
||||
"dueDates": {
|
||||
"nextDueDates": "Nächste Termine"
|
||||
"nextDueDates": "Nächste Termine",
|
||||
"noDueDatesAvailable": "Keine Termine vorhanden",
|
||||
"showAllDueDates": "Alle Termine anzeigen"
|
||||
},
|
||||
"feedback": {
|
||||
"answers": "Antworten",
|
||||
|
|
@ -185,6 +188,7 @@
|
|||
"learningPathPage": {
|
||||
"currentCircle": "Aktueller Circle",
|
||||
"listView": "Listenansicht",
|
||||
"nextDueDates": "Nächste Termine",
|
||||
"nextStep": "Nächster Schritt",
|
||||
"pathView": "Pfadansicht",
|
||||
"progressText": "Du hast {{ inProgressCount }} von {{ allCount }} Circles bearbeitet",
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@
|
|||
"assignmentSubmitted": "Tes résultats ont bien été transmis.",
|
||||
"confirmSubmitPerson": "Par la présente, je confirme que la personne suivante doit évaluer mes résultats.",
|
||||
"confirmSubmitResults": "Par la présente, je confirme que j’ai vérifié la synthèse de mes résultats et que je souhaite la remettre telle quelle.",
|
||||
"dueDateIntroduction": "Envoie tes résultats dans les délais avant le {{date}} à {{time}} heures.",
|
||||
"dueDateIntroduction": "Envoie tes résultats dans les délais avant le:",
|
||||
"dueDateNotSet": "Aucune date de remise n’a été spécifiée pour cette opération.",
|
||||
"dueDateSubmission": "Date de clôture : {{date}}",
|
||||
"dueDateSubmission": "Date de clôture: ",
|
||||
"dueDateTitle": "Date de remise",
|
||||
"edit": "Traiter",
|
||||
"effortTitle": "Temps nécessaire",
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@
|
|||
"assignmentSubmitted": "I tuoi risultati sono stati consegnati con successo.",
|
||||
"confirmSubmitPerson": "Confermo che i miei risultati dovranno essere valutati dalla seguente persona.",
|
||||
"confirmSubmitResults": "Confermo di aver controllato il riepilogo dei miei risultati e di volerli consegnare.",
|
||||
"dueDateIntroduction": "Presenta i tuoi risultati entro il {{date}} alle {{time}}.",
|
||||
"dueDateIntroduction": "Presenta i tuoi risultati entro il:",
|
||||
"dueDateNotSet": "Non sono stati registrati dati di consegna per questo svolgimento",
|
||||
"dueDateSubmission": "Termine di presentazione: {{date}}",
|
||||
"dueDateSubmission": "Termine di presentazione: ",
|
||||
"dueDateTitle": "Termine di consegna",
|
||||
"edit": "Modificare",
|
||||
"effortTitle": "Tempo richiesto",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import DueDatesList from "@/components/dueDates/DueDatesList.vue";
|
||||
import LearningPathDiagramSmall from "@/components/learningPath/LearningPathDiagramSmall.vue";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
|
|
@ -15,6 +16,7 @@ onMounted(async () => {
|
|||
log.debug("DashboardPage mounted");
|
||||
});
|
||||
|
||||
const allDueDates = courseSessionsStore.allDueDates();
|
||||
const getNextStepLink = (courseSession: CourseSession) => {
|
||||
return computed(() => {
|
||||
if (courseSessionsStore.hasCockpit(courseSession)) {
|
||||
|
|
@ -69,22 +71,13 @@ const getNextStepLink = (courseSession: CourseSession) => {
|
|||
<p>{{ $t("dashboard.nocourses") }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="mb-6">Termine</h3>
|
||||
<ul class="bg-white p-4">
|
||||
<li class="flex flex-row py-4">
|
||||
{{ $t("Zur Zeit sind keine Termine vorhanden") }}
|
||||
</li>
|
||||
<!-- li class="flex flex-row border-b py-4">
|
||||
<p class="text-bold w-60">Austausch mit Trainer</p>
|
||||
<p class="grow">Fr, 24. November 2022, 11 Uhr</p>
|
||||
<p class="underline">Details anschauen</p>
|
||||
</li>
|
||||
<li class="flex flex-row py-4">
|
||||
<p class="text-bold w-60">Vernetzen - Live Session</p>
|
||||
<p class="grow">Di, 4. Dezember 2022, 10.30 Uhr</p>
|
||||
<p class="underline">Details anschauen</p>
|
||||
</li -->
|
||||
</ul>
|
||||
<h3 class="mb-6">{{ $t("dashboard.dueDatesTitle") }}</h3>
|
||||
<DueDatesList
|
||||
class="bg-white p-6"
|
||||
:due-dates="allDueDates"
|
||||
:max-count="10"
|
||||
:show-top-border="false"
|
||||
></DueDatesList>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -216,6 +216,15 @@ function log(data: any) {
|
|||
ls-end
|
||||
<it-icon-ls-end />
|
||||
</div>
|
||||
|
||||
<div class="inline-flex flex-col">
|
||||
calendar
|
||||
<it-icon-calendar />
|
||||
</div>
|
||||
<div class="inline-flex flex-col">
|
||||
calendar-light
|
||||
<it-icon-calenda-light />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-8 mt-8 flex flex-col flex-wrap gap-4 lg:flex-row">
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ function editTask(task: AssignmentEvaluationTask) {
|
|||
const assignmentDetail = computed(() => findAssignmentDetail(props.assignment.id));
|
||||
|
||||
const dueDate = computed(() =>
|
||||
dayjs(assignmentDetail.value?.evaluationDeadlineDateTimeUtc)
|
||||
dayjs(assignmentDetail.value?.evaluation_deadline_start)
|
||||
);
|
||||
|
||||
const inEvaluationTask = computed(
|
||||
|
|
|
|||
|
|
@ -56,12 +56,12 @@ const assignmentDetail = computed(() =>
|
|||
<div v-if="assignmentDetail">
|
||||
<span>
|
||||
Abgabetermin:
|
||||
{{ dayjs(assignmentDetail.submissionDeadlineDateTimeUtc).format("DD.MM.YYYY") }}
|
||||
{{ dayjs(assignmentDetail.submission_deadline_start).format("DD.MM.YYYY") }}
|
||||
</span>
|
||||
-
|
||||
<span>
|
||||
Freigabetermin:
|
||||
{{ dayjs(assignmentDetail.evaluationDeadlineDateTimeUtc).format("DD.MM.YYYY") }}
|
||||
{{ dayjs(assignmentDetail.evaluation_deadline_start).format("DD.MM.YYYY") }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
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;
|
||||
|
|
@ -12,6 +14,10 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
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");
|
||||
</script>
|
||||
|
||||
|
|
@ -37,12 +43,8 @@ const step = useRouteQuery("step");
|
|||
|
||||
<h3 class="mb-4 mt-8">{{ $t("assignment.dueDateTitle") }}</h3>
|
||||
<p v-if="props.dueDate" class="text-large">
|
||||
{{
|
||||
$t("assignment.dueDateIntroduction", {
|
||||
date: dueDate!.format("DD.MM.YYYY"),
|
||||
time: dueDate!.format("HH:mm"),
|
||||
})
|
||||
}}
|
||||
{{ $t("assignment.dueDateIntroduction") }}
|
||||
<DateEmbedding :single-date="dueDate"></DateEmbedding>
|
||||
</p>
|
||||
<p v-else class="text-large">
|
||||
{{ $t("assignment.dueDateNotSet") }}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import DateEmbedding from "@/components/dueDates/DateEmbedding.vue";
|
||||
import ItButton from "@/components/ui/ItButton.vue";
|
||||
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
|
||||
import ItSuccessAlert from "@/components/ui/ItSuccessAlert.vue";
|
||||
|
|
@ -119,7 +120,8 @@ const onSubmit = async () => {
|
|||
</a>
|
||||
</div>
|
||||
<p class="pt-6">
|
||||
{{ $t("assignment.dueDateSubmission", { date: dueDate.format("DD.MM.YYYY") }) }}
|
||||
{{ $t("assignment.dueDateSubmission") }}
|
||||
<DateEmbedding :single-date="dueDate"></DateEmbedding>
|
||||
</p>
|
||||
<ItButton
|
||||
class="mt-6"
|
||||
|
|
|
|||
|
|
@ -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_start)
|
||||
);
|
||||
const currentTask = computed(() => {
|
||||
if (stepIndex.value > 0 && stepIndex.value <= numTasks.value) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div class="mb-12 grid grid-cols-icon-card gap-x-4 grid-areas-icon-card">
|
||||
<it-icon-calendar class="w-[60px] grid-in-icon" />
|
||||
<it-icon-calendar-light class="w-[60px] grid-in-icon" />
|
||||
<h2 class="text-large font-bold grid-in-title">Datum</h2>
|
||||
<p class="grid-in-value">{{ start }} - {{ end }}</p>
|
||||
<p class="grid-in-value">{{ formatDate(start, end) }}</p>
|
||||
</div>
|
||||
<div class="mb-12 grid grid-cols-icon-card gap-x-4 grid-areas-icon-card">
|
||||
<it-icon-location class="w-[60px] grid-in-icon" />
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { formatDate } from "@/components/dueDates/dueDatesUtils";
|
||||
import type { CourseSessionAttendanceCourse } from "@/types";
|
||||
import dayjs from "dayjs";
|
||||
import LocalizedFormat from "dayjs/plugin/localizedFormat";
|
||||
|
|
@ -32,9 +33,8 @@ export interface Props {
|
|||
const props = defineProps<Props>();
|
||||
|
||||
dayjs.extend(LocalizedFormat);
|
||||
const format = "LLLL";
|
||||
const start = computed(() => dayjs(props.attendanceCourse.start).format(format));
|
||||
const end = computed(() => dayjs(props.attendanceCourse.end).format(format));
|
||||
const start = computed(() => dayjs(props.attendanceCourse.start));
|
||||
const end = computed(() => dayjs(props.attendanceCourse.end));
|
||||
const location = computed(() => props.attendanceCourse.location);
|
||||
const trainer = computed(() => props.attendanceCourse.trainer);
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
<template>
|
||||
<p class="mb-4 font-bold">{{ $t("dueDates.nextDueDates") }}:</p>
|
||||
<!-- ul>
|
||||
<li class="border-b border-t py-3">
|
||||
<p class="pr-12">24. November 2022, 11 Uhr - Austausch mit Trainer</p>
|
||||
</li>
|
||||
<li class="border-b py-3">
|
||||
<p class="pr-12">4. Dezember 2022, 10.30 Uhr - Vernetzen - Live Session</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="pt-2 underline">Alle Termine anzeigen</p -->
|
||||
<p class="min-h-[150px] pt-2">{{ $t("Zur Zeit sind keine Termine vorhanden") }}</p>
|
||||
</template>
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import * as d3 from "d3";
|
||||
import * as log from "loglevel";
|
||||
import { computed, onMounted } from "vue";
|
||||
|
||||
// @ts-ignore
|
||||
|
|
@ -17,7 +16,6 @@ const props = defineProps<{
|
|||
}>();
|
||||
|
||||
onMounted(async () => {
|
||||
log.debug("LearningPathCircle mounted");
|
||||
render();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import LearningPathAppointmentsMock from "@/pages/learningPath/learningPathPage/LearningPathAppointmentsMock.vue";
|
||||
import DueDatesShortList from "@/components/dueDates/DueDatesShortList.vue";
|
||||
import LearningPathListView from "@/pages/learningPath/learningPathPage/LearningPathListView.vue";
|
||||
import LearningPathPathView from "@/pages/learningPath/learningPathPage/LearningPathPathView.vue";
|
||||
import CircleProgress from "@/pages/learningPath/learningPathPage/LearningPathProgress.vue";
|
||||
|
|
@ -74,7 +74,7 @@ const changeViewType = (viewType: ViewType) => {
|
|||
<!-- Top -->
|
||||
<div class="flex flex-row justify-between space-x-8 bg-gray-200 p-6 sm:p-12">
|
||||
<!-- Left -->
|
||||
<div class="flex flex-col justify-between">
|
||||
<div class="flex w-1/2 flex-col justify-between">
|
||||
<div>
|
||||
<p class="font-bold">
|
||||
{{ $t("learningPathPage.welcomeBack") }}
|
||||
|
|
@ -91,8 +91,11 @@ const changeViewType = (viewType: ViewType) => {
|
|||
</div>
|
||||
|
||||
<!-- Right -->
|
||||
<div v-if="!useMobileLayout" class="max-w-md">
|
||||
<LearningPathAppointmentsMock></LearningPathAppointmentsMock>
|
||||
<div v-if="!useMobileLayout" class="flex-grow">
|
||||
<div class="text-bold pb-3">
|
||||
{{ $t("learningPathPage.nextDueDates") }}
|
||||
</div>
|
||||
<DueDatesShortList :max-count="2" :show-top-border="true"></DueDatesShortList>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -3,13 +3,15 @@ import { deleteCircleDocument } from "@/services/files";
|
|||
import type {
|
||||
CircleDocument,
|
||||
CourseSession,
|
||||
CourseSessionAssignmentDetails,
|
||||
CourseSessionAssignment,
|
||||
CourseSessionAttendanceCourse,
|
||||
CourseSessionUser,
|
||||
DueDate,
|
||||
ExpertSessionUser,
|
||||
} from "@/types";
|
||||
import eventBus from "@/utils/eventBus";
|
||||
import { useLocalStorage } from "@vueuse/core";
|
||||
import dayjs from "dayjs";
|
||||
import uniqBy from "lodash/uniqBy";
|
||||
import log from "loglevel";
|
||||
import { defineStore } from "pinia";
|
||||
|
|
@ -25,7 +27,6 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
|||
|
||||
async function loadCourseSessionsData(reload = false) {
|
||||
log.debug("loadCourseSessionsData called");
|
||||
|
||||
allCourseSessions.value = await itGetCached(`/api/course/sessions/`, {
|
||||
reload: reload,
|
||||
});
|
||||
|
|
@ -39,6 +40,10 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
|||
reload: reload,
|
||||
})) as CourseSessionUser[];
|
||||
cs.users = users;
|
||||
cs.due_dates.forEach((dueDate) => {
|
||||
dueDate.start = dayjs(dueDate.start);
|
||||
dueDate.end = dayjs(dueDate.end);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -190,6 +195,16 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
|||
currentCourseSession.value?.documents.push(document);
|
||||
}
|
||||
|
||||
function allDueDates() {
|
||||
const allDueDatesReturn: DueDate[] = [];
|
||||
|
||||
allCourseSessions.value?.forEach((cs) => {
|
||||
allDueDatesReturn.push(...cs.due_dates);
|
||||
});
|
||||
allDueDatesReturn.sort((a, b) => dayjs(a.end).diff(dayjs(b.end)));
|
||||
return allDueDatesReturn;
|
||||
}
|
||||
|
||||
async function startUpload() {
|
||||
log.debug("loadCourseSessionsData called");
|
||||
allCourseSessions.value = await itPost(`/api/core/file/start`, {
|
||||
|
|
@ -215,17 +230,17 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
|||
): CourseSessionAttendanceCourse | undefined {
|
||||
if (currentCourseSession.value) {
|
||||
return currentCourseSession.value.attendance_courses.find(
|
||||
(attendanceCourse) => attendanceCourse.learningContentId === contentId
|
||||
(attendanceCourse) => attendanceCourse.learning_content_id === contentId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function findAssignmentDetails(
|
||||
function findCourseSessionAssignment(
|
||||
contentId?: number
|
||||
): CourseSessionAssignmentDetails | undefined {
|
||||
): CourseSessionAssignment | undefined {
|
||||
if (contentId && currentCourseSession.value) {
|
||||
return currentCourseSession.value.assignment_details_list.find(
|
||||
(assignmentDetails) => assignmentDetails.learningContentId === contentId
|
||||
return currentCourseSession.value.assignments.find(
|
||||
(a) => a.learning_content_id === contentId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -244,7 +259,8 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
|||
startUpload,
|
||||
removeDocument,
|
||||
findAttendanceCourse,
|
||||
findAssignmentDetails,
|
||||
findCourseSessionAssignment,
|
||||
allDueDates,
|
||||
|
||||
// use `useCurrentCourseSession` whenever possible
|
||||
currentCourseSession,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { AssignmentCompletionStatus as AssignmentCompletionStatusGenerated } from "@/gql/graphql";
|
||||
import type { Circle } from "@/services/circle";
|
||||
import type { Dayjs } from "dayjs";
|
||||
import type { Component } from "vue";
|
||||
|
||||
export type LoginMethod = "local" | "sso";
|
||||
|
|
@ -412,17 +413,24 @@ export interface CircleDocument {
|
|||
}
|
||||
|
||||
export interface CourseSessionAttendanceCourse {
|
||||
learningContentId: number;
|
||||
id: number;
|
||||
course_session_id: number;
|
||||
learning_content_id: number;
|
||||
start: string;
|
||||
end: string;
|
||||
location: string;
|
||||
trainer: string;
|
||||
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_start: string;
|
||||
evaluation_deadline_id: number;
|
||||
evaluation_deadline_start: string;
|
||||
}
|
||||
|
||||
export interface CourseSession {
|
||||
|
|
@ -439,9 +447,10 @@ export interface CourseSession {
|
|||
course_url: string;
|
||||
media_library_url: string;
|
||||
attendance_courses: CourseSessionAttendanceCourse[];
|
||||
assignment_details_list: CourseSessionAssignmentDetails[];
|
||||
assignments: CourseSessionAssignment[];
|
||||
documents: CircleDocument[];
|
||||
users: CourseSessionUser[];
|
||||
due_dates: DueDate[];
|
||||
}
|
||||
|
||||
export type Role = "MEMBER" | "EXPERT" | "TUTOR";
|
||||
|
|
@ -556,3 +565,15 @@ export interface UserAssignmentCompletionStatus {
|
|||
completion_status: AssignmentCompletionStatus;
|
||||
evaluation_grade: number | null;
|
||||
}
|
||||
|
||||
export type DueDate = {
|
||||
id: number;
|
||||
start: Dayjs;
|
||||
end: Dayjs;
|
||||
title: string;
|
||||
learning_content_description: string;
|
||||
description: string;
|
||||
url: string;
|
||||
course_session: number | null;
|
||||
page: number | null;
|
||||
};
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -116,10 +116,12 @@ LOCAL_APPS = [
|
|||
"vbv_lernwelt.learnpath",
|
||||
"vbv_lernwelt.competence",
|
||||
"vbv_lernwelt.media_library",
|
||||
"vbv_lernwelt.course_session",
|
||||
"vbv_lernwelt.feedback",
|
||||
"vbv_lernwelt.files",
|
||||
"vbv_lernwelt.notify",
|
||||
"vbv_lernwelt.assignment",
|
||||
"vbv_lernwelt.duedate",
|
||||
]
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
||||
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import json
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import wagtail_factories
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from slugify import slugify
|
||||
from wagtail.models import Site
|
||||
from wagtail.rich_text import RichText
|
||||
|
|
@ -34,7 +37,15 @@ from vbv_lernwelt.course.models import (
|
|||
CourseSession,
|
||||
CourseSessionUser,
|
||||
)
|
||||
from vbv_lernwelt.learnpath.models import Circle
|
||||
from vbv_lernwelt.course_session.models import (
|
||||
CourseSessionAssignment,
|
||||
CourseSessionAttendanceCourse,
|
||||
)
|
||||
from vbv_lernwelt.learnpath.models import (
|
||||
Circle,
|
||||
LearningContentAssignment,
|
||||
LearningContentAttendanceCourse,
|
||||
)
|
||||
from vbv_lernwelt.learnpath.tests.learning_path_factories import (
|
||||
CircleFactory,
|
||||
LearningContentAssignmentFactory,
|
||||
|
|
@ -79,16 +90,19 @@ def create_test_course(include_uk=True, include_vv=True, with_sessions=False):
|
|||
create_test_media_library()
|
||||
|
||||
if with_sessions:
|
||||
now = timezone.now()
|
||||
# course sessions
|
||||
cs_bern = CourseSession.objects.create(
|
||||
course_id=COURSE_TEST_ID,
|
||||
title="Test Bern 2022 a",
|
||||
id=TEST_COURSE_SESSION_BERN_ID,
|
||||
start_date=now,
|
||||
)
|
||||
cs_zurich = CourseSession.objects.create(
|
||||
course_id=COURSE_TEST_ID,
|
||||
title="Test Zürich 2022 a",
|
||||
id=TEST_COURSE_SESSION_ZURICH_ID,
|
||||
start_date=now,
|
||||
)
|
||||
|
||||
trainer1 = User.objects.get(email="test-trainer1@example.com")
|
||||
|
|
@ -115,6 +129,18 @@ def create_test_course(include_uk=True, include_vv=True, with_sessions=False):
|
|||
course_session=cs_zurich,
|
||||
user=student2,
|
||||
)
|
||||
course = Course.objects.get(id=COURSE_TEST_ID)
|
||||
for cs in CourseSession.objects.filter(course_id=COURSE_TEST_ID):
|
||||
for assignment in LearningContentAssignment.objects.descendant_of(
|
||||
course.coursepage
|
||||
):
|
||||
create_course_session_assignment(cs, assignment)
|
||||
for (
|
||||
attendance_course
|
||||
) in LearningContentAttendanceCourse.objects.descendant_of(
|
||||
course.coursepage
|
||||
):
|
||||
create_course_session_attendance_course(cs, attendance_course)
|
||||
|
||||
return course
|
||||
|
||||
|
|
@ -143,6 +169,45 @@ def create_test_assignment_submitted_data(assignment, course_session, user):
|
|||
)
|
||||
|
||||
|
||||
def create_course_session_assignment(course_session, assignment):
|
||||
csa, created = CourseSessionAssignment.objects.get_or_create(
|
||||
course_session=course_session,
|
||||
learning_content=assignment,
|
||||
)
|
||||
|
||||
if course_session.start_date is None:
|
||||
course_session.start_date = datetime.now() + timedelta(days=12)
|
||||
course_session.save()
|
||||
submission_deadline = csa.submission_deadline
|
||||
if submission_deadline:
|
||||
submission_deadline.start = course_session.start_date + timedelta(days=14)
|
||||
submission_deadline.save()
|
||||
evaluation_deadline = csa.evaluation_deadline
|
||||
if evaluation_deadline:
|
||||
evaluation_deadline.start = course_session.start_date + timedelta(days=28)
|
||||
evaluation_deadline.save()
|
||||
return csa
|
||||
|
||||
|
||||
def create_course_session_attendance_course(course_session, course):
|
||||
casc = CourseSessionAttendanceCourse.objects.create(
|
||||
course_session=course_session,
|
||||
learning_content=course,
|
||||
location="Handelsschule KV Bern, Zimmer 123, Eigerstrasse 16, 3012 Bern",
|
||||
trainer="Roland Grossenbacher, roland.grossenbacher@helvetia.ch",
|
||||
)
|
||||
random_week = random.randint(1, 26)
|
||||
|
||||
casc.due_date.start = timezone.make_aware(
|
||||
datetime(2023, 6, 14, 8, 30) + timedelta(weeks=random_week)
|
||||
)
|
||||
casc.due_date.end = timezone.make_aware(
|
||||
datetime(2023, 6, 14, 17, 0) + timedelta(weeks=random_week)
|
||||
)
|
||||
casc.due_date.save()
|
||||
return casc
|
||||
|
||||
|
||||
def create_test_course_with_categories(apps=None, schema_editor=None):
|
||||
if apps is not None:
|
||||
Course = apps.get_model("course", "Course")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import os
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import djclick as click
|
||||
from django.utils import timezone
|
||||
|
||||
from vbv_lernwelt.assignment.creators.create_assignments import (
|
||||
create_uk_basis_prep_assignment,
|
||||
|
|
@ -68,6 +70,10 @@ from vbv_lernwelt.course.models import (
|
|||
CourseSessionUser,
|
||||
)
|
||||
from vbv_lernwelt.course.services import mark_course_completion
|
||||
from vbv_lernwelt.course_session.models import (
|
||||
CourseSessionAssignment,
|
||||
CourseSessionAttendanceCourse,
|
||||
)
|
||||
from vbv_lernwelt.feedback.creators.create_demo_feedback import create_feedback
|
||||
from vbv_lernwelt.importer.services import (
|
||||
import_course_sessions_from_excel,
|
||||
|
|
@ -237,35 +243,34 @@ def create_course_uk_de():
|
|||
cs = CourseSession.objects.create(
|
||||
course_id=COURSE_UK,
|
||||
title="Bern 2023 a",
|
||||
attendance_courses=[
|
||||
{
|
||||
"learningContentId": LearningContentAttendanceCourse.objects.get(
|
||||
slug="überbetriebliche-kurse-lp-circle-fahrzeug-lc-präsenzkurs-fahrzeug"
|
||||
).id,
|
||||
"start": "2023-05-23T08:30:00+0200",
|
||||
"end": "2023-05-23T17:00:00+0200",
|
||||
"location": "Handelsschule KV Bern, Zimmer 123, Eigerstrasse 16, 3012 Bern",
|
||||
"trainer": "Roland Grossenbacher, roland.grossenbacher@helvetia.ch",
|
||||
}
|
||||
],
|
||||
assignment_details_list=[
|
||||
{
|
||||
"learningContentId": LearningContentAssignment.objects.get(
|
||||
slug="überbetriebliche-kurse-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"
|
||||
).id,
|
||||
"submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z",
|
||||
"evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
||||
},
|
||||
{
|
||||
"learningContentId": LearningContentAssignment.objects.get(
|
||||
slug="überbetriebliche-kurse-lp-circle-fahrzeug-lc-fahrzeug-mein-erstes-auto"
|
||||
).id,
|
||||
"submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z",
|
||||
"evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
for i, cs in enumerate(CourseSession.objects.filter(course_id=COURSE_UK_TRAINING)):
|
||||
create_course_session_assignments(
|
||||
cs,
|
||||
f"{course.slug}-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice",
|
||||
i=i,
|
||||
)
|
||||
|
||||
csac = CourseSessionAttendanceCourse.objects.create(
|
||||
course_session=cs,
|
||||
learning_content=LearningContentAttendanceCourse.objects.get(
|
||||
slug="überbetriebliche-kurse-lp-circle-fahrzeug-lc-präsenzkurs-fahrzeug"
|
||||
),
|
||||
location="Handelsschule KV Bern, Zimmer 123, Eigerstrasse 16, 3012 Bern",
|
||||
trainer="Roland Grossenbacher, roland.grossenbacher@helvetia.ch",
|
||||
)
|
||||
|
||||
# TODO: create dates schlauer
|
||||
random_week = random.randint(1, 26)
|
||||
csac.due_date.start = timezone.make_aware(
|
||||
datetime(2023, 6, 14, 8, 30) + timedelta(weeks=random_week)
|
||||
)
|
||||
csac.due_date.end = timezone.make_aware(
|
||||
datetime(2023, 6, 14, 17, 0) + timedelta(weeks=random_week)
|
||||
)
|
||||
csac.due_date.save()
|
||||
|
||||
# figma demo users and data
|
||||
csu = CourseSessionUser.objects.create(
|
||||
course_session=cs,
|
||||
|
|
@ -537,24 +542,12 @@ def create_course_training_de():
|
|||
f"{current_dir}/../../../importer/tests/Schulungen_Teilnehmende.xlsx",
|
||||
)
|
||||
|
||||
for cs in CourseSession.objects.filter(course_id=COURSE_UK_TRAINING):
|
||||
cs.assignment_details_list = [
|
||||
{
|
||||
"learningContentId": LearningContentAssignment.objects.get(
|
||||
slug=f"{course.slug}-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"
|
||||
).id,
|
||||
"submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z",
|
||||
"evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
||||
},
|
||||
{
|
||||
"learningContentId": LearningContentAssignment.objects.get(
|
||||
slug=f"{course.slug}-lp-circle-fahrzeug-lc-fahrzeug-mein-erstes-auto"
|
||||
).id,
|
||||
"submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z",
|
||||
"evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
||||
},
|
||||
]
|
||||
cs.save()
|
||||
for i, cs in enumerate(CourseSession.objects.filter(course_id=COURSE_UK_TRAINING)):
|
||||
create_course_session_assignments(
|
||||
cs,
|
||||
f"{course.slug}-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice",
|
||||
i=i,
|
||||
)
|
||||
|
||||
# attach users as trainers to ÜK course
|
||||
course_uk = Course.objects.filter(id=COURSE_UK).first()
|
||||
|
|
@ -586,6 +579,24 @@ def create_course_training_de():
|
|||
csu.save()
|
||||
|
||||
|
||||
def create_course_session_assignments(course_session, assignment_slug, i=1):
|
||||
csa = CourseSessionAssignment.objects.create(
|
||||
course_session=course_session,
|
||||
learning_content=LearningContentAssignment.objects.get(slug=assignment_slug),
|
||||
)
|
||||
if course_session.start_date is None:
|
||||
course_session.start_date = datetime.now() + timedelta(days=i * 12)
|
||||
course_session.save()
|
||||
submission_deadline = csa.submission_deadline
|
||||
if submission_deadline:
|
||||
submission_deadline.start = course_session.start_date + timedelta(days=14)
|
||||
submission_deadline.save()
|
||||
evaluation_deadline = csa.evaluation_deadline
|
||||
if evaluation_deadline:
|
||||
evaluation_deadline.start = course_session.start_date + timedelta(days=28)
|
||||
evaluation_deadline.save()
|
||||
|
||||
|
||||
def create_course_training_fr():
|
||||
# Test Lehrgang für üK Trainer FR
|
||||
course = create_versicherungsvermittlerin_with_categories(
|
||||
|
|
@ -622,22 +633,22 @@ def create_course_training_fr():
|
|||
)
|
||||
|
||||
for cs in CourseSession.objects.filter(course_id=COURSE_UK_TRAINING_FR):
|
||||
cs.assignment_details_list = [
|
||||
{
|
||||
"learningContentId": LearningContentAssignment.objects.get(
|
||||
slug=f"{course.slug}-lp-circle-véhicule-lc-vérification-dune-police-dassurance-de-véhicule-à-moteur"
|
||||
).id,
|
||||
"submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z",
|
||||
"evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
||||
},
|
||||
{
|
||||
"learningContentId": LearningContentAssignment.objects.get(
|
||||
slug=f"{course.slug}-lp-circle-véhicule-lc-véhicule-à-moteur-ma-première-voiture"
|
||||
).id,
|
||||
"submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z",
|
||||
"evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
||||
},
|
||||
]
|
||||
# cs.assignment_details_list = [
|
||||
# {
|
||||
# "learningContentId": LearningContentAssignment.objects.get(
|
||||
# slug=f"{course.slug}-lp-circle-véhicule-lc-vérification-dune-police-dassurance-de-véhicule-à-moteur"
|
||||
# ).id,
|
||||
# "submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z",
|
||||
# "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
||||
# },
|
||||
# {
|
||||
# "learningContentId": LearningContentAssignment.objects.get(
|
||||
# slug=f"{course.slug}-lp-circle-véhicule-lc-véhicule-à-moteur-ma-première-voiture"
|
||||
# ).id,
|
||||
# "submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z",
|
||||
# "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
||||
# },
|
||||
# ]
|
||||
cs.save()
|
||||
|
||||
# attach users as trainers to ÜK course
|
||||
|
|
@ -709,22 +720,22 @@ def create_course_training_it():
|
|||
)
|
||||
|
||||
for cs in CourseSession.objects.filter(course_id=COURSE_UK_TRAINING_IT):
|
||||
cs.assignment_details_list = [
|
||||
{
|
||||
"learningContentId": LearningContentAssignment.objects.get(
|
||||
slug=f"{course.slug}-lp-circle-veicolo-lc-verifica-di-una-polizza-di-assicurazione-veicoli-a-motore"
|
||||
).id,
|
||||
"submissionDeadlineDateTimeUtc": "2023-06-20T19:00:00Z",
|
||||
"evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
||||
},
|
||||
{
|
||||
"learningContentId": LearningContentAssignment.objects.get(
|
||||
slug=f"{course.slug}-lp-circle-veicolo-lc-veicolo-la-mia-prima-auto"
|
||||
).id,
|
||||
"submissionDeadlineDateTimeUtc": "2023-06-20T19:00:00Z",
|
||||
"evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
||||
},
|
||||
]
|
||||
# cs.assignment_details_list = [
|
||||
# {
|
||||
# "learningContentId": LearningContentAssignment.objects.get(
|
||||
# slug=f"{course.slug}-lp-circle-veicolo-lc-verifica-di-una-polizza-di-assicurazione-veicoli-a-motore"
|
||||
# ).id,
|
||||
# "submissionDeadlineDateTimeUtc": "2023-06-20T19:00:00Z",
|
||||
# "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
||||
# },
|
||||
# {
|
||||
# "learningContentId": LearningContentAssignment.objects.get(
|
||||
# slug=f"{course.slug}-lp-circle-veicolo-lc-veicolo-la-mia-prima-auto"
|
||||
# ).id,
|
||||
# "submissionDeadlineDateTimeUtc": "2023-06-20T19:00:00Z",
|
||||
# "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
||||
# },
|
||||
# ]
|
||||
cs.save()
|
||||
|
||||
# attach users as trainers to ÜK course
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
# Generated by Django 3.2.13 on 2023-06-14 14:02
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("course", "0004_import_fields"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="coursesession",
|
||||
name="attendance_courses",
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# Generated by Django 3.2.13 on 2023-06-21 14:19
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("course", "0005_remove_coursesession_attendance_courses"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="coursesession",
|
||||
name="assignment_details_list",
|
||||
),
|
||||
]
|
||||
|
|
@ -2,7 +2,6 @@ from django.db import models
|
|||
from django.db.models import UniqueConstraint
|
||||
from django.utils.text import slugify
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_jsonform.models.fields import JSONField
|
||||
from grapple.models import GraphQLString
|
||||
from wagtail.models import Page
|
||||
|
||||
|
|
@ -193,24 +192,6 @@ class CourseSession(models.Model):
|
|||
Das anhängen kann via CourseSessionUser oder "Schulklasse (TODO)" geschehen
|
||||
"""
|
||||
|
||||
ATTENDANCE_COURSES_SCHEMA = {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"learningContentId": {
|
||||
"type": "number",
|
||||
"title": "ID des Lerninhalts",
|
||||
"required": True,
|
||||
},
|
||||
"start": {"type": "string", "format": "datetime"},
|
||||
"end": {"type": "string", "format": "datetime"},
|
||||
"location": {"type": "string"},
|
||||
"trainer": {"type": "string"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
|
|
@ -226,11 +207,6 @@ class CourseSession(models.Model):
|
|||
start_date = models.DateField(null=True, blank=True)
|
||||
end_date = models.DateField(null=True, blank=True)
|
||||
|
||||
attendance_courses = JSONField(
|
||||
schema=ATTENDANCE_COURSES_SCHEMA, blank=True, default=list
|
||||
)
|
||||
assignment_details_list = models.JSONField(default=list, blank=True)
|
||||
|
||||
additional_json_data = models.JSONField(default=dict, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
|
|
|
|||
|
|
@ -7,6 +7,16 @@ from vbv_lernwelt.course.models import (
|
|||
CourseCompletion,
|
||||
CourseSession,
|
||||
)
|
||||
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
|
||||
from vbv_lernwelt.duedate.serializers import DueDateSerializer
|
||||
|
||||
|
||||
class CourseSerializer(serializers.ModelSerializer):
|
||||
|
|
@ -50,6 +60,9 @@ class CourseSessionSerializer(serializers.ModelSerializer):
|
|||
competence_url = serializers.SerializerMethodField()
|
||||
media_library_url = serializers.SerializerMethodField()
|
||||
documents = serializers.SerializerMethodField()
|
||||
attendance_courses = serializers.SerializerMethodField()
|
||||
assignments = serializers.SerializerMethodField()
|
||||
due_dates = serializers.SerializerMethodField()
|
||||
|
||||
def get_course(self, obj):
|
||||
return CourseSerializer(obj.course).data
|
||||
|
|
@ -75,6 +88,20 @@ class CourseSessionSerializer(serializers.ModelSerializer):
|
|||
)
|
||||
return CircleDocumentSerializer(documents, many=True).data
|
||||
|
||||
def get_attendance_courses(self, obj):
|
||||
return CourseSessionAttendanceCourseSerializer(
|
||||
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_due_dates(self, obj):
|
||||
due_dates = DueDate.objects.filter(course_session=obj)
|
||||
return DueDateSerializer(due_dates, many=True).data
|
||||
|
||||
class Meta:
|
||||
model = CourseSession
|
||||
fields = [
|
||||
|
|
@ -87,13 +114,14 @@ class CourseSessionSerializer(serializers.ModelSerializer):
|
|||
"end_date",
|
||||
"additional_json_data",
|
||||
"attendance_courses",
|
||||
"assignment_details_list",
|
||||
"assignments",
|
||||
"learning_path_url",
|
||||
"cockpit_url",
|
||||
"competence_url",
|
||||
"media_library_url",
|
||||
"course_url",
|
||||
"documents",
|
||||
"due_dates",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from vbv_lernwelt.course_session.models import (
|
||||
CourseSessionAssignment,
|
||||
CourseSessionAttendanceCourse,
|
||||
)
|
||||
|
||||
|
||||
@admin.register(CourseSessionAttendanceCourse)
|
||||
class CourseSessionAttendanceCourseAdmin(admin.ModelAdmin):
|
||||
# Inline fields are not possible for the DueDate model, because it is not a ForeignKey relatoion.
|
||||
readonly_fields = ["course_session", "learning_content", "due_date"]
|
||||
list_display = [
|
||||
"course_session",
|
||||
"learning_content",
|
||||
"trainer",
|
||||
]
|
||||
list_filter = ["course_session__course", "course_session"]
|
||||
|
||||
|
||||
@admin.register(CourseSessionAssignment)
|
||||
class CourseSessionAssignmentAdmin(admin.ModelAdmin):
|
||||
# Inline fields are not possible for the DueDate model, because it is not a ForeignKey relatoion.
|
||||
readonly_fields = ["course_session", "learning_content"]
|
||||
list_display = [
|
||||
"course_session",
|
||||
"learning_content",
|
||||
]
|
||||
list_filter = ["course_session__course"]
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CourseSessionConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "vbv_lernwelt.course_session"
|
||||
|
||||
def ready(self):
|
||||
try:
|
||||
# pylint: disable=unused-import,import-outside-toplevel
|
||||
import vbv_lernwelt.course_session.signals # noqa F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import factory
|
||||
from factory.django import DjangoModelFactory
|
||||
|
||||
from vbv_lernwelt.course.factories import CourseFactory
|
||||
from vbv_lernwelt.course.models import CourseSession
|
||||
from vbv_lernwelt.course_session.models import (
|
||||
CourseSessionAssignment,
|
||||
CourseSessionAttendanceCourse,
|
||||
)
|
||||
|
||||
|
||||
class CourseSessionAssignmentFactory(DjangoModelFactory):
|
||||
class Meta:
|
||||
model = CourseSessionAssignment
|
||||
|
||||
|
||||
class CourseSessionAttendanceCourseFactory(DjangoModelFactory):
|
||||
class Meta:
|
||||
model = CourseSessionAttendanceCourse
|
||||
|
||||
|
||||
class CourseSessionFactory(DjangoModelFactory):
|
||||
class Meta:
|
||||
model = CourseSession
|
||||
|
||||
course = CourseFactory()
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
# Generated by Django 3.2.13 on 2023-06-14 15:01
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("learnpath", "0007_learningunit_title_hidden"),
|
||||
("course", "0005_remove_coursesession_attendance_courses"),
|
||||
("duedate", "0002_auto_20230614_1500"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="CourseSessionAttendanceCourse",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("location", models.CharField(blank=True, default="", max_length=255)),
|
||||
("trainer", models.CharField(blank=True, default="", max_length=255)),
|
||||
(
|
||||
"course_session",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="course.coursesession",
|
||||
),
|
||||
),
|
||||
(
|
||||
"due_date",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="attendance_course_due_date",
|
||||
to="duedate.duedate",
|
||||
),
|
||||
),
|
||||
(
|
||||
"learning_content",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="learnpath.learningcontentattendancecourse",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
# Generated by Django 3.2.13 on 2023-06-21 14:19
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("course", "0006_remove_coursesession_assignment_details_list"),
|
||||
("learnpath", "0007_learningunit_title_hidden"),
|
||||
("duedate", "0003_alter_duedate_course_session"),
|
||||
("course_session", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="CourseSessionAssignment",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"course_session",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="course.coursesession",
|
||||
),
|
||||
),
|
||||
(
|
||||
"evaluation_deadline",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="assignment_evaluation_deadline",
|
||||
to="duedate.duedate",
|
||||
),
|
||||
),
|
||||
(
|
||||
"learning_content",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="learnpath.learningcontentassignment",
|
||||
),
|
||||
),
|
||||
(
|
||||
"submission_deadline",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="assignment_submission_deadline",
|
||||
to="duedate.duedate",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
# Generated by Django 3.2.13 on 2023-06-28 11:21
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("duedate", "0006_auto_20230627_1553"),
|
||||
("course_session", "0002_coursesessionassignment"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="coursesessionassignment",
|
||||
name="evaluation_deadline",
|
||||
field=models.OneToOneField(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="assignment_evaluation_deadline",
|
||||
to="duedate.duedate",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="coursesessionassignment",
|
||||
name="submission_deadline",
|
||||
field=models.OneToOneField(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="assignment_submission_deadline",
|
||||
to="duedate.duedate",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from vbv_lernwelt.assignment.models import AssignmentType
|
||||
from vbv_lernwelt.duedate.models import DueDate
|
||||
|
||||
|
||||
class CourseSessionAttendanceCourse(models.Model):
|
||||
"""
|
||||
Präsenztag Durchührung
|
||||
|
||||
Kann über einen Zeitraum von meheren Tagen gehen.
|
||||
"""
|
||||
|
||||
course_session = models.ForeignKey(
|
||||
"course.CourseSession",
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
learning_content = models.ForeignKey(
|
||||
"learnpath.LearningContentAttendanceCourse",
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
due_date = models.OneToOneField(
|
||||
"duedate.DueDate",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="attendance_course_due_date",
|
||||
)
|
||||
|
||||
location = models.CharField(max_length=255, blank=True, default="")
|
||||
trainer = models.CharField(max_length=255, blank=True, default="")
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.pk:
|
||||
title = ""
|
||||
url = ""
|
||||
page = None
|
||||
if self.learning_content_id:
|
||||
title = self.learning_content.title
|
||||
page = self.learning_content.page_ptr
|
||||
url = self.learning_content.get_frontend_url()
|
||||
|
||||
self.due_date = DueDate.objects.create(
|
||||
url=url,
|
||||
title=f"{title}",
|
||||
learning_content_description=f"{_('Präsenzkurs')}",
|
||||
description="",
|
||||
course_session=self.course_session,
|
||||
page=page,
|
||||
)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.course_session} - {self.learning_content}"
|
||||
|
||||
|
||||
class CourseSessionAssignment(models.Model):
|
||||
"""
|
||||
Auftrag
|
||||
- Geletitete Fallarbeit ist eine speziefische ausprägung eines Auftrags (assignment_type)
|
||||
|
||||
"""
|
||||
|
||||
course_session = models.ForeignKey(
|
||||
"course.CourseSession",
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
learning_content = models.ForeignKey(
|
||||
"learnpath.LearningContentAssignment",
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
submission_deadline = models.OneToOneField(
|
||||
"duedate.DueDate",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="assignment_submission_deadline",
|
||||
null=True,
|
||||
)
|
||||
|
||||
evaluation_deadline = models.OneToOneField(
|
||||
"duedate.DueDate",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="assignment_evaluation_deadline",
|
||||
null=True,
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.pk:
|
||||
title = ""
|
||||
url = ""
|
||||
page = None
|
||||
assignment_type_description = ""
|
||||
if self.learning_content_id:
|
||||
title = self.learning_content.title
|
||||
page = self.learning_content.page_ptr
|
||||
url = self.learning_content.get_frontend_url()
|
||||
assignment_type = self.learning_content.assignment_type
|
||||
assignment_type_descriptions = {
|
||||
AssignmentType.CASEWORK.name: _("Geleitete Fallarbeit"),
|
||||
AssignmentType.PREP_ASSIGNMENT.name: _("Vorbereitungsauftrag"),
|
||||
AssignmentType.REFLECTION.name: _("Reflexion"),
|
||||
}
|
||||
assignment_type_description = assignment_type_descriptions.get(
|
||||
assignment_type, ""
|
||||
)
|
||||
|
||||
if assignment_type in {
|
||||
AssignmentType.CASEWORK.value,
|
||||
AssignmentType.PREP_ASSIGNMENT.value,
|
||||
}: # Reflexion
|
||||
self.submission_deadline = DueDate.objects.create(
|
||||
url=url,
|
||||
title=f"{title}",
|
||||
learning_content_description=assignment_type_description,
|
||||
description=f"{_('Abgabe Termin')}",
|
||||
course_session=self.course_session,
|
||||
page=page,
|
||||
)
|
||||
|
||||
if assignment_type == AssignmentType.CASEWORK.value:
|
||||
self.evaluation_deadline = DueDate.objects.create(
|
||||
url=url,
|
||||
title=f"{title}",
|
||||
learning_content_description=assignment_type_description,
|
||||
description=f"{_('Freigabe Termin Bewertungen')}",
|
||||
course_session=self.course_session,
|
||||
page=page,
|
||||
)
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.course_session} - {self.learning_content}"
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from vbv_lernwelt.course_session.models import (
|
||||
CourseSessionAssignment,
|
||||
CourseSessionAttendanceCourse,
|
||||
)
|
||||
|
||||
|
||||
class CourseSessionAttendanceCourseSerializer(serializers.ModelSerializer):
|
||||
start = serializers.SerializerMethodField()
|
||||
end = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = CourseSessionAttendanceCourse
|
||||
fields = [
|
||||
"id",
|
||||
"course_session_id",
|
||||
"learning_content_id",
|
||||
"due_date_id",
|
||||
"location",
|
||||
"trainer",
|
||||
"start",
|
||||
"end",
|
||||
]
|
||||
|
||||
def get_start(self, obj):
|
||||
return obj.due_date.start
|
||||
|
||||
def get_end(self, obj):
|
||||
return obj.due_date.end
|
||||
|
||||
|
||||
class CourseSessionAssignmentSerializer(serializers.ModelSerializer):
|
||||
submission_deadline_start = serializers.SerializerMethodField()
|
||||
evaluation_deadline_start = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = CourseSessionAssignment
|
||||
fields = [
|
||||
"id",
|
||||
"course_session_id",
|
||||
"learning_content_id",
|
||||
"submission_deadline_id",
|
||||
"submission_deadline_start",
|
||||
"evaluation_deadline_id",
|
||||
"evaluation_deadline_start",
|
||||
]
|
||||
|
||||
def get_evaluation_deadline_start(self, obj):
|
||||
if obj.evaluation_deadline:
|
||||
return obj.evaluation_deadline.start
|
||||
|
||||
def get_submission_deadline_start(self, obj):
|
||||
if obj.submission_deadline:
|
||||
return obj.submission_deadline.start
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
from datetime import datetime
|
||||
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
from vbv_lernwelt.core.create_default_users import create_default_users
|
||||
from vbv_lernwelt.course.creators.test_course import create_test_course
|
||||
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
||||
from vbv_lernwelt.duedate.models import DueDate
|
||||
|
||||
|
||||
class CourseSessionModelsTestCase(TestCase):
|
||||
def setUp(self) -> None:
|
||||
create_default_users()
|
||||
create_test_course(with_sessions=True)
|
||||
|
||||
def test_course_session_attendance_course(self):
|
||||
csac = CourseSessionAttendanceCourse.objects.all().first()
|
||||
|
||||
due_date = csac.due_date
|
||||
|
||||
deadline_date = datetime(
|
||||
2023, 7, 6, 8, 30, tzinfo=timezone.get_current_timezone()
|
||||
)
|
||||
due_date.start = deadline_date
|
||||
due_date.save()
|
||||
|
||||
this_date = DueDate.objects.get(pk=due_date.pk)
|
||||
self.assertEqual(this_date.start, deadline_date)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
from django.contrib import admin
|
||||
from wagtail.models import Page
|
||||
|
||||
from vbv_lernwelt.duedate.models import DueDate
|
||||
from vbv_lernwelt.learnpath.models import (
|
||||
LearningContentAttendanceCourse,
|
||||
LearningContentTest,
|
||||
)
|
||||
|
||||
|
||||
# Register your models here.
|
||||
@admin.register(DueDate)
|
||||
class DueDateAdmin(admin.ModelAdmin):
|
||||
date_hierarchy = "end"
|
||||
list_display = ["title", "course_session", "start", "end", "is_undefined"]
|
||||
list_filter = ["course_session"]
|
||||
readonly_fields = ["course_session", "page"]
|
||||
|
||||
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
||||
if db_field.name == "page":
|
||||
if request.resolver_match.kwargs.get("object_id"):
|
||||
object_id = int(request.resolver_match.kwargs.get("object_id"))
|
||||
csd = DueDate.objects.get(id=object_id)
|
||||
kwargs["queryset"] = Page.objects.descendant_of(
|
||||
csd.course_session.course.coursepage
|
||||
).exact_type(LearningContentAttendanceCourse, LearningContentTest)
|
||||
else:
|
||||
kwargs["queryset"] = Page.objects.none()
|
||||
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class DueDatesConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "vbv_lernwelt.duedate"
|
||||
|
||||
def ready(self):
|
||||
try:
|
||||
# pylint: disable=unused-import,import-outside-toplevel
|
||||
import vbv_lernwelt.course.signals # noqa F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import datetime
|
||||
|
||||
import structlog
|
||||
from django.utils import timezone
|
||||
from factory.django import DjangoModelFactory
|
||||
|
||||
from .models import DueDate
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
def get_date(date_string):
|
||||
return datetime.datetime.strptime(
|
||||
date_string,
|
||||
"%b %d %Y",
|
||||
).astimezone(timezone.get_current_timezone())
|
||||
|
||||
|
||||
class DueDateFactory(DjangoModelFactory):
|
||||
class Meta:
|
||||
model = DueDate
|
||||
|
||||
title = "Prüfung Versicherungsvermittler/-in"
|
||||
end = get_date("Jan 01 2021")
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
# Generated by Django 3.2.13 on 2023-06-14 09:29
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("course", "0004_import_fields"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="DueDate",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("start", models.DateTimeField(db_index=True, null=True)),
|
||||
("end", models.DateTimeField(db_index=True, null=True)),
|
||||
("title", models.CharField(default="Termin", max_length=1024)),
|
||||
("url", models.URLField(blank=True, max_length=1024, null=True)),
|
||||
(
|
||||
"learning_content_id",
|
||||
models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
(
|
||||
"course_session",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="events",
|
||||
to="course.coursesession",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# Generated by Django 3.2.13 on 2023-06-14 13:00
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("wagtailcore", "0083_workflowcontenttype"),
|
||||
("duedate", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="duedate",
|
||||
name="learning_content_id",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="duedate",
|
||||
name="page",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="wagtailcore.page",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# Generated by Django 3.2.13 on 2023-06-21 14:19
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("course", "0006_remove_coursesession_assignment_details_list"),
|
||||
("duedate", "0002_auto_20230614_1500"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="duedate",
|
||||
name="course_session",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="duedates",
|
||||
to="course.coursesession",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# Generated by Django 3.2.13 on 2023-06-21 15:03
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("contenttypes", "0002_remove_content_type_name"),
|
||||
("duedate", "0003_alter_duedate_course_session"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="duedate",
|
||||
name="content_type",
|
||||
field=models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="contenttypes.contenttype",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="duedate",
|
||||
name="object_id",
|
||||
field=models.PositiveIntegerField(null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# Generated by Django 3.2.13 on 2023-06-22 09:38
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("duedate", "0004_auto_20230621_1703"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="duedate",
|
||||
name="content_type",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="duedate",
|
||||
name="object_id",
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 3.2.13 on 2023-06-27 13:53
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("duedate", "0005_auto_20230622_1138"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="duedate",
|
||||
name="description",
|
||||
field=models.CharField(default="Abgabetermin", max_length=1024),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="duedate",
|
||||
name="learning_content_description",
|
||||
field=models.CharField(default="GeleiteteFallarbeit", max_length=1024),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
# Generated by Django 3.2.13 on 2023-07-03 15:41
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("course", "0006_remove_coursesession_assignment_details_list"),
|
||||
("duedate", "0006_auto_20230627_1553"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="duedate",
|
||||
name="course_session",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=1,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="duedates",
|
||||
to="course.coursesession",
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="duedate",
|
||||
name="description",
|
||||
field=models.CharField(default="", max_length=1024),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="duedate",
|
||||
name="learning_content_description",
|
||||
field=models.CharField(default="", max_length=1024),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="duedate",
|
||||
name="url",
|
||||
field=models.URLField(blank=True, default="", max_length=1024),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 3.2.13 on 2023-07-11 09:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("duedate", "0007_auto_20230703_1741"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="duedate",
|
||||
name="start",
|
||||
field=models.DateTimeField(blank=True, db_index=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="duedate",
|
||||
name="url",
|
||||
field=models.CharField(blank=True, default="", max_length=1024),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# Generated by Django 3.2.13 on 2023-07-12 07:05
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("course", "0006_remove_coursesession_assignment_details_list"),
|
||||
("duedate", "0008_auto_20230711_1116"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="duedate",
|
||||
name="course_session",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="course.coursesession",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# Generated by Django 3.2.13 on 2023-07-12 07:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("duedate", "0009_alter_duedate_course_session"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="duedate",
|
||||
name="description",
|
||||
field=models.CharField(blank=True, default="", max_length=1024),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="duedate",
|
||||
name="end",
|
||||
field=models.DateTimeField(blank=True, db_index=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="duedate",
|
||||
name="learning_content_description",
|
||||
field=models.CharField(blank=True, default="", max_length=1024),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="duedate",
|
||||
name="start",
|
||||
field=models.DateTimeField(db_index=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="duedate",
|
||||
name="title",
|
||||
field=models.CharField(default="", max_length=1024),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
import datetime
|
||||
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from wagtail.models import Page
|
||||
|
||||
from vbv_lernwelt.core.models import User
|
||||
from vbv_lernwelt.course.models import CourseSession
|
||||
|
||||
|
||||
class DueDate(models.Model):
|
||||
start = models.DateTimeField(null=True, db_index=True)
|
||||
end = models.DateTimeField(null=True, blank=True, db_index=True)
|
||||
title = models.CharField(default="", max_length=1024)
|
||||
learning_content_description = models.CharField(
|
||||
default="", blank=True, max_length=1024
|
||||
)
|
||||
description = models.CharField(default="", blank=True, max_length=1024)
|
||||
url = models.CharField(default="", blank=True, max_length=1024)
|
||||
course_session = models.ForeignKey(
|
||||
"course.CourseSession",
|
||||
on_delete=models.CASCADE,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
page = models.ForeignKey(Page, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
|
||||
def Meta(self):
|
||||
ordering = ["start", "end"]
|
||||
verbose_name = _("Termin")
|
||||
help = "The start date is mandatory. You can set the end date if you want to have a deadline with a duration."
|
||||
|
||||
def __str__(self):
|
||||
if self.is_undefined:
|
||||
return f"DueDate: {self.title} undefined"
|
||||
start_str = self.start.strftime("%Y-%m-%d %H:%M") if self.start else "-"
|
||||
result = f"DueDate: {self.title} {start_str}"
|
||||
|
||||
if self.end:
|
||||
end_str = self.end.strftime("%Y-%m-%d %H:%M") if self.end else "-"
|
||||
result += f" - {end_str}"
|
||||
|
||||
return result
|
||||
|
||||
@property
|
||||
def is_undefined(self):
|
||||
return self.start is None
|
||||
|
||||
@property
|
||||
def duration(self):
|
||||
if self.end is None:
|
||||
return datetime.timedelta(0)
|
||||
return self.end - self.start
|
||||
|
||||
@classmethod
|
||||
def get_users_next_events_qs(
|
||||
cls, user: User, course_session: CourseSession = None, limit=10
|
||||
):
|
||||
"""
|
||||
Returns a queryset of all due dates that are relevant for the given user.
|
||||
If course_session is given, only due dates for that course_session are returned.
|
||||
The user is determined by via a course session user of a course_assignment.
|
||||
|
||||
"""
|
||||
qs = cls.get_next_due_dates_qs()
|
||||
|
||||
if course_session:
|
||||
qs = qs.filter(
|
||||
course_session=course_session,
|
||||
course_session__course_assignment__user=user,
|
||||
)
|
||||
else:
|
||||
qs = qs.filter(course_session__course_assignment__user=user)
|
||||
|
||||
qs = qs.order_by("start")[:limit]
|
||||
|
||||
return qs
|
||||
|
||||
@classmethod
|
||||
def get_next_due_dates_qs(cls):
|
||||
now = timezone.now()
|
||||
qs = cls.objects.filter(start__gte=now)
|
||||
return qs
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from vbv_lernwelt.duedate.models import DueDate
|
||||
|
||||
|
||||
class DueDateSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = DueDate
|
||||
fields = "__all__"
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# class TesDueDatetModel(TestCase):
|
||||
#
|
||||
# def test_get_next_duedate_qs_is_really_next(self):
|
||||
# pass
|
||||
# start = timezone.now() - datetime.timedelta(days=18)
|
||||
# generate_duedates(start=start)
|
||||
# self.assertEqual(DueDate.objects.count(), 20)
|
||||
# self.assertEqual(DueDate.get_next_duedates_qs().count(), 2)
|
||||
|
||||
# def test_event_model_factory_validation(self):
|
||||
# e = DueDateFactory()
|
||||
# e.start = get_date("Jan 01 2021")
|
||||
# e.end = get_date("Jan 02 2021")
|
||||
# e.validate()
|
||||
# self.assertTrue(True)
|
||||
#
|
||||
# def test_event_model_factory_validation_invalid(self):
|
||||
# e = DueDateFactory()
|
||||
# e.start = get_date("Jan 04 2021")
|
||||
# e.end = get_date("Jan 02 2021")
|
||||
# self.assertRaises(ValueError, e.validate)
|
||||
#
|
||||
#
|
||||
# def generate_duedates(start=timezone.now()):
|
||||
# for i in range(20):
|
||||
# DueDateFactory(
|
||||
# title=f"{i}",
|
||||
# start=start + datetime.timedelta(days=i),
|
||||
# end=start + datetime.timedelta(days=i, hours=1),
|
||||
# )
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# class TestDueDateSerializer(TestCase):
|
||||
# def test_duedate_serializer(self):
|
||||
# pass
|
||||
# create_test_course()
|
||||
# self.assertEqual(DueDate.objects.count(), 1)
|
||||
#
|
||||
# duedates = DueDate.objects.all()
|
||||
# result = DueDateSerializer(duedates, many=True).data
|
||||
# self.assertEqual(result[0]["title"], "Prüfung Versicherungsvermittler/-in")
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
|
|
@ -5,6 +5,7 @@ from openpyxl.reader.excel import load_workbook
|
|||
|
||||
from vbv_lernwelt.core.models import User
|
||||
from vbv_lernwelt.course.models import Course, CourseSession, CourseSessionUser
|
||||
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
||||
from vbv_lernwelt.importer.utils import (
|
||||
calc_header_tuple_list_from_pyxl_sheet,
|
||||
parse_circle_group_string,
|
||||
|
|
@ -109,42 +110,37 @@ def create_or_update_course_session(
|
|||
|
||||
cs.save()
|
||||
for circle in circles:
|
||||
attendance_course_lp_qs = None
|
||||
if language == "de":
|
||||
attendance_course_lp_qs = LearningContentAttendanceCourse.objects.filter(
|
||||
slug=f"{course.slug}-lp-circle-{circle.lower()}-lc-präsenzkurs-{circle.lower()}"
|
||||
)
|
||||
add_attendance_course_date(cs, attendance_course_lp_qs, circle, data)
|
||||
|
||||
elif language == "fr":
|
||||
# todo: this is a hack remove me
|
||||
attendance_course_lp_qs = LearningContentAttendanceCourse.objects.filter(
|
||||
slug=f"{course.slug}-lp-circle-véhicule-lc-cours-de-présence-véhicule-à-moteur"
|
||||
)
|
||||
add_attendance_course_date(cs, attendance_course_lp_qs, circle, data)
|
||||
elif language == "it":
|
||||
# todo: this is a hack remove me
|
||||
attendance_course_lp_qs = LearningContentAttendanceCourse.objects.filter(
|
||||
slug=f"{course.slug}-lp-circle-veicolo-lc-corso-di-presenza-veicolo"
|
||||
)
|
||||
print(attendance_course_lp_qs)
|
||||
add_attendance_course_date(cs, attendance_course_lp_qs, circle, data)
|
||||
|
||||
if attendance_course_lp_qs and attendance_course_lp_qs.exists():
|
||||
csa = CourseSessionAttendanceCourse.objects.create(
|
||||
course_session=cs,
|
||||
learning_content=attendance_course_lp_qs.first(),
|
||||
location=data[f"{circle} Raum"],
|
||||
trainer="",
|
||||
)
|
||||
csa.due_date.start = try_parse_datetime(data[f"{circle} Start"])[1]
|
||||
csa.due_date.end = try_parse_datetime(data[f"{circle} Ende"])[1]
|
||||
csa.due_date.save()
|
||||
|
||||
return cs
|
||||
|
||||
|
||||
def add_attendance_course_date(course_session, attendance_course_lp_qs, circle, data):
|
||||
if attendance_course_lp_qs.exists():
|
||||
course_session.attendance_courses.append(
|
||||
{
|
||||
"learningContentId": attendance_course_lp_qs.first().id,
|
||||
"start": try_parse_datetime(data[f"{circle} Start"])[1].isoformat(),
|
||||
"end": try_parse_datetime(data[f"{circle} Ende"])[1].isoformat(),
|
||||
"location": data[f"{circle} Raum"],
|
||||
"trainer": "",
|
||||
}
|
||||
)
|
||||
course_session.save()
|
||||
|
||||
|
||||
def import_trainers_from_excel(course: Course, filename: str, language="de"):
|
||||
workbook = load_workbook(filename=filename)
|
||||
sheet = workbook["Schulungen Trainer"]
|
||||
|
|
@ -157,6 +153,7 @@ def import_trainers_from_excel(course: Course, filename: str, language="de"):
|
|||
def create_or_update_trainer(course: Course, data: Dict[str, Any], language="de"):
|
||||
logger.debug(
|
||||
"create_or_update_trainer",
|
||||
course=course.title,
|
||||
data=data,
|
||||
label="import",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from openpyxl.reader.excel import load_workbook
|
|||
|
||||
from vbv_lernwelt.course.creators.test_course import create_test_course
|
||||
from vbv_lernwelt.course.models import CourseSession
|
||||
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
||||
from vbv_lernwelt.importer.services import create_or_update_course_session
|
||||
from vbv_lernwelt.importer.utils import calc_header_tuple_list_from_pyxl_sheet
|
||||
|
||||
|
|
@ -61,20 +62,12 @@ class CreateOrUpdateCourseSessionTestCase(TestCase):
|
|||
self.assertEqual(cs.region, "Deutschschweiz")
|
||||
self.assertEqual(cs.group, "A")
|
||||
|
||||
attendance_course = cs.attendance_courses[0]
|
||||
attendance_course = {
|
||||
k: v
|
||||
for k, v in attendance_course.items()
|
||||
if k not in ["learningContentId", "location"]
|
||||
}
|
||||
|
||||
self.assertDictEqual(
|
||||
attendance_course,
|
||||
{
|
||||
"start": "2023-06-06T13:30:00",
|
||||
"end": "2023-06-06T15:00:00",
|
||||
"trainer": "",
|
||||
},
|
||||
attendance_course = CourseSessionAttendanceCourse.objects.first()
|
||||
self.assertEqual(
|
||||
attendance_course.due_date.start.isoformat(), "2023-06-06T11:30:00+00:00"
|
||||
)
|
||||
self.assertEqual(
|
||||
attendance_course.due_date.end.isoformat(), "2023-06-06T13:00:00+00:00"
|
||||
)
|
||||
|
||||
def test_update_course_session(self):
|
||||
|
|
@ -112,18 +105,10 @@ class CreateOrUpdateCourseSessionTestCase(TestCase):
|
|||
self.assertEqual(cs.region, "Deutschschweiz")
|
||||
self.assertEqual(cs.group, "A")
|
||||
|
||||
attendance_course = cs.attendance_courses[0]
|
||||
attendance_course = {
|
||||
k: v
|
||||
for k, v in attendance_course.items()
|
||||
if k not in ["learningContentId", "location"]
|
||||
}
|
||||
|
||||
self.assertDictEqual(
|
||||
attendance_course,
|
||||
{
|
||||
"start": "2023-06-06T13:30:00",
|
||||
"end": "2023-06-06T15:00:00",
|
||||
"trainer": "",
|
||||
},
|
||||
attendance_course = CourseSessionAttendanceCourse.objects.first()
|
||||
self.assertEqual(
|
||||
attendance_course.due_date.start.isoformat(), "2023-06-06T11:30:00+00:00"
|
||||
)
|
||||
self.assertEqual(
|
||||
attendance_course.due_date.end.isoformat(), "2023-06-06T13:00:00+00:00"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
# Generated by Django 3.2.13 on 2023-06-28 11:21
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("assignment", "0004_assignment_assignment_type"),
|
||||
("learnpath", "0007_learningunit_title_hidden"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="learningcontentassignment",
|
||||
name="content_assignment",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT, to="assignment.assignment"
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
@ -279,6 +279,10 @@ class LearningContent(CourseBasePage):
|
|||
|
||||
|
||||
class LearningContentAttendanceCourse(LearningContent):
|
||||
"""
|
||||
Präsenzkurs
|
||||
"""
|
||||
|
||||
parent_page_types = ["learnpath.Circle"]
|
||||
subpage_types = []
|
||||
|
||||
|
|
@ -346,7 +350,6 @@ class LearningContentAssignment(LearningContent):
|
|||
content_assignment = models.ForeignKey(
|
||||
"assignment.Assignment",
|
||||
on_delete=models.PROTECT,
|
||||
related_name="+",
|
||||
)
|
||||
|
||||
assignment_type = models.CharField(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,162 @@
|
|||
# Generated by Django 3.2.13 on 2023-07-12 07:05
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import jsonfield.fields
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("contenttypes", "0002_remove_content_type_name"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("notify", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="notification",
|
||||
options={
|
||||
"ordering": ["-timestamp"],
|
||||
"verbose_name": "Notification",
|
||||
"verbose_name_plural": "Notifications",
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="action_object_content_type",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="notify_action_object",
|
||||
to="contenttypes.contenttype",
|
||||
verbose_name="action object content type",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="action_object_object_id",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
max_length=255,
|
||||
null=True,
|
||||
verbose_name="action object object id",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="actor_content_type",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="notify_actor",
|
||||
to="contenttypes.contenttype",
|
||||
verbose_name="actor content type",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="actor_object_id",
|
||||
field=models.CharField(max_length=255, verbose_name="actor object id"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="data",
|
||||
field=jsonfield.fields.JSONField(
|
||||
blank=True, null=True, verbose_name="data"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="deleted",
|
||||
field=models.BooleanField(
|
||||
db_index=True, default=False, verbose_name="deleted"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="description",
|
||||
field=models.TextField(blank=True, null=True, verbose_name="description"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="emailed",
|
||||
field=models.BooleanField(
|
||||
db_index=True, default=False, verbose_name="emailed"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="level",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("success", "success"),
|
||||
("info", "info"),
|
||||
("warning", "warning"),
|
||||
("error", "error"),
|
||||
],
|
||||
default="info",
|
||||
max_length=20,
|
||||
verbose_name="level",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="public",
|
||||
field=models.BooleanField(
|
||||
db_index=True, default=True, verbose_name="public"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="recipient",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="notifications",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="recipient",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="target_content_type",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="notify_target",
|
||||
to="contenttypes.contenttype",
|
||||
verbose_name="target content type",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="target_object_id",
|
||||
field=models.CharField(
|
||||
blank=True, max_length=255, null=True, verbose_name="target object id"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="timestamp",
|
||||
field=models.DateTimeField(
|
||||
db_index=True,
|
||||
default=django.utils.timezone.now,
|
||||
verbose_name="timestamp",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="unread",
|
||||
field=models.BooleanField(
|
||||
db_index=True, default=True, verbose_name="unread"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="verb",
|
||||
field=models.CharField(max_length=255, verbose_name="verb"),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Outline" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
|
||||
<path
|
||||
d="m23.71,4.86h-2.86v-1.53c0-.41-.34-.75-.75-.75s-.75.34-.75.75v1.53h-8.71v-1.53c0-.41-.34-.75-.75-.75s-.75.34-.75.75v1.53h-2.86c-1.24,0-2.25,1.01-2.25,2.25v15.77c0,1.24,1.01,2.25,2.25,2.25h17.43c1.24,0,2.25-1.01,2.25-2.25V7.11c0-1.24-1.01-2.25-2.25-2.25Zm-17.43,1.5h17.43c.41,0,.75.34.75.75v2.21H5.54v-2.21c0-.41.34-.75.75-.75Zm17.43,17.27H6.29c-.41,0-.75-.34-.75-.75v-12.06h18.93v12.06c0,.41-.34.75-.75.75Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 550 B |
Loading…
Reference in New Issue