Add unit test, model and mutation for snapshots

This commit is contained in:
Ramon Wenger 2021-04-14 23:16:38 +02:00
parent 85706d73d1
commit 15aff9054c
17 changed files with 597 additions and 278 deletions

View File

@ -0,0 +1,49 @@
# Generated by Django 2.2.19 on 2021-04-14 21:16
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('books', '0024_auto_20210218_1336'),
]
operations = [
migrations.CreateModel(
name='ChapterSnapshot',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title_hidden', models.BooleanField(default=False)),
('description_hidden', models.BooleanField(default=False)),
('chapter', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='chapter_snapshots', to='books.Chapter')),
],
),
migrations.CreateModel(
name='Snapshot',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('chapters', models.ManyToManyField(through='books.ChapterSnapshot', to='books.Chapter')),
('hidden_content_blocks', models.ManyToManyField(related_name='hidden_for_snapshots', to='books.ContentBlock')),
('module', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='books.Module')),
],
),
migrations.CreateModel(
name='ContentBlockSnapshot',
fields=[
('contentblock_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='books.ContentBlock')),
('hidden', models.BooleanField(default=False)),
('snapshot', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='custom_content_blocks', to='books.Snapshot')),
],
options={
'abstract': False,
},
bases=('books.contentblock',),
),
migrations.AddField(
model_name='chaptersnapshot',
name='snapshot',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chapter_snapshots', to='books.Snapshot'),
),
]

View File

@ -3,3 +3,4 @@ from .module import *
from .topic import *
from .chapter import *
from .contentblock import *
from .snapshot import *

View File

@ -110,3 +110,13 @@ class ContentBlock(StrictHierarchyPage):
survey.module = module
survey.save()
super().save(*args, **kwargs)
class ContentBlockSnapshot(ContentBlock):
hidden = models.BooleanField(default=False)
snapshot = models.ForeignKey(
'books.snapshot',
on_delete=models.SET_NULL,
null=True,
related_name='custom_content_blocks'
)

View File

@ -0,0 +1,73 @@
from django.db import models
from django.db.models import Q
from books.models import Chapter, ContentBlock, ContentBlockSnapshot
class ChapterSnapshot(models.Model):
"""
Captures the state of a chapter at the time when the snapshot was taken, for the school class that was selected
for the user creating the snapshot
"""
chapter = models.ForeignKey(
'books.Chapter',
related_name='chapter_snapshots',
on_delete=models.PROTECT
)
snapshot = models.ForeignKey(
'books.Snapshot',
related_name='chapter_snapshots',
on_delete=models.CASCADE
)
title_hidden = models.BooleanField(default=False)
description_hidden = models.BooleanField(default=False)
class SnapshotManager(models.Manager):
def create_snapshot(self, module, school_class, user, *args, **kwargs):
snapshot = self.create(module=module, *args, **kwargs)
chapters = Chapter.get_by_parent(module).filter(
Q(description_hidden_for=school_class)
| Q(title_hidden_for=school_class)
)
for chapter in chapters:
ChapterSnapshot.objects.create(
chapter=chapter,
snapshot=snapshot,
title_hidden=chapter.title_hidden_for.filter(id=school_class.id).exists(),
description_hidden=chapter.description_hidden_for.filter(id=school_class.id).exists()
)
base_qs = ContentBlock.get_by_parent(chapter).filter(snapshotcontentblock__isnull=True)
for content_block in base_qs.filter(user_created=False):
if content_block.hidden_for.filter(id=school_class.id).exists():
snapshot.hidden_content_blocks.add(content_block)
for content_block in base_qs.filter(user_created=True).filter(owner=user):
new_content_block = SnapshotContentBlock(
hidden=False,
snapshot=snapshot,
contents=content_block.contents,
type=content_block.type,
title=content_block.title
)
content_block.add_sibling(instance=new_content_block, pos='right')
revision = new_content_block.save_revision()
revision.publish()
new_content_block.save()
return snapshot
class Snapshot(models.Model):
module = models.ForeignKey(
'books.Module',
on_delete=models.PROTECT
)
chapters = models.ManyToManyField(
'books.Chapter',
through=ChapterSnapshot
)
hidden_content_blocks = models.ManyToManyField(
'books.ContentBlock',
related_name='hidden_for_snapshots'
)
objects = SnapshotManager()

View File

@ -0,0 +1,2 @@
from .chapter import *
from .module import *

View File

@ -0,0 +1,5 @@
import graphene
from graphene_django.filter import DjangoFilterConnectionField
class ChapterInterface(graphene.Interface):
content_blocks = DjangoFilterConnectionField('books.schema.nodes.ContentBlockNode')

View File

@ -0,0 +1,8 @@
import graphene
class ModuleInterface(graphene.Interface):
pk = graphene.Int()
def resolve_pk(self, info, **kwargs):
return self.id

View File

@ -1,6 +1,7 @@
from books.schema.mutations.chapter import UpdateChapterVisibility
from books.schema.mutations.contentblock import MutateContentBlock, AddContentBlock, DeleteContentBlock
from books.schema.mutations.module import UpdateSolutionVisibility, UpdateLastModule, SyncModuleVisibility
from books.schema.mutations.snapshot import CreateSnapshot
from books.schema.mutations.topic import UpdateLastTopic
@ -13,3 +14,4 @@ class BookMutations(object):
update_last_topic = UpdateLastTopic.Field()
update_chapter_visibility = UpdateChapterVisibility.Field()
sync_module_visibility = SyncModuleVisibility.Field()
create_snapshot = CreateSnapshot.Field()

View File

@ -0,0 +1,27 @@
import graphene
from graphene import relay
from api.utils import get_object
from books.models import Module
from books.models.snapshot import Snapshot
from books.schema.nodes import SnapshotNode
from users.models import SchoolClass
class CreateSnapshot(relay.ClientIDMutation):
class Input:
module = graphene.ID(required=True)
selected_class = graphene.ID(required=True)
snapshot = graphene.Field(SnapshotNode)
success = graphene.Boolean()
@classmethod
def mutate_and_get_payload(cls, root, info, **args):
module_id = args.get('module')
module = get_object(Module, module_id)
user = info.context.user
selected_class_id = args.get('selected_class')
selected_class = get_object(SchoolClass, selected_class_id)
snapshot = Snapshot.objects.create_snapshot(module, selected_class, user)
return cls(snapshot=snapshot, success=True)

View File

@ -1,260 +0,0 @@
import graphene
from django.db.models import Q
from graphene import relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from assignments.models import StudentSubmission
from assignments.schema.types import StudentSubmissionNode
from books.models import ContentBlock, Chapter, Module, RecentModule, Topic, Book
from books.utils import are_solutions_enabled_for
from core.logger import get_logger
from notes.models import ContentBlockBookmark, ChapterBookmark, ModuleBookmark
from notes.schema import ContentBlockBookmarkNode, ChapterBookmarkNode, ModuleBookmarkNode
from rooms.models import ModuleRoomSlug
from surveys.models import Answer
from surveys.schema import AnswerNode
logger = get_logger(__name__)
class TextBlockNode(graphene.ObjectType):
text = graphene.String()
def resolve_text(root, info, **kwargs):
return root['value']['text']
class ContentNode(graphene.Union):
class Meta:
types = (TextBlockNode,)
@classmethod
def resolve_type(cls, instance, info):
logger.info(instance)
if instance['type'] == 'text_block':
return TextBlockNode
class ContentBlockNode(DjangoObjectType):
mine = graphene.Boolean()
bookmarks = graphene.List(ContentBlockBookmarkNode)
# contents = graphene.List(ContentNode)
class Meta:
model = ContentBlock
only_fields = [
'slug', 'title', 'type', 'contents', 'hidden_for', 'visible_for', 'user_created'
]
filter_fields = [
'slug', 'title',
]
interfaces = (relay.Node,)
def resolve_mine(self, info, **kwargs):
return self.owner is not None and self.owner.pk == info.context.user.pk
def resolve_contents(self, info, **kwargs):
updated_stream_data = []
for content in self.contents.stream_data:
# only show solutions to teachers and students for whom their teachers have them enabled
if content['type'] == 'solution' \
and not (are_solutions_enabled_for(info.context.user, self.module) or info.context.user.is_teacher()):
logger.debug('Solution is hidden for this user')
continue
if content['type'] == 'content_list_item':
for index, list_block in enumerate(content['value']):
content['value'][index] = process_module_room_slug_block(list_block)
content = process_module_room_slug_block(content)
updated_stream_data.append(content)
self.contents.stream_data = updated_stream_data
return self.contents
def resolve_bookmarks(self, info, **kwargs):
return ContentBlockBookmark.objects.filter(
user=info.context.user,
content_block=self
)
class ChapterNode(DjangoObjectType):
content_blocks = DjangoFilterConnectionField(ContentBlockNode)
bookmark = graphene.Field(ChapterBookmarkNode)
class Meta:
model = Chapter
only_fields = [
'slug', 'title', 'description', 'title_hidden_for', 'description_hidden_for'
]
filter_fields = [
'slug', 'title',
]
interfaces = (relay.Node,)
def resolve_content_blocks(self, info, **kwargs):
user = info.context.user
school_classes = user.school_classes.values_list('pk')
by_parent = ContentBlock.get_by_parent(self) \
.prefetch_related('visible_for') \
.prefetch_related('hidden_for')
# don't filter the hidden blocks on the server any more, we do this on the client now, as they are not secret
default_blocks = Q(user_created=False)
owned_by_user = Q(user_created=True, owner=user)
teacher_created_and_visible = Q(Q(user_created=True) & Q(visible_for__in=school_classes))
if user.has_perm('users.can_manage_school_class_content'): # teacher
return by_parent.filter(default_blocks | owned_by_user | teacher_created_and_visible).distinct()
else: # student
return by_parent.filter(default_blocks | teacher_created_and_visible).distinct()
def resolve_bookmark(self, info, **kwargs):
return ChapterBookmark.objects.filter(
user=info.context.user,
chapter=self
).first()
class ModuleNode(DjangoObjectType):
pk = graphene.Int()
chapters = DjangoFilterConnectionField(ChapterNode)
topic = graphene.Field('books.schema.queries.TopicNode')
hero_image = graphene.String()
solutions_enabled = graphene.Boolean()
bookmark = graphene.Field(ModuleBookmarkNode)
my_submissions = DjangoFilterConnectionField(StudentSubmissionNode)
my_answers = DjangoFilterConnectionField(AnswerNode)
my_content_bookmarks = DjangoFilterConnectionField(ContentBlockBookmarkNode)
my_chapter_bookmarks = DjangoFilterConnectionField(ChapterBookmarkNode)
class Meta:
model = Module
only_fields = [
'slug', 'title', 'meta_title', 'teaser', 'intro', 'objective_groups', 'assignments', 'hero_image', 'topic'
]
filter_fields = {
'slug': ['exact', 'icontains', 'in'],
'title': ['exact', 'icontains', 'in'],
}
interfaces = (relay.Node,)
def resolve_pk(self, info, **kwargs):
return self.id
def resolve_hero_image(self, info, **kwargs):
if self.hero_image:
return self.hero_image.file.url
def resolve_chapters(self, info, **kwargs):
return Chapter.get_by_parent(self)
def resolve_topic(self, info, **kwargs):
return self.get_parent().specific
def resolve_solutions_enabled(self, info, **kwargs):
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
def resolve_bookmark(self, info, **kwags):
return ModuleBookmark.objects.filter(
user=info.context.user,
module=self
).first()
def resolve_my_submissions(self, info, **kwargs):
user = info.context.user
return StudentSubmission.objects.filter(student=user, assignment__module=self)
# we want:
# StudentSubmission
def resolve_my_answers(self, info, **kwargs):
user = info.context.user
return Answer.objects.filter(owner=user, survey__module=self)
# Survey
def resolve_my_content_bookmarks(self, info, **kwargs):
user = info.context.user
content_blocks = ContentBlock.objects.live().descendant_of(self)
return ContentBlockBookmark.objects.filter(content_block__in=content_blocks, user=user)
# Bookmark Text
# Bookmark Image etc
# Bookmark Other
# Note
#
def resolve_my_chapter_bookmarks(self, info, **kwargs):
user = info.context.user
chapters = Chapter.objects.live().descendant_of(self)
return ChapterBookmark.objects.filter(chapter__in=chapters, user=user)
def resolve_objective_groups(self, root, **kwargs):
return self.objective_groups.all() \
.prefetch_related('hidden_for')
class RecentModuleNode(DjangoObjectType):
class Meta:
model = RecentModule
interfaces = (relay.Node,)
class TopicNode(DjangoObjectType):
pk = graphene.Int()
modules = DjangoFilterConnectionField(ModuleNode)
class Meta:
model = Topic
only_fields = [
'slug', 'title', 'teaser', 'description', 'vimeo_id', 'order', 'instructions'
]
filter_fields = {
'slug': ['exact', 'icontains', 'in'],
'title': ['exact', 'icontains', 'in'],
}
interfaces = (relay.Node,)
def resolve_pk(self, *args, **kwargs):
return self.id
def resolve_modules(self, *args, **kwargs):
return Module.get_by_parent(self)
class BookNode(DjangoObjectType):
pk = graphene.Int()
topics = DjangoFilterConnectionField(TopicNode)
class Meta:
model = Book
only_fields = [
'slug', 'title',
]
filter_fields = {
'slug': ['exact', 'icontains', 'in'],
'title': ['exact', 'icontains', 'in'],
}
interfaces = (relay.Node,)
def resolve_pk(self, *args, **kwargs):
return self.id
def resolve_topics(self, *args, **kwargs):
return Topic.get_by_parent(self)
def process_module_room_slug_block(content):
if content['type'] == 'module_room_slug':
try:
module_room_slug = ModuleRoomSlug.objects.get(title=content['value']['title'])
content['value'] = {
'title': content['value']['title'],
'slug': module_room_slug.slug
}
except ModuleRoomSlug.DoesNotExist:
pass
return content

View File

@ -0,0 +1,6 @@
from .chapter import *
from .module import *
from .content import *
from .snapshot import *
from .topic import *

View File

@ -0,0 +1,100 @@
import graphene
from django.db.models import Q
from graphene import relay
from graphene_django import DjangoObjectType
from graphql_relay import to_global_id
from books.models import Chapter, ContentBlock
from books.models.snapshot import ChapterSnapshot
from books.schema.interfaces import ChapterInterface
from notes.models import ChapterBookmark
from notes.schema import ChapterBookmarkNode
class ChapterNode(DjangoObjectType):
bookmark = graphene.Field(ChapterBookmarkNode)
class Meta:
model = Chapter
only_fields = [
'slug', 'title', 'description', 'title_hidden_for', 'description_hidden_for'
]
filter_fields = [
'slug', 'title',
]
interfaces = (relay.Node, ChapterInterface,)
def resolve_content_blocks(self, info, **kwargs):
user = info.context.user
school_classes = user.school_classes.values_list('pk')
by_parent = ContentBlock.get_by_parent(self) \
.prefetch_related('visible_for') \
.prefetch_related('hidden_for')
# don't filter the hidden blocks on the server any more, we do this on the client now, as they are not secret
default_blocks = Q(user_created=False)
owned_by_user = Q(user_created=True, owner=user)
teacher_created_and_visible = Q(Q(user_created=True) & Q(visible_for__in=school_classes))
if user.can_manage_school_class_content: # teacher
return by_parent.filter(default_blocks | owned_by_user | teacher_created_and_visible).distinct()
else: # student
return by_parent.filter(default_blocks | teacher_created_and_visible).distinct()
def resolve_bookmark(self, info, **kwargs):
return ChapterBookmark.objects.filter(
user=info.context.user,
chapter=self
).first()
class SnapshotChapterNode(DjangoObjectType):
title_hidden = graphene.Boolean()
description_hidden = graphene.Boolean()
title = graphene.String()
description = graphene.String()
id = graphene.ID(required=True)
class Meta:
model = ChapterSnapshot
only_fields = '__all__'
filter_fields = [
'id',
]
interfaces = (relay.Node, ChapterInterface,)
@staticmethod
def resolve_title_hidden(parent, info):
return parent
@staticmethod
def resolve_title(parent, info):
return parent.chapter.title
@staticmethod
def resolve_description(parent, info):
return parent.chapter.description
@staticmethod
def resolve_content_blocks(parent, info, **kwargs):
snapshot = parent.snapshot
user_created = Q(user_created=True)
hidden_for_snapshot = Q(hidden_for_snapshots=snapshot)
custom_hidden = Q(snapshotcontentblock__hidden=True)
qs = ContentBlock.get_by_parent(parent.chapter) \
.exclude(user_created) \
.exclude(hidden_for_snapshot) \
.exclude(custom_hidden)
# exclude hidden for snapshot
# exclude with owner
# include visible snapshot content blocks
# todo
return qs
def resolve_id(self, *args):
return to_global_id('SnapshotChapterNode', self.chapter.pk)

View File

@ -0,0 +1,91 @@
import graphene
from graphene import relay
from graphene_django import DjangoObjectType
from books.models import ContentBlock
from books.utils import are_solutions_enabled_for
from notes.models import ContentBlockBookmark
from notes.schema import ContentBlockBookmarkNode
from rooms.models import ModuleRoomSlug
from core.logger import get_logger
logger = get_logger(__name__)
class TextBlockNode(graphene.ObjectType):
text = graphene.String()
def resolve_text(root, info, **kwargs):
return root['value']['text']
class ContentNode(graphene.Union):
class Meta:
types = (TextBlockNode,)
@classmethod
def resolve_type(cls, instance, info):
logger.info(instance)
if instance['type'] == 'text_block':
return TextBlockNode
class ContentBlockNode(DjangoObjectType):
mine = graphene.Boolean()
bookmarks = graphene.List(ContentBlockBookmarkNode)
# contents = graphene.List(ContentNode)
class Meta:
model = ContentBlock
only_fields = [
'slug', 'title', 'type', 'contents', 'hidden_for', 'visible_for', 'user_created'
]
filter_fields = [
'slug', 'title',
]
interfaces = (relay.Node,)
def resolve_mine(self, info, **kwargs):
return self.owner is not None and self.owner.pk == info.context.user.pk
def resolve_contents(self, info, **kwargs):
updated_stream_data = []
for content in self.contents.stream_data:
# only show solutions to teachers and students for whom their teachers have them enabled
if content['type'] == 'solution' \
and not (are_solutions_enabled_for(info.context.user, self.module) or info.context.user.is_teacher()):
logger.debug('Solution is hidden for this user')
continue
if content['type'] == 'content_list_item':
for index, list_block in enumerate(content['value']):
content['value'][index] = process_module_room_slug_block(list_block)
content = process_module_room_slug_block(content)
updated_stream_data.append(content)
self.contents.stream_data = updated_stream_data
return self.contents
def resolve_bookmarks(self, info, **kwargs):
return ContentBlockBookmark.objects.filter(
user=info.context.user,
content_block=self
)
def process_module_room_slug_block(content):
if content['type'] == 'module_room_slug':
try:
module_room_slug = ModuleRoomSlug.objects.get(title=content['value']['title'])
content['value'] = {
'title': content['value']['title'],
'slug': module_room_slug.slug
}
except ModuleRoomSlug.DoesNotExist:
pass
return content

View File

@ -0,0 +1,93 @@
import graphene
from graphene import relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from assignments.models import StudentSubmission
from assignments.schema.types import StudentSubmissionNode
from books.models import Module, Chapter, ContentBlock, RecentModule
from books.schema.interfaces.module import ModuleInterface
from books.schema.nodes.chapter import ChapterNode
from notes.models import ModuleBookmark, ContentBlockBookmark, ChapterBookmark
from notes.schema import ModuleBookmarkNode, ContentBlockBookmarkNode, ChapterBookmarkNode
from surveys.models import Answer
from surveys.schema import AnswerNode
class ModuleNode(DjangoObjectType):
chapters = DjangoFilterConnectionField(ChapterNode)
topic = graphene.Field('books.schema.queries.TopicNode')
hero_image = graphene.String()
solutions_enabled = graphene.Boolean()
bookmark = graphene.Field(ModuleBookmarkNode)
my_submissions = DjangoFilterConnectionField(StudentSubmissionNode)
my_answers = DjangoFilterConnectionField(AnswerNode)
my_content_bookmarks = DjangoFilterConnectionField(ContentBlockBookmarkNode)
my_chapter_bookmarks = DjangoFilterConnectionField(ChapterBookmarkNode)
class Meta:
model = Module
only_fields = [
'slug', 'title', 'meta_title', 'teaser', 'intro', 'objective_groups', 'assignments', 'hero_image', 'topic'
]
filter_fields = {
'slug': ['exact', 'icontains', 'in'],
'title': ['exact', 'icontains', 'in'],
}
interfaces = (relay.Node, ModuleInterface, )
def resolve_hero_image(self, info, **kwargs):
if self.hero_image:
return self.hero_image.file.url
def resolve_chapters(self, info, **kwargs):
return Chapter.get_by_parent(self)
def resolve_topic(self, info, **kwargs):
return self.get_parent().specific
def resolve_solutions_enabled(self, info, **kwargs):
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
def resolve_bookmark(self, info, **kwags):
return ModuleBookmark.objects.filter(
user=info.context.user,
module=self
).first()
def resolve_my_submissions(self, info, **kwargs):
user = info.context.user
return StudentSubmission.objects.filter(student=user, assignment__module=self)
# we want:
# StudentSubmission
def resolve_my_answers(self, info, **kwargs):
user = info.context.user
return Answer.objects.filter(owner=user, survey__module=self)
# Survey
def resolve_my_content_bookmarks(self, info, **kwargs):
user = info.context.user
content_blocks = ContentBlock.objects.live().descendant_of(self)
return ContentBlockBookmark.objects.filter(content_block__in=content_blocks, user=user)
# Bookmark Text
# Bookmark Image etc
# Bookmark Other
# Note
#
def resolve_my_chapter_bookmarks(self, info, **kwargs):
user = info.context.user
chapters = Chapter.objects.live().descendant_of(self)
return ChapterBookmark.objects.filter(chapter__in=chapters, user=user)
def resolve_objective_groups(self, root, **kwargs):
return self.objective_groups.all() \
.prefetch_related('hidden_for')
class RecentModuleNode(DjangoObjectType):
class Meta:
model = RecentModule
interfaces = (relay.Node,)

View File

@ -0,0 +1,19 @@
from graphene import relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from .chapter import SnapshotChapterNode
from books.models.snapshot import Snapshot
class SnapshotNode(DjangoObjectType):
class Meta:
model = Snapshot
interfaces = (relay.Node,)
# chapters = relay.ConnectionField('books.schema.connections.ChapterSnapshotConnection')
chapters = DjangoFilterConnectionField(SnapshotChapterNode)
def resolve_chapters(self, info, **kwargs):
# return Chapter.objects.filter(chapter_snapshots__snapshot=self)
return self.chapters.through.objects.all()

View File

@ -0,0 +1,29 @@
import graphene
from graphene import relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from books.models import Topic, Module
from books.schema.nodes import ModuleNode
class TopicNode(DjangoObjectType):
pk = graphene.Int()
modules = DjangoFilterConnectionField(ModuleNode)
class Meta:
model = Topic
only_fields = [
'slug', 'title', 'teaser', 'description', 'vimeo_id', 'order', 'instructions'
]
filter_fields = {
'slug': ['exact', 'icontains', 'in'],
'title': ['exact', 'icontains', 'in'],
}
interfaces = (relay.Node,)
def resolve_pk(self, *args, **kwargs):
return self.id
def resolve_modules(self, *args, **kwargs):
return Module.get_by_parent(self)

View File

@ -1,6 +1,6 @@
from django.test import TestCase, RequestFactory
from graphene.test import Client
from graphql_relay import to_global_id
from graphql_relay import to_global_id, from_global_id
from api.schema import schema
from books.factories import ModuleFactory, ChapterFactory, ContentBlockFactory
@ -44,11 +44,45 @@ query ModulesQuery($slug: String!) {
}
"""
CREATE_SNAPSHOT_MUTATION = """
mutation CreateSnapshot($input: CreateSnapshotInput!) {
createSnapshot(input: $input) {
snapshot {
id
chapters {
edges {
node {
id
descriptionHidden
titleHidden
title
description
contentBlocks {
edges {
node {
id
title
}
}
}
}
}
}
}
success
}
}
"""
def edges_to_array(entity):
return [edge['node'] for edge in entity.get('edges')]
class CreateSnapshotTestCase(TestCase):
def setUp(self):
create_users()
skillbox_class = SchoolClass.objects.get(name='skillbox')
self.skillbox_class = SchoolClass.objects.get(name='skillbox')
second_class = SchoolClass.objects.get(name='second_class')
# teacher will create snapshot
self.teacher = User.objects.get(username='teacher')
@ -56,38 +90,68 @@ class CreateSnapshotTestCase(TestCase):
# module M has a chapter
self.chapter = ChapterFactory(parent=self.module, slug='some-chapter')
# chapter has some content blocks a, b, c
self.a = ContentBlockFactory(parent=self.chapter, module=self.module, slug='cb-a')
self.b = ContentBlockFactory(parent=self.chapter, module=self.module, slug='cb-b')
self.title_visible = 'visible'
self.title_hidden = 'hidden'
self.title_custom = 'custom'
self.visible_content_block = ContentBlockFactory(parent=self.chapter, module=self.module,
title=self.title_visible, slug='cb-a')
self.hidden_content_block = ContentBlockFactory(parent=self.chapter, module=self.module, title=self.title_hidden, slug='cb-b')
# content block c is user created
self.c = ContentBlockFactory(parent=self.chapter, owner=self.teacher, user_created=True, module=self.module, slug='cb-c')
self.custom_content_block = ContentBlockFactory(parent=self.chapter, owner=self.teacher, user_created=True,
module=self.module, title=self.title_custom,
slug='cb-c')
# content block a and c are visible to school class X
self.b.hidden_for.add(skillbox_class)
self.c.visible_for.add(skillbox_class)
self.hidden_content_block.hidden_for.add(self.skillbox_class)
self.custom_content_block.visible_for.add(self.skillbox_class)
# chapter description is hidden for school class X
self.chapter.title_hidden_for.add(self.skillbox_class)
request = RequestFactory().get('/')
request.user = self.teacher
self.client = Client(schema=schema, context_value=request)
# we make a snapshot S of the module M
# snapshot S looks like module M for school class X
def test_visible_and_hidden(self):
def test_setup(self):
# make sure everything is setup correctly
result = self.client.execute(MODULE_QUERY, variables={
'slug': self.module.slug
})
self.assertIsNone(result.get('errors'))
module = result.get('data').get('module')
chapter = module.get('chapters').get('edges')[0]['node']
chapter = edges_to_array(module.get('chapters'))[0]
self.assertIsNotNone(chapter)
content_blocks = [edge['node'] for edge in chapter.get('contentBlocks').get('edges')]
content_blocks = edges_to_array(chapter.get('contentBlocks'))
content_block_ids = [node['id'] for node in content_blocks]
self.assertTrue(to_global_id('ContentBlockNode', self.a.id) in content_block_ids)
self.assertTrue(to_global_id('ContentBlockNode', self.b.id) in content_block_ids)
self.assertTrue(to_global_id('ContentBlockNode', self.c.id) in content_block_ids)
b = [node for node in content_blocks if node['id'] == to_global_id('ContentBlockNode', self.b.id)][0]
c = [node for node in content_blocks if node['id'] == to_global_id('ContentBlockNode', self.c.id)][0]
self.assertTrue('skillbox' in [edge['node']['name'] for edge in b.get('hiddenFor').get('edges')])
self.assertTrue('skillbox' in [edge['node']['name'] for edge in c.get('visibleFor').get('edges')])
self.assertTrue(to_global_id('ContentBlockNode', self.visible_content_block.id) in content_block_ids)
self.assertTrue(to_global_id('ContentBlockNode', self.hidden_content_block.id) in content_block_ids)
self.assertTrue(to_global_id('ContentBlockNode', self.custom_content_block.id) in content_block_ids)
b = [node for node in content_blocks if
node['id'] == to_global_id('ContentBlockNode', self.hidden_content_block.id)][0]
c = [node for node in content_blocks if
node['id'] == to_global_id('ContentBlockNode', self.custom_content_block.id)][0]
self.assertTrue('skillbox' in [school_class['name'] for school_class in edges_to_array(b.get('hiddenFor'))])
self.assertTrue('skillbox' in [school_class['name'] for school_class in edges_to_array(c.get('visibleFor'))])
def test_create_snapshot(self):
result = self.client.execute(CREATE_SNAPSHOT_MUTATION, variables={
'input': {
'module': to_global_id('ContentBlockNode', self.module.pk),
'selectedClass': to_global_id('SchoolClassNode', self.skillbox_class.pk),
}
})
self.assertIsNone(result.get('errors'))
snapshot = result.get('data').get('createSnapshot').get('snapshot')
chapter = snapshot.get('chapters').get('edges')[0]['node']
self.assertTrue(chapter['titleHidden'])
self.assertFalse(chapter['descriptionHidden'])
_, chapter_id = from_global_id(chapter['id'])
self.assertEqual(int(chapter_id), self.chapter.id)
content_blocks = [edge['node'] for edge in chapter['contentBlocks']['edges']]
self.assertEqual(len(content_blocks), 2)
self.assertEqual(content_blocks[0]['title'], self.title_visible)
self.assertEqual(content_blocks[1]['title'], self.title_custom)