Add mutation to enable solutions by module
This commit is contained in:
parent
07785ae2c1
commit
4b93b410a5
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
]
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
Loading…
Reference in New Issue