Source code for timApp.document.editing.routes_clipboard

"""Routes for the clipboard."""
from dataclasses import dataclass
from typing import Optional

from flask import current_app
from flask import g

from timApp.auth.accesshelper import verify_logged_in, get_doc_or_abort, AccessDenied
from timApp.auth.accesshelper import verify_view_access, verify_edit_access
from timApp.auth.sessioninfo import get_current_user_object
from timApp.document.docinfo import DocInfo
from timApp.document.docparagraph import DocParagraph
from timApp.document.document import Document, par_list_to_text
from timApp.document.editing.clipboard import Clipboard
from timApp.document.editing.documenteditresult import DocumentEditResult
from timApp.document.editing.routes import par_response, verify_par_edit_access
from timApp.document.translation.synchronize_translations import (
    synchronize_translations,
)
from timApp.note.notes import move_notes
from timApp.notification.notification import NotificationType
from timApp.notification.notify import notify_doc_watchers
from timApp.timdb.exceptions import TimDbException
from timApp.timdb.sqa import db
from timApp.util.flask.requesthelper import RouteException, NotExist
from timApp.util.flask.responsehelper import json_response, ok_response
from timApp.util.flask.typedblueprint import TypedBlueprint

clipboard = TypedBlueprint(
    "clipboard",
    __name__,
    url_prefix="",  # TODO: Better URL prefix.
)


[docs]@dataclass class WithDocData: doc_id: int docentry: DocInfo
wd: WithDocData = g
[docs]@clipboard.url_value_preprocessor def pull_doc_id(endpoint, values): if current_app.url_map.is_endpoint_expecting(endpoint, "doc_id"): doc_id = values["doc_id"] if doc_id is None: raise RouteException() wd.doc_id = doc_id wd.docentry = get_doc_or_abort(doc_id) if not wd.docentry: raise NotExist()
[docs]@clipboard.post("/clipboard/cut/<int:doc_id>/<from_par>/<to_par>") def cut_to_clipboard(doc_id, from_par, to_par, area_name: str | None = None): verify_logged_in() verify_edit_access(wd.docentry) doc: Document = wd.docentry.document_as_current_user version_before = doc.get_version() clip = Clipboard().get(get_current_user_object()) try: for p in doc.get_section(from_par, to_par): verify_par_edit_access(p) pars = clip.cut_pars(doc, from_par, to_par, area_name) except TimDbException as e: raise RouteException(str(e)) wd.docentry.update_last_modified() db.session.commit() synchronize_translations(wd.docentry, DocumentEditResult(deleted=pars)) notify_doc_watchers( wd.docentry, par_list_to_text(pars), NotificationType.ParDeleted, old_version=version_before, ) db.session.commit() return json_response( {"doc_ver": doc.get_version(), "pars": [{"id": p.get_id()} for p in pars]} )
[docs]@clipboard.post("/clipboard/copy/<int:doc_id>/<from_par>/<to_par>") def copy_to_clipboard( doc_id, from_par, to_par, area_name: str | None = None, ): verify_logged_in() verify_view_access(wd.docentry) doc = wd.docentry.document_as_current_user clip = Clipboard().get(get_current_user_object()) try: clip.copy_pars(doc, from_par, to_par, area_name, disable_ref=False) except TimDbException as e: raise RouteException(str(e)) return ok_response()
[docs]@clipboard.post("/clipboard/paste/<int:doc_id>") def paste_from_clipboard( doc_id, par_before: str | None = None, par_after: str | None = None, as_ref: bool = False, ): verify_logged_in() verify_edit_access(wd.docentry) doc = wd.docentry.document_as_current_user version_before = doc.get_version() clip = Clipboard().get(get_current_user_object()) meta = clip.read_metadata() was_cut = meta.get("last_action") == "cut" if meta.get("empty", True): raise RouteException("The clipboard is empty.") if not as_ref and meta.get("disable_content"): raise AccessDenied("The contents of the clipboard cannot be pasted as content.") if as_ref and meta.get("disable_ref"): raise RouteException( "The contents of the clipboard cannot be pasted as a reference." ) try: if par_before and not par_after: pars = clip.paste_before(doc, par_before, as_ref) elif not par_before and par_after: pars = clip.paste_after(doc, par_after, as_ref) else: raise RouteException( "Missing required parameter in request: par_before or par_after (not both)" ) except TimDbException as e: raise RouteException(str(e)) src_doc = None parrefs = clip.read(as_ref=True, force_parrefs=True) for (src_par_dict, dest_par) in zip(parrefs, pars): try: src_docid = int(src_par_dict["attrs"]["rd"]) src_parid = src_par_dict["attrs"]["rp"] par_id = dest_par.get_id() if (doc_id, par_id) != (src_docid, src_parid): if src_doc is None or str(src_doc.doc_id) != str(src_docid): src_doc = Document(src_docid) src_par = DocParagraph.get_latest(src_doc, src_parid) # Copying readings has been disabled because it causes database bloat if paragraphs have hundreds # of thousands of readings. It will also be very slow for the user. # if has_teacher_access(src_doc.get_docinfo()): # copy_readings(src_par, dest_par) if was_cut: move_notes(src_par, dest_par) except ValueError: pass edit_result = DocumentEditResult(added=pars) synchronize_translations(wd.docentry, edit_result) notify_doc_watchers( wd.docentry, par_list_to_text(pars), NotificationType.ParAdded, old_version=version_before, par=pars[0], ) return par_response(pars, wd.docentry, edit_result=edit_result)
# TODO unused route?
[docs]@clipboard.post("/clipboard/deletesrc/<int:doc_id>") def delete_from_source(doc_id): verify_logged_in() verify_edit_access(wd.docentry) doc = wd.docentry.document_as_current_user clip = Clipboard().get(get_current_user_object()) pars = clip.read(as_ref=True, force_parrefs=True) if not pars: return json_response({"doc_ver": doc.get_version(), "pars": []}) my_pars = [ {"id": p["attrs"]["rp"]} for p in pars if p["attrs"]["rd"] == str(doc_id) ] clip.delete_from_source() clip.clear() return json_response({"doc_ver": doc.get_version(), "pars": my_pars})
[docs]@clipboard.get("/clipboard") def show_clipboard(doc_id: int): verify_logged_in() d = get_doc_or_abort(doc_id) verify_view_access(d) doc = d.document clip = Clipboard().get(get_current_user_object()) pars = [DocParagraph.from_dict(doc, par) for par in clip.read() or []] return par_response(pars, d)
[docs]@clipboard.get("/clipboardstatus") def get_clipboard_status(): verify_logged_in() clip = Clipboard().get(get_current_user_object()) status = clip.read_metadata() return json_response(status)