176 lines
6.2 KiB
Python
176 lines
6.2 KiB
Python
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 wagtail.blocks import StreamValue
|
|
|
|
|
|
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 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:
|
|
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"] == "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
|