From cf6f8e82ee3b881cf656cdbb546186cd3e25001b Mon Sep 17 00:00:00 2001 From: Stephan Porada Date: Thu, 8 Oct 2020 12:48:29 +0200 Subject: [PATCH] Merge query_results into corpora blueprint --- web/app/__init__.py | 4 - web/app/corpora/forms.py | 40 +++++ web/app/corpora/tasks.py | 12 +- web/app/corpora/views.py | 149 ++++++++++++++++- web/app/query_results/__init__.py | 5 - web/app/query_results/forms.py | 21 --- web/app/query_results/tasks.py | 13 -- web/app/query_results/views.py | 150 ------------------ web/app/static/js/nopaque.lists.js | 6 +- .../corpora/query_results/inspect.html.j2 | 14 +- .../query_results/query_result.html.j2 | 9 +- web/app/templates/main/dashboard.html.j2 | 2 +- .../services/corpus_analysis.html.j2 | 2 +- 13 files changed, 214 insertions(+), 213 deletions(-) delete mode 100644 web/app/query_results/__init__.py delete mode 100644 web/app/query_results/forms.py delete mode 100644 web/app/query_results/tasks.py delete mode 100644 web/app/query_results/views.py diff --git a/web/app/__init__.py b/web/app/__init__.py index 6244a0e4..4fa0f820 100644 --- a/web/app/__init__.py +++ b/web/app/__init__.py @@ -52,10 +52,6 @@ def create_app(config_name): from .profile import profile as profile_blueprint app.register_blueprint(profile_blueprint, url_prefix='/profile') - from .query_results import query_results as query_results_blueprint - app.register_blueprint(query_results_blueprint, - url_prefix='/query_results') - from .services import services as services_blueprint app.register_blueprint(services_blueprint, url_prefix='/services') diff --git a/web/app/corpora/forms.py b/web/app/corpora/forms.py index cab8b3bf..f25c6b64 100644 --- a/web/app/corpora/forms.py +++ b/web/app/corpora/forms.py @@ -6,6 +6,9 @@ from wtforms.validators import DataRequired, Length, NumberRange class AddCorpusFileForm(FlaskForm): + ''' + Form to add a .vrt corpus file to the current corpus. + ''' address = StringField('Adress', validators=[Length(0, 255)]) author = StringField('Author', validators=[DataRequired(), Length(1, 255)]) booktitle = StringField('Booktitle', validators=[Length(0, 255)]) @@ -37,6 +40,9 @@ class AddCorpusFileForm(FlaskForm): class EditCorpusFileForm(FlaskForm): + ''' + Form to edit meta data of one corpus file. + ''' address = StringField('Adress', validators=[Length(0, 255)]) author = StringField('Author', validators=[DataRequired(), Length(1, 255)]) booktitle = StringField('Booktitle', validators=[Length(0, 255)]) @@ -54,6 +60,9 @@ class EditCorpusFileForm(FlaskForm): class AddCorpusForm(FlaskForm): + ''' + Form to add a a new corpus. + ''' description = StringField('Description', validators=[DataRequired(), Length(1, 255)]) submit = SubmitField() @@ -61,12 +70,18 @@ class AddCorpusForm(FlaskForm): class QueryForm(FlaskForm): + ''' + Form to submit a query to the server which is executed via cqi-py. + ''' query = StringField('Query', validators=[DataRequired(), Length(1, 1024)]) submit = SubmitField('Search') class DisplayOptionsForm(FlaskForm): + ''' + Form to alter how the matches are represented to the user by the user. + ''' expert_mode = BooleanField('Expert mode') result_context = SelectField('Result context', choices=[('', 'Choose your option'), @@ -85,6 +100,10 @@ class DisplayOptionsForm(FlaskForm): class InspectDisplayOptionsForm(FlaskForm): + ''' + Form for the inspect modal where the user can interact with how the current + match is being represented to him. + ''' expert_mode_inspect = BooleanField('Expert mode') highlight_sentences = BooleanField('Split sentences') context_sentences = IntegerField('Context sentences', @@ -93,6 +112,10 @@ class InspectDisplayOptionsForm(FlaskForm): class QueryDownloadForm(FlaskForm): + ''' + Form to choose in what file format the analysis results are being + downloaded. WIP. + ''' file_type = SelectField('File type', choices=[('', 'Choose file type'), ('csv', 'csv'), @@ -100,3 +123,20 @@ class QueryDownloadForm(FlaskForm): ('excel', 'excel'), ('html', 'html-table')], validators=[DataRequired()]) + + +class AddQueryResultForm(FlaskForm): + ''' + Form used to import one result json file. + ''' + description = StringField('Description', + validators=[DataRequired(), Length(1, 255)]) + file = FileField('File', validators=[DataRequired()]) + title = StringField('Title', validators=[DataRequired(), Length(1, 32)]) + submit = SubmitField() + + def validate_file(self, field): + if not field.data.filename.lower().endswith('.json'): + raise ValidationError('File does not have an approved extension: ' + '.json') + field.data.filename = secure_filename(field.data.filename) diff --git a/web/app/corpora/tasks.py b/web/app/corpora/tasks.py index 97d2e378..f65a00de 100644 --- a/web/app/corpora/tasks.py +++ b/web/app/corpora/tasks.py @@ -1,6 +1,6 @@ from .. import db from ..decorators import background -from ..models import Corpus, CorpusFile +from ..models import Corpus, CorpusFile, QueryResult @background @@ -32,3 +32,13 @@ def delete_corpus_file(corpus_file_id, *args, **kwargs): raise Exception('Corpus file {} not found'.format(corpus_file_id)) corpus_file.delete() db.session.commit() + + +@background +def delete_query_result(query_result_id, *args, **kwargs): + with kwargs['app'].app_context(): + query_result = QueryResult.query.get(query_result_id) + if query_result is None: + raise Exception('QueryResult {} not found'.format(query_result_id)) + query_result.delete() + db.session.commit() diff --git a/web/app/corpora/views.py b/web/app/corpora/views.py index 2cbc3f6a..ed3d00a6 100644 --- a/web/app/corpora/views.py +++ b/web/app/corpora/views.py @@ -3,11 +3,13 @@ from flask import (abort, current_app, flash, make_response, redirect, request, from flask_login import current_user, login_required from . import corpora from . import tasks -from .forms import (AddCorpusFileForm, AddCorpusForm, EditCorpusFileForm, - QueryDownloadForm, QueryForm, DisplayOptionsForm, - InspectDisplayOptionsForm) +from .forms import (AddCorpusFileForm, AddCorpusForm, AddQueryResultForm, + EditCorpusFileForm, QueryDownloadForm, QueryForm, + DisplayOptionsForm, InspectDisplayOptionsForm) from .. import db -from ..models import Corpus, CorpusFile +from ..models import Corpus, CorpusFile, QueryResult +import json +from jsonschema import validate import os @@ -230,3 +232,142 @@ def prepare_corpus(corpus_id): else: flash('Can not build corpus, please add corpus file(s).', 'corpus') return redirect(url_for('corpora.corpus', corpus_id=corpus_id)) + + +# Following are view functions to add, view etc. exported results. +@corpora.route('/result/add', methods=['GET', 'POST']) +@login_required +def add_query_result(): + ''' + View to import a result as a json file. + ''' + add_query_result_form = AddQueryResultForm(prefix='add-query-result-form') + if add_query_result_form.is_submitted(): + if not add_query_result_form.validate(): + return make_response(add_query_result_form.errors, 400) + query_result = QueryResult( + creator=current_user, + description=add_query_result_form.description.data, + filename=add_query_result_form.file.data.filename, + title=add_query_result_form.title.data + ) + db.session.add(query_result) + db.session.commit() + # create paths to save the uploaded json file + query_result_dir = os.path.join(current_app.config['NOPAQUE_STORAGE'], + str(current_user.id), + 'query_results', + str(query_result.id)) + try: + os.makedirs(query_result_dir) + except Exception: + db.session.delete(query_result) + db.session.commit() + flash('Internal Server Error', 'error') + redirect_url = url_for('corpora.add_query_result') + return make_response({'redirect_url': redirect_url}, 500) + # save the uploaded file + query_result_file_path = os.path.join(query_result_dir, + query_result.filename) + add_query_result_form.file.data.save(query_result_file_path) + # parse json from file + with open(query_result_file_path, 'r') as file: + query_result_file_content = json.load(file) + # parse json schema + with open('app/static/json_schema/nopaque_cqi_py_results_schema.json', 'r') as file: # noqa + schema = json.load(file) + try: + # validate imported json file + validate(instance=query_result_file_content, schema=schema) + except Exception: + tasks.delete_query_result(query_result.id) + flash('Uploaded file is invalid', 'result') + redirect_url = url_for('corpora.add_query_result') + return make_response({'redirect_url': redirect_url}, 201) + query_result_file_content.pop('matches') + query_result_file_content.pop('cpos_lookup') + query_result.query_metadata = query_result_file_content + db.session.commit() + flash('Query result added!', 'result') + redirect_url = url_for('corpora.query_result', + query_result_id=query_result.id) + return make_response({'redirect_url': redirect_url}, 201) + return render_template('corpora/query_results/add_query_result.html.j2', + add_query_result_form=add_query_result_form, + title='Add query result') + + +@corpora.route('/result/') +@login_required +def query_result(query_result_id): + query_result = QueryResult.query.get_or_404(query_result_id) + if not (query_result.creator == current_user + or current_user.is_administrator()): + abort(403) + return render_template('corpora/query_results/query_result.html.j2', + query_result=query_result, + title='Query result') + + +@corpora.route('/result//inspect') +@login_required +def inspect_query_result(query_result_id): + ''' + View to inspect imported result file in a corpus analysis like interface + ''' + query_result = QueryResult.query.get_or_404(query_result_id) + query_metadata = query_result.query_metadata + if not (query_result.creator == current_user + or current_user.is_administrator()): + abort(403) + display_options_form = DisplayOptionsForm( + prefix='display-options-form', + results_per_page=request.args.get('results_per_page', 30), + result_context=request.args.get('context', 20) + ) + inspect_display_options_form = InspectDisplayOptionsForm( + prefix='inspect-display-options-form' + ) + query_result_file_path = os.path.join( + current_app.config['NOPAQUE_STORAGE'], + str(current_user.id), + 'query_results', + str(query_result.id), + query_result.filename + ) + with open(query_result_file_path, 'r') as query_result_file: + query_result_file_content = json.load(query_result_file) + return render_template('corpora/query_results/inspect.html.j2', + display_options_form=display_options_form, + inspect_display_options_form=inspect_display_options_form, + query_result_file_content=query_result_file_content, + query_metadata=query_metadata, + title='Inspect query result') + + +@corpora.route('/result//delete') +@login_required +def delete_query_result(query_result_id): + query_result = QueryResult.query.get_or_404(query_result_id) + if not (query_result.creator == current_user + or current_user.is_administrator()): + abort(403) + tasks.delete_query_result(query_result_id) + flash('Query result deleted!', 'result') + return redirect(url_for('services.service', service="corpus_analysis")) + + +@corpora.route('/result//download') +@login_required +def download_query_result(query_result_id): + query_result = QueryResult.query.get_or_404(query_result_id) + if not (query_result.creator == current_user + or current_user.is_administrator()): + abort(403) + query_result_dir = os.path.join(current_app.config['NOPAQUE_STORAGE'], + str(current_user.id), + 'query_results', + str(query_result.id)) + return send_from_directory(as_attachment=True, + directory=query_result_dir, + filename=query_result.filename) diff --git a/web/app/query_results/__init__.py b/web/app/query_results/__init__.py deleted file mode 100644 index a9e7c358..00000000 --- a/web/app/query_results/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from flask import Blueprint - - -query_results = Blueprint('query_results', __name__) -from . import views # noqa diff --git a/web/app/query_results/forms.py b/web/app/query_results/forms.py deleted file mode 100644 index bb55e513..00000000 --- a/web/app/query_results/forms.py +++ /dev/null @@ -1,21 +0,0 @@ -from flask_wtf import FlaskForm -from werkzeug.utils import secure_filename -from wtforms import FileField, StringField, SubmitField, ValidationError -from wtforms.validators import DataRequired, Length - - -class AddQueryResultForm(FlaskForm): - ''' - Form used to import one result json file. - ''' - description = StringField('Description', - validators=[DataRequired(), Length(1, 255)]) - file = FileField('File', validators=[DataRequired()]) - title = StringField('Title', validators=[DataRequired(), Length(1, 32)]) - submit = SubmitField() - - def validate_file(self, field): - if not field.data.filename.lower().endswith('.json'): - raise ValidationError('File does not have an approved extension: ' - '.json') - field.data.filename = secure_filename(field.data.filename) diff --git a/web/app/query_results/tasks.py b/web/app/query_results/tasks.py deleted file mode 100644 index 64f047ca..00000000 --- a/web/app/query_results/tasks.py +++ /dev/null @@ -1,13 +0,0 @@ -from .. import db -from ..decorators import background -from ..models import QueryResult - - -@background -def delete_query_result(query_result_id, *args, **kwargs): - with kwargs['app'].app_context(): - query_result = QueryResult.query.get(query_result_id) - if query_result is None: - raise Exception('QueryResult {} not found'.format(query_result_id)) - query_result.delete() - db.session.commit() diff --git a/web/app/query_results/views.py b/web/app/query_results/views.py deleted file mode 100644 index ac21a749..00000000 --- a/web/app/query_results/views.py +++ /dev/null @@ -1,150 +0,0 @@ -from . import query_results -from . import tasks -from .. import db -from ..corpora.forms import DisplayOptionsForm, InspectDisplayOptionsForm -from ..models import QueryResult -from .forms import AddQueryResultForm -from flask import (abort, current_app, flash, make_response, redirect, - render_template, request, send_from_directory, url_for) -from flask_login import current_user, login_required -import json -import os -from jsonschema import validate - - -@query_results.route('/add', methods=['GET', 'POST']) -@login_required -def add_query_result(): - ''' - View to import a result as a json file. - ''' - add_query_result_form = AddQueryResultForm(prefix='add-query-result-form') - if add_query_result_form.is_submitted(): - if not add_query_result_form.validate(): - return make_response(add_query_result_form.errors, 400) - query_result = QueryResult( - creator=current_user, - description=add_query_result_form.description.data, - filename=add_query_result_form.file.data.filename, - title=add_query_result_form.title.data - ) - db.session.add(query_result) - db.session.commit() - # create paths to save the uploaded json file - query_result_dir = os.path.join(current_app.config['NOPAQUE_STORAGE'], - str(current_user.id), - 'query_results', - str(query_result.id)) - try: - os.makedirs(query_result_dir) - except Exception: - db.session.delete(query_result) - db.session.commit() - flash('Internal Server Error', 'error') - redirect_url = url_for('query_results.add_query_result') - return make_response({'redirect_url': redirect_url}, 500) - # save the uploaded file - query_result_file_path = os.path.join(query_result_dir, - query_result.filename) - add_query_result_form.file.data.save(query_result_file_path) - # parse json from file - with open(query_result_file_path, 'r') as file: - query_result_file_content = json.load(file) - # parse json schema - with open('app/static/json_schema/nopaque_cqi_py_results_schema.json', 'r') as file: # noqa - schema = json.load(file) - try: - # validate imported json file - validate(instance=query_result_file_content, schema=schema) - except Exception: - tasks.delete_query_result(query_result.id) - flash('Uploaded file is invalid', 'result') - redirect_url = url_for('query_results.add_query_result') - return make_response({'redirect_url': redirect_url}, 201) - query_result_file_content.pop('matches') - query_result_file_content.pop('cpos_lookup') - query_result.query_metadata = query_result_file_content - db.session.commit() - flash('Query result added!', 'result') - redirect_url = url_for('query_results.query_result', - query_result_id=query_result.id) - return make_response({'redirect_url': redirect_url}, 201) - return render_template('corpora/query_results/add_query_result.html.j2', - add_query_result_form=add_query_result_form, - title='Add query result') - - -@query_results.route('/') -@login_required -def query_result(query_result_id): - query_result = QueryResult.query.get_or_404(query_result_id) - if not (query_result.creator == current_user - or current_user.is_administrator()): - abort(403) - return render_template('corpora/query_results/query_result.html.j2', - query_result=query_result, - title='Query result') - - -@query_results.route('//inspect') -@login_required -def inspect_query_result(query_result_id): - ''' - View to inspect imported result file in a corpus analysis like interface - ''' - query_result = QueryResult.query.get_or_404(query_result_id) - query_metadata = query_result.query_metadata - if not (query_result.creator == current_user - or current_user.is_administrator()): - abort(403) - display_options_form = DisplayOptionsForm( - prefix='display-options-form', - results_per_page=request.args.get('results_per_page', 30), - result_context=request.args.get('context', 20) - ) - inspect_display_options_form = InspectDisplayOptionsForm( - prefix='inspect-display-options-form' - ) - query_result_file_path = os.path.join( - current_app.config['NOPAQUE_STORAGE'], - str(current_user.id), - 'query_results', - str(query_result.id), - query_result.filename - ) - with open(query_result_file_path, 'r') as query_result_file: - query_result_file_content = json.load(query_result_file) - return render_template('corpora/query_results/inspect.html.j2', - display_options_form=display_options_form, - inspect_display_options_form=inspect_display_options_form, - query_result_file_content=query_result_file_content, - query_metadata=query_metadata, - title='Inspect query result') - - -@query_results.route('//delete') -@login_required -def delete_query_result(query_result_id): - query_result = QueryResult.query.get_or_404(query_result_id) - if not (query_result.creator == current_user - or current_user.is_administrator()): - abort(403) - tasks.delete_query_result(query_result_id) - flash('Query result deleted!', 'result') - return redirect(url_for('services.service', service="corpus_analysis")) - - -@query_results.route('//download') -@login_required -def download_query_result(query_result_id): - query_result = QueryResult.query.get_or_404(query_result_id) - if not (query_result.creator == current_user - or current_user.is_administrator()): - abort(403) - query_result_dir = os.path.join(current_app.config['NOPAQUE_STORAGE'], - str(current_user.id), - 'query_results', - str(query_result.id)) - return send_from_directory(as_attachment=True, - directory=query_result_dir, - filename=query_result.filename) diff --git a/web/app/static/js/nopaque.lists.js b/web/app/static/js/nopaque.lists.js index d44bb468..d91186dc 100644 --- a/web/app/static/js/nopaque.lists.js +++ b/web/app/static/js/nopaque.lists.js @@ -118,13 +118,13 @@ RessourceList.dataMappers = { corpus_name: query_result.query_metadata.corpus_name, description: query_result.description, id: query_result.id, - link: `/query_results/${query_result.id}`, + link: `/corpora/result/${query_result.id}`, query: query_result.query_metadata.query, title: query_result.title, - "delete-link": `/query_results/${query_result.id}/delete`, + "delete-link": `/corpora/result/${query_result.id}/delete`, "delete-modal": `delete-query-result-${query_result.id}-modal`, "delete-modal-trigger": `delete-query-result-${query_result.id}-modal`, - "inspect-link": `/query_results/${query_result.id}/inspect`, + "inspect-link": `/corpora/result/${query_result.id}/inspect`, }), /* ### User mapper ### */ User: user => ({ diff --git a/web/app/templates/corpora/query_results/inspect.html.j2 b/web/app/templates/corpora/query_results/inspect.html.j2 index 493ae8bd..9de1a779 100644 --- a/web/app/templates/corpora/query_results/inspect.html.j2 +++ b/web/app/templates/corpora/query_results/inspect.html.j2 @@ -70,7 +70,7 @@ import { Client, ClientEventListener, ListenerCallback, -} from '../../static/js/modules/corpus_analysis/client/Client.js'; +} from '../../../static/js/modules/corpus_analysis/client/Client.js'; /** * Import Client listener functions which will listen for defined socket or * javascript events. @@ -78,16 +78,16 @@ import { import { recieveQueryStatus, recieveQueryData, -} from '../../static/js/modules/corpus_analysis/client/listeners.js'; +} from '../../../static/js/modules/corpus_analysis/client/listeners.js'; // Import client listener callbacks so they can be registered to the listeners. import { prepareQueryData, saveQueryData, -} from '../../static/js/modules/corpus_analysis/client/callbacks.js'; +} from '../../../static/js/modules/corpus_analysis/client/callbacks.js'; // Import Results class which will be used to save results data of a query etc. import { Results, -} from '../../static/js/modules/corpus_analysis/model/Results.js'; +} from '../../../static/js/modules/corpus_analysis/model/Results.js'; /** * Import the ResultsList which can be understood as a View class that handles * how the data from Results is represented to the user. The ViewEventListener @@ -97,7 +97,7 @@ import { import { ResultsList, ViewEventListener, -} from '../../static/js/modules/corpus_analysis/view/ResultsView.js'; +} from '../../../static/js/modules/corpus_analysis/view/ResultsView.js'; // Import listener which will be registered to the ViewEventListener class. import { // listener listening for client dispatched 'notify-vie' custom event. @@ -109,10 +109,10 @@ import { displayOptions, showMetaData, showCorpusFiles, -} from '../../static/js/modules/corpus_analysis/view/listeners.js'; +} from '../../../static/js/modules/corpus_analysis/view/listeners.js'; import { scrollToTop, -} from '../../static/js/modules/corpus_analysis/view/scrollToTop.js' +} from '../../../static/js/modules/corpus_analysis/view/scrollToTop.js' /** * Second Phase: * Asynchronus and event driven code. diff --git a/web/app/templates/corpora/query_results/query_result.html.j2 b/web/app/templates/corpora/query_results/query_result.html.j2 index b02cb26b..95ba18ca 100644 --- a/web/app/templates/corpora/query_results/query_result.html.j2 +++ b/web/app/templates/corpora/query_results/query_result.html.j2 @@ -1,8 +1,7 @@ {% extends "nopaque.html.j2" %} {% block page_content %} - - +{{ Macros.insert_color_scheme(corpus_analysis_color_darken) }}

Below the metadata for the results from the Corpus {{ query_result.query_metadata.corpus_name }} generated with the query @@ -13,6 +12,10 @@

+
@@ -63,7 +66,7 @@ diff --git a/web/app/templates/main/dashboard.html.j2 b/web/app/templates/main/dashboard.html.j2 index 116b3e02..194bdecb 100644 --- a/web/app/templates/main/dashboard.html.j2 +++ b/web/app/templates/main/dashboard.html.j2 @@ -76,7 +76,7 @@
    diff --git a/web/app/templates/services/corpus_analysis.html.j2 b/web/app/templates/services/corpus_analysis.html.j2 index f6eadf60..e50cab77 100644 --- a/web/app/templates/services/corpus_analysis.html.j2 +++ b/web/app/templates/services/corpus_analysis.html.j2 @@ -86,7 +86,7 @@