def passage_json (passage_or_id = None): """Endpoint. Serve information about a passage. Return information about a passage or navigate to it. :param string passage_or_id: The passage id. :param string siglum: The siglum of the book to navigate to. :param string chapter: The chapter to navigate to. :param string verse: The verse to navigate to. :param string word: The word (range) to navigate to. :param string button: The button pressed. """ auth () passage_or_id = request.args.get ('pass_id') or passage_or_id or '0' siglum = request.args.get ('siglum') chapter = request.args.get ('chapter') verse = request.args.get ('verse') word = request.args.get ('word') button = request.args.get ('button') with current_app.config.dba.engine.begin () as conn: if siglum and chapter and verse and word and button == 'Go': parsed_passage = Passage.parse ("%s %s:%s/%s" % (siglum, chapter, verse, word)) # log (logging.INFO, parsed_passage) passage = Passage (conn, parsed_passage) return make_json_response (passage.to_json ()) if button in ('-1', '1'): passage = Passage (conn, passage_or_id) passage = Passage (conn, int (passage.pass_id) + int (button)) return make_json_response (passage.to_json ()) passage = Passage (conn, passage_or_id) return make_json_response (passage.to_json ())
def passage_json(passage_or_id=None): """Endpoint. Serve information about a passage. Return information about a passage or navigate to it. :param string passage_or_id: The passage id. :param string siglum: The siglum of the book to navigate to. :param string chapter: The chapter to navigate to. :param string verse: The verse to navigate to. :param string word: The word (range) to navigate to. :param string button: The button pressed. """ auth() passage_or_id = request.args.get('pass_id') or passage_or_id or '0' siglum = request.args.get('siglum') chapter = request.args.get('chapter') verse = request.args.get('verse') word = request.args.get('word') button = request.args.get('button') with current_app.config.dba.engine.begin() as conn: if siglum and chapter and verse and word and button == 'Go': parsed_passage = Passage.parse("%s %s:%s/%s" % (siglum, chapter, verse, word)) passage = Passage(conn, parsed_passage) return make_json_response(passage.to_json()) if button in ('-1', '1'): passage = Passage(conn, passage_or_id) passage = Passage(conn, int(passage.pass_id) + int(button)) return make_json_response(passage.to_json()) passage = Passage(conn, passage_or_id) return cache(make_json_response(passage.to_json()))
def stemma_edit(passage_or_id): """Edit a local stemma. Called from local-stemma.js (split, merge, move) and textflow.js (move-manuscripts). """ edit_auth() args = request.get_json() action = args.get('action') if action not in ('add', 'del', 'split', 'merge', 'move', 'move-manuscripts'): raise EditError('Bad request') params = {} for n in 'labez_old labez_new source_labez'.split(): if n in args: params[n] = args.get(n) if not RE_VALID_LABEZ.match(params[n]): raise EditError('Bad request') for n in 'clique_old clique_new source_clique'.split(): if n in args: params[n] = args.get(n) if not RE_VALID_CLIQUE.match(params[n]): raise EditError('Bad request') def integrity_error(e): if 'ix_locstem_unique_original' in str(e): raise EditError( '''Only one original reading allowed. If you want to change the original reading, first remove the old original reading.<br/><br/>''' + str(e)) if 'locstem_pkey' in str(e): raise EditError( '''This readings already dependes on that reading.<br/><br/>''' + str(e)) if 'same_source' in str(e): raise EditError( '''A reading cannot be derived from the same reading. If you want to <b>merge two readings</b>, use shift + drag.''') raise EditError(str(e)) with current_app.config.dba.engine.begin() as conn: passage = Passage(conn, passage_or_id) params['pass_id'] = passage.pass_id params['user_id'] = flask_login.current_user.id res = execute( conn, """ SET LOCAL ntg.user_id = :user_id; """, dict(parameters, **params)) if action == 'move': # reassign a source reading # there may be multiple existent assignments, there'll be only one left try: res = execute( conn, """ DELETE FROM locstem WHERE (pass_id, labez, clique) = (:pass_id, :labez_old, :clique_old); INSERT INTO locstem (pass_id, labez, clique, source_labez, source_clique) VALUES (:pass_id, :labez_old, :clique_old, :labez_new, :clique_new) """, dict(parameters, **params)) except sqlalchemy.exc.IntegrityError as e: integrity_error(e) except sqlalchemy.exc.DatabaseError as e: raise EditError(str(e)) if action == 'del': # remove a source reading try: # check if we are asked to remove the only link, # in that case reassign to 'unknown' res = execute( conn, """ SELECT pass_id FROM locstem WHERE (pass_id, labez, clique) = (:pass_id, :labez_old, :clique_old); """, dict(parameters, **params)) tools.log(logging.INFO, 'Deleting: ' + str(params)) if res.rowcount > 1: res = execute( conn, """ DELETE FROM locstem WHERE (pass_id, labez, clique) = (:pass_id, :labez_old, :clique_old) AND (source_labez, source_clique) = (:source_labez, :source_clique) """, dict(parameters, **params)) else: res = execute( conn, """ UPDATE locstem SET (source_labez, source_clique) = ('?', '1') WHERE (pass_id, labez, clique) = (:pass_id, :labez_old, :clique_old); """, dict(parameters, **params)) except sqlalchemy.exc.IntegrityError as e: integrity_error(e) except sqlalchemy.exc.DatabaseError as e: raise EditError(str(e)) if action == 'add': # add a source reading try: res = execute( conn, """ INSERT INTO locstem (pass_id, labez, clique, source_labez, source_clique) VALUES (:pass_id, :labez_old, :clique_old, :labez_new, :clique_new) """, dict(parameters, **params)) except sqlalchemy.exc.IntegrityError as e: integrity_error(e) except sqlalchemy.exc.DatabaseError as e: raise EditError(str(e)) if action in ('add', 'del', 'move'): # test the still uncommitted changes graph = db_tools.local_stemma_to_nx(conn, passage.pass_id) # test: not a DAG if not nx.is_directed_acyclic_graph(graph): raise EditError('The new graph contains cycles.') # test: not connected graph.add_edge('*', '?') if not nx.is_weakly_connected(graph): raise EditError('The new graph is not connected.') elif action == 'split': # Get the lowest free integer for the new clique. See: #122 res = execute( conn, """ SELECT clique FROM cliques WHERE pass_id = :pass_id AND labez = :labez_old """, dict(parameters, **params)) taken = set([int(r[0]) for r in res]) n = 1 while n in taken: n += 1 params['clique_next'] = str(n) # insert into cliques table res = execute( conn, """ INSERT INTO cliques (pass_id, labez, clique) VALUES (:pass_id, :labez_old, :clique_next) """, dict(parameters, **params)) # insert into locstem table with source = '?' res = execute( conn, """ INSERT INTO locstem (pass_id, labez, clique, source_labez, source_clique) VALUES (:pass_id, :labez_old, :clique_next, '?', '1') """, dict(parameters, **params)) elif action == 'merge': # merge two cliques (eg. b1, b2) into one clique (eg. b1) # # reassign manuscripts to merged clique res = execute( conn, """ UPDATE ms_cliques SET clique = :clique_new WHERE (pass_id, labez, clique) = (:pass_id, :labez_old, :clique_old) """, dict(parameters, **params)) # reassign sources to merged clique res = execute( conn, """ UPDATE locstem SET source_clique = :clique_new WHERE (pass_id, source_labez, source_clique) = (:pass_id, :labez_old, :clique_old) """, dict(parameters, **params)) # remove clique from locstem res = execute( conn, """ DELETE FROM locstem WHERE (pass_id, labez, clique) = (:pass_id, :labez_old, :clique_old) """, dict(parameters, **params)) # remove clique from cliques res = execute( conn, """ DELETE FROM cliques WHERE (pass_id, labez, clique) = (:pass_id, :labez_old, :clique_old) """, dict(parameters, **params)) elif action == 'move-manuscripts': # reassign a set of manuscripts to a new clique ms_ids = set(args.get('ms_ids') or []) res = execute( conn, """ UPDATE apparatus_cliques_view SET clique = :clique_new WHERE (pass_id, labez, clique) = (:pass_id, :labez_old, :clique_old) AND ms_id IN :ms_ids """, dict(parameters, ms_ids=tuple(ms_ids), **params)) tools.log(logging.INFO, 'Moved ms_ids: ' + str(ms_ids)) # return the changed passage passage = Passage(conn, passage_or_id) return make_json_response(passage.to_json()) raise EditError('Could not edit local stemma.')
def stemma_edit (passage_or_id): """Edit a local stemma. Called from local-stemma.js (split, merge, move) and textflow.js (move-manuscripts). """ if not flask_login.current_user.has_role ('editor'): raise PrivilegeError ('You don\'t have editor privilege.') args = request.get_json () action = args.get ('action') if action not in ('split', 'merge', 'move', 'move-manuscripts'): raise EditError ('Bad request') params = { 'original_new' : args.get ('labez_new') == '*' } for n in 'labez_old labez_new'.split (): params[n] = args.get (n) if not RE_VALID_LABEZ.match (params[n]): raise EditError ('Bad request') if params[n] in ('*', '?'): params[n] = None for n in 'clique_old clique_new'.split (): params[n] = args.get (n) if not RE_VALID_CLIQUE.match (params[n]): raise EditError ('Bad request') if params[n] == '0': params[n] = None with current_app.config.dba.engine.begin () as conn: passage = Passage (conn, passage_or_id) params['pass_id'] = passage.pass_id params['user_id'] = flask_login.current_user.id res = execute (conn, """ SET LOCAL ntg.user_id = :user_id; """, dict (parameters, **params)) if action == 'move': try: res = execute (conn, """ UPDATE locstem SET source_labez = :labez_new, source_clique = :clique_new, original = :original_new WHERE pass_id = :pass_id AND labez = :labez_old AND clique = :clique_old """, dict (parameters, **params)) except sqlalchemy.exc.IntegrityError as e: if 'unique constraint' in str (e): raise EditError ( '''Only one original reading allowed. If you want to change the original reading, first remove the old original reading.<br/><br/>''' + str (e) ) raise EditError (str (e)) except sqlalchemy.exc.DatabaseError as e: raise EditError (str (e)) # test the still uncommited changes graph = db_tools.local_stemma_to_nx (conn, passage.pass_id) # test: not a DAG if not nx.is_directed_acyclic_graph (graph): raise EditError ('The graph is not a DAG anymore.') # test: not connected graph.add_edge ('*', '?') if not nx.is_weakly_connected (graph): raise EditError ('The graph is not connected anymore.') # test: x derived from x for e in graph.edges: m0 = RE_EXTRACT_LABEZ.match (e[0]) m1 = RE_EXTRACT_LABEZ.match (e[1]) if m0 and m1 and m0.group (1) == m1.group (1): raise EditError ( '''A reading cannot be derived from the same reading. If you want to <b>merge</b> instead, use shift + drag.''' ) elif action == 'split': # get the next free clique res = execute (conn, """ SELECT max (clique) FROM cliques WHERE pass_id = :pass_id AND labez = :labez_old """, dict (parameters, **params)) params['clique_next'] = str (int (res.fetchone ()[0]) + 1) # insert into cliques table res = execute (conn, """ INSERT INTO cliques (pass_id, labez, clique) VALUES (:pass_id, :labez_old, :clique_next) """, dict (parameters, **params)) # insert into locstem table with source = '?' res = execute (conn, """ INSERT INTO locstem (pass_id, labez, clique, source_labez, source_clique, original) VALUES (:pass_id, :labez_old, :clique_next, NULL, NULL, false) """, dict (parameters, **params)) elif action == 'merge': # reassign manuscripts to merged clique res = execute (conn, """ UPDATE ms_cliques SET clique = :clique_new WHERE (pass_id, labez, clique) = (:pass_id, :labez_old, :clique_old) """, dict (parameters, **params)) # reassign sources to merged clique res = execute (conn, """ UPDATE locstem SET source_clique = :clique_new WHERE (pass_id, source_labez, source_clique) = (:pass_id, :labez_old, :clique_old) """, dict (parameters, **params)) # remove clique from locstem res = execute (conn, """ DELETE FROM locstem WHERE (pass_id, labez, clique) = (:pass_id, :labez_old, :clique_old) """, dict (parameters, **params)) # remove clique from cliques res = execute (conn, """ DELETE FROM cliques WHERE (pass_id, labez, clique) = (:pass_id, :labez_old, :clique_old) """, dict (parameters, **params)) elif action == 'move-manuscripts': ms_ids = set (args.get ('ms_ids') or []) # reassign manuscripts to new clique res = execute (conn, """ UPDATE apparatus_cliques_view SET clique = :clique_new WHERE (pass_id, labez, clique) = (:pass_id, :labez_old, :clique_old) AND ms_id IN :ms_ids """, dict (parameters, ms_ids = tuple (ms_ids), **params)) tools.log (logging.INFO, 'Moved ms_ids: ' + str (ms_ids)) # return the changed passage passage = Passage (conn, passage_or_id) return make_json_response (passage.to_json ()) raise EditError ('Could not edit local stemma.')