From ab9da652cf01292a32d92aa3734de07e641eada0 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Mon, 2 Mar 2020 17:37:29 +0100 Subject: [PATCH] Allow multiple teachers per school class For that purpose, now the enabled solutions are stored on a per school class basis instead of a per teacher basis --- .../0017_module_solutions_enabled_for.py | 19 +++++++++++++++++++ ...0018_remove_module_solutions_enabled_by.py | 17 +++++++++++++++++ server/books/models/module.py | 4 ++-- server/books/schema/mutations/module.py | 5 +++-- server/books/schema/queries.py | 4 ++-- .../tests/test_module_solution_visibility.py | 19 ++++++++----------- server/books/utils.py | 4 ++-- .../core/management/commands/hidesolutions.py | 2 +- .../migrations/0008_auto_20200302_1613.py | 17 +++++++++++++++++ .../migrations/0011_auto_20200302_1613.py | 17 +++++++++++++++++ 10 files changed, 88 insertions(+), 20 deletions(-) create mode 100644 server/books/migrations/0017_module_solutions_enabled_for.py create mode 100644 server/books/migrations/0018_remove_module_solutions_enabled_by.py create mode 100644 server/rooms/migrations/0008_auto_20200302_1613.py create mode 100644 server/users/migrations/0011_auto_20200302_1613.py diff --git a/server/books/migrations/0017_module_solutions_enabled_for.py b/server/books/migrations/0017_module_solutions_enabled_for.py new file mode 100644 index 00000000..0b75c9ee --- /dev/null +++ b/server/books/migrations/0017_module_solutions_enabled_for.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1.15 on 2020-03-02 16:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0011_auto_20200302_1613'), + ('books', '0016_auto_20191128_1601'), + ] + + operations = [ + migrations.AddField( + model_name='module', + name='solutions_enabled_for', + field=models.ManyToManyField(to='users.SchoolClass'), + ), + ] diff --git a/server/books/migrations/0018_remove_module_solutions_enabled_by.py b/server/books/migrations/0018_remove_module_solutions_enabled_by.py new file mode 100644 index 00000000..d3ff22c6 --- /dev/null +++ b/server/books/migrations/0018_remove_module_solutions_enabled_by.py @@ -0,0 +1,17 @@ +# Generated by Django 2.1.15 on 2020-03-02 16:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('books', '0017_module_solutions_enabled_for'), + ] + + operations = [ + migrations.RemoveField( + model_name='module', + name='solutions_enabled_by', + ), + ] diff --git a/server/books/models/module.py b/server/books/models/module.py index 4d750aa9..97ff8051 100644 --- a/server/books/models/module.py +++ b/server/books/models/module.py @@ -7,7 +7,7 @@ from wagtail.images.edit_handlers import ImageChooserPanel from books.blocks import DEFAULT_RICH_TEXT_FEATURES from core.wagtail_utils import StrictHierarchyPage -from users.models import User +from users.models import SchoolClass logger = logging.getLogger(__name__) @@ -32,7 +32,7 @@ class Module(StrictHierarchyPage): teaser = models.TextField() intro = RichTextField(features=DEFAULT_RICH_TEXT_FEATURES) - solutions_enabled_by = models.ManyToManyField(User) + solutions_enabled_for = models.ManyToManyField(SchoolClass) content_panels = [ FieldPanel('title', classname="full title"), diff --git a/server/books/schema/mutations/module.py b/server/books/schema/mutations/module.py index 15479642..1165f2d6 100644 --- a/server/books/schema/mutations/module.py +++ b/server/books/schema/mutations/module.py @@ -21,14 +21,15 @@ class UpdateSolutionVisibility(relay.ClientIDMutation): slug = args.get('slug') enabled = args.get('enabled') user = info.context.user + selected_class = user.selected_class() if 'users.can_manage_school_class_content' not in user.get_role_permissions(): raise PermissionError() module = Module.objects.get(slug=slug) if enabled: - module.solutions_enabled_by.add(user) + module.solutions_enabled_for.add(selected_class) else: - module.solutions_enabled_by.remove(user) + module.solutions_enabled_for.remove(selected_class) module.save() return cls(success=True, solutions_enabled=enabled) diff --git a/server/books/schema/queries.py b/server/books/schema/queries.py index bb69fa52..ff0d849b 100644 --- a/server/books/schema/queries.py +++ b/server/books/schema/queries.py @@ -145,8 +145,8 @@ class ModuleNode(DjangoObjectType): return self.get_parent().specific def resolve_solutions_enabled(self, info, **kwargs): - teacher = info.context.user.get_teacher() - return self.solutions_enabled_by.filter(pk=teacher.pk).exists() if teacher is not None else False + 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( diff --git a/server/books/tests/test_module_solution_visibility.py b/server/books/tests/test_module_solution_visibility.py index 3b3fcd7b..e9b769b9 100644 --- a/server/books/tests/test_module_solution_visibility.py +++ b/server/books/tests/test_module_solution_visibility.py @@ -3,9 +3,9 @@ from graphene.test import Client from graphql_relay import to_global_id from api.schema import schema +from api.utils import get_graphql_mutation -from books.factories import BookFactory, TopicFactory, ModuleFactory, ChapterFactory, ContentBlockFactory -from books.models import ContentBlock +from books.factories import BookFactory, ContentBlockFactory from users.models import User from users.services import create_users @@ -30,6 +30,7 @@ class ModuleSolutionVisibilityTest(TestCase): ) self.teacher = User.objects.get(username="teacher") + self.selected_class = self.teacher.selected_class() self.student = User.objects.get(username="student1") student_request = RequestFactory().get('/') student_request.user = self.student @@ -41,13 +42,7 @@ class ModuleSolutionVisibilityTest(TestCase): self.content_block_id = to_global_id('ContentBlockNode', content_block.pk) - self.update_mutation = mutation = """ - mutation UpdateSolutionVisibility($input: UpdateSolutionVisibilityInput!) { - updateSolutionVisibility(input: $input) { - success - } - } - """ + self.update_mutation = get_graphql_mutation('updateSolutionVisibility.gql') self.query = """ query ContentBlockQuery($id: ID!) { @@ -59,6 +54,8 @@ class ModuleSolutionVisibilityTest(TestCase): """ def test_hide_solutions_for_students_and_then_show_them(self): + self.assertEqual(self.student.selected_class(), self.teacher.selected_class()) + result = self.student_client.execute(self.query, variables={ 'id': self.content_block_id }) @@ -70,7 +67,7 @@ class ModuleSolutionVisibilityTest(TestCase): 'id': self.content_block_id }) self.assertIsNone(result.get('errors')) - self.assertEqual(len(result.get('data').get('contentBlock').get('contents')), 1) + self.assertEqual(len(result.get('data').get('contentBlock').get('contents')), 0) result = self.teacher_client.execute(self.update_mutation, variables={ 'input': { @@ -81,7 +78,7 @@ class ModuleSolutionVisibilityTest(TestCase): self.assertEqual(result.get('data').get('updateSolutionVisibility').get('success'), True) - self.assertEqual(self.module.solutions_enabled_by.filter(pk=self.teacher.pk).count(), 1) + self.assertEqual(self.module.solutions_enabled_for.filter(pk=self.selected_class.pk).count(), 1) result = self.student_client.execute(self.query, variables={ 'id': self.content_block_id diff --git a/server/books/utils.py b/server/books/utils.py index 9ba67c98..70a5082a 100644 --- a/server/books/utils.py +++ b/server/books/utils.py @@ -4,8 +4,8 @@ from users.models import User, Role def are_solutions_enabled_for(user: User, module: Module): teacher = user.users_in_same_school_class().filter(user_roles__role=Role.objects.get_default_teacher_role()).first() - return 'users.can_manage_school_class_content' in user.get_role_permissions() or user.is_superuser or ( - teacher is not None and module.solutions_enabled_by.filter(pk=teacher.pk).exists()) + school_class = user.selected_class() + return module.solutions_enabled_for.filter(pk=school_class.pk).exists() def get_type_and_value(data): diff --git a/server/core/management/commands/hidesolutions.py b/server/core/management/commands/hidesolutions.py index 9d3c020a..6f6a85f9 100644 --- a/server/core/management/commands/hidesolutions.py +++ b/server/core/management/commands/hidesolutions.py @@ -8,5 +8,5 @@ class Command(BaseCommand): self.stdout.write("Hiding solutions") for module in Module.objects.all(): self.stdout.write("Hiding solutions in module {}.".format(module.title)) - module.solutions_enabled_by.clear() + module.solutions_enabled_for.clear() self.stdout.write("Hiding done") diff --git a/server/rooms/migrations/0008_auto_20200302_1613.py b/server/rooms/migrations/0008_auto_20200302_1613.py new file mode 100644 index 00000000..ad949e3f --- /dev/null +++ b/server/rooms/migrations/0008_auto_20200302_1613.py @@ -0,0 +1,17 @@ +# Generated by Django 2.1.15 on 2020-03-02 16:13 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('rooms', '0007_auto_20190808_0649'), + ] + + operations = [ + migrations.AlterModelOptions( + name='moduleroomslug', + options={'verbose_name': 'Slug für Modul-Raum', 'verbose_name_plural': 'Slugs für Modul-Raum'}, + ), + ] diff --git a/server/users/migrations/0011_auto_20200302_1613.py b/server/users/migrations/0011_auto_20200302_1613.py new file mode 100644 index 00000000..0b7be76f --- /dev/null +++ b/server/users/migrations/0011_auto_20200302_1613.py @@ -0,0 +1,17 @@ +# Generated by Django 2.1.15 on 2020-03-02 16:13 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0010_schoolclass_code'), + ] + + operations = [ + migrations.AlterModelOptions( + name='schoolclass', + options={'verbose_name': 'Schulklasse', 'verbose_name_plural': 'Schulklassen'}, + ), + ]