skillbox/server/book/schema/mutations.py

188 lines
6.4 KiB
Python

import json
import bleach
import graphene
import re
from django.core.exceptions import ValidationError
from graphene import relay
from api.utils import get_object, get_errors
from book.models import ContentBlock, Chapter, UserGroup
from book.schema.inputs import ContentBlockInput
from book.schema.queries import ContentBlockNode
def newlines_to_paragraphs(text):
parts = re.split(r'[\r\n]+', text)
paragraphs = ['<p>{}</p>'.format(p.strip()) for p in parts]
return '\n'.join(paragraphs)
ALLOWED_BLOCKS = (
'text_block',
'student_entry',
'image_url_block',
'link_block',
'video_block',
'document_block',
)
def handle_content_blocks(content_data, allowed_blocks=ALLOWED_BLOCKS):
new_contents = []
for content in content_data:
# todo: add all the content blocks
# todo: sanitize user inputs!
if content['type'] not in allowed_blocks:
continue
if content['type'] == 'text_block':
new_contents.append({
'type': 'text_block',
'value': {
'text': newlines_to_paragraphs(bleach.clean(content['value']['text'], strip=True))
}})
elif content['type'] == 'student_entry':
pass
elif content['type'] == 'image_url_block':
new_contents.append({
'type': 'image_url_block',
'value': {
'url': bleach.clean(content['value']['url'])
}})
elif content['type'] == 'link_block':
new_contents.append({
'type': 'link_block',
'value': {
'text': bleach.clean(content['value']['text']),
'url': bleach.clean(content['value']['url'])
}
})
elif content['type'] == 'task':
pass
elif content['type'] == 'video_block':
new_contents.append({
'type': 'video_block',
'value': {
'url': bleach.clean(content['value']['url'])
}})
elif content['type'] == 'document_block':
new_contents.append({
'type': 'document_block',
'value': {
'url': bleach.clean(content['value']['url'])
}})
return new_contents
class MutateContentBlock(relay.ClientIDMutation):
class Input:
id = graphene.ID(required=True)
content_block = graphene.Argument(ContentBlockInput)
errors = graphene.List(graphene.String)
content_block = graphene.Field(ContentBlockNode)
@classmethod
def mutate_and_get_payload(cls, *args, **kwargs):
try:
id_param = kwargs['id']
content_block_data = kwargs.get('content_block')
# type_param = content_block_data.get('type')
title = content_block_data.get('title', None)
contents = content_block_data.get('contents', None)
visibility_list = content_block_data.get('visibility', None)
content_block = get_object(ContentBlock, id_param)
if visibility_list is not None:
for v in visibility_list:
user_group = get_object(UserGroup, v.user_group_id)
if v.hidden:
content_block.hidden_for.add(user_group)
else:
content_block.hidden_for.remove(user_group)
if title is not None:
content_block.title = title
if contents is not None:
content_block.contents = json.dumps(handle_content_blocks(contents))
content_block.save()
return cls(content_block=content_block)
except ValidationError as e:
errors = get_errors(e)
except Exception as e:
errors = ['Error: {}'.format(e)]
return cls(content_block=None, errors=errors)
class AddContentBlock(relay.ClientIDMutation):
class Input:
content_block = graphene.Argument(ContentBlockInput)
parent = graphene.ID() # ID of chapter node; new content block will be inserted at the start of it
after = graphene.ID() # ID of content block node; new content block will be inserted after this content block node
new_content_block = graphene.Field(ContentBlockNode)
errors = graphene.List(graphene.String)
@classmethod
def create_content_block(cls, content_block_data, parent=None, after=None):
if after is None and parent is None:
raise Exception('Define either a parent or a sibling id')
title = content_block_data.get('title')
contents = content_block_data.get('contents')
new_content_block = ContentBlock(title=title)
if parent is not None:
parent_chapter = get_object(Chapter, parent).specific
first_sibling = parent_chapter.get_first_child()
if first_sibling is not None:
first_sibling.add_sibling(instance=new_content_block, pos='left')
else:
parent_chapter.add_child(instance=new_content_block)
elif after is not None:
sibling = get_object(ContentBlock, after).specific
sibling.add_sibling(instance=new_content_block, pos='right')
revision = new_content_block.save_revision()
revision.publish()
new_content_block.save()
new_contents = handle_content_blocks(contents) # can only do this after the content block has been saved
new_content_block.contents = json.dumps(new_contents)
new_content_block.save()
return new_content_block
@classmethod
def mutate_and_get_payload(cls, root, info, **args):
try:
parent = args.get('parent', None)
after = args.get('after', None)
new_content_block = cls.create_content_block(content_block_data=args.get('content_block'),
parent=parent,
after=after)
return cls(new_content_block=new_content_block)
except ValidationError as e:
errors = get_errors(e)
except Exception as e:
errors = ['Error: {}'.format(e)]
return cls(new_content_block=None, errors=errors)
class BookMutations(object):
mutate_content_block = MutateContentBlock.Field()
add_content_block = AddContentBlock.Field()