Add method for syncing school classes
This commit is contained in:
parent
14022fa9eb
commit
209838dadb
|
|
@ -10,8 +10,6 @@ from books.blocks import DEFAULT_RICH_TEXT_FEATURES
|
||||||
from core.wagtail_utils import StrictHierarchyPage
|
from core.wagtail_utils import StrictHierarchyPage
|
||||||
from users.models import SchoolClass
|
from users.models import SchoolClass
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Module(StrictHierarchyPage):
|
class Module(StrictHierarchyPage):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -60,6 +58,34 @@ 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)
|
||||||
|
|
||||||
|
def sync_from_school_class(self, school_class_pattern, school_class_to_sync):
|
||||||
|
# import here so we don't get a circular import error
|
||||||
|
from books.models import Chapter, ContentBlock
|
||||||
|
|
||||||
|
# get chapters of module
|
||||||
|
chapters = Chapter.get_by_parent(self)
|
||||||
|
content_block_query = ContentBlock.objects.none()
|
||||||
|
|
||||||
|
# get content blocks of chapters
|
||||||
|
for chapter in chapters:
|
||||||
|
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`
|
||||||
|
for content_block in school_class_to_sync.hidden_content_blocks.intersection(content_block_query):
|
||||||
|
content_block.hidden_for.remove(school_class_to_sync)
|
||||||
|
for content_block in school_class_to_sync.visible_content_blocks.intersection(content_block_query):
|
||||||
|
content_block.visible_for.remove(school_class_to_sync)
|
||||||
|
|
||||||
|
# get all content blocks with `hidden for` for class `school_class_pattern`
|
||||||
|
for content_block in school_class_pattern.hidden_content_blocks.intersection(content_block_query):
|
||||||
|
# add `school_class_to_sync` to these blocks' `hidden for`
|
||||||
|
content_block.hidden_for.add(school_class_to_sync)
|
||||||
|
|
||||||
|
# get all content blocks with `visible for` for class `school_class_pattern`
|
||||||
|
for content_block in school_class_pattern.visible_content_blocks.intersection(content_block_query):
|
||||||
|
# add `school_class_to_sync` to these blocks' `visible for`
|
||||||
|
content_block.visible_for.add(school_class_to_sync)
|
||||||
|
|
||||||
|
|
||||||
class RecentModule(models.Model):
|
class RecentModule(models.Model):
|
||||||
module = models.ForeignKey(Module, on_delete=models.CASCADE, related_name='recent_modules')
|
module = models.ForeignKey(Module, on_delete=models.CASCADE, related_name='recent_modules')
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,46 @@ from users.factories import SchoolClassFactory
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from users.services import create_users
|
from users.services import create_users
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
CONTENT_BLOCK_QUERY = """
|
||||||
|
query ContentBlockQuery($id: ID!) {
|
||||||
|
contentBlock(id: $id) {
|
||||||
|
hiddenFor {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visibleFor {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class CopyVisibilityForClassesTestCase(TestCase):
|
class CopyVisibilityForClassesTestCase(TestCase):
|
||||||
|
"""
|
||||||
|
what do we want to happen?
|
||||||
|
we have 3 public content blocks [X, Y, Z]
|
||||||
|
we have 2 custom content block [M, N]
|
||||||
|
we have 2 school classes [A, B]
|
||||||
|
one public content block is hidden for class A | [X, Y]
|
||||||
|
one custom content block is visible for class A | [M]
|
||||||
|
class B also sees two of three public content blocks, but one is different from what A sees | [X, Z]
|
||||||
|
class B doesn't see the custom content block, but another one | [N]
|
||||||
|
so A sees | [X, Y, M]
|
||||||
|
B sees | [X, Z, N]
|
||||||
|
we want to copy the settings from class A to class B
|
||||||
|
now class B sees the same content blocks as class A | [X, Y, N]
|
||||||
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
module = ModuleFactory()
|
module = ModuleFactory()
|
||||||
chapter = Chapter(title='Some Chapter')
|
chapter = Chapter(title='Some Chapter')
|
||||||
|
|
@ -22,16 +59,28 @@ class CopyVisibilityForClassesTestCase(TestCase):
|
||||||
teacher = User.objects.get(username='teacher')
|
teacher = User.objects.get(username='teacher')
|
||||||
student1 = User.objects.get(username='student1')
|
student1 = User.objects.get(username='student1')
|
||||||
student2 = User.objects.get(username='student2')
|
student2 = User.objects.get(username='student2')
|
||||||
school_class1 = SchoolClassFactory(name='hidden-class', users=[teacher, student1])
|
# school class to be used as the pattern or model
|
||||||
school_class2 = SchoolClassFactory(name='default-class', users=[teacher, student2])
|
template_school_class = SchoolClassFactory(name='template-class', users=[teacher, student1])
|
||||||
|
# school class to be synced, e.g. adapted to be like the other
|
||||||
|
school_class_to_be_synced = SchoolClassFactory(name='class-to-be-synced', users=[teacher, student2])
|
||||||
|
|
||||||
default_content_block = ContentBlock(title='default block', slug='default')
|
default_content_block = ContentBlock(title='default block', slug='default')
|
||||||
hidden_content_block = ContentBlock(title='hidden block', slug='hidden')
|
hidden_content_block = ContentBlock(title='hidden block', slug='hidden')
|
||||||
|
other_hidden_content_block = ContentBlock(title='other hidden block', slug='other-hidden')
|
||||||
|
custom_content_block = ContentBlock(title='custom block', slug='custom', owner=teacher)
|
||||||
|
other_custom_content_block = ContentBlock(title='other custom block', slug='other-custom', owner=teacher)
|
||||||
|
|
||||||
chapter.specific.add_child(instance=default_content_block)
|
chapter.specific.add_child(instance=default_content_block)
|
||||||
chapter.specific.add_child(instance=hidden_content_block)
|
chapter.specific.add_child(instance=hidden_content_block)
|
||||||
|
chapter.specific.add_child(instance=custom_content_block)
|
||||||
|
chapter.specific.add_child(instance=other_custom_content_block)
|
||||||
|
chapter.specific.add_child(instance=other_hidden_content_block)
|
||||||
|
|
||||||
hidden_content_block.hidden_for.add(school_class1)
|
hidden_content_block.hidden_for.add(template_school_class)
|
||||||
|
custom_content_block.visible_for.add(template_school_class)
|
||||||
|
|
||||||
|
other_hidden_content_block.hidden_for.add(school_class_to_be_synced)
|
||||||
|
other_custom_content_block.visible_for.add(school_class_to_be_synced)
|
||||||
|
|
||||||
teacher_request = RequestFactory().get('/')
|
teacher_request = RequestFactory().get('/')
|
||||||
teacher_request.user = teacher
|
teacher_request.user = teacher
|
||||||
|
|
@ -45,9 +94,15 @@ class CopyVisibilityForClassesTestCase(TestCase):
|
||||||
student2_request.user = student2
|
student2_request.user = student2
|
||||||
self.student2_client = Client(schema=schema, context_value=student2_request)
|
self.student2_client = Client(schema=schema, context_value=student2_request)
|
||||||
|
|
||||||
|
self.template_school_class = template_school_class
|
||||||
|
self.school_class_to_be_synced = school_class_to_be_synced
|
||||||
|
self.module = module
|
||||||
self.chapter = to_global_id('ChapterNode', chapter.pk)
|
self.chapter = to_global_id('ChapterNode', chapter.pk)
|
||||||
self.default_content_block = to_global_id('ContentBlockNode', default_content_block.pk)
|
self.default_content_block = to_global_id('ContentBlockNode', default_content_block.pk)
|
||||||
self.hidden_content_block = to_global_id('ContentBlockNode', hidden_content_block.pk)
|
self.hidden_content_block = to_global_id('ContentBlockNode', hidden_content_block.pk)
|
||||||
|
self.custom_content_block = to_global_id('ContentBlockNode', custom_content_block.pk)
|
||||||
|
self.other_custom_content_block = to_global_id('ContentBlockNode', other_custom_content_block.pk)
|
||||||
|
self.other_hidden_content_block = to_global_id('ContentBlockNode', other_hidden_content_block.pk)
|
||||||
|
|
||||||
def _get_result(self, query, client, id):
|
def _get_result(self, query, client, id):
|
||||||
result = client.execute(query, variables={
|
result = client.execute(query, variables={
|
||||||
|
|
@ -55,29 +110,63 @@ class CopyVisibilityForClassesTestCase(TestCase):
|
||||||
})
|
})
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def test_hidden_for_set_correctly(self):
|
def test_hidden_for_and_visible_for_set_correctly(self):
|
||||||
self.assertEqual(ContentBlock.objects.count(), 2)
|
self.assertEqual(ContentBlock.objects.count(), 5)
|
||||||
|
|
||||||
query = """
|
hidden_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.hidden_content_block)
|
||||||
query ContentBlockQuery($id: ID!) {
|
hidden_for = hidden_result.get('data').get('contentBlock').get('hiddenFor').get('edges')
|
||||||
contentBlock(id: $id) {
|
self.assertTrue('template-class' in map(lambda x: x['node']['name'], hidden_for))
|
||||||
hiddenFor {
|
self.assertFalse('class-to-be-synced' in map(lambda x: x['node']['name'], hidden_for))
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = self._get_result(query, self.teacher_client, self.hidden_content_block)
|
other_hidden_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client,
|
||||||
logger.info(result)
|
self.other_hidden_content_block)
|
||||||
hiddenFor = result.get('data').get('contentBlock').get('hiddenFor').get('edges')
|
hidden_for = other_hidden_result.get('data').get('contentBlock').get('hiddenFor').get('edges')
|
||||||
logger.info(hiddenFor)
|
self.assertFalse('template-class' in map(lambda x: x['node']['name'], hidden_for))
|
||||||
self.assertTrue('hidden-class' in map(lambda x: x['node']['name'], hiddenFor))
|
self.assertTrue('class-to-be-synced' in map(lambda x: x['node']['name'], hidden_for))
|
||||||
self.assertFalse('default-class' in map(lambda x: x['node']['name'], hiddenFor))
|
|
||||||
|
|
||||||
|
default_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.default_content_block)
|
||||||
|
hidden_for = default_result.get('data').get('contentBlock').get('hiddenFor').get('edges')
|
||||||
|
self.assertEqual(len(hidden_for), 0)
|
||||||
|
|
||||||
|
custom_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.custom_content_block)
|
||||||
|
visible_for = custom_result.get('data').get('contentBlock').get('visibleFor').get('edges')
|
||||||
|
self.assertTrue('template-class' in map(lambda x: x['node']['name'], visible_for))
|
||||||
|
self.assertFalse('class-to-be-synced' in map(lambda x: x['node']['name'], visible_for))
|
||||||
|
|
||||||
|
other_custom_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client,
|
||||||
|
self.other_custom_content_block)
|
||||||
|
visible_for = other_custom_result.get('data').get('contentBlock').get('visibleFor').get('edges')
|
||||||
|
self.assertFalse('template-class' in map(lambda x: x['node']['name'], visible_for))
|
||||||
|
self.assertTrue('class-to-be-synced' in map(lambda x: x['node']['name'], visible_for))
|
||||||
|
|
||||||
|
def test_syncs_correctly(self):
|
||||||
|
self.module.sync_from_school_class(self.template_school_class, self.school_class_to_be_synced)
|
||||||
|
|
||||||
|
# the hidden block is hidden for both now
|
||||||
|
hidden_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.hidden_content_block)
|
||||||
|
hidden_for = hidden_result.get('data').get('contentBlock').get('hiddenFor').get('edges')
|
||||||
|
self.assertTrue('template-class' in map(lambda x: x['node']['name'], hidden_for))
|
||||||
|
self.assertTrue('class-to-be-synced' in map(lambda x: x['node']['name'], hidden_for))
|
||||||
|
|
||||||
|
# the other hidden block is hidden for no one now
|
||||||
|
other_hidden_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client,
|
||||||
|
self.other_hidden_content_block)
|
||||||
|
hidden_for = other_hidden_result.get('data').get('contentBlock').get('hiddenFor').get('edges')
|
||||||
|
self.assertEqual(len(hidden_for), 0)
|
||||||
|
|
||||||
|
# the default block is still hidden for no one
|
||||||
|
default_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.default_content_block)
|
||||||
|
hidden_for = default_result.get('data').get('contentBlock').get('hiddenFor').get('edges')
|
||||||
|
self.assertEqual(len(hidden_for), 0)
|
||||||
|
|
||||||
|
# the custom block is visible for both
|
||||||
|
custom_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.custom_content_block)
|
||||||
|
visible_for = custom_result.get('data').get('contentBlock').get('visibleFor').get('edges')
|
||||||
|
self.assertTrue('template-class' in map(lambda x: x['node']['name'], visible_for))
|
||||||
|
self.assertTrue('class-to-be-synced' in map(lambda x: x['node']['name'], visible_for))
|
||||||
|
|
||||||
|
# the other custom block is visible for no one
|
||||||
|
other_custom_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client,
|
||||||
|
self.other_custom_content_block)
|
||||||
|
visible_for = other_custom_result.get('data').get('contentBlock').get('visibleFor').get('edges')
|
||||||
|
self.assertEqual(len(visible_for), 0)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue