Add properties to models and schema
This commit is contained in:
parent
c6e6491ef9
commit
5954151e2e
|
|
@ -9,11 +9,12 @@ from wagtail.search import index
|
||||||
|
|
||||||
from core.constants import DEFAULT_RICH_TEXT_FEATURES
|
from core.constants import DEFAULT_RICH_TEXT_FEATURES
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from modelcluster.fields import ParentalKey
|
|
||||||
|
from core.mixins import GraphqlNodeMixin
|
||||||
|
|
||||||
|
|
||||||
@register_snippet
|
@register_snippet
|
||||||
class Assignment(index.Indexed, TimeStampedModel):
|
class Assignment(index.Indexed, TimeStampedModel, GraphqlNodeMixin):
|
||||||
title = models.CharField(max_length=255)
|
title = models.CharField(max_length=255)
|
||||||
assignment = RichTextField(features=DEFAULT_RICH_TEXT_FEATURES)
|
assignment = RichTextField(features=DEFAULT_RICH_TEXT_FEATURES)
|
||||||
solution = RichTextField(null=True, blank=True, 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)
|
user_created = models.BooleanField(default=False)
|
||||||
taskbase_id = models.CharField(max_length=255, null=True, blank=True)
|
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 = [
|
search_fields = [
|
||||||
index.AutocompleteField("title"),
|
index.AutocompleteField("title"),
|
||||||
index.AutocompleteField("assignment"),
|
index.AutocompleteField("assignment"),
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ class StudentSubmissionNode(DjangoObjectType):
|
||||||
def resolve_submission_feedback(root: StudentSubmission, info, **kwargs):
|
def resolve_submission_feedback(root: StudentSubmission, info, **kwargs):
|
||||||
user = info.context.user
|
user = info.context.user
|
||||||
|
|
||||||
if not hasattr(root, 'submission_feedback'):
|
if not hasattr(root, "submission_feedback"):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# teacher path
|
# teacher path
|
||||||
|
|
@ -43,6 +43,7 @@ class StudentSubmissionNode(DjangoObjectType):
|
||||||
class AssignmentNode(DjangoObjectType):
|
class AssignmentNode(DjangoObjectType):
|
||||||
submission = graphene.Field(StudentSubmissionNode)
|
submission = graphene.Field(StudentSubmissionNode)
|
||||||
submissions = graphene.List(StudentSubmissionNode)
|
submissions = graphene.List(StudentSubmissionNode)
|
||||||
|
path = graphene.String(required=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Assignment
|
model = Assignment
|
||||||
|
|
@ -50,16 +51,27 @@ class AssignmentNode(DjangoObjectType):
|
||||||
interfaces = (relay.Node,)
|
interfaces = (relay.Node,)
|
||||||
|
|
||||||
def resolve_submission(self, info, **kwargs):
|
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):
|
def resolve_submissions(self, info, **kwargs):
|
||||||
user = info.context.user
|
user = info.context.user
|
||||||
if user.has_perm('users.can_manage_school_class_content'):
|
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 self.submissions.filter(
|
||||||
|
student__in=user.users_in_active_school_class()
|
||||||
|
).filter(final=True)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def resolve_solution(self, info, **kwargs):
|
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
|
return self.solution
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_path(root: Assignment, info, **kwargs):
|
||||||
|
return root.route
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,10 @@ class Chapter(StrictHierarchyPage, GraphqlNodeMixin):
|
||||||
SchoolClass, related_name="hidden_chapter_descriptions"
|
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):
|
def sync_title_visibility(self, school_class_template, school_class_to_sync):
|
||||||
if (
|
if (
|
||||||
self.title_hidden_for.filter(id=school_class_template.id).exists()
|
self.title_hidden_for.filter(id=school_class_template.id).exists()
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,10 @@ class ContentBlock(StrictHierarchyPage, GraphqlNodeMixin):
|
||||||
def module(self):
|
def module(self):
|
||||||
return self.get_parent().get_parent().specific
|
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
|
# duplicate all attached Surveys and Assignments
|
||||||
def duplicate_attached_entities(self):
|
def duplicate_attached_entities(self):
|
||||||
duplicate_entities_func = duplicate_entities_generator(self.module)
|
duplicate_entities_func = duplicate_entities_generator(self.module)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,9 @@ class ModuleLevel(models.Model):
|
||||||
filter_attribute_type = models.CharField(
|
filter_attribute_type = models.CharField(
|
||||||
max_length=16, choices=FILTER_ATTRIBUTE_TYPE, default=EXACT
|
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:
|
class Meta:
|
||||||
verbose_name_plural = _("module Levels")
|
verbose_name_plural = _("module Levels")
|
||||||
|
|
@ -36,7 +38,9 @@ class ModuleCategory(models.Model):
|
||||||
filter_attribute_type = models.CharField(
|
filter_attribute_type = models.CharField(
|
||||||
max_length=16, choices=FILTER_ATTRIBUTE_TYPE, default=EXACT
|
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:
|
class Meta:
|
||||||
verbose_name = _("module type")
|
verbose_name = _("module type")
|
||||||
|
|
@ -120,6 +124,10 @@ class Module(StrictHierarchyPage):
|
||||||
def get_child_ids(self):
|
def get_child_ids(self):
|
||||||
return self.get_children().values_list("id", flat=True)
|
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):
|
def sync_from_school_class(self, school_class_template, school_class_to_sync):
|
||||||
# import here so we don't get a circular import error
|
# import here so we don't get a circular import error
|
||||||
from books.models import Chapter, ContentBlock
|
from books.models import Chapter, ContentBlock
|
||||||
|
|
|
||||||
|
|
@ -71,8 +71,7 @@ class ChapterNode(DjangoObjectType):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_path(root: Chapter, info, **kwargs):
|
def resolve_path(root: Chapter, info, **kwargs):
|
||||||
module = root.get_parent()
|
return root.route
|
||||||
return f"module/{module.slug}#{root.graphql_id}"
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_highlights(root: Chapter, info, **kwargs):
|
def resolve_highlights(root: Chapter, info, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -120,8 +120,7 @@ class ContentBlockNode(DjangoObjectType, HiddenAndVisibleForMixin):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_path(root: ContentBlock, info, **kwargs):
|
def resolve_path(root: ContentBlock, info, **kwargs):
|
||||||
module = root.get_parent().get_parent()
|
return root.route
|
||||||
return f"module/{module.slug}#{root.graphql_id}"
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_highlights(root: ContentBlock, info, **kwargs):
|
def resolve_highlights(root: ContentBlock, info, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,11 @@ class ModuleNode(DjangoObjectType):
|
||||||
category = graphene.Field(ModuleCategoryNode)
|
category = graphene.Field(ModuleCategoryNode)
|
||||||
language = graphene.String()
|
language = graphene.String()
|
||||||
highlights = graphene.List("notes.schema.HighlightNode")
|
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")
|
my_bookmarks = graphene.List("notes.schema.BookmarkNode")
|
||||||
|
path = graphene.String()
|
||||||
|
|
||||||
def resolve_chapters(self, info, **kwargs):
|
def resolve_chapters(self, info, **kwargs):
|
||||||
return Chapter.get_by_parent(self)
|
return Chapter.get_by_parent(self)
|
||||||
|
|
@ -134,7 +137,7 @@ class ModuleNode(DjangoObjectType):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_my_highlights(root: Module, info, **kwargs):
|
def resolve_my_highlights(root: Module, info, **kwargs):
|
||||||
# todo: is this too expensive, query-wise
|
# 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(
|
highlights = Highlight.objects.filter(user=info.context.user).filter(
|
||||||
page__in=pages
|
page__in=pages
|
||||||
)
|
)
|
||||||
|
|
@ -161,6 +164,10 @@ class ModuleNode(DjangoObjectType):
|
||||||
+ list(content_block_bookmarks)
|
+ list(content_block_bookmarks)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_path(root: Module, info, **kwargs):
|
||||||
|
return root.route
|
||||||
|
|
||||||
|
|
||||||
class RecentModuleNode(DjangoObjectType):
|
class RecentModuleNode(DjangoObjectType):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ class NotFound(graphene.ObjectType):
|
||||||
|
|
||||||
class TopicNode(DjangoObjectType):
|
class TopicNode(DjangoObjectType):
|
||||||
pk = graphene.Int()
|
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")
|
highlights = graphene.List("notes.schema.HighlightNode")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ class NoteNode(DjangoObjectType):
|
||||||
class ContentBlockBookmarkNode(DjangoObjectType):
|
class ContentBlockBookmarkNode(DjangoObjectType):
|
||||||
uuid = graphene.UUID()
|
uuid = graphene.UUID()
|
||||||
note = graphene.Field(NoteNode)
|
note = graphene.Field(NoteNode)
|
||||||
|
path = graphene.String()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ContentBlockBookmark
|
model = ContentBlockBookmark
|
||||||
|
|
@ -39,17 +40,27 @@ class ContentBlockBookmarkNode(DjangoObjectType):
|
||||||
filter_fields = []
|
filter_fields = []
|
||||||
interfaces = (relay.Node,)
|
interfaces = (relay.Node,)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_path(root: ContentBlockBookmark, info, **kwargs):
|
||||||
|
return root.content_block.route
|
||||||
|
|
||||||
|
|
||||||
class ModuleBookmarkNode(DjangoObjectType):
|
class ModuleBookmarkNode(DjangoObjectType):
|
||||||
note = graphene.Field(NoteNode)
|
note = graphene.Field(NoteNode)
|
||||||
|
path = graphene.String()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ModuleBookmark
|
model = ModuleBookmark
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_path(root: ModuleBookmark, info, **kwargs):
|
||||||
|
return root.module.route
|
||||||
|
|
||||||
|
|
||||||
class ChapterBookmarkNode(DjangoObjectType):
|
class ChapterBookmarkNode(DjangoObjectType):
|
||||||
note = graphene.Field(NoteNode)
|
note = graphene.Field(NoteNode)
|
||||||
|
path = graphene.String()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ChapterBookmark
|
model = ChapterBookmark
|
||||||
|
|
@ -57,6 +68,10 @@ class ChapterBookmarkNode(DjangoObjectType):
|
||||||
filter_fields = []
|
filter_fields = []
|
||||||
interfaces = (relay.Node,)
|
interfaces = (relay.Node,)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_path(root: ChapterBookmark, info, **kwargs):
|
||||||
|
return root.chapter.route
|
||||||
|
|
||||||
|
|
||||||
class InstrumentBookmarkNode(DjangoObjectType):
|
class InstrumentBookmarkNode(DjangoObjectType):
|
||||||
uuid = graphene.UUID()
|
uuid = graphene.UUID()
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,7 @@ type SurveyNode implements Node {
|
||||||
answers(offset: Int, before: String, after: String, first: Int, last: Int): AnswerNodeConnection!
|
answers(offset: Int, before: String, after: String, first: Int, last: Int): AnswerNodeConnection!
|
||||||
pk: Int
|
pk: Int
|
||||||
answer: AnswerNode
|
answer: AnswerNode
|
||||||
|
path: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModuleNode implements ModuleInterface {
|
type ModuleNode implements ModuleInterface {
|
||||||
|
|
@ -130,8 +131,9 @@ type ModuleNode implements ModuleInterface {
|
||||||
snapshots: [SnapshotNode]
|
snapshots: [SnapshotNode]
|
||||||
language: String
|
language: String
|
||||||
highlights: [HighlightNode]
|
highlights: [HighlightNode]
|
||||||
myHighlights: [HighlightNode]
|
myHighlights: [HighlightNode!]!
|
||||||
myBookmarks: [BookmarkNode]
|
myBookmarks: [BookmarkNode]
|
||||||
|
path: String
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ModuleInterface {
|
interface ModuleInterface {
|
||||||
|
|
@ -161,7 +163,7 @@ type TopicNode implements Node {
|
||||||
"""The ID of the object"""
|
"""The ID of the object"""
|
||||||
id: ID!
|
id: ID!
|
||||||
pk: Int
|
pk: Int
|
||||||
modules: [ModuleNode]
|
modules: [ModuleNode!]
|
||||||
highlights: [HighlightNode]
|
highlights: [HighlightNode]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -360,6 +362,7 @@ type ContentBlockBookmarkNode implements Node {
|
||||||
note: NoteNode
|
note: NoteNode
|
||||||
uuid: UUID
|
uuid: UUID
|
||||||
contentBlock: ContentBlockNode!
|
contentBlock: ContentBlockNode!
|
||||||
|
path: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type NoteNode implements Node {
|
type NoteNode implements Node {
|
||||||
|
|
@ -379,6 +382,7 @@ type ModuleBookmarkNode {
|
||||||
user: PrivateUserNode!
|
user: PrivateUserNode!
|
||||||
note: NoteNode
|
note: NoteNode
|
||||||
module: ModuleNode!
|
module: ModuleNode!
|
||||||
|
path: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChapterBookmarkNode implements Node {
|
type ChapterBookmarkNode implements Node {
|
||||||
|
|
@ -387,6 +391,7 @@ type ChapterBookmarkNode implements Node {
|
||||||
user: PrivateUserNode!
|
user: PrivateUserNode!
|
||||||
note: NoteNode
|
note: NoteNode
|
||||||
chapter: ChapterNode!
|
chapter: ChapterNode!
|
||||||
|
path: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChapterNode implements Node & ChapterInterface {
|
type ChapterNode implements Node & ChapterInterface {
|
||||||
|
|
@ -499,6 +504,7 @@ type AssignmentNode implements Node {
|
||||||
taskbaseId: String
|
taskbaseId: String
|
||||||
submissions: [StudentSubmissionNode]
|
submissions: [StudentSubmissionNode]
|
||||||
submission: StudentSubmissionNode
|
submission: StudentSubmissionNode
|
||||||
|
path: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -963,7 +969,7 @@ type InstrumentNodeEdge {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActivityNode {
|
type ActivityNode {
|
||||||
topics: [TopicNode]
|
topics: [TopicNode!]
|
||||||
}
|
}
|
||||||
|
|
||||||
"""Debugging information for the current query."""
|
"""Debugging information for the current query."""
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,11 @@ from wagtail.snippets.models import register_snippet
|
||||||
from wagtail.search import index
|
from wagtail.search import index
|
||||||
from modelcluster.fields import ParentalKey
|
from modelcluster.fields import ParentalKey
|
||||||
|
|
||||||
|
from core.mixins import GraphqlNodeMixin
|
||||||
|
|
||||||
|
|
||||||
@register_snippet
|
@register_snippet
|
||||||
class Survey(models.Model, index.Indexed):
|
class Survey(models.Model, index.Indexed, GraphqlNodeMixin):
|
||||||
title = models.CharField(max_length=255)
|
title = models.CharField(max_length=255)
|
||||||
module = models.ForeignKey(
|
module = models.ForeignKey(
|
||||||
"books.Module",
|
"books.Module",
|
||||||
|
|
@ -18,6 +20,10 @@ class Survey(models.Model, index.Indexed):
|
||||||
)
|
)
|
||||||
data = JSONField()
|
data = JSONField()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def route(self):
|
||||||
|
return f"module/{self.module.slug}#{self.graphql_id}"
|
||||||
|
|
||||||
search_fields = [
|
search_fields = [
|
||||||
index.AutocompleteField("title"),
|
index.AutocompleteField("title"),
|
||||||
index.AutocompleteField("module__meta_title"),
|
index.AutocompleteField("module__meta_title"),
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ class AnswerNode(DjangoObjectType):
|
||||||
class SurveyNode(DjangoObjectType):
|
class SurveyNode(DjangoObjectType):
|
||||||
pk = graphene.Int()
|
pk = graphene.Int()
|
||||||
answer = graphene.Field(AnswerNode)
|
answer = graphene.Field(AnswerNode)
|
||||||
|
path = graphene.String(required=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Survey
|
model = Survey
|
||||||
|
|
@ -39,6 +40,11 @@ class SurveyNode(DjangoObjectType):
|
||||||
except Answer.DoesNotExist:
|
except Answer.DoesNotExist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_path(root: Survey, info, **kwargs):
|
||||||
|
return root.route
|
||||||
|
|
||||||
|
|
||||||
class SurveysQuery(object):
|
class SurveysQuery(object):
|
||||||
survey = graphene.Field(SurveyNode, id=graphene.ID())
|
survey = graphene.Field(SurveyNode, id=graphene.ID())
|
||||||
surveys = DjangoFilterConnectionField(SurveyNode)
|
surveys = DjangoFilterConnectionField(SurveyNode)
|
||||||
|
|
|
||||||
|
|
@ -242,4 +242,4 @@ class UpdateError(graphene.ObjectType):
|
||||||
|
|
||||||
|
|
||||||
class ActivityNode(graphene.ObjectType):
|
class ActivityNode(graphene.ObjectType):
|
||||||
topics = graphene.List("books.schema.nodes.TopicNode")
|
topics = graphene.List(graphene.NonNull("books.schema.nodes.TopicNode"))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue