Add properties to models and schema

This commit is contained in:
Ramon Wenger 2024-02-27 16:25:33 +01:00
parent c6e6491ef9
commit 5954151e2e
14 changed files with 92 additions and 21 deletions

View File

@ -9,11 +9,12 @@ from wagtail.search import index
from core.constants import DEFAULT_RICH_TEXT_FEATURES
from django.utils.translation import gettext_lazy as _
from modelcluster.fields import ParentalKey
from core.mixins import GraphqlNodeMixin
@register_snippet
class Assignment(index.Indexed, TimeStampedModel):
class Assignment(index.Indexed, TimeStampedModel, GraphqlNodeMixin):
title = models.CharField(max_length=255)
assignment = RichTextField(features=DEFAULT_RICH_TEXT_FEATURES)
solution = RichTextField(null=True, blank=True, features=DEFAULT_RICH_TEXT_FEATURES)
@ -27,6 +28,10 @@ class Assignment(index.Indexed, TimeStampedModel):
user_created = models.BooleanField(default=False)
taskbase_id = models.CharField(max_length=255, null=True, blank=True)
@property
def route(self):
return f"module/{self.module.slug}#{self.graphql_id}"
search_fields = [
index.AutocompleteField("title"),
index.AutocompleteField("assignment"),

View File

@ -26,7 +26,7 @@ class StudentSubmissionNode(DjangoObjectType):
def resolve_submission_feedback(root: StudentSubmission, info, **kwargs):
user = info.context.user
if not hasattr(root, 'submission_feedback'):
if not hasattr(root, "submission_feedback"):
return None
# teacher path
@ -43,6 +43,7 @@ class StudentSubmissionNode(DjangoObjectType):
class AssignmentNode(DjangoObjectType):
submission = graphene.Field(StudentSubmissionNode)
submissions = graphene.List(StudentSubmissionNode)
path = graphene.String(required=True)
class Meta:
model = Assignment
@ -50,16 +51,27 @@ class AssignmentNode(DjangoObjectType):
interfaces = (relay.Node,)
def resolve_submission(self, info, **kwargs):
return self.submissions.filter(student=info.context.user).first() # returns None if it doesn't exist yet
return self.submissions.filter(
student=info.context.user
).first() # returns None if it doesn't exist yet
def resolve_submissions(self, info, **kwargs):
user = info.context.user
if user.has_perm('users.can_manage_school_class_content'):
return self.submissions.filter(student__in=user.users_in_active_school_class()).filter(final=True)
if user.has_perm("users.can_manage_school_class_content"):
return self.submissions.filter(
student__in=user.users_in_active_school_class()
).filter(final=True)
return []
def resolve_solution(self, info, **kwargs):
if (info.context.user.is_teacher() or are_solutions_enabled_for(info.context.user, self.module)) and self.solution is not None:
if (
info.context.user.is_teacher()
or are_solutions_enabled_for(info.context.user, self.module)
) and self.solution is not None:
return self.solution
else:
return None
@staticmethod
def resolve_path(root: Assignment, info, **kwargs):
return root.route

View File

@ -38,6 +38,10 @@ class Chapter(StrictHierarchyPage, GraphqlNodeMixin):
SchoolClass, related_name="hidden_chapter_descriptions"
)
@property
def route(self):
return f"module/{self.get_parent().slug}#{self.graphql_id}"
def sync_title_visibility(self, school_class_template, school_class_to_sync):
if (
self.title_hidden_for.filter(id=school_class_template.id).exists()

View File

@ -161,6 +161,10 @@ class ContentBlock(StrictHierarchyPage, GraphqlNodeMixin):
def module(self):
return self.get_parent().get_parent().specific
@property
def route(self): # path is probably used by treebeard
return f"module/{self.module.slug}#{self.graphql_id}"
# duplicate all attached Surveys and Assignments
def duplicate_attached_entities(self):
duplicate_entities_func = duplicate_entities_generator(self.module)

View File

@ -20,7 +20,9 @@ class ModuleLevel(models.Model):
filter_attribute_type = models.CharField(
max_length=16, choices=FILTER_ATTRIBUTE_TYPE, default=EXACT
)
order = models.PositiveIntegerField(null=False, blank=False, default=99, help_text='Order in the Dropdown List')
order = models.PositiveIntegerField(
null=False, blank=False, default=99, help_text="Order in the Dropdown List"
)
class Meta:
verbose_name_plural = _("module Levels")
@ -36,7 +38,9 @@ class ModuleCategory(models.Model):
filter_attribute_type = models.CharField(
max_length=16, choices=FILTER_ATTRIBUTE_TYPE, default=EXACT
)
order = models.PositiveIntegerField(null=False, blank=False, default=99, help_text='Order in the Dropdown List')
order = models.PositiveIntegerField(
null=False, blank=False, default=99, help_text="Order in the Dropdown List"
)
class Meta:
verbose_name = _("module type")
@ -120,6 +124,10 @@ class Module(StrictHierarchyPage):
def get_child_ids(self):
return self.get_children().values_list("id", flat=True)
@property
def route(self):
return f"module/{self.slug}"
def sync_from_school_class(self, school_class_template, school_class_to_sync):
# import here so we don't get a circular import error
from books.models import Chapter, ContentBlock

View File

@ -71,8 +71,7 @@ class ChapterNode(DjangoObjectType):
@staticmethod
def resolve_path(root: Chapter, info, **kwargs):
module = root.get_parent()
return f"module/{module.slug}#{root.graphql_id}"
return root.route
@staticmethod
def resolve_highlights(root: Chapter, info, **kwargs):

View File

@ -120,8 +120,7 @@ class ContentBlockNode(DjangoObjectType, HiddenAndVisibleForMixin):
@staticmethod
def resolve_path(root: ContentBlock, info, **kwargs):
module = root.get_parent().get_parent()
return f"module/{module.slug}#{root.graphql_id}"
return root.route
@staticmethod
def resolve_highlights(root: ContentBlock, info, **kwargs):

View File

@ -58,8 +58,11 @@ class ModuleNode(DjangoObjectType):
category = graphene.Field(ModuleCategoryNode)
language = graphene.String()
highlights = graphene.List("notes.schema.HighlightNode")
my_highlights = graphene.List("notes.schema.HighlightNode")
my_highlights = graphene.List(
graphene.NonNull("notes.schema.HighlightNode"), required=True
)
my_bookmarks = graphene.List("notes.schema.BookmarkNode")
path = graphene.String()
def resolve_chapters(self, info, **kwargs):
return Chapter.get_by_parent(self)
@ -134,7 +137,7 @@ class ModuleNode(DjangoObjectType):
@staticmethod
def resolve_my_highlights(root: Module, info, **kwargs):
# todo: is this too expensive, query-wise
pages = Page.objects.live().descendant_of(root)
pages = list(Page.objects.live().descendant_of(root)) + [root]
highlights = Highlight.objects.filter(user=info.context.user).filter(
page__in=pages
)
@ -161,6 +164,10 @@ class ModuleNode(DjangoObjectType):
+ list(content_block_bookmarks)
)
@staticmethod
def resolve_path(root: Module, info, **kwargs):
return root.route
class RecentModuleNode(DjangoObjectType):
class Meta:

View File

@ -19,7 +19,7 @@ class NotFound(graphene.ObjectType):
class TopicNode(DjangoObjectType):
pk = graphene.Int()
modules = graphene.List("books.schema.nodes.ModuleNode")
modules = graphene.List(graphene.NonNull("books.schema.nodes.ModuleNode"))
highlights = graphene.List("notes.schema.HighlightNode")
class Meta:

View File

@ -32,6 +32,7 @@ class NoteNode(DjangoObjectType):
class ContentBlockBookmarkNode(DjangoObjectType):
uuid = graphene.UUID()
note = graphene.Field(NoteNode)
path = graphene.String()
class Meta:
model = ContentBlockBookmark
@ -39,17 +40,27 @@ class ContentBlockBookmarkNode(DjangoObjectType):
filter_fields = []
interfaces = (relay.Node,)
@staticmethod
def resolve_path(root: ContentBlockBookmark, info, **kwargs):
return root.content_block.route
class ModuleBookmarkNode(DjangoObjectType):
note = graphene.Field(NoteNode)
path = graphene.String()
class Meta:
model = ModuleBookmark
fields = "__all__"
@staticmethod
def resolve_path(root: ModuleBookmark, info, **kwargs):
return root.module.route
class ChapterBookmarkNode(DjangoObjectType):
note = graphene.Field(NoteNode)
path = graphene.String()
class Meta:
model = ChapterBookmark
@ -57,6 +68,10 @@ class ChapterBookmarkNode(DjangoObjectType):
filter_fields = []
interfaces = (relay.Node,)
@staticmethod
def resolve_path(root: ChapterBookmark, info, **kwargs):
return root.chapter.route
class InstrumentBookmarkNode(DjangoObjectType):
uuid = graphene.UUID()

View File

@ -92,6 +92,7 @@ type SurveyNode implements Node {
answers(offset: Int, before: String, after: String, first: Int, last: Int): AnswerNodeConnection!
pk: Int
answer: AnswerNode
path: String!
}
type ModuleNode implements ModuleInterface {
@ -130,8 +131,9 @@ type ModuleNode implements ModuleInterface {
snapshots: [SnapshotNode]
language: String
highlights: [HighlightNode]
myHighlights: [HighlightNode]
myHighlights: [HighlightNode!]!
myBookmarks: [BookmarkNode]
path: String
}
interface ModuleInterface {
@ -161,7 +163,7 @@ type TopicNode implements Node {
"""The ID of the object"""
id: ID!
pk: Int
modules: [ModuleNode]
modules: [ModuleNode!]
highlights: [HighlightNode]
}
@ -360,6 +362,7 @@ type ContentBlockBookmarkNode implements Node {
note: NoteNode
uuid: UUID
contentBlock: ContentBlockNode!
path: String
}
type NoteNode implements Node {
@ -379,6 +382,7 @@ type ModuleBookmarkNode {
user: PrivateUserNode!
note: NoteNode
module: ModuleNode!
path: String
}
type ChapterBookmarkNode implements Node {
@ -387,6 +391,7 @@ type ChapterBookmarkNode implements Node {
user: PrivateUserNode!
note: NoteNode
chapter: ChapterNode!
path: String
}
type ChapterNode implements Node & ChapterInterface {
@ -499,6 +504,7 @@ type AssignmentNode implements Node {
taskbaseId: String
submissions: [StudentSubmissionNode]
submission: StudentSubmissionNode
path: String!
}
"""
@ -963,7 +969,7 @@ type InstrumentNodeEdge {
}
type ActivityNode {
topics: [TopicNode]
topics: [TopicNode!]
}
"""Debugging information for the current query."""

View File

@ -5,9 +5,11 @@ from wagtail.snippets.models import register_snippet
from wagtail.search import index
from modelcluster.fields import ParentalKey
from core.mixins import GraphqlNodeMixin
@register_snippet
class Survey(models.Model, index.Indexed):
class Survey(models.Model, index.Indexed, GraphqlNodeMixin):
title = models.CharField(max_length=255)
module = models.ForeignKey(
"books.Module",
@ -18,6 +20,10 @@ class Survey(models.Model, index.Indexed):
)
data = JSONField()
@property
def route(self):
return f"module/{self.module.slug}#{self.graphql_id}"
search_fields = [
index.AutocompleteField("title"),
index.AutocompleteField("module__meta_title"),

View File

@ -23,6 +23,7 @@ class AnswerNode(DjangoObjectType):
class SurveyNode(DjangoObjectType):
pk = graphene.Int()
answer = graphene.Field(AnswerNode)
path = graphene.String(required=True)
class Meta:
model = Survey
@ -39,6 +40,11 @@ class SurveyNode(DjangoObjectType):
except Answer.DoesNotExist:
return None
@staticmethod
def resolve_path(root: Survey, info, **kwargs):
return root.route
class SurveysQuery(object):
survey = graphene.Field(SurveyNode, id=graphene.ID())
surveys = DjangoFilterConnectionField(SurveyNode)

View File

@ -242,4 +242,4 @@ class UpdateError(graphene.ObjectType):
class ActivityNode(graphene.ObjectType):
topics = graphene.List("books.schema.nodes.TopicNode")
topics = graphene.List(graphene.NonNull("books.schema.nodes.TopicNode"))