173 lines
5.8 KiB
Python
173 lines
5.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
#
|
|
# Iterativ GmbH
|
|
# http://www.iterativ.ch/
|
|
#
|
|
# Copyright (c) 2018 Iterativ GmbH. All rights reserved.
|
|
#
|
|
# Created on 25.09.18
|
|
# @author: Ramon Wenger <ramon.wenger@iterativ.ch>
|
|
|
|
import bleach
|
|
import re
|
|
|
|
from typing import List, Union
|
|
|
|
from wagtail.core.blocks import StreamValue
|
|
|
|
from api.utils import get_object
|
|
from assignments.models import Assignment
|
|
from books.models import ContentBlock
|
|
|
|
|
|
class AssignmentParameterException(Exception):
|
|
pass
|
|
|
|
|
|
class ContentTypeException(Exception):
|
|
pass
|
|
|
|
|
|
def get_previous_item(previous_contents: Union[StreamValue, List[dict]], item: dict):
|
|
if isinstance(previous_contents, StreamValue):
|
|
contents = previous_contents.raw_data
|
|
else:
|
|
contents = previous_contents
|
|
return next((c for c in contents if c.get('id', None) == item['id']), None)
|
|
|
|
|
|
def handle_text(text):
|
|
is_list = bool(re.search(r'<ul>', text))
|
|
if is_list:
|
|
# let's assume this is formatted correctly already, otherwise bleach or the browser should strip / ignore it
|
|
return text
|
|
else:
|
|
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',
|
|
'assignment',
|
|
'document_block',
|
|
'content_list_item',
|
|
'subtitle',
|
|
'readonly'
|
|
)
|
|
|
|
|
|
def handle_content_block(content, context=None, module=None, allowed_blocks=ALLOWED_BLOCKS, previous_contents=None):
|
|
"""
|
|
Handle different content types of a content block
|
|
|
|
:param content: The current content block to be handled
|
|
:param context: The request context of the current request, contains the user
|
|
:param module: The module where this content block comes from, used for assignments
|
|
:type module: books.models.Module
|
|
:param allowed_blocks: List of allowed types of blocks
|
|
:type allowed_blocks: list(str)
|
|
:param previous_contents: The original content list from where this content block came from before being modified by the user
|
|
"""
|
|
# todo: add all the content blocks
|
|
# todo: sanitize user inputs!
|
|
if content['type'] not in allowed_blocks:
|
|
return
|
|
|
|
if content['type'] == 'text_block':
|
|
return {
|
|
'type': 'text_block',
|
|
'value': {
|
|
'text': handle_text(bleach.clean(content['value']['text'], strip=True))
|
|
}}
|
|
elif content['type'] == 'assignment':
|
|
if module is None:
|
|
raise AssignmentParameterException('Module is missing for assignment') # todo: define better exception
|
|
if context is None:
|
|
raise AssignmentParameterException('Context is missing for assignment')
|
|
value = content['value']
|
|
if value.get('id') is not None:
|
|
assignment = get_object(Assignment, value.get('id'))
|
|
if assignment.user_created and assignment.owner == context.user:
|
|
assignment.title = value.get('title')
|
|
assignment.assignment = value.get('assignment')
|
|
assignment.save()
|
|
else:
|
|
assignment = Assignment.objects.create(
|
|
title=value.get('title'),
|
|
assignment=value.get('assignment'),
|
|
owner=context.user,
|
|
module=module,
|
|
user_created=True
|
|
)
|
|
|
|
return {
|
|
'type': 'assignment',
|
|
'value': {
|
|
'assignment_id': assignment.id
|
|
}}
|
|
elif content['type'] == 'image_url_block':
|
|
return {
|
|
'type': 'image_url_block',
|
|
'value': {
|
|
'url': bleach.clean(content['value']['url'])
|
|
}}
|
|
elif content['type'] == 'link_block':
|
|
return {
|
|
'type': 'link_block',
|
|
'value': {
|
|
'text': bleach.clean(content['value']['text']),
|
|
'url': bleach.clean(content['value']['url'])
|
|
}
|
|
}
|
|
elif content['type'] == 'video_block':
|
|
return {
|
|
'type': 'video_block',
|
|
'value': {
|
|
'url': bleach.clean(content['value']['url'])
|
|
}}
|
|
elif content['type'] == 'document_block':
|
|
return {
|
|
'type': 'document_block',
|
|
'value': {
|
|
'url': bleach.clean(content['value']['url'])
|
|
}}
|
|
elif content['type'] == 'subtitle':
|
|
return {
|
|
'type': 'subtitle',
|
|
'value': {
|
|
'text': bleach.clean(content['value']['text'])
|
|
}
|
|
}
|
|
elif content['type'] == 'content_list_item':
|
|
if content.get('id') is not None: # the list block existed already
|
|
previous_item = get_previous_item(previous_contents=previous_contents, item=content)
|
|
previous_content = previous_item.get('value')
|
|
else:
|
|
previous_content = None
|
|
value = [handle_content_block(c, context, module, previous_contents=previous_content) for c in
|
|
content['contents']]
|
|
content['value'] = value
|
|
return content
|
|
elif content['type'] == 'readonly' and previous_contents is not None:
|
|
# get first item that matches the id
|
|
# users can re-order readonly items, but we won't let them change them otherwise, so we just take the
|
|
# item from before and ignore anything else
|
|
previous_item = get_previous_item(previous_contents=previous_contents, item=content)
|
|
if previous_item is None:
|
|
raise ContentTypeException('Readonly item found that is not allowed here')
|
|
return previous_item
|
|
|
|
raise ContentTypeException('Type did not match a case')
|
|
|
|
|
|
def set_user_defined_block_type(block_type):
|
|
if block_type == ContentBlock.TASK.upper():
|
|
return ContentBlock.TASK
|
|
else:
|
|
return ContentBlock.NORMAL
|