parent
46d42eb279
commit
3ab0662a6a
|
|
@ -16,11 +16,19 @@
|
||||||
>
|
>
|
||||||
{{ module.title }}
|
{{ module.title }}
|
||||||
</h1>
|
</h1>
|
||||||
<img
|
<div class="module__hero">
|
||||||
:src="module.heroImage"
|
<img
|
||||||
alt=""
|
:src="module.heroImage"
|
||||||
class="module__hero"
|
alt=""
|
||||||
/>
|
class="module__hero-image"
|
||||||
|
/>
|
||||||
|
<h5
|
||||||
|
class="module__hero-source"
|
||||||
|
v-if="module.heroSource"
|
||||||
|
>
|
||||||
|
Quelle: {{ module.heroSource }}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="module__intro-wrapper">
|
<div class="module__intro-wrapper">
|
||||||
<bookmark-actions
|
<bookmark-actions
|
||||||
|
|
@ -131,8 +139,16 @@ export default {
|
||||||
|
|
||||||
&__hero {
|
&__hero {
|
||||||
margin-bottom: 35px;
|
margin-bottom: 35px;
|
||||||
border-radius: 12px;
|
}
|
||||||
|
|
||||||
|
&__hero-image {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__hero-source {
|
||||||
|
@include tiny-text;
|
||||||
|
ling-height: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__meta-title {
|
&__meta-title {
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,7 @@
|
||||||
:src="teaser.imageUrl"
|
:src="teaser.imageUrl"
|
||||||
class="news-teaser__image"
|
class="news-teaser__image"
|
||||||
/>
|
/>
|
||||||
<a
|
<h5 class="news-teaser__image-source">Quelle {{ teaser.imageSource }}</h5>
|
||||||
:href="teaser.imageSource"
|
|
||||||
class="news-teaser__image-source"
|
|
||||||
>Quelle {{ teaser.imageSource }}</a
|
|
||||||
>
|
|
||||||
|
|
||||||
<h4 class="news-teaser__title">{{ teaser.title }}</h4>
|
<h4 class="news-teaser__title">{{ teaser.title }}</h4>
|
||||||
<p class="news-teaser__description">{{ teaser.description }}</p>
|
<p class="news-teaser__description">{{ teaser.description }}</p>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ fragment ModuleParts on ModuleNode {
|
||||||
intro
|
intro
|
||||||
slug
|
slug
|
||||||
heroImage
|
heroImage
|
||||||
|
heroSource
|
||||||
solutionsEnabled
|
solutionsEnabled
|
||||||
inEditMode @client
|
inEditMode @client
|
||||||
topic {
|
topic {
|
||||||
|
|
|
||||||
|
|
@ -11,19 +11,20 @@ from users.models import SchoolClass
|
||||||
|
|
||||||
class Module(StrictHierarchyPage):
|
class Module(StrictHierarchyPage):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'Modul'
|
verbose_name = "Modul"
|
||||||
verbose_name_plural = 'Module'
|
verbose_name_plural = "Module"
|
||||||
|
|
||||||
meta_title = models.CharField(
|
meta_title = models.CharField(
|
||||||
max_length=255,
|
max_length=255, help_text="e.g. 'Intro' or 'Modul 1'")
|
||||||
help_text='e.g. \'Intro\' or \'Modul 1\''
|
|
||||||
)
|
|
||||||
hero_image = models.ForeignKey(
|
hero_image = models.ForeignKey(
|
||||||
'wagtailimages.Image',
|
"wagtailimages.Image",
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
related_name='+'
|
related_name="+",
|
||||||
|
)
|
||||||
|
hero_source = models.CharField(
|
||||||
|
max_length=255, help_text="e.g. 'Reuters', 'Wikipedia'", blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
teaser = models.TextField()
|
teaser = models.TextField()
|
||||||
|
|
@ -32,28 +33,26 @@ class Module(StrictHierarchyPage):
|
||||||
solutions_enabled_for = models.ManyToManyField(SchoolClass)
|
solutions_enabled_for = models.ManyToManyField(SchoolClass)
|
||||||
|
|
||||||
content_panels = [
|
content_panels = [
|
||||||
FieldPanel('title', classname="full title"),
|
FieldPanel("title", classname="full title"),
|
||||||
FieldPanel('meta_title', classname="full title"),
|
FieldPanel("meta_title", classname="full title"),
|
||||||
ImageChooserPanel('hero_image'),
|
ImageChooserPanel("hero_image"),
|
||||||
FieldPanel('teaser'),
|
FieldPanel("hero_source"),
|
||||||
FieldPanel('intro'),
|
FieldPanel("teaser"),
|
||||||
|
FieldPanel("intro"),
|
||||||
]
|
]
|
||||||
|
|
||||||
edit_handler = TabbedInterface([
|
edit_handler = TabbedInterface(
|
||||||
ObjectList(content_panels, heading='Content'),
|
[ObjectList(content_panels, heading="Content"), get_default_settings()]
|
||||||
get_default_settings()
|
)
|
||||||
])
|
|
||||||
|
|
||||||
template = 'generic_page.html'
|
|
||||||
|
|
||||||
parent_page_types = ['books.Topic']
|
|
||||||
subpage_types = ['books.Chapter']
|
|
||||||
|
|
||||||
|
template = "generic_page.html"
|
||||||
|
|
||||||
|
parent_page_types = ["books.Topic"]
|
||||||
|
subpage_types = ["books.Chapter"]
|
||||||
|
|
||||||
# todo: isn't this a duplicate definition?
|
# todo: isn't this a duplicate definition?
|
||||||
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)
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -65,41 +64,57 @@ class Module(StrictHierarchyPage):
|
||||||
|
|
||||||
# get content blocks of chapters
|
# get content blocks of chapters
|
||||||
for chapter in chapters:
|
for chapter in chapters:
|
||||||
content_block_query = content_block_query.union(ContentBlock.get_by_parent(chapter))
|
content_block_query = content_block_query.union(
|
||||||
|
ContentBlock.get_by_parent(chapter)
|
||||||
|
)
|
||||||
|
|
||||||
# clear all `hidden for` and `visible for` for class `school_class_to_sync`
|
# clear all `hidden for` and `visible for` for class `school_class_to_sync`
|
||||||
for content_block in school_class_to_sync.hidden_content_blocks.intersection(content_block_query):
|
for content_block in school_class_to_sync.hidden_content_blocks.intersection(
|
||||||
|
content_block_query
|
||||||
|
):
|
||||||
content_block.hidden_for.remove(school_class_to_sync)
|
content_block.hidden_for.remove(school_class_to_sync)
|
||||||
for content_block in school_class_to_sync.visible_content_blocks.intersection(content_block_query):
|
for content_block in school_class_to_sync.visible_content_blocks.intersection(
|
||||||
|
content_block_query
|
||||||
|
):
|
||||||
content_block.visible_for.remove(school_class_to_sync)
|
content_block.visible_for.remove(school_class_to_sync)
|
||||||
|
|
||||||
# get all content blocks with `hidden for` for class `school_class_pattern`
|
# get all content blocks with `hidden for` for class `school_class_pattern`
|
||||||
for content_block in school_class_template.hidden_content_blocks.intersection(content_block_query):
|
for content_block in school_class_template.hidden_content_blocks.intersection(
|
||||||
|
content_block_query
|
||||||
|
):
|
||||||
# add `school_class_to_sync` to these blocks' `hidden for`
|
# add `school_class_to_sync` to these blocks' `hidden for`
|
||||||
content_block.hidden_for.add(school_class_to_sync)
|
content_block.hidden_for.add(school_class_to_sync)
|
||||||
|
|
||||||
# get all content blocks with `visible for` for class `school_class_pattern`
|
# get all content blocks with `visible for` for class `school_class_pattern`
|
||||||
for content_block in school_class_template.visible_content_blocks.intersection(content_block_query):
|
for content_block in school_class_template.visible_content_blocks.intersection(
|
||||||
|
content_block_query
|
||||||
|
):
|
||||||
# add `school_class_to_sync` to these blocks' `visible for`
|
# add `school_class_to_sync` to these blocks' `visible for`
|
||||||
content_block.visible_for.add(school_class_to_sync)
|
content_block.visible_for.add(school_class_to_sync)
|
||||||
|
|
||||||
for chapter in chapters:
|
for chapter in chapters:
|
||||||
chapter.sync_title_visibility(school_class_template, school_class_to_sync)
|
chapter.sync_title_visibility(
|
||||||
chapter.sync_description_visibility(school_class_template, school_class_to_sync)
|
school_class_template, school_class_to_sync)
|
||||||
|
chapter.sync_description_visibility(
|
||||||
|
school_class_template, school_class_to_sync
|
||||||
|
)
|
||||||
|
|
||||||
objective_groups = self.objective_groups.all()
|
objective_groups = self.objective_groups.all()
|
||||||
|
|
||||||
for objective_group in objective_groups:
|
for objective_group in objective_groups:
|
||||||
objective_group.sync_visibility(school_class_template, school_class_to_sync)
|
objective_group.sync_visibility(
|
||||||
|
school_class_template, school_class_to_sync)
|
||||||
|
|
||||||
def get_admin_display_title(self):
|
def get_admin_display_title(self):
|
||||||
return f"{self.meta_title} - {self.title}"
|
return f"{self.meta_title} - {self.title}"
|
||||||
|
|
||||||
|
|
||||||
class RecentModule(models.Model):
|
class RecentModule(models.Model):
|
||||||
module = models.ForeignKey(Module, on_delete=models.CASCADE, related_name='recent_modules')
|
module = models.ForeignKey(
|
||||||
user = models.ForeignKey('users.User', on_delete=models.CASCADE)
|
Module, on_delete=models.CASCADE, related_name="recent_modules"
|
||||||
|
)
|
||||||
|
user = models.ForeignKey("users.User", on_delete=models.CASCADE)
|
||||||
visited = models.DateTimeField(default=timezone.now)
|
visited = models.DateTimeField(default=timezone.now)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
get_latest_by = 'visited'
|
get_latest_by = "visited"
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,11 @@ from books.models import Module, Chapter, ContentBlock, RecentModule
|
||||||
from books.schema.interfaces.module import ModuleInterface
|
from books.schema.interfaces.module import ModuleInterface
|
||||||
from books.schema.nodes.chapter import ChapterNode
|
from books.schema.nodes.chapter import ChapterNode
|
||||||
from notes.models import ModuleBookmark, ContentBlockBookmark, ChapterBookmark
|
from notes.models import ModuleBookmark, ContentBlockBookmark, ChapterBookmark
|
||||||
from notes.schema import ModuleBookmarkNode, ContentBlockBookmarkNode, ChapterBookmarkNode
|
from notes.schema import (
|
||||||
|
ModuleBookmarkNode,
|
||||||
|
ContentBlockBookmarkNode,
|
||||||
|
ChapterBookmarkNode,
|
||||||
|
)
|
||||||
from objectives.schema import ObjectiveGroupNode
|
from objectives.schema import ObjectiveGroupNode
|
||||||
from surveys.models import Answer
|
from surveys.models import Answer
|
||||||
from surveys.schema import AnswerNode
|
from surveys.schema import AnswerNode
|
||||||
|
|
@ -20,11 +24,20 @@ class ModuleNode(DjangoObjectType):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Module
|
model = Module
|
||||||
only_fields = [
|
only_fields = [
|
||||||
'slug', 'title', 'meta_title', 'teaser', 'intro', 'objective_groups', 'assignments', 'hero_image', 'topic'
|
"slug",
|
||||||
|
"title",
|
||||||
|
"meta_title",
|
||||||
|
"teaser",
|
||||||
|
"intro",
|
||||||
|
"objective_groups",
|
||||||
|
"assignments",
|
||||||
|
"hero_image",
|
||||||
|
"hero_source",
|
||||||
|
"topic",
|
||||||
]
|
]
|
||||||
filter_fields = {
|
filter_fields = {
|
||||||
'slug': ['exact', 'icontains', 'in'],
|
"slug": ["exact", "icontains", "in"],
|
||||||
'title': ['exact', 'icontains', 'in'],
|
"title": ["exact", "icontains", "in"],
|
||||||
}
|
}
|
||||||
interfaces = (ModuleInterface,)
|
interfaces = (ModuleInterface,)
|
||||||
|
|
||||||
|
|
@ -33,9 +46,10 @@ class ModuleNode(DjangoObjectType):
|
||||||
bookmark = graphene.Field(ModuleBookmarkNode)
|
bookmark = graphene.Field(ModuleBookmarkNode)
|
||||||
my_submissions = DjangoFilterConnectionField(StudentSubmissionNode)
|
my_submissions = DjangoFilterConnectionField(StudentSubmissionNode)
|
||||||
my_answers = DjangoFilterConnectionField(AnswerNode)
|
my_answers = DjangoFilterConnectionField(AnswerNode)
|
||||||
my_content_bookmarks = DjangoFilterConnectionField(ContentBlockBookmarkNode)
|
my_content_bookmarks = DjangoFilterConnectionField(
|
||||||
|
ContentBlockBookmarkNode)
|
||||||
my_chapter_bookmarks = DjangoFilterConnectionField(ChapterBookmarkNode)
|
my_chapter_bookmarks = DjangoFilterConnectionField(ChapterBookmarkNode)
|
||||||
snapshots = graphene.List('books.schema.nodes.SnapshotNode')
|
snapshots = graphene.List("books.schema.nodes.SnapshotNode")
|
||||||
objective_groups = graphene.List(ObjectiveGroupNode)
|
objective_groups = graphene.List(ObjectiveGroupNode)
|
||||||
assignments = graphene.List(AssignmentNode)
|
assignments = graphene.List(AssignmentNode)
|
||||||
|
|
||||||
|
|
@ -47,12 +61,15 @@ class ModuleNode(DjangoObjectType):
|
||||||
|
|
||||||
def resolve_solutions_enabled(self, info, **kwargs):
|
def resolve_solutions_enabled(self, info, **kwargs):
|
||||||
school_class = info.context.user.selected_class
|
school_class = info.context.user.selected_class
|
||||||
return self.solutions_enabled_for.filter(pk=school_class.pk).exists() if school_class is not None else False
|
return (
|
||||||
|
self.solutions_enabled_for.filter(pk=school_class.pk).exists()
|
||||||
|
if school_class is not None
|
||||||
|
else False
|
||||||
|
)
|
||||||
|
|
||||||
def resolve_bookmark(self, info, **kwags):
|
def resolve_bookmark(self, info, **kwags):
|
||||||
return ModuleBookmark.objects.filter(
|
return ModuleBookmark.objects.filter(
|
||||||
user=info.context.user,
|
user=info.context.user, module=self
|
||||||
module=self
|
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
def resolve_my_submissions(self, info, **kwargs):
|
def resolve_my_submissions(self, info, **kwargs):
|
||||||
|
|
@ -69,7 +86,9 @@ class ModuleNode(DjangoObjectType):
|
||||||
def resolve_my_content_bookmarks(self, info, **kwargs):
|
def resolve_my_content_bookmarks(self, info, **kwargs):
|
||||||
user = info.context.user
|
user = info.context.user
|
||||||
content_blocks = ContentBlock.objects.live().descendant_of(self)
|
content_blocks = ContentBlock.objects.live().descendant_of(self)
|
||||||
return ContentBlockBookmark.objects.filter(content_block__in=content_blocks, user=user)
|
return ContentBlockBookmark.objects.filter(
|
||||||
|
content_block__in=content_blocks, user=user
|
||||||
|
)
|
||||||
# Bookmark Text
|
# Bookmark Text
|
||||||
# Bookmark Image etc
|
# Bookmark Image etc
|
||||||
# Bookmark Other
|
# Bookmark Other
|
||||||
|
|
@ -83,13 +102,14 @@ class ModuleNode(DjangoObjectType):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_objective_groups(parent, info, **kwargs):
|
def resolve_objective_groups(parent, info, **kwargs):
|
||||||
return parent.objective_groups.all() \
|
return parent.objective_groups.all().prefetch_related("hidden_for")
|
||||||
.prefetch_related('hidden_for')
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_snapshots(parent, info, **kwargs):
|
def resolve_snapshots(parent, info, **kwargs):
|
||||||
user = info.context.user
|
user = info.context.user
|
||||||
return parent.snapshots.filter(Q(creator=user) | Q(Q(creator__team=user.team ) & Q(shared=True)))
|
return parent.snapshots.filter(
|
||||||
|
Q(creator=user) | Q(Q(creator__team=user.team) & Q(shared=True))
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_assignments(parent: Module, info, **kwargs):
|
def resolve_assignments(parent: Module, info, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -227,6 +227,7 @@ type ChapterNode implements Node & ChapterInterface {
|
||||||
id: ID!
|
id: ID!
|
||||||
bookmark: ChapterBookmarkNode
|
bookmark: ChapterBookmarkNode
|
||||||
contentBlocks: [ContentBlockNode]
|
contentBlocks: [ContentBlockNode]
|
||||||
|
path: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChapterNodeConnection {
|
type ChapterNodeConnection {
|
||||||
|
|
@ -621,6 +622,7 @@ type ModuleNode implements ModuleInterface {
|
||||||
slug: String!
|
slug: String!
|
||||||
metaTitle: String!
|
metaTitle: String!
|
||||||
heroImage: String!
|
heroImage: String!
|
||||||
|
heroSource: String!
|
||||||
teaser: String!
|
teaser: String!
|
||||||
intro: String!
|
intro: String!
|
||||||
assignments: [AssignmentNode]
|
assignments: [AssignmentNode]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue