"""
The module handles the main logic related to annotations. This includes adding, modifiying and deleting annotations
as well as adding comments to the annotations. The module also retrieves the annotations to the document.
:authors: Joonas Lattu, Petteri Palojärvi
:copyright: 2016 Timber project members
:version: 1.0.0
"""
from typing import Dict
from flask import Blueprint
from .common import *
from timdb.annotations import Annotations
annotations = Blueprint('annotations',
__name__,
url_prefix='')
# TODO connect the routes in this file to the ui.
@annotations.route("/add_annotation", methods=['POST'])
[docs]def add_annotation() -> Dict:
"""Creates a new annotation to the database.
Required key(s):
- velp: velp ID
- visible_to: visibility group number (1 = Myself, 2 = Teacher, 3 = Document owner, 4 = Everyone)
- doc_id: document ID
- coord: start and end coordinates of the annotation.
:return: Dictionary oontaining annotation id and annotator name.
"""
json_data = request.get_json()
print(json_data)
timdb = getTimDb()
# first get the non-optional arguments and abort if there is missing data.
try:
velp_id = json_data['velp']
visible_to = timdb.annotations.AnnotationVisibility(json_data['visible_to'])
document_id = json_data['doc_id']
coordinates = json_data['coord']
start = coordinates['start']
end = coordinates['end']
except KeyError as e: # one of the json_data['foo'] fails
return abort(400, "Missing data: " + e.args[0])
except TypeError as e: # one of the element paths is not a list of integers
return abort(400, "Malformed element path. " + e.args[0])
except ValueError as e: # visible_to could not be casted to the enum used.
return abort(400, e.args[0])
# .get() returns None if there is no data instead of throwing.
offset_start = start.get('offset')
depth_start = start.get('depth')
node_start = start.get('node')
offset_end = end.get('offset')
depth_end = end.get('depth')
node_end = end.get('node')
element_path_start = start.get('el_path')
if element_path_start is not None:
if type(element_path_start) is not list or any(type(i) is not int for i in element_path_start):
return abort(400, "Malformed element path. " + str(element_path_start))
element_path_start = str(element_path_start)
element_path_end = end.get('el_path')
if element_path_end is not None:
if type(element_path_end) is not list or any(type(i) is not int for i in element_path_end):
return abort(400, "Malformed element path. " + str(element_path_end))
element_path_end = str(element_path_end)
points = json_data.get('points')
icon_id = json_data.get('icon_id')
answer_id = json_data.get('answer_id')
try:
paragraph_id_start = start['par_id']
hash_start = start['t']
paragraph_id_end = end['par_id']
hash_end = end['t']
except KeyError as e:
return abort(400, "Missing data: " + e.args[0])
verifyLoggedIn()
annotator_id = getCurrentUserId()
velp_version_id = timdb.velps.get_latest_velp_version(velp_id)["id"]
new_id = timdb.annotations.create_annotation(velp_version_id, visible_to, points, annotator_id, document_id,
paragraph_id_start, paragraph_id_end, offset_start, node_start,
depth_start, offset_end, node_end, depth_end, hash_start, hash_end,
element_path_start, element_path_end, None, icon_id, answer_id)
return jsonResponse({"id": new_id, "annotator_name": timdb.users.get_user(annotator_id)['name']})
@annotations.route("/update_annotation", methods=['POST'])
[docs]def update_annotation():
"""Updates the visibility and/or points of the annotation.
Required key(s):
- annotation_id: annotation ID.
Optional key(s):
- visible_to: visibility group number (1 = Myself, 2 = Teacher, 3 = Document owner, 4 = Everyone)
- points: number of points
- doc_id: document ID.
:return: okJsonResponse()
"""
verifyLoggedIn()
user_id = getCurrentUserId()
json_data = request.get_json()
try:
annotation_id = json_data['annotation_id']
except KeyError as e:
return abort(400, "Missing data: " + e.args[0])
visible_to = json_data.get('visible_to')
points = json_data.get('points')
doc_id = json_data.get('doc_id')
timdb = getTimDb()
# Get values from the database to fill in unchanged new values.
new_values = timdb.annotations.get_annotation(annotation_id)
if not new_values:
return abort(404, "No such annotation.")
new_values = new_values[0]
# Todo panic if there is more than one item in the list.
if not new_values['annotator_id'] == user_id:
return abort(403, "You are not the annotator.")
if visible_to:
try:
visible_to = Annotations.AnnotationVisibility(visible_to)
except ValueError as e:
return abort(400, "Visibility should be 1, 2, 3 or 4.")
new_values['visible_to'] = visible_to
if timdb.users.has_teacher_access(user_id, doc_id):
new_values['points'] = points
else:
if points is None:
new_values['points'] = points
else:
new_values['points'] = None
timdb.annotations.update_annotation(new_values['id'], new_values['velp_version_id'],
new_values['visible_to'], new_values['points'], new_values['icon_id'])
return okJsonResponse()
@annotations.route("/invalidate_annotation", methods=['POST'])
[docs]def invalidate_annotation():
"""Invalidates annotation by setting its valid from to current moment.
Required key(s):
- annotation_id: annotation ID
:return: okJsonResponse()
"""
json_data = request.get_json()
try:
annotation_id = json_data['annotation_id']
except KeyError as e:
return abort(400, "Missing data: " + e.args[0])
timdb = getTimDb()
annotation = timdb.annotations.get_annotation(annotation_id)
if not annotation:
return abort(404, "No such annotation.")
annotation = annotation[0]
verifyLoggedIn()
user_id = getCurrentUserId()
if not annotation['annotator_id'] == user_id:
return abort(403, "You are not the annotator.")
# TODO: Add option to choose when annotation gets invalidated
timdb.annotations.invalidate_annotation(annotation_id)
return okJsonResponse()
@annotations.route("/add_annotation_comment", methods=['POST'])
@annotations.route("/<int:doc_id>/get_annotations", methods=['GET'])
[docs]def get_annotations(doc_id: int):
"""Returns all annotations with comments user can see, e.g. has access to them in a document.
:param doc_id: ID of the document
:return: List of dictionaries containing annotations with comments
"""
timdb = getTimDb()
user_id = getCurrentUserId()
if not timdb.documents.exists(doc_id):
return abort(404, "No such document.")
if not timdb.users.has_view_access(user_id, doc_id):
return abort(403, "View access required.")
user_has_see_answers = timdb.users.has_seeanswers_access(user_id, doc_id)
user_has_teacher = timdb.users.has_teacher_access(user_id, doc_id)
user_has_owner = timdb.users.user_is_owner(user_id, doc_id)
results = timdb.annotations.get_annotations_with_comments_in_document(getCurrentUserId(), user_has_see_answers,
user_has_teacher, user_has_owner, doc_id)
response = jsonResponse(results)
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate'
return response