Extend graphql scheme for KompetenzNavi

This commit is contained in:
Daniel Egger 2023-09-01 11:51:44 +02:00
parent 0537d96dbb
commit a95974c54f
10 changed files with 330 additions and 122 deletions

View File

@ -162,6 +162,42 @@ export type CircleObjectType = CoursePageInterface & {
translation_key?: Maybe<Scalars['String']['output']>;
};
export type CompetenceCertificateListObjectType = CoursePageInterface & {
__typename?: 'CompetenceCertificateListObjectType';
circle?: Maybe<CircleObjectType>;
competence_certificates?: Maybe<Array<Maybe<CompetenceCertificateObjectType>>>;
content_type?: Maybe<Scalars['String']['output']>;
course?: Maybe<CourseObjectType>;
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']['output'];
owner?: Maybe<UserType>;
path: Scalars['String']['output'];
/** Die informative Beschreibung, dargestellt in Suchmaschinen-Ergebnissen unter der Überschrift. */
search_description: Scalars['String']['output'];
/** Der Titel der Seite, dargestellt in Suchmaschinen-Ergebnissen als die verlinkte Überschrift. */
seo_title: Scalars['String']['output'];
/** Ob ein Link zu dieser Seite in automatisch generierten Menüs auftaucht. */
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 CompetenceCertificateObjectType = CoursePageInterface & {
__typename?: 'CompetenceCertificateObjectType';
assignments?: Maybe<Array<Maybe<AssignmentObjectType>>>;
@ -532,6 +568,7 @@ export type Query = {
assignment_completion?: Maybe<AssignmentCompletionObjectType>;
circle?: Maybe<CircleObjectType>;
competence_certificate?: Maybe<CompetenceCertificateObjectType>;
competence_certificate_list?: Maybe<CompetenceCertificateListObjectType>;
course?: Maybe<CourseObjectType>;
course_session_attendance_course?: Maybe<CourseSessionAttendanceCourseType>;
learning_content_assignment?: Maybe<LearningContentAssignmentObjectType>;
@ -563,13 +600,22 @@ export type QueryAssignmentCompletionArgs = {
export type QueryCircleArgs = {
id?: InputMaybe<Scalars['Int']['input']>;
id?: InputMaybe<Scalars['ID']['input']>;
slug?: InputMaybe<Scalars['String']['input']>;
};
export type QueryCompetenceCertificateArgs = {
id?: InputMaybe<Scalars['ID']['input']>;
slug?: InputMaybe<Scalars['String']['input']>;
};
export type QueryCompetenceCertificateListArgs = {
course_id?: InputMaybe<Scalars['ID']['input']>;
course_slug?: InputMaybe<Scalars['String']['input']>;
id?: InputMaybe<Scalars['ID']['input']>;
slug?: InputMaybe<Scalars['String']['input']>;
};
@ -585,7 +631,9 @@ export type QueryCourseSessionAttendanceCourseArgs = {
export type QueryLearningPathArgs = {
id?: InputMaybe<Scalars['Int']['input']>;
course_id?: InputMaybe<Scalars['ID']['input']>;
course_slug?: InputMaybe<Scalars['String']['input']>;
id?: InputMaybe<Scalars['ID']['input']>;
slug?: InputMaybe<Scalars['String']['input']>;
};

View File

@ -1,6 +1,6 @@
type Query {
circle(id: Int, slug: String): CircleObjectType
learning_path(id: Int, slug: String): LearningPathObjectType
learning_path(id: ID, slug: String, course_id: ID, course_slug: String): LearningPathObjectType
circle(id: ID, slug: String): CircleObjectType
learning_content_media_library: LearningContentMediaLibraryObjectType
learning_content_assignment: LearningContentAssignmentObjectType
learning_content_attendance_course: LearningContentAttendanceCourseObjectType
@ -13,46 +13,12 @@ type Query {
learning_content_document_list: LearningContentDocumentListObjectType
course_session_attendance_course(id: ID!, assignment_user_id: ID): CourseSessionAttendanceCourseType
course(id: Int): CourseObjectType
competence_certificate(id: ID): CompetenceCertificateObjectType
competence_certificate(id: ID, slug: String): CompetenceCertificateObjectType
competence_certificate_list(id: ID, slug: String, course_id: ID, course_slug: String): CompetenceCertificateListObjectType
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
}
type CircleObjectType implements CoursePageInterface {
description: String!
goals: String!
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
circle: CircleObjectType
course: CourseObjectType
learning_sequences: [LearningSequenceObjectType]
}
interface CoursePageInterface {
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
circle: CircleObjectType
course: CourseObjectType
}
type CourseObjectType {
id: ID!
title: String!
category_name: String!
slug: String!
learning_path: LearningPathObjectType
}
type LearningPathObjectType implements CoursePageInterface {
id: ID
path: String!
@ -97,6 +63,83 @@ type LearningPathObjectType implements CoursePageInterface {
topics: [TopicObjectType]
}
interface CoursePageInterface {
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
circle: CircleObjectType
course: CourseObjectType
}
type CircleObjectType implements CoursePageInterface {
description: String!
goals: String!
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
circle: CircleObjectType
course: CourseObjectType
learning_sequences: [LearningSequenceObjectType]
}
type CourseObjectType {
id: ID!
title: String!
category_name: String!
slug: String!
learning_path: LearningPathObjectType
}
type LearningSequenceObjectType implements CoursePageInterface {
icon: String!
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
circle: CircleObjectType
course: CourseObjectType
learning_units: [LearningUnitObjectType]
}
type LearningUnitObjectType implements CoursePageInterface {
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
circle: CircleObjectType
course: CourseObjectType
learning_contents: [LearningContentInterface]
}
interface LearningContentInterface {
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
circle: CircleObjectType
course: CourseObjectType
minutes: Int
description: String
content: String
}
"""
The `DateTime` scalar type represents a DateTime
value as specified by
@ -149,48 +192,6 @@ type TopicObjectType implements CoursePageInterface {
circles: [CircleObjectType]
}
type LearningSequenceObjectType implements CoursePageInterface {
icon: String!
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
circle: CircleObjectType
course: CourseObjectType
learning_units: [LearningUnitObjectType]
}
type LearningUnitObjectType implements CoursePageInterface {
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
circle: CircleObjectType
course: CourseObjectType
learning_contents: [LearningContentInterface]
}
interface LearningContentInterface {
id: ID
title: String
slug: String
content_type: String
live: Boolean
translation_key: String
frontend_url: String
circle: CircleObjectType
course: CourseObjectType
minutes: Int
description: String
content: String
}
type LearningContentMediaLibraryObjectType implements LearningContentInterface {
id: ID
title: String
@ -451,6 +452,50 @@ type CompetenceCertificateObjectType implements CoursePageInterface {
assignments: [AssignmentObjectType]
}
type CompetenceCertificateListObjectType implements CoursePageInterface {
id: ID
path: String!
depth: Int!
numchild: Int!
translation_key: String
live: Boolean
has_unpublished_changes: Boolean!
first_published_at: DateTime
last_published_at: DateTime
go_live_at: DateTime
expire_at: DateTime
expired: Boolean!
locked: Boolean!
locked_at: DateTime
locked_by: UserType
title: String
draft_title: String!
slug: String
content_type: String
url_path: String!
owner: UserType
"""
Der Titel der Seite, dargestellt in Suchmaschinen-Ergebnissen als die verlinkte Überschrift.
"""
seo_title: String!
"""
Ob ein Link zu dieser Seite in automatisch generierten Menüs auftaucht.
"""
show_in_menus: Boolean!
"""
Die informative Beschreibung, dargestellt in Suchmaschinen-Ergebnissen unter der Überschrift.
"""
search_description: String!
latest_revision_created_at: DateTime
frontend_url: String
circle: CircleObjectType
course: CourseObjectType
competence_certificates: [CompetenceCertificateObjectType]
}
type AssignmentCompletionObjectType {
id: UUID!
created_at: DateTime!

View File

@ -10,6 +10,7 @@ export const AttendanceUserStatus = "AttendanceUserStatus";
export const AttendanceUserType = "AttendanceUserType";
export const Boolean = "Boolean";
export const CircleObjectType = "CircleObjectType";
export const CompetenceCertificateListObjectType = "CompetenceCertificateListObjectType";
export const CompetenceCertificateObjectType = "CompetenceCertificateObjectType";
export const CoreUserLanguageChoices = "CoreUserLanguageChoices";
export const CourseObjectType = "CourseObjectType";

View File

@ -1,15 +1,29 @@
fragment CoursePageFields on CoursePageInterface {
title
id
slug
content_type
}
{
competence_certificate(id:1) {
title,
assignments {
id
title
max_points
learning_content {
title
frontend_url
content_type
competence_certificate_list(course_slug:"test-lehrgang") {
...CoursePageFields
competence_certificates {
...CoursePageFields
assignments {
...CoursePageFields
assignment_type
max_points
learning_content {
title
id
slug
content_type
circle {
...CoursePageFields
}
}
}
}
}
}
}

View File

@ -1,14 +1,41 @@
import graphene
from vbv_lernwelt.competence.graphql.types import CompetenceCertificateObjectType
from vbv_lernwelt.competence.models import CompetenceCertificate
from vbv_lernwelt.competence.graphql.types import (
CompetenceCertificateObjectType,
CompetenceCertificateListObjectType,
)
from vbv_lernwelt.competence.models import (
CompetenceCertificate,
CompetenceCertificateList,
)
from vbv_lernwelt.course.graphql.types import resolve_course_page
class CompetenceCertificateQuery(object):
competence_certificate = graphene.Field(
CompetenceCertificateObjectType,
id=graphene.ID(),
CompetenceCertificateObjectType, id=graphene.ID(), slug=graphene.String()
)
def resolve_competence_certificate(root, info, id):
return CompetenceCertificate.objects.get(id=id)
competence_certificate_list = graphene.Field(
CompetenceCertificateListObjectType,
id=graphene.ID(),
slug=graphene.String(),
course_id=graphene.ID(),
course_slug=graphene.String(),
)
def resolve_competence_certificate(root, info, id=None, slug=None):
return resolve_course_page(CompetenceCertificate, root, info, id=id, slug=slug)
def resolve_competence_certificate_list(
root, info, id=None, slug=None, course_id=None, course_slug=None
):
return resolve_course_page(
CompetenceCertificateList,
root,
info,
id=id,
slug=slug,
course_id=course_id,
course_slug=course_slug,
)

View File

@ -2,7 +2,10 @@ from graphene import List
from graphene_django import DjangoObjectType
from vbv_lernwelt.assignment.graphql.types import AssignmentObjectType
from vbv_lernwelt.competence.models import CompetenceCertificate
from vbv_lernwelt.competence.models import (
CompetenceCertificate,
CompetenceCertificateList,
)
from vbv_lernwelt.course.graphql.interfaces import CoursePageInterface
@ -14,6 +17,16 @@ class CompetenceCertificateObjectType(DjangoObjectType):
interfaces = (CoursePageInterface,)
fields = ["assignments"]
# resolver for the assignments
def resolve_assignments(self, info):
return self.assignment_set.all()
class CompetenceCertificateListObjectType(DjangoObjectType):
competence_certificates = List(CompetenceCertificateObjectType)
class Meta:
model = CompetenceCertificateList
interfaces = (CoursePageInterface,)
def resolve_competence_certificates(self, info):
return CompetenceCertificate.objects.child_of(self)

View File

@ -30,6 +30,16 @@ class CompetenceCertificateList(CourseBasePage):
parent_page_types = ["competence.CompetenceNaviPage"]
subpage_types = ["competence.CompetenceCertificate"]
def get_frontend_url(self):
return f"/course/{self.slug.replace('-competencenavi-certificates', '')}/competence/certificates"
def save(self, clean=True, user=None, log_action=False, **kwargs):
self.slug = find_available_slug(
slugify(f"{self.get_parent().slug}-certificates", allow_unicode=True),
ignore_page_id=self.id,
)
super(CompetenceCertificateList, self).save(clean, user, log_action, **kwargs)
class CompetenceCertificate(CourseBasePage):
"""einzelner Kompetenznachweis"""
@ -40,6 +50,16 @@ class CompetenceCertificate(CourseBasePage):
course = self.get_course()
return f"{course.title} - {self.title}"
def get_frontend_url(self):
return f"/course/{self.slug.replace('-competencenavi-certificates-', '/competence/certificates/')}"
def save(self, clean=True, user=None, log_action=False, **kwargs):
self.slug = find_available_slug(
slugify(f"{self.get_parent().slug}-{self.title}", allow_unicode=True),
ignore_page_id=self.id,
)
super(CompetenceCertificate, self).save(clean, user, log_action, **kwargs)
class CompetenceProfilePage(CourseBasePage):
serialize_field_names = [

View File

@ -7,7 +7,7 @@ from vbv_lernwelt.course.graphql.queries import CourseQuery
from vbv_lernwelt.course_session.graphql.mutations import CourseSessionMutation
from vbv_lernwelt.course_session.graphql.queries import CourseSessionQuery
from vbv_lernwelt.feedback.graphql.mutations import FeedbackMutation
from vbv_lernwelt.learnpath.graphql.queries import CircleQuery
from vbv_lernwelt.learnpath.graphql.queries import LearningPathQuery
class Query(
@ -15,7 +15,7 @@ class Query(
CompetenceCertificateQuery,
CourseQuery,
CourseSessionQuery,
CircleQuery,
LearningPathQuery,
graphene.ObjectType,
):
pass

View File

@ -6,7 +6,7 @@ from graphene_django import DjangoObjectType
from graphql import GraphQLError
from rest_framework.exceptions import PermissionDenied
from vbv_lernwelt.course.models import Course, CourseBasePage
from vbv_lernwelt.course.models import Course, CourseBasePage, CoursePage
from vbv_lernwelt.course.permissions import has_course_access
from vbv_lernwelt.learnpath.graphql.types import LearningPathObjectType
@ -14,23 +14,49 @@ logger = structlog.get_logger(__name__)
def resolve_course_page(
page_model_class: Type[CourseBasePage], root, info, id=None, slug=None
page_model_class: Type[CourseBasePage],
root,
info,
id=None,
slug=None,
course_id=None,
course_slug=None,
):
try:
if id is None and slug is None:
raise GraphQLError("Either 'id' or 'slug' must be provided.")
if id is None and slug is None and course_id is None and course_slug is None:
raise GraphQLError(
"Either 'id', 'slug', 'course_id' or 'course_slug' must be provided."
)
page = None
if id is not None:
page = page_model_class.objects.get(pk=id)
elif slug is not None:
page = page_model_class.objects.get(slug=slug)
if id or slug:
# try to fetch page directly
page = None
if id is not None:
page = page_model_class.objects.get(pk=id)
elif slug is not None:
page = page_model_class.objects.get(slug=slug)
if page and not has_course_access(
info.context.user, page.specific.get_course().id
):
raise PermissionDenied("You do not have access to this course.")
return page
if page and not has_course_access(
info.context.user, page.specific.get_course().id
):
raise PermissionDenied("You do not have access to this course.")
return page
if course_id or course_slug:
# fetch first page of type `page_model_class` via course
# makes sense for "Index" pages like "lernpfad" or "kompetenznachweise" etc
course_page = None
if course_id is not None:
course_page = CoursePage.objects.get(pk=course_id)
elif course_slug is not None:
course_page = CoursePage.objects.get(slug=course_slug)
page = course_page.get_descendants().type(page_model_class).first()
if page and not has_course_access(
info.context.user, page.specific.get_course().id
):
raise PermissionDenied("You do not have access to this course.")
return page.specific
except PermissionDenied as e:
raise e

View File

@ -18,17 +18,31 @@ from vbv_lernwelt.learnpath.graphql.types import (
from vbv_lernwelt.learnpath.models import Circle, LearningPath
class CircleQuery:
circle = graphene.Field(CircleObjectType, id=graphene.Int(), slug=graphene.String())
class LearningPathQuery:
learning_path = graphene.Field(
LearningPathObjectType, id=graphene.Int(), slug=graphene.String()
LearningPathObjectType,
id=graphene.ID(),
slug=graphene.String(),
course_id=graphene.ID(),
course_slug=graphene.String(),
)
circle = graphene.Field(CircleObjectType, id=graphene.ID(), 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)
def resolve_learning_path(
root, info, id=None, slug=None, course_id=None, course_slug=None
):
return resolve_course_page(
LearningPath,
root,
info,
id=id,
slug=slug,
course_id=course_id,
course_slug=course_slug,
)
# dummy import, so that graphene recognizes the types
learning_content_media_library = graphene.Field(