import re import uuid from typing import List, Union import bleach from api.utils import get_object from assignments.models import Assignment from books.models import ContentBlock from core.logger import get_logger from wagtail.blocks import StreamValue logger = get_logger(__name__) 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"
{}
".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", "solution", "readonly", ) def get_content_dict(content_type, id, value): return {"type": content_type, "id": id, "value": value} 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: logger.error(f"{content['type']} not in allowed blocks") return id = content.get("id") if id is None: id = str(uuid.uuid4()) if content["type"] == "text_block": content_type = "text_block" value = { "text": handle_text(bleach.clean(content["value"]["text"], strip=True)) } return get_content_dict( content_type=content_type, id=id, value=value, ) 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, ) content_type = "assignment" value = {"assignment_id": assignment.id} return get_content_dict(content_type=content_type, id=id, value=value) elif content["type"] == "image_url_block": content_type = "image_url_block" value = {"url": bleach.clean(content["value"]["url"])} return get_content_dict(content_type=content_type, id=id, value=value) elif content["type"] == "link_block": content_type = "link_block" value = { "text": bleach.clean(content["value"]["text"]), "url": bleach.clean(content["value"]["url"]), } return get_content_dict(content_type=content_type, id=id, value=value) elif content["type"] == "video_block": content_type = "video_block" value = {"url": bleach.clean(content["value"]["url"])} return get_content_dict(content_type=content_type, id=id, value=value) elif content["type"] == "document_block": content_type = "document_block" value = {"url": bleach.clean(content["value"]["url"])} return get_content_dict(content_type=content_type, id=id, value=value) elif content["type"] == "solution": content_type = "solution" value = { "text": handle_text(bleach.clean(content["value"]["text"], strip=True)), } return get_content_dict(content_type=content_type, id=id, value=value) elif content["type"] == "subtitle": content_type = "subtitle" value = {"text": bleach.clean(content["value"]["text"])} return get_content_dict(content_type=content_type, id=id, value=value) 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_type = "content_list_item" return get_content_dict(content_type=content_type, id=id, value=value) 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