Add query for activities on server

This commit is contained in:
Ramon Wenger 2024-02-26 10:48:40 +01:00
parent c7c406f0ba
commit 799e60e63a
6 changed files with 334 additions and 266 deletions

View File

@ -4,7 +4,9 @@ from graphene import relay
from graphene_django.debug import DjangoDebug
# noinspection PyUnresolvedReferences
from api import graphene_wagtail # Keep this import exactly here, it's necessary for StreamField conversion
from api import (
graphene_wagtail,
) # Keep this import exactly here, it's necessary for StreamField conversion
from assignments.schema.mutations import AssignmentMutations
from assignments.schema.queries import AssignmentsQuery, StudentSubmissionQuery
from basicknowledge.queries import InstrumentQuery
@ -25,20 +27,42 @@ from rooms.schema import RoomsQuery, ModuleRoomsQuery
from users.schema import AllUsersQuery, UsersQuery, ProfileMutations
class Query(UsersQuery, AllUsersQuery, ModuleRoomsQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery,
StudentSubmissionQuery, InstrumentQuery, PortfolioQuery, SurveysQuery, AllNewsTeasersQuery,
graphene.ObjectType):
class Query(
UsersQuery,
AllUsersQuery,
ModuleRoomsQuery,
RoomsQuery,
ObjectivesQuery,
BookQuery,
AssignmentsQuery,
StudentSubmissionQuery,
InstrumentQuery,
PortfolioQuery,
SurveysQuery,
AllNewsTeasersQuery,
graphene.ObjectType,
):
node = relay.Node.Field()
if settings.DEBUG:
debug = graphene.Field(DjangoDebug, name='_debug')
debug = graphene.Field(DjangoDebug, name="_debug")
class Mutation(BookMutations, RoomMutations, AssignmentMutations, ObjectiveMutations, OauthMutations,
PortfolioMutations, ProfileMutations, SurveyMutations, NoteMutations, SpellCheckMutations,
graphene.ObjectType):
class Mutation(
BookMutations,
RoomMutations,
AssignmentMutations,
ObjectiveMutations,
OauthMutations,
PortfolioMutations,
ProfileMutations,
SurveyMutations,
NoteMutations,
SpellCheckMutations,
graphene.ObjectType,
):
if settings.DEBUG:
debug = graphene.Field(DjangoDebug, name='_debug')
debug = graphene.Field(DjangoDebug, name="_debug")
schema = graphene.Schema(query=Query, mutation=Mutation)

View File

@ -1,4 +1,5 @@
import graphene
from wagtail.models import Page
from assignments.models import StudentSubmission
from assignments.schema.types import AssignmentNode, StudentSubmissionNode
from books.models import Chapter, ContentBlock, Module, RecentModule
@ -10,7 +11,12 @@ from django.db.models import Q
from graphene import relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from notes.models import ChapterBookmark, ContentBlockBookmark, ModuleBookmark
from notes.models import (
ChapterBookmark,
ContentBlockBookmark,
Highlight,
ModuleBookmark,
)
from objectives.schema import ObjectiveGroupNode
from surveys.models import Answer
from surveys.schema import AnswerNode
@ -57,6 +63,7 @@ class ModuleNode(DjangoObjectType):
category = graphene.Field(ModuleCategoryNode)
language = graphene.String()
highlights = graphene.List("notes.schema.HighlightNode")
all_highlights = graphene.List("notes.schema.HighlightNode")
def resolve_chapters(self, info, **kwargs):
return Chapter.get_by_parent(self)
@ -128,6 +135,15 @@ class ModuleNode(DjangoObjectType):
def resolve_highlights(root: Module, info, **kwargs):
return root.highlights.filter(user=info.context.user)
@staticmethod
def resolve_all_highlights(root: Module, info, **kwargs):
# todo: is this too expensive, query-wise
pages = Page.objects.live().descendant_of(root)
highlights = Highlight.objects.filter(user=info.context.user).filter(
page__in=pages
)
return highlights
class RecentModuleNode(DjangoObjectType):
class Meta:

View File

@ -1,10 +1,13 @@
import graphene
from wagtail.models import Page
from books.models import Module, Topic
from books.schema.nodes import ModuleNode
from graphene import relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from notes.models import Highlight
class NotFoundFailure:
reason = "Not Found"
@ -16,7 +19,8 @@ class NotFound(graphene.ObjectType):
class TopicNode(DjangoObjectType):
pk = graphene.Int()
modules = DjangoFilterConnectionField("books.schema.nodes.ModuleNode")
modules = graphene.List("books.schema.nodes.ModuleNode")
highlights = graphene.List("notes.schema.HighlightNode")
class Meta:
model = Topic
@ -45,6 +49,12 @@ class TopicNode(DjangoObjectType):
ids += list(translation.get_child_ids())
return Module.objects.filter(id__in=ids).live()
@staticmethod
def resolve_highlights(root: Topic, info, **kwargs):
# todo: is this too expensive, query-wise?
pages = Page.objects.live().descendant_of(root)
return Highlight.objects.filter(user=info.context.user).filter(page__in=pages)
class TopicOr404Node(graphene.Union):
class Meta:

View File

@ -53,6 +53,8 @@ type Query {
allUsers(offset: Int, before: String, after: String, first: Int, last: Int, username: String, email: String): PrivateUserNodeConnection
myActivity(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, slug_Icontains: String, slug_In: [String], title: String, title_Icontains: String, title_In: [String]): ModuleNodeConnection
myInstrumentActivity(offset: Int, before: String, after: String, first: Int, last: Int, slug: String): InstrumentNodeConnection
myActivities: ActivityNode
something: String!
_debug: DjangoDebug
}
@ -129,6 +131,7 @@ type ModuleNode implements ModuleInterface {
snapshots: [SnapshotNode]
language: String
highlights: [HighlightNode]
allHighlights: [HighlightNode]
}
interface ModuleInterface {
@ -158,107 +161,25 @@ type TopicNode implements Node {
"""The ID of the object"""
id: ID!
pk: Int
modules(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, slug_Icontains: String, slug_In: [String], title: String, title_Icontains: String, title_In: [String]): ModuleNodeConnection
modules: [ModuleNode]
highlights: [HighlightNode]
}
type ModuleNodeConnection {
"""Pagination data for this connection."""
pageInfo: PageInfo!
"""Contains the nodes in this connection."""
edges: [ModuleNodeEdge]!
}
"""
The Relay compliant `PageInfo` type, containing data necessary to paginate this connection.
"""
type PageInfo {
"""When paginating forwards, are there more items?"""
hasNextPage: Boolean!
"""When paginating backwards, are there more items?"""
hasPreviousPage: Boolean!
"""When paginating backwards, the cursor to continue."""
startCursor: String
"""When paginating forwards, the cursor to continue."""
endCursor: String
}
"""A Relay edge containing a `ModuleNode` and its cursor."""
type ModuleNodeEdge {
"""The item at the end of the edge"""
node: ModuleNode
"""A cursor for use in pagination"""
cursor: String!
}
type ModuleLevelNode implements Node {
type HighlightNode implements Node {
"""The ID of the object"""
id: ID!
name: String!
filterAttributeType: BooksModuleLevelFilterAttributeTypeChoices!
"""Order in the Dropdown List"""
order: Int!
moduleSet(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, slug_Icontains: String, slug_In: [String], title: String, title_Icontains: String, title_In: [String]): ModuleNodeConnection!
user: PrivateUserNode!
page: HighlightableNode
contentIndex: Int
contentUuid: UUID
paragraphIndex: Int!
startPosition: Int!
selectionLength: Int!
text: String!
note: NoteNode
color: String!
}
"""An enumeration."""
enum BooksModuleLevelFilterAttributeTypeChoices {
"""All"""
ALL
"""Exact"""
EXACT
}
type ModuleCategoryNode implements Node {
"""The ID of the object"""
id: ID!
name: String!
filterAttributeType: BooksModuleCategoryFilterAttributeTypeChoices!
"""Order in the Dropdown List"""
order: Int!
moduleSet(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, slug_Icontains: String, slug_In: [String], title: String, title_Icontains: String, title_In: [String]): ModuleNodeConnection!
}
"""An enumeration."""
enum BooksModuleCategoryFilterAttributeTypeChoices {
"""All"""
ALL
"""Exact"""
EXACT
}
type AssignmentNode implements Node {
"""The ID of the object"""
id: ID!
created: DateTime!
modified: DateTime!
title: String!
assignment: String!
solution: String
deleted: Boolean!
owner: PrivateUserNode
module: ModuleNode!
userCreated: Boolean!
taskbaseId: String
submissions: [StudentSubmissionNode]
submission: StudentSubmissionNode
}
"""
The `DateTime` scalar type represents a DateTime
value as specified by
[iso8601](https://en.wikipedia.org/wiki/ISO_8601).
"""
scalar DateTime
type PrivateUserNode implements Node {
firstName: String!
lastName: String!
@ -298,6 +219,60 @@ type PrivateUserNode implements Node {
readOnly: Boolean
}
type ModuleLevelNode implements Node {
"""The ID of the object"""
id: ID!
name: String!
filterAttributeType: BooksModuleLevelFilterAttributeTypeChoices!
"""Order in the Dropdown List"""
order: Int!
moduleSet(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, slug_Icontains: String, slug_In: [String], title: String, title_Icontains: String, title_In: [String]): ModuleNodeConnection!
}
"""An enumeration."""
enum BooksModuleLevelFilterAttributeTypeChoices {
"""All"""
ALL
"""Exact"""
EXACT
}
type ModuleNodeConnection {
"""Pagination data for this connection."""
pageInfo: PageInfo!
"""Contains the nodes in this connection."""
edges: [ModuleNodeEdge]!
}
"""
The Relay compliant `PageInfo` type, containing data necessary to paginate this connection.
"""
type PageInfo {
"""When paginating forwards, are there more items?"""
hasNextPage: Boolean!
"""When paginating backwards, are there more items?"""
hasPreviousPage: Boolean!
"""When paginating backwards, the cursor to continue."""
startCursor: String
"""When paginating forwards, the cursor to continue."""
endCursor: String
}
"""A Relay edge containing a `ModuleNode` and its cursor."""
type ModuleNodeEdge {
"""The item at the end of the edge"""
node: ModuleNode
"""A cursor for use in pagination"""
cursor: String!
}
type TeamNode implements Node {
"""The ID of the object"""
id: ID!
@ -345,6 +320,194 @@ type ClassMemberNode {
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!
}
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!
}
type ChapterBookmarkNode implements Node {
"""The ID of the object"""
id: ID!
user: PrivateUserNode!
note: NoteNode
chapter: ChapterNode!
}
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!
}
"""
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]
}
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!
name: String!
filterAttributeType: BooksModuleCategoryFilterAttributeTypeChoices!
"""Order in the Dropdown List"""
order: Int!
moduleSet(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, slug_Icontains: String, slug_In: [String], title: String, title_Icontains: String, title_In: [String]): ModuleNodeConnection!
}
"""An enumeration."""
enum BooksModuleCategoryFilterAttributeTypeChoices {
"""All"""
ALL
"""Exact"""
EXACT
}
type AssignmentNode implements Node {
"""The ID of the object"""
id: ID!
created: DateTime!
modified: DateTime!
title: String!
assignment: String!
solution: String
deleted: Boolean!
owner: PrivateUserNode
module: ModuleNode!
userCreated: Boolean!
taskbaseId: String
submissions: [StudentSubmissionNode]
submission: StudentSubmissionNode
}
"""
The `DateTime` scalar type represents a DateTime
value as specified by
[iso8601](https://en.wikipedia.org/wiki/ISO_8601).
"""
scalar DateTime
type StudentSubmissionNode implements Node {
"""The ID of the object"""
id: ID!
@ -453,11 +616,6 @@ type SnapshotChapterNode implements Node & ChapterInterface {
titleHidden: Boolean
}
interface ChapterInterface {
description: String
title: String
}
type SnapshotContentBlockNode implements Node & ContentBlockInterface {
"""The ID of the object"""
id: ID!
@ -467,14 +625,6 @@ type SnapshotContentBlockNode implements Node & ContentBlockInterface {
hidden: Boolean
}
interface ContentBlockInterface {
title: String
contents: GenericStreamFieldType
type: String!
}
scalar GenericStreamFieldType
type ContentBlockNodeConnection {
"""Pagination data for this connection."""
pageInfo: PageInfo!
@ -492,152 +642,6 @@ type ContentBlockNodeEdge {
cursor: String!
}
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]
}
type ContentBlockBookmarkNode implements Node {
"""The ID of the object"""
id: ID!
user: PrivateUserNode!
note: NoteNode
uuid: UUID
contentBlock: ContentBlockNode!
}
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!
}
type ChapterBookmarkNode implements Node {
"""The ID of the object"""
id: ID!
user: PrivateUserNode!
note: NoteNode
chapter: ChapterNode!
}
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]
}
type HighlightNode implements Node {
"""The ID of the object"""
id: ID!
user: PrivateUserNode!
page: HighlightableNode
contentIndex: Int
contentUuid: UUID
paragraphIndex: Int!
startPosition: Int!
selectionLength: Int!
text: String!
note: NoteNode
color: String!
}
union HighlightableNode = ContentBlockNode | InstrumentNode | ModuleNode | ChapterNode
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]
}
type InstrumentBookmarkNode implements Node {
"""The ID of the object"""
id: ID!
user: PrivateUserNode!
note: NoteNode
uuid: UUID
instrument: InstrumentNode!
}
"""
Leverages the internal Python implementation of UUID (uuid.UUID) to provide native UUID objects
in fields, resolvers and input.
"""
scalar UUID
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 SnapshotObjectiveGroupNode implements Node {
"""The ID of the object"""
id: ID!
@ -973,6 +977,10 @@ type InstrumentNodeEdge {
cursor: String!
}
type ActivityNode {
topics: [TopicNode]
}
"""Debugging information for the current query."""
type DjangoDebug {
"""Executed SQL queries for this API query."""

View File

@ -2,9 +2,10 @@ import graphene
from basicknowledge.models import BasicKnowledge
from books.models import Module
from graphene_django.filter import DjangoFilterConnectionField
from books.models.topic import Topic
from users.models import User
from .types import PrivateUserNode
from .types import ActivityNode, PrivateUserNode
class UsersQuery(object):
@ -14,6 +15,7 @@ class UsersQuery(object):
my_instrument_activity = DjangoFilterConnectionField(
"basicknowledge.queries.InstrumentNode"
)
my_activities = graphene.Field(ActivityNode)
def resolve_me(self, info, **kwargs):
return info.context.user
@ -30,6 +32,10 @@ class UsersQuery(object):
def resolve_my_instrument_activity(self, info, **kwargs):
return BasicKnowledge.objects.filter(instrumentbookmark__user=info.context.user)
def resolve_my_activities(self, info, **kwargs):
topics = Topic.objects.all()
return {"topics": topics}
class AllUsersQuery(object):
me = graphene.Field(PrivateUserNode)

View File

@ -239,3 +239,7 @@ class FieldError(graphene.ObjectType):
class UpdateError(graphene.ObjectType):
field = graphene.String()
errors = graphene.List(FieldError)
class ActivityNode(graphene.ObjectType):
topics = graphene.List("books.schema.nodes.TopicNode")