Merged in feature/VBV-453-graphql-learningpath-server (pull request #161)

Feature/VBV-453 graphql learningpath server

Approved-by: Elia Bieri
This commit is contained in:
Daniel Egger 2023-07-21 06:40:25 +00:00
commit 0798efc7f2
19 changed files with 1053 additions and 150 deletions

View File

@ -258,9 +258,6 @@ There are some rules when it comes to the folder structure of the frontend.
## GraphQL ## GraphQL
When you change something on the server side run the following command to update the
graphql schema:
```bash ```bash
python manage.py graphql_schema python manage.py graphql_schema
``` ```
@ -270,13 +267,25 @@ generated code
```bash ```bash
npm run codegen npm run codegen
# `npm run dev` includes `npm run codegen` as step...
npm run dev
``` ```
💡 If you run `npm run dev`, the codegen command will be run automatically in watch mode." If you run `npm run dev`, the codegen command will be run automatically in watch mode.
For the `ObjectTypes` on the server, please use the postfix `ObjectType` for types,
like `LearningContentAttendanceCourseObjectType`.
This will prevent problems with the hand written types on the client side,
when `npm run codegen` will create the types automatically.
When you change something on the server side run the following command to update the
graphql schema.
### Open Questions ### Open Questions
- The `id` field has to be a string? - The `id` field has to be a string?
- Is running `codegen` a prerequisite so that it even works?
- What about the generated types from `codegen`? Hand written types seem to be better. - What about the generated types from `codegen`? Hand written types seem to be better.
- The functions is `cacheExchange` should be nearer the concrete implementation - The functions in `cacheExchange` should be nearer the concrete implementation...

View File

@ -138,6 +138,20 @@ export type AttendanceUserType = {
user_id: Scalars['UUID']['output']; user_id: Scalars['UUID']['output'];
}; };
export type CircleObjectType = CoursePageInterface & {
__typename?: 'CircleObjectType';
content_type?: Maybe<Scalars['String']['output']>;
description: Scalars['String']['output'];
frontend_url?: Maybe<Scalars['String']['output']>;
goals: Scalars['String']['output'];
id?: Maybe<Scalars['ID']['output']>;
learning_sequences?: Maybe<Array<Maybe<LearningSequenceObjectType>>>;
live?: Maybe<Scalars['Boolean']['output']>;
slug?: Maybe<Scalars['String']['output']>;
title?: Maybe<Scalars['String']['output']>;
translation_key?: Maybe<Scalars['String']['output']>;
};
/** An enumeration. */ /** An enumeration. */
export type CoreUserLanguageChoices = export type CoreUserLanguageChoices =
/** Deutsch */ /** Deutsch */
@ -147,6 +161,15 @@ export type CoreUserLanguageChoices =
/** Italiano */ /** Italiano */
| 'IT'; | 'IT';
export type CourseObjectType = {
__typename?: 'CourseObjectType';
category_name: Scalars['String']['output'];
id: Scalars['ID']['output'];
learning_path?: Maybe<LearningPathObjectType>;
slug: Scalars['String']['output'];
title: Scalars['String']['output'];
};
export type CoursePageInterface = { export type CoursePageInterface = {
content_type?: Maybe<Scalars['String']['output']>; content_type?: Maybe<Scalars['String']['output']>;
frontend_url?: Maybe<Scalars['String']['output']>; frontend_url?: Maybe<Scalars['String']['output']>;
@ -170,15 +193,6 @@ export type CourseSessionAttendanceCourseType = {
trainer: Scalars['String']['output']; trainer: Scalars['String']['output'];
}; };
export type CourseType = {
__typename?: 'CourseType';
category_name: Scalars['String']['output'];
id: Scalars['ID']['output'];
learning_path?: Maybe<LearningPathType>;
slug: Scalars['String']['output'];
title: Scalars['String']['output'];
};
export type ErrorType = { export type ErrorType = {
__typename?: 'ErrorType'; __typename?: 'ErrorType';
field: Scalars['String']['output']; field: Scalars['String']['output'];
@ -187,14 +201,170 @@ export type ErrorType = {
export type FeedbackResponse = Node & { export type FeedbackResponse = Node & {
__typename?: 'FeedbackResponse'; __typename?: 'FeedbackResponse';
circle: CircleObjectType;
created_at: Scalars['DateTime']['output']; created_at: Scalars['DateTime']['output'];
data?: Maybe<Scalars['GenericScalar']['output']>; data?: Maybe<Scalars['GenericScalar']['output']>;
/** The ID of the object */ /** The ID of the object */
id: Scalars['ID']['output']; id: Scalars['ID']['output'];
}; };
export type LearningPathType = CoursePageInterface & { export type LearningContentAssignmentObjectType = LearningContentInterface & {
__typename?: 'LearningPathType'; __typename?: 'LearningContentAssignmentObjectType';
assignment_type: LearnpathLearningContentAssignmentAssignmentTypeChoices;
content?: Maybe<Scalars['String']['output']>;
content_assignment: AssignmentObjectType;
content_type?: Maybe<Scalars['String']['output']>;
description?: Maybe<Scalars['String']['output']>;
frontend_url?: Maybe<Scalars['String']['output']>;
id?: Maybe<Scalars['ID']['output']>;
live?: Maybe<Scalars['Boolean']['output']>;
minutes?: Maybe<Scalars['Int']['output']>;
slug?: Maybe<Scalars['String']['output']>;
title?: Maybe<Scalars['String']['output']>;
translation_key?: Maybe<Scalars['String']['output']>;
};
export type LearningContentAttendanceCourseObjectType = LearningContentInterface & {
__typename?: 'LearningContentAttendanceCourseObjectType';
content?: Maybe<Scalars['String']['output']>;
content_type?: Maybe<Scalars['String']['output']>;
description?: Maybe<Scalars['String']['output']>;
frontend_url?: Maybe<Scalars['String']['output']>;
id?: Maybe<Scalars['ID']['output']>;
live?: Maybe<Scalars['Boolean']['output']>;
minutes?: Maybe<Scalars['Int']['output']>;
slug?: Maybe<Scalars['String']['output']>;
title?: Maybe<Scalars['String']['output']>;
translation_key?: Maybe<Scalars['String']['output']>;
};
export type LearningContentDocumentListObjectType = LearningContentInterface & {
__typename?: 'LearningContentDocumentListObjectType';
content?: Maybe<Scalars['String']['output']>;
content_type?: Maybe<Scalars['String']['output']>;
description?: Maybe<Scalars['String']['output']>;
frontend_url?: Maybe<Scalars['String']['output']>;
id?: Maybe<Scalars['ID']['output']>;
live?: Maybe<Scalars['Boolean']['output']>;
minutes?: Maybe<Scalars['Int']['output']>;
slug?: Maybe<Scalars['String']['output']>;
title?: Maybe<Scalars['String']['output']>;
translation_key?: Maybe<Scalars['String']['output']>;
};
export type LearningContentFeedbackObjectType = LearningContentInterface & {
__typename?: 'LearningContentFeedbackObjectType';
content?: Maybe<Scalars['String']['output']>;
content_type?: Maybe<Scalars['String']['output']>;
description?: Maybe<Scalars['String']['output']>;
frontend_url?: Maybe<Scalars['String']['output']>;
id?: Maybe<Scalars['ID']['output']>;
live?: Maybe<Scalars['Boolean']['output']>;
minutes?: Maybe<Scalars['Int']['output']>;
slug?: Maybe<Scalars['String']['output']>;
title?: Maybe<Scalars['String']['output']>;
translation_key?: Maybe<Scalars['String']['output']>;
};
export type LearningContentInterface = {
content?: Maybe<Scalars['String']['output']>;
content_type?: Maybe<Scalars['String']['output']>;
description?: Maybe<Scalars['String']['output']>;
frontend_url?: Maybe<Scalars['String']['output']>;
id?: Maybe<Scalars['ID']['output']>;
live?: Maybe<Scalars['Boolean']['output']>;
minutes?: Maybe<Scalars['Int']['output']>;
slug?: Maybe<Scalars['String']['output']>;
title?: Maybe<Scalars['String']['output']>;
translation_key?: Maybe<Scalars['String']['output']>;
};
export type LearningContentLearningModuleObjectType = LearningContentInterface & {
__typename?: 'LearningContentLearningModuleObjectType';
content?: Maybe<Scalars['String']['output']>;
content_type?: Maybe<Scalars['String']['output']>;
description?: Maybe<Scalars['String']['output']>;
frontend_url?: Maybe<Scalars['String']['output']>;
id?: Maybe<Scalars['ID']['output']>;
live?: Maybe<Scalars['Boolean']['output']>;
minutes?: Maybe<Scalars['Int']['output']>;
slug?: Maybe<Scalars['String']['output']>;
title?: Maybe<Scalars['String']['output']>;
translation_key?: Maybe<Scalars['String']['output']>;
};
export type LearningContentMediaLibraryObjectType = LearningContentInterface & {
__typename?: 'LearningContentMediaLibraryObjectType';
content?: Maybe<Scalars['String']['output']>;
content_type?: Maybe<Scalars['String']['output']>;
description?: Maybe<Scalars['String']['output']>;
frontend_url?: Maybe<Scalars['String']['output']>;
id?: Maybe<Scalars['ID']['output']>;
live?: Maybe<Scalars['Boolean']['output']>;
minutes?: Maybe<Scalars['Int']['output']>;
slug?: Maybe<Scalars['String']['output']>;
title?: Maybe<Scalars['String']['output']>;
translation_key?: Maybe<Scalars['String']['output']>;
};
export type LearningContentPlaceholderObjectType = LearningContentInterface & {
__typename?: 'LearningContentPlaceholderObjectType';
content?: Maybe<Scalars['String']['output']>;
content_type?: Maybe<Scalars['String']['output']>;
description?: Maybe<Scalars['String']['output']>;
frontend_url?: Maybe<Scalars['String']['output']>;
id?: Maybe<Scalars['ID']['output']>;
live?: Maybe<Scalars['Boolean']['output']>;
minutes?: Maybe<Scalars['Int']['output']>;
slug?: Maybe<Scalars['String']['output']>;
title?: Maybe<Scalars['String']['output']>;
translation_key?: Maybe<Scalars['String']['output']>;
};
export type LearningContentRichTextObjectType = LearningContentInterface & {
__typename?: 'LearningContentRichTextObjectType';
content?: Maybe<Scalars['String']['output']>;
content_type?: Maybe<Scalars['String']['output']>;
description?: Maybe<Scalars['String']['output']>;
frontend_url?: Maybe<Scalars['String']['output']>;
id?: Maybe<Scalars['ID']['output']>;
live?: Maybe<Scalars['Boolean']['output']>;
minutes?: Maybe<Scalars['Int']['output']>;
slug?: Maybe<Scalars['String']['output']>;
title?: Maybe<Scalars['String']['output']>;
translation_key?: Maybe<Scalars['String']['output']>;
};
export type LearningContentTestObjectType = LearningContentInterface & {
__typename?: 'LearningContentTestObjectType';
content?: Maybe<Scalars['String']['output']>;
content_type?: Maybe<Scalars['String']['output']>;
description?: Maybe<Scalars['String']['output']>;
frontend_url?: Maybe<Scalars['String']['output']>;
id?: Maybe<Scalars['ID']['output']>;
live?: Maybe<Scalars['Boolean']['output']>;
minutes?: Maybe<Scalars['Int']['output']>;
slug?: Maybe<Scalars['String']['output']>;
title?: Maybe<Scalars['String']['output']>;
translation_key?: Maybe<Scalars['String']['output']>;
};
export type LearningContentVideoObjectType = LearningContentInterface & {
__typename?: 'LearningContentVideoObjectType';
content?: Maybe<Scalars['String']['output']>;
content_type?: Maybe<Scalars['String']['output']>;
description?: Maybe<Scalars['String']['output']>;
frontend_url?: Maybe<Scalars['String']['output']>;
id?: Maybe<Scalars['ID']['output']>;
live?: Maybe<Scalars['Boolean']['output']>;
minutes?: Maybe<Scalars['Int']['output']>;
slug?: Maybe<Scalars['String']['output']>;
title?: Maybe<Scalars['String']['output']>;
translation_key?: Maybe<Scalars['String']['output']>;
};
export type LearningPathObjectType = CoursePageInterface & {
__typename?: 'LearningPathObjectType';
content_type?: Maybe<Scalars['String']['output']>; content_type?: Maybe<Scalars['String']['output']>;
depth: Scalars['Int']['output']; depth: Scalars['Int']['output'];
draft_title: Scalars['String']['output']; draft_title: Scalars['String']['output'];
@ -222,10 +392,45 @@ export type LearningPathType = CoursePageInterface & {
show_in_menus: Scalars['Boolean']['output']; show_in_menus: Scalars['Boolean']['output'];
slug?: Maybe<Scalars['String']['output']>; slug?: Maybe<Scalars['String']['output']>;
title?: Maybe<Scalars['String']['output']>; title?: Maybe<Scalars['String']['output']>;
topics?: Maybe<Array<Maybe<TopicObjectType>>>;
translation_key?: Maybe<Scalars['String']['output']>; translation_key?: Maybe<Scalars['String']['output']>;
url_path: Scalars['String']['output']; url_path: Scalars['String']['output'];
}; };
export type LearningSequenceObjectType = CoursePageInterface & {
__typename?: 'LearningSequenceObjectType';
content_type?: Maybe<Scalars['String']['output']>;
frontend_url?: Maybe<Scalars['String']['output']>;
icon: Scalars['String']['output'];
id?: Maybe<Scalars['ID']['output']>;
learning_units?: Maybe<Array<Maybe<LearningUnitObjectType>>>;
live?: Maybe<Scalars['Boolean']['output']>;
slug?: Maybe<Scalars['String']['output']>;
title?: Maybe<Scalars['String']['output']>;
translation_key?: Maybe<Scalars['String']['output']>;
};
export type LearningUnitObjectType = CoursePageInterface & {
__typename?: 'LearningUnitObjectType';
content_type?: Maybe<Scalars['String']['output']>;
frontend_url?: Maybe<Scalars['String']['output']>;
id?: Maybe<Scalars['ID']['output']>;
learning_contents?: Maybe<Array<Maybe<LearningContentInterface>>>;
live?: Maybe<Scalars['Boolean']['output']>;
slug?: Maybe<Scalars['String']['output']>;
title?: Maybe<Scalars['String']['output']>;
translation_key?: Maybe<Scalars['String']['output']>;
};
/** An enumeration. */
export type LearnpathLearningContentAssignmentAssignmentTypeChoices =
/** CASEWORK */
| 'CASEWORK'
/** PREP_ASSIGNMENT */
| 'PREP_ASSIGNMENT'
/** REFLECTION */
| 'REFLECTION';
export type Mutation = { export type Mutation = {
__typename?: 'Mutation'; __typename?: 'Mutation';
send_feedback?: Maybe<SendFeedbackPayload>; send_feedback?: Maybe<SendFeedbackPayload>;
@ -266,8 +471,20 @@ export type Query = {
__typename?: 'Query'; __typename?: 'Query';
assignment?: Maybe<AssignmentObjectType>; assignment?: Maybe<AssignmentObjectType>;
assignment_completion?: Maybe<AssignmentCompletionObjectType>; assignment_completion?: Maybe<AssignmentCompletionObjectType>;
course?: Maybe<CourseType>; circle?: Maybe<CircleObjectType>;
course?: Maybe<CourseObjectType>;
course_session_attendance_course?: Maybe<CourseSessionAttendanceCourseType>; course_session_attendance_course?: Maybe<CourseSessionAttendanceCourseType>;
learning_content_assignment?: Maybe<LearningContentAssignmentObjectType>;
learning_content_attendance_course?: Maybe<LearningContentAttendanceCourseObjectType>;
learning_content_document_list?: Maybe<LearningContentDocumentListObjectType>;
learning_content_feedback?: Maybe<LearningContentFeedbackObjectType>;
learning_content_learning_module?: Maybe<LearningContentLearningModuleObjectType>;
learning_content_media_library?: Maybe<LearningContentMediaLibraryObjectType>;
learning_content_placeholder?: Maybe<LearningContentPlaceholderObjectType>;
learning_content_rich_text?: Maybe<LearningContentRichTextObjectType>;
learning_content_test?: Maybe<LearningContentTestObjectType>;
learning_content_video?: Maybe<LearningContentVideoObjectType>;
learning_path?: Maybe<LearningPathObjectType>;
}; };
@ -285,6 +502,12 @@ export type QueryAssignmentCompletionArgs = {
}; };
export type QueryCircleArgs = {
id?: InputMaybe<Scalars['Int']['input']>;
slug?: InputMaybe<Scalars['String']['input']>;
};
export type QueryCourseArgs = { export type QueryCourseArgs = {
id?: InputMaybe<Scalars['Int']['input']>; id?: InputMaybe<Scalars['Int']['input']>;
}; };
@ -295,6 +518,12 @@ export type QueryCourseSessionAttendanceCourseArgs = {
id: Scalars['ID']['input']; id: Scalars['ID']['input'];
}; };
export type QueryLearningPathArgs = {
id?: InputMaybe<Scalars['Int']['input']>;
slug?: InputMaybe<Scalars['String']['input']>;
};
export type SendFeedbackInput = { export type SendFeedbackInput = {
clientMutationId?: InputMaybe<Scalars['String']['input']>; clientMutationId?: InputMaybe<Scalars['String']['input']>;
course_session: Scalars['Int']['input']; course_session: Scalars['Int']['input'];
@ -310,6 +539,19 @@ export type SendFeedbackPayload = {
feedback_response?: Maybe<FeedbackResponse>; feedback_response?: Maybe<FeedbackResponse>;
}; };
export type TopicObjectType = CoursePageInterface & {
__typename?: 'TopicObjectType';
circles?: Maybe<Array<Maybe<CircleObjectType>>>;
content_type?: Maybe<Scalars['String']['output']>;
frontend_url?: Maybe<Scalars['String']['output']>;
id?: Maybe<Scalars['ID']['output']>;
is_visible: Scalars['Boolean']['output'];
live?: Maybe<Scalars['Boolean']['output']>;
slug?: Maybe<Scalars['String']['output']>;
title?: Maybe<Scalars['String']['output']>;
translation_key?: Maybe<Scalars['String']['output']>;
};
export type UserType = { export type UserType = {
__typename?: 'UserType'; __typename?: 'UserType';
avatar_url: Scalars['String']['output']; avatar_url: Scalars['String']['output'];
@ -373,7 +615,7 @@ export type CourseQueryQueryVariables = Exact<{
}>; }>;
export type CourseQueryQuery = { __typename?: 'Query', course?: { __typename?: 'CourseType', id: string, slug: string, title: string, category_name: string, learning_path?: { __typename?: 'LearningPathType', id?: string | null } | null } | null }; export type CourseQueryQuery = { __typename?: 'Query', course?: { __typename?: 'CourseObjectType', id: string, slug: string, title: string, category_name: string, learning_path?: { __typename?: 'LearningPathObjectType', id?: string | null } | null } | null };
export const SendFeedbackMutationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SendFeedbackMutation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SendFeedbackInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"send_feedback"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"feedback_response"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"field"}},{"kind":"Field","name":{"kind":"Name","value":"messages"}}]}}]}}]}}]} as unknown as DocumentNode<SendFeedbackMutationMutation, SendFeedbackMutationMutationVariables>; export const SendFeedbackMutationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SendFeedbackMutation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SendFeedbackInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"send_feedback"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"feedback_response"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"field"}},{"kind":"Field","name":{"kind":"Name","value":"messages"}}]}}]}}]}}]} as unknown as DocumentNode<SendFeedbackMutationMutation, SendFeedbackMutationMutationVariables>;

View File

@ -1,58 +1,82 @@
type Query { type Query {
circle(id: Int, slug: String): CircleObjectType
learning_path(id: Int, slug: String): LearningPathObjectType
learning_content_media_library: LearningContentMediaLibraryObjectType
learning_content_assignment: LearningContentAssignmentObjectType
learning_content_attendance_course: LearningContentAttendanceCourseObjectType
learning_content_feedback: LearningContentFeedbackObjectType
learning_content_learning_module: LearningContentLearningModuleObjectType
learning_content_placeholder: LearningContentPlaceholderObjectType
learning_content_rich_text: LearningContentRichTextObjectType
learning_content_test: LearningContentTestObjectType
learning_content_video: LearningContentVideoObjectType
learning_content_document_list: LearningContentDocumentListObjectType
course_session_attendance_course(id: ID!, assignment_user_id: ID): CourseSessionAttendanceCourseType course_session_attendance_course(id: ID!, assignment_user_id: ID): CourseSessionAttendanceCourseType
course(id: Int): CourseType course(id: Int): CourseObjectType
assignment(id: ID, slug: String): AssignmentObjectType assignment(id: ID, slug: String): AssignmentObjectType
assignment_completion(assignment_id: ID!, course_session_id: ID!, learning_content_page_id: ID, assignment_user_id: UUID): AssignmentCompletionObjectType assignment_completion(assignment_id: ID!, course_session_id: ID!, learning_content_page_id: ID, assignment_user_id: UUID): AssignmentCompletionObjectType
} }
type CourseSessionAttendanceCourseType { type CircleObjectType implements CoursePageInterface {
id: ID! description: String!
location: String! goals: String!
trainer: String! id: ID
course_session_id: ID title: String
learning_content_id: ID slug: String
due_date_id: ID content_type: String
end: DateTime live: Boolean
start: DateTime translation_key: String
attendance_user_list: [AttendanceUserType] frontend_url: String
learning_sequences: [LearningSequenceObjectType]
} }
""" interface CoursePageInterface {
The `DateTime` scalar type represents a DateTime id: ID
value as specified by title: String
[iso8601](https://en.wikipedia.org/wiki/ISO_8601). slug: String
""" content_type: String
scalar DateTime live: Boolean
translation_key: String
type AttendanceUserType { frontend_url: String
user_id: UUID!
status: AttendanceUserStatus!
first_name: String
last_name: String
email: String
} }
""" type LearningSequenceObjectType implements CoursePageInterface {
Leverages the internal Python implementation of UUID (uuid.UUID) to provide native UUID objects icon: String!
in fields, resolvers and input. id: ID
""" title: String
scalar UUID slug: String
content_type: String
"""An enumeration.""" live: Boolean
enum AttendanceUserStatus { translation_key: String
PRESENT frontend_url: String
ABSENT learning_units: [LearningUnitObjectType]
} }
type CourseType { type LearningUnitObjectType implements CoursePageInterface {
id: ID! id: ID
title: String! title: String
category_name: String! slug: String
slug: String! content_type: String
learning_path: LearningPathType live: Boolean
translation_key: String
frontend_url: String
learning_contents: [LearningContentInterface]
} }
type LearningPathType implements CoursePageInterface { interface LearningContentInterface {
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
minutes: Int
description: String
content: String
}
type LearningPathObjectType implements CoursePageInterface {
id: ID id: ID
path: String! path: String!
depth: Int! depth: Int!
@ -91,17 +115,15 @@ type LearningPathType implements CoursePageInterface {
search_description: String! search_description: String!
latest_revision_created_at: DateTime latest_revision_created_at: DateTime
frontend_url: String frontend_url: String
topics: [TopicObjectType]
} }
interface CoursePageInterface { """
id: ID The `DateTime` scalar type represents a DateTime
title: String value as specified by
slug: String [iso8601](https://en.wikipedia.org/wiki/ISO_8601).
content_type: String """
live: Boolean scalar DateTime
translation_key: String
frontend_url: String
}
type UserType { type UserType {
""" """
@ -116,6 +138,12 @@ type UserType {
language: CoreUserLanguageChoices! language: CoreUserLanguageChoices!
} }
"""
Leverages the internal Python implementation of UUID (uuid.UUID) to provide native UUID objects
in fields, resolvers and input.
"""
scalar UUID
"""An enumeration.""" """An enumeration."""
enum CoreUserLanguageChoices { enum CoreUserLanguageChoices {
"""Deutsch""" """Deutsch"""
@ -128,6 +156,46 @@ enum CoreUserLanguageChoices {
IT IT
} }
type TopicObjectType implements CoursePageInterface {
is_visible: Boolean!
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
circles: [CircleObjectType]
}
type LearningContentMediaLibraryObjectType implements LearningContentInterface {
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
minutes: Int
description: String
content: String
}
type LearningContentAssignmentObjectType implements LearningContentInterface {
content_assignment: AssignmentObjectType!
assignment_type: LearnpathLearningContentAssignmentAssignmentTypeChoices!
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
minutes: Int
description: String
content: String
}
type AssignmentObjectType implements CoursePageInterface { type AssignmentObjectType implements CoursePageInterface {
assignment_type: AssignmentAssignmentAssignmentTypeChoices! assignment_type: AssignmentAssignmentAssignmentTypeChoices!
@ -168,6 +236,156 @@ enum AssignmentAssignmentAssignmentTypeChoices {
scalar JSONStreamField scalar JSONStreamField
"""An enumeration."""
enum LearnpathLearningContentAssignmentAssignmentTypeChoices {
"""CASEWORK"""
CASEWORK
"""PREP_ASSIGNMENT"""
PREP_ASSIGNMENT
"""REFLECTION"""
REFLECTION
}
type LearningContentAttendanceCourseObjectType implements LearningContentInterface {
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
minutes: Int
description: String
content: String
}
type LearningContentFeedbackObjectType implements LearningContentInterface {
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
minutes: Int
description: String
content: String
}
type LearningContentLearningModuleObjectType implements LearningContentInterface {
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
minutes: Int
description: String
content: String
}
type LearningContentPlaceholderObjectType implements LearningContentInterface {
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
minutes: Int
description: String
content: String
}
type LearningContentRichTextObjectType implements LearningContentInterface {
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
minutes: Int
description: String
content: String
}
type LearningContentTestObjectType implements LearningContentInterface {
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
minutes: Int
description: String
content: String
}
type LearningContentVideoObjectType implements LearningContentInterface {
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
minutes: Int
description: String
content: String
}
type LearningContentDocumentListObjectType implements LearningContentInterface {
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
minutes: Int
description: String
content: String
}
type CourseSessionAttendanceCourseType {
id: ID!
location: String!
trainer: String!
course_session_id: ID
learning_content_id: ID
due_date_id: ID
end: DateTime
start: DateTime
attendance_user_list: [AttendanceUserType]
}
type AttendanceUserType {
user_id: UUID!
status: AttendanceUserStatus!
first_name: String
last_name: String
email: String
}
"""An enumeration."""
enum AttendanceUserStatus {
PRESENT
ABSENT
}
type CourseObjectType {
id: ID!
title: String!
category_name: String!
slug: String!
learning_path: LearningPathObjectType
}
type AssignmentCompletionObjectType { type AssignmentCompletionObjectType {
id: UUID! id: UUID!
created_at: DateTime! created_at: DateTime!
@ -234,6 +452,7 @@ type FeedbackResponse implements Node {
id: ID! id: ID!
data: GenericScalar data: GenericScalar
created_at: DateTime! created_at: DateTime!
circle: CircleObjectType!
} }
"""An object with an ID""" """An object with an ID"""

View File

@ -9,10 +9,11 @@ export const AttendanceUserInputType = "AttendanceUserInputType";
export const AttendanceUserStatus = "AttendanceUserStatus"; export const AttendanceUserStatus = "AttendanceUserStatus";
export const AttendanceUserType = "AttendanceUserType"; export const AttendanceUserType = "AttendanceUserType";
export const Boolean = "Boolean"; export const Boolean = "Boolean";
export const CircleObjectType = "CircleObjectType";
export const CoreUserLanguageChoices = "CoreUserLanguageChoices"; export const CoreUserLanguageChoices = "CoreUserLanguageChoices";
export const CourseObjectType = "CourseObjectType";
export const CoursePageInterface = "CoursePageInterface"; export const CoursePageInterface = "CoursePageInterface";
export const CourseSessionAttendanceCourseType = "CourseSessionAttendanceCourseType"; export const CourseSessionAttendanceCourseType = "CourseSessionAttendanceCourseType";
export const CourseType = "CourseType";
export const DateTime = "DateTime"; export const DateTime = "DateTime";
export const ErrorType = "ErrorType"; export const ErrorType = "ErrorType";
export const FeedbackResponse = "FeedbackResponse"; export const FeedbackResponse = "FeedbackResponse";
@ -22,12 +23,27 @@ export const ID = "ID";
export const Int = "Int"; export const Int = "Int";
export const JSONStreamField = "JSONStreamField"; export const JSONStreamField = "JSONStreamField";
export const JSONString = "JSONString"; export const JSONString = "JSONString";
export const LearningPathType = "LearningPathType"; export const LearningContentAssignmentObjectType = "LearningContentAssignmentObjectType";
export const LearningContentAttendanceCourseObjectType = "LearningContentAttendanceCourseObjectType";
export const LearningContentDocumentListObjectType = "LearningContentDocumentListObjectType";
export const LearningContentFeedbackObjectType = "LearningContentFeedbackObjectType";
export const LearningContentInterface = "LearningContentInterface";
export const LearningContentLearningModuleObjectType = "LearningContentLearningModuleObjectType";
export const LearningContentMediaLibraryObjectType = "LearningContentMediaLibraryObjectType";
export const LearningContentPlaceholderObjectType = "LearningContentPlaceholderObjectType";
export const LearningContentRichTextObjectType = "LearningContentRichTextObjectType";
export const LearningContentTestObjectType = "LearningContentTestObjectType";
export const LearningContentVideoObjectType = "LearningContentVideoObjectType";
export const LearningPathObjectType = "LearningPathObjectType";
export const LearningSequenceObjectType = "LearningSequenceObjectType";
export const LearningUnitObjectType = "LearningUnitObjectType";
export const LearnpathLearningContentAssignmentAssignmentTypeChoices = "LearnpathLearningContentAssignmentAssignmentTypeChoices";
export const Mutation = "Mutation"; export const Mutation = "Mutation";
export const Node = "Node"; export const Node = "Node";
export const Query = "Query"; export const Query = "Query";
export const SendFeedbackInput = "SendFeedbackInput"; export const SendFeedbackInput = "SendFeedbackInput";
export const SendFeedbackPayload = "SendFeedbackPayload"; export const SendFeedbackPayload = "SendFeedbackPayload";
export const String = "String"; export const String = "String";
export const TopicObjectType = "TopicObjectType";
export const UUID = "UUID"; export const UUID = "UUID";
export const UserType = "UserType"; export const UserType = "UserType";

View File

@ -24,7 +24,7 @@ def main():
reset_queries() reset_queries()
page = Page.objects.get( page = Page.objects.get(
slug="versicherungsvermittlerin", locale__language_code="de-CH" slug="überbetriebliche-kurse-lp", locale__language_code="de-CH"
) )
serializer = page.specific.get_serializer_class()(page.specific) serializer = page.specific.get_serializer_class()(page.specific)
@ -32,6 +32,13 @@ def main():
print(len(json.dumps(serializer.data))) print(len(json.dumps(serializer.data)))
print(len(connection.queries)) print(len(connection.queries))
# reference
page = Page.objects.get(
slug="überbetriebliche-kurse-lp", locale__language_code="de-CH"
)
list(page.get_descendants().specific())
print(len(connection.queries))
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
import os
import sys
import django
import graphene
from graphene import Context
sys.path.append("../server")
os.environ.setdefault("IT_APP_ENVIRONMENT", "local")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.base")
django.setup()
from vbv_lernwelt.core.schema import Query
from vbv_lernwelt.core.models import User
def main():
from django.conf import settings
settings.DEBUG = True
from django.db import connection
from django.db import reset_queries
reset_queries()
schema = graphene.Schema(query=Query, auto_camelcase=False)
context = Context()
context.user = User.objects.get(username="admin")
result = schema.execute(
"""
{
learning_path(slug: "überbetriebliche-kurse-lp") {
id
title
content_type
topics {
id
title
content_type
circles {
id
title
content_type
learning_sequences {
id
title
icon
learning_units {
id
title
learning_contents {
id
title
}
}
}
}
}
}
}
""",
context=context,
)
print(result)
print(len(connection.queries))
# reference
reset_queries()
if __name__ == "__main__":
main()

View File

@ -4,7 +4,7 @@ from graphene_django import DjangoObjectType
from vbv_lernwelt.assignment.models import Assignment, AssignmentCompletion from vbv_lernwelt.assignment.models import Assignment, AssignmentCompletion
from vbv_lernwelt.core.graphql.types import JSONStreamField from vbv_lernwelt.core.graphql.types import JSONStreamField
from vbv_lernwelt.course.schema import CoursePageInterface from vbv_lernwelt.course.graphql.interfaces import CoursePageInterface
class AssignmentObjectType(DjangoObjectType): class AssignmentObjectType(DjangoObjectType):

View File

@ -2,13 +2,16 @@ import graphene
from vbv_lernwelt.assignment.graphql.mutations import AssignmentMutation from vbv_lernwelt.assignment.graphql.mutations import AssignmentMutation
from vbv_lernwelt.assignment.graphql.queries import AssignmentQuery from vbv_lernwelt.assignment.graphql.queries import AssignmentQuery
from vbv_lernwelt.course.schema import CourseQuery from vbv_lernwelt.course.graphql.queries import CourseQuery
from vbv_lernwelt.course_session.graphql.mutations import CourseSessionMutation from vbv_lernwelt.course_session.graphql.mutations import CourseSessionMutation
from vbv_lernwelt.course_session.graphql.queries import CourseSessionQuery from vbv_lernwelt.course_session.graphql.queries import CourseSessionQuery
from vbv_lernwelt.feedback.graphql.mutations import FeedbackMutation from vbv_lernwelt.feedback.graphql.mutations import FeedbackMutation
from vbv_lernwelt.learnpath.graphql.queries import CircleQuery
class Query(AssignmentQuery, CourseQuery, CourseSessionQuery, graphene.ObjectType): class Query(
AssignmentQuery, CourseQuery, CourseSessionQuery, CircleQuery, graphene.ObjectType
):
pass pass

View File

@ -1,7 +1,7 @@
import wagtail.api.v2.serializers as wagtail_serializers import wagtail.api.v2.serializers as wagtail_serializers
from rest_framework.fields import SerializerMethodField from rest_framework.fields import SerializerMethodField
from vbv_lernwelt.learnpath.utils import get_wagtail_type from vbv_lernwelt.core.utils import get_django_content_type
def get_it_serializer_class( def get_it_serializer_class(
@ -33,7 +33,7 @@ def get_it_serializer_class(
class ItWagtailTypeField(wagtail_serializers.TypeField): class ItWagtailTypeField(wagtail_serializers.TypeField):
def to_representation(self, obj): def to_representation(self, obj):
name = get_wagtail_type(obj) name = get_django_content_type(obj)
return name return name

View File

@ -39,19 +39,17 @@ class DayUserRateThrottle(UserRateThrottle):
scope = "day-throttle" scope = "day-throttle"
def find_first(iterable, default=False, pred=None): def find_first(iterable, pred=None, default=None):
"""Returns the first true value in the iterable.
If no true value is found, returns *default*
If *pred* is not None, returns the first item
for which pred(item) is true.
"""
# first_true([a,b,c], x) --> a or b or c or x
# first_true([a,b], x, f) --> a if f(a) else b if f(b) else x
return next(filter(pred, iterable), default) return next(filter(pred, iterable), default)
def find_first_index(iterable, pred, default=None):
return next((i for i, x in enumerate(iterable) if pred(x)), default)
def replace_whitespace(text, replacement=" "): def replace_whitespace(text, replacement=" "):
return re.sub(r"\s+", replacement, text).strip() return re.sub(r"\s+", replacement, text).strip()
def get_django_content_type(obj):
return obj._meta.app_label + "." + type(obj).__name__

View File

@ -0,0 +1,19 @@
import graphene
from vbv_lernwelt.core.utils import get_django_content_type
class CoursePageInterface(graphene.Interface):
id = graphene.ID()
title = graphene.String()
slug = graphene.String()
content_type = graphene.String()
live = graphene.Boolean()
translation_key = graphene.String()
frontend_url = graphene.String()
def resolve_frontend_url(self, info):
return self.get_frontend_url()
def resolve_content_type(self, info):
return get_django_content_type(self)

View File

@ -1,16 +1,15 @@
import graphene import graphene
from graphql import GraphQLError
from vbv_lernwelt.course.graphql.types import CourseType from vbv_lernwelt.course.graphql.types import CourseObjectType
from vbv_lernwelt.course.models import Course from vbv_lernwelt.course.models import Course
from vbv_lernwelt.course.permissions import has_course_access from vbv_lernwelt.course.permissions import has_course_access
class CourseQuery: class CourseQuery(graphene.ObjectType):
course = graphene.Field(CourseType, id=graphene.Int()) course = graphene.Field(CourseObjectType, id=graphene.Int())
def resolve_course(root, info, id): def resolve_course(root, info, id):
course = Course.objects.get(pk=id) course = Course.objects.get(pk=id)
if has_course_access(info.context.user, course): if has_course_access(info.context.user, course):
return course return course
return GraphQLError("You do not have access to this course") raise PermissionError("You do not have access to this course")

View File

@ -8,7 +8,7 @@ from rest_framework.exceptions import PermissionDenied
from vbv_lernwelt.course.models import Course, CourseBasePage from vbv_lernwelt.course.models import Course, CourseBasePage
from vbv_lernwelt.course.permissions import has_course_access from vbv_lernwelt.course.permissions import has_course_access
from vbv_lernwelt.course.schema import CoursePageInterface from vbv_lernwelt.learnpath.graphql.types import LearningPathObjectType
logger = structlog.get_logger(__name__) logger = structlog.get_logger(__name__)
@ -39,12 +39,12 @@ def resolve_course_page(
raise e raise e
class CourseType(DjangoObjectType): class CourseObjectType(DjangoObjectType):
learning_path = graphene.Field(CoursePageInterface) learning_path = graphene.Field(LearningPathObjectType)
class Meta: class Meta:
model = Course model = Course
fields = ("id", "title", "category_name", "slug") fields = ("id", "title", "category_name", "slug", "learning_path")
def resolve_learning_path(self, info): def resolve_learning_path(self, info):
return self.get_learning_path() return self.get_learning_path()

View File

@ -1,46 +0,0 @@
import graphene
from graphene_django import DjangoObjectType
from vbv_lernwelt.course.models import Course
from vbv_lernwelt.course.permissions import has_course_access
from vbv_lernwelt.learnpath.models import LearningPath
class CoursePageInterface(graphene.Interface):
id = graphene.ID()
title = graphene.String()
slug = graphene.String()
content_type = graphene.String()
live = graphene.Boolean()
translation_key = graphene.String()
frontend_url = graphene.String()
def resolve_frontend_url(self, info):
return self.get_frontend_url()
class LearningPathType(DjangoObjectType):
class Meta:
model = LearningPath
interfaces = (CoursePageInterface,)
class CourseType(DjangoObjectType):
learning_path = graphene.Field(LearningPathType)
class Meta:
model = Course
fields = ("id", "title", "category_name", "slug", "learning_path")
def resolve_learning_path(self, info):
return self.get_learning_path()
class CourseQuery(graphene.ObjectType):
course = graphene.Field(CourseType, id=graphene.Int())
def resolve_course(root, info, id):
course = Course.objects.get(pk=id)
if has_course_access(info.context.user, course):
return course
raise PermissionError("You do not have access to this course")

View File

@ -1,5 +1,5 @@
from vbv_lernwelt.core.utils import get_django_content_type
from vbv_lernwelt.course.models import CourseCompletion, CourseCompletionStatus from vbv_lernwelt.course.models import CourseCompletion, CourseCompletionStatus
from vbv_lernwelt.learnpath.utils import get_wagtail_type
def mark_course_completion( def mark_course_completion(
@ -15,7 +15,7 @@ def mark_course_completion(
and page.specific.has_course_completion_status and page.specific.has_course_completion_status
): ):
return ValueError( return ValueError(
f"Page {page.id} of type {get_wagtail_type(page)}" f"Page {page.id} of type {get_django_content_type(page)}"
f" cannot be marked as completed" f" cannot be marked as completed"
) )
@ -25,7 +25,7 @@ def mark_course_completion(
course_session_id=course_session.id, course_session_id=course_session.id,
) )
cc.completion_status = completion_status cc.completion_status = completion_status
cc.page_type = get_wagtail_type(page.specific) cc.page_type = get_django_content_type(page.specific)
cc.save() cc.save()
return cc return cc

View File

@ -5,6 +5,8 @@ from rest_framework.exceptions import PermissionDenied
from rest_framework.response import Response from rest_framework.response import Response
from wagtail.models import Page from wagtail.models import Page
from vbv_lernwelt.core.utils import get_django_content_type
from vbv_lernwelt.course.models import ( from vbv_lernwelt.course.models import (
CircleDocument, CircleDocument,
CourseCompletion, CourseCompletion,
@ -27,7 +29,6 @@ from vbv_lernwelt.course.serializers import (
from vbv_lernwelt.course.services import mark_course_completion from vbv_lernwelt.course.services import mark_course_completion
from vbv_lernwelt.files.models import UploadFile from vbv_lernwelt.files.models import UploadFile
from vbv_lernwelt.files.services import FileDirectUploadService from vbv_lernwelt.files.services import FileDirectUploadService
from vbv_lernwelt.learnpath.utils import get_wagtail_type
logger = structlog.get_logger(__name__) logger = structlog.get_logger(__name__)
@ -115,7 +116,7 @@ def mark_course_completion_view(request):
"mark_course_completion successful", "mark_course_completion successful",
label="completion_api", label="completion_api",
page_id=page_id, page_id=page_id,
page_type=get_wagtail_type(page.specific), page_type=get_django_content_type(page.specific),
page_slug=page.slug, page_slug=page.slug,
page_title=page.title, page_title=page.title,
user_id=request.user.id, user_id=request.user.id,

View File

@ -0,0 +1,81 @@
import graphene
from vbv_lernwelt.course.graphql.types import resolve_course_page
from vbv_lernwelt.learnpath.graphql.types import (
CircleObjectType,
LearningContentAssignmentObjectType,
LearningContentAttendanceCourseObjectType,
LearningContentDocumentListObjectType,
LearningContentFeedbackObjectType,
LearningContentLearningModuleObjectType,
LearningContentMediaLibraryObjectType,
LearningContentPlaceholderObjectType,
LearningContentRichTextObjectType,
LearningContentTestObjectType,
LearningContentVideoObjectType,
LearningPathObjectType,
)
from vbv_lernwelt.learnpath.models import Circle, LearningPath
class CircleQuery:
circle = graphene.Field(CircleObjectType, id=graphene.Int(), slug=graphene.String())
learning_path = graphene.Field(
LearningPathObjectType, id=graphene.Int(), slug=graphene.String()
)
def resolve_circle(root, info, id=None, slug=None):
return resolve_course_page(Circle, root, info, id=id, slug=slug)
def resolve_learning_path(root, info, id=None, slug=None):
return resolve_course_page(LearningPath, root, info, id=id, slug=slug)
# dummy import, so that graphene recognizes the types
learning_content_media_library = graphene.Field(
LearningContentMediaLibraryObjectType
)
learning_content_assignment = graphene.Field(LearningContentAssignmentObjectType)
learning_content_attendance_course = graphene.Field(
LearningContentAttendanceCourseObjectType
)
learning_content_feedback = graphene.Field(LearningContentFeedbackObjectType)
learning_content_learning_module = graphene.Field(
LearningContentLearningModuleObjectType
)
learning_content_placeholder = graphene.Field(LearningContentPlaceholderObjectType)
learning_content_rich_text = graphene.Field(LearningContentRichTextObjectType)
learning_content_test = graphene.Field(LearningContentTestObjectType)
learning_content_video = graphene.Field(LearningContentVideoObjectType)
learning_content_document_list = graphene.Field(
LearningContentDocumentListObjectType
)
def resolve_learning_content_media_library(self, info):
return None
def resolve_learning_content_assignment(self, info):
return None
def resolve_learning_content_attendance_course(self, info):
return None
def resolve_learning_content_feedback(self, info):
return None
def resolve_learning_content_learning_module(self, info):
return None
def resolve_learning_content_placeholder(self, info):
return None
def resolve_learning_content_rich_text(self, info):
return None
def resolve_learning_content_test(self, info):
return None
def resolve_learning_content_video(self, info):
return None
def resolve_learning_content_document_list(self, info):
return None

View File

@ -0,0 +1,282 @@
import graphene
import structlog
from graphene_django import DjangoObjectType
from vbv_lernwelt.core.utils import find_first_index
from vbv_lernwelt.course.graphql.interfaces import CoursePageInterface
from vbv_lernwelt.learnpath.models import (
Circle,
LearningContentAssignment,
LearningContentAttendanceCourse,
LearningContentDocumentList,
LearningContentFeedback,
LearningContentLearningModule,
LearningContentMediaLibrary,
LearningContentPlaceholder,
LearningContentRichText,
LearningContentTest,
LearningContentVideo,
LearningPath,
LearningSequence,
LearningUnit,
Topic,
)
logger = structlog.get_logger(__name__)
class LearningContentInterface(CoursePageInterface):
minutes = graphene.Int()
description = graphene.String()
content = graphene.String()
@classmethod
def resolve_type(cls, instance, info):
if isinstance(instance, LearningContentAssignment):
return LearningContentAssignmentObjectType
elif isinstance(instance, LearningContentAttendanceCourse):
return LearningContentAttendanceCourseObjectType
elif isinstance(instance, LearningContentFeedback):
return LearningContentFeedbackObjectType
elif isinstance(instance, LearningContentLearningModule):
return LearningContentLearningModuleObjectType
elif isinstance(instance, LearningContentMediaLibrary):
return LearningContentMediaLibraryObjectType
elif isinstance(instance, LearningContentPlaceholder):
return LearningContentPlaceholderObjectType
elif isinstance(instance, LearningContentRichText):
return LearningContentRichTextObjectType
elif isinstance(instance, LearningContentTest):
return LearningContentTestObjectType
elif isinstance(instance, LearningContentVideo):
return LearningContentVideoObjectType
elif isinstance(instance, LearningContentDocumentList):
return LearningContentDocumentListObjectType
else:
logger.error(
"Could not resolve type for LearningContentInterface",
django_instance=instance,
)
return None
class LearningContentAttendanceCourseObjectType(DjangoObjectType):
class Meta:
model = LearningContentAttendanceCourse
interfaces = (LearningContentInterface,)
fields = []
class LearningContentVideoObjectType(DjangoObjectType):
class Meta:
model = LearningContentVideo
interfaces = (LearningContentInterface,)
fields = []
class LearningContentPlaceholderObjectType(DjangoObjectType):
class Meta:
model = LearningContentPlaceholder
interfaces = (LearningContentInterface,)
fields = []
class LearningContentFeedbackObjectType(DjangoObjectType):
class Meta:
model = LearningContentFeedback
interfaces = (LearningContentInterface,)
fields = []
class LearningContentLearningModuleObjectType(DjangoObjectType):
class Meta:
model = LearningContentLearningModule
interfaces = (LearningContentInterface,)
fields = []
class LearningContentMediaLibraryObjectType(DjangoObjectType):
class Meta:
model = LearningContentMediaLibrary
interfaces = (LearningContentInterface,)
fields = []
class LearningContentTestObjectType(DjangoObjectType):
class Meta:
model = LearningContentTest
interfaces = (LearningContentInterface,)
fields = []
class LearningContentRichTextObjectType(DjangoObjectType):
class Meta:
model = LearningContentRichText
interfaces = (LearningContentInterface,)
fields = []
class LearningContentAssignmentObjectType(DjangoObjectType):
class Meta:
model = LearningContentAssignment
interfaces = (LearningContentInterface,)
fields = [
"content_assignment",
"assignment_type",
]
class LearningContentDocumentListObjectType(DjangoObjectType):
class Meta:
model = LearningContentDocumentList
interfaces = (LearningContentInterface,)
fields = []
class LearningUnitObjectType(DjangoObjectType):
learning_contents = graphene.List(LearningContentInterface)
class Meta:
model = LearningUnit
interfaces = (CoursePageInterface,)
fields = []
@staticmethod
def resolve_learning_contents(root: LearningUnit, info, **kwargs):
siblings = None
if hasattr(info.context, "circle_descendants"):
circle_descendants = info.context.circle_descendants
index = circle_descendants.index(root)
siblings = circle_descendants[index + 1 :]
if not siblings:
siblings = root.get_siblings().live().specific()
learning_contents = []
for sibling in siblings:
if (
sibling.specific_class == LearningUnit
or sibling.specific_class == LearningSequence
):
break
else:
learning_contents.append(sibling.specific)
return learning_contents
class LearningSequenceObjectType(DjangoObjectType):
learning_units = graphene.List(LearningUnitObjectType)
class Meta:
model = LearningSequence
interfaces = (CoursePageInterface,)
fields = ["icon"]
@staticmethod
def resolve_learning_units(root: LearningSequence, info, **kwargs):
siblings = None
if hasattr(info.context, "circle_descendants"):
circle_descendants = info.context.circle_descendants
index = circle_descendants.index(root)
siblings = circle_descendants[index + 1 :]
if not siblings:
siblings = root.get_siblings().live().specific()
learning_units = []
for sibling in siblings:
if sibling.specific_class == LearningSequence:
# finished with this sequence
break
if sibling.specific_class == LearningUnit:
learning_units.append(sibling.specific)
return learning_units
class CircleObjectType(DjangoObjectType):
learning_sequences = graphene.List(LearningSequenceObjectType)
class Meta:
model = Circle
interfaces = (CoursePageInterface,)
fields = [
"description",
"goals",
]
@staticmethod
def resolve_learning_sequences(root: Circle, info, **kwargs):
circle_descendants = None
if hasattr(info.context, "learning_path_descendants"):
children = info.context.learning_path_descendants
circle_start_index = children.index(root)
next_circle_index = find_first_index(
children[circle_start_index + 1 :],
pred=lambda child: child.specific_class == Circle,
)
circle_descendants = children[circle_start_index + 1 : next_circle_index]
if not circle_descendants:
circle_descendants = list(root.get_descendants().live().specific())
# store flattened descendents to improve performance (no need for db queries)
info.context.circle_descendants = list(circle_descendants)
return [
descendant.specific
for descendant in circle_descendants
if descendant.specific_class == LearningSequence
]
class TopicObjectType(DjangoObjectType):
circles = graphene.List(CircleObjectType)
class Meta:
model = Topic
interfaces = (CoursePageInterface,)
fields = [
"is_visible",
]
@staticmethod
def resolve_circles(root: LearningPath, info, **kwargs):
siblings = None
if hasattr(info.context, "learning_path_descendants"):
learning_path_descendants = info.context.learning_path_descendants
index = learning_path_descendants.index(root)
siblings = learning_path_descendants[index + 1 :]
if not siblings:
siblings = root.get_next_siblings().live().specific()
circles = []
for sibling in siblings:
if sibling.specific_class == Topic:
# finished with this topic
break
if sibling.specific_class == Circle:
circles.append(sibling.specific)
return circles
class LearningPathObjectType(DjangoObjectType):
topics = graphene.List(TopicObjectType)
class Meta:
model = LearningPath
interfaces = (CoursePageInterface,)
@staticmethod
def resolve_topics(root: LearningPath, info, **kwargs):
learning_path_descendants_qs = root.get_descendants().live().specific()
# store flattened descendents to improve performance (no need for db queries)
info.context.learning_path_descendants = list(learning_path_descendants_qs)
return [
descendant.specific
for descendant in learning_path_descendants_qs
if descendant.specific_class == Topic
]

View File

@ -1,2 +0,0 @@
def get_wagtail_type(obj):
return obj._meta.app_label + "." + type(obj).__name__