Add mutation to enable solutions by module

This commit is contained in:
Ramon Wenger 2019-02-05 18:45:03 +01:00
parent 07785ae2c1
commit 4b93b410a5
9 changed files with 231 additions and 3 deletions

View File

@ -4,6 +4,7 @@ import factory
import wagtail_factories
from django.contrib.auth import get_user_model
from factory import CREATE_STRATEGY
from wagtail.core.models import Page
from wagtail.core.rich_text import RichText
from assignments.models import Assignment
@ -16,6 +17,24 @@ class BookFactory(BasePageFactory):
class Meta:
model = Book
@staticmethod
def create_default_structure():
site = wagtail_factories.SiteFactory.create(is_default_site=True)
Page.objects.get(title='Root').delete()
book = BookFactory.create(parent=site.root_page, title='A book')
topic = TopicFactory.create(parent=book, order=1, title='A topic')
module = ModuleFactory.create(parent=topic,
title="A module",
meta_title="Modul 1",
teaser="Whatever",
intro="<p>Hello</p>")
chapter = ChapterFactory.create(parent=module, title="A chapter")
content_block = ContentBlockFactory.create(parent=chapter, module=module, title="A content block", type="task",
contents=[])
return book, topic, module, chapter, content_block
class TopicFactory(BasePageFactory):
class Meta:
@ -170,7 +189,8 @@ class ContentBlockFactory(BasePageFactory):
'url')] = 'https://picsum.photos/400/?random={}'.format(
''.join(random.choice('abcdefghiklmn') for _ in range(6)))
elif block_type == 'solution':
kwargs['{}__{}__{}__{}'.format(stream_field_name, i, 'solution', 'text')] = RichText(fake_paragraph())
kwargs['{}__{}__{}__{}'.format(stream_field_name, i, 'solution', 'text')] = RichText(
fake_paragraph())
@classmethod
def create(cls, module, **kwargs):

View File

@ -0,0 +1,30 @@
# Generated by Django 2.0.6 on 2019-02-05 15:29
import assignments.models
from django.conf import settings
from django.db import migrations, models
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.images.blocks
import wagtail.snippets.blocks
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('books', '0006_auto_20181204_1629'),
]
operations = [
migrations.AddField(
model_name='module',
name='solutions_enabled_by',
field=models.ManyToManyField(to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='contentblock',
name='contents',
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock())])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock()), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock())], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())]))], blank=True, null=True),
),
]

View File

@ -72,4 +72,6 @@ class ContentBlock(StrictHierarchyPage):
parent_page_types = ['books.Chapter']
subpage_types = []
@property
def module(self):
return self.get_parent().get_parent().specific

View File

@ -7,6 +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
logger = logging.getLogger(__name__)
@ -31,6 +32,8 @@ class Module(StrictHierarchyPage):
teaser = models.TextField()
intro = RichTextField(features=DEFAULT_RICH_TEXT_FEATURES)
solutions_enabled_by = models.ManyToManyField(User)
content_panels = [
FieldPanel('title', classname="full title"),
FieldPanel('meta_title', classname="full title"),
@ -55,3 +58,4 @@ class Module(StrictHierarchyPage):
def get_child_ids(self):
return self.get_children().values_list('id', flat=True)

View File

@ -1,7 +1,9 @@
from books.schema.mutations.contentblock import MutateContentBlock, AddContentBlock, DeleteContentBlock
from books.schema.mutations.module import UpdateSolutionVisibility
class BookMutations(object):
mutate_content_block = MutateContentBlock.Field()
add_content_block = AddContentBlock.Field()
delete_content_block = DeleteContentBlock.Field()
update_solution_visibility = UpdateSolutionVisibility.Field()

View File

@ -0,0 +1,39 @@
import graphene
from graphene import relay
from api.utils import get_errors, get_object
from books.models import Module
class UpdateSolutionVisibility(relay.ClientIDMutation):
class Input:
id = graphene.ID()
enabled = graphene.Boolean()
success = graphene.Boolean()
errors = graphene.List(graphene.String)
@classmethod
def mutate_and_get_payload(cls, root, info, **args):
try:
id = args.get('id')
enabled = args.get('enabled')
user = info.context.user
if 'users.can_manage_school_class_content' not in user.get_role_permissions():
raise PermissionError()
module = get_object(Module, id)
if enabled:
module.solutions_enabled_by.add(user)
else:
module.solutions_enabled_by.remove(user)
module.save()
return cls(success=True)
except PermissionError:
errors = ["You don't have the permission to do that."]
except Exception as e:
errors = ['Error: {}'.format(e)]
return cls(success=False, errors=errors)

View File

@ -4,9 +4,15 @@ from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from api.utils import get_object
from users.models import User, Role
from ..models import Book, Topic, Module, Chapter, ContentBlock
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 teacher is not None and module.solutions_enabled_by.filter(pk=teacher.pk).exists()
class ContentBlockNode(DjangoObjectType):
mine = graphene.Boolean()
@ -23,6 +29,13 @@ class ContentBlockNode(DjangoObjectType):
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):
if 'users.can_manage_school_class_content' not in info.context.user.get_role_permissions() \
and not are_solutions_enabled_for(info.context.user, self.module):
self.contents.stream_data = [content for content in self.contents.stream_data if
content['type'] != 'solution']
return self.contents
class ChapterNode(DjangoObjectType):
content_blocks = DjangoFilterConnectionField(ContentBlockNode)

View File

@ -22,7 +22,7 @@ class NewContentBlockMutationTest(TestCase):
request.user = user
self.client = Client(schema=schema, context_value=request)
self.sibling_id = to_global_id('ContentBlock', content_block.pk)
self.sibling_id = to_global_id('ContentBlockNode', content_block.pk)
def test_add_new_content_block(self):
self.assertEqual(ContentBlock.objects.count(), 1)

View File

@ -0,0 +1,118 @@
from django.test import TestCase, RequestFactory
from graphene.test import Client
from graphql_relay import to_global_id
from api.schema import schema
from books.factories import BookFactory, TopicFactory, ModuleFactory, ChapterFactory, ContentBlockFactory
from books.models import ContentBlock
from users.models import User
from users.services import create_users
class ModuleSolutionVisibilityTest(TestCase):
def setUp(self):
create_users()
_, _, self.module, chapter, _ = BookFactory.create_default_structure()
content = {
'type': 'solution',
'value': {
'text': '<p>Das ist eine Lösung.</p>'
}
}
content_block = ContentBlockFactory.create(
parent=chapter,
module=self.module,
title="Another content block",
type="task",
contents=[content]
)
self.teacher = User.objects.get(username="teacher")
self.student = User.objects.get(username="student1")
student_request = RequestFactory().get('/')
student_request.user = self.student
self.student_client = Client(schema=schema, context_value=student_request)
teacher_request = RequestFactory().get('/')
teacher_request.user = self.teacher
self.teacher_client = Client(schema=schema, context_value=teacher_request)
self.content_block_id = to_global_id('ContentBlockNode', content_block.pk)
self.update_mutation = mutation = """
mutation UpdateSolutionVisibility($input: UpdateSolutionVisibilityInput!) {
updateSolutionVisibility(input: $input) {
success
}
}
"""
self.query = """
query ContentBlockQuery($id: ID!) {
contentBlock(id: $id) {
contents
title
}
}
"""
def test_hide_solutions_for_students_and_then_show_them(self):
result = self.student_client.execute(self.query, variables={
'id': self.content_block_id
})
self.assertIsNone(result.get('errors'))
self.assertEqual(len(result.get('data').get('contentBlock').get('contents')), 0)
result = self.teacher_client.execute(self.query, variables={
'id': self.content_block_id
})
self.assertIsNone(result.get('errors'))
self.assertEqual(len(result.get('data').get('contentBlock').get('contents')), 1)
module_id = to_global_id('ModuleNode', self.module.pk)
result = self.teacher_client.execute(self.update_mutation, variables={
'input': {
'id': module_id,
'enabled': True
}
})
self.assertEqual(result.get('data').get('updateSolutionVisibility').get('success'), True)
self.assertEqual(self.module.solutions_enabled_by.filter(pk=self.teacher.pk).count(), 1)
result = self.student_client.execute(self.query, variables={
'id': self.content_block_id
})
self.assertIsNone(result.get('errors'))
self.assertEqual(len(result.get('data').get('contentBlock').get('contents')), 1)
def test_try_to_show_solutions_as_student_and_fail(self):
result = self.student_client.execute(self.query, variables={
'id': self.content_block_id
})
self.assertEqual(len(result.get('data').get('contentBlock').get('contents')), 0)
module_id = to_global_id('ModuleNode', self.module.pk)
result = self.student_client.execute(self.update_mutation, variables={
'input': {
'id': module_id,
'enabled': True
}
})
self.assertIsNone(result.get('errors'))
self.assertFalse(result.get('data').get('success'))
result = self.student_client.execute(self.query, variables={
'id': self.content_block_id
})
self.assertEqual(len(result.get('data').get('contentBlock').get('contents')), 0)