mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2024-12-25 02:44:18 +00:00
Add function to import exported results and view them after the import
This commit is contained in:
parent
a997fbe0ee
commit
1811623583
@ -55,4 +55,7 @@ def create_app(config_name):
|
|||||||
from .services import services as services_blueprint
|
from .services import services as services_blueprint
|
||||||
app.register_blueprint(services_blueprint, url_prefix='/services')
|
app.register_blueprint(services_blueprint, url_prefix='/services')
|
||||||
|
|
||||||
|
from .results import results as results_blueprint
|
||||||
|
app.register_blueprint(results_blueprint, url_prefix='/results')
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
@ -2,9 +2,9 @@ from flask_table import Table, Col, LinkCol
|
|||||||
|
|
||||||
|
|
||||||
class AdminUserTable(Table):
|
class AdminUserTable(Table):
|
||||||
"""
|
'''
|
||||||
Declares the table describing colum by column.
|
Declares the table describing colum by column.
|
||||||
"""
|
'''
|
||||||
classes = ['highlight', 'responsive-table']
|
classes = ['highlight', 'responsive-table']
|
||||||
username = Col('Username', column_html_attrs={'class': 'username'},
|
username = Col('Username', column_html_attrs={'class': 'username'},
|
||||||
th_html_attrs={'class': 'sort',
|
th_html_attrs={'class': 'sort',
|
||||||
@ -28,9 +28,9 @@ class AdminUserTable(Table):
|
|||||||
|
|
||||||
|
|
||||||
class AdminUserItem(object):
|
class AdminUserItem(object):
|
||||||
"""
|
'''
|
||||||
Describes one item like one row per table.
|
Describes one item like one row per table.
|
||||||
"""
|
'''
|
||||||
|
|
||||||
def __init__(self, username, email, role_id, confirmed, id):
|
def __init__(self, username, email, role_id, confirmed, id):
|
||||||
self.username = username
|
self.username = username
|
||||||
|
@ -129,6 +129,8 @@ class User(UserMixin, db.Model):
|
|||||||
cascade='save-update, merge, delete')
|
cascade='save-update, merge, delete')
|
||||||
jobs = db.relationship('Job', backref='creator', lazy='dynamic',
|
jobs = db.relationship('Job', backref='creator', lazy='dynamic',
|
||||||
cascade='save-update, merge, delete')
|
cascade='save-update, merge, delete')
|
||||||
|
results = db.relationship('Result', backref='creator', lazy='dynamic',
|
||||||
|
cascade='save-update, merge, delete')
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {'id': self.id,
|
return {'id': self.id,
|
||||||
@ -532,6 +534,34 @@ class Corpus(db.Model):
|
|||||||
return '<Corpus {corpus_title}>'.format(corpus_title=self.title)
|
return '<Corpus {corpus_title}>'.format(corpus_title=self.title)
|
||||||
|
|
||||||
|
|
||||||
|
class Result (db.Model):
|
||||||
|
'''
|
||||||
|
Class to define a result set of one query.
|
||||||
|
'''
|
||||||
|
__tablename__ = 'results'
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
# Foreign keys
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
|
||||||
|
# Relationships'
|
||||||
|
corpus_metadata = db.Column(db.JSON())
|
||||||
|
file = db.relationship('ResultFile', backref='result', lazy='dynamic',
|
||||||
|
cascade='save-update, merge, delete')
|
||||||
|
|
||||||
|
|
||||||
|
class ResultFile(db.Model):
|
||||||
|
'''
|
||||||
|
Class to define a ResultFile
|
||||||
|
'''
|
||||||
|
__tablename__ = 'result_files'
|
||||||
|
# Primary key
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
# Foreign keys
|
||||||
|
result_id = db.Column(db.Integer, db.ForeignKey('results.id'))
|
||||||
|
# Fields
|
||||||
|
filename = db.Column(db.String(255))
|
||||||
|
dir = db.Column(db.String(255))
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
' Flask-Login is told to use the application’s custom anonymous user by setting
|
' Flask-Login is told to use the application’s custom anonymous user by setting
|
||||||
' its class in the login_manager.anonymous_user attribute.
|
' its class in the login_manager.anonymous_user attribute.
|
||||||
|
5
web/app/results/__init__.py
Normal file
5
web/app/results/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
|
||||||
|
results = Blueprint('results', __name__)
|
||||||
|
from . import views # noqa
|
0
web/app/results/forms.py
Normal file
0
web/app/results/forms.py
Normal file
0
web/app/results/tables.py
Normal file
0
web/app/results/tables.py
Normal file
46
web/app/results/views.py
Normal file
46
web/app/results/views.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
from . import results
|
||||||
|
from ..models import Result
|
||||||
|
from flask import abort, render_template, current_app, request
|
||||||
|
from flask_login import current_user, login_required
|
||||||
|
from ..corpora.forms import DisplayOptionsForm
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
@results.route('/<int:result_id>/details')
|
||||||
|
@login_required
|
||||||
|
def result_details(result_id):
|
||||||
|
'''
|
||||||
|
View to show metadate and details about on imported result file.
|
||||||
|
'''
|
||||||
|
result = Result.query.get_or_404(result_id)
|
||||||
|
if not (result.creator == current_user or current_user.is_administrator()):
|
||||||
|
abort(403)
|
||||||
|
return render_template('results/result_details.html.j2',
|
||||||
|
result=result,
|
||||||
|
title='Result Details')
|
||||||
|
|
||||||
|
|
||||||
|
@results.route('/<int:result_id>/inspect')
|
||||||
|
@login_required
|
||||||
|
def result_inspect(result_id):
|
||||||
|
'''
|
||||||
|
View to inspect one importe result file in a corpus analysis like interface
|
||||||
|
'''
|
||||||
|
display_options_form = DisplayOptionsForm(
|
||||||
|
prefix='display-options-form',
|
||||||
|
result_context=request.args.get('context', 20),
|
||||||
|
results_per_page=request.args.get('results_per_page', 30))
|
||||||
|
result = Result.query.get_or_404(result_id)
|
||||||
|
result_file_path = os.path.join(current_app.config['NOPAQUE_STORAGE'],
|
||||||
|
result.file[0].dir,
|
||||||
|
result.file[0].filename)
|
||||||
|
with open(result_file_path, 'r') as result_json:
|
||||||
|
result_json = json.load(result_json)
|
||||||
|
if not (result.creator == current_user or current_user.is_administrator()):
|
||||||
|
abort(403)
|
||||||
|
return render_template('results/result_inspect.html.j2',
|
||||||
|
display_options_form=display_options_form,
|
||||||
|
result=result,
|
||||||
|
result_json=result_json,
|
||||||
|
title='Result Insepct')
|
18
web/app/services/forms.py
Normal file
18
web/app/services/forms.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
from wtforms import FileField, SubmitField, ValidationError
|
||||||
|
from wtforms.validators import DataRequired
|
||||||
|
|
||||||
|
|
||||||
|
class ImportResultsForm(FlaskForm):
|
||||||
|
'''
|
||||||
|
Form used to import one result json file.
|
||||||
|
'''
|
||||||
|
file = FileField('File', validators=[DataRequired()])
|
||||||
|
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)
|
65
web/app/services/tables.py
Normal file
65
web/app/services/tables.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
from flask_table import Table, Col, DatetimeCol, LinkCol
|
||||||
|
|
||||||
|
|
||||||
|
class ResultTable(Table):
|
||||||
|
'''
|
||||||
|
Declares the Table showing results. Declaration is column by column.
|
||||||
|
'''
|
||||||
|
classes = ['highlight', 'responsive-table']
|
||||||
|
query = Col('Query', column_html_attrs={'class': 'query'},
|
||||||
|
th_html_attrs={'class': 'sort',
|
||||||
|
'data-sort': 'query'})
|
||||||
|
match_count = Col('Match count', column_html_attrs={'class':
|
||||||
|
'match-count'},
|
||||||
|
th_html_attrs={'class': 'sort',
|
||||||
|
'data-sort': 'match-count'})
|
||||||
|
corpus_name = Col('Corpus name', column_html_attrs={'class':
|
||||||
|
'corpus-name'},
|
||||||
|
th_html_attrs={'class': 'sort',
|
||||||
|
'data-sort': 'corpus-name'})
|
||||||
|
corpus_creation_date = DatetimeCol('Corpus creation date',
|
||||||
|
column_html_attrs={'class':
|
||||||
|
'corpus-creation- date'}, # noqa
|
||||||
|
th_html_attrs={'class': 'sort',
|
||||||
|
'data-sort':
|
||||||
|
'corpus-creation-date'},
|
||||||
|
datetime_format='dd/MM/yyyy, HH:mm:ss a') # noqa
|
||||||
|
corpus_analysis_date = DatetimeCol('Date of result creation',
|
||||||
|
column_html_attrs={'class':
|
||||||
|
'corpus-analysis-data'}, # noqa
|
||||||
|
th_html_attrs={'class': 'sort',
|
||||||
|
'data-sort':
|
||||||
|
'corpus-analysis-data'},
|
||||||
|
datetime_format='dd/MM/yyyy, HH:mm:ss a') # noqa
|
||||||
|
corpus_type = Col('Result Type',
|
||||||
|
column_html_attrs={'class':
|
||||||
|
'corpus-type'},
|
||||||
|
th_html_attrs={'class': 'sort',
|
||||||
|
'data-sort':
|
||||||
|
'corpus-type'})
|
||||||
|
details = LinkCol('Details', 'results.result_details',
|
||||||
|
url_kwargs=dict(result_id='id'),
|
||||||
|
anchor_attrs={'class': 'waves-effect waves-light btn-floating'}, # noqa
|
||||||
|
text_fallback='<i class="material-icons">info_outline</i>') # noqa
|
||||||
|
inspect = LinkCol('Inspect', 'results.result_inspect',
|
||||||
|
url_kwargs=dict(result_id='id'),
|
||||||
|
anchor_attrs={'class': 'waves-effect waves-light btn-floating'}, # noqa
|
||||||
|
text_fallback='<i class="material-icons">search</i>') # noqa
|
||||||
|
# TODO: Maybe somehow fix taht there are two columns fpr two action buttons
|
||||||
|
# Or maybe just get rid of flask tables?
|
||||||
|
|
||||||
|
|
||||||
|
class ResultItem(object):
|
||||||
|
'''
|
||||||
|
Describes one result item row.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, query, match_count, corpus_name, corpus_creation_date,
|
||||||
|
corpus_analysis_date, corpus_type, id):
|
||||||
|
self.query = query
|
||||||
|
self.match_count = match_count
|
||||||
|
self.corpus_name = corpus_name
|
||||||
|
self.corpus_creation_date = corpus_creation_date
|
||||||
|
self.corpus_analysis_date = corpus_analysis_date
|
||||||
|
self.corpus_type = corpus_type
|
||||||
|
self.id = id
|
@ -1,13 +1,17 @@
|
|||||||
from flask import (abort, current_app, flash, make_response, render_template,
|
from flask import (abort, current_app, flash, make_response, render_template,
|
||||||
url_for)
|
url_for)
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
|
from .forms import ImportResultsForm
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
from . import services
|
from . import services
|
||||||
from .. import db
|
from .. import db
|
||||||
from ..jobs.forms import AddFileSetupJobForm, AddNLPJobForm, AddOCRJobForm
|
from ..jobs.forms import AddFileSetupJobForm, AddNLPJobForm, AddOCRJobForm
|
||||||
from ..models import Job, JobInput
|
from ..models import Job, JobInput, Result, ResultFile, User
|
||||||
|
from .tables import ResultTable, ResultItem
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import html
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
SERVICES = {'corpus_analysis': {'name': 'Corpus analysis'},
|
SERVICES = {'corpus_analysis': {'name': 'Corpus analysis'},
|
||||||
@ -81,3 +85,84 @@ def service(service):
|
|||||||
return render_template('services/{}.html.j2'.format(service),
|
return render_template('services/{}.html.j2'.format(service),
|
||||||
title=SERVICES[service]['name'],
|
title=SERVICES[service]['name'],
|
||||||
add_job_form=add_job_form)
|
add_job_form=add_job_form)
|
||||||
|
|
||||||
|
|
||||||
|
@services.route('/import_results', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def import_results():
|
||||||
|
'''
|
||||||
|
View to import one json result file. Uses the ImportReultFileForm.
|
||||||
|
'''
|
||||||
|
# TODO: Build in a check if uploaded json is actually a result file and
|
||||||
|
# not something different
|
||||||
|
# Add the possibility to add several result files at once.
|
||||||
|
import_results_form = ImportResultsForm(prefix='add-result-file-form')
|
||||||
|
if import_results_form.is_submitted():
|
||||||
|
if not import_results_form.validate():
|
||||||
|
return make_response(import_results_form.errors, 400)
|
||||||
|
# Save the file
|
||||||
|
# result creation only happens on file save to avoid creating a result
|
||||||
|
# object in the db everytime by just visiting the import_results page
|
||||||
|
result = Result(user_id=current_user.id)
|
||||||
|
db.session.add(result)
|
||||||
|
db.session.commit()
|
||||||
|
if not (result.creator == current_user
|
||||||
|
or current_user.is_administrator()):
|
||||||
|
abort(403)
|
||||||
|
dir = os.path.join(str(result.user_id),
|
||||||
|
'results',
|
||||||
|
'corpus_analysis_results',
|
||||||
|
str(result.id))
|
||||||
|
abs_dir = os.path.join(current_app.config['NOPAQUE_STORAGE'], dir)
|
||||||
|
abs_file_path = os.path.join(abs_dir,
|
||||||
|
import_results_form.file.data.filename)
|
||||||
|
os.makedirs(abs_dir)
|
||||||
|
import_results_form.file.data.save(abs_file_path)
|
||||||
|
# Saves all needed metadata entries in one json field
|
||||||
|
with open(abs_file_path, 'r') as f:
|
||||||
|
corpus_metadata = json.load(f)
|
||||||
|
del corpus_metadata['matches']
|
||||||
|
del corpus_metadata['cpos_lookup']
|
||||||
|
result_file = ResultFile(
|
||||||
|
result_id=result.id,
|
||||||
|
dir=dir,
|
||||||
|
filename=import_results_form.file.data.filename)
|
||||||
|
result.corpus_metadata = corpus_metadata
|
||||||
|
db.session.add(result_file)
|
||||||
|
db.session.commit()
|
||||||
|
flash('Result file added!', 'result')
|
||||||
|
return make_response(
|
||||||
|
{'redirect_url': url_for('services.results')},
|
||||||
|
201)
|
||||||
|
return render_template('services/import_results.html.j2',
|
||||||
|
import_results_form=import_results_form,
|
||||||
|
title='Add corpus file')
|
||||||
|
|
||||||
|
|
||||||
|
@services.route('/results')
|
||||||
|
@login_required
|
||||||
|
def results():
|
||||||
|
'''
|
||||||
|
Shows an overview of imported results.
|
||||||
|
'''
|
||||||
|
# get all results of current user
|
||||||
|
results = User.query.get(current_user.id).results
|
||||||
|
# create table row for every result#
|
||||||
|
|
||||||
|
def __p_time(time_str):
|
||||||
|
return datetime.strptime(time_str, '%Y-%m-%dT%H:%M:%S.%f')
|
||||||
|
|
||||||
|
items = [ResultItem(r.corpus_metadata['query'],
|
||||||
|
r.corpus_metadata['match_count'],
|
||||||
|
r.corpus_metadata['corpus_name'],
|
||||||
|
__p_time(r.corpus_metadata['corpus_creation_date']),
|
||||||
|
__p_time(r.corpus_metadata['corpus_analysis_date']),
|
||||||
|
r.corpus_metadata['corpus_type'],
|
||||||
|
r.id) for r in results]
|
||||||
|
# create table with items and save it as html
|
||||||
|
table = html.unescape(ResultTable(items).__html__())
|
||||||
|
# add class=list to table body with string replacement
|
||||||
|
table = table.replace('tbody', 'tbody class=list', 1)
|
||||||
|
return render_template('services/results.html.j2',
|
||||||
|
title='Imported Results',
|
||||||
|
table=table)
|
||||||
|
@ -29,4 +29,32 @@ class InteractionElement {
|
|||||||
let boundedCallback = callback["function"].bind(callback.bindThis);
|
let boundedCallback = callback["function"].bind(callback.bindThis);
|
||||||
return boundedCallback;
|
return boundedCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static onChangeExecute(interactionElements) {
|
||||||
|
// checks if a change for every interactionElement happens and executes
|
||||||
|
// the callbacks accordingly
|
||||||
|
// TODO: This function scould be a static function of the Class InteractionElements
|
||||||
|
// This class does not exist yet. The Class InteractionElements should hold
|
||||||
|
// a list of InteractionElement objects. onChangeExecute loops over InteractionElements
|
||||||
|
// and executes the callbacks as mentioned accordingly. An additional
|
||||||
|
// InteractionElements Class is logically right but also makes things a little more
|
||||||
|
// complex. It is not yet decided.
|
||||||
|
for (let interaction of interactionElements) {
|
||||||
|
if (interaction.checkStatus) {
|
||||||
|
interaction.element.addEventListener("change", (event) => {
|
||||||
|
if (event.target.checked) {
|
||||||
|
let f_on = interaction.bindThisToCallback("on");
|
||||||
|
let args_on = interaction.callbacks.on.args;
|
||||||
|
f_on(...args_on);
|
||||||
|
} else if (!event.target.checked){
|
||||||
|
let f_off = interaction.bindThisToCallback("off");
|
||||||
|
let args_off = interaction.callbacks.off.args;
|
||||||
|
f_off(...args_off);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
@ -63,7 +63,7 @@ function querySetup(payload) {
|
|||||||
|
|
||||||
// This callback is called on socket.on "query_results"
|
// This callback is called on socket.on "query_results"
|
||||||
// this handels the incoming result chunks
|
// this handels the incoming result chunks
|
||||||
function queryRenderResults(payload) {
|
function queryRenderResults(payload, imported=false) {
|
||||||
let resultItems; // array of built html result items row element
|
let resultItems; // array of built html result items row element
|
||||||
// This is called when results are transmitted and being recieved
|
// This is called when results are transmitted and being recieved
|
||||||
console.log("Current recieved chunk:", payload.chunk);
|
console.log("Current recieved chunk:", payload.chunk);
|
||||||
@ -102,18 +102,23 @@ function queryRenderResults(payload) {
|
|||||||
console.log("Results recieved:", results.data);
|
console.log("Results recieved:", results.data);
|
||||||
// upate progress status
|
// upate progress status
|
||||||
progress = payload.progress; // global declaration
|
progress = payload.progress; // global declaration
|
||||||
if (progress === 100) {
|
if (progress === 100 && !imported) {
|
||||||
queryResultsProgressElement.classList.add("hide");
|
queryResultsProgressElement.classList.add("hide");
|
||||||
queryResultsUserFeedbackElement.classList.add("hide");
|
queryResultsUserFeedbackElement.classList.add("hide");
|
||||||
queryResultsExportElement.classList.remove("disabled");
|
queryResultsExportElement.classList.remove("disabled");
|
||||||
addToSubResultsElement.removeAttribute("disabled");
|
addToSubResultsElement.removeAttribute("disabled");
|
||||||
results.jsList.activateInspect();
|
|
||||||
// inital expert mode check and sub results activation
|
// inital expert mode check and sub results activation
|
||||||
|
results.jsList.activateInspect();
|
||||||
if (addToSubResultsElement.checked) {
|
if (addToSubResultsElement.checked) {
|
||||||
results.jsList.activateAddToSubResults();
|
results.jsList.activateAddToSubResults();
|
||||||
}
|
}
|
||||||
if (expertModeSwitchElement.checked) {
|
if (expertModeSwitchElement.checked) {
|
||||||
results.jsList.expertModeOn("query-display");
|
results.jsList.expertModeOn("query-display");
|
||||||
}
|
}
|
||||||
|
} else if (imported) {
|
||||||
|
results.jsList.activateInspect();
|
||||||
|
if (expertModeSwitchElement.checked) {
|
||||||
|
results.jsList.expertModeOn("query-display");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -200,7 +200,6 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<ul class="pagination paginationBottom"></ul>
|
<ul class="pagination paginationBottom"></ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -567,23 +566,7 @@
|
|||||||
|
|
||||||
// checks if a change for every interactionElement happens and executes
|
// checks if a change for every interactionElement happens and executes
|
||||||
// the callbacks accordingly
|
// the callbacks accordingly
|
||||||
for (let interaction of interactionElements) {
|
InteractionElement.onChangeExecute(interactionElements);
|
||||||
if (interaction.checkStatus) {
|
|
||||||
interaction.element.addEventListener("change", (event) => {
|
|
||||||
if (event.target.checked) {
|
|
||||||
let f_on = interaction.bindThisToCallback("on");
|
|
||||||
let args_on = interaction.callbacks.on.args;
|
|
||||||
f_on(...args_on);
|
|
||||||
} else if (!event.target.checked){
|
|
||||||
let f_off = interaction.bindThisToCallback("off");
|
|
||||||
let args_off = interaction.callbacks.off.args;
|
|
||||||
f_off(...args_off);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// eventListener if pagination is used to apply new context size to new page
|
// eventListener if pagination is used to apply new context size to new page
|
||||||
// and also activate inspect match if progress is 100
|
// and also activate inspect match if progress is 100
|
||||||
|
@ -27,13 +27,13 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s12 m6">
|
<div class="col s12 m6">
|
||||||
<div class="input-field">
|
<div class="input-field">
|
||||||
<input disabled value="{{ corpus.creation_date.strftime('%m/%d/%Y, %H:%M:%S %p') }}" id="creation-date" type="text" class="validate">
|
<input disabled value="{{ corpus.creation_date.strftime('%d/%m/%Y, %H:%M:%S %p') }}" id="creation-date" type="text" class="validate">
|
||||||
<label for="creation-date">Creation date</label>
|
<label for="creation-date">Creation date</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col s12 m6">
|
<div class="col s12 m6">
|
||||||
<div class="input-field">
|
<div class="input-field">
|
||||||
<input disabled value="{{ corpus.last_edited_date.strftime('%m/%d/%Y, %H:%M:%S %p') }}" id="last_edited_date" type="text" class="validate">
|
<input disabled value="{{ corpus.last_edited_date.strftime('%d/%m/%Y, %H:%M:%S %p') }}" id="last_edited_date" type="text" class="validate">
|
||||||
<label for="creation-date">Last edited</label>
|
<label for="creation-date">Last edited</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
<div class="col s12 m6">
|
<div class="col s12 m6">
|
||||||
<div class="input-field">
|
<div class="input-field">
|
||||||
<input disabled id="creation-date" type="text" value="{{ job.creation_date.strftime('%m/%d/%Y, %H:%M:%S %p') }}">
|
<input disabled id="creation-date" type="text" value="{{ job.creation_date.strftime('%d/%m/%Y, %H:%M:%S %p') }}">
|
||||||
<label for="creation-date">Creation date</label>
|
<label for="creation-date">Creation date</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
<ul class="pagination"></ul>
|
<ul class="pagination"></ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-action right-align">
|
<div class="card-action right-align">
|
||||||
|
<a class="waves-effect waves-light btn" href="{{ url_for('services.results') }}">Show Imported Results<i class="material-icons right">folder</i></a>
|
||||||
<a class="waves-effect waves-light btn" href="{{ url_for('corpora.add_corpus') }}">New corpus<i class="material-icons right">add</i></a>
|
<a class="waves-effect waves-light btn" href="{{ url_for('corpora.add_corpus') }}">New corpus<i class="material-icons right">add</i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
118
web/app/templates/results/result_details.html.j2
Normal file
118
web/app/templates/results/result_details.html.j2
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
{% extends "nopaque.html.j2" %}
|
||||||
|
|
||||||
|
{% block page_content %}
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col s12">
|
||||||
|
<p>Below the metadata for the results from the Corpus
|
||||||
|
<i>{{ result.corpus_metadata.corpus_name }}</i> generated with the query
|
||||||
|
<i>{{ result.corpus_metadata.query }}</i> are shown.
|
||||||
|
</p>
|
||||||
|
<p>{{ texts_metadata }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col s12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content" id="results">
|
||||||
|
<table class="responsive-table highlight">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Metadata Description</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for pair in result.corpus_metadata|dictsort %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ pair[0] }}</td>
|
||||||
|
{% if pair[0] == 'corpus_all_texts'
|
||||||
|
or pair[0] == 'text_lookup' %}
|
||||||
|
<td>
|
||||||
|
<table>
|
||||||
|
{% for key, value in pair[1].items() %}
|
||||||
|
<tr style="border-bottom: none;">
|
||||||
|
<td>
|
||||||
|
<i>{{ value['title'] }}</i> written
|
||||||
|
by <i>{{ value['author'] }}</i>
|
||||||
|
in <i>{{ value['publishing_year'] }}</i>
|
||||||
|
<a class="waves-effect
|
||||||
|
waves-light
|
||||||
|
btn
|
||||||
|
right
|
||||||
|
more-text-detials"
|
||||||
|
data-metadata-key="{{ pair[0] }}"
|
||||||
|
data-text-key="{{ key }}"
|
||||||
|
href="#modal-text-details">More
|
||||||
|
<i class="material-icons right"
|
||||||
|
data-metadata-key="{{ pair[0] }}"
|
||||||
|
data-text-key="{{ key }}">
|
||||||
|
info_outline
|
||||||
|
</i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
{% else %}
|
||||||
|
<td>{{ pair[1] }}</td>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="card-action right-align">
|
||||||
|
<a class="waves-effect waves-light btn" href="{{ url_for('services.import_results') }}">Inspect Results<i class="material-icons right">search</i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Structure -->
|
||||||
|
<div id="modal-text-details" class="modal modal-fixed-footer">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h4>Bibliographic data</h4>
|
||||||
|
<p id="bibliographic-data"></p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<a href="#!" class="modal-close waves-effect waves-green red btn">Close</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var moreTextDetailsButtons;
|
||||||
|
moreTextDetailsButtons = document.getElementsByClassName("more-text-detials");
|
||||||
|
for (var btn of moreTextDetailsButtons) {
|
||||||
|
btn.onclick = () => {
|
||||||
|
let modal = document.getElementById("modal-text-details");
|
||||||
|
modal = M.Modal.init(modal, {"dismissible": true});
|
||||||
|
modal.open();
|
||||||
|
let metadataKey = event.target.dataset.metadataKey;
|
||||||
|
let textKey = event.target.dataset.textKey;
|
||||||
|
let textData = {{ result.corpus_metadata|tojson|safe }}[metadataKey][textKey];
|
||||||
|
console.log(textData);
|
||||||
|
let bibliographicData = document.getElementById("bibliographic-data");
|
||||||
|
bibliographicData.innerHTML = "";
|
||||||
|
let table = document.createElement("table");
|
||||||
|
for (let [key, value] of Object.entries(textData)) {
|
||||||
|
table.insertAdjacentHTML("afterbegin",
|
||||||
|
`
|
||||||
|
<tr>
|
||||||
|
<td>${key}</td>
|
||||||
|
<td>${value}</td>
|
||||||
|
</tr>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
table.insertAdjacentHTML("afterbegin",
|
||||||
|
`
|
||||||
|
<thead>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</thead>
|
||||||
|
`)
|
||||||
|
bibliographicData.appendChild(table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
211
web/app/templates/results/result_inspect.html.j2
Normal file
211
web/app/templates/results/result_inspect.html.j2
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
{% extends "nopaque.html.j2" %}
|
||||||
|
|
||||||
|
{% set headline = ' ' %}
|
||||||
|
|
||||||
|
{% set full_width = True %}
|
||||||
|
|
||||||
|
{% block page_content %}
|
||||||
|
<div class="col s12" id="query-display">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content" id="result-list" style="overflow: hidden;">
|
||||||
|
<div class="row" style="margin-bottom: 0px;">
|
||||||
|
<div class="col s12 m3 l3" id="results-info">
|
||||||
|
<div class="row section">
|
||||||
|
<h6 style="margin-top: 0px;">Infos</h6>
|
||||||
|
<div class="divider" style="margin-bottom: 10px;"></div>
|
||||||
|
<div class="col" id="infos">
|
||||||
|
<p>
|
||||||
|
Displaying
|
||||||
|
<span id="received-match-count">
|
||||||
|
</span> of
|
||||||
|
<span id="match-count"></span>
|
||||||
|
matches.
|
||||||
|
<br>
|
||||||
|
Matches occured in
|
||||||
|
<span id="text-lookup-count"></span>
|
||||||
|
corpus files:
|
||||||
|
<br>
|
||||||
|
<span id=text-titles></span>
|
||||||
|
</p>
|
||||||
|
<div class="progress hide" id="query-results-progress">
|
||||||
|
<div class="determinate" id="query-results-determinate"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col s12 m9 l9" id="actions-and-tools">
|
||||||
|
<div class="row section">
|
||||||
|
<div class="col s12 m3 l3" id="display">
|
||||||
|
<h6 style="margin-top: 0px;">Display</h6>
|
||||||
|
<div class="divider" style="margin-bottom: 10px;"></div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12">
|
||||||
|
<form id="display-options-form">
|
||||||
|
{{ M.render_field(display_options_form.results_per_page,
|
||||||
|
material_icon='format_list_numbered') }}
|
||||||
|
{{ M.render_field(display_options_form.result_context,
|
||||||
|
material_icon='short_text') }}
|
||||||
|
{{ M.render_field(display_options_form.expert_mode) }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Table showing the query results -->
|
||||||
|
<div class="col s12">
|
||||||
|
<ul class="pagination paginationTop"></ul>
|
||||||
|
<table class="responsive-table highlight">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 2%">Nr.</th>
|
||||||
|
<th style="width: 3%">Title</th>
|
||||||
|
<th style="width: 25%">Left context</th>
|
||||||
|
<th style="width: 35%">Match</th>
|
||||||
|
<th style="width: 10%">Actions</th>
|
||||||
|
<th style="width: 25%">Right Context</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="list" id="query-results">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<ul class="pagination paginationBottom"></ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="{{ url_for('static', filename='js/nopaque.Results.js') }}">
|
||||||
|
</script>
|
||||||
|
<script src="{{ url_for('static', filename='js/nopaque.callbacks.js') }}">
|
||||||
|
</script>
|
||||||
|
<script src="{{ url_for('static', filename='js/nopaque.InteractionElement.js') }}">
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
// ###### global variables ######
|
||||||
|
var full_result_json;
|
||||||
|
var result_json;
|
||||||
|
var queryResultsDeterminateElement; // The progress bar for recieved results
|
||||||
|
var receivedMatchCountElement; // Nr. of loaded matches will be displayed in this element
|
||||||
|
var textLookupCountElement // Nr of texts the matches occured in will be shown in this element
|
||||||
|
var textTitlesElement; // matched text titles
|
||||||
|
var progress; // global progress value
|
||||||
|
var queryResultsProgressElement; // Div element holding the progress bar
|
||||||
|
var expertModeSwitchElement; // Expert mode switch Element
|
||||||
|
var matchCountElement; // Total nr. of matches will be displayed in this element
|
||||||
|
var interactionElements; // Interaction elements and their parameters
|
||||||
|
|
||||||
|
// ###### Defining local scope variables
|
||||||
|
let displayOptionsFormElement; // Form holding the display informations
|
||||||
|
let resultItems; // array of built html result items row element. This is called when results are transmitted and being recieved
|
||||||
|
let hitsPerPageInputElement;let contextPerItemElement; // Form Element for display option
|
||||||
|
let paginationElements;
|
||||||
|
|
||||||
|
// ###### Initializing variables ######
|
||||||
|
displayOptionsFormElement = document.getElementById("display-options-form");
|
||||||
|
resultItems = [];
|
||||||
|
queryResultsDeterminateElement = document.getElementById("query-results-determinate");
|
||||||
|
receivedMatchCountElement = document.getElementById("received-match-count");
|
||||||
|
textLookupCountElement = document.getElementById("text-lookup-count");
|
||||||
|
textTitlesElement = document.getElementById("text-titles");
|
||||||
|
queryResultsProgressElement = document.getElementById("query-results-progress");
|
||||||
|
expertModeSwitchElement = document.getElementById("display-options-form-expert_mode");
|
||||||
|
matchCountElement = document.getElementById("match-count");
|
||||||
|
hitsPerPageInputElement = document.getElementById("display-options-form-results_per_page");
|
||||||
|
contextPerItemElement = document.getElementById("display-options-form-result_context");
|
||||||
|
paginationElements = document.getElementsByClassName("pagination");
|
||||||
|
|
||||||
|
// js list options
|
||||||
|
displayOptionsData = ResultsList.getDisplayOptions(displayOptionsFormElement);
|
||||||
|
resultsListOptions = {page: displayOptionsData["resultsPerPage"],
|
||||||
|
pagination: [{
|
||||||
|
name: "paginationTop",
|
||||||
|
paginationClass: "paginationTop",
|
||||||
|
innerWindow: 8,
|
||||||
|
outerWindow: 1
|
||||||
|
}, {
|
||||||
|
paginationClass: "paginationBottom",
|
||||||
|
innerWindow: 8,
|
||||||
|
outerWindow: 1
|
||||||
|
}],
|
||||||
|
valueNames: ["titles", "lc", "c", "rc", {data: ["index"]}],
|
||||||
|
item: `<span></span>`
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
// ###### recreating chunk structure to reuse callback queryRenderResults()
|
||||||
|
full_result_json = {{ result_json|tojson|safe }};
|
||||||
|
result_json = {};
|
||||||
|
result_json.chunk = {};
|
||||||
|
result_json.chunk["cpos_lookup"] = full_result_json.cpos_lookup;
|
||||||
|
result_json.chunk["cpos_ranges"] = full_result_json.cpos_ranges;
|
||||||
|
result_json.chunk["matches"] = full_result_json.matches;
|
||||||
|
result_json.chunk["text_lookup"] = full_result_json.text_lookup;
|
||||||
|
|
||||||
|
// Init corpus analysis components
|
||||||
|
data = new Data();
|
||||||
|
resultsList = new ResultsList("result-list", resultsListOptions);
|
||||||
|
resultsMetaData = new MetaData();
|
||||||
|
results = new Results(data, resultsList, resultsMetaData);
|
||||||
|
results.clearAll(); // inits some object keys and values
|
||||||
|
// TODO: save metadate into results.metaData
|
||||||
|
|
||||||
|
// setting some initial values for user feedback
|
||||||
|
matchCountElement.innerText = full_result_json.match_count;
|
||||||
|
|
||||||
|
// Initialization of interactionElemnts
|
||||||
|
// An interactionElement is an object identifing a switch or button via
|
||||||
|
// htmlID. Callbacks are set for these elements which will be triggered on
|
||||||
|
// a pagination interaction by the user or if the status of the element has
|
||||||
|
// been altered. (Like the switche has ben turned on or off).
|
||||||
|
interactionElements = new Array();
|
||||||
|
let expertModeInteraction = new InteractionElement("display-options-form-expert_mode");
|
||||||
|
expertModeInteraction.setCallback("on",
|
||||||
|
results.jsList.expertModeOn,
|
||||||
|
results.jsList,
|
||||||
|
["query-display"])
|
||||||
|
expertModeInteraction.setCallback("off",
|
||||||
|
results.jsList.expertModeOff,
|
||||||
|
results.jsList,
|
||||||
|
["query-display"])
|
||||||
|
|
||||||
|
let activateInspectInteraction = new InteractionElement("inspect",
|
||||||
|
false);
|
||||||
|
activateInspectInteraction.setCallback("noCheck",
|
||||||
|
results.jsList.activateInspect,
|
||||||
|
results.jsList);
|
||||||
|
|
||||||
|
let changeContextInteraction = new InteractionElement("display-options-form-results_per_page",
|
||||||
|
false);
|
||||||
|
changeContextInteraction.setCallback("noCheck",
|
||||||
|
results.jsList.changeContext,
|
||||||
|
results.jsList)
|
||||||
|
interactionElements.push(expertModeInteraction, activateInspectInteraction, changeContextInteraction);
|
||||||
|
|
||||||
|
// checks if a change for every interactionElement happens and executes
|
||||||
|
// the callbacks accordingly
|
||||||
|
InteractionElement.onChangeExecute(interactionElements);
|
||||||
|
|
||||||
|
// eventListener if pagination is used to apply new context size to new page
|
||||||
|
// and also activate inspect match if progress is 100
|
||||||
|
// also adds more interaction buttons like add to sub results
|
||||||
|
for (let element of paginationElements) {
|
||||||
|
element.addEventListener("click", (event) => {
|
||||||
|
results.jsList.pageChangeEventInteractionHandler(interactionElements);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// render results in table imported parameter is true
|
||||||
|
queryRenderResults(result_json, true)
|
||||||
|
|
||||||
|
// live update of hits per page if hits per page value is changed
|
||||||
|
let changeHitsPerPageBind = results.jsList.changeHitsPerPage.bind(results.jsList);
|
||||||
|
hitsPerPageInputElement.onchange = changeHitsPerPageBind;
|
||||||
|
|
||||||
|
// live update of lr context per item if context value is changed
|
||||||
|
contextPerItemElement.onchange = results.jsList.changeContext;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
@ -36,6 +36,7 @@
|
|||||||
<ul class="pagination"></ul>
|
<ul class="pagination"></ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-action right-align">
|
<div class="card-action right-align">
|
||||||
|
<a class="waves-effect waves-light btn" href="{{ url_for('services.results') }}">Show Imported Results<i class="material-icons right">folder</i></a>
|
||||||
<a class="waves-effect waves-light btn" href="{{ url_for('corpora.add_corpus') }}">New corpus<i class="material-icons right">add</i></a>
|
<a class="waves-effect waves-light btn" href="{{ url_for('corpora.add_corpus') }}">New corpus<i class="material-icons right">add</i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
39
web/app/templates/services/import_results.html.j2
Normal file
39
web/app/templates/services/import_results.html.j2
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{% extends "nopaque.html.j2" %}
|
||||||
|
|
||||||
|
{% block page_content %}
|
||||||
|
<div class="col s12 m4">
|
||||||
|
<p>Fill out the following form to upload and view Results and Sub Results
|
||||||
|
exported from the Corpus analsis Tool.</p>
|
||||||
|
<a class="waves-effect waves-light btn" href="{{ url_for('main.dashboard') }}"><i class="material-icons left">arrow_back</i>Back to dashboard</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col s12 m8">
|
||||||
|
<form class="nopaque-submit-form" data-progress-modal="progress-modal">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
{{ import_results_form.hidden_tag() }}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12">
|
||||||
|
{{ M.render_field(import_results_form.file, accept='.json', placeholder='Choose your .json file') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-action right-align">
|
||||||
|
{{ M.render_field(import_results_form.submit, material_icon='send') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="progress-modal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h4><i class="material-icons prefix">file_upload</i> Uploading file...</h4>
|
||||||
|
<div class="progress">
|
||||||
|
<div class="determinate" style="width: 0%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<a href="#!" class="modal-close waves-effect waves-light btn red abort-request">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
52
web/app/templates/services/results.html.j2
Normal file
52
web/app/templates/services/results.html.j2
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{% extends "nopaque.html.j2" %}
|
||||||
|
|
||||||
|
{% block page_content %}
|
||||||
|
|
||||||
|
<div class="col s12">
|
||||||
|
<p>This is an overview of all your imported results.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col s12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content" id="results">
|
||||||
|
<div class="input-field">
|
||||||
|
<i class="material-icons prefix">search</i>
|
||||||
|
<input id="search-results" class="search" type="search"></input>
|
||||||
|
<label for="search-results">Search results</label>
|
||||||
|
</div>
|
||||||
|
<ul class="pagination paginationTop"></ul>
|
||||||
|
{{ table }}
|
||||||
|
<ul class="pagination paginationBottom"></ul>
|
||||||
|
<ul class="pagination"></ul>
|
||||||
|
</div>
|
||||||
|
<div class="card-action right-align">
|
||||||
|
<a class="waves-effect waves-light btn" href="{{ url_for('services.import_results') }}">Import Results<i class="material-icons right">file_upload</i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var options = {page: 10,
|
||||||
|
pagination: [
|
||||||
|
{
|
||||||
|
name: "paginationTop",
|
||||||
|
paginationClass: "paginationTop",
|
||||||
|
innerWindow: 8,
|
||||||
|
outerWindow: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
paginationClass: "paginationBottom",
|
||||||
|
innerWindow: 8,
|
||||||
|
outerWindow: 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
valueNames: ['query',
|
||||||
|
'match-count',
|
||||||
|
'corpus-name',
|
||||||
|
'corpus-creation-date',
|
||||||
|
'corpus-analysis-date',
|
||||||
|
'corpus-type']
|
||||||
|
};
|
||||||
|
var resultsList = new List('results', options);
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
28
web/migrations/versions/0d7aed934679_.py
Normal file
28
web/migrations/versions/0d7aed934679_.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 0d7aed934679
|
||||||
|
Revises: b15366b25bea
|
||||||
|
Create Date: 2020-06-30 13:57:48.782173
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '0d7aed934679'
|
||||||
|
down_revision = 'b15366b25bea'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('result_files', sa.Column('corpus_metadata', sa.JSON(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('result_files', 'corpus_metadata')
|
||||||
|
# ### end Alembic commands ###
|
28
web/migrations/versions/318074622d14_.py
Normal file
28
web/migrations/versions/318074622d14_.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 318074622d14
|
||||||
|
Revises: 0d7aed934679
|
||||||
|
Create Date: 2020-06-30 14:00:18.968769
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '318074622d14'
|
||||||
|
down_revision = '0d7aed934679'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('result_files', 'corpus_metadata')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('result_files', sa.Column('corpus_metadata', postgresql.JSON(astext_type=sa.Text()), autoincrement=False, nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
28
web/migrations/versions/389bcf564726_.py
Normal file
28
web/migrations/versions/389bcf564726_.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 389bcf564726
|
||||||
|
Revises: 318074622d14
|
||||||
|
Create Date: 2020-06-30 14:03:33.384379
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '389bcf564726'
|
||||||
|
down_revision = '318074622d14'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('result_files', sa.Column('corpus_metadata', sa.JSON(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('result_files', 'corpus_metadata')
|
||||||
|
# ### end Alembic commands ###
|
42
web/migrations/versions/b15366b25bea_.py
Normal file
42
web/migrations/versions/b15366b25bea_.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: b15366b25bea
|
||||||
|
Revises: 4886241e0f5d
|
||||||
|
Create Date: 2020-06-29 13:41:14.394680
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'b15366b25bea'
|
||||||
|
down_revision = '4886241e0f5d'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('results',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('result_files',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('result_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('filename', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('dir', sa.String(length=255), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['result_id'], ['results.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('result_files')
|
||||||
|
op.drop_table('results')
|
||||||
|
# ### end Alembic commands ###
|
30
web/migrations/versions/e256f5cac75d_.py
Normal file
30
web/migrations/versions/e256f5cac75d_.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: e256f5cac75d
|
||||||
|
Revises: 389bcf564726
|
||||||
|
Create Date: 2020-07-01 07:45:24.637861
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'e256f5cac75d'
|
||||||
|
down_revision = '389bcf564726'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('result_files', 'corpus_metadata')
|
||||||
|
op.add_column('results', sa.Column('corpus_metadata', sa.JSON(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('results', 'corpus_metadata')
|
||||||
|
op.add_column('result_files', sa.Column('corpus_metadata', postgresql.JSON(astext_type=sa.Text()), autoincrement=False, nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
@ -2,11 +2,11 @@ import eventlet
|
|||||||
eventlet.monkey_patch() # noqa
|
eventlet.monkey_patch() # noqa
|
||||||
from app import create_app, db, socketio
|
from app import create_app, db, socketio
|
||||||
from app.models import (Corpus, CorpusFile, Job, JobInput, JobResult,
|
from app.models import (Corpus, CorpusFile, Job, JobInput, JobResult,
|
||||||
NotificationData, NotificationEmailData, Role, User)
|
NotificationData, NotificationEmailData, Result,
|
||||||
|
ResultFile, Role, User)
|
||||||
from flask_migrate import Migrate, upgrade
|
from flask_migrate import Migrate, upgrade
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
app = create_app(os.getenv('FLASK_CONFIG') or 'default')
|
app = create_app(os.getenv('FLASK_CONFIG') or 'default')
|
||||||
migrate = Migrate(app, db)
|
migrate = Migrate(app, db)
|
||||||
|
|
||||||
@ -21,6 +21,8 @@ def make_shell_context():
|
|||||||
'JobResult': JobResult,
|
'JobResult': JobResult,
|
||||||
'NotificationData': NotificationData,
|
'NotificationData': NotificationData,
|
||||||
'NotificationEmailData': NotificationEmailData,
|
'NotificationEmailData': NotificationEmailData,
|
||||||
|
'Result': Result,
|
||||||
|
'ResultFile': ResultFile,
|
||||||
'Role': Role,
|
'Role': Role,
|
||||||
'User': User}
|
'User': User}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user