From 4bdcdd8774ace6f73ee807664e7206f6c2df452f Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Sat, 8 May 2021 23:19:58 +0200 Subject: [PATCH] Add share snapshot mutation, including unit tests --- .../books/migrations/0028_snapshot_shared.py | 18 +++++ server/books/models/snapshot.py | 1 + server/books/schema/mutations/__init__.py | 3 +- server/books/schema/mutations/snapshot.py | 25 +++++- server/books/schema/nodes/module.py | 2 +- ...t_create_snapshot.py => test_snapshots.py} | 76 +++++++++++++++++++ 6 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 server/books/migrations/0028_snapshot_shared.py rename server/books/tests/{test_create_snapshot.py => test_snapshots.py} (80%) diff --git a/server/books/migrations/0028_snapshot_shared.py b/server/books/migrations/0028_snapshot_shared.py new file mode 100644 index 00000000..b574b85a --- /dev/null +++ b/server/books/migrations/0028_snapshot_shared.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.22 on 2021-05-08 20:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('books', '0027_auto_20210429_1444'), + ] + + operations = [ + migrations.AddField( + model_name='snapshot', + name='shared', + field=models.BooleanField(default=False), + ), + ] diff --git a/server/books/models/snapshot.py b/server/books/models/snapshot.py index c7ddfa67..8125dbd2 100644 --- a/server/books/models/snapshot.py +++ b/server/books/models/snapshot.py @@ -71,6 +71,7 @@ class Snapshot(models.Model): ) created = models.DateTimeField(auto_now_add=True) creator = models.ForeignKey(get_user_model(), on_delete=models.SET_NULL, null=True) + shared = models.BooleanField(default=False) objects = SnapshotManager() diff --git a/server/books/schema/mutations/__init__.py b/server/books/schema/mutations/__init__.py index 92cd8087..fab3c1e9 100644 --- a/server/books/schema/mutations/__init__.py +++ b/server/books/schema/mutations/__init__.py @@ -1,7 +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, ApplySnapshot +from books.schema.mutations.snapshot import CreateSnapshot, ApplySnapshot, ShareSnapshot from books.schema.mutations.topic import UpdateLastTopic @@ -16,3 +16,4 @@ class BookMutations(object): sync_module_visibility = SyncModuleVisibility.Field() create_snapshot = CreateSnapshot.Field() apply_snapshot = ApplySnapshot.Field() + share_snapshot = ShareSnapshot.Field() diff --git a/server/books/schema/mutations/snapshot.py b/server/books/schema/mutations/snapshot.py index 5700ff67..5688855c 100644 --- a/server/books/schema/mutations/snapshot.py +++ b/server/books/schema/mutations/snapshot.py @@ -45,7 +45,7 @@ class ApplySnapshot(relay.ClientIDMutation): user = info.context.user selected_class_id = args.get('selected_class') selected_class = get_object(SchoolClass, selected_class_id) - #reset everything + # reset everything for chapter in Chapter.get_by_parent(snapshot.module): cb_qs = ContentBlock.get_by_parent(chapter) without_owner = Q(owner__isnull=True) @@ -55,7 +55,7 @@ class ApplySnapshot(relay.ClientIDMutation): cb.hidden_for.remove(selected_class) for cb in cb_qs.filter(owner_user): cb.visible_for.remove(selected_class) - #apply snapshot + # apply snapshot if not selected_class.users.filter(username=user.username).exists() or not user.is_teacher(): raise PermissionError('Not allowed') for content_block in snapshot.hidden_content_blocks.all(): @@ -69,3 +69,24 @@ class ApplySnapshot(relay.ClientIDMutation): if chapter_snapshot.description_hidden: chapter.description_hidden_for.add(selected_class) return cls(success=True, module=snapshot.module) + + +class ShareSnapshot(relay.ClientIDMutation): + class Input: + snapshot = graphene.ID(required=True) + shared = graphene.Boolean(required=True) + + success = graphene.Boolean(required=True) + snapshot = graphene.Field(SnapshotNode) + + @classmethod + def mutate_and_get_payload(cls, root, info, **args): + snapshot_id = args.get('snapshot') + shared = args.get('shared') + user = info.context.user + snapshot = get_object(Snapshot, snapshot_id) + if snapshot.creator != user: + raise PermissionError('Not permitted') + snapshot.shared = shared + snapshot.save() + return cls(success=True, snapshot=snapshot) diff --git a/server/books/schema/nodes/module.py b/server/books/schema/nodes/module.py index 051d3faa..7156658a 100644 --- a/server/books/schema/nodes/module.py +++ b/server/books/schema/nodes/module.py @@ -86,7 +86,7 @@ class ModuleNode(DjangoObjectType): @staticmethod def resolve_snapshots(parent, info, **kwargs): - return parent.snapshots.all() + return parent.snapshots.filter(creator=info.context.user) class RecentModuleNode(DjangoObjectType): diff --git a/server/books/tests/test_create_snapshot.py b/server/books/tests/test_snapshots.py similarity index 80% rename from server/books/tests/test_create_snapshot.py rename to server/books/tests/test_snapshots.py index ce2c9022..e0a34ff8 100644 --- a/server/books/tests/test_create_snapshot.py +++ b/server/books/tests/test_snapshots.py @@ -85,6 +85,32 @@ query SnapshotDetail($id: ID!) { } """ +SHARE_SNAPSHOT_MUTATION = """ +mutation ShareSnapshot($input: ShareSnapshotInput!) { + shareSnapshot(input: $input) { + success + snapshot { + shared + } + } +} +""" + +MODULE_SNAPSHOTS_QUERY = """ +query SnapshotQuery($slug: String!) { + module(slug: $slug) { + snapshots { + id + title + created + creator { + username + } + } + } +} +""" + def edges_to_array(entity): return [edge['node'] for edge in entity.get('edges')] @@ -263,3 +289,53 @@ class CreateSnapshotTestCase(SkillboxTestCase): self.assertTrue(self.skillbox_class.name not in [sc['name'] for sc in cb1['hiddenFor']]) self.assertTrue(self.skillbox_class.name not in [sc['name'] for sc in cb2['hiddenFor']]) self.assertTrue(self.skillbox_class.name not in [sc['name'] for sc in cb3['visibleFor']]) + + +class SnapshotTestCase(SkillboxTestCase): + def setUp(self) -> None: + self.createDefault() + self.client = self.get_client() + self.slug = 'some-module' + + self.teacher2 = User.objects.get(username='teacher2') + self.module = ModuleFactory(slug=self.slug) + self.skillbox_class = SchoolClass.objects.get(name='skillbox') + self.snapshot = Snapshot.objects.create_snapshot(module=self.module, school_class=self.skillbox_class, + user=self.teacher) + Snapshot.objects.create_snapshot(module=self.module, school_class=self.skillbox_class, + user=self.teacher2) + + def test_show_only_own_snapshots(self): + result = self.client.execute(MODULE_SNAPSHOTS_QUERY, variables={ + "slug": self.slug + }) + self.assertIsNone(result.get('errors')) + snapshots = result['data']['module']['snapshots'] + self.assertEqual(len(snapshots), 1) + self.assertEqual(snapshots[0]['creator']['username'], 'teacher') + + def test_share_snapshot(self): + self.assertFalse(self.snapshot.shared) + result = self.client.execute(SHARE_SNAPSHOT_MUTATION, variables={ + 'input': { + 'snapshot': to_global_id('Snapshot', self.snapshot.id), + 'shared': True + } + }) + self.assertIsNone(result.get('errors')) + data = result['data']['shareSnapshot'] + self.assertTrue(data['success']) + self.assertTrue(data['snapshot']['shared']) + snapshot = Snapshot.objects.get(pk=self.snapshot.pk) + self.assertTrue(snapshot.shared) + + def test_dont_share_foreign_snapshot(self): + self.assertFalse(self.snapshot.shared) + teacher2_client = self.get_client(self.teacher2) + result = teacher2_client.execute(SHARE_SNAPSHOT_MUTATION, variables={ + 'input': { + 'snapshot': to_global_id('Snapshot', self.snapshot.id), + 'shared': True + } + }) + self.assertIsNotNone(result.get('errors'))