mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2024-11-14 16:55:42 +00:00
Use enums where appropriate. This commit includes new migrations that are NOT compatible with older nopaque instances
This commit is contained in:
parent
fe938c0ca2
commit
df6ab3991c
@ -1,13 +1,16 @@
|
|||||||
from wtforms import BooleanField, SelectField
|
from app.models import Role
|
||||||
from ..models import Role
|
from flask_wtf import FlaskForm
|
||||||
from ..settings.forms import EditGeneralSettingsForm
|
from wtforms import BooleanField, SelectField, SubmitField
|
||||||
|
|
||||||
|
|
||||||
class EditGeneralSettingsAdminForm(EditGeneralSettingsForm):
|
class AdminEditUserForm(FlaskForm):
|
||||||
confirmed = BooleanField('Confirmed')
|
confirmed = BooleanField('Confirmed')
|
||||||
role = SelectField('Role', coerce=int)
|
role = SelectField('Role')
|
||||||
|
submit = SubmitField('Submit')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.role.choices = [(role.id, role.name)
|
self.role.choices = [
|
||||||
for role in Role.query.order_by(Role.name).all()]
|
(role.hashid, role.name)
|
||||||
|
for role in Role.query.order_by(Role.name).all()
|
||||||
|
]
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
|
from app import db, hashids
|
||||||
|
from app.decorators import admin_required
|
||||||
|
from app.models import JobStatusMailNotificationLevel, Role, User
|
||||||
|
from app.settings import tasks as settings_tasks
|
||||||
|
from app.settings.forms import (
|
||||||
|
EditGeneralSettingsForm,
|
||||||
|
EditInterfaceSettingsForm,
|
||||||
|
EditNotificationSettingsForm
|
||||||
|
)
|
||||||
from flask import flash, redirect, render_template, url_for
|
from flask import flash, redirect, render_template, url_for
|
||||||
from flask_login import login_required
|
from flask_login import login_required
|
||||||
from . import bp
|
from . import bp
|
||||||
from .forms import EditGeneralSettingsAdminForm
|
from .forms import AdminEditUserForm
|
||||||
from .. import db
|
|
||||||
from ..decorators import admin_required
|
|
||||||
from ..models import Role, User
|
|
||||||
from ..settings import tasks as settings_tasks
|
|
||||||
|
|
||||||
|
|
||||||
@bp.before_request
|
@bp.before_request
|
||||||
@ -26,10 +31,15 @@ def index():
|
|||||||
|
|
||||||
@bp.route('/users')
|
@bp.route('/users')
|
||||||
def users():
|
def users():
|
||||||
dict_users = {user.id: user.to_dict(backrefs=True, relationships=False)
|
dict_users = {
|
||||||
for user in User.query.all()}
|
user.id: user.to_dict(backrefs=True, relationships=False)
|
||||||
|
for user in User.query.all()
|
||||||
|
}
|
||||||
return render_template(
|
return render_template(
|
||||||
'admin/users.html.j2', title='Users', dict_users=dict_users)
|
'admin/users.html.j2',
|
||||||
|
dict_users=dict_users,
|
||||||
|
title='Users'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/users/<hashid:user_id>')
|
@bp.route('/users/<hashid:user_id>')
|
||||||
@ -41,27 +51,76 @@ def user(user_id):
|
|||||||
@bp.route('/users/<hashid:user_id>/delete')
|
@bp.route('/users/<hashid:user_id>/delete')
|
||||||
def delete_user(user_id):
|
def delete_user(user_id):
|
||||||
settings_tasks.delete_user(user_id)
|
settings_tasks.delete_user(user_id)
|
||||||
flash('User has been marked for deletion!')
|
flash('User has been marked for deletion')
|
||||||
return redirect(url_for('.users'))
|
return redirect(url_for('.users'))
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/users/<hashid:user_id>/edit', methods=['GET', 'POST']) # noqa
|
@bp.route('/users/<hashid:user_id>/edit', methods=['GET', 'POST'])
|
||||||
def edit_user(user_id):
|
def edit_user(user_id):
|
||||||
user = User.query.get_or_404(user_id)
|
user = User.query.get_or_404(user_id)
|
||||||
form = EditGeneralSettingsAdminForm(user)
|
admin_edit_user_form = AdminEditUserForm(
|
||||||
if form.validate_on_submit():
|
prefix='admin_edit_user_form'
|
||||||
user.setting_dark_mode = form.dark_mode.data
|
)
|
||||||
user.email = form.email.data
|
edit_general_settings_form = EditGeneralSettingsForm(
|
||||||
user.username = form.username.data
|
user,
|
||||||
user.confirmed = form.confirmed.data
|
prefix='edit_general_settings_form'
|
||||||
user.role = Role.query.get(form.role.data)
|
)
|
||||||
db.session.commit()
|
edit_interface_settings_form = EditInterfaceSettingsForm(
|
||||||
flash('Settings have been updated.')
|
prefix='edit_interface_settings_form'
|
||||||
|
)
|
||||||
|
edit_notification_settings_form = EditNotificationSettingsForm(
|
||||||
|
prefix='edit_notification_settings_form'
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
admin_edit_user_form.submit.data
|
||||||
|
and admin_edit_user_form.validate()
|
||||||
|
):
|
||||||
|
user.confirmed = admin_edit_user_form.confirmed.data
|
||||||
|
role_id = hashids.decode(admin_edit_user_form.role.data)
|
||||||
|
user.role = Role.query.get(role_id)
|
||||||
|
flash('Your changes have been saved')
|
||||||
return redirect(url_for('.edit_user', user_id=user.id))
|
return redirect(url_for('.edit_user', user_id=user.id))
|
||||||
form.confirmed.data = user.confirmed
|
if (
|
||||||
form.dark_mode.data = user.setting_dark_mode
|
edit_general_settings_form.submit.data
|
||||||
form.email.data = user.email
|
and edit_general_settings_form.validate()
|
||||||
form.role.data = user.role_id
|
):
|
||||||
form.username.data = user.username
|
user.email = edit_general_settings_form.email.data
|
||||||
|
user.username = edit_general_settings_form.username.data
|
||||||
|
db.session.commit()
|
||||||
|
flash('Your changes have been saved')
|
||||||
|
return redirect(url_for('.edit_user', user_id=user.id))
|
||||||
|
if (
|
||||||
|
edit_interface_settings_form.submit.data
|
||||||
|
and edit_interface_settings_form.validate()
|
||||||
|
):
|
||||||
|
user.setting_dark_mode = edit_interface_settings_form.dark_mode.data
|
||||||
|
db.session.commit()
|
||||||
|
flash('Your changes have been saved')
|
||||||
|
return redirect(url_for('.edit_user', user_id=user.id))
|
||||||
|
if (
|
||||||
|
edit_notification_settings_form.submit.data
|
||||||
|
and edit_notification_settings_form.validate()
|
||||||
|
):
|
||||||
|
user.setting_job_status_mail_notification_level = \
|
||||||
|
JobStatusMailNotificationLevel[
|
||||||
|
edit_notification_settings_form.job_status_mail_notification_level.data # noqa
|
||||||
|
]
|
||||||
|
db.session.commit()
|
||||||
|
flash('Your changes have been saved')
|
||||||
|
return redirect(url_for('.edit_user', user_id=user.id))
|
||||||
|
admin_edit_user_form.confirmed.data = user.confirmed
|
||||||
|
admin_edit_user_form.role.data = user.role.hashid
|
||||||
|
edit_general_settings_form.email.data = user.email
|
||||||
|
edit_general_settings_form.username.data = user.username
|
||||||
|
edit_interface_settings_form.dark_mode.data = user.setting_dark_mode
|
||||||
|
edit_notification_settings_form.job_status_mail_notification_level.data = \
|
||||||
|
user.setting_job_status_mail_notification_level.name
|
||||||
return render_template(
|
return render_template(
|
||||||
'admin/edit_user.html.j2', form=form, title='Edit user', user=user)
|
'admin/edit_user.html.j2',
|
||||||
|
admin_edit_user_form=admin_edit_user_form,
|
||||||
|
edit_general_settings_form=edit_general_settings_form,
|
||||||
|
edit_interface_settings_form=edit_interface_settings_form,
|
||||||
|
edit_notification_settings_form=edit_notification_settings_form,
|
||||||
|
title='Edit user',
|
||||||
|
user=user
|
||||||
|
)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
from app.models import User
|
||||||
from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth
|
from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth
|
||||||
from sqlalchemy import or_
|
from sqlalchemy import or_
|
||||||
from werkzeug.http import HTTP_STATUS_CODES
|
from werkzeug.http import HTTP_STATUS_CODES
|
||||||
from ..models import User
|
|
||||||
|
|
||||||
basic_auth = HTTPBasicAuth()
|
basic_auth = HTTPBasicAuth()
|
||||||
token_auth = HTTPTokenAuth()
|
token_auth = HTTPTokenAuth()
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
from app import db
|
||||||
from flask_restx import Namespace, Resource
|
from flask_restx import Namespace, Resource
|
||||||
from .auth import basic_auth, token_auth
|
from .auth import basic_auth, token_auth
|
||||||
from .. import db
|
|
||||||
|
|
||||||
|
|
||||||
ns = Namespace('tokens', description='Token operations')
|
ns = Namespace('tokens', description='Token operations')
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
from . import USERNAME_REGEX
|
from app.models import User
|
||||||
from ..models import User
|
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import (BooleanField, PasswordField, StringField, SubmitField,
|
from wtforms import (
|
||||||
ValidationError)
|
BooleanField,
|
||||||
|
PasswordField,
|
||||||
|
StringField,
|
||||||
|
SubmitField,
|
||||||
|
ValidationError
|
||||||
|
)
|
||||||
from wtforms.validators import DataRequired, Email, EqualTo, Length, Regexp
|
from wtforms.validators import DataRequired, Email, EqualTo, Length, Regexp
|
||||||
|
from . import USERNAME_REGEX
|
||||||
|
|
||||||
|
|
||||||
class LoginForm(FlaskForm):
|
class LoginForm(FlaskForm):
|
||||||
@ -17,42 +22,54 @@ class RegistrationForm(FlaskForm):
|
|||||||
email = StringField('Email', validators=[DataRequired(), Email()])
|
email = StringField('Email', validators=[DataRequired(), Email()])
|
||||||
username = StringField(
|
username = StringField(
|
||||||
'Username',
|
'Username',
|
||||||
validators=[DataRequired(), Length(1, 64),
|
validators=[
|
||||||
Regexp(USERNAME_REGEX,
|
DataRequired(),
|
||||||
message='Usernames must have only letters, numbers,'
|
Length(1, 64),
|
||||||
' dots or underscores')]
|
Regexp(
|
||||||
|
USERNAME_REGEX,
|
||||||
|
message='Usernames must have only letters, numbers, dots or underscores' # noqa
|
||||||
|
)
|
||||||
|
]
|
||||||
)
|
)
|
||||||
password = PasswordField(
|
password = PasswordField(
|
||||||
'Password',
|
'Password',
|
||||||
validators=[DataRequired(), EqualTo('password_confirmation',
|
validators=[
|
||||||
message='Passwords must match.')]
|
DataRequired(),
|
||||||
|
EqualTo('password_confirmation', message='Passwords must match')
|
||||||
|
]
|
||||||
)
|
)
|
||||||
password_confirmation = PasswordField(
|
password_confirmation = PasswordField(
|
||||||
'Password confirmation',
|
'Password confirmation',
|
||||||
validators=[DataRequired(), EqualTo('password',
|
validators=[
|
||||||
message='Passwords must match.')]
|
DataRequired(),
|
||||||
|
EqualTo('password', message='Passwords must match')
|
||||||
|
]
|
||||||
)
|
)
|
||||||
submit = SubmitField('Register')
|
submit = SubmitField('Register')
|
||||||
|
|
||||||
def validate_email(self, field):
|
def validate_email(self, field):
|
||||||
if User.query.filter_by(email=field.data.lower()).first():
|
if User.query.filter_by(email=field.data.lower()).first():
|
||||||
raise ValidationError('Email already registered.')
|
raise ValidationError('Email already registered')
|
||||||
|
|
||||||
def validate_username(self, field):
|
def validate_username(self, field):
|
||||||
if User.query.filter_by(username=field.data).first():
|
if User.query.filter_by(username=field.data).first():
|
||||||
raise ValidationError('Username already in use.')
|
raise ValidationError('Username already in use')
|
||||||
|
|
||||||
|
|
||||||
class ResetPasswordForm(FlaskForm):
|
class ResetPasswordForm(FlaskForm):
|
||||||
password = PasswordField(
|
password = PasswordField(
|
||||||
'New password',
|
'New password',
|
||||||
validators=[DataRequired(), EqualTo('password_confirmation',
|
validators=[
|
||||||
message='Passwords must match.')]
|
DataRequired(),
|
||||||
|
EqualTo('password_confirmation', message='Passwords must match')
|
||||||
|
]
|
||||||
)
|
)
|
||||||
password_confirmation = PasswordField(
|
password_confirmation = PasswordField(
|
||||||
'Password confirmation',
|
'Password confirmation',
|
||||||
validators=[DataRequired(),
|
validators=[
|
||||||
EqualTo('password', message='Passwords must match.')]
|
DataRequired(),
|
||||||
|
EqualTo('password', message='Passwords must match')
|
||||||
|
]
|
||||||
)
|
)
|
||||||
submit = SubmitField('Reset Password')
|
submit = SubmitField('Reset Password')
|
||||||
|
|
||||||
|
@ -1,15 +1,25 @@
|
|||||||
|
from app import db
|
||||||
|
from app.email import create_message, send
|
||||||
|
from app.models import User
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from flask import (abort, current_app, flash, redirect, render_template,
|
from flask import (
|
||||||
request, url_for)
|
abort,
|
||||||
|
current_app,
|
||||||
|
flash,
|
||||||
|
redirect,
|
||||||
|
render_template,
|
||||||
|
request,
|
||||||
|
url_for
|
||||||
|
)
|
||||||
from flask_login import current_user, login_user, login_required, logout_user
|
from flask_login import current_user, login_user, login_required, logout_user
|
||||||
from sqlalchemy import or_
|
from sqlalchemy import or_
|
||||||
from . import bp
|
from . import bp
|
||||||
from .forms import (LoginForm, ResetPasswordForm, ResetPasswordRequestForm,
|
from .forms import (
|
||||||
RegistrationForm)
|
LoginForm,
|
||||||
from .. import db
|
ResetPasswordForm,
|
||||||
from ..email import create_message, send
|
ResetPasswordRequestForm,
|
||||||
from ..models import User
|
RegistrationForm
|
||||||
import os
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.before_app_request
|
@bp.before_app_request
|
||||||
@ -21,10 +31,12 @@ def before_request():
|
|||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
current_user.last_seen = datetime.utcnow()
|
current_user.last_seen = datetime.utcnow()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
if (not current_user.confirmed
|
if (
|
||||||
and request.endpoint
|
not current_user.confirmed
|
||||||
and request.blueprint != 'auth'
|
and request.endpoint
|
||||||
and request.endpoint != 'static'):
|
and request.blueprint != 'auth'
|
||||||
|
and request.endpoint != 'static'
|
||||||
|
):
|
||||||
return redirect(url_for('auth.unconfirmed'))
|
return redirect(url_for('auth.unconfirmed'))
|
||||||
|
|
||||||
|
|
||||||
@ -34,15 +46,19 @@ def login():
|
|||||||
return redirect(url_for('main.dashboard'))
|
return redirect(url_for('main.dashboard'))
|
||||||
form = LoginForm(prefix='login-form')
|
form = LoginForm(prefix='login-form')
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
user = User.query.filter(or_(User.username == form.user.data,
|
user = User.query.filter(
|
||||||
User.email == form.user.data.lower())).first()
|
or_(
|
||||||
|
User.username == form.user.data,
|
||||||
|
User.email == form.user.data.lower()
|
||||||
|
)
|
||||||
|
).first()
|
||||||
if user and user.verify_password(form.password.data):
|
if user and user.verify_password(form.password.data):
|
||||||
login_user(user, form.remember_me.data)
|
login_user(user, form.remember_me.data)
|
||||||
next = request.args.get('next')
|
next = request.args.get('next')
|
||||||
if next is None or not next.startswith('/'):
|
if next is None or not next.startswith('/'):
|
||||||
next = url_for('main.dashboard')
|
next = url_for('main.dashboard')
|
||||||
return redirect(next)
|
return redirect(next)
|
||||||
flash('Invalid email/username or password.')
|
flash('Invalid email/username or password', category='error')
|
||||||
return render_template('auth/login.html.j2', form=form, title='Log in')
|
return render_template('auth/login.html.j2', form=form, title='Log in')
|
||||||
|
|
||||||
|
|
||||||
@ -50,7 +66,7 @@ def login():
|
|||||||
@login_required
|
@login_required
|
||||||
def logout():
|
def logout():
|
||||||
logout_user()
|
logout_user()
|
||||||
flash('You have been logged out.')
|
flash('You have been logged out')
|
||||||
return redirect(url_for('main.index'))
|
return redirect(url_for('main.index'))
|
||||||
|
|
||||||
|
|
||||||
@ -73,19 +89,20 @@ def register():
|
|||||||
except OSError as e:
|
except OSError as e:
|
||||||
current_app.logger.error(e)
|
current_app.logger.error(e)
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
|
flash('Internal Server Error', category='error')
|
||||||
abort(500)
|
abort(500)
|
||||||
else:
|
token = user.generate_confirmation_token()
|
||||||
token = user.generate_confirmation_token()
|
msg = create_message(
|
||||||
msg = create_message(
|
user.email,
|
||||||
user.email,
|
'Confirm Your Account',
|
||||||
'Confirm Your Account',
|
'auth/email/confirm',
|
||||||
'auth/email/confirm',
|
token=token,
|
||||||
token=token,
|
user=user
|
||||||
user=user
|
)
|
||||||
)
|
send(msg)
|
||||||
send(msg)
|
flash('A confirmation email has been sent to you by email')
|
||||||
flash('A confirmation email has been sent to you by email.')
|
db.session.commit()
|
||||||
return redirect(url_for('.login'))
|
return redirect(url_for('.login'))
|
||||||
return render_template(
|
return render_template(
|
||||||
'auth/register.html.j2',
|
'auth/register.html.j2',
|
||||||
form=form,
|
form=form,
|
||||||
@ -100,10 +117,13 @@ def confirm(token):
|
|||||||
return redirect(url_for('main.dashboard'))
|
return redirect(url_for('main.dashboard'))
|
||||||
if current_user.confirm(token):
|
if current_user.confirm(token):
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash('You have confirmed your account. Thanks!')
|
flash('You have confirmed your account')
|
||||||
return redirect(url_for('main.dashboard'))
|
return redirect(url_for('main.dashboard'))
|
||||||
else:
|
else:
|
||||||
flash('The confirmation link is invalid or has expired.')
|
flash(
|
||||||
|
'The confirmation link is invalid or has expired',
|
||||||
|
category='error'
|
||||||
|
)
|
||||||
return redirect(url_for('.unconfirmed'))
|
return redirect(url_for('.unconfirmed'))
|
||||||
|
|
||||||
|
|
||||||
@ -120,10 +140,15 @@ def unconfirmed():
|
|||||||
@login_required
|
@login_required
|
||||||
def resend_confirmation():
|
def resend_confirmation():
|
||||||
token = current_user.generate_confirmation_token()
|
token = current_user.generate_confirmation_token()
|
||||||
msg = create_message(current_user.email, 'Confirm Your Account',
|
msg = create_message(
|
||||||
'auth/email/confirm', token=token, user=current_user)
|
current_user.email,
|
||||||
|
'Confirm Your Account',
|
||||||
|
'auth/email/confirm',
|
||||||
|
token=token,
|
||||||
|
user=current_user
|
||||||
|
)
|
||||||
send(msg)
|
send(msg)
|
||||||
flash('A new confirmation email has been sent to you by email.')
|
flash('A new confirmation email has been sent to you by email')
|
||||||
return redirect(url_for('auth.unconfirmed'))
|
return redirect(url_for('auth.unconfirmed'))
|
||||||
|
|
||||||
|
|
||||||
@ -136,14 +161,23 @@ def reset_password_request():
|
|||||||
user = User.query.filter_by(email=form.email.data.lower()).first()
|
user = User.query.filter_by(email=form.email.data.lower()).first()
|
||||||
if user is not None:
|
if user is not None:
|
||||||
token = user.generate_reset_token()
|
token = user.generate_reset_token()
|
||||||
msg = create_message(user.email, 'Reset Your Password',
|
msg = create_message(
|
||||||
'auth/email/reset_password', token=token,
|
user.email,
|
||||||
user=user)
|
'Reset Your Password',
|
||||||
|
'auth/email/reset_password',
|
||||||
|
token=token,
|
||||||
|
user=user
|
||||||
|
)
|
||||||
send(msg)
|
send(msg)
|
||||||
flash('An email with instructions to reset your password has been sent to you.') # noqa
|
flash(
|
||||||
|
'An email with instructions to reset your password has been sent to you' # noqa
|
||||||
|
)
|
||||||
return redirect(url_for('.login'))
|
return redirect(url_for('.login'))
|
||||||
return render_template('auth/reset_password_request.html.j2', form=form,
|
return render_template(
|
||||||
title='Password Reset')
|
'auth/reset_password_request.html.j2',
|
||||||
|
form=form,
|
||||||
|
title='Password Reset'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/reset/<token>', methods=['GET', 'POST'])
|
@bp.route('/reset/<token>', methods=['GET', 'POST'])
|
||||||
@ -154,9 +188,13 @@ def reset_password(token):
|
|||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
if User.reset_password(token, form.password.data):
|
if User.reset_password(token, form.password.data):
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash('Your password has been updated.')
|
flash('Your password has been updated')
|
||||||
return redirect(url_for('.login'))
|
return redirect(url_for('.login'))
|
||||||
else:
|
else:
|
||||||
return redirect(url_for('main.index'))
|
return redirect(url_for('main.index'))
|
||||||
return render_template('auth/reset_password.html.j2', form=form,
|
return render_template(
|
||||||
title='Password Reset', token=token)
|
'auth/reset_password.html.j2',
|
||||||
|
form=form,
|
||||||
|
title='Password Reset',
|
||||||
|
token=token
|
||||||
|
)
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
|
from app import db
|
||||||
|
from app.decorators import permission_required
|
||||||
|
from app.models import Permission, Role, User
|
||||||
|
from app.settings import tasks as settings_tasks
|
||||||
from flask import flash, redirect, render_template, url_for
|
from flask import flash, redirect, render_template, url_for
|
||||||
from flask_login import login_required
|
from flask_login import login_required
|
||||||
from . import bp
|
from . import bp
|
||||||
from .. import db
|
|
||||||
from ..decorators import permission_required
|
|
||||||
from ..models import Permission, Role, User
|
|
||||||
from ..settings import tasks as settings_tasks
|
|
||||||
|
|
||||||
|
|
||||||
@bp.before_request
|
@bp.before_request
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from app import db, hashids, socketio
|
from app import db, hashids, socketio
|
||||||
from app.decorators import socketio_login_required
|
from app.decorators import socketio_login_required
|
||||||
from app.models import Corpus
|
from app.models import Corpus, CorpusStatus
|
||||||
from flask import session
|
from flask import session
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from flask_socketio import ConnectionRefusedError
|
from flask_socketio import ConnectionRefusedError
|
||||||
@ -65,7 +65,12 @@ def connect(auth):
|
|||||||
if not (corpus.user == current_user or current_user.is_administrator()):
|
if not (corpus.user == current_user or current_user.is_administrator()):
|
||||||
# return {'code': 403, 'msg': 'Forbidden'}
|
# return {'code': 403, 'msg': 'Forbidden'}
|
||||||
raise ConnectionRefusedError('Forbidden')
|
raise ConnectionRefusedError('Forbidden')
|
||||||
if corpus.status not in ['prepared', 'start analysis', 'analysing', 'stop analysis']:
|
if corpus.status not in [
|
||||||
|
CorpusStatus.BUILT,
|
||||||
|
CorpusStatus.STARTING_ANALYSIS_SESSION,
|
||||||
|
CorpusStatus.RUNNING_ANALYSIS_SESSION,
|
||||||
|
CorpusStatus.CANCELING_ANALYSIS_SESSION
|
||||||
|
]:
|
||||||
# return {'code': 424, 'msg': 'Failed Dependency'}
|
# return {'code': 424, 'msg': 'Failed Dependency'}
|
||||||
raise ConnectionRefusedError('Failed Dependency')
|
raise ConnectionRefusedError('Failed Dependency')
|
||||||
if corpus.num_analysis_sessions is None:
|
if corpus.num_analysis_sessions is None:
|
||||||
@ -74,7 +79,7 @@ def connect(auth):
|
|||||||
corpus.num_analysis_sessions = Corpus.num_analysis_sessions + 1
|
corpus.num_analysis_sessions = Corpus.num_analysis_sessions + 1
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
retry_counter = 20
|
retry_counter = 20
|
||||||
while corpus.status != 'analysing':
|
while corpus.status != CorpusStatus.RUNNING_ANALYSIS_SESSION:
|
||||||
if retry_counter == 0:
|
if retry_counter == 0:
|
||||||
corpus.num_analysis_sessions = Corpus.num_analysis_sessions - 1
|
corpus.num_analysis_sessions = Corpus.num_analysis_sessions - 1
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
from wtforms import (FileField, StringField, SubmitField,
|
from wtforms import (
|
||||||
ValidationError, IntegerField)
|
FileField,
|
||||||
|
StringField,
|
||||||
|
SubmitField,
|
||||||
|
ValidationError,
|
||||||
|
IntegerField
|
||||||
|
)
|
||||||
from wtforms.validators import DataRequired, Length
|
from wtforms.validators import DataRequired, Length
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ def add_query_result():
|
|||||||
query_result_file_content.pop('cpos_lookup')
|
query_result_file_content.pop('cpos_lookup')
|
||||||
query_result.query_metadata = query_result_file_content
|
query_result.query_metadata = query_result_file_content
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash('Query result added!', 'result')
|
flash('Query result added', 'result')
|
||||||
return make_response({'redirect_url': url_for('.query_result', query_result_id=query_result.id)}, 201) # noqa
|
return make_response({'redirect_url': url_for('.query_result', query_result_id=query_result.id)}, 201) # noqa
|
||||||
return render_template('corpora/query_results/add_query_result.html.j2',
|
return render_template('corpora/query_results/add_query_result.html.j2',
|
||||||
form=form, title='Add query result')
|
form=form, title='Add query result')
|
||||||
@ -117,7 +117,7 @@ def delete_query_result(query_result_id):
|
|||||||
if not (query_result.user == current_user
|
if not (query_result.user == current_user
|
||||||
or current_user.is_administrator()):
|
or current_user.is_administrator()):
|
||||||
abort(403)
|
abort(403)
|
||||||
flash(f'Query result "{query_result}" marked for deletion!', 'result')
|
flash(f'Query result "{query_result}" marked for deletion', 'result')
|
||||||
tasks.delete_query_result(query_result_id)
|
tasks.delete_query_result(query_result_id)
|
||||||
return redirect(url_for('services.service', service="corpus_analysis"))
|
return redirect(url_for('services.service', service="corpus_analysis"))
|
||||||
|
|
||||||
|
13
app/corpora/query_results_tasks.py
Normal file
13
app/corpora/query_results_tasks.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
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(f'QueryResult {query_result_id} not found')
|
||||||
|
query_result.delete()
|
||||||
|
db.session.commit()
|
@ -1,19 +1,31 @@
|
|||||||
from flask import (abort, current_app, flash, make_response, redirect,
|
from app import db
|
||||||
render_template, url_for, send_from_directory)
|
from app.models import Corpus, CorpusFile, CorpusStatus
|
||||||
|
from flask import (
|
||||||
|
abort,
|
||||||
|
current_app,
|
||||||
|
flash,
|
||||||
|
make_response,
|
||||||
|
redirect,
|
||||||
|
render_template,
|
||||||
|
url_for,
|
||||||
|
send_from_directory
|
||||||
|
)
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
from zipfile import ZipFile
|
||||||
from . import bp
|
from . import bp
|
||||||
from . import tasks
|
from . import tasks
|
||||||
from .forms import (AddCorpusFileForm, AddCorpusForm, EditCorpusFileForm,
|
from .forms import (
|
||||||
ImportCorpusForm)
|
AddCorpusFileForm,
|
||||||
from .. import db
|
AddCorpusForm,
|
||||||
from ..models import Corpus, CorpusFile
|
EditCorpusFileForm,
|
||||||
|
ImportCorpusForm
|
||||||
|
)
|
||||||
|
from .import_corpus import check_zip_contents
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import glob
|
import glob
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
from zipfile import ZipFile
|
|
||||||
from .import_corpus import check_zip_contents
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/add', methods=['GET', 'POST'])
|
@bp.route('/add', methods=['GET', 'POST'])
|
||||||
@ -34,10 +46,10 @@ def add_corpus():
|
|||||||
except OSError as e:
|
except OSError as e:
|
||||||
current_app.logger.error(e)
|
current_app.logger.error(e)
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
flash('Internal Server Error', 'error')
|
flash('Internal Server Error', category='error')
|
||||||
abort(500)
|
abort(500)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash(f'Corpus "{corpus.title}" added', 'corpus')
|
flash(f'Corpus "{corpus.title}" added', category='corpus')
|
||||||
return redirect(url_for('.corpus', corpus_id=corpus.id))
|
return redirect(url_for('.corpus', corpus_id=corpus.id))
|
||||||
return render_template(
|
return render_template(
|
||||||
'corpora/add_corpus.html.j2',
|
'corpora/add_corpus.html.j2',
|
||||||
@ -46,6 +58,21 @@ def add_corpus():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/<hashid:corpus_id>/export')
|
||||||
|
@login_required
|
||||||
|
def export_corpus(corpus_id):
|
||||||
|
abort(503)
|
||||||
|
corpus = Corpus.query.get_or_404(corpus_id)
|
||||||
|
if not (corpus.user == current_user or current_user.is_administrator()):
|
||||||
|
abort(403)
|
||||||
|
return send_from_directory(
|
||||||
|
as_attachment=True,
|
||||||
|
directory=os.path.join(corpus.user.path, 'corpora'),
|
||||||
|
filename=corpus.archive_file,
|
||||||
|
mimetype='zip'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/import', methods=['GET', 'POST'])
|
@bp.route('/import', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def import_corpus():
|
def import_corpus():
|
||||||
@ -65,11 +92,10 @@ def import_corpus():
|
|||||||
try:
|
try:
|
||||||
os.makedirs(corpus.path)
|
os.makedirs(corpus.path)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
current_app.logger.error(f'Could not import corpus: {e}')
|
current_app.logger.error(e)
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
flash('Internal Server Error', 'error')
|
flash('Internal Server Error', category='error')
|
||||||
return make_response(
|
return make_response({'redirect_url': url_for('.import_corpus')}, 500) # noqa
|
||||||
{'redirect_url': url_for('.import_corpus')}, 500)
|
|
||||||
# Upload zip
|
# Upload zip
|
||||||
archive_file = os.path.join(corpus.path, form.file.data.filename)
|
archive_file = os.path.join(corpus.path, form.file.data.filename)
|
||||||
form.file.data.save(archive_file)
|
form.file.data.save(archive_file)
|
||||||
@ -102,20 +128,25 @@ def import_corpus():
|
|||||||
)
|
)
|
||||||
db.session.add(corpus_file)
|
db.session.add(corpus_file)
|
||||||
# finish import and redirect to imported corpus
|
# finish import and redirect to imported corpus
|
||||||
corpus.status = 'prepared'
|
corpus.status = CorpusStatus.BUILT
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
os.remove(archive_file)
|
os.remove(archive_file)
|
||||||
flash(f'Corpus "{corpus.title}" imported!', 'corpus')
|
flash(f'Corpus "{corpus.title}" imported', 'corpus')
|
||||||
return make_response(
|
return make_response(
|
||||||
{'redirect_url': url_for('.corpus', corpus_id=corpus.id)}, 201)
|
{'redirect_url': url_for('.corpus', corpus_id=corpus.id)}, 201)
|
||||||
else:
|
else:
|
||||||
# If imported zip is not valid delete corpus and give feedback
|
# If imported zip is not valid delete corpus and give feedback
|
||||||
flash('Can not import corpus "{}" not imported: Invalid archive file!', 'error') # noqa
|
flash(
|
||||||
|
f'Can\'t import corpus "{corpus.title}": Invalid archive file',
|
||||||
|
category='error'
|
||||||
|
)
|
||||||
tasks.delete_corpus(corpus.id)
|
tasks.delete_corpus(corpus.id)
|
||||||
return make_response(
|
return make_response({'redirect_url': url_for('.import_corpus')}, 201) # noqa
|
||||||
{'redirect_url': url_for('.import_corpus')}, 201)
|
return render_template(
|
||||||
return render_template('corpora/import_corpus.html.j2', form=form,
|
'corpora/import_corpus.html.j2',
|
||||||
title='Import Corpus')
|
form=form,
|
||||||
|
title='Import Corpus'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:corpus_id>')
|
@bp.route('/<hashid:corpus_id>')
|
||||||
@ -124,9 +155,11 @@ def corpus(corpus_id):
|
|||||||
corpus = Corpus.query.get_or_404(corpus_id)
|
corpus = Corpus.query.get_or_404(corpus_id)
|
||||||
if not (corpus.user == current_user or current_user.is_administrator()):
|
if not (corpus.user == current_user or current_user.is_administrator()):
|
||||||
abort(403)
|
abort(403)
|
||||||
corpus_files = [corpus_file.to_dict() for corpus_file in corpus.files]
|
return render_template(
|
||||||
return render_template('corpora/corpus.html.j2', corpus=corpus,
|
'corpora/corpus.html.j2',
|
||||||
corpus_files=corpus_files, title='Corpus')
|
corpus=corpus,
|
||||||
|
title='Corpus'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:corpus_id>/analyse')
|
@bp.route('/<hashid:corpus_id>/analyse')
|
||||||
@ -140,28 +173,13 @@ def analyse_corpus(corpus_id):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:corpus_id>/download')
|
|
||||||
@login_required
|
|
||||||
def download_corpus(corpus_id):
|
|
||||||
abort(503)
|
|
||||||
corpus = Corpus.query.get_or_404(corpus_id)
|
|
||||||
if not (corpus.user == current_user or current_user.is_administrator()):
|
|
||||||
abort(403)
|
|
||||||
return send_from_directory(
|
|
||||||
as_attachment=True,
|
|
||||||
directory=os.path.join(corpus.user.path, 'corpora'),
|
|
||||||
filename=corpus.archive_file,
|
|
||||||
mimetype='zip'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:corpus_id>/delete')
|
@bp.route('/<hashid:corpus_id>/delete')
|
||||||
@login_required
|
@login_required
|
||||||
def delete_corpus(corpus_id):
|
def delete_corpus(corpus_id):
|
||||||
corpus = Corpus.query.get_or_404(corpus_id)
|
corpus = Corpus.query.get_or_404(corpus_id)
|
||||||
if not (corpus.user == current_user or current_user.is_administrator()):
|
if not (corpus.user == current_user or current_user.is_administrator()):
|
||||||
abort(403)
|
abort(403)
|
||||||
flash(f'Corpus "{corpus.title}" marked for deletion!', 'corpus')
|
flash(f'Corpus "{corpus.title}" marked for deletion', 'corpus')
|
||||||
tasks.delete_corpus(corpus_id)
|
tasks.delete_corpus(corpus_id)
|
||||||
return redirect(url_for('main.dashboard'))
|
return redirect(url_for('main.dashboard'))
|
||||||
|
|
||||||
@ -203,11 +221,11 @@ def add_corpus_file(corpus_id):
|
|||||||
except OSError as e:
|
except OSError as e:
|
||||||
current_app.logger.error(e)
|
current_app.logger.error(e)
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
flash('Internal Server Error', 'error')
|
flash('Internal Server Error', category='error')
|
||||||
return make_response({'redirect_url': url_for('.add_corpus_file', corpus_id=corpus.id)}, 500) # noqa
|
return make_response({'redirect_url': url_for('.add_corpus_file', corpus_id=corpus.id)}, 500) # noqa
|
||||||
corpus.status = 'unprepared'
|
corpus.status = CorpusStatus.UNPREPARED
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash(f'Corpus file "{corpus_file.title}" added!', 'corpus')
|
flash(f'Corpus file "{corpus_file.filename}" added', category='corpus')
|
||||||
return make_response({'redirect_url': url_for('.corpus', corpus_id=corpus.id)}, 201) # noqa
|
return make_response({'redirect_url': url_for('.corpus', corpus_id=corpus.id)}, 201) # noqa
|
||||||
return render_template(
|
return render_template(
|
||||||
'corpora/add_corpus_file.html.j2',
|
'corpora/add_corpus_file.html.j2',
|
||||||
@ -220,14 +238,19 @@ def add_corpus_file(corpus_id):
|
|||||||
@bp.route('/<hashid:corpus_id>/files/<hashid:corpus_file_id>/delete')
|
@bp.route('/<hashid:corpus_id>/files/<hashid:corpus_file_id>/delete')
|
||||||
@login_required
|
@login_required
|
||||||
def delete_corpus_file(corpus_id, corpus_file_id):
|
def delete_corpus_file(corpus_id, corpus_file_id):
|
||||||
corpus_file = CorpusFile.query.get_or_404(corpus_file_id)
|
corpus_file = CorpusFile.query.filter(
|
||||||
if not corpus_file.corpus_id == corpus_id:
|
CorpusFile.corpus_id == corpus_id,
|
||||||
abort(404)
|
CorpusFile.id == corpus_file_id
|
||||||
if not (corpus_file.corpus.user == current_user
|
).first_or_404()
|
||||||
or current_user.is_administrator()):
|
if not (
|
||||||
|
corpus_file.corpus.user == current_user
|
||||||
|
or current_user.is_administrator()
|
||||||
|
):
|
||||||
abort(403)
|
abort(403)
|
||||||
flash(
|
flash(
|
||||||
f'Corpus file "{corpus_file.filename}" marked for deletion!', 'corpus')
|
f'Corpus file "{corpus_file.filename}" marked for deletion',
|
||||||
|
category='corpus'
|
||||||
|
)
|
||||||
tasks.delete_corpus_file(corpus_file_id)
|
tasks.delete_corpus_file(corpus_file_id)
|
||||||
return redirect(url_for('.corpus', corpus_id=corpus_id))
|
return redirect(url_for('.corpus', corpus_id=corpus_id))
|
||||||
|
|
||||||
@ -235,26 +258,34 @@ def delete_corpus_file(corpus_id, corpus_file_id):
|
|||||||
@bp.route('/<hashid:corpus_id>/files/<hashid:corpus_file_id>/download')
|
@bp.route('/<hashid:corpus_id>/files/<hashid:corpus_file_id>/download')
|
||||||
@login_required
|
@login_required
|
||||||
def download_corpus_file(corpus_id, corpus_file_id):
|
def download_corpus_file(corpus_id, corpus_file_id):
|
||||||
corpus_file = CorpusFile.query.get_or_404(corpus_file_id)
|
corpus_file = CorpusFile.query.filter(
|
||||||
if not corpus_file.corpus_id == corpus_id:
|
CorpusFile.corpus_id == corpus_id,
|
||||||
abort(404)
|
CorpusFile.id == corpus_file_id
|
||||||
if not (corpus_file.corpus.user == current_user
|
).first_or_404()
|
||||||
or current_user.is_administrator()):
|
if not (
|
||||||
|
corpus_file.corpus.user == current_user
|
||||||
|
or current_user.is_administrator()
|
||||||
|
):
|
||||||
abort(403)
|
abort(403)
|
||||||
return send_from_directory(as_attachment=True,
|
return send_from_directory(
|
||||||
directory=os.path.dirname(corpus_file.path),
|
as_attachment=True,
|
||||||
filename=corpus_file.filename)
|
directory=os.path.dirname(corpus_file.path),
|
||||||
|
filename=corpus_file.filename
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:corpus_id>/files/<hashid:corpus_file_id>', methods=['GET', 'POST'])
|
@bp.route('/<hashid:corpus_id>/files/<hashid:corpus_file_id>', methods=['GET', 'POST']) # noqa
|
||||||
@login_required
|
@login_required
|
||||||
def corpus_file(corpus_id, corpus_file_id):
|
def corpus_file(corpus_id, corpus_file_id):
|
||||||
corpus = Corpus.query.get_or_404(corpus_id)
|
corpus_file = CorpusFile.query.filter(
|
||||||
if not (corpus.user == current_user or current_user.is_administrator()):
|
CorpusFile.corpus_id == corpus_id,
|
||||||
|
CorpusFile.id == corpus_file_id
|
||||||
|
).first_or_404()
|
||||||
|
if not (
|
||||||
|
corpus_file.corpus.user == current_user
|
||||||
|
or current_user.is_administrator()
|
||||||
|
):
|
||||||
abort(403)
|
abort(403)
|
||||||
corpus_file = CorpusFile.query.get_or_404(corpus_file_id)
|
|
||||||
if corpus_file.corpus != corpus:
|
|
||||||
abort(404)
|
|
||||||
form = EditCorpusFileForm(prefix='edit-corpus-file-form')
|
form = EditCorpusFileForm(prefix='edit-corpus-file-form')
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
corpus_file.address = form.address.data
|
corpus_file.address = form.address.data
|
||||||
@ -269,9 +300,9 @@ def corpus_file(corpus_id, corpus_file_id):
|
|||||||
corpus_file.publishing_year = form.publishing_year.data
|
corpus_file.publishing_year = form.publishing_year.data
|
||||||
corpus_file.school = form.school.data
|
corpus_file.school = form.school.data
|
||||||
corpus_file.title = form.title.data
|
corpus_file.title = form.title.data
|
||||||
corpus.status = 'unprepared'
|
corpus_file.corpus.status = CorpusStatus.UNPREPARED
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash(f'Corpus file "{corpus_file.filename}" edited!', 'corpus')
|
flash(f'Corpus file "{corpus_file.filename}" edited', category='corpus') # noqa
|
||||||
return redirect(url_for('.corpus', corpus_id=corpus_id))
|
return redirect(url_for('.corpus', corpus_id=corpus_id))
|
||||||
# If no form is submitted or valid, fill out fields with current values
|
# If no form is submitted or valid, fill out fields with current values
|
||||||
form.address.data = corpus_file.address
|
form.address.data = corpus_file.address
|
||||||
@ -286,9 +317,13 @@ def corpus_file(corpus_id, corpus_file_id):
|
|||||||
form.publishing_year.data = corpus_file.publishing_year
|
form.publishing_year.data = corpus_file.publishing_year
|
||||||
form.school.data = corpus_file.school
|
form.school.data = corpus_file.school
|
||||||
form.title.data = corpus_file.title
|
form.title.data = corpus_file.title
|
||||||
return render_template('corpora/corpus_file.html.j2', corpus=corpus,
|
return render_template(
|
||||||
corpus_file=corpus_file, form=form,
|
'corpora/corpus_file.html.j2',
|
||||||
title='Edit corpus file')
|
corpus=corpus,
|
||||||
|
corpus_file=corpus_file,
|
||||||
|
form=form,
|
||||||
|
title='Edit corpus file'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:corpus_id>/build')
|
@bp.route('/<hashid:corpus_id>/build')
|
||||||
@ -299,7 +334,13 @@ def build_corpus(corpus_id):
|
|||||||
abort(403)
|
abort(403)
|
||||||
if corpus.files.all():
|
if corpus.files.all():
|
||||||
tasks.build_corpus(corpus_id)
|
tasks.build_corpus(corpus_id)
|
||||||
flash(f'Corpus "{corpus.title}" marked for building!', 'corpus')
|
flash(
|
||||||
|
f'Corpus "{corpus.title}" marked for building',
|
||||||
|
category='corpus'
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
flash(f'Can\'t build corpus "{corpus.title}": No corpus file(s)!', 'error') # noqa
|
flash(
|
||||||
|
f'Can\'t build corpus "{corpus.title}": No corpus file(s)',
|
||||||
|
category='error'
|
||||||
|
)
|
||||||
return redirect(url_for('.corpus', corpus_id=corpus_id))
|
return redirect(url_for('.corpus', corpus_id=corpus_id))
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from .. import db
|
from app import db
|
||||||
from ..decorators import background
|
from app.decorators import background
|
||||||
from ..models import Corpus, CorpusFile, QueryResult
|
from app.models import Corpus, CorpusFile
|
||||||
|
|
||||||
|
|
||||||
@background
|
@background
|
||||||
@ -32,13 +32,3 @@ def delete_corpus_file(corpus_file_id, *args, **kwargs):
|
|||||||
raise Exception(f'Corpus file {corpus_file_id} not found')
|
raise Exception(f'Corpus file {corpus_file_id} not found')
|
||||||
corpus_file.delete()
|
corpus_file.delete()
|
||||||
db.session.commit()
|
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(f'QueryResult {query_result_id} not found')
|
|
||||||
query_result.delete()
|
|
||||||
db.session.commit()
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
from app.models import Corpus, CorpusStatus
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from ..models import Corpus
|
|
||||||
import docker
|
import docker
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
@ -8,19 +8,19 @@ import shutil
|
|||||||
class CheckCorporaMixin:
|
class CheckCorporaMixin:
|
||||||
def check_corpora(self):
|
def check_corpora(self):
|
||||||
corpora = Corpus.query.all()
|
corpora = Corpus.query.all()
|
||||||
for corpus in (x for x in corpora if x.status == 'submitted'):
|
for corpus in (x for x in corpora if x.status == CorpusStatus.SUBMITTED): # noqa
|
||||||
self.create_build_corpus_service(corpus)
|
self.create_build_corpus_service(corpus)
|
||||||
for corpus in (x for x in corpora if x.status == 'queued' or x.status == 'running'): # noqa
|
for corpus in (x for x in corpora if x.status == CorpusStatus.QUEUED or x.status == CorpusStatus.BUILDING): # noqa
|
||||||
self.checkout_build_corpus_service(corpus)
|
self.checkout_build_corpus_service(corpus)
|
||||||
for corpus in (x for x in corpora if x.status == 'prepared' and x.num_analysis_sessions > 0): # noqa
|
for corpus in (x for x in corpora if x.status == CorpusStatus.BUILT and x.num_analysis_sessions > 0): # noqa
|
||||||
corpus.status = 'start analysis'
|
corpus.status = CorpusStatus.STARTING_ANALYSIS_SESSION
|
||||||
for corpus in (x for x in corpora if x.status == 'analysing' and x.num_analysis_sessions == 0): # noqa
|
for corpus in (x for x in corpora if x.status == CorpusStatus.RUNNING_ANALYSIS_SESSION and x.num_analysis_sessions == 0): # noqa
|
||||||
corpus.status = 'stop analysis'
|
corpus.status = CorpusStatus.CANCELING_ANALYSIS_SESSION
|
||||||
for corpus in (x for x in corpora if x.status == 'analysing'):
|
for corpus in (x for x in corpora if x.status == CorpusStatus.RUNNING_ANALYSIS_SESSION): # noqa
|
||||||
self.checkout_analysing_corpus_container(corpus)
|
self.checkout_analysing_corpus_container(corpus)
|
||||||
for corpus in (x for x in corpora if x.status == 'start analysis'):
|
for corpus in (x for x in corpora if x.status == CorpusStatus.STARTING_ANALYSIS_SESSION): # noqa
|
||||||
self.create_cqpserver_container(corpus)
|
self.create_cqpserver_container(corpus)
|
||||||
for corpus in (x for x in corpora if x.status == 'stop analysis'):
|
for corpus in (x for x in corpora if x.status == CorpusStatus.CANCELING_ANALYSIS_SESSION): # noqa
|
||||||
self.remove_cqpserver_container(corpus)
|
self.remove_cqpserver_container(corpus)
|
||||||
|
|
||||||
def create_build_corpus_service(self, corpus):
|
def create_build_corpus_service(self, corpus):
|
||||||
@ -95,7 +95,7 @@ class CheckCorporaMixin:
|
|||||||
f'due to "docker.errors.APIError": {e}'
|
f'due to "docker.errors.APIError": {e}'
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
corpus.status = 'queued'
|
corpus.status = CorpusStatus.QUEUED
|
||||||
|
|
||||||
def checkout_build_corpus_service(self, corpus):
|
def checkout_build_corpus_service(self, corpus):
|
||||||
service_name = f'build-corpus_{corpus.id}'
|
service_name = f'build-corpus_{corpus.id}'
|
||||||
@ -106,7 +106,7 @@ class CheckCorporaMixin:
|
|||||||
f'Get service "{service_name}" failed '
|
f'Get service "{service_name}" failed '
|
||||||
f'due to "docker.errors.NotFound": {e}'
|
f'due to "docker.errors.NotFound": {e}'
|
||||||
)
|
)
|
||||||
corpus.status = 'failed'
|
corpus.status = CorpusStatus.FAILED
|
||||||
return
|
return
|
||||||
except docker.errors.APIError as e:
|
except docker.errors.APIError as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
@ -117,22 +117,22 @@ class CheckCorporaMixin:
|
|||||||
if not service_tasks:
|
if not service_tasks:
|
||||||
return
|
return
|
||||||
task_state = service_tasks[0].get('Status').get('State')
|
task_state = service_tasks[0].get('Status').get('State')
|
||||||
if corpus.status == 'queued' and task_state != 'pending':
|
if corpus.status == CorpusStatus.QUEUED and task_state != 'pending': # noqa
|
||||||
corpus.status = 'running'
|
corpus.status = CorpusStatus.BUILDING
|
||||||
return
|
return
|
||||||
elif corpus.status == 'running' and task_state == 'complete':
|
elif corpus.status == CorpusStatus.BUILDING and task_state == 'complete': # noqa
|
||||||
corpus.status = 'prepared'
|
corpus.status = CorpusStatus.BUILT
|
||||||
elif corpus.status == 'running' and task_state == 'failed':
|
elif corpus.status == CorpusStatus.BUILDING and task_state == 'failed': # noqa
|
||||||
corpus.status = 'failed'
|
corpus.status = CorpusStatus.FAILED
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
# try:
|
try:
|
||||||
# service.remove()
|
service.remove()
|
||||||
# except docker.errors.APIError as e:
|
except docker.errors.APIError as e:
|
||||||
# current_app.logger.error(
|
current_app.logger.error(
|
||||||
# f'Remove service "{service_name}" failed '
|
f'Remove service "{service_name}" failed '
|
||||||
# f'due to "docker.errors.APIError": {e}'
|
f'due to "docker.errors.APIError": {e}'
|
||||||
# )
|
)
|
||||||
|
|
||||||
def create_cqpserver_container(self, corpus):
|
def create_cqpserver_container(self, corpus):
|
||||||
''' # Docker container settings # '''
|
''' # Docker container settings # '''
|
||||||
@ -203,7 +203,7 @@ class CheckCorporaMixin:
|
|||||||
f'Run container "{name}" failed '
|
f'Run container "{name}" failed '
|
||||||
f'due to "docker.errors.ImageNotFound" error: {e}'
|
f'due to "docker.errors.ImageNotFound" error: {e}'
|
||||||
)
|
)
|
||||||
corpus.status = 'failed'
|
corpus.status = CorpusStatus.FAILED
|
||||||
return
|
return
|
||||||
except docker.errors.APIError as e:
|
except docker.errors.APIError as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
@ -211,7 +211,7 @@ class CheckCorporaMixin:
|
|||||||
f'due to "docker.errors.APIError" error: {e}'
|
f'due to "docker.errors.APIError" error: {e}'
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
corpus.status = 'analysing'
|
corpus.status = CorpusStatus.RUNNING_ANALYSIS_SESSION
|
||||||
|
|
||||||
def checkout_analysing_corpus_container(self, corpus):
|
def checkout_analysing_corpus_container(self, corpus):
|
||||||
container_name = f'cqpserver_{corpus.id}'
|
container_name = f'cqpserver_{corpus.id}'
|
||||||
@ -223,7 +223,7 @@ class CheckCorporaMixin:
|
|||||||
f'due to "docker.errors.NotFound": {e}'
|
f'due to "docker.errors.NotFound": {e}'
|
||||||
)
|
)
|
||||||
corpus.num_analysis_sessions = 0
|
corpus.num_analysis_sessions = 0
|
||||||
corpus.status = 'prepared'
|
corpus.status = CorpusStatus.BUILT
|
||||||
except docker.errors.APIError as e:
|
except docker.errors.APIError as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
f'Get container "{container_name}" failed '
|
f'Get container "{container_name}" failed '
|
||||||
@ -235,7 +235,7 @@ class CheckCorporaMixin:
|
|||||||
try:
|
try:
|
||||||
container = self.docker.containers.get(container_name)
|
container = self.docker.containers.get(container_name)
|
||||||
except docker.errors.NotFound:
|
except docker.errors.NotFound:
|
||||||
corpus.status = 'prepared'
|
corpus.status = CorpusStatus.BUILT
|
||||||
return
|
return
|
||||||
except docker.errors.APIError as e:
|
except docker.errors.APIError as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
|
from app import db
|
||||||
|
from app.models import Job, JobResult, JobStatus, TesseractOCRModel
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
from .. import db
|
|
||||||
from ..models import Job, JobResult, TesseractOCRModel
|
|
||||||
import docker
|
import docker
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
@ -12,11 +12,11 @@ import shutil
|
|||||||
class CheckJobsMixin:
|
class CheckJobsMixin:
|
||||||
def check_jobs(self):
|
def check_jobs(self):
|
||||||
jobs = Job.query.all()
|
jobs = Job.query.all()
|
||||||
for job in (x for x in jobs if x.status == 'submitted'):
|
for job in (x for x in jobs if x.status == JobStatus.SUBMITTED):
|
||||||
self.create_job_service(job)
|
self.create_job_service(job)
|
||||||
for job in (x for x in jobs if x.status in ['queued', 'running']):
|
for job in (x for x in jobs if x.status in [JobStatus.QUEUED, JobStatus.RUNNING]): # noqa
|
||||||
self.checkout_job_service(job)
|
self.checkout_job_service(job)
|
||||||
for job in (x for x in jobs if x.status == 'canceling'):
|
for job in (x for x in jobs if x.status == JobStatus.CANCELING):
|
||||||
self.remove_job_service(job)
|
self.remove_job_service(job)
|
||||||
|
|
||||||
def create_job_service(self, job):
|
def create_job_service(self, job):
|
||||||
@ -74,7 +74,7 @@ class CheckJobsMixin:
|
|||||||
service_args = json.loads(job.service_args)
|
service_args = json.loads(job.service_args)
|
||||||
model = TesseractOCRModel.query.get(service_args['model'])
|
model = TesseractOCRModel.query.get(service_args['model'])
|
||||||
if model is None:
|
if model is None:
|
||||||
job.status = 'failed'
|
job.status = JobStatus.FAILED
|
||||||
return
|
return
|
||||||
models_mount_source = model.path
|
models_mount_source = model.path
|
||||||
models_mount_target = f'/usr/local/share/tessdata/{model.filename}'
|
models_mount_target = f'/usr/local/share/tessdata/{model.filename}'
|
||||||
@ -122,7 +122,7 @@ class CheckJobsMixin:
|
|||||||
f'due to "docker.errors.APIError": {e}'
|
f'due to "docker.errors.APIError": {e}'
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
job.status = 'queued'
|
job.status = JobStatus.QUEUED
|
||||||
|
|
||||||
def checkout_job_service(self, job):
|
def checkout_job_service(self, job):
|
||||||
service_name = f'job_{job.id}'
|
service_name = f'job_{job.id}'
|
||||||
@ -133,7 +133,7 @@ class CheckJobsMixin:
|
|||||||
f'Get service "{service_name}" failed '
|
f'Get service "{service_name}" failed '
|
||||||
f'due to "docker.errors.NotFound": {e}'
|
f'due to "docker.errors.NotFound": {e}'
|
||||||
)
|
)
|
||||||
job.status = 'failed'
|
job.status = JobStatus.FAILED
|
||||||
return
|
return
|
||||||
except docker.errors.APIError as e:
|
except docker.errors.APIError as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
@ -145,11 +145,11 @@ class CheckJobsMixin:
|
|||||||
if not service_tasks:
|
if not service_tasks:
|
||||||
return
|
return
|
||||||
task_state = service_tasks[0].get('Status').get('State')
|
task_state = service_tasks[0].get('Status').get('State')
|
||||||
if job.status == 'queued' and task_state != 'pending':
|
if job.status == JobStatus.QUEUED and task_state != 'pending':
|
||||||
job.status = 'running'
|
job.status = JobStatus.RUNNING
|
||||||
return
|
return
|
||||||
elif job.status == 'running' and task_state == 'complete':
|
elif job.status == JobStatus.RUNNING and task_state == 'complete': # noqa
|
||||||
job.status = 'complete'
|
job.status = JobStatus.COMPLETED
|
||||||
results_dir = os.path.join(job.path, 'results')
|
results_dir = os.path.join(job.path, 'results')
|
||||||
with open(os.path.join(results_dir, 'outputs.json')) as f:
|
with open(os.path.join(results_dir, 'outputs.json')) as f:
|
||||||
outputs = json.load(f)
|
outputs = json.load(f)
|
||||||
@ -169,8 +169,8 @@ class CheckJobsMixin:
|
|||||||
os.path.join(results_dir, output['file']),
|
os.path.join(results_dir, output['file']),
|
||||||
job_result.path
|
job_result.path
|
||||||
)
|
)
|
||||||
elif job.status == 'running' and task_state == 'failed':
|
elif job.status == JobStatus.RUNNING and task_state == 'failed':
|
||||||
job.status = 'failed'
|
job.status = JobStatus.FAILED
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
job.end_date = datetime.utcnow()
|
job.end_date = datetime.utcnow()
|
||||||
@ -187,7 +187,7 @@ class CheckJobsMixin:
|
|||||||
try:
|
try:
|
||||||
service = self.docker.services.get(service_name)
|
service = self.docker.services.get(service_name)
|
||||||
except docker.errors.NotFound:
|
except docker.errors.NotFound:
|
||||||
job.status = 'canceled'
|
job.status = JobStatus.CANCELED
|
||||||
return
|
return
|
||||||
except docker.errors.APIError as e:
|
except docker.errors.APIError as e:
|
||||||
current_app.logger.error(
|
current_app.logger.error(
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
from app import hashids
|
from app import hashids, socketio
|
||||||
|
from app.decorators import socketio_login_required
|
||||||
|
from app.models import User
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from flask_socketio import join_room
|
from flask_socketio import join_room
|
||||||
from .. import socketio
|
|
||||||
from ..decorators import socketio_login_required
|
|
||||||
from ..models import User
|
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
|
from app import db, mail, socketio
|
||||||
|
from app.email import create_message
|
||||||
|
from app.models import (
|
||||||
|
Corpus,
|
||||||
|
CorpusFile,
|
||||||
|
Job,
|
||||||
|
JobInput,
|
||||||
|
JobResult,
|
||||||
|
JobStatus,
|
||||||
|
JobStatusMailNotificationLevel
|
||||||
|
)
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from flask import current_app
|
|
||||||
from .. import db, mail, socketio
|
|
||||||
from ..email import create_message
|
|
||||||
from ..models import Corpus, CorpusFile, Job, JobInput, JobResult, QueryResult
|
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@ -13,7 +20,6 @@ from ..models import Corpus, CorpusFile, Job, JobInput, JobResult, QueryResult
|
|||||||
@db.event.listens_for(Job, 'after_delete')
|
@db.event.listens_for(Job, 'after_delete')
|
||||||
@db.event.listens_for(JobInput, 'after_delete')
|
@db.event.listens_for(JobInput, 'after_delete')
|
||||||
@db.event.listens_for(JobResult, 'after_delete')
|
@db.event.listens_for(JobResult, 'after_delete')
|
||||||
@db.event.listens_for(QueryResult, 'after_delete')
|
|
||||||
def ressource_after_delete(mapper, connection, ressource):
|
def ressource_after_delete(mapper, connection, ressource):
|
||||||
jsonpatch = [{'op': 'remove', 'path': ressource.jsonpatch_path}]
|
jsonpatch = [{'op': 'remove', 'path': ressource.jsonpatch_path}]
|
||||||
room = f'users.{ressource.user_hashid}'
|
room = f'users.{ressource.user_hashid}'
|
||||||
@ -25,14 +31,10 @@ def ressource_after_delete(mapper, connection, ressource):
|
|||||||
@db.event.listens_for(Job, 'after_insert')
|
@db.event.listens_for(Job, 'after_insert')
|
||||||
@db.event.listens_for(JobInput, 'after_insert')
|
@db.event.listens_for(JobInput, 'after_insert')
|
||||||
@db.event.listens_for(JobResult, 'after_insert')
|
@db.event.listens_for(JobResult, 'after_insert')
|
||||||
@db.event.listens_for(QueryResult, 'after_insert')
|
|
||||||
def ressource_after_insert_handler(mapper, connection, ressource):
|
def ressource_after_insert_handler(mapper, connection, ressource):
|
||||||
value = ressource.to_dict(backrefs=False, relationships=False)
|
value = ressource.to_dict(backrefs=False, relationships=False)
|
||||||
if isinstance(ressource, Job):
|
for relationship in mapper.relationships:
|
||||||
value['inputs'] = {}
|
value[relationship.key] = {}
|
||||||
value['results'] = {}
|
|
||||||
elif isinstance(ressource, Corpus):
|
|
||||||
value['files'] = {}
|
|
||||||
jsonpatch = [
|
jsonpatch = [
|
||||||
{'op': 'add', 'path': ressource.jsonpatch_path, 'value': value}
|
{'op': 'add', 'path': ressource.jsonpatch_path, 'value': value}
|
||||||
]
|
]
|
||||||
@ -45,35 +47,43 @@ def ressource_after_insert_handler(mapper, connection, ressource):
|
|||||||
@db.event.listens_for(Job, 'after_update')
|
@db.event.listens_for(Job, 'after_update')
|
||||||
@db.event.listens_for(JobInput, 'after_update')
|
@db.event.listens_for(JobInput, 'after_update')
|
||||||
@db.event.listens_for(JobResult, 'after_update')
|
@db.event.listens_for(JobResult, 'after_update')
|
||||||
@db.event.listens_for(QueryResult, 'after_update')
|
|
||||||
def ressource_after_update_handler(mapper, connection, ressource):
|
def ressource_after_update_handler(mapper, connection, ressource):
|
||||||
jsonpatch = []
|
jsonpatch = []
|
||||||
for attr in db.inspect(ressource).attrs:
|
for attr in db.inspect(ressource).attrs:
|
||||||
# We don't want to handle changes in relationship fields
|
# Don't handle changes in relationship fields
|
||||||
# TODO: Find a way to handle this without a hardcoded list
|
if attr.key in mapper.relationships:
|
||||||
if attr.key in ['files', 'inputs', 'results']:
|
|
||||||
continue
|
continue
|
||||||
|
# Check if their are changes for the current field
|
||||||
history = attr.load_history()
|
history = attr.load_history()
|
||||||
if not history.has_changes():
|
if not history.has_changes():
|
||||||
continue
|
continue
|
||||||
new_value = history.added[0]
|
if isinstance(history.added[0], datetime):
|
||||||
# In order to be JSON serializable, DateTime attributes must be
|
# In order to be JSON serializable, DateTime attributes must be
|
||||||
# converted to a string
|
# converted to a string
|
||||||
if isinstance(new_value, datetime):
|
attr_name = attr.key
|
||||||
new_value = new_value.isoformat() + 'Z'
|
value = history.added[0].isoformat() + 'Z'
|
||||||
|
elif attr.key.endswith('_enum_value'):
|
||||||
|
# Handling fake enum attributes
|
||||||
|
attr_name = attr.key[:-11]
|
||||||
|
value = getattr(ressource, attr_name).name
|
||||||
|
else:
|
||||||
|
attr_name = attr.key
|
||||||
|
value = history.added[0]
|
||||||
jsonpatch.append(
|
jsonpatch.append(
|
||||||
{
|
{
|
||||||
'op': 'replace',
|
'op': 'replace',
|
||||||
'path': f'{ressource.jsonpatch_path}/{attr.key}',
|
'path': f'{ressource.jsonpatch_path}/{attr_name}',
|
||||||
'value': new_value
|
'value': value
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
# Job status update notification if it changed and wanted by the user
|
# Job status update notification if it changed and wanted by the user
|
||||||
if isinstance(ressource, Job) and attr.key == 'status':
|
if isinstance(ressource, Job) and attr_name == 'status':
|
||||||
if ressource.user.setting_job_status_mail_notifications == 'none': # noqa
|
if ressource.user.setting_job_status_mail_notification_level == JobStatusMailNotificationLevel.NONE: # noqa
|
||||||
pass
|
pass
|
||||||
elif (ressource.user.setting_job_status_mail_notifications == 'end' # noqa
|
elif (
|
||||||
and ressource.status not in ['complete', 'failed']):
|
ressource.user.setting_job_status_mail_notification_level == JobStatusMailNotificationLevel.END # noqa
|
||||||
|
and ressource.status not in [JobStatus.COMPLETED, JobStatus.FAILED] # noqa
|
||||||
|
):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
msg = create_message(
|
msg = create_message(
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
from flask import (abort, flash, redirect, render_template,
|
from app.decorators import admin_required
|
||||||
send_from_directory, url_for)
|
from app.models import Job, JobInput, JobResult, JobStatus
|
||||||
|
from flask import (
|
||||||
|
abort,
|
||||||
|
flash,
|
||||||
|
redirect,
|
||||||
|
render_template,
|
||||||
|
send_from_directory,
|
||||||
|
url_for
|
||||||
|
)
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
from . import bp
|
from . import bp
|
||||||
from . import tasks
|
from . import tasks
|
||||||
from ..decorators import admin_required
|
|
||||||
from ..models import Job, JobInput, JobResult
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
@ -14,9 +20,11 @@ def job(job_id):
|
|||||||
job = Job.query.get_or_404(job_id)
|
job = Job.query.get_or_404(job_id)
|
||||||
if not (job.user == current_user or current_user.is_administrator()):
|
if not (job.user == current_user or current_user.is_administrator()):
|
||||||
abort(403)
|
abort(403)
|
||||||
job_inputs = [job_input.to_dict() for job_input in job.inputs]
|
return render_template(
|
||||||
return render_template('jobs/job.html.j2', job=job, job_inputs=job_inputs,
|
'jobs/job.html.j2',
|
||||||
title='Job')
|
job=job,
|
||||||
|
title='Job'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:job_id>/delete')
|
@bp.route('/<hashid:job_id>/delete')
|
||||||
@ -26,15 +34,21 @@ def delete_job(job_id):
|
|||||||
if not (job.user == current_user or current_user.is_administrator()):
|
if not (job.user == current_user or current_user.is_administrator()):
|
||||||
abort(403)
|
abort(403)
|
||||||
tasks.delete_job(job_id)
|
tasks.delete_job(job_id)
|
||||||
flash('Job has been marked for deletion!', 'job')
|
flash(f'Job "{job.title}" marked for deletion', 'job')
|
||||||
return redirect(url_for('main.dashboard'))
|
return redirect(url_for('main.dashboard'))
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:job_id>/inputs/<hashid:job_input_id>/download')
|
@bp.route('/<hashid:job_id>/inputs/<hashid:job_input_id>/download')
|
||||||
@login_required
|
@login_required
|
||||||
def download_job_input(job_id, job_input_id):
|
def download_job_input(job_id, job_input_id):
|
||||||
job_input = JobInput.query.filter(JobInput.job_id == job_id, JobInput.id == job_input_id).first_or_404() # noqa
|
job_input = JobInput.query.filter(
|
||||||
if not (job_input.job.user == current_user or current_user.is_administrator()): # noqa
|
JobInput.job_id == job_id,
|
||||||
|
JobInput.id == job_input_id
|
||||||
|
).first_or_404()
|
||||||
|
if not (
|
||||||
|
job_input.job.user == current_user
|
||||||
|
or current_user.is_administrator()
|
||||||
|
):
|
||||||
abort(403)
|
abort(403)
|
||||||
return send_from_directory(
|
return send_from_directory(
|
||||||
as_attachment=True,
|
as_attachment=True,
|
||||||
@ -49,19 +63,28 @@ def download_job_input(job_id, job_input_id):
|
|||||||
@admin_required
|
@admin_required
|
||||||
def restart(job_id):
|
def restart(job_id):
|
||||||
job = Job.query.get_or_404(job_id)
|
job = Job.query.get_or_404(job_id)
|
||||||
if job.status not in ['complete', 'failed']:
|
if job.status not in [JobStatus.COMPLETED, JobStatus.FAILED]:
|
||||||
flash(f'Can not restart job "{job.title}": Status is not "complete/failed"', 'error') # noqa
|
flash(
|
||||||
|
f'Can\'t restart job "{job.title}": Status is not "Completed/Failed"', # noqa
|
||||||
|
category='error'
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
tasks.restart_job(job_id)
|
tasks.restart_job(job_id)
|
||||||
flash(f'Job "{job.title}" marked to get restarted!', 'job')
|
flash(f'Job "{job.title}" marked to get restarted', category='job')
|
||||||
return redirect(url_for('.job', job_id=job_id))
|
return redirect(url_for('.job', job_id=job_id))
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:job_id>/results/<hashid:job_result_id>/download')
|
@bp.route('/<hashid:job_id>/results/<hashid:job_result_id>/download')
|
||||||
@login_required
|
@login_required
|
||||||
def download_job_result(job_id, job_result_id):
|
def download_job_result(job_id, job_result_id):
|
||||||
job_result = JobResult.query.filter(JobResult.job_id == job_id, JobResult.id == job_result_id).first_or_404() # noqa
|
job_result = JobResult.query.filter(
|
||||||
if not (job_result.job.user == current_user or current_user.is_administrator()): # noqa
|
JobResult.job_id == job_id,
|
||||||
|
JobResult.id == job_result_id
|
||||||
|
).first_or_404()
|
||||||
|
if not (
|
||||||
|
job_result.job.user == current_user
|
||||||
|
or current_user.is_administrator()
|
||||||
|
):
|
||||||
abort(403)
|
abort(403)
|
||||||
return send_from_directory(
|
return send_from_directory(
|
||||||
as_attachment=True,
|
as_attachment=True,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from .. import db
|
from app import db
|
||||||
from ..decorators import background
|
from app.decorators import background
|
||||||
from ..models import Job
|
from app.models import Job
|
||||||
|
|
||||||
|
|
||||||
@background
|
@background
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
|
from app.auth.forms import LoginForm
|
||||||
|
from app.models import User
|
||||||
from flask import flash, redirect, render_template, url_for
|
from flask import flash, redirect, render_template, url_for
|
||||||
from flask_login import login_required, login_user
|
from flask_login import login_required, login_user
|
||||||
from . import bp
|
from . import bp
|
||||||
from ..auth.forms import LoginForm
|
|
||||||
from ..models import User
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/', methods=['GET', 'POST'])
|
@bp.route('/', methods=['GET', 'POST'])
|
||||||
@ -21,8 +21,10 @@ def index():
|
|||||||
|
|
||||||
@bp.route('/faq')
|
@bp.route('/faq')
|
||||||
def faq():
|
def faq():
|
||||||
return render_template('main/faq.html.j2',
|
return render_template(
|
||||||
title='Frequently Asked Questions')
|
'main/faq.html.j2',
|
||||||
|
title='Frequently Asked Questions'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/dashboard')
|
@bp.route('/dashboard')
|
||||||
@ -38,8 +40,10 @@ def news():
|
|||||||
|
|
||||||
@bp.route('/privacy_policy')
|
@bp.route('/privacy_policy')
|
||||||
def privacy_policy():
|
def privacy_policy():
|
||||||
return render_template('main/privacy_policy.html.j2',
|
return render_template(
|
||||||
title='Privacy statement (GDPR)')
|
'main/privacy_policy.html.j2',
|
||||||
|
title='Privacy statement (GDPR)'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/terms_of_use')
|
@bp.route('/terms_of_use')
|
||||||
|
206
app/models.py
206
app/models.py
@ -1,4 +1,5 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from enum import IntEnum
|
||||||
from flask import current_app, url_for
|
from flask import current_app, url_for
|
||||||
from flask_hashids import HashidMixin
|
from flask_hashids import HashidMixin
|
||||||
from flask_login import UserMixin
|
from flask_login import UserMixin
|
||||||
@ -8,7 +9,6 @@ from tqdm import tqdm
|
|||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
from . import db, login
|
from . import db, login
|
||||||
import base64
|
import base64
|
||||||
import enum
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
@ -17,7 +17,36 @@ import xml.etree.ElementTree as ET
|
|||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
class Permission(enum.IntEnum):
|
class CorpusStatus(IntEnum):
|
||||||
|
UNPREPARED = 1
|
||||||
|
SUBMITTED = 2
|
||||||
|
QUEUED = 3
|
||||||
|
BUILDING = 4
|
||||||
|
BUILT = 5
|
||||||
|
FAILED = 6
|
||||||
|
STARTING_ANALYSIS_SESSION = 7
|
||||||
|
RUNNING_ANALYSIS_SESSION = 8
|
||||||
|
CANCELING_ANALYSIS_SESSION = 9
|
||||||
|
|
||||||
|
|
||||||
|
class JobStatus(IntEnum):
|
||||||
|
INITIALIZING = 1
|
||||||
|
SUBMITTED = 2
|
||||||
|
QUEUED = 3
|
||||||
|
RUNNING = 4
|
||||||
|
CANCELING = 5
|
||||||
|
CANCELED = 6
|
||||||
|
COMPLETED = 7
|
||||||
|
FAILED = 8
|
||||||
|
|
||||||
|
|
||||||
|
class JobStatusMailNotificationLevel(IntEnum):
|
||||||
|
NONE = 1
|
||||||
|
END = 2
|
||||||
|
ALL = 3
|
||||||
|
|
||||||
|
|
||||||
|
class Permission(IntEnum):
|
||||||
'''
|
'''
|
||||||
Defines User permissions as integers by the power of 2. User permission
|
Defines User permissions as integers by the power of 2. User permission
|
||||||
can be evaluated using the bitwise operator &.
|
can be evaluated using the bitwise operator &.
|
||||||
@ -130,10 +159,11 @@ class User(HashidMixin, UserMixin, db.Model):
|
|||||||
token_expiration = db.Column(db.DateTime)
|
token_expiration = db.Column(db.DateTime)
|
||||||
username = db.Column(db.String(64), unique=True, index=True)
|
username = db.Column(db.String(64), unique=True, index=True)
|
||||||
setting_dark_mode = db.Column(db.Boolean, default=False)
|
setting_dark_mode = db.Column(db.Boolean, default=False)
|
||||||
setting_job_status_mail_notifications = db.Column(
|
setting_job_status_mail_notification_level_enum_value = db.Column(
|
||||||
db.String(16), default='end')
|
'setting_job_status_mail_notification_level',
|
||||||
setting_job_status_site_notifications = db.Column(
|
db.Integer,
|
||||||
db.String(16), default='all')
|
default=2
|
||||||
|
)
|
||||||
# Backrefs: role: Role
|
# Backrefs: role: Role
|
||||||
# Relationships
|
# Relationships
|
||||||
tesseract_ocr_models = db.relationship(
|
tesseract_ocr_models = db.relationship(
|
||||||
@ -154,12 +184,6 @@ class User(HashidMixin, UserMixin, db.Model):
|
|||||||
cascade='all, delete-orphan',
|
cascade='all, delete-orphan',
|
||||||
lazy='dynamic'
|
lazy='dynamic'
|
||||||
)
|
)
|
||||||
query_results = db.relationship(
|
|
||||||
'QueryResult',
|
|
||||||
backref='user',
|
|
||||||
cascade='all, delete-orphan',
|
|
||||||
lazy='dynamic'
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
@ -188,7 +212,20 @@ class User(HashidMixin, UserMixin, db.Model):
|
|||||||
@property
|
@property
|
||||||
def path(self):
|
def path(self):
|
||||||
return os.path.join(
|
return os.path.join(
|
||||||
current_app.config.get('NOPAQUE_DATA_DIR'), str(self.id))
|
current_app.config.get('NOPAQUE_DATA_DIR'), 'users', str(self.id))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def setting_job_status_mail_notification_level(self):
|
||||||
|
return JobStatusMailNotificationLevel(
|
||||||
|
self.setting_job_status_mail_notification_level_enum_value
|
||||||
|
)
|
||||||
|
|
||||||
|
@setting_job_status_mail_notification_level.setter
|
||||||
|
def setting_job_status_mail_notification_level(self, enum_member):
|
||||||
|
if not isinstance(enum_member, JobStatusMailNotificationLevel):
|
||||||
|
return TypeError()
|
||||||
|
self.setting_job_status_mail_notification_level_enum_value = \
|
||||||
|
enum_member.value
|
||||||
|
|
||||||
def can(self, permission):
|
def can(self, permission):
|
||||||
return self.role.has_permission(permission)
|
return self.role.has_permission(permission)
|
||||||
@ -251,10 +288,8 @@ class User(HashidMixin, UserMixin, db.Model):
|
|||||||
'username': self.username,
|
'username': self.username,
|
||||||
'settings': {
|
'settings': {
|
||||||
'dark_mode': self.setting_dark_mode,
|
'dark_mode': self.setting_dark_mode,
|
||||||
'job_status_mail_notifications':
|
'job_status_mail_notification_level':
|
||||||
self.setting_job_status_mail_notifications,
|
self.setting_job_status_mail_notification_level.name
|
||||||
'job_status_site_notifications':
|
|
||||||
self.setting_job_status_site_notifications
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if backrefs:
|
if backrefs:
|
||||||
@ -269,9 +304,9 @@ class User(HashidMixin, UserMixin, db.Model):
|
|||||||
x.hashid: x.to_dict(backrefs=False, relationships=True)
|
x.hashid: x.to_dict(backrefs=False, relationships=True)
|
||||||
for x in self.jobs
|
for x in self.jobs
|
||||||
}
|
}
|
||||||
dict_user['query_results'] = {
|
dict_user['tesseract_ocr_models'] = {
|
||||||
x.hashid: x.to_dict(backrefs=False, relationships=True)
|
x.hashid: x.to_dict(backrefs=False, relationships=True)
|
||||||
for x in self.query_results
|
for x in self.tesseract_ocr_models
|
||||||
}
|
}
|
||||||
return dict_user
|
return dict_user
|
||||||
|
|
||||||
@ -338,6 +373,25 @@ class TesseractOCRModel(FileMixin, HashidMixin, db.Model):
|
|||||||
str(self.id)
|
str(self.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def to_dict(self, backrefs=False, relationships=False):
|
||||||
|
compatible_service_versions = json.loads(self.compatible_service_versions) # noqa
|
||||||
|
dict_tesseract_ocr_model = {
|
||||||
|
'id': self.hashid,
|
||||||
|
'user_id': self.user.hashid,
|
||||||
|
'compatible_service_versions': compatible_service_versions,
|
||||||
|
'description': self.description,
|
||||||
|
'publisher': self.publisher,
|
||||||
|
'publishing_year': self.publishing_year,
|
||||||
|
'title': self.title,
|
||||||
|
**self.file_mixin_to_dict()
|
||||||
|
}
|
||||||
|
if backrefs:
|
||||||
|
dict_tesseract_ocr_model['user'] = self.user.to_dict(
|
||||||
|
backrefs=True, relationships=False)
|
||||||
|
if relationships:
|
||||||
|
pass
|
||||||
|
return dict_tesseract_ocr_model
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def insert_defaults():
|
def insert_defaults():
|
||||||
user = User.query.filter_by(username='nopaque').first()
|
user = User.query.filter_by(username='nopaque').first()
|
||||||
@ -519,7 +573,7 @@ class Job(HashidMixin, db.Model):
|
|||||||
'''
|
'''
|
||||||
service_args = db.Column(db.String(255))
|
service_args = db.Column(db.String(255))
|
||||||
service_version = db.Column(db.String(16))
|
service_version = db.Column(db.String(16))
|
||||||
status = db.Column(db.String(16))
|
status_enum_value = db.Column('status', db.Integer, default=1)
|
||||||
title = db.Column(db.String(32))
|
title = db.Column(db.String(32))
|
||||||
# Backrefs: user: User
|
# Backrefs: user: User
|
||||||
# Relationships
|
# Relationships
|
||||||
@ -547,6 +601,16 @@ class Job(HashidMixin, db.Model):
|
|||||||
def path(self):
|
def path(self):
|
||||||
return os.path.join(self.user.path, 'jobs', str(self.id))
|
return os.path.join(self.user.path, 'jobs', str(self.id))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self):
|
||||||
|
return JobStatus(self.status_enum_value)
|
||||||
|
|
||||||
|
@status.setter
|
||||||
|
def status(self, enum_member):
|
||||||
|
if not isinstance(enum_member, JobStatus):
|
||||||
|
return TypeError()
|
||||||
|
self.status_enum_value = enum_member.value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def url(self):
|
def url(self):
|
||||||
return url_for('jobs.job', job_id=self.id)
|
return url_for('jobs.job', job_id=self.id)
|
||||||
@ -559,13 +623,13 @@ class Job(HashidMixin, db.Model):
|
|||||||
'''
|
'''
|
||||||
Delete the job and its inputs and results from the database.
|
Delete the job and its inputs and results from the database.
|
||||||
'''
|
'''
|
||||||
if self.status not in ['complete', 'failed']:
|
if self.status not in [JobStatus.COMPLETED, JobStatus.FAILED]: # noqa
|
||||||
self.status = 'canceling'
|
self.status = JobStatus.CANCELING
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
while self.status != 'canceled':
|
while self.status != JobStatus.CANCELED:
|
||||||
# In case the daemon handled a job in any way
|
# In case the daemon handled a job in any way
|
||||||
if self.status != 'canceling':
|
if self.status != JobStatus.CANCELING:
|
||||||
self.status = 'canceling'
|
self.status = JobStatus.CANCELING
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
sleep(1)
|
sleep(1)
|
||||||
db.session.refresh(self)
|
db.session.refresh(self)
|
||||||
@ -583,14 +647,14 @@ class Job(HashidMixin, db.Model):
|
|||||||
Restart a job - only if the status is complete or failed
|
Restart a job - only if the status is complete or failed
|
||||||
'''
|
'''
|
||||||
|
|
||||||
if self.status not in ['complete', 'failed']:
|
if self.status not in [JobStatus.COMPLETED, JobStatus.FAILED]: # noqa
|
||||||
raise Exception('Could not restart job: status is not "complete/failed"') # noqa
|
raise Exception('Could not restart job: status is not "completed/failed"') # noqa
|
||||||
shutil.rmtree(os.path.join(self.path, 'results'), ignore_errors=True)
|
shutil.rmtree(os.path.join(self.path, 'results'), ignore_errors=True)
|
||||||
shutil.rmtree(os.path.join(self.path, 'pyflow.data'), ignore_errors=True) # noqa
|
shutil.rmtree(os.path.join(self.path, 'pyflow.data'), ignore_errors=True) # noqa
|
||||||
for result in self.results:
|
for result in self.results:
|
||||||
db.session.delete(result)
|
db.session.delete(result)
|
||||||
self.end_date = None
|
self.end_date = None
|
||||||
self.status = 'submitted'
|
self.status = JobStatus.SUBMITTED
|
||||||
|
|
||||||
def to_dict(self, backrefs=False, relationships=False):
|
def to_dict(self, backrefs=False, relationships=False):
|
||||||
service_args = json.loads(self.service_args)
|
service_args = json.loads(self.service_args)
|
||||||
@ -606,7 +670,7 @@ class Job(HashidMixin, db.Model):
|
|||||||
'service': self.service,
|
'service': self.service,
|
||||||
'service_args': service_args,
|
'service_args': service_args,
|
||||||
'service_version': self.service_version,
|
'service_version': self.service_version,
|
||||||
'status': self.status,
|
'status': self.status.name,
|
||||||
'title': self.title,
|
'title': self.title,
|
||||||
'url': self.url
|
'url': self.url
|
||||||
}
|
}
|
||||||
@ -687,7 +751,7 @@ class CorpusFile(FileMixin, HashidMixin, db.Model):
|
|||||||
)
|
)
|
||||||
pass
|
pass
|
||||||
db.session.delete(self)
|
db.session.delete(self)
|
||||||
self.corpus.status = 'unprepared'
|
self.corpus.status = CorpusStatus.UNPREPARED
|
||||||
|
|
||||||
def to_dict(self, backrefs=False, relationships=False):
|
def to_dict(self, backrefs=False, relationships=False):
|
||||||
dict_corpus_file = {
|
dict_corpus_file = {
|
||||||
@ -729,7 +793,7 @@ class Corpus(HashidMixin, db.Model):
|
|||||||
creation_date = db.Column(db.DateTime(), default=datetime.utcnow)
|
creation_date = db.Column(db.DateTime(), default=datetime.utcnow)
|
||||||
description = db.Column(db.String(255))
|
description = db.Column(db.String(255))
|
||||||
last_edited_date = db.Column(db.DateTime(), default=datetime.utcnow)
|
last_edited_date = db.Column(db.DateTime(), default=datetime.utcnow)
|
||||||
status = db.Column(db.String(16), default='unprepared')
|
status_enum_value = db.Column('status', db.Integer, default=1)
|
||||||
title = db.Column(db.String(32))
|
title = db.Column(db.String(32))
|
||||||
num_analysis_sessions = db.Column(db.Integer, default=0)
|
num_analysis_sessions = db.Column(db.Integer, default=0)
|
||||||
num_tokens = db.Column(db.Integer, default=0)
|
num_tokens = db.Column(db.Integer, default=0)
|
||||||
@ -742,7 +806,7 @@ class Corpus(HashidMixin, db.Model):
|
|||||||
lazy='dynamic',
|
lazy='dynamic',
|
||||||
cascade='all, delete-orphan'
|
cascade='all, delete-orphan'
|
||||||
)
|
)
|
||||||
# Python class variables
|
# "static" attributes
|
||||||
max_num_tokens = 2147483647
|
max_num_tokens = 2147483647
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@ -760,6 +824,16 @@ class Corpus(HashidMixin, db.Model):
|
|||||||
def path(self):
|
def path(self):
|
||||||
return os.path.join(self.user.path, 'corpora', str(self.id))
|
return os.path.join(self.user.path, 'corpora', str(self.id))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self):
|
||||||
|
return CorpusStatus(self.status_enum_value)
|
||||||
|
|
||||||
|
@status.setter
|
||||||
|
def status(self, enum_member):
|
||||||
|
if not isinstance(enum_member, CorpusStatus):
|
||||||
|
return TypeError()
|
||||||
|
self.status_enum_value = enum_member.value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def url(self):
|
def url(self):
|
||||||
return url_for('corpora.corpus', corpus_id=self.id)
|
return url_for('corpora.corpus', corpus_id=self.id)
|
||||||
@ -791,7 +865,7 @@ class Corpus(HashidMixin, db.Model):
|
|||||||
encoding='utf-8'
|
encoding='utf-8'
|
||||||
)
|
)
|
||||||
self.last_edited_date = datetime.utcnow()
|
self.last_edited_date = datetime.utcnow()
|
||||||
self.status = 'submitted'
|
self.status = CorpusStatus.SUBMITTED
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
shutil.rmtree(self.path, ignore_errors=True)
|
shutil.rmtree(self.path, ignore_errors=True)
|
||||||
@ -815,7 +889,7 @@ class Corpus(HashidMixin, db.Model):
|
|||||||
'max_num_tokens': self.max_num_tokens,
|
'max_num_tokens': self.max_num_tokens,
|
||||||
'num_analysis_sessions': self.num_analysis_sessions,
|
'num_analysis_sessions': self.num_analysis_sessions,
|
||||||
'num_tokens': self.num_tokens,
|
'num_tokens': self.num_tokens,
|
||||||
'status': self.status,
|
'status': self.status.name,
|
||||||
'last_edited_date': self.last_edited_date.isoformat() + 'Z',
|
'last_edited_date': self.last_edited_date.isoformat() + 'Z',
|
||||||
'title': self.title
|
'title': self.title
|
||||||
}
|
}
|
||||||
@ -830,70 +904,6 @@ class Corpus(HashidMixin, db.Model):
|
|||||||
return dict_corpus
|
return dict_corpus
|
||||||
|
|
||||||
|
|
||||||
class QueryResult(FileMixin, HashidMixin, db.Model):
|
|
||||||
__tablename__ = 'query_results'
|
|
||||||
# Primary key
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
|
||||||
# Foreign keys
|
|
||||||
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
|
|
||||||
# Fields
|
|
||||||
description = db.Column(db.String(255))
|
|
||||||
query_metadata = db.Column(db.JSON())
|
|
||||||
title = db.Column(db.String(32))
|
|
||||||
# Backrefs: user: User
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
'''
|
|
||||||
String representation of the QueryResult. For human readability.
|
|
||||||
'''
|
|
||||||
return f'<QueryResult {self.title}>'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def download_url(self):
|
|
||||||
return url_for(
|
|
||||||
'corpora.download_query_result', query_result_id=self.id)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def jsonpatch_path(self):
|
|
||||||
return f'{self.user.jsonpatch_path}/query_results/{self.hashid}'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def path(self):
|
|
||||||
return os.path.join(
|
|
||||||
self.user.path, 'query_results', str(self.id), self.filename)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def url(self):
|
|
||||||
return url_for('corpora.query_result', query_result_id=self.id)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def user_hashid(self):
|
|
||||||
return self.user.hashid
|
|
||||||
|
|
||||||
def delete(self):
|
|
||||||
shutil.rmtree(self.path, ignore_errors=True)
|
|
||||||
db.session.delete(self)
|
|
||||||
|
|
||||||
def to_dict(self, backrefs=False, relationships=False):
|
|
||||||
dict_query_result = {
|
|
||||||
'id': self.hashid,
|
|
||||||
'user_id': self.user.hashid,
|
|
||||||
'download_url': self.download_url,
|
|
||||||
'url': self.url,
|
|
||||||
'corpus_title': self.query_metadata['corpus_name'],
|
|
||||||
'description': self.description,
|
|
||||||
'filename': self.filename,
|
|
||||||
'query': self.query_metadata['query'],
|
|
||||||
'query_metadata': self.query_metadata,
|
|
||||||
'title': self.title,
|
|
||||||
**self.file_mixin_to_dict(
|
|
||||||
backrefs=backrefs, relationships=relationships)
|
|
||||||
}
|
|
||||||
if backrefs:
|
|
||||||
dict_query_result['user'] = self.user.to_dict(
|
|
||||||
backrefs=True, relationships=False)
|
|
||||||
|
|
||||||
|
|
||||||
@login.user_loader
|
@login.user_loader
|
||||||
def load_user(user_id):
|
def load_user(user_id):
|
||||||
return User.query.get(int(user_id))
|
return User.query.get(int(user_id))
|
||||||
|
62
app/query_results_models.py
Normal file
62
app/query_results_models.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
class QueryResult(FileMixin, HashidMixin, db.Model):
|
||||||
|
__tablename__ = 'query_results'
|
||||||
|
# Primary key
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
# Foreign keys
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
|
||||||
|
# Fields
|
||||||
|
description = db.Column(db.String(255))
|
||||||
|
query_metadata = db.Column(db.JSON())
|
||||||
|
title = db.Column(db.String(32))
|
||||||
|
# Backrefs: user: User
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
'''
|
||||||
|
String representation of the QueryResult. For human readability.
|
||||||
|
'''
|
||||||
|
return f'<QueryResult {self.title}>'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def download_url(self):
|
||||||
|
return url_for(
|
||||||
|
'corpora.download_query_result', query_result_id=self.id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def jsonpatch_path(self):
|
||||||
|
return f'{self.user.jsonpatch_path}/query_results/{self.hashid}'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
return os.path.join(
|
||||||
|
self.user.path, 'query_results', str(self.id), self.filename)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self):
|
||||||
|
return url_for('corpora.query_result', query_result_id=self.id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user_hashid(self):
|
||||||
|
return self.user.hashid
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
shutil.rmtree(self.path, ignore_errors=True)
|
||||||
|
db.session.delete(self)
|
||||||
|
|
||||||
|
def to_dict(self, backrefs=False, relationships=False):
|
||||||
|
dict_query_result = {
|
||||||
|
'id': self.hashid,
|
||||||
|
'user_id': self.user.hashid,
|
||||||
|
'download_url': self.download_url,
|
||||||
|
'url': self.url,
|
||||||
|
'corpus_title': self.query_metadata['corpus_name'],
|
||||||
|
'description': self.description,
|
||||||
|
'filename': self.filename,
|
||||||
|
'query': self.query_metadata['query'],
|
||||||
|
'query_metadata': self.query_metadata,
|
||||||
|
'title': self.title,
|
||||||
|
**self.file_mixin_to_dict(
|
||||||
|
backrefs=backrefs, relationships=relationships)
|
||||||
|
}
|
||||||
|
if backrefs:
|
||||||
|
dict_query_result['user'] = self.user.to_dict(
|
||||||
|
backrefs=True, relationships=False)
|
@ -1,7 +1,13 @@
|
|||||||
from app.models import TesseractOCRModel
|
from app.models import TesseractOCRModel
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import (BooleanField, MultipleFileField, SelectField, StringField,
|
from wtforms import (
|
||||||
SubmitField, ValidationError)
|
BooleanField,
|
||||||
|
MultipleFileField,
|
||||||
|
SelectField,
|
||||||
|
StringField,
|
||||||
|
SubmitField,
|
||||||
|
ValidationError
|
||||||
|
)
|
||||||
from wtforms.validators import DataRequired, Length
|
from wtforms.validators import DataRequired, Length
|
||||||
from . import SERVICES
|
from . import SERVICES
|
||||||
|
|
||||||
@ -25,7 +31,7 @@ class AddSpacyNLPJobForm(AddJobForm):
|
|||||||
|
|
||||||
def validate_encoding_detection(self, field):
|
def validate_encoding_detection(self, field):
|
||||||
service_info = SERVICES['spacy-nlp']['versions'][self.version.data]
|
service_info = SERVICES['spacy-nlp']['versions'][self.version.data]
|
||||||
if field.data and 'encoding_detection' not in service_info:
|
if field.data and 'encoding_detection' not in service_info['methods']:
|
||||||
raise ValidationError('Encoding detection is not available')
|
raise ValidationError('Encoding detection is not available')
|
||||||
|
|
||||||
def validate_files(form, field):
|
def validate_files(form, field):
|
||||||
@ -41,7 +47,7 @@ class AddSpacyNLPJobForm(AddJobForm):
|
|||||||
version = kwargs.pop('version', SERVICES['spacy-nlp']['latest_version']) # noqa
|
version = kwargs.pop('version', SERVICES['spacy-nlp']['latest_version']) # noqa
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
service_info = SERVICES['spacy-nlp']['versions'][version]
|
service_info = SERVICES['spacy-nlp']['versions'][version]
|
||||||
if 'check_encoding' not in service_info['methods']:
|
if 'encoding_detection' not in service_info['methods']:
|
||||||
self.encoding_detection.render_kw = {'disabled': True}
|
self.encoding_detection.render_kw = {'disabled': True}
|
||||||
self.model.choices += [(x, y) for x, y in service_info['models'].items()] # noqa
|
self.model.choices += [(x, y) for x, y in service_info['models'].items()] # noqa
|
||||||
self.version.choices = [(x, x) for x in SERVICES['spacy-nlp']['versions']] # noqa
|
self.version.choices = [(x, x) for x in SERVICES['spacy-nlp']['versions']] # noqa
|
||||||
@ -60,7 +66,7 @@ class AddTesseractOCRJobForm(AddJobForm):
|
|||||||
|
|
||||||
def validate_binarization(self, field):
|
def validate_binarization(self, field):
|
||||||
service_info = SERVICES['tesseract-ocr']['versions'][self.version.data]
|
service_info = SERVICES['tesseract-ocr']['versions'][self.version.data]
|
||||||
if field.data and 'binarization' not in service_info:
|
if field.data and 'binarization' not in service_info['methods']:
|
||||||
raise ValidationError('Binarization is not available')
|
raise ValidationError('Binarization is not available')
|
||||||
|
|
||||||
def validate_files(self, field):
|
def validate_files(self, field):
|
||||||
|
@ -1,21 +1,29 @@
|
|||||||
from app import hashids
|
from app import db, hashids
|
||||||
from flask import (abort, current_app, flash, make_response, render_template,
|
from app.models import Job, JobInput, JobStatus
|
||||||
request, url_for)
|
from flask import (
|
||||||
|
abort,
|
||||||
|
current_app,
|
||||||
|
flash,
|
||||||
|
make_response,
|
||||||
|
render_template,
|
||||||
|
request,
|
||||||
|
url_for
|
||||||
|
)
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
from . import bp
|
from . import bp
|
||||||
from . import SERVICES
|
from . import SERVICES
|
||||||
from .. import db
|
|
||||||
from .forms import AddJobForms
|
from .forms import AddJobForms
|
||||||
from ..models import Job, JobInput
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/corpus-analysis')
|
@bp.route('/corpus-analysis')
|
||||||
@login_required
|
@login_required
|
||||||
def corpus_analysis():
|
def corpus_analysis():
|
||||||
return render_template('services/corpus_analysis.html.j2',
|
return render_template(
|
||||||
title='Corpus analysis')
|
'services/corpus_analysis.html.j2',
|
||||||
|
title='Corpus analysis'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<service>', methods=['GET', 'POST'])
|
@bp.route('/<service>', methods=['GET', 'POST'])
|
||||||
@ -47,7 +55,6 @@ def service(service):
|
|||||||
service=service,
|
service=service,
|
||||||
service_args=json.dumps(service_args),
|
service_args=json.dumps(service_args),
|
||||||
service_version=form.version.data,
|
service_version=form.version.data,
|
||||||
status='preparing',
|
|
||||||
title=form.title.data
|
title=form.title.data
|
||||||
)
|
)
|
||||||
db.session.add(job)
|
db.session.add(job)
|
||||||
@ -77,7 +84,7 @@ def service(service):
|
|||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
flash('Internal Server Error', 'error')
|
flash('Internal Server Error', 'error')
|
||||||
return make_response({'redirect_url': url_for('.service', service=service)}, 500) # noqa
|
return make_response({'redirect_url': url_for('.service', service=service)}, 500) # noqa
|
||||||
job.status = 'submitted'
|
job.status = JobStatus.SUBMITTED
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash(f'Job "{job.title}" added', 'job')
|
flash(f'Job "{job.title}" added', 'job')
|
||||||
return make_response({'redirect_url': url_for('jobs.job', job_id=job.id)}, 201) # noqa
|
return make_response({'redirect_url': url_for('jobs.job', job_id=job.id)}, 201) # noqa
|
||||||
|
@ -1,21 +1,37 @@
|
|||||||
|
from app.auth import USERNAME_REGEX
|
||||||
|
from app.models import JobStatusMailNotificationLevel, User
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import (BooleanField, PasswordField, SelectField, StringField,
|
from wtforms import (
|
||||||
SubmitField, ValidationError)
|
BooleanField,
|
||||||
|
PasswordField,
|
||||||
|
SelectField,
|
||||||
|
StringField,
|
||||||
|
SubmitField,
|
||||||
|
ValidationError
|
||||||
|
)
|
||||||
from wtforms.validators import DataRequired, Email, EqualTo, Length, Regexp
|
from wtforms.validators import DataRequired, Email, EqualTo, Length, Regexp
|
||||||
from ..auth import USERNAME_REGEX
|
|
||||||
from ..models import User
|
|
||||||
|
|
||||||
|
|
||||||
class ChangePasswordForm(FlaskForm):
|
class ChangePasswordForm(FlaskForm):
|
||||||
password = PasswordField('Old password', validators=[DataRequired()])
|
password = PasswordField('Old password', validators=[DataRequired()])
|
||||||
new_password = PasswordField(
|
new_password = PasswordField(
|
||||||
'New password',
|
'New password',
|
||||||
validators=[DataRequired(), EqualTo('password_confirmation',
|
validators=[
|
||||||
message='Passwords must match.')]
|
DataRequired(),
|
||||||
|
EqualTo(
|
||||||
|
'new_password_confirmation',
|
||||||
|
message='Passwords must match'
|
||||||
|
)
|
||||||
|
]
|
||||||
)
|
)
|
||||||
new_password2 = PasswordField(
|
new_password_confirmation = PasswordField(
|
||||||
'Confirm new password', validators=[DataRequired()])
|
'Confirm new password',
|
||||||
submit = SubmitField('Change password')
|
validators=[
|
||||||
|
DataRequired(),
|
||||||
|
EqualTo('new_password', message='Passwords must match')
|
||||||
|
]
|
||||||
|
)
|
||||||
|
submit = SubmitField('Submit')
|
||||||
|
|
||||||
def __init__(self, user, *args, **kwargs):
|
def __init__(self, user, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@ -23,20 +39,24 @@ class ChangePasswordForm(FlaskForm):
|
|||||||
|
|
||||||
def validate_current_password(self, field):
|
def validate_current_password(self, field):
|
||||||
if not self.user.verify_password(field.data):
|
if not self.user.verify_password(field.data):
|
||||||
raise ValidationError('Invalid password.')
|
raise ValidationError('Invalid password')
|
||||||
|
|
||||||
|
|
||||||
class EditGeneralSettingsForm(FlaskForm):
|
class EditGeneralSettingsForm(FlaskForm):
|
||||||
dark_mode = BooleanField('Dark mode')
|
email = StringField(
|
||||||
email = StringField('E-Mail',
|
'E-Mail',
|
||||||
validators=[DataRequired(), Length(1, 254), Email()])
|
validators=[DataRequired(), Length(1, 254), Email()]
|
||||||
|
)
|
||||||
username = StringField(
|
username = StringField(
|
||||||
'Benutzername',
|
'Username',
|
||||||
validators=[DataRequired(),
|
validators=[
|
||||||
Length(1, 64),
|
DataRequired(),
|
||||||
Regexp(USERNAME_REGEX,
|
Length(1, 64),
|
||||||
message='Usernames must have only letters, numbers,'
|
Regexp(
|
||||||
' dots or underscores')]
|
USERNAME_REGEX,
|
||||||
|
message='Usernames must have only letters, numbers, dots or underscores' # noqa
|
||||||
|
)
|
||||||
|
]
|
||||||
)
|
)
|
||||||
submit = SubmitField('Submit')
|
submit = SubmitField('Submit')
|
||||||
|
|
||||||
@ -45,29 +65,36 @@ class EditGeneralSettingsForm(FlaskForm):
|
|||||||
self.user = user
|
self.user = user
|
||||||
|
|
||||||
def validate_email(self, field):
|
def validate_email(self, field):
|
||||||
if (field.data != self.user.email
|
if (
|
||||||
and User.query.filter_by(email=field.data).first()):
|
field.data != self.user.email
|
||||||
raise ValidationError('Email already registered.')
|
and User.query.filter_by(email=field.data).first()
|
||||||
|
):
|
||||||
|
raise ValidationError('Email already registered')
|
||||||
|
|
||||||
def validate_username(self, field):
|
def validate_username(self, field):
|
||||||
if (field.data != self.user.username
|
if (
|
||||||
and User.query.filter_by(username=field.data).first()):
|
field.data != self.user.username
|
||||||
raise ValidationError('Username already in use.')
|
and User.query.filter_by(username=field.data).first()
|
||||||
|
):
|
||||||
|
raise ValidationError('Username already in use')
|
||||||
|
|
||||||
|
|
||||||
|
class EditInterfaceSettingsForm(FlaskForm):
|
||||||
|
dark_mode = BooleanField('Dark mode')
|
||||||
|
submit = SubmitField('Submit')
|
||||||
|
|
||||||
|
|
||||||
class EditNotificationSettingsForm(FlaskForm):
|
class EditNotificationSettingsForm(FlaskForm):
|
||||||
job_status_mail_notifications = SelectField(
|
job_status_mail_notification_level = SelectField(
|
||||||
'Job status mail notifications',
|
'Job status mail notification level',
|
||||||
choices=[('', 'Choose your option'),
|
choices=[('', 'Choose your option')],
|
||||||
('all', 'Notify on all status changes'),
|
validators=[DataRequired()]
|
||||||
('end', 'Notify only when a job ended'),
|
)
|
||||||
('none', 'No status update notifications')],
|
submit = SubmitField('Submit')
|
||||||
validators=[DataRequired()])
|
|
||||||
job_status_site_notifications = SelectField(
|
def __init__(self, *args, **kwargs):
|
||||||
'Job status site notifications',
|
super().__init__(*args, **kwargs)
|
||||||
choices=[('', 'Choose your option'),
|
self.job_status_mail_notification_level.choices += [
|
||||||
('all', 'Notify on all status changes'),
|
(enum_member.name, enum_member.name.capitalize())
|
||||||
('end', 'Notify only when a job ended'),
|
for enum_member in JobStatusMailNotificationLevel
|
||||||
('none', 'No status update notifications')],
|
]
|
||||||
validators=[DataRequired()])
|
|
||||||
submit = SubmitField('Save settings')
|
|
||||||
|
@ -1,66 +1,82 @@
|
|||||||
from flask import flash, redirect, render_template, url_for
|
from flask import flash, redirect, render_template, url_for
|
||||||
from flask_login import current_user, login_required, logout_user
|
from flask_login import current_user, login_required, logout_user
|
||||||
from . import bp, tasks
|
from . import bp, tasks
|
||||||
from .forms import (ChangePasswordForm, EditGeneralSettingsForm,
|
from .forms import (
|
||||||
EditNotificationSettingsForm)
|
ChangePasswordForm,
|
||||||
|
EditGeneralSettingsForm,
|
||||||
|
EditInterfaceSettingsForm,
|
||||||
|
EditNotificationSettingsForm
|
||||||
|
)
|
||||||
from .. import db
|
from .. import db
|
||||||
|
from ..models import JobStatusMailNotificationLevel
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/')
|
@bp.route('', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def index():
|
def index():
|
||||||
return redirect(url_for('.edit_general_settings'))
|
change_password_form = ChangePasswordForm(
|
||||||
|
current_user._get_current_object(),
|
||||||
|
prefix='change_password_form'
|
||||||
|
)
|
||||||
|
edit_general_settings_form = EditGeneralSettingsForm(
|
||||||
|
current_user._get_current_object(),
|
||||||
|
prefix='edit_general_settings_form'
|
||||||
|
)
|
||||||
|
edit_interface_settings_form = EditInterfaceSettingsForm(
|
||||||
|
prefix='edit_interface_settings_form'
|
||||||
|
)
|
||||||
|
edit_notification_settings_form = EditNotificationSettingsForm(
|
||||||
|
prefix='edit_notification_settings_form'
|
||||||
|
)
|
||||||
|
|
||||||
|
if change_password_form.submit.data and change_password_form.validate():
|
||||||
@bp.route('/change_password', methods=['GET', 'POST'])
|
current_user.password = change_password_form.new_password.data
|
||||||
@login_required
|
|
||||||
def change_password():
|
|
||||||
form = ChangePasswordForm(current_user._get_current_object())
|
|
||||||
if form.validate_on_submit():
|
|
||||||
current_user.password = form.new_password.data
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash('Your password has been updated.')
|
flash('Your changes have been saved')
|
||||||
return redirect(url_for('.change_password'))
|
return redirect(url_for('.index'))
|
||||||
return render_template('settings/change_password.html.j2',
|
if (
|
||||||
form=form, title='Change password')
|
edit_general_settings_form.submit.data
|
||||||
|
and edit_general_settings_form.validate()
|
||||||
|
):
|
||||||
@bp.route('/edit_general_settings', methods=['GET', 'POST'])
|
current_user.email = edit_general_settings_form.email.data
|
||||||
@login_required
|
current_user.username = edit_general_settings_form.username.data
|
||||||
def edit_general_settings():
|
|
||||||
form = EditGeneralSettingsForm(current_user._get_current_object())
|
|
||||||
if form.validate_on_submit():
|
|
||||||
current_user.email = form.email.data
|
|
||||||
current_user.setting_dark_mode = form.dark_mode.data
|
|
||||||
current_user.username = form.username.data
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash('Your changes have been saved.')
|
flash('Your changes have been saved')
|
||||||
return redirect(url_for('.edit_general_settings'))
|
return redirect(url_for('.index'))
|
||||||
form.dark_mode.data = current_user.setting_dark_mode
|
if (
|
||||||
form.email.data = current_user.email
|
edit_interface_settings_form.submit.data
|
||||||
form.username.data = current_user.username
|
and edit_interface_settings_form.validate()
|
||||||
return render_template('settings/edit_general_settings.html.j2',
|
):
|
||||||
form=form, title='General settings')
|
current_user.setting_dark_mode = \
|
||||||
|
edit_interface_settings_form.dark_mode.data
|
||||||
|
|
||||||
@bp.route('/edit_notification_settings', methods=['GET', 'POST'])
|
|
||||||
@login_required
|
|
||||||
def edit_notification_settings():
|
|
||||||
form = EditNotificationSettingsForm()
|
|
||||||
if form.validate_on_submit():
|
|
||||||
current_user.setting_job_status_mail_notifications = \
|
|
||||||
form.job_status_mail_notifications.data
|
|
||||||
current_user.setting_job_status_site_notifications = \
|
|
||||||
form.job_status_site_notifications.data
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash('Your changes have been saved.')
|
flash('Your changes have been saved')
|
||||||
return redirect(url_for('.edit_notification_settings'))
|
return redirect(url_for('.index'))
|
||||||
form.job_status_mail_notifications.data = \
|
if (
|
||||||
current_user.setting_job_status_mail_notifications
|
edit_notification_settings_form.submit.data
|
||||||
form.job_status_site_notifications.data = \
|
and edit_notification_settings_form.validate()
|
||||||
current_user.setting_job_status_site_notifications
|
):
|
||||||
return render_template('settings/edit_notification_settings.html.j2',
|
current_user.setting_job_status_mail_notification_level = \
|
||||||
form=form, title='Notification settings')
|
JobStatusMailNotificationLevel[
|
||||||
|
edit_notification_settings_form.job_status_mail_notification_level.data # noqa
|
||||||
|
]
|
||||||
|
db.session.commit()
|
||||||
|
flash('Your changes have been saved')
|
||||||
|
return redirect(url_for('.index'))
|
||||||
|
edit_general_settings_form.email.data = current_user.email
|
||||||
|
edit_general_settings_form.username.data = current_user.username
|
||||||
|
edit_interface_settings_form.dark_mode.data = \
|
||||||
|
current_user.setting_dark_mode
|
||||||
|
edit_notification_settings_form.job_status_mail_notification_level.data = \
|
||||||
|
current_user.setting_job_status_mail_notification_level.name
|
||||||
|
return render_template(
|
||||||
|
'settings/index.html.j2',
|
||||||
|
change_password_form=change_password_form,
|
||||||
|
edit_general_settings_form=edit_general_settings_form,
|
||||||
|
edit_interface_settings_form=edit_interface_settings_form,
|
||||||
|
edit_notification_settings_form=edit_notification_settings_form,
|
||||||
|
title='Settings'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/delete')
|
@bp.route('/delete')
|
||||||
@ -71,5 +87,5 @@ def delete():
|
|||||||
"""
|
"""
|
||||||
tasks.delete_user(current_user.id)
|
tasks.delete_user(current_user.id)
|
||||||
logout_user()
|
logout_user()
|
||||||
flash('Your account has been marked for deletion!')
|
flash('Your account has been marked for deletion')
|
||||||
return redirect(url_for('main.index'))
|
return redirect(url_for('main.index'))
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from .. import db
|
from app import db
|
||||||
from ..decorators import background
|
from app.decorators import background
|
||||||
from ..models import User
|
from app.models import User
|
||||||
|
|
||||||
|
|
||||||
@background
|
@background
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
:root {
|
||||||
|
--main-bg-color: brown;
|
||||||
|
}
|
||||||
|
|
||||||
/* Change navbar height bacause an extended and fixed navbar is used */
|
/* Change navbar height bacause an extended and fixed navbar is used */
|
||||||
.navbar-fixed {
|
.navbar-fixed {
|
||||||
height: 112px;
|
height: 112px;
|
||||||
@ -12,15 +16,6 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* preloader circle in the size of a button icon */
|
|
||||||
.button-icon-spinner {
|
|
||||||
bottom: -5px !important;
|
|
||||||
right: 55px !important;
|
|
||||||
margin-right: 12px !important;
|
|
||||||
width: 19.5px !important;
|
|
||||||
height: 19.5px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* changes preoloader size etc. to fit visually better with the chip status
|
* changes preoloader size etc. to fit visually better with the chip status
|
||||||
* indicator of jobs
|
* indicator of jobs
|
||||||
@ -39,36 +34,37 @@
|
|||||||
transform: scale(2);
|
transform: scale(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-scale-x2 .nopaque-icons.service-icon {
|
.btn-scale-x2 .nopaque-icon.nopaque-service-icon {
|
||||||
font-size: 2.5rem;
|
font-size: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fix material icon vertical alignment when nested in various elements */
|
/* Fix material icon vertical alignment when nested in various elements */
|
||||||
h1 .nopaque-icons, h2 .nopaque-icons, h3 .nopaque-icons, h4 .nopaque-icons,
|
h1 .nopaque-icon, h2 .nopaque-icon, h3 .nopaque-icon, h4 .nopaque-icon, .tab .nopaque-icon, .tab .material-icons {
|
||||||
.tab .nopaque-icons, .tab .material-icons {
|
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
}
|
}
|
||||||
.nopaque-icons.service-icon[data-service="corpus-analysis"]:empty:before {content: "H";}
|
|
||||||
.nopaque-icons.service-icon[data-service="file-setup"]:empty:before {content: "E";}
|
|
||||||
.nopaque-icons.service-icon[data-service="spacy-nlp"]:empty:before {content: "G";}
|
|
||||||
.nopaque-icons.service-icon[data-service="tesseract-ocr"]:empty:before {content: "F";}
|
|
||||||
|
|
||||||
.status-text[data-status]:empty:before {content: attr(data-status);}
|
.nopaque-icon.nopaque-service-icon[data-service="file-setup"]:empty:before {content: "E";}
|
||||||
|
.nopaque-icon.nopaque-service-icon[data-service="tesseract-ocr"]:empty:before {content: "F";}
|
||||||
|
.nopaque-icon.nopaque-service-icon[data-service="spacy-nlp"]:empty:before {content: "G";}
|
||||||
|
.nopaque-icon.nopaque-service-icon[data-service="corpus-analysis"]:empty:before {content: "H";}
|
||||||
|
|
||||||
|
.nopaque-corpus-status-text[data-corpus-status="UNPREPARED"]:empty:before {content: "Unprepared";}
|
||||||
|
.nopaque-corpus-status-text[data-corpus-status="SUBMITTED"]:empty:before {content: "Submitted";}
|
||||||
|
.nopaque-corpus-status-text[data-corpus-status="QUEUED"]:empty:before {content: "Queued";}
|
||||||
|
.nopaque-corpus-status-text[data-corpus-status="BUILDING"]:empty:before {content: "Building";}
|
||||||
|
.nopaque-corpus-status-text[data-corpus-status="BUILT"]:empty:before {content: "Built";}
|
||||||
|
.nopaque-corpus-status-text[data-corpus-status="STARTING_ANALYSIS_SESSION"]:empty:before {content: "Starting analysis session";}
|
||||||
|
.nopaque-corpus-status-text[data-corpus-status="RUNNING_ANALYSIS_SESSION"]:empty:before {content: "Running analysis session";}
|
||||||
|
.nopaque-corpus-status-text[data-corpus-status="CANCELING_ANALYSIS_SESSION"]:empty:before {content: "Canceling analysis session";}
|
||||||
|
|
||||||
|
.nopaque-job-status-text[data-job-status="INITIALIZING"]:empty:before {content: "Initializing";}
|
||||||
|
.nopaque-job-status-text[data-job-status="SUBMITTED"]:empty:before {content: "Submitted";}
|
||||||
|
.nopaque-job-status-text[data-job-status="QUEUED"]:empty:before {content: "Queued";}
|
||||||
|
.nopaque-job-status-text[data-job-status="RUNNING"]:empty:before {content: "Running";}
|
||||||
|
.nopaque-job-status-text[data-job-status="CANCELING"]:empty:before {content: "Canceling";}
|
||||||
|
.nopaque-job-status-text[data-job-status="CANCELED"]:empty:before {content: "Canceled";}
|
||||||
|
.nopaque-job-status-text[data-job-status="COMPLETED"]:empty:before {content: "Completed";}
|
||||||
|
.nopaque-job-status-text[data-job-status="FAILED"]:empty:before {content: "Failed";}
|
||||||
|
|
||||||
.hoverable {cursor: pointer;}
|
.hoverable {cursor: pointer;}
|
||||||
.s-attr.chip .p-attr.chip {background-color: inherit;}
|
.chip.s-attr .chip.p-attr {background-color: inherit;}
|
||||||
|
|
||||||
|
|
||||||
.responsive-youtube-video-container {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 0;
|
|
||||||
padding-bottom: 56.25%;
|
|
||||||
}
|
|
||||||
.responsive-youtube-video {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'nopaque Icons';
|
font-family: 'Nopaque Icons';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: local('nopaque Icons'),
|
src: local('nopaque Icons'),
|
||||||
local('nopaqueIcons-Regular'),
|
local('NopaqueIcons-Regular'),
|
||||||
url(../fonts/nopaque_icons/nopaqueIcons-Regular.otf) format('opentype');
|
url(../fonts/nopaque_icons/NopaqueIcons-Regular.otf) format('opentype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.nopaque-icons {
|
.nopaque-icon {
|
||||||
font-family: 'nopaque Icons';
|
font-family: 'Nopaque Icons';
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-size: 24px; /* Preferred icon size */
|
font-size: 24px; /* Preferred icon size */
|
||||||
|
@ -31,7 +31,7 @@ class App {
|
|||||||
iconPrefix = '<i class="error-color-text left material-icons">error</i>';
|
iconPrefix = '<i class="error-color-text left material-icons">error</i>';
|
||||||
break;
|
break;
|
||||||
case 'job':
|
case 'job':
|
||||||
iconPrefix = '<i class="left nopaque-icons">J</i>';
|
iconPrefix = '<i class="left nopaque-icon">J</i>';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
iconPrefix = '<i class="left material-icons">notifications</i>';
|
iconPrefix = '<i class="left material-icons">notifications</i>';
|
||||||
|
@ -16,7 +16,7 @@ class JobStatusNotifier {
|
|||||||
.filter(operation => re.test(operation.path));
|
.filter(operation => re.test(operation.path));
|
||||||
for (operation of filteredPatch) {
|
for (operation of filteredPatch) {
|
||||||
[match, jobId] = operation.path.match(re);
|
[match, jobId] = operation.path.match(re);
|
||||||
app.flash(`[<a href="/jobs/${jobId}">${app.users[this.userId].jobs[jobId].title}</a>] New status: ${operation.value}`, 'job');
|
app.flash(`[<a href="/jobs/${jobId}">${app.users[this.userId].jobs[jobId].title}</a>] New status: <span class="nopaque-job-status-text" data-job-status="${operation.value}"></span>`, 'job');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,30 +67,29 @@ class CorpusDisplay extends RessourceDisplay {
|
|||||||
let element;
|
let element;
|
||||||
let elements;
|
let elements;
|
||||||
|
|
||||||
this.setElements(this.displayElement.querySelectorAll('.corpus-status'), status);
|
elements = this.displayElement.querySelectorAll('.corpus-analyse-trigger')
|
||||||
elements = this.displayElement.querySelectorAll('.analyse-corpus-trigger')
|
|
||||||
for (element of elements) {
|
for (element of elements) {
|
||||||
if (['analysing', 'prepared', 'start analysis'].includes(status)) {
|
if (['BUILT', 'STARTING_ANALYSIS_SESSION', 'RUNNING_ANALYSIS_SESSION', 'CANCELING_ANALYSIS_SESSION'].includes(status)) {
|
||||||
element.classList.remove('disabled');
|
element.classList.remove('disabled');
|
||||||
} else {
|
} else {
|
||||||
element.classList.add('disabled');
|
element.classList.add('disabled');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
elements = this.displayElement.querySelectorAll('.build-corpus-trigger');
|
elements = this.displayElement.querySelectorAll('.corpus-build-trigger');
|
||||||
for (element of elements) {
|
for (element of elements) {
|
||||||
if (status === 'unprepared' && Object.values(app.users[this.userId].corpora[this.corpusId].files).length > 0) {
|
if (status === 'UNPREPARED' && Object.values(app.users[this.userId].corpora[this.corpusId].files).length > 0) {
|
||||||
element.classList.remove('disabled');
|
element.classList.remove('disabled');
|
||||||
} else {
|
} else {
|
||||||
element.classList.add('disabled');
|
element.classList.add('disabled');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
elements = this.displayElement.querySelectorAll('.status');
|
elements = this.displayElement.querySelectorAll('.corpus-status');
|
||||||
for (element of elements) {
|
for (element of elements) {
|
||||||
element.dataset.status = status;
|
element.dataset.corpusStatus = status;
|
||||||
}
|
}
|
||||||
elements = this.displayElement.querySelectorAll('.status-spinner');
|
elements = this.displayElement.querySelectorAll('.corpus-status-spinner');
|
||||||
for (element of elements) {
|
for (element of elements) {
|
||||||
if (['submitted', 'queued', 'running', 'canceling', 'start analysis', 'stop analysis'].includes(status)) {
|
if (['SUBMITTED', 'QUEUED', 'BUILDING', 'STARTING_ANALYSIS_SESSION', 'CANCELING_ANALYSIS_SESSION'].includes(status)) {
|
||||||
element.classList.remove('hide');
|
element.classList.remove('hide');
|
||||||
} else {
|
} else {
|
||||||
element.classList.add('hide');
|
element.classList.add('hide');
|
||||||
|
@ -57,23 +57,21 @@ class JobDisplay extends RessourceDisplay {
|
|||||||
let element;
|
let element;
|
||||||
let elements;
|
let elements;
|
||||||
|
|
||||||
this.setElements(this.displayElement.querySelectorAll('.job-status'), status);
|
elements = this.displayElement.querySelectorAll('.job-status');
|
||||||
|
|
||||||
elements = this.displayElement.querySelectorAll('.status');
|
|
||||||
for (element of elements) {
|
for (element of elements) {
|
||||||
element.dataset.status = status;
|
element.dataset.jobStatus = status;
|
||||||
}
|
}
|
||||||
elements = this.displayElement.querySelectorAll('.status-spinner');
|
elements = this.displayElement.querySelectorAll('.job-status-spinner');
|
||||||
for (element of elements) {
|
for (element of elements) {
|
||||||
if (['complete', 'failed'].includes(status)) {
|
if (['COMPLETED', 'FAILED'].includes(status)) {
|
||||||
element.classList.add('hide');
|
element.classList.add('hide');
|
||||||
} else {
|
} else {
|
||||||
element.classList.remove('hide');
|
element.classList.remove('hide');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
elements = this.displayElement.querySelectorAll('.restart-job-trigger');
|
elements = this.displayElement.querySelectorAll('.job-restart-trigger');
|
||||||
for (element of elements) {
|
for (element of elements) {
|
||||||
if (['complete', 'failed'].includes(status)) {
|
if (['COMPLETED', 'FAILED'].includes(status)) {
|
||||||
element.classList.remove('hide');
|
element.classList.remove('hide');
|
||||||
} else {
|
} else {
|
||||||
element.classList.add('hide');
|
element.classList.add('hide');
|
||||||
|
@ -5,31 +5,31 @@ class CorpusFileList extends RessourceList {
|
|||||||
<td><span class="filename"></span></td>
|
<td><span class="filename"></span></td>
|
||||||
<td><span class="author"></span></td>
|
<td><span class="author"></span></td>
|
||||||
<td><span class="title"></span></td>
|
<td><span class="title"></span></td>
|
||||||
<td><span class="publishing_year"></span></td>
|
<td><span class="publishing-year"></span></td>
|
||||||
<td class="right-align">
|
<td class="right-align">
|
||||||
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
|
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
|
||||||
<a class="action-button btn-floating service-color darken tooltipped waves-effect waves-light" data-action="download" data-position="top" data-service="corpus-analysis" data-tooltip="View"><i class="material-icons">file_download</i></a>
|
<a class="action-button btn-floating tooltipped nopaque-service-color darken waves-effect waves-light" data-action="download" data-position="top" data-service="corpus-analysis" data-tooltip="View"><i class="material-icons">file_download</i></a>
|
||||||
<a class="action-button btn-floating service-color darken tooltipped waves-effect waves-light" data-action="view" data-position="top" data-service="corpus-analysis" data-tooltip="View"><i class="material-icons">send</i></a>
|
<a class="action-button btn-floating tooltipped nopaque-service-color darken waves-effect waves-light" data-action="view" data-position="top" data-service="corpus-analysis" data-tooltip="View"><i class="material-icons">send</i></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`.trim(),
|
`.trim(),
|
||||||
ressourceMapper: corpusFile => {
|
ressourceMapper: corpusFile => {
|
||||||
return {
|
return {
|
||||||
id: corpusFile.id,
|
'id': corpusFile.id,
|
||||||
author: corpusFile.author,
|
'author': corpusFile.author,
|
||||||
creationDate: corpusFile.creation_date,
|
'creation-date': corpusFile.creation_date,
|
||||||
filename: corpusFile.filename,
|
'filename': corpusFile.filename,
|
||||||
publishingYear: corpusFile.publishing_year,
|
'publishing-year': corpusFile.publishing_year,
|
||||||
title: corpusFile.title
|
'title': corpusFile.title
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
sortValueName: 'creationDate',
|
sortValueName: 'creation-date',
|
||||||
valueNames: [
|
valueNames: [
|
||||||
{data: ['id']},
|
{data: ['id']},
|
||||||
{data: ['creationDate']},
|
{data: ['creation-date']},
|
||||||
'author',
|
'author',
|
||||||
'filename',
|
'filename',
|
||||||
'publishingYear',
|
'publishing-year',
|
||||||
'title'
|
'title'
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@ -125,7 +125,7 @@ class CorpusFileList extends RessourceList {
|
|||||||
re = new RegExp(`^/users/${this.userId}/corpora/${this.corpusId}/files/([A-Za-z0-9]*)/(author|filename|publishing_year|title)$`);
|
re = new RegExp(`^/users/${this.userId}/corpora/${this.corpusId}/files/([A-Za-z0-9]*)/(author|filename|publishing_year|title)$`);
|
||||||
if (re.test(operation.path)) {
|
if (re.test(operation.path)) {
|
||||||
[match, corpusFileId, valueName] = operation.path.match(re);
|
[match, corpusFileId, valueName] = operation.path.match(re);
|
||||||
this.replace(corpusFileId, valueName, operation.value);
|
this.replace(corpusFileId, valueName.replace('_', '-'), operation.value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -2,29 +2,29 @@ class CorpusList extends RessourceList {
|
|||||||
static options = {
|
static options = {
|
||||||
item: `
|
item: `
|
||||||
<tr class="hoverable">
|
<tr class="hoverable">
|
||||||
<td><a class="btn-floating disabled"><i class="material-icons service-color darken" data-service="corpus-analysis">book</i></a></td>
|
<td><a class="btn-floating disabled"><i class="material-icons nopaque-service-color darken" data-service="corpus-analysis">book</i></a></td>
|
||||||
<td><b class="title"></b><br><i class="description"></i></td>
|
<td><b class="title"></b><br><i class="description"></i></td>
|
||||||
<td><span class="badge new status status-color status-text" data-badge-caption=""></span></td>
|
<td><span class="status badge new nopaque-corpus-status-color nopaque-corpus-status-text" data-badge-caption=""></span></td>
|
||||||
<td class="right-align">
|
<td class="right-align">
|
||||||
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
|
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
|
||||||
<a class="action-button btn-floating service-color darken tooltipped waves-effect waves-light" data-action="view" data-position="top" data-service="corpus-analysis" data-tooltip="View"><i class="material-icons">send</i></a>
|
<a class="action-button btn-floating nopaque-service-color darken tooltipped waves-effect waves-light" data-action="view" data-position="top" data-service="corpus-analysis" data-tooltip="View"><i class="material-icons">send</i></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`.trim(),
|
`.trim(),
|
||||||
ressourceMapper: corpus => {
|
ressourceMapper: corpus => {
|
||||||
return {
|
return {
|
||||||
id: corpus.id,
|
'id': corpus.id,
|
||||||
creationDate: corpus.creation_date,
|
'creation-date': corpus.creation_date,
|
||||||
description: corpus.description,
|
'description': corpus.description,
|
||||||
status: corpus.status,
|
'status': corpus.status,
|
||||||
title: corpus.title
|
'title': corpus.title
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
sortValueName: 'creationDate',
|
sortValueName: 'creation-date',
|
||||||
valueNames: [
|
valueNames: [
|
||||||
{data: ['id']},
|
{data: ['id']},
|
||||||
{data: ['creationDate']},
|
{data: ['creation-date']},
|
||||||
{name: 'status', attr: 'data-status'},
|
{name: 'status', attr: 'data-corpus-status'},
|
||||||
'description',
|
'description',
|
||||||
'title'
|
'title'
|
||||||
]
|
]
|
||||||
|
@ -10,13 +10,17 @@ class JobInputList extends RessourceList {
|
|||||||
`.trim(),
|
`.trim(),
|
||||||
ressourceMapper: jobInput => {
|
ressourceMapper: jobInput => {
|
||||||
return {
|
return {
|
||||||
id: jobInput.id,
|
'id': jobInput.id,
|
||||||
creationDate: jobInput.creation_date,
|
'creation-date': jobInput.creation_date,
|
||||||
filename: jobInput.filename
|
'filename': jobInput.filename
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
sortValueName: 'creationDate',
|
sortValueName: 'creation-date',
|
||||||
valueNames: [{data: ['id']}, {data: ['creationDate']}, 'filename']
|
valueNames: [
|
||||||
|
{data: ['id']},
|
||||||
|
{data: ['creation-date']},
|
||||||
|
'filename'
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,36 +1,36 @@
|
|||||||
class JobList extends RessourceList {
|
class JobList extends RessourceList {
|
||||||
static options = {
|
static options = {
|
||||||
item: `
|
item: `
|
||||||
<tr class="hoverable service-color lighten">
|
<tr class="hoverable nopaque-service-color lighten">
|
||||||
<td><a class="btn-floating disabled"><i class="nopaque-icons service-color darken serviceDuplicate1 service-icon"></i></a></td>
|
<td><a class="btn-floating disabled"><i class="service-1 nopaque-icon nopaque-service-color darken nopaque-service-icon"></i></a></td>
|
||||||
<td><b class="title"></b><br><i class="description"></i></td>
|
<td><b class="title"></b><br><i class="description"></i></td>
|
||||||
<td><span class="badge new status status-color status-text" data-badge-caption=""></span></td>
|
<td><span class="status badge new nopaque-job-status-color nopaque-job-status-text" data-badge-caption=""></span></td>
|
||||||
<td class="right-align">
|
<td class="right-align">
|
||||||
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
|
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
|
||||||
<a class="action-button btn-floating serviceDuplicate2 service-color darken tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
|
<a class="service-2 action-button btn-floating nopaque-service-color darken tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`.trim(),
|
`.trim(),
|
||||||
ressourceMapper: job => {
|
ressourceMapper: job => {
|
||||||
return {
|
return {
|
||||||
id: job.id,
|
'id': job.id,
|
||||||
creationDate: job.creation_date,
|
'creation-date': job.creation_date,
|
||||||
description: job.description,
|
'description': job.description,
|
||||||
service: job.service,
|
'service': job.service,
|
||||||
serviceDuplicate1: job.service,
|
'service-1': job.service,
|
||||||
serviceDuplicate2: job.service,
|
'service-2': job.service,
|
||||||
status: job.status,
|
'status': job.status,
|
||||||
title: job.title
|
'title': job.title
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
sortValueName: 'creationDate',
|
sortValueName: 'creation-date',
|
||||||
valueNames: [
|
valueNames: [
|
||||||
{data: ['id']},
|
{data: ['id']},
|
||||||
{data: ['creationDate']},
|
{data: ['creation-date']},
|
||||||
{data: ['service']},
|
{data: ['service']},
|
||||||
{name: 'serviceDuplicate1', attr: 'data-service'},
|
{name: 'service-1', attr: 'data-service'},
|
||||||
{name: 'serviceDuplicate2', attr: 'data-service'},
|
{name: 'service-2', attr: 'data-service'},
|
||||||
{name: 'status', attr: 'data-status'},
|
{name: 'status', attr: 'data-job-status'},
|
||||||
'description',
|
'description',
|
||||||
'title'
|
'title'
|
||||||
]
|
]
|
||||||
|
@ -11,16 +11,16 @@ class JobResultList extends RessourceList {
|
|||||||
`.trim(),
|
`.trim(),
|
||||||
ressourceMapper: jobResult => {
|
ressourceMapper: jobResult => {
|
||||||
return {
|
return {
|
||||||
id: jobResult.id,
|
'id': jobResult.id,
|
||||||
creationDate: jobResult.creation_date,
|
'creation-date': jobResult.creation_date,
|
||||||
description: jobResult.description,
|
'description': jobResult.description,
|
||||||
filename: jobResult.filename
|
'filename': jobResult.filename
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
sortValueName: 'creationDate',
|
sortValueName: 'creation-date',
|
||||||
valueNames: [
|
valueNames: [
|
||||||
{data: ['id']},
|
{data: ['id']},
|
||||||
{data: ['creationDate']},
|
{data: ['creation-date']},
|
||||||
'description',
|
'description',
|
||||||
'filename'
|
'filename'
|
||||||
]
|
]
|
||||||
|
@ -3,7 +3,7 @@ class QueryResultList extends RessourceList {
|
|||||||
item: `
|
item: `
|
||||||
<tr class="hoverable">
|
<tr class="hoverable">
|
||||||
<td><b class="title"></b><br><i class="description"></i><br></td>
|
<td><b class="title"></b><br><i class="description"></i><br></td>
|
||||||
<td><span class="corpus_title"></span><br><span class="query"></span></td>
|
<td><span class="corpus-title"></span><br><span class="query"></span></td>
|
||||||
<td class="right-align">
|
<td class="right-align">
|
||||||
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
|
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
|
||||||
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
|
<a class="action-button btn-floating tooltipped waves-effect waves-light" data-action="view" data-position="top" data-tooltip="View"><i class="material-icons">send</i></a>
|
||||||
@ -12,19 +12,19 @@ class QueryResultList extends RessourceList {
|
|||||||
`.trim(),
|
`.trim(),
|
||||||
ressourceMapper: queryResult => {
|
ressourceMapper: queryResult => {
|
||||||
return {
|
return {
|
||||||
id: queryResult.id,
|
'id': queryResult.id,
|
||||||
corpusTitle: queryResult.corpus_title,
|
'corpus-title': queryResult.corpus_title,
|
||||||
creationDate: queryResult.creation_date,
|
'creation-date': queryResult.creation_date,
|
||||||
description: queryResult.description,
|
'description': queryResult.description,
|
||||||
query: queryResult.query,
|
'query': queryResult.query,
|
||||||
title: queryResult.title
|
'title': queryResult.title
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
sortValueName: 'creationDate',
|
sortValueName: 'creation-date',
|
||||||
valueNames: [
|
valueNames: [
|
||||||
{data: ['id']},
|
{data: ['id']},
|
||||||
{data: ['creationDate']},
|
{data: ['creation-date']},
|
||||||
'corpusTitle',
|
'corpus-title',
|
||||||
'description',
|
'description',
|
||||||
'query',
|
'query',
|
||||||
'title'
|
'title'
|
||||||
@ -118,7 +118,7 @@ class QueryResultList extends RessourceList {
|
|||||||
re = new RegExp(`^/users/${this.userId}/query_results/([A-Za-z0-9]*)/(corpus_title|description|query|title)$`);
|
re = new RegExp(`^/users/${this.userId}/query_results/([A-Za-z0-9]*)/(corpus_title|description|query|title)$`);
|
||||||
if (re.test(operation.path)) {
|
if (re.test(operation.path)) {
|
||||||
[match, queryResultId, valueName] = operation.path.match(re);
|
[match, queryResultId, valueName] = operation.path.match(re);
|
||||||
this.replace(queryResultId, valueName, operation.value);
|
this.replace(queryResultId, valueName.replace('_', '-'), operation.value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -2,10 +2,10 @@ class UserList extends RessourceList {
|
|||||||
static options = {
|
static options = {
|
||||||
item: `
|
item: `
|
||||||
<tr class="hoverable">
|
<tr class="hoverable">
|
||||||
<td><span class="idDuplicate"></span></td>
|
<td><span class="id-1"></span></td>
|
||||||
<td><span class="username"></span></td>
|
<td><span class="username"></span></td>
|
||||||
<td><span class="email"></span></td>
|
<td><span class="email"></span></td>
|
||||||
<td><span class="last_seen"></span></td>
|
<td><span class="last-seen"></span></td>
|
||||||
<td><span class="role"></span></td>
|
<td><span class="role"></span></td>
|
||||||
<td class="right-align">
|
<td class="right-align">
|
||||||
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
|
<a class="action-button btn-floating red tooltipped waves-effect waves-light" data-action="delete" data-position="top" data-tooltip="Delete"><i class="material-icons">delete</i></a>
|
||||||
@ -16,21 +16,22 @@ class UserList extends RessourceList {
|
|||||||
`.trim(),
|
`.trim(),
|
||||||
ressourceMapper: user => {
|
ressourceMapper: user => {
|
||||||
return {
|
return {
|
||||||
id: user.id,
|
'id': user.id,
|
||||||
idDuplicate: user.id,
|
'id-1': user.id,
|
||||||
username: user.username,
|
'username': user.username,
|
||||||
email: user.email,
|
'email': user.email,
|
||||||
last_seen: new Date(user.last_seen).toLocaleString("en-US"),
|
'last-seen': new Date(user.last_seen).toLocaleString("en-US"),
|
||||||
role: user.role.name
|
'member-since': user.member_since,
|
||||||
|
'role': user.role.name
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
sortValueName: 'memberSince',
|
sortValueName: 'member-since',
|
||||||
valueNames: [
|
valueNames: [
|
||||||
{data: ['id']},
|
{data: ['id']},
|
||||||
{data: ['memberSince']},
|
{data: ['member-since']},
|
||||||
'email',
|
'email',
|
||||||
'idDuplicate',
|
'id-1',
|
||||||
'last_seen',
|
'last-seen',
|
||||||
'role',
|
'role',
|
||||||
'username'
|
'username'
|
||||||
]
|
]
|
||||||
|
@ -32,14 +32,24 @@
|
|||||||
} %}
|
} %}
|
||||||
|
|
||||||
{% set status = {
|
{% set status = {
|
||||||
'unprepared': '#9e9e9e',
|
'corpus': {
|
||||||
'submitted': '#9e9e9e',
|
'UNPREPARED': '#9e9e9e',
|
||||||
'queued': '#2196f3',
|
'QUEUED': '#2196f3',
|
||||||
'running': '#ffc107',
|
'BUILDING': '#ffc107',
|
||||||
'complete': '#4caf50',
|
'BUILT': '#4caf50',
|
||||||
'failed': '#f44336',
|
'FAILED': '#f44336',
|
||||||
'prepared': '#4caf50',
|
'STARTING_ANALYSIS_SESSION': '#2196f3',
|
||||||
'start analysis': '#2196f3',
|
'RUNNING_ANALYSIS_SESSION': '#4caf50',
|
||||||
'analysing': '#4caf50',
|
'CANCELING_ANALYSIS_SESSION': '#ff5722'
|
||||||
'stop analysis': '#ff5722'
|
},
|
||||||
|
'job': {
|
||||||
|
'INITIALIZING': '#9e9e9e',
|
||||||
|
'SUBMITTED': '#9e9e9e',
|
||||||
|
'QUEUED': '#2196f3',
|
||||||
|
'RUNNING': '#ffc107',
|
||||||
|
'CANCELING': '#ff5722',
|
||||||
|
'CANCELED': '#ff5722',
|
||||||
|
'COMPLETED': '#4caf50',
|
||||||
|
'FAILED': '#f44336'
|
||||||
|
}
|
||||||
} %}
|
} %}
|
||||||
|
@ -10,14 +10,14 @@
|
|||||||
<li><a href="{{ url_for('main.news') }}"><i class="material-icons left">email</i>News</a></li>
|
<li><a href="{{ url_for('main.news') }}"><i class="material-icons left">email</i>News</a></li>
|
||||||
<li><a href="#"><i class="material-icons">linear_scale</i>Workflow</a></li>
|
<li><a href="#"><i class="material-icons">linear_scale</i>Workflow</a></li>
|
||||||
<li><a href="{{ url_for('main.dashboard') }}"><i class="material-icons">dashboard</i>Dashboard</a></li>
|
<li><a href="{{ url_for('main.dashboard') }}"><i class="material-icons">dashboard</i>Dashboard</a></li>
|
||||||
<li><a href="{{ url_for('main.dashboard', _anchor='corpora') }}" style="padding-left: 47px;"><i class="nopaque-icons">I</i>My Corpora</a></li>
|
<li><a href="{{ url_for('main.dashboard', _anchor='corpora') }}" style="padding-left: 47px;"><i class="nopaque-icon">I</i>My Corpora</a></li>
|
||||||
<li><a href="{{ url_for('main.dashboard', _anchor='jobs') }}" style="padding-left: 47px;"><i class="nopaque-icons">J</i>My Jobs</a></li>
|
<li><a href="{{ url_for('main.dashboard', _anchor='jobs') }}" style="padding-left: 47px;"><i class="nopaque-icon">J</i>My Jobs</a></li>
|
||||||
<li><div class="divider"></div></li>
|
<li><div class="divider"></div></li>
|
||||||
<li><a class="subheader">Processes & Services</a></li>
|
<li><a class="subheader">Processes & Services</a></li>
|
||||||
<li class="service-color service-color-border border-darken" data-service="file-setup" style="border-left: 10px solid; margin-top: 5px;"><a href="{{ url_for('services.service', service='file-setup') }}"><i class="nopaque-icons service-icon" data-service="file-setup"></i>File setup</a></li>
|
<li class="nopaque-service-color nopaque-service-color-border border-darken" data-service="file-setup" style="border-left: 10px solid; margin-top: 5px;"><a href="{{ url_for('services.service', service='file-setup') }}"><i class="nopaque-icon nopaque-service-icon" data-service="file-setup"></i>File setup</a></li>
|
||||||
<li class="service-color service-color-border border-darken" data-service="tesseract-ocr" style="border-left: 10px solid; margin-top: 5px;"><a href="{{ url_for('services.service', service='tesseract-ocr') }}"><i class="nopaque-icons service-icon" data-service="tesseract-ocr"></i>OCR</a></li>
|
<li class="nopaque-service-color nopaque-service-color-border border-darken" data-service="tesseract-ocr" style="border-left: 10px solid; margin-top: 5px;"><a href="{{ url_for('services.service', service='tesseract-ocr') }}"><i class="nopaque-icon nopaque-service-icon" data-service="tesseract-ocr"></i>OCR</a></li>
|
||||||
<li class="service-color service-color-border border-darken" data-service="spacy-nlp" style="border-left: 10px solid; margin-top: 5px;"><a href="{{ url_for('services.service', service='spacy-nlp') }}"><i class="nopaque-icons service-icon" data-service="spacy-nlp"></i>NLP</a></li>
|
<li class="nopaque-service-color nopaque-service-color-border border-darken" data-service="spacy-nlp" style="border-left: 10px solid; margin-top: 5px;"><a href="{{ url_for('services.service', service='spacy-nlp') }}"><i class="nopaque-icon nopaque-service-icon" data-service="spacy-nlp"></i>NLP</a></li>
|
||||||
<li class="service-color service-color-border border-darken" data-service="corpus-analysis" style="border-left: 10px solid; margin-top: 5px;"><a href="{{ url_for('services.service', service='corpus-analysis') }}"><i class="nopaque-icons service-icon" data-service="corpus-analysis"></i>Corpus analysis</a></li>
|
<li class="nopaque-service-color nopaque-service-color-border border-darken" data-service="corpus-analysis" style="border-left: 10px solid; margin-top: 5px;"><a href="{{ url_for('services.service', service='corpus-analysis') }}"><i class="nopaque-icon nopaque-service-icon" data-service="corpus-analysis"></i>Corpus analysis</a></li>
|
||||||
<li><div class="divider"></div></li>
|
<li><div class="divider"></div></li>
|
||||||
<li><a class="subheader">Account</a></li>
|
<li><a class="subheader">Account</a></li>
|
||||||
<li><a href="{{ url_for('settings.index') }}"><i class="material-icons">settings</i>Settings</a></li>
|
<li><a href="{{ url_for('settings.index') }}"><i class="material-icons">settings</i>Settings</a></li>
|
||||||
|
@ -45,29 +45,31 @@
|
|||||||
main .tabs .indicator {background-color: {{ colors['baseline']['secondary'] }};}
|
main .tabs .indicator {background-color: {{ colors['baseline']['secondary'] }};}
|
||||||
|
|
||||||
{% for service in colors['services'] %}
|
{% for service in colors['services'] %}
|
||||||
.service-scheme[data-service="{{ service }}"] {background-color: {{ colors['services'][service]['lighten'] }};}
|
.nopaque-service-scheme[data-service="{{ service }}"] {background-color: {{ colors['services'][service]['lighten'] }};}
|
||||||
.service-scheme[data-service="{{ service }}"] .btn, .service-scheme[data-service="{{ service }}"] .btn-small, .service-scheme[data-service="{{ service }}"] .btn-large, .service-scheme[data-service="{{ service }}"] .btn-floating {background-color: {{ colors['services'][service]['darken'] }};}
|
.nopaque-service-scheme[data-service="{{ service }}"] .btn, .nopaque-service-scheme[data-service="{{ service }}"] .btn-small, .nopaque-service-scheme[data-service="{{ service }}"] .btn-large, .nopaque-service-scheme[data-service="{{ service }}"] .btn-floating {background-color: {{ colors['services'][service]['darken'] }};}
|
||||||
.service-scheme[data-service="{{ service }}"] .btn:hover, .service-scheme[data-service="{{ service }}"] .btn-large:hover, .service-scheme[data-service="{{ service }}"] .btn-small:hover, .service-scheme[data-service="{{ service }}"] .btn-floating:hover {background-color: {{ colors['services'][service]['base'] }};}
|
.nopaque-service-scheme[data-service="{{ service }}"] .btn:hover, .nopaque-service-scheme[data-service="{{ service }}"] .btn-large:hover, .nopaque-service-scheme[data-service="{{ service }}"] .btn-small:hover, .nopaque-service-scheme[data-service="{{ service }}"] .btn-floating:hover {background-color: {{ colors['services'][service]['base'] }};}
|
||||||
.service-scheme[data-service="{{ service }}"] .pagination li.active {background-color: {{ colors['services'][service]['darken'] }};}
|
.nopaque-service-scheme[data-service="{{ service }}"] .pagination li.active {background-color: {{ colors['services'][service]['darken'] }};}
|
||||||
.service-scheme[data-service="{{ service }}"] .table-of-contents a.active {border-color: {{ colors['services'][service]['darken'] }};}
|
.nopaque-service-scheme[data-service="{{ service }}"] .table-of-contents a.active {border-color: {{ colors['services'][service]['darken'] }};}
|
||||||
.service-scheme[data-service="{{ service }}"] .tabs .tab a {color: inherit;}
|
.nopaque-service-scheme[data-service="{{ service }}"] .tabs .tab a {color: inherit;}
|
||||||
.service-scheme[data-service="{{ service }}"] .tabs .tab.disabled a, .service-scheme[data-service="{{ service }}"] .tabs .tab.disabled a:hover {color: {{ colors['services'][service]['darken'] }}28;}
|
.nopaque-service-scheme[data-service="{{ service }}"] .tabs .tab.disabled a, .nopaque-service-scheme[data-service="{{ service }}"] .tabs .tab.disabled a:hover {color: {{ colors['services'][service]['darken'] }}28;}
|
||||||
.service-scheme[data-service="{{ service }}"] .tabs .tab a:hover {color: {{ colors['services'][service]['darken'] }};}
|
.nopaque-service-scheme[data-service="{{ service }}"] .tabs .tab a:hover {color: {{ colors['services'][service]['darken'] }};}
|
||||||
.service-scheme[data-service="{{ service }}"] .tabs .tab a.active, .service-scheme[data-service="{{ service }}"] .tabs .tab a:focus.active {color: {{ colors['services'][service]['darken'] }}; background-color: {{ colors['services'][service]['darken'] }}28;}
|
.nopaque-service-scheme[data-service="{{ service }}"] .tabs .tab a.active, .nopaque-service-scheme[data-service="{{ service }}"] .tabs .tab a:focus.active {color: {{ colors['services'][service]['darken'] }}; background-color: {{ colors['services'][service]['darken'] }}28;}
|
||||||
.service-scheme[data-service="{{ service }}"] .tabs .indicator {background-color: {{ colors['services'][service]['darken'] }};}
|
.nopaque-service-scheme[data-service="{{ service }}"] .tabs .indicator {background-color: {{ colors['services'][service]['darken'] }};}
|
||||||
|
|
||||||
.service-color[data-service="{{ service }}"] {background-color: {{ colors['services'][service]['base'] }} !important;}
|
.nopaque-service-color[data-service="{{ service }}"] {background-color: {{ colors['services'][service]['base'] }} !important;}
|
||||||
.service-color-text[data-service="{{ service }}"] {color: {{ colors['services'][service]['base'] }} !important;}
|
.nopaque-service-color-text[data-service="{{ service }}"] {color: {{ colors['services'][service]['base'] }} !important;}
|
||||||
.service-color-border[data-service="{{ service }}"] {border-color: {{ colors['services'][service]['base'] }} !important;}
|
.nopaque-service-color-border[data-service="{{ service }}"] {border-color: {{ colors['services'][service]['base'] }} !important;}
|
||||||
.service-color[data-service="{{ service }}"].darken {background-color: {{ colors['services'][service]['darken'] }} !important;}
|
.nopaque-service-color[data-service="{{ service }}"].darken {background-color: {{ colors['services'][service]['darken'] }} !important;}
|
||||||
.service-color-text[data-service="{{ service }}"].text-darken {color: {{ colors['services'][service]['darken'] }} !important;}
|
.nopaque-service-color-text[data-service="{{ service }}"].text-darken {color: {{ colors['services'][service]['darken'] }} !important;}
|
||||||
.service-color-border[data-service="{{ service }}"].border-darken {border-color: {{ colors['services'][service]['darken'] }} !important;}
|
.nopaque-service-color-border[data-service="{{ service }}"].border-darken {border-color: {{ colors['services'][service]['darken'] }} !important;}
|
||||||
.service-color[data-service="{{ service }}"].lighten {background-color: {{ colors['services'][service]['lighten'] }} !important;}
|
.nopaque-service-color[data-service="{{ service }}"].lighten {background-color: {{ colors['services'][service]['lighten'] }} !important;}
|
||||||
.service-color-text[data-service="{{ service }}"].text-lighten {color: {{ colors['services'][service]['lighten'] }} !important;}
|
.nopaque-service-color-text[data-service="{{ service }}"].text-lighten {color: {{ colors['services'][service]['lighten'] }} !important;}
|
||||||
.service-color-border[data-service="{{ service }}"].border-lighten {border-color: {{ colors['services'][service]['lighten'] }} !important;}
|
.nopaque-service-color-border[data-service="{{ service }}"].border-lighten {border-color: {{ colors['services'][service]['lighten'] }} !important;}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% for status in colors['status'] %}
|
{% for status_type in colors['status'] %}
|
||||||
.status-color[data-status="{{ status }}"] {background-color: {{ colors['status'][status] }} !important;}
|
{% for status in colors['status'][status_type] %}
|
||||||
|
.nopaque-{{ status_type }}-status-color[data-{{status_type}}-status="{{ status }}"] {background-color: {{ colors['status'][status_type][status] }} !important;}
|
||||||
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</style>
|
</style>
|
||||||
|
@ -6,66 +6,132 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
<h1 id="title">Edit user</h1>
|
<h1 id="title">{{ title }}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col s12 m4">
|
<div class="col s12">
|
||||||
<h2>{{ user.username }}</h2>
|
<form method="POST">
|
||||||
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,</p>
|
{{ edit_general_settings_form.hidden_tag() }}
|
||||||
<a class="waves-effect waves-light btn" href="{{ url_for('.user', user_id=user.hashid) }}"><i class="material-icons left">arrow_back</i>Back to user administration</a>
|
<div class="card">
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col s12 m8">
|
|
||||||
<div class="card">
|
|
||||||
<form method="POST">
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
{{ form.hidden_tag() }}
|
<span class="card-title">General settings</span>
|
||||||
{{ wtf.render_field(form.username, data_length='64', material_icon='account_circle') }}
|
{{ wtf.render_field(edit_general_settings_form.username, data_length='64', material_icon='person') }}
|
||||||
{{ wtf.render_field(form.email, class_='validate', material_icon='email', type='email') }}
|
{{ wtf.render_field(edit_general_settings_form.email, data_length='254', material_icon='email') }}
|
||||||
{{ wtf.render_field(form.role, material_icon='swap_vert') }}
|
</div>
|
||||||
<div class="row">
|
<div class="card-action">
|
||||||
<div class="col s12"><p> </p></div>
|
<div class="right-align">
|
||||||
<div class="col s1">
|
{{ wtf.render_field(edit_general_settings_form.submit, material_icon='send') }}
|
||||||
<p><i class="material-icons">brightness_3</i></p>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col s8">
|
</form>
|
||||||
<p>{{ form.dark_mode.label.text }}</p>
|
</div>
|
||||||
<p class="light">Enable dark mode to ease your eyes.</p>
|
</form>
|
||||||
</div>
|
|
||||||
<div class="col s3 right-align">
|
<form method="POST">
|
||||||
<div class="switch">
|
{{ edit_interface_settings_form.hidden_tag() }}
|
||||||
<label>
|
<div class="card">
|
||||||
{{ form.dark_mode() }}
|
<div class="card-content">
|
||||||
<span class="lever"></span>
|
<span class="card-title">Interface settings</span>
|
||||||
</label>
|
<div class="row">
|
||||||
</div>
|
<div class="col s12"><p> </p></div>
|
||||||
</div>
|
<div class="col s1">
|
||||||
<div class="col s12"><p> </p></div>
|
<p><i class="material-icons">brightness_3</i></p>
|
||||||
<div class="col s12 divider"></div>
|
</div>
|
||||||
<div class="col s12"><p> </p></div>
|
<div class="col s8">
|
||||||
<div class="col s1">
|
<p>{{ edit_interface_settings_form.dark_mode.label.text }}</p>
|
||||||
<p><i class="material-icons">check</i></p>
|
<p class="light">Enable dark mode to ease your eyes.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col s8">
|
<div class="col s3 right-align">
|
||||||
<p>{{ form.confirmed.label.text }}</p>
|
<div class="switch">
|
||||||
<p class="light">Change confirmation status manually.</p>
|
<label>
|
||||||
</div>
|
{{ edit_interface_settings_form.dark_mode() }}
|
||||||
<div class="col s3 right-align">
|
<span class="lever"></span>
|
||||||
<div class="switch">
|
</label>
|
||||||
<label>
|
|
||||||
{{ form.confirmed() }}
|
|
||||||
<span class="lever"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-action right-align">
|
</div>
|
||||||
{{ wtf.render_field(form.submit, material_icon='send') }}
|
<div class="card-action">
|
||||||
|
<div class="right-align">
|
||||||
|
{{ wtf.render_field(edit_interface_settings_form.submit, material_icon='send') }}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
{{ edit_notification_settings_form.hidden_tag() }}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title">Notification settings</span>
|
||||||
|
{{ wtf.render_field(edit_notification_settings_form.job_status_mail_notification_level, material_icon='notifications') }}
|
||||||
|
</div>
|
||||||
|
<div class="card-action">
|
||||||
|
<div class="right-align">
|
||||||
|
{{ wtf.render_field(edit_notification_settings_form.submit, material_icon='send') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
{{ admin_edit_user_form.hidden_tag() }}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title">Administrator settings</span>
|
||||||
|
{{ wtf.render_field(admin_edit_user_form.role, material_icon='swap_vert') }}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12"><p> </p></div>
|
||||||
|
<div class="col s1">
|
||||||
|
<p><i class="material-icons">check</i></p>
|
||||||
|
</div>
|
||||||
|
<div class="col s8">
|
||||||
|
<p>{{ admin_edit_user_form.confirmed.label.text }}</p>
|
||||||
|
<p class="light">Change confirmation status manually.</p>
|
||||||
|
</div>
|
||||||
|
<div class="col s3 right-align">
|
||||||
|
<div class="switch">
|
||||||
|
<label>
|
||||||
|
{{ admin_edit_user_form.confirmed() }}
|
||||||
|
<span class="lever"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-action right-align">
|
||||||
|
{{ wtf.render_field(admin_edit_user_form.submit, material_icon='send') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title">Delete account</span>
|
||||||
|
<p>Deleting an account has the following effects:</p>
|
||||||
|
<ul>
|
||||||
|
<li>All data associated with your corpora and jobs will be permanently deleted.</li>
|
||||||
|
<li>All settings will be permanently deleted.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="card-action right-align">
|
||||||
|
<a href="#delete-account-modal" class="btn modal-trigger red waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock page_content %}
|
{% endblock page_content %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{{ super() }}
|
||||||
|
<div class="modal" id="delete-account-modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h4>Confirm deletion</h4>
|
||||||
|
<p>Do you really want to delete your account and all associated data? All associated corpora, jobs and files will be permanently deleted!</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
|
||||||
|
<a href="{{ url_for('.delete_user', user_id=user.id) }}" class="btn red waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock modals %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% from "corpora/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
{% from "corpora/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
||||||
{% import "materialize/wtf.html.j2" as wtf %}
|
{% import "materialize/wtf.html.j2" as wtf %}
|
||||||
|
|
||||||
{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
|
{% block main_attribs %} class="nopaque-service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
|
||||||
|
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% from "corpora/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
{% from "corpora/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
||||||
{% import "materialize/wtf.html.j2" as wtf %}
|
{% import "materialize/wtf.html.j2" as wtf %}
|
||||||
|
|
||||||
{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
|
{% block main_attribs %} class="nopaque-service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
|
||||||
|
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
{% extends "base.html.j2" %}
|
{% extends "base.html.j2" %}
|
||||||
{% import "materialize/wtf.html.j2" as wtf %}
|
{% import "materialize/wtf.html.j2" as wtf %}
|
||||||
|
|
||||||
{% block main_attribs %} class="service-scheme" data-service="corpus-analysis" id="corpus-analysis-app-container"{% endblock main_attribs %}
|
{% block main_attribs %} class="nopaque-service-scheme" data-service="corpus-analysis" id="corpus-analysis-app-container"{% endblock main_attribs %}
|
||||||
|
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
<ul class="row tabs no-autoinit" id="corpus-analysis-app-extension-tabs">
|
<ul class="row tabs no-autoinit" id="corpus-analysis-app-extension-tabs">
|
||||||
<li class="tab col s3"><a class="active" href="#corpus-analysis-app-overview"><i class="nopaque-icons service-icon left" data-service="corpus-analysis"></i>Corpus analysis</a></li>
|
<li class="tab col s3"><a class="active" href="#corpus-analysis-app-overview"><i class="nopaque-icon nopaque-service-icon left" data-service="corpus-analysis"></i>Corpus analysis</a></li>
|
||||||
<li class="tab col s3"><a href="#concordance-extension-container"><i class="material-icons left">list_alt</i>Concordance</a></li>
|
<li class="tab col s3"><a href="#concordance-extension-container"><i class="material-icons left">list_alt</i>Concordance</a></li>
|
||||||
<li class="tab col s3"><a href="#reader-extension-container"><i class="material-icons left">chrome_reader_mode</i>Reader</a></li>
|
<li class="tab col s3"><a href="#reader-extension-container"><i class="material-icons left">chrome_reader_mode</i>Reader</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{% extends "base.html.j2" %}
|
{% extends "base.html.j2" %}
|
||||||
{% from "corpora/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
{% from "corpora/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
||||||
|
|
||||||
{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
|
{% block main_attribs %} class="nopaque-service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
|
||||||
|
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -14,8 +14,8 @@
|
|||||||
<div class="col s4 m3 l2 right-align">
|
<div class="col s4 m3 l2 right-align">
|
||||||
<p> </p>
|
<p> </p>
|
||||||
<p> </p>
|
<p> </p>
|
||||||
<span class="chip status status-color status-text white-text"></span>
|
<span class="chip corpus-status nopaque-corpus-status-color nopaque-corpus-status-text white-text"></span>
|
||||||
<div class="active preloader-wrapper small status-spinner">
|
<div class="active preloader-wrapper small corpus-status-spinner">
|
||||||
<div class="spinner-layer spinner-blue-only">
|
<div class="spinner-layer spinner-blue-only">
|
||||||
<div class="circle-clipper left">
|
<div class="circle-clipper left">
|
||||||
<div class="circle"></div>
|
<div class="circle"></div>
|
||||||
@ -31,7 +31,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card service-color-border border-darken" data-service="corpus-analysis" style="border-top: 10px solid">
|
<div class="card nopaque-service-color-border border-darken" data-service="corpus-analysis" style="border-top: 10px solid">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
@ -64,9 +64,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-action right-align">
|
<div class="card-action right-align">
|
||||||
<a class="analyse-corpus-trigger btn disabled waves-effect waves-light" href="{{ url_for('corpora.analyse_corpus', corpus_id=corpus.id) }}"><i class="material-icons left">search</i>Analyze</a>
|
<a class="btn corpus-analyse-trigger disabled waves-effect waves-light" href="{{ url_for('corpora.analyse_corpus', corpus_id=corpus.id) }}"><i class="material-icons left">search</i>Analyze</a>
|
||||||
<a class="btn build-corpus-trigger disabled waves-effect waves-light" href="{{ url_for('corpora.build_corpus', corpus_id=corpus.id) }}"><i class="nopaque-icons left">K</i>Build</a>
|
<a class="btn corpus-build-trigger disabled waves-effect waves-light" href="{{ url_for('corpora.build_corpus', corpus_id=corpus.id) }}"><i class="nopaque-icon left">K</i>Build</a>
|
||||||
<a class="btn disabled export-corpus-trigger waves-effect waves-light"><i class="material-icons left">import_export</i>Export</a>
|
<a class="btn disabled export-corpus-trigger waves-effect waves-light" href="{{ url_for('corpora.export_corpus', corpus_id=corpus.id) }}"><i class="material-icons left">import_export</i>Export</a>
|
||||||
<a class="btn modal-trigger red waves-effect waves-light" data-target="delete-corpus-modal"><i class="material-icons left">delete</i>Delete</a>
|
<a class="btn modal-trigger red waves-effect waves-light" data-target="delete-corpus-modal"><i class="material-icons left">delete</i>Delete</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% from "corpora/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
{% from "corpora/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
||||||
{% import "materialize/wtf.html.j2" as wtf %}
|
{% import "materialize/wtf.html.j2" as wtf %}
|
||||||
|
|
||||||
{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
|
{% block main_attribs %} class="nopaque-service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
|
||||||
|
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% from "corpora/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
{% from "corpora/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
||||||
{% import "materialize/wtf.html.j2" as wtf %}
|
{% import "materialize/wtf.html.j2" as wtf %}
|
||||||
|
|
||||||
{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
|
{% block main_attribs %} class="nopaque-service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
|
||||||
|
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{% extends "base.html.j2" %}
|
{% extends "base.html.j2" %}
|
||||||
{% from "jobs/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
{% from "jobs/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
||||||
|
|
||||||
{% block main_attribs %} class="service-scheme" data-service="{{ job.service }}"{% endblock main_attribs %}
|
{% block main_attribs %} class="nopaque-service-scheme" data-service="{{ job.service }}"{% endblock main_attribs %}
|
||||||
|
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -9,13 +9,13 @@
|
|||||||
<div class="col s12" data-job-id="{{ job.hashid }}" data-user-id="{{ job.user.hashid }}" id="job-display">
|
<div class="col s12" data-job-id="{{ job.hashid }}" data-user-id="{{ job.user.hashid }}" id="job-display">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s8 m9 l10">
|
<div class="col s8 m9 l10">
|
||||||
<h1 id="title"><i style="font-size: inherit;" class="nopaque-icons service-icon" data-service="{{ job.service }}"></i> <span class="job-title"></span></h1>
|
<h1 id="title"><i style="font-size: inherit;" class="nopaque-icon nopaque-service-icon" data-service="{{ job.service }}"></i> <span class="job-title"></span></h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="col s4 m3 l2 right-align">
|
<div class="col s4 m3 l2 right-align">
|
||||||
<p> </p>
|
<p> </p>
|
||||||
<p> </p>
|
<p> </p>
|
||||||
<span class="chip status status-text status-color white-text"></span>
|
<span class="chip job-status nopaque-job-status-text nopaque-job-status-color white-text"></span>
|
||||||
<div class="active preloader-wrapper small status-spinner">
|
<div class="active preloader-wrapper small job-status-spinner">
|
||||||
<div class="spinner-layer spinner-blue-only">
|
<div class="spinner-layer spinner-blue-only">
|
||||||
<div class="circle-clipper left">
|
<div class="circle-clipper left">
|
||||||
<div class="circle"></div>
|
<div class="circle"></div>
|
||||||
@ -31,7 +31,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card service-color-border border-darken" data-service="{{ job.service }}" style="border-top: 10px solid">
|
<div class="card nopaque-service-color-border border-darken" data-service="{{ job.service }}" style="border-top: 10px solid">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
|
@ -40,8 +40,8 @@
|
|||||||
<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('corpora.import_corpus') }}"><i class="material-icons right">import_export</i>Import Corpus</a>
|
<a class="btn disabled waves-effect waves-light" href="{{ url_for('corpora.import_corpus') }}"><i class="material-icons right">import_export</i>Import Corpus</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="btn waves-effect waves-light" href="{{ url_for('corpora.add_corpus') }}">New corpus<i class="material-icons right">add</i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -116,36 +116,36 @@
|
|||||||
<div class="card-panel center-align hoverable">
|
<div class="card-panel center-align hoverable">
|
||||||
<br>
|
<br>
|
||||||
<a href="{{ url_for('services.service', service='file-setup') }}" class="btn-floating btn-large waves-effect waves-light" style="transform: scale(2);">
|
<a href="{{ url_for('services.service', service='file-setup') }}" class="btn-floating btn-large waves-effect waves-light" style="transform: scale(2);">
|
||||||
<i class="nopaque-icons service-color darken service-icon" data-service="file-setup"></i>
|
<i class="nopaque-icon nopaque-service-color darken nopaque-service-icon" data-service="file-setup"></i>
|
||||||
</a>
|
</a>
|
||||||
<br><br>
|
<br><br>
|
||||||
<p class="service-color-text darken" data-service="file-setup"><b>File setup</b></p>
|
<p class="nopaque-service-color-text darken" data-service="file-setup"><b>File setup</b></p>
|
||||||
<p class="light">Digital copies of text based research data (books, letters, etc.) often comprise various files and formats. nopaque converts and merges those files to facilitate further processing.</p>
|
<p class="light">Digital copies of text based research data (books, letters, etc.) often comprise various files and formats. nopaque converts and merges those files to facilitate further processing.</p>
|
||||||
<a href="{{ url_for('services.service', service='file-setup') }}" class="waves-effect waves-light btn service-color darken" data-service="file-setup">Create Job</a>
|
<a href="{{ url_for('services.service', service='file-setup') }}" class="waves-effect waves-light btn nopaque-service-color darken" data-service="file-setup">Create Job</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col s12 m4">
|
<div class="col s12 m4">
|
||||||
<div class="card-panel center-align hoverable">
|
<div class="card-panel center-align hoverable">
|
||||||
<br>
|
<br>
|
||||||
<a href="{{ url_for('services.service', service='tesseract-ocr') }}" class="btn-floating btn-large waves-effect waves-light" style="transform: scale(2);">
|
<a href="{{ url_for('services.service', service='tesseract-ocr') }}" class="btn-floating btn-large waves-effect waves-light" style="transform: scale(2);">
|
||||||
<i class="nopaque-icons service-color darken service-icon" data-service="tesseract-ocr" style="font-size: 2.5rem;"></i>
|
<i class="nopaque-icon nopaque-service-color darken nopaque-service-icon" data-service="tesseract-ocr" style="font-size: 2.5rem;"></i>
|
||||||
</a>
|
</a>
|
||||||
<br><br>
|
<br><br>
|
||||||
<p class="service-color-text darken" data-service="tesseract-ocr"><b>Optical Character Recognition</b></p>
|
<p class="nopaque-service-color-text darken" data-service="tesseract-ocr"><b>Optical Character Recognition</b></p>
|
||||||
<p class="light">nopaque converts your image data – like photos or scans – into text data through a process called OCR. This step enables you to proceed with further computational analysis of your documents.</p>
|
<p class="light">nopaque converts your image data – like photos or scans – into text data through a process called OCR. This step enables you to proceed with further computational analysis of your documents.</p>
|
||||||
<a href="{{ url_for('services.service', service='tesseract-ocr') }}" class="waves-effect waves-light btn service-color darken" data-service="tesseract-ocr">Create Job</a>
|
<a href="{{ url_for('services.service', service='tesseract-ocr') }}" class="waves-effect waves-light btn nopaque-service-color darken" data-service="tesseract-ocr">Create Job</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col s12 m4">
|
<div class="col s12 m4">
|
||||||
<div class="card-panel center-align hoverable">
|
<div class="card-panel center-align hoverable">
|
||||||
<br>
|
<br>
|
||||||
<a href="{{ url_for('services.service', service='spacy-nlp') }}" class="btn-floating btn-large waves-effect waves-light" style="transform: scale(2);">
|
<a href="{{ url_for('services.service', service='spacy-nlp') }}" class="btn-floating btn-large waves-effect waves-light" style="transform: scale(2);">
|
||||||
<i class="nopaque-icons service-color darken service-icon" data-service="spacy-nlp" style="font-size: 2.5rem;"></i>
|
<i class="nopaque-icon nopaque-service-color darken nopaque-service-icon" data-service="spacy-nlp" style="font-size: 2.5rem;"></i>
|
||||||
</a>
|
</a>
|
||||||
<br><br>
|
<br><br>
|
||||||
<p class="service-color-text darken" data-service="spacy-nlp"><b>Natural Language Processing</b></p>
|
<p class="nopaque-service-color-text darken" data-service="spacy-nlp"><b>Natural Language Processing</b></p>
|
||||||
<p class="light">By means of computational linguistic data processing (tokenization, lemmatization, part-of-speech tagging and named-entity recognition) nopaque extracts additional information from your text.</p>
|
<p class="light">By means of computational linguistic data processing (tokenization, lemmatization, part-of-speech tagging and named-entity recognition) nopaque extracts additional information from your text.</p>
|
||||||
<a href="{{ url_for('services.service', service='spacy-nlp') }}" class="waves-effect waves-light btn service-color darken" data-service="spacy-nlp">Create Job</a>
|
<a href="{{ url_for('services.service', service='spacy-nlp') }}" class="waves-effect waves-light btn nopaque-service-color darken" data-service="spacy-nlp">Create Job</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,22 +21,22 @@
|
|||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s12 m6 l3 center-align">
|
<div class="col s12 m6 l3 center-align">
|
||||||
<i class="large nopaque-icons secondary-color-text">A</i><br>
|
<i class="large nopaque-icon secondary-color-text">A</i><br>
|
||||||
<b class="primary-color-text">Speeds up your work</b>
|
<b class="primary-color-text">Speeds up your work</b>
|
||||||
<p class="light">All tools provided by nopaque are carefully selected to provide a complete tool suite without being held up by compatibility issues.</p>
|
<p class="light">All tools provided by nopaque are carefully selected to provide a complete tool suite without being held up by compatibility issues.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col s12 m6 l3 center-align">
|
<div class="col s12 m6 l3 center-align">
|
||||||
<i class="large nopaque-icons secondary-color-text">B</i><br>
|
<i class="large nopaque-icon secondary-color-text">B</i><br>
|
||||||
<b class="primary-color-text">Cloud infrastructure</b>
|
<b class="primary-color-text">Cloud infrastructure</b>
|
||||||
<p class="light">All computational work is processed within nopaque’s cloud infrastructure. You don't need to install any software. Great, right?</p>
|
<p class="light">All computational work is processed within nopaque’s cloud infrastructure. You don't need to install any software. Great, right?</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col s12 m6 l3 center-align">
|
<div class="col s12 m6 l3 center-align">
|
||||||
<i class="large nopaque-icons secondary-color-text">C</i><br>
|
<i class="large nopaque-icon secondary-color-text">C</i><br>
|
||||||
<b class="primary-color-text">User friendly</b>
|
<b class="primary-color-text">User friendly</b>
|
||||||
<p class="light">You can start right away without having to read mile-long manuals. All services come with default settings that make it easy for you to just get going. Also great, right?</p>
|
<p class="light">You can start right away without having to read mile-long manuals. All services come with default settings that make it easy for you to just get going. Also great, right?</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col s12 m6 l3 center-align">
|
<div class="col s12 m6 l3 center-align">
|
||||||
<i class="large nopaque-icons secondary-color-text">D</i><br>
|
<i class="large nopaque-icon secondary-color-text">D</i><br>
|
||||||
<b class="primary-color-text">Meshing processes</b>
|
<b class="primary-color-text">Meshing processes</b>
|
||||||
<p class="light">No matter where you step in, nopaque facilitates and accompanies your research. Its workflow perfectly ties in with your research process.</p>
|
<p class="light">No matter where you step in, nopaque facilitates and accompanies your research. Its workflow perfectly ties in with your research process.</p>
|
||||||
</div>
|
</div>
|
||||||
@ -77,34 +77,34 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s12 m6 l3 center-align">
|
<div class="col s12 m6 l3 center-align">
|
||||||
<a href="{{ url_for('services.service', service='file-setup') }}" class="btn-floating btn-large btn-scale-x2 waves-effect waves-light">
|
<a href="{{ url_for('services.service', service='file-setup') }}" class="btn-floating btn-large btn-scale-x2 waves-effect waves-light">
|
||||||
<i class="nopaque-icons service-color darken service-icon" data-service="file-setup"></i>
|
<i class="nopaque-icon nopaque-service-color darken nopaque-service-icon" data-service="file-setup"></i>
|
||||||
</a>
|
</a>
|
||||||
<br><br>
|
<br><br>
|
||||||
<p class="service-color-text text-darken" data-service="file-setup"><b>File setup</b></p>
|
<p class="nopaque-service-color-text text-darken" data-service="file-setup"><b>File setup</b></p>
|
||||||
<p class="light">Digital copies of text based research data (books, letters, etc.) often comprise various files and formats. nopaque converts and merges those files to facilitate further processing and the application of other services.</p>
|
<p class="light">Digital copies of text based research data (books, letters, etc.) often comprise various files and formats. nopaque converts and merges those files to facilitate further processing and the application of other services.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col s12 m6 l3 center-align">
|
<div class="col s12 m6 l3 center-align">
|
||||||
<a href="{{ url_for('services.service', service='tesseract-ocr') }}" class="btn-floating btn-large btn-scale-x2 waves-effect waves-light">
|
<a href="{{ url_for('services.service', service='tesseract-ocr') }}" class="btn-floating btn-large btn-scale-x2 waves-effect waves-light">
|
||||||
<i class="nopaque-icons service-color darken service-icon" data-service="tesseract-ocr"></i>
|
<i class="nopaque-icon nopaque-service-color darken nopaque-service-icon" data-service="tesseract-ocr"></i>
|
||||||
</a>
|
</a>
|
||||||
<br><br>
|
<br><br>
|
||||||
<p class="service-color-text text-darken" data-service="tesseract-ocr"><b>Optical Character Recognition</b></p>
|
<p class="nopaque-service-color-text text-darken" data-service="tesseract-ocr"><b>Optical Character Recognition</b></p>
|
||||||
<p class="light">nopaque converts your image data – like photos or scans – into text data through OCR making it machine readable. This step enables you to proceed with further computational analysis of your documents.</p>
|
<p class="light">nopaque converts your image data – like photos or scans – into text data through OCR making it machine readable. This step enables you to proceed with further computational analysis of your documents.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col s12 m6 l3 center-align">
|
<div class="col s12 m6 l3 center-align">
|
||||||
<a href="{{ url_for('services.service', service='nlp') }}" class="btn-floating btn-large btn-scale-x2 waves-effect waves-light">
|
<a href="{{ url_for('services.service', service='nlp') }}" class="btn-floating btn-large btn-scale-x2 waves-effect waves-light">
|
||||||
<i class="nopaque-icons service-color darken service-icon" data-service="nlp"></i>
|
<i class="nopaque-icon nopaque-service-color darken nopaque-service-icon" data-service="nlp"></i>
|
||||||
</a>
|
</a>
|
||||||
<br><br>
|
<br><br>
|
||||||
<p class="service-color-text text-darken" data-service="nlp"><b>Natural Language Processing</b></p>
|
<p class="nopaque-service-color-text text-darken" data-service="nlp"><b>Natural Language Processing</b></p>
|
||||||
<p class="light">By means of computational linguistic data processing (tokenization, lemmatization, part-of-speech tagging and named-entity recognition) nopaque extracts additional information from your text.</p>
|
<p class="light">By means of computational linguistic data processing (tokenization, lemmatization, part-of-speech tagging and named-entity recognition) nopaque extracts additional information from your text.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col s12 m6 l3 center-align">
|
<div class="col s12 m6 l3 center-align">
|
||||||
<a href="{{ url_for('services.service', service='corpus_analysis') }}" class="btn-floating btn-large btn-scale-x2 waves-effect waves-light">
|
<a href="{{ url_for('services.service', service='corpus_analysis') }}" class="btn-floating btn-large btn-scale-x2 waves-effect waves-light">
|
||||||
<i class="nopaque-icons service-color darken service-icon" data-service="corpus-analysis"></i>
|
<i class="nopaque-icon nopaque-service-color darken nopaque-service-icon" data-service="corpus-analysis"></i>
|
||||||
</a>
|
</a>
|
||||||
<br><br>
|
<br><br>
|
||||||
<p class="service-color-text text-darken" data-service="corpus-analysis"><b>Corpus analysis</b></p>
|
<p class="nopaque-service-color-text text-darken" data-service="corpus-analysis"><b>Corpus analysis</b></p>
|
||||||
<p class="light">nopaque lets you create and upload as many text corpora as you want. It makes use of CQP Query Language, which allows for complex search requests with the aid of metadata and NLP tags.</p>
|
<p class="light">nopaque lets you create and upload as many text corpora as you want. It makes use of CQP Query Language, which allows for complex search requests with the aid of metadata and NLP tags.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -192,8 +192,8 @@
|
|||||||
<!-- <div class="col s12 m10"> -->
|
<!-- <div class="col s12 m10"> -->
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
<h3>Introduction video</h3>
|
<h3>Introduction video</h3>
|
||||||
<div class="responsive-youtube-video-container">
|
<div style="position: relative; width: 100%; height: 0; padding-bottom: 56.25%;">
|
||||||
<iframe class="responsive-youtube-video" src="https://www.youtube-nocookie.com/embed/KPGZSW_7SWk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
<iframe style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" src="https://www.youtube-nocookie.com/embed/KPGZSW_7SWk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{% extends "base.html.j2" %}
|
{% extends "base.html.j2" %}
|
||||||
{% from "services/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
{% from "services/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
||||||
|
|
||||||
{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
|
{% block main_attribs %} class="nopaque-service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
|
||||||
|
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -13,7 +13,7 @@
|
|||||||
<div class="col s12 m3 push-m9">
|
<div class="col s12 m3 push-m9">
|
||||||
<div class="center-align">
|
<div class="center-align">
|
||||||
<a class="btn-floating btn-large btn-scale-x2 waves-effect waves-light" style="transform: scale(2);">
|
<a class="btn-floating btn-large btn-scale-x2 waves-effect waves-light" style="transform: scale(2);">
|
||||||
<i class="nopaque-icons service-color darken service-icon" data-service="corpus-analysis"></i>
|
<i class="nopaque-icon nopaque-service-color darken nopaque-service-icon" data-service="corpus-analysis"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% from "services/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
{% from "services/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
||||||
{% import "materialize/wtf.html.j2" as wtf %}
|
{% import "materialize/wtf.html.j2" as wtf %}
|
||||||
|
|
||||||
{% block main_attribs %} class="service-scheme" data-service="file-setup"{% endblock main_attribs %}
|
{% block main_attribs %} class="nopaque-service-scheme" data-service="file-setup"{% endblock main_attribs %}
|
||||||
|
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -16,13 +16,13 @@
|
|||||||
<p class="hide-on-small-only"> </p>
|
<p class="hide-on-small-only"> </p>
|
||||||
<p class="hide-on-small-only"> </p>
|
<p class="hide-on-small-only"> </p>
|
||||||
<a class="btn-floating btn-large btn-scale-x2 waves-effect waves-light">
|
<a class="btn-floating btn-large btn-scale-x2 waves-effect waves-light">
|
||||||
<i class="nopaque-icons service-color darken service-icon" data-service="file-setup"></i>
|
<i class="nopaque-icon nopaque-service-color darken nopaque-service-icon" data-service="file-setup"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col s12 m9 pull-m3">
|
<div class="col s12 m9 pull-m3">
|
||||||
<div class="card service-color-border border-darken" data-service="file-setup" style="border-top: 10px solid;">
|
<div class="card nopaque-service-color-border border-darken" data-service="file-setup" style="border-top: 10px solid;">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% from "services/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
{% from "services/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
||||||
{% import "materialize/wtf.html.j2" as wtf %}
|
{% import "materialize/wtf.html.j2" as wtf %}
|
||||||
|
|
||||||
{% block main_attribs %} class="service-scheme" data-service="spacy-nlp"{% endblock main_attribs %}
|
{% block main_attribs %} class="nopaque-service-scheme" data-service="spacy-nlp"{% endblock main_attribs %}
|
||||||
|
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -16,13 +16,13 @@
|
|||||||
<p class="hide-on-small-only"> </p>
|
<p class="hide-on-small-only"> </p>
|
||||||
<p class="hide-on-small-only"> </p>
|
<p class="hide-on-small-only"> </p>
|
||||||
<a class="btn-floating btn-large btn-scale-x2 waves-effect waves-light">
|
<a class="btn-floating btn-large btn-scale-x2 waves-effect waves-light">
|
||||||
<i class="nopaque-icons service-color darken service-icon" data-service="spacy-nlp"></i>
|
<i class="nopaque-icon nopaque-service-color darken nopaque-service-icon" data-service="spacy-nlp"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col s12 m9 pull-m3">
|
<div class="col s12 m9 pull-m3">
|
||||||
<div class="card service-color-border border-darken" data-service="spacy-nlp" style="border-top: 10px solid;">
|
<div class="card nopaque-service-color-border border-darken" data-service="spacy-nlp" style="border-top: 10px solid;">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s12 m6">
|
<div class="col s12 m6">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% from "services/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
{% from "services/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
||||||
{% import "materialize/wtf.html.j2" as wtf %}
|
{% import "materialize/wtf.html.j2" as wtf %}
|
||||||
|
|
||||||
{% block main_attribs %} class="service-scheme" data-service="tesseract-ocr"{% endblock main_attribs %}
|
{% block main_attribs %} class="nopaque-service-scheme" data-service="tesseract-ocr"{% endblock main_attribs %}
|
||||||
|
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -16,13 +16,13 @@
|
|||||||
<p class="hide-on-small-only"> </p>
|
<p class="hide-on-small-only"> </p>
|
||||||
<p class="hide-on-small-only"> </p>
|
<p class="hide-on-small-only"> </p>
|
||||||
<a class="btn-floating btn-large btn-scale-x2 waves-effect waves-light">
|
<a class="btn-floating btn-large btn-scale-x2 waves-effect waves-light">
|
||||||
<i class="nopaque-icons service-color darken service-icon" data-service="tesseract-ocr"></i>
|
<i class="nopaque-icon nopaque-service-color darken nopaque-service-icon" data-service="tesseract-ocr"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col s12 m9 pull-m3">
|
<div class="col s12 m9 pull-m3">
|
||||||
<div class="card service-color-border border-darken" data-service="tesseract-ocr" style="border-top: 10px solid;">
|
<div class="card nopaque-service-color-border border-darken" data-service="tesseract-ocr" style="border-top: 10px solid;">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
|
@ -1,12 +1,6 @@
|
|||||||
{% set breadcrumbs %}
|
{% set breadcrumbs %}
|
||||||
<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
|
<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
|
||||||
|
{% if request.path == url_for('settings.index') %}
|
||||||
<li class="tab"><a{%if request.path == url_for('settings.index') %} class="active"{% endif %} href="{{ url_for('settings.index') }}" target="_self">Settings</a></li>
|
<li class="tab"><a{%if request.path == url_for('settings.index') %} class="active"{% endif %} href="{{ url_for('settings.index') }}" target="_self">Settings</a></li>
|
||||||
<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
|
|
||||||
{% if request.path == url_for('settings.change_password') %}
|
|
||||||
<li class="tab"><a class="active" href="{{ url_for('settings.change_password') }}" target="_self">Change password</a></li>
|
|
||||||
{% elif request.path == url_for('settings.edit_general_settings') %}
|
|
||||||
<li class="tab"><a class="active" href="{{ url_for('settings.edit_general_settings') }}" target="_self">Edit general settings</a></li>
|
|
||||||
{% elif request.path == url_for('settings.edit_notification_settings') %}
|
|
||||||
<li class="tab"><a class="active" href="{{ url_for('settings.edit_notification_settings') }}" target="_self">Edit notification settings</a></li>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endset %}
|
{% endset %}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
<div class="collection">
|
|
||||||
<a href="{{ url_for('.edit_general_settings') }}" class="collection-item{%if request.path == url_for('.edit_general_settings') %} active{% endif %}">Edit general settings</a>
|
|
||||||
<a href="{{ url_for('.change_password') }}" class="collection-item{%if request.path == url_for('.change_password') %} active{% endif %}">Change password</a>
|
|
||||||
<a href="{{ url_for('.edit_notification_settings') }}" class="collection-item{%if request.path == url_for('.edit_notification_settings') %} active{% endif %}">Edit notification settings</a>
|
|
||||||
</div>
|
|
@ -1,36 +0,0 @@
|
|||||||
{% extends "base.html.j2" %}
|
|
||||||
{% from "settings/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
|
||||||
{% import "materialize/wtf.html.j2" as wtf %}
|
|
||||||
|
|
||||||
{% block page_content %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col s12">
|
|
||||||
<h1 id="title">{{ title }}</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col s12 m4">
|
|
||||||
{% include 'settings/_menu.html.j2' %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col s12 m8">
|
|
||||||
<div class="card">
|
|
||||||
<form enctype="multipart/form-data" method="POST">
|
|
||||||
<div class="card-content">
|
|
||||||
<span class="card-title">{{ title }}</span>
|
|
||||||
{{ form.hidden_tag() }}
|
|
||||||
{{ wtf.render_field(form.password, material_icon='vpn_key') }}
|
|
||||||
{{ wtf.render_field(form.new_password, material_icon='vpn_key') }}
|
|
||||||
{{ wtf.render_field(form.new_password2, material_icon='vpn_key') }}
|
|
||||||
</div>
|
|
||||||
<div class="card-action">
|
|
||||||
<div class="right-align">
|
|
||||||
{{ wtf.render_field(form.submit, material_icon='send') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock page_content %}
|
|
@ -1,81 +0,0 @@
|
|||||||
{% extends "base.html.j2" %}
|
|
||||||
{% from "settings/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
|
||||||
{% import "materialize/wtf.html.j2" as wtf %}
|
|
||||||
|
|
||||||
{% block page_content %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col s12">
|
|
||||||
<h1 id="title">{{ title }}</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col s12 m4">
|
|
||||||
{% include 'settings/_menu.html.j2' %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col s12 m8">
|
|
||||||
<div class="card">
|
|
||||||
<form enctype="multipart/form-data" method="POST">
|
|
||||||
<div class="card-content">
|
|
||||||
<span class="card-title">{{ title }}</span>
|
|
||||||
{{ form.hidden_tag() }}
|
|
||||||
{{ wtf.render_field(form.username, data_length='64', material_icon='person') }}
|
|
||||||
{{ wtf.render_field(form.email, data_length='254', material_icon='email') }}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col s12"><p> </p></div>
|
|
||||||
<div class="col s1">
|
|
||||||
<p><i class="material-icons">brightness_3</i></p>
|
|
||||||
</div>
|
|
||||||
<div class="col s8">
|
|
||||||
<p>{{ form.dark_mode.label.text }}</p>
|
|
||||||
<p class="light">Enable dark mode to ease your eyes.</p>
|
|
||||||
</div>
|
|
||||||
<div class="col s3 right-align">
|
|
||||||
<div class="switch">
|
|
||||||
<label>
|
|
||||||
{{ form.dark_mode() }}
|
|
||||||
<span class="lever"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-action">
|
|
||||||
<div class="right-align">
|
|
||||||
{{ wtf.render_field(form.submit, material_icon='send') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-content">
|
|
||||||
<span class="card-title">Delete account</span>
|
|
||||||
<p>Deleting an account has the following effects:</p>
|
|
||||||
<ul>
|
|
||||||
<li>All data associated with your corpora and jobs will be permanently deleted.</li>
|
|
||||||
<li>All settings will be permanently deleted.</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="card-action right-align">
|
|
||||||
<a href="#delete-account-modal" class="btn modal-trigger red waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock page_content %}
|
|
||||||
|
|
||||||
{% block modals %}
|
|
||||||
{{ super() }}
|
|
||||||
<div class="modal" id="delete-account-modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<h4>Confirm deletion</h4>
|
|
||||||
<p>Do you really want to delete your account and all associated data? All associated corpora, jobs and files will be permanently deleted!</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
|
|
||||||
<a href="{{ url_for('.delete') }}" class="btn red waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock modals %}
|
|
@ -1,35 +0,0 @@
|
|||||||
{% extends "base.html.j2" %}
|
|
||||||
{% from "settings/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
|
||||||
{% import "materialize/wtf.html.j2" as wtf %}
|
|
||||||
|
|
||||||
{% block page_content %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col s12">
|
|
||||||
<h1 id="title">{{ title }}</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col s12 m4">
|
|
||||||
{% include 'settings/_menu.html.j2' %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col s12 m8">
|
|
||||||
<div class="card">
|
|
||||||
<form enctype="multipart/form-data" method="POST">
|
|
||||||
<div class="card-content">
|
|
||||||
<span class="card-title">{{ title }}</span>
|
|
||||||
{{ form.hidden_tag() }}
|
|
||||||
{{ wtf.render_field(form.job_status_mail_notifications, material_icon='notifications') }}
|
|
||||||
{{ wtf.render_field(form.job_status_site_notifications, material_icon='feedback') }}
|
|
||||||
</div>
|
|
||||||
<div class="card-action">
|
|
||||||
<div class="right-align">
|
|
||||||
{{ wtf.render_field(form.submit, material_icon='send') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock page_content %}
|
|
123
app/templates/settings/index.html.j2
Normal file
123
app/templates/settings/index.html.j2
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
{% extends "base.html.j2" %}
|
||||||
|
{% from "settings/_breadcrumbs.html.j2" import breadcrumbs with context %}
|
||||||
|
{% import "materialize/wtf.html.j2" as wtf %}
|
||||||
|
|
||||||
|
{% block page_content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12">
|
||||||
|
<h1 id="title">{{ title }}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col s12">
|
||||||
|
<form method="POST">
|
||||||
|
{{ edit_general_settings_form.hidden_tag() }}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title">General settings</span>
|
||||||
|
{{ wtf.render_field(edit_general_settings_form.username, data_length='64', material_icon='person') }}
|
||||||
|
{{ wtf.render_field(edit_general_settings_form.email, data_length='254', material_icon='email') }}
|
||||||
|
</div>
|
||||||
|
<div class="card-action">
|
||||||
|
<div class="right-align">
|
||||||
|
{{ wtf.render_field(edit_general_settings_form.submit, material_icon='send') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
{{ edit_interface_settings_form.hidden_tag() }}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title">Interface settings</span>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12"><p> </p></div>
|
||||||
|
<div class="col s1">
|
||||||
|
<p><i class="material-icons">brightness_3</i></p>
|
||||||
|
</div>
|
||||||
|
<div class="col s8">
|
||||||
|
<p>{{ edit_interface_settings_form.dark_mode.label.text }}</p>
|
||||||
|
<p class="light">Enable dark mode to ease your eyes.</p>
|
||||||
|
</div>
|
||||||
|
<div class="col s3 right-align">
|
||||||
|
<div class="switch">
|
||||||
|
<label>
|
||||||
|
{{ edit_interface_settings_form.dark_mode() }}
|
||||||
|
<span class="lever"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-action">
|
||||||
|
<div class="right-align">
|
||||||
|
{{ wtf.render_field(edit_interface_settings_form.submit, material_icon='send') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
{{ edit_notification_settings_form.hidden_tag() }}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title">Notification settings</span>
|
||||||
|
{{ wtf.render_field(edit_notification_settings_form.job_status_mail_notification_level, material_icon='notifications') }}
|
||||||
|
</div>
|
||||||
|
<div class="card-action">
|
||||||
|
<div class="right-align">
|
||||||
|
{{ wtf.render_field(edit_notification_settings_form.submit, material_icon='send') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
{{ change_password_form.hidden_tag() }}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title">Change Password</span>
|
||||||
|
{{ wtf.render_field(change_password_form.password, material_icon='vpn_key') }}
|
||||||
|
{{ wtf.render_field(change_password_form.new_password, material_icon='vpn_key') }}
|
||||||
|
{{ wtf.render_field(change_password_form.new_password_confirmation, material_icon='vpn_key') }}
|
||||||
|
</div>
|
||||||
|
<div class="card-action">
|
||||||
|
<div class="right-align">
|
||||||
|
{{ wtf.render_field(change_password_form.submit, material_icon='send') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title">Delete account</span>
|
||||||
|
<p>Deleting an account has the following effects:</p>
|
||||||
|
<ul>
|
||||||
|
<li>All data associated with your corpora and jobs will be permanently deleted.</li>
|
||||||
|
<li>All settings will be permanently deleted.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="card-action right-align">
|
||||||
|
<a href="#delete-account-modal" class="btn modal-trigger red waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock page_content %}
|
||||||
|
|
||||||
|
{% block modals %}
|
||||||
|
{{ super() }}
|
||||||
|
<div class="modal" id="delete-account-modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h4>Confirm deletion</h4>
|
||||||
|
<p>Do you really want to delete your account and all associated data? All associated corpora, jobs and files will be permanently deleted!</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
|
||||||
|
<a href="{{ url_for('.delete') }}" class="btn red waves-effect waves-light"><i class="material-icons left">delete</i>Delete</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock modals %}
|
@ -1,7 +1,7 @@
|
|||||||
<p>Dear <b>{{ job.user.username }}</b>,</p>
|
<p>Dear <b>{{ job.user.username }}</b>,</p>
|
||||||
|
|
||||||
<p>The status of your Job "<b>{{ job.title }}</b>" has changed!</p>
|
<p>The status of your Job "<b>{{ job.title }}</b>" has changed!</p>
|
||||||
<p>It is now <b>{{ job.status }}</b>!</p>
|
<p>It is now <b>{{ job.status.name.lower() }}</b>!</p>
|
||||||
|
|
||||||
<p>You can access your Job here: <a href="{{ url_for('jobs.job', job_id=job.id) }}">{{ url_for('jobs.job', job_id=job.id) }}</a></p>
|
<p>You can access your Job here: <a href="{{ url_for('jobs.job', job_id=job.id) }}">{{ url_for('jobs.job', job_id=job.id) }}</a></p>
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
Dear {{ job.user.username }},
|
Dear {{ job.user.username }},
|
||||||
|
|
||||||
The status of your Job "{{ job.title }}" has changed!
|
The status of your Job "{{ job.title }}" has changed!
|
||||||
It is now {{ job.status }}!
|
It is now {{ job.status.name.lower() }}!
|
||||||
|
|
||||||
You can access your Job here: {{ url_for('jobs.job', job_id=job.id) }}
|
You can access your Job here: {{ url_for('jobs.job', job_id=job.id) }}
|
||||||
|
|
||||||
|
@ -1 +1 @@
|
|||||||
Generic single-database configuration.
|
Single-database configuration for Flask.
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
# Logging configuration
|
# Logging configuration
|
||||||
[loggers]
|
[loggers]
|
||||||
keys = root,sqlalchemy,alembic
|
keys = root,sqlalchemy,alembic,flask_migrate
|
||||||
|
|
||||||
[handlers]
|
[handlers]
|
||||||
keys = console
|
keys = console
|
||||||
@ -34,6 +34,11 @@ level = INFO
|
|||||||
handlers =
|
handlers =
|
||||||
qualname = alembic
|
qualname = alembic
|
||||||
|
|
||||||
|
[logger_flask_migrate]
|
||||||
|
level = INFO
|
||||||
|
handlers =
|
||||||
|
qualname = flask_migrate
|
||||||
|
|
||||||
[handler_console]
|
[handler_console]
|
||||||
class = StreamHandler
|
class = StreamHandler
|
||||||
args = (sys.stderr,)
|
args = (sys.stderr,)
|
||||||
|
@ -3,8 +3,7 @@ from __future__ import with_statement
|
|||||||
import logging
|
import logging
|
||||||
from logging.config import fileConfig
|
from logging.config import fileConfig
|
||||||
|
|
||||||
from sqlalchemy import engine_from_config
|
from flask import current_app
|
||||||
from sqlalchemy import pool
|
|
||||||
|
|
||||||
from alembic import context
|
from alembic import context
|
||||||
|
|
||||||
@ -21,10 +20,10 @@ logger = logging.getLogger('alembic.env')
|
|||||||
# for 'autogenerate' support
|
# for 'autogenerate' support
|
||||||
# from myapp import mymodel
|
# from myapp import mymodel
|
||||||
# target_metadata = mymodel.Base.metadata
|
# target_metadata = mymodel.Base.metadata
|
||||||
from flask import current_app
|
|
||||||
config.set_main_option(
|
config.set_main_option(
|
||||||
'sqlalchemy.url', current_app.config.get(
|
'sqlalchemy.url',
|
||||||
'SQLALCHEMY_DATABASE_URI').replace('%', '%%'))
|
str(current_app.extensions['migrate'].db.get_engine().url).replace(
|
||||||
|
'%', '%%'))
|
||||||
target_metadata = current_app.extensions['migrate'].db.metadata
|
target_metadata = current_app.extensions['migrate'].db.metadata
|
||||||
|
|
||||||
# other values from the config, defined by the needs of env.py,
|
# other values from the config, defined by the needs of env.py,
|
||||||
@ -72,11 +71,7 @@ def run_migrations_online():
|
|||||||
directives[:] = []
|
directives[:] = []
|
||||||
logger.info('No changes in schema detected.')
|
logger.info('No changes in schema detected.')
|
||||||
|
|
||||||
connectable = engine_from_config(
|
connectable = current_app.extensions['migrate'].db.get_engine()
|
||||||
config.get_section(config.config_ini_section),
|
|
||||||
prefix='sqlalchemy.',
|
|
||||||
poolclass=pool.NullPool,
|
|
||||||
)
|
|
||||||
|
|
||||||
with connectable.connect() as connection:
|
with connectable.connect() as connection:
|
||||||
context.configure(
|
context.configure(
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
"""empty message
|
"""empty message
|
||||||
|
|
||||||
Revision ID: da9fd175af8c
|
Revision ID: 097aae1f02d7
|
||||||
Revises:
|
Revises:
|
||||||
Create Date: 2019-10-23 08:11:02.741683
|
Create Date: 2022-02-08 10:02:03.748588
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
@ -10,7 +10,7 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = 'da9fd175af8c'
|
revision = '097aae1f02d7'
|
||||||
down_revision = None
|
down_revision = None
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
@ -29,67 +29,108 @@ def upgrade():
|
|||||||
op.create_index(op.f('ix_roles_default'), 'roles', ['default'], unique=False)
|
op.create_index(op.f('ix_roles_default'), 'roles', ['default'], unique=False)
|
||||||
op.create_table('users',
|
op.create_table('users',
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('role_id', sa.Integer(), nullable=True),
|
||||||
sa.Column('confirmed', sa.Boolean(), nullable=True),
|
sa.Column('confirmed', sa.Boolean(), nullable=True),
|
||||||
sa.Column('email', sa.String(length=254), nullable=True),
|
sa.Column('email', sa.String(length=254), nullable=True),
|
||||||
|
sa.Column('last_seen', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('member_since', sa.DateTime(), nullable=True),
|
||||||
sa.Column('password_hash', sa.String(length=128), nullable=True),
|
sa.Column('password_hash', sa.String(length=128), nullable=True),
|
||||||
sa.Column('registration_date', sa.DateTime(), nullable=True),
|
sa.Column('token', sa.String(length=32), nullable=True),
|
||||||
sa.Column('role_id', sa.Integer(), nullable=True),
|
sa.Column('token_expiration', sa.DateTime(), nullable=True),
|
||||||
sa.Column('username', sa.String(length=64), nullable=True),
|
sa.Column('username', sa.String(length=64), nullable=True),
|
||||||
sa.Column('is_dark', sa.Boolean(), nullable=True),
|
sa.Column('setting_dark_mode', sa.Boolean(), nullable=True),
|
||||||
|
sa.Column('setting_job_status_mail_notification_level', sa.Integer(), nullable=True),
|
||||||
sa.ForeignKeyConstraint(['role_id'], ['roles.id'], ),
|
sa.ForeignKeyConstraint(['role_id'], ['roles.id'], ),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id')
|
||||||
)
|
)
|
||||||
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
|
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
|
||||||
|
op.create_index(op.f('ix_users_token'), 'users', ['token'], unique=True)
|
||||||
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
|
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
|
||||||
op.create_table('corpora',
|
op.create_table('corpora',
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||||
sa.Column('creation_date', sa.DateTime(), nullable=True),
|
sa.Column('creation_date', sa.DateTime(), nullable=True),
|
||||||
sa.Column('description', sa.String(length=255), nullable=True),
|
sa.Column('description', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('last_edited_date', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('status', sa.Integer(), nullable=True),
|
||||||
sa.Column('title', sa.String(length=32), nullable=True),
|
sa.Column('title', sa.String(length=32), nullable=True),
|
||||||
sa.Column('user_id', sa.Integer(), nullable=True),
|
sa.Column('num_analysis_sessions', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('num_tokens', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('archive_file', sa.String(length=255), nullable=True),
|
||||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id')
|
||||||
)
|
)
|
||||||
op.create_table('jobs',
|
op.create_table('jobs',
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||||
sa.Column('creation_date', sa.DateTime(), nullable=True),
|
sa.Column('creation_date', sa.DateTime(), nullable=True),
|
||||||
sa.Column('description', sa.String(length=255), nullable=True),
|
sa.Column('description', sa.String(length=255), nullable=True),
|
||||||
sa.Column('end_date', sa.DateTime(), nullable=True),
|
sa.Column('end_date', sa.DateTime(), nullable=True),
|
||||||
sa.Column('mem_mb', sa.Integer(), nullable=True),
|
|
||||||
sa.Column('n_cores', sa.Integer(), nullable=True),
|
|
||||||
sa.Column('service', sa.String(length=64), nullable=True),
|
sa.Column('service', sa.String(length=64), nullable=True),
|
||||||
sa.Column('service_args', sa.String(length=255), nullable=True),
|
sa.Column('service_args', sa.String(length=255), nullable=True),
|
||||||
sa.Column('service_version', sa.String(length=16), nullable=True),
|
sa.Column('service_version', sa.String(length=16), nullable=True),
|
||||||
sa.Column('status', sa.String(length=16), nullable=True),
|
sa.Column('status', sa.Integer(), nullable=True),
|
||||||
sa.Column('title', sa.String(length=32), nullable=True),
|
sa.Column('title', sa.String(length=32), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('tesseract_ocr_models',
|
||||||
|
sa.Column('creation_date', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('filename', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('last_edited_date', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('mimetype', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('user_id', sa.Integer(), nullable=True),
|
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('compatible_service_versions', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('description', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('publisher', sa.String(length=128), nullable=True),
|
||||||
|
sa.Column('publishing_year', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('title', sa.String(length=64), nullable=True),
|
||||||
|
sa.Column('version', sa.String(length=16), nullable=True),
|
||||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id')
|
||||||
)
|
)
|
||||||
op.create_table('corpus_files',
|
op.create_table('corpus_files',
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('creation_date', sa.DateTime(), nullable=True),
|
||||||
sa.Column('filename', sa.String(length=255), nullable=True),
|
sa.Column('filename', sa.String(length=255), nullable=True),
|
||||||
sa.Column('dir', sa.String(length=255), nullable=True),
|
sa.Column('last_edited_date', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('mimetype', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('corpus_id', sa.Integer(), nullable=True),
|
sa.Column('corpus_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('address', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('author', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('booktitle', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('chapter', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('editor', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('institution', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('journal', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('pages', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('publisher', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('publishing_year', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('school', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('title', sa.String(length=255), nullable=True),
|
||||||
sa.ForeignKeyConstraint(['corpus_id'], ['corpora.id'], ),
|
sa.ForeignKeyConstraint(['corpus_id'], ['corpora.id'], ),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id')
|
||||||
)
|
)
|
||||||
op.create_table('job_inputs',
|
op.create_table('job_inputs',
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('creation_date', sa.DateTime(), nullable=True),
|
||||||
sa.Column('filename', sa.String(length=255), nullable=True),
|
sa.Column('filename', sa.String(length=255), nullable=True),
|
||||||
sa.Column('dir', sa.String(length=255), nullable=True),
|
sa.Column('last_edited_date', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('mimetype', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('job_id', sa.Integer(), nullable=True),
|
sa.Column('job_id', sa.Integer(), nullable=True),
|
||||||
sa.ForeignKeyConstraint(['job_id'], ['jobs.id'], ),
|
sa.ForeignKeyConstraint(['job_id'], ['jobs.id'], ),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id')
|
||||||
)
|
)
|
||||||
op.create_table('job_results',
|
op.create_table('job_results',
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('creation_date', sa.DateTime(), nullable=True),
|
||||||
sa.Column('filename', sa.String(length=255), nullable=True),
|
sa.Column('filename', sa.String(length=255), nullable=True),
|
||||||
sa.Column('dir', sa.String(length=255), nullable=True),
|
sa.Column('last_edited_date', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('mimetype', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('job_id', sa.Integer(), nullable=True),
|
sa.Column('job_id', sa.Integer(), nullable=True),
|
||||||
sa.Column('job_input_id', sa.Integer(), nullable=True),
|
sa.Column('description', sa.String(length=255), nullable=True),
|
||||||
sa.ForeignKeyConstraint(['job_id'], ['jobs.id'], ),
|
sa.ForeignKeyConstraint(['job_id'], ['jobs.id'], ),
|
||||||
sa.ForeignKeyConstraint(['job_input_id'], ['job_inputs.id'], ),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id')
|
||||||
)
|
)
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
@ -100,9 +141,11 @@ def downgrade():
|
|||||||
op.drop_table('job_results')
|
op.drop_table('job_results')
|
||||||
op.drop_table('job_inputs')
|
op.drop_table('job_inputs')
|
||||||
op.drop_table('corpus_files')
|
op.drop_table('corpus_files')
|
||||||
|
op.drop_table('tesseract_ocr_models')
|
||||||
op.drop_table('jobs')
|
op.drop_table('jobs')
|
||||||
op.drop_table('corpora')
|
op.drop_table('corpora')
|
||||||
op.drop_index(op.f('ix_users_username'), table_name='users')
|
op.drop_index(op.f('ix_users_username'), table_name='users')
|
||||||
|
op.drop_index(op.f('ix_users_token'), table_name='users')
|
||||||
op.drop_index(op.f('ix_users_email'), table_name='users')
|
op.drop_index(op.f('ix_users_email'), table_name='users')
|
||||||
op.drop_table('users')
|
op.drop_table('users')
|
||||||
op.drop_index(op.f('ix_roles_default'), table_name='roles')
|
op.drop_index(op.f('ix_roles_default'), table_name='roles')
|
@ -1,30 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 099037c4aa06
|
|
||||||
Revises: 66253783654f
|
|
||||||
Create Date: 2020-04-27 09:17:15.039728
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '099037c4aa06'
|
|
||||||
down_revision = '66253783654f'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('users', sa.Column('last_seen', sa.DateTime(), nullable=True))
|
|
||||||
op.add_column('users', sa.Column('setting_site_job_status_notifications', sa.String(length=16), nullable=True))
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_column('users', 'setting_site_job_status_notifications')
|
|
||||||
op.drop_column('users', 'last_seen')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,28 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 0aa38a7973c5
|
|
||||||
Revises: 1210adfe1e34
|
|
||||||
Create Date: 2019-11-06 09:33:46.296653
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '0aa38a7973c5'
|
|
||||||
down_revision = '1210adfe1e34'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('corpora', sa.Column('analysis_ip', sa.String(length=16), nullable=True))
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_column('corpora', 'analysis_ip')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,28 +0,0 @@
|
|||||||
"""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 ###
|
|
@ -1,30 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 10a92d8f4616
|
|
||||||
Revises: 4638e6509e13
|
|
||||||
Create Date: 2020-05-12 11:24:46.022674
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '10a92d8f4616'
|
|
||||||
down_revision = '4638e6509e13'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('notifications_data', sa.Column('notified_on', sa.String(length=16), nullable=True))
|
|
||||||
op.drop_column('notifications_data', 'notified')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('notifications_data', sa.Column('notified', sa.BOOLEAN(), autoincrement=False, nullable=True))
|
|
||||||
op.drop_column('notifications_data', 'notified_on')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,28 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 1210adfe1e34
|
|
||||||
Revises: abf60427ff84
|
|
||||||
Create Date: 2019-11-04 12:54:39.389263
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '1210adfe1e34'
|
|
||||||
down_revision = 'abf60427ff84'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('corpora', sa.Column('status', sa.String(length=16), nullable=True))
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_column('corpora', 'status')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,28 +0,0 @@
|
|||||||
"""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 ###
|
|
@ -1,30 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 33ec4d09b4ca
|
|
||||||
Revises: 4cf5e5606a83
|
|
||||||
Create Date: 2020-07-13 09:07:19.297185
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '33ec4d09b4ca'
|
|
||||||
down_revision = '4cf5e5606a83'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('query_results', sa.Column('description', sa.String(length=255), nullable=True))
|
|
||||||
op.add_column('query_results', sa.Column('title', sa.String(length=32), nullable=True))
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_column('query_results', 'title')
|
|
||||||
op.drop_column('query_results', 'description')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,28 +0,0 @@
|
|||||||
"""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 ###
|
|
@ -1,28 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 3d9a20b8b26c
|
|
||||||
Revises: 421ba4373e50
|
|
||||||
Create Date: 2020-05-14 09:30:56.266381
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '3d9a20b8b26c'
|
|
||||||
down_revision = '421ba4373e50'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('notification_email_data', sa.Column('notify_status', sa.String(length=16), nullable=True))
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_column('notification_email_data', 'notify_status')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,33 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 421ba4373e50
|
|
||||||
Revises: 5ba6786a847e
|
|
||||||
Create Date: 2020-05-14 08:35:47.367125
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '421ba4373e50'
|
|
||||||
down_revision = '5ba6786a847e'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.create_table('notification_email_data',
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('job_id', sa.Integer(), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(['job_id'], ['jobs.id'], ),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_table('notification_email_data')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,38 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 4638e6509e13
|
|
||||||
Revises: 471aa04c1a92
|
|
||||||
Create Date: 2020-05-12 06:42:04.475585
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '4638e6509e13'
|
|
||||||
down_revision = '471aa04c1a92'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('notifications_data', sa.Column('notified', sa.Boolean(), nullable=True))
|
|
||||||
op.drop_column('notifications_data', 'notified_on_queued')
|
|
||||||
op.drop_column('notifications_data', 'notified_on_running')
|
|
||||||
op.drop_column('notifications_data', 'notified_on_submitted')
|
|
||||||
op.drop_column('notifications_data', 'notified_on_complete')
|
|
||||||
op.drop_column('notifications_data', 'notified_on_canceling')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('notifications_data', sa.Column('notified_on_canceling', sa.BOOLEAN(), autoincrement=False, nullable=True))
|
|
||||||
op.add_column('notifications_data', sa.Column('notified_on_complete', sa.BOOLEAN(), autoincrement=False, nullable=True))
|
|
||||||
op.add_column('notifications_data', sa.Column('notified_on_submitted', sa.BOOLEAN(), autoincrement=False, nullable=True))
|
|
||||||
op.add_column('notifications_data', sa.Column('notified_on_running', sa.BOOLEAN(), autoincrement=False, nullable=True))
|
|
||||||
op.add_column('notifications_data', sa.Column('notified_on_queued', sa.BOOLEAN(), autoincrement=False, nullable=True))
|
|
||||||
op.drop_column('notifications_data', 'notified')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,38 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 471aa04c1a92
|
|
||||||
Revises: 62233e0cb2c7
|
|
||||||
Create Date: 2020-05-11 14:07:12.934869
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '471aa04c1a92'
|
|
||||||
down_revision = '62233e0cb2c7'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.create_table('notifications_data',
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('job_id', sa.Integer(), nullable=True),
|
|
||||||
sa.Column('notified_on_submitted', sa.Boolean(), nullable=True),
|
|
||||||
sa.Column('notified_on_queued', sa.Boolean(), nullable=True),
|
|
||||||
sa.Column('notified_on_running', sa.Boolean(), nullable=True),
|
|
||||||
sa.Column('notified_on_complete', sa.Boolean(), nullable=True),
|
|
||||||
sa.Column('notified_on_canceling', sa.Boolean(), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(['job_id'], ['jobs.id'], ),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_table('notifications_data')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,28 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 4886241e0f5d
|
|
||||||
Revises: 3d9a20b8b26c
|
|
||||||
Create Date: 2020-05-14 11:58:08.498454
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '4886241e0f5d'
|
|
||||||
down_revision = '3d9a20b8b26c'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('notification_email_data', sa.Column('creation_date', sa.DateTime(), nullable=True))
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_column('notification_email_data', 'creation_date')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,36 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 49a42c69e523
|
|
||||||
Revises: 099037c4aa06
|
|
||||||
Create Date: 2020-04-27 11:18:32.999099
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
from sqlalchemy.dialects import postgresql
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '49a42c69e523'
|
|
||||||
down_revision = '099037c4aa06'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('users', sa.Column('member_since', sa.DateTime(), nullable=True))
|
|
||||||
op.add_column('users', sa.Column('setting_job_status_mail_notifications', sa.String(length=16), nullable=True))
|
|
||||||
op.add_column('users', sa.Column('setting_job_status_site_notifications', sa.String(length=16), nullable=True))
|
|
||||||
op.drop_column('users', 'setting_site_job_status_notifications')
|
|
||||||
op.drop_column('users', 'registration_date')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('users', sa.Column('registration_date', postgresql.TIMESTAMP(), autoincrement=False, nullable=True))
|
|
||||||
op.add_column('users', sa.Column('setting_site_job_status_notifications', sa.VARCHAR(length=16), autoincrement=False, nullable=True))
|
|
||||||
op.drop_column('users', 'setting_job_status_site_notifications')
|
|
||||||
op.drop_column('users', 'setting_job_status_mail_notifications')
|
|
||||||
op.drop_column('users', 'member_since')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,35 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 4cf5e5606a83
|
|
||||||
Revises: e256f5cac75d
|
|
||||||
Create Date: 2020-07-13 08:30:57.369850
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '4cf5e5606a83'
|
|
||||||
down_revision = 'e256f5cac75d'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.create_table('query_results',
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('user_id', sa.Integer(), nullable=True),
|
|
||||||
sa.Column('filename', sa.String(length=255), nullable=True),
|
|
||||||
sa.Column('query_metadata', sa.JSON(), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_table('query_results')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,30 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 55d2b1a82ba9
|
|
||||||
Revises: 8b2e0d43384a
|
|
||||||
Create Date: 2021-04-14 12:10:08.675542
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '55d2b1a82ba9'
|
|
||||||
down_revision = '8b2e0d43384a'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_column('jobs', 'mem_mb')
|
|
||||||
op.drop_column('jobs', 'n_cores')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('jobs', sa.Column('n_cores', sa.INTEGER(), autoincrement=False, nullable=True))
|
|
||||||
op.add_column('jobs', sa.Column('mem_mb', sa.INTEGER(), autoincrement=False, nullable=True))
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,42 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 5ba6786a847e
|
|
||||||
Revises: 10a92d8f4616
|
|
||||||
Create Date: 2020-05-12 13:15:56.265610
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '5ba6786a847e'
|
|
||||||
down_revision = '10a92d8f4616'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.create_table('notification_data',
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('job_id', sa.Integer(), nullable=True),
|
|
||||||
sa.Column('notified_on', sa.String(length=16), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(['job_id'], ['jobs.id'], ),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
op.drop_table('notifications_data')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.create_table('notifications_data',
|
|
||||||
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
|
|
||||||
sa.Column('job_id', sa.INTEGER(), autoincrement=False, nullable=True),
|
|
||||||
sa.Column('notified_on', sa.VARCHAR(length=16), autoincrement=False, nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(['job_id'], ['jobs.id'], name='notifications_data_job_id_fkey'),
|
|
||||||
sa.PrimaryKeyConstraint('id', name='notifications_data_pkey')
|
|
||||||
)
|
|
||||||
op.drop_table('notification_data')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,34 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 62233e0cb2c7
|
|
||||||
Revises: 68772b6560c3
|
|
||||||
Create Date: 2020-05-04 09:42:25.408403
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '62233e0cb2c7'
|
|
||||||
down_revision = '68772b6560c3'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('corpora', sa.Column('current_nr_of_tokens', sa.BigInteger(), nullable=True))
|
|
||||||
op.add_column('corpora', sa.Column('max_nr_of_tokens', sa.BigInteger(), nullable=True))
|
|
||||||
op.drop_column('corpora', 'analysis_container_name')
|
|
||||||
op.drop_column('corpora', 'analysis_container_ip')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('corpora', sa.Column('analysis_container_ip', sa.VARCHAR(length=16), autoincrement=False, nullable=True))
|
|
||||||
op.add_column('corpora', sa.Column('analysis_container_name', sa.VARCHAR(length=32), autoincrement=False, nullable=True))
|
|
||||||
op.drop_column('corpora', 'max_nr_of_tokens')
|
|
||||||
op.drop_column('corpora', 'current_nr_of_tokens')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,30 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 6227310c2112
|
|
||||||
Revises: ded5a37f8a7b
|
|
||||||
Create Date: 2020-01-30 09:28:06.770159
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '6227310c2112'
|
|
||||||
down_revision = 'ded5a37f8a7b'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_constraint('job_results_job_input_id_fkey', 'job_results', type_='foreignkey')
|
|
||||||
op.drop_column('job_results', 'job_input_id')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('job_results', sa.Column('job_input_id', sa.INTEGER(), autoincrement=False, nullable=True))
|
|
||||||
op.create_foreign_key('job_results_job_input_id_fkey', 'job_results', 'job_inputs', ['job_input_id'], ['id'])
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,30 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 66253783654f
|
|
||||||
Revises: 7378391345fa
|
|
||||||
Create Date: 2020-04-27 08:26:19.772088
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '66253783654f'
|
|
||||||
down_revision = '7378391345fa'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('users', sa.Column('setting_dark_mode', sa.Boolean(), nullable=True))
|
|
||||||
op.drop_column('users', 'is_dark')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('users', sa.Column('is_dark', sa.BOOLEAN(), autoincrement=False, nullable=True))
|
|
||||||
op.drop_column('users', 'setting_dark_mode')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,28 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 68772b6560c3
|
|
||||||
Revises: 49a42c69e523
|
|
||||||
Create Date: 2020-04-28 07:47:40.495698
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '68772b6560c3'
|
|
||||||
down_revision = '49a42c69e523'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('corpora', sa.Column('last_edited_date', sa.DateTime(), nullable=True))
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_column('corpora', 'last_edited_date')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,50 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 68ed092ffe5e
|
|
||||||
Revises: be010d5d708d
|
|
||||||
Create Date: 2021-11-24 15:33:16.258600
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '68ed092ffe5e'
|
|
||||||
down_revision = 'be010d5d708d'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('corpus_files', sa.Column('creation_date', sa.DateTime(), nullable=True))
|
|
||||||
op.add_column('corpus_files', sa.Column('last_edited_date', sa.DateTime(), nullable=True))
|
|
||||||
op.add_column('corpus_files', sa.Column('mimetype', sa.String(length=255), nullable=True))
|
|
||||||
op.add_column('job_inputs', sa.Column('creation_date', sa.DateTime(), nullable=True))
|
|
||||||
op.add_column('job_inputs', sa.Column('last_edited_date', sa.DateTime(), nullable=True))
|
|
||||||
op.add_column('job_inputs', sa.Column('mimetype', sa.String(length=255), nullable=True))
|
|
||||||
op.add_column('job_results', sa.Column('creation_date', sa.DateTime(), nullable=True))
|
|
||||||
op.add_column('job_results', sa.Column('last_edited_date', sa.DateTime(), nullable=True))
|
|
||||||
op.add_column('job_results', sa.Column('mimetype', sa.String(length=255), nullable=True))
|
|
||||||
op.add_column('query_results', sa.Column('creation_date', sa.DateTime(), nullable=True))
|
|
||||||
op.add_column('query_results', sa.Column('last_edited_date', sa.DateTime(), nullable=True))
|
|
||||||
op.add_column('query_results', sa.Column('mimetype', sa.String(length=255), nullable=True))
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_column('query_results', 'mimetype')
|
|
||||||
op.drop_column('query_results', 'last_edited_date')
|
|
||||||
op.drop_column('query_results', 'creation_date')
|
|
||||||
op.drop_column('job_results', 'mimetype')
|
|
||||||
op.drop_column('job_results', 'last_edited_date')
|
|
||||||
op.drop_column('job_results', 'creation_date')
|
|
||||||
op.drop_column('job_inputs', 'mimetype')
|
|
||||||
op.drop_column('job_inputs', 'last_edited_date')
|
|
||||||
op.drop_column('job_inputs', 'creation_date')
|
|
||||||
op.drop_column('corpus_files', 'mimetype')
|
|
||||||
op.drop_column('corpus_files', 'last_edited_date')
|
|
||||||
op.drop_column('corpus_files', 'creation_date')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,59 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 6c2227f1cc77
|
|
||||||
Revises: befe5326787e
|
|
||||||
Create Date: 2020-12-02 08:50:45.880062
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
from sqlalchemy.dialects import postgresql
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '6c2227f1cc77'
|
|
||||||
down_revision = 'befe5326787e'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_table('notification_data')
|
|
||||||
op.drop_table('notification_email_data')
|
|
||||||
op.drop_column('corpus_files', 'dir')
|
|
||||||
op.drop_column('job_inputs', 'dir')
|
|
||||||
op.drop_column('job_results', 'dir')
|
|
||||||
op.drop_column('jobs', 'secure_filename')
|
|
||||||
op.alter_column('roles', 'permissions',
|
|
||||||
existing_type=sa.BIGINT(),
|
|
||||||
type_=sa.Integer(),
|
|
||||||
existing_nullable=True)
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.alter_column('roles', 'permissions',
|
|
||||||
existing_type=sa.Integer(),
|
|
||||||
type_=sa.BIGINT(),
|
|
||||||
existing_nullable=True)
|
|
||||||
op.add_column('jobs', sa.Column('secure_filename', sa.VARCHAR(length=32), autoincrement=False, nullable=True))
|
|
||||||
op.add_column('job_results', sa.Column('dir', sa.VARCHAR(length=255), autoincrement=False, nullable=True))
|
|
||||||
op.add_column('job_inputs', sa.Column('dir', sa.VARCHAR(length=255), autoincrement=False, nullable=True))
|
|
||||||
op.add_column('corpus_files', sa.Column('dir', sa.VARCHAR(length=255), autoincrement=False, nullable=True))
|
|
||||||
op.create_table('notification_email_data',
|
|
||||||
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
|
|
||||||
sa.Column('job_id', sa.INTEGER(), autoincrement=False, nullable=True),
|
|
||||||
sa.Column('notify_status', sa.VARCHAR(length=16), autoincrement=False, nullable=True),
|
|
||||||
sa.Column('creation_date', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(['job_id'], ['jobs.id'], name='notification_email_data_job_id_fkey'),
|
|
||||||
sa.PrimaryKeyConstraint('id', name='notification_email_data_pkey')
|
|
||||||
)
|
|
||||||
op.create_table('notification_data',
|
|
||||||
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
|
|
||||||
sa.Column('job_id', sa.INTEGER(), autoincrement=False, nullable=True),
|
|
||||||
sa.Column('notified_on', sa.VARCHAR(length=16), autoincrement=False, nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(['job_id'], ['jobs.id'], name='notification_data_job_id_fkey'),
|
|
||||||
sa.PrimaryKeyConstraint('id', name='notification_data_pkey')
|
|
||||||
)
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,28 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 7378391345fa
|
|
||||||
Revises: 6227310c2112
|
|
||||||
Create Date: 2020-02-17 12:29:17.851954
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '7378391345fa'
|
|
||||||
down_revision = '6227310c2112'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('jobs', sa.Column('secure_filename', sa.String(length=32), nullable=True))
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_column('jobs', 'secure_filename')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,32 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 776761fb7466
|
|
||||||
Revises: 0aa38a7973c5
|
|
||||||
Create Date: 2019-11-07 08:34:01.676055
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '776761fb7466'
|
|
||||||
down_revision = '0aa38a7973c5'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('corpora', sa.Column('analysis_container_ip', sa.String(length=16), nullable=True))
|
|
||||||
op.add_column('corpora', sa.Column('analysis_container_name', sa.String(length=32), nullable=True))
|
|
||||||
op.drop_column('corpora', 'analysis_ip')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('corpora', sa.Column('analysis_ip', sa.VARCHAR(length=16), autoincrement=False, nullable=True))
|
|
||||||
op.drop_column('corpora', 'analysis_container_name')
|
|
||||||
op.drop_column('corpora', 'analysis_container_ip')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,42 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 790ce9512e75
|
|
||||||
Revises: 6c2227f1cc77
|
|
||||||
Create Date: 2021-01-25 11:13:36.953269
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '790ce9512e75'
|
|
||||||
down_revision = '6c2227f1cc77'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.alter_column('corpora', 'current_nr_of_tokens',
|
|
||||||
existing_type=sa.BIGINT(),
|
|
||||||
type_=sa.Integer(),
|
|
||||||
existing_nullable=True)
|
|
||||||
op.alter_column('corpora', 'max_nr_of_tokens',
|
|
||||||
existing_type=sa.BIGINT(),
|
|
||||||
type_=sa.Integer(),
|
|
||||||
existing_nullable=True)
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.alter_column('corpora', 'max_nr_of_tokens',
|
|
||||||
existing_type=sa.Integer(),
|
|
||||||
type_=sa.BIGINT(),
|
|
||||||
existing_nullable=True)
|
|
||||||
op.alter_column('corpora', 'current_nr_of_tokens',
|
|
||||||
existing_type=sa.Integer(),
|
|
||||||
type_=sa.BIGINT(),
|
|
||||||
existing_nullable=True)
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,28 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 8b2e0d43384a
|
|
||||||
Revises: 790ce9512e75
|
|
||||||
Create Date: 2021-01-25 11:18:45.256537
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '8b2e0d43384a'
|
|
||||||
down_revision = '790ce9512e75'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_column('corpora', 'max_nr_of_tokens')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('corpora', sa.Column('max_nr_of_tokens', sa.INTEGER(), autoincrement=False, nullable=True))
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,50 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 9d21b228d353
|
|
||||||
Revises: 33ec4d09b4ca
|
|
||||||
Create Date: 2020-07-15 08:58:59.062442
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '9d21b228d353'
|
|
||||||
down_revision = '33ec4d09b4ca'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.alter_column('corpus_files', 'author',
|
|
||||||
existing_type=sa.VARCHAR(length=64),
|
|
||||||
type_=sa.String(length=255),
|
|
||||||
existing_nullable=True)
|
|
||||||
op.alter_column('corpus_files', 'title',
|
|
||||||
existing_type=sa.VARCHAR(length=64),
|
|
||||||
type_=sa.String(length=255),
|
|
||||||
existing_nullable=True)
|
|
||||||
op.alter_column('roles', 'permissions',
|
|
||||||
existing_type=sa.INTEGER(),
|
|
||||||
type_=sa.BigInteger(),
|
|
||||||
existing_nullable=True)
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.alter_column('roles', 'permissions',
|
|
||||||
existing_type=sa.BigInteger(),
|
|
||||||
type_=sa.INTEGER(),
|
|
||||||
existing_nullable=True)
|
|
||||||
op.alter_column('corpus_files', 'title',
|
|
||||||
existing_type=sa.String(length=255),
|
|
||||||
type_=sa.VARCHAR(length=64),
|
|
||||||
existing_nullable=True)
|
|
||||||
op.alter_column('corpus_files', 'author',
|
|
||||||
existing_type=sa.String(length=255),
|
|
||||||
type_=sa.VARCHAR(length=64),
|
|
||||||
existing_nullable=True)
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,30 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: a4b3cf4ab098
|
|
||||||
Revises: c384d7b3268a
|
|
||||||
Create Date: 2021-09-23 13:14:16.227784
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = 'a4b3cf4ab098'
|
|
||||||
down_revision = 'c384d7b3268a'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('corpora', sa.Column('num_tokens', sa.Integer(), nullable=True))
|
|
||||||
op.drop_column('corpora', 'current_nr_of_tokens')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('corpora', sa.Column('current_nr_of_tokens', sa.INTEGER(), autoincrement=False, nullable=True))
|
|
||||||
op.drop_column('corpora', 'num_tokens')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,32 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: abf60427ff84
|
|
||||||
Revises: da9fd175af8c
|
|
||||||
Create Date: 2019-10-28 14:43:39.691313
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = 'abf60427ff84'
|
|
||||||
down_revision = 'da9fd175af8c'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('corpus_files', sa.Column('author', sa.String(length=64), nullable=True))
|
|
||||||
op.add_column('corpus_files', sa.Column('publishing_year', sa.Integer(), nullable=True))
|
|
||||||
op.add_column('corpus_files', sa.Column('title', sa.String(length=64), nullable=True))
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_column('corpus_files', 'title')
|
|
||||||
op.drop_column('corpus_files', 'publishing_year')
|
|
||||||
op.drop_column('corpus_files', 'author')
|
|
||||||
# ### end Alembic commands ###
|
|
@ -1,45 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: ad0d835fe5b1
|
|
||||||
Revises: 68ed092ffe5e
|
|
||||||
Create Date: 2022-01-18 16:23:45.673993
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = 'ad0d835fe5b1'
|
|
||||||
down_revision = '68ed092ffe5e'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.create_table('tesseract_ocr_models',
|
|
||||||
sa.Column('creation_date', sa.DateTime(), nullable=True),
|
|
||||||
sa.Column('filename', sa.String(length=255), nullable=True),
|
|
||||||
sa.Column('last_edited_date', sa.DateTime(), nullable=True),
|
|
||||||
sa.Column('mimetype', sa.String(length=255), nullable=True),
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('user_id', sa.Integer(), nullable=True),
|
|
||||||
sa.Column('compatible_service_versions', sa.String(length=255), nullable=True),
|
|
||||||
sa.Column('description', sa.String(length=255), nullable=True),
|
|
||||||
sa.Column('publisher', sa.String(length=128), nullable=True),
|
|
||||||
sa.Column('publishing_year', sa.Integer(), nullable=True),
|
|
||||||
sa.Column('title', sa.String(length=64), nullable=True),
|
|
||||||
sa.Column('version', sa.String(length=16), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
op.add_column('job_results', sa.Column('description', sa.String(length=255), nullable=True))
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_column('job_results', 'description')
|
|
||||||
op.drop_table('tesseract_ocr_models')
|
|
||||||
# ### end Alembic commands ###
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user