diff --git a/client/src/__generated__/graphql.ts b/client/src/__generated__/graphql.ts index 830adaa3..1c1b372d 100644 --- a/client/src/__generated__/graphql.ts +++ b/client/src/__generated__/graphql.ts @@ -262,7 +262,7 @@ export type AnswerNode = Node & { data: Scalars['JSONString']['output']; /** The ID of the object */ id: Scalars['ID']['output']; - owner: PrivateUserNode; + owner: PublicUserNode; pk?: Maybe; survey: SurveyNode; }; @@ -313,7 +313,7 @@ export type AssignmentNode = Node & { id: Scalars['ID']['output']; modified: Scalars['DateTime']['output']; module: ModuleNode; - owner?: Maybe; + owner?: Maybe; path: Scalars['String']['output']; solution?: Maybe; submission?: Maybe; @@ -349,7 +349,7 @@ export type ChapterBookmarkNode = Node & { id: Scalars['ID']['output']; note?: Maybe; path?: Maybe; - user: PrivateUserNode; + user: PublicUserNode; }; export type ChapterBookmarkNodeConnection = { @@ -439,7 +439,7 @@ export type ContentBlockBookmarkNode = Node & { id: Scalars['ID']['output']; note?: Maybe; path?: Maybe; - user: PrivateUserNode; + user: PublicUserNode; uuid?: Maybe; }; @@ -770,7 +770,7 @@ export type HighlightNode = Node & { selectionLength: Scalars['Int']['output']; startPosition: Scalars['Int']['output']; text: Scalars['String']['output']; - user: PrivateUserNode; + user: PublicUserNode; }; export type HighlightableNode = ChapterNode | ContentBlockNode | InstrumentNode | ModuleNode; @@ -796,7 +796,7 @@ export type InstrumentBookmarkNode = Node & { instrument: InstrumentNode; note?: Maybe; path: Scalars['String']['output']; - user: PrivateUserNode; + user: PublicUserNode; uuid?: Maybe; }; @@ -899,7 +899,7 @@ export type ModuleBookmarkNode = { module: ModuleNode; note?: Maybe; path?: Maybe; - user: PrivateUserNode; + user: PublicUserNode; }; export type ModuleCategoryNode = Node & { @@ -1519,7 +1519,7 @@ export type ObjectiveNode = Node & { id: Scalars['ID']['output']; mine?: Maybe; order?: Maybe; - owner?: Maybe; + owner?: Maybe; pk?: Maybe; text: Scalars['String']['output']; userCreated?: Maybe; @@ -1655,11 +1655,10 @@ export type ProjectNode = Node & { /** The ID of the object */ id: Scalars['ID']['output']; objectives: Scalars['String']['output']; - owner?: Maybe; pk?: Maybe; schoolClass?: Maybe; slug: Scalars['String']['output']; - student: PrivateUserNode; + student?: Maybe; title: Scalars['String']['output']; }; @@ -2125,7 +2124,7 @@ export type StudentSubmissionNode = Node & { /** The ID of the object */ id: Scalars['ID']['output']; modified: Scalars['DateTime']['output']; - student: PrivateUserNode; + student: PublicUserNode; submissionFeedback?: Maybe; text: Scalars['String']['output']; }; @@ -2145,7 +2144,7 @@ export type SubmissionFeedbackNode = Node & { id: Scalars['ID']['output']; modified: Scalars['DateTime']['output']; studentSubmission: StudentSubmissionNode; - teacher: PrivateUserNode; + teacher: PublicUserNode; text: Scalars['String']['output']; }; @@ -2213,7 +2212,7 @@ export type SyncModuleVisibilityPayload = { export type TeamNode = Node & { __typename?: 'TeamNode'; code?: Maybe; - creator?: Maybe; + creator: PublicUserNode; /** The ID of the object */ id: Scalars['ID']['output']; isDeleted: Scalars['Boolean']['output']; @@ -2695,7 +2694,7 @@ export type ReadOnlyQueryQueryVariables = Exact<{ [key: string]: never; }>; export type ReadOnlyQueryQuery = { __typename?: 'Query', me?: { __typename?: 'PrivateUserNode', readOnly?: boolean | null, selectedClass?: { __typename?: 'SchoolClassNode', readOnly?: boolean | null } | null } | null }; -export type SubmissionPartsFragment = { __typename?: 'StudentSubmissionNode', id: string, text: string, final: boolean, document: string, submissionFeedback?: { __typename?: 'SubmissionFeedbackNode', id: string, text: string, teacher: { __typename?: 'PrivateUserNode', firstName: string, lastName: string } } | null } & { ' $fragmentName'?: 'SubmissionPartsFragment' }; +export type SubmissionPartsFragment = { __typename?: 'StudentSubmissionNode', id: string, text: string, final: boolean, document: string, submissionFeedback?: { __typename?: 'SubmissionFeedbackNode', id: string, text: string, teacher: { __typename?: 'PublicUserNode', firstName: string, lastName: string } } | null } & { ' $fragmentName'?: 'SubmissionPartsFragment' }; export type AssignmentPartsFragment = { __typename?: 'AssignmentNode', id: string, title: string, assignment: string, solution?: string | null, submission?: ( { __typename?: 'StudentSubmissionNode' } diff --git a/server/portfolio/schema.py b/server/portfolio/schema.py index a578e41f..afa4ee18 100644 --- a/server/portfolio/schema.py +++ b/server/portfolio/schema.py @@ -5,7 +5,7 @@ from graphene_django import DjangoObjectType from api.utils import get_by_id_or_slug from portfolio.models import Project, ProjectEntry -from users.models import Role, UserRole, User +from users.models import Role, UserRole from users.schema import PublicUserNode @@ -13,18 +13,18 @@ class ProjectEntryNode(DjangoObjectType): class Meta: model = ProjectEntry interfaces = (relay.Node,) - fields = ('description', 'document_url', 'project', 'created') + fields = ("description", "document_url", "project", "created") class ProjectNode(DjangoObjectType): pk = graphene.Int() entries_count = graphene.Int() entries = graphene.List(ProjectEntryNode) - owner = graphene.Field(PublicUserNode) + student = graphene.Field(PublicUserNode) class Meta: model = Project - filter_fields = ['slug', 'appearance'] + filter_fields = ["slug", "appearance"] interfaces = (relay.Node,) def resolve_pk(self, *args, **kwargs): @@ -45,11 +45,16 @@ class PortfolioQuery(object): def resolve_projects(self, info, **kwargs): user = info.context.user if user.is_superuser: - return Project.objects.all().order_by('-pk') + return Project.objects.all().order_by("-pk") - if UserRole.get_role_for_user(user).role == Role.objects.get_default_teacher_role(): - return Project.objects.filter(Q(student__school_classes__in=user.school_classes.all(), final=True) | - Q(student=user, final=False)).distinct() + if ( + UserRole.get_role_for_user(user).role + == Role.objects.get_default_teacher_role() + ): + return Project.objects.filter( + Q(student__school_classes__in=user.school_classes.all(), final=True) + | Q(student=user, final=False) + ).distinct() return Project.objects.filter(student=user) diff --git a/server/schema.graphql b/server/schema.graphql index 7c2bfde4..9d4755ab 100644 --- a/server/schema.graphql +++ b/server/schema.graphql @@ -170,7 +170,7 @@ type TopicNode implements Node { type HighlightNode implements Node { """The ID of the object""" id: ID! - user: PrivateUserNode! + user: PublicUserNode! page: HighlightableNode! contentIndex: Int contentUuid: UUID @@ -182,45 +182,195 @@ type HighlightNode implements Node { color: String! } -type PrivateUserNode implements Node { +type PublicUserNode implements Node { firstName: String! lastName: String! avatarUrl: String! + """The ID of the object""" + id: ID! + fullName: String! + isMe: Boolean +} + +union HighlightableNode = ContentBlockNode | InstrumentNode | ModuleNode | ChapterNode + +type ContentBlockNode implements Node & ContentBlockInterface { + title: String + """ - Erforderlich. 150 Zeichen oder weniger. Nur Buchstaben, Ziffern und @/./+/-/_. + Der Name der Seite, wie er in URLs angezeigt werden soll, z.B. http://domain.com/blog/[my-slug]/ """ - username: String! - lastModule: ModuleNode - lastModuleLevel: ModuleLevelNode - lastTopic: TopicNode - email: String! - onboardingVisited: Boolean! - team: TeamNode - schoolClasses: [SchoolClassNode] + slug: String! + hiddenFor: [SchoolClassNode] + visibleFor: [SchoolClassNode] + userCreated: Boolean! + contents: GenericStreamFieldType + type: String! + + """The ID of the object""" + id: ID! + mine: Boolean + bookmarks: [ContentBlockBookmarkNode] + originalCreator: PublicUserNode + instrumentCategory: InstrumentCategoryNode + path: String + highlights: [HighlightNode] +} + +interface ContentBlockInterface { + title: String + contents: GenericStreamFieldType + type: String! +} + +scalar GenericStreamFieldType + +type SchoolClassNode implements Node { + name: String! + code: String """The ID of the object""" id: ID! pk: Int - permissions: [String] - selectedClass: SchoolClassNode - expiryDate: String - isTeacher: Boolean - oldClasses: [SchoolClassNode] - recentModules( - offset: Int - before: String - after: String - first: Int - last: Int - recentModules: ID - - """Sortierung""" - orderBy: String - ): ModuleNodeConnection + members: [ClassMemberNode] readOnly: Boolean } +""" +We need to build this ourselves, because we want the active property on the node, because providing it on the +Connection or Edge for a UserNodeConnection is difficult. +""" +type ClassMemberNode { + user: PublicUserNode + active: Boolean + firstName: String + lastName: String + isTeacher: Boolean + id: ID + isMe: Boolean +} + +type ContentBlockBookmarkNode implements Node { + """The ID of the object""" + id: ID! + user: PublicUserNode! + note: NoteNode + uuid: UUID + contentBlock: ContentBlockNode! + path: String + content: String +} + +type NoteNode implements Node { + """The ID of the object""" + id: ID! + text: String! + contentblockbookmark: ContentBlockBookmarkNode + modulebookmark: ModuleBookmarkNode + chapterbookmark: ChapterBookmarkNode + instrumentbookmark: InstrumentBookmarkNode + highlight: HighlightNode + pk: Int +} + +type ModuleBookmarkNode { + id: ID! + user: PublicUserNode! + note: NoteNode + module: ModuleNode! + path: String + content: String +} + +type ChapterBookmarkNode implements Node { + """The ID of the object""" + id: ID! + user: PublicUserNode! + note: NoteNode + chapter: ChapterNode! + path: String + content: String +} + +type ChapterNode implements Node & ChapterInterface { + title: String + + """ + Der Name der Seite, wie er in URLs angezeigt werden soll, z.B. http://domain.com/blog/[my-slug]/ + """ + slug: String! + description: String + titleHiddenFor: [SchoolClassNode] + descriptionHiddenFor: [SchoolClassNode] + + """The ID of the object""" + id: ID! + bookmark: ChapterBookmarkNode + contentBlocks: [ContentBlockNode] + path: String + highlights: [HighlightNode] +} + +interface ChapterInterface { + description: String + title: String +} + +type InstrumentBookmarkNode implements Node { + """The ID of the object""" + id: ID! + user: PublicUserNode! + note: NoteNode + uuid: UUID + instrument: InstrumentNode! + path: String! + content: String +} + +""" +Leverages the internal Python implementation of UUID (uuid.UUID) to provide native UUID objects +in fields, resolvers and input. +""" +scalar UUID + +type InstrumentNode implements Node { + """Der Seitentitel, der öffentlich angezeigt werden soll""" + title: String! + + """ + Der Name der Seite, wie er in URLs angezeigt werden soll, z.B. http://domain.com/blog/[my-slug]/ + """ + slug: String! + intro: String! + contents: GenericStreamFieldType + + """The ID of the object""" + id: ID! + bookmarks: [InstrumentBookmarkNode] + type: InstrumentTypeNode + language: String + highlights: [HighlightNode!]! + path: String! +} + +type InstrumentTypeNode implements Node { + """The ID of the object""" + id: ID! + name: String! + category: InstrumentCategoryNode + type: String! +} + +type InstrumentCategoryNode implements Node { + """The ID of the object""" + id: ID! + name: String! + background: String! + foreground: String! + types: [InstrumentTypeNode] +} + type ModuleLevelNode implements Node { """The ID of the object""" id: ID! @@ -275,206 +425,6 @@ type ModuleNodeEdge { cursor: String! } -type TeamNode implements Node { - """The ID of the object""" - id: ID! - name: String! - isDeleted: Boolean! - code: String - creator: PrivateUserNode - members: [PublicUserNode] - pk: Int -} - -type PublicUserNode implements Node { - firstName: String! - lastName: String! - avatarUrl: String! - - """The ID of the object""" - id: ID! - fullName: String! - isMe: Boolean -} - -type SchoolClassNode implements Node { - name: String! - code: String - - """The ID of the object""" - id: ID! - pk: Int - members: [ClassMemberNode] - readOnly: Boolean -} - -""" -We need to build this ourselves, because we want the active property on the node, because providing it on the -Connection or Edge for a UserNodeConnection is difficult. -""" -type ClassMemberNode { - user: PublicUserNode - active: Boolean - firstName: String - lastName: String - isTeacher: Boolean - id: ID - isMe: Boolean -} - -union HighlightableNode = ContentBlockNode | InstrumentNode | ModuleNode | ChapterNode - -type ContentBlockNode implements Node & ContentBlockInterface { - title: String - - """ - Der Name der Seite, wie er in URLs angezeigt werden soll, z.B. http://domain.com/blog/[my-slug]/ - """ - slug: String! - hiddenFor: [SchoolClassNode] - visibleFor: [SchoolClassNode] - userCreated: Boolean! - contents: GenericStreamFieldType - type: String! - - """The ID of the object""" - id: ID! - mine: Boolean - bookmarks: [ContentBlockBookmarkNode] - originalCreator: PublicUserNode - instrumentCategory: InstrumentCategoryNode - path: String - highlights: [HighlightNode] -} - -interface ContentBlockInterface { - title: String - contents: GenericStreamFieldType - type: String! -} - -scalar GenericStreamFieldType - -type ContentBlockBookmarkNode implements Node { - """The ID of the object""" - id: ID! - user: PrivateUserNode! - note: NoteNode - uuid: UUID - contentBlock: ContentBlockNode! - path: String - content: String -} - -type NoteNode implements Node { - """The ID of the object""" - id: ID! - text: String! - contentblockbookmark: ContentBlockBookmarkNode - modulebookmark: ModuleBookmarkNode - chapterbookmark: ChapterBookmarkNode - instrumentbookmark: InstrumentBookmarkNode - highlight: HighlightNode - pk: Int -} - -type ModuleBookmarkNode { - id: ID! - user: PrivateUserNode! - note: NoteNode - module: ModuleNode! - path: String - content: String -} - -type ChapterBookmarkNode implements Node { - """The ID of the object""" - id: ID! - user: PrivateUserNode! - note: NoteNode - chapter: ChapterNode! - path: String - content: String -} - -type ChapterNode implements Node & ChapterInterface { - title: String - - """ - Der Name der Seite, wie er in URLs angezeigt werden soll, z.B. http://domain.com/blog/[my-slug]/ - """ - slug: String! - description: String - titleHiddenFor: [SchoolClassNode] - descriptionHiddenFor: [SchoolClassNode] - - """The ID of the object""" - id: ID! - bookmark: ChapterBookmarkNode - contentBlocks: [ContentBlockNode] - path: String - highlights: [HighlightNode] -} - -interface ChapterInterface { - description: String - title: String -} - -type InstrumentBookmarkNode implements Node { - """The ID of the object""" - id: ID! - user: PrivateUserNode! - note: NoteNode - uuid: UUID - instrument: InstrumentNode! - path: String! - content: String -} - -""" -Leverages the internal Python implementation of UUID (uuid.UUID) to provide native UUID objects -in fields, resolvers and input. -""" -scalar UUID - -type InstrumentNode implements Node { - """Der Seitentitel, der öffentlich angezeigt werden soll""" - title: String! - - """ - Der Name der Seite, wie er in URLs angezeigt werden soll, z.B. http://domain.com/blog/[my-slug]/ - """ - slug: String! - intro: String! - contents: GenericStreamFieldType - - """The ID of the object""" - id: ID! - bookmarks: [InstrumentBookmarkNode] - type: InstrumentTypeNode - language: String - highlights: [HighlightNode!]! - path: String! -} - -type InstrumentTypeNode implements Node { - """The ID of the object""" - id: ID! - name: String! - category: InstrumentCategoryNode - type: String! -} - -type InstrumentCategoryNode implements Node { - """The ID of the object""" - id: ID! - name: String! - background: String! - foreground: String! - types: [InstrumentTypeNode] -} - type ModuleCategoryNode implements Node { """The ID of the object""" id: ID! @@ -504,7 +454,7 @@ type AssignmentNode implements Node { assignment: String! solution: String deleted: Boolean! - owner: PrivateUserNode + owner: PublicUserNode module: ModuleNode! userCreated: Boolean! taskbaseId: String @@ -528,7 +478,7 @@ type StudentSubmissionNode implements Node { text: String! document: String! assignment: AssignmentNode! - student: PrivateUserNode! + student: PublicUserNode! final: Boolean! submissionFeedback: SubmissionFeedbackNode } @@ -537,7 +487,7 @@ type SubmissionFeedbackNode implements Node { created: DateTime! modified: DateTime! text: String! - teacher: PrivateUserNode! + teacher: PublicUserNode! studentSubmission: StudentSubmissionNode! final: Boolean! @@ -573,7 +523,7 @@ type ObjectiveNode implements Node { id: ID! text: String! group: ObjectiveGroupNode! - owner: PrivateUserNode + owner: PublicUserNode hiddenFor: [SchoolClassNode] visibleFor: [SchoolClassNode] order: Int @@ -697,7 +647,7 @@ type SnapshotChangesNode { type AnswerNode implements Node { """The ID of the object""" id: ID! - owner: PrivateUserNode! + owner: PublicUserNode! data: JSONString! survey: SurveyNode! pk: Int @@ -789,13 +739,12 @@ type ProjectNode implements Node { slug: String! objectives: String! appearance: String! - student: PrivateUserNode! + student: PublicUserNode final: Boolean! schoolClass: SchoolClassNode entries: [ProjectEntryNode] pk: Int entriesCount: Int - owner: PublicUserNode } type ProjectEntryNode implements Node { @@ -940,6 +889,55 @@ type CommentNode implements Node { id: ID! } +type PrivateUserNode implements Node { + """ + Erforderlich. 150 Zeichen oder weniger. Nur Buchstaben, Ziffern und @/./+/-/_. + """ + username: String! + firstName: String! + lastName: String! + lastModule: ModuleNode + lastModuleLevel: ModuleLevelNode + lastTopic: TopicNode + avatarUrl: String! + email: String! + onboardingVisited: Boolean! + team: TeamNode + schoolClasses: [SchoolClassNode] + + """The ID of the object""" + id: ID! + pk: Int + permissions: [String] + selectedClass: SchoolClassNode + expiryDate: String + isTeacher: Boolean + oldClasses: [SchoolClassNode] + recentModules( + offset: Int + before: String + after: String + first: Int + last: Int + recentModules: ID + + """Sortierung""" + orderBy: String + ): ModuleNodeConnection + readOnly: Boolean +} + +type TeamNode implements Node { + """The ID of the object""" + id: ID! + name: String! + isDeleted: Boolean! + code: String + creator: PublicUserNode + members: [PublicUserNode] + pk: Int +} + type PrivateUserNodeConnection { """Pagination data for this connection.""" pageInfo: PageInfo! diff --git a/server/users/schema/types.py b/server/users/schema/types.py index 79dfea2b..f3989aef 100644 --- a/server/users/schema/types.py +++ b/server/users/schema/types.py @@ -70,20 +70,6 @@ class TeamNode(DjangoObjectType): return self.members.all() -class PublicUserNode(DjangoObjectType): - full_name = graphene.String(required=True) - is_me = graphene.Boolean() - - class Meta: - model = User - only_fields = ["full_name", "first_name", "last_name", "avatar_url"] - interfaces = (relay.Node,) - - @staticmethod - def resolve_is_me(parent: User, info, **kwargs): - return info.context.user.pk == parent.pk - - class PrivateUserNode(DjangoObjectType): class Meta: model = User @@ -168,6 +154,20 @@ class PrivateUserNode(DjangoObjectType): return self.team +class PublicUserNode(DjangoObjectType): + full_name = graphene.String(required=True) + is_me = graphene.Boolean() + + class Meta: + model = User + only_fields = ["full_name", "first_name", "last_name", "avatar_url"] + interfaces = (relay.Node,) + + @staticmethod + def resolve_is_me(parent: User, info, **kwargs): + return info.context.user.pk == parent.pk + + class ClassMemberNode(ObjectType): """ We need to build this ourselves, because we want the active property on the node, because providing it on the