Skip to content
Snippets Groups Projects
Commit 61a6ddd4 authored by Inga Kirschnick's avatar Inga Kirschnick
Browse files

Privacy settings for profile pages

parent 7856e974
No related branches found
No related tags found
No related merge requests found
Showing
with 428 additions and 763 deletions
...@@ -90,7 +90,4 @@ def create_app(config: Config = Config) -> Flask: ...@@ -90,7 +90,4 @@ def create_app(config: Config = Config) -> Flask:
from .users import bp as users_blueprint from .users import bp as users_blueprint
app.register_blueprint(users_blueprint, url_prefix='/users') app.register_blueprint(users_blueprint, url_prefix='/users')
from .test import bp as test_blueprint
app.register_blueprint(test_blueprint, url_prefix='/test')
return app return app
...@@ -66,7 +66,7 @@ class TesseractOCRPipelineModelSchema(ma.SQLAlchemySchema): ...@@ -66,7 +66,7 @@ class TesseractOCRPipelineModelSchema(ma.SQLAlchemySchema):
publishing_year = ma.Int( publishing_year = ma.Int(
required=True required=True
) )
shared = ma.Boolean(required=True) is_public = ma.Boolean(required=True)
class JobSchema(ma.SQLAlchemySchema): class JobSchema(ma.SQLAlchemySchema):
......
...@@ -61,6 +61,12 @@ class UserSettingJobStatusMailNotificationLevel(IntEnum): ...@@ -61,6 +61,12 @@ class UserSettingJobStatusMailNotificationLevel(IntEnum):
NONE = 1 NONE = 1
END = 2 END = 2
ALL = 3 ALL = 3
class ProfilePrivacySettings(IntEnum):
SHOW_EMAIL = 1
SHOW_LAST_SEEN = 2
SHOW_MEMBER_SINCE = 4
# endregion enums # endregion enums
...@@ -220,7 +226,6 @@ class Role(HashidMixin, db.Model): ...@@ -220,7 +226,6 @@ class Role(HashidMixin, db.Model):
db.session.add(role) db.session.add(role)
db.session.commit() db.session.commit()
class Token(db.Model): class Token(db.Model):
__tablename__ = 'tokens' __tablename__ = 'tokens'
# Primary key # Primary key
...@@ -255,12 +260,20 @@ class Avatar(HashidMixin, FileMixin, db.Model): ...@@ -255,12 +260,20 @@ class Avatar(HashidMixin, FileMixin, db.Model):
def path(self): def path(self):
return os.path.join(self.user.path, 'avatar') return os.path.join(self.user.path, 'avatar')
def delete(self):
try:
os.remove(self.path)
except OSError as e:
current_app.logger.error(e)
db.session.delete(self)
class User(HashidMixin, UserMixin, db.Model): class User(HashidMixin, UserMixin, db.Model):
__tablename__ = 'users' __tablename__ = 'users'
# Primary key # Primary key
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
# Foreign keys # Foreign keys
role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
# privacy_id = db.Column(db.Integer, db.ForeignKey('privacies.id'))
# Fields # Fields
email = db.Column(db.String(254), index=True, unique=True) email = db.Column(db.String(254), index=True, unique=True)
username = db.Column(db.String(64), index=True, unique=True) username = db.Column(db.String(64), index=True, unique=True)
...@@ -277,6 +290,8 @@ class User(HashidMixin, UserMixin, db.Model): ...@@ -277,6 +290,8 @@ class User(HashidMixin, UserMixin, db.Model):
location = db.Column(db.String(64)) location = db.Column(db.String(64))
website = db.Column(db.String(128)) website = db.Column(db.String(128))
organization = db.Column(db.String(128)) organization = db.Column(db.String(128))
is_public = db.Column(db.Boolean, default=False)
profile_privacy_settings = db.Column(db.Integer(), default=0)
# Backrefs: role: Role # Backrefs: role: Role
# Relationships # Relationships
avatar = db.relationship( avatar = db.relationship(
...@@ -502,6 +517,22 @@ class User(HashidMixin, UserMixin, db.Model): ...@@ -502,6 +517,22 @@ class User(HashidMixin, UserMixin, db.Model):
return False return False
return check_password_hash(self.password_hash, password) return check_password_hash(self.password_hash, password)
#region Profile Privacy settings
def has_profile_privacy_setting(self, setting):
return self.profile_privacy_settings & setting == setting
def add_profile_privacy_setting(self, setting):
if not self.has_profile_privacy_setting(setting):
self.profile_privacy_settings += setting
def remove_profile_privacy_setting(self, setting):
if self.has_profile_privacy_setting(setting):
self.profile_privacy_settings -= setting
def reset_profile_privacy_settings(self):
self.profile_privacy_settings = 0
#endregion Profile Privacy settings
def to_json_serializeable(self, backrefs=False, relationships=False): def to_json_serializeable(self, backrefs=False, relationships=False):
json_serializeable = { json_serializeable = {
'id': self.hashid, 'id': self.hashid,
...@@ -519,7 +550,12 @@ class User(HashidMixin, UserMixin, db.Model): ...@@ -519,7 +550,12 @@ class User(HashidMixin, UserMixin, db.Model):
'location': self.location, 'location': self.location,
'organization': self.organization, 'organization': self.organization,
'job_status_mail_notification_level': \ 'job_status_mail_notification_level': \
self.setting_job_status_mail_notification_level.name self.setting_job_status_mail_notification_level.name,
'is_public': self.is_public,
'show_email': self.has_profile_privacy_setting(ProfilePrivacySettings.SHOW_EMAIL),
'show_last_seen': self.has_profile_privacy_setting(ProfilePrivacySettings.SHOW_LAST_SEEN),
'show_member_since': self.has_profile_privacy_setting(ProfilePrivacySettings.SHOW_MEMBER_SINCE)
} }
if backrefs: if backrefs:
json_serializeable['role'] = \ json_serializeable['role'] = \
......
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import ( from wtforms import (
BooleanField,
FileField, FileField,
StringField, StringField,
SubmitField, SubmitField,
...@@ -16,9 +17,6 @@ from app.models import User ...@@ -16,9 +17,6 @@ from app.models import User
from app.auth import USERNAME_REGEX from app.auth import USERNAME_REGEX
class EditProfileSettingsForm(FlaskForm): class EditProfileSettingsForm(FlaskForm):
avatar = FileField(
'Image File'
)
email = StringField( email = StringField(
'E-Mail', 'E-Mail',
validators=[InputRequired(), Length(max=254), Email()] validators=[InputRequired(), Length(max=254), Email()]
...@@ -37,6 +35,26 @@ class EditProfileSettingsForm(FlaskForm): ...@@ -37,6 +35,26 @@ class EditProfileSettingsForm(FlaskForm):
) )
] ]
) )
submit = SubmitField()
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
self.user = user
def validate_email(self, field):
if (field.data != self.user.email
and User.query.filter_by(email=field.data).first()):
raise ValidationError('Email already registered')
def validate_username(self, field):
if (field.data != self.user.username
and User.query.filter_by(username=field.data).first()):
raise ValidationError('Username already in use')
class EditPublicProfileInformationForm(FlaskForm):
avatar = FileField(
'Image File'
)
full_name = StringField( full_name = StringField(
'Full name', 'Full name',
validators=[Length(max=128)] validators=[Length(max=128)]
...@@ -68,20 +86,13 @@ class EditProfileSettingsForm(FlaskForm): ...@@ -68,20 +86,13 @@ class EditProfileSettingsForm(FlaskForm):
submit = SubmitField() submit = SubmitField()
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
self.user = user
def validate_email(self, field):
if (field.data != self.user.email
and User.query.filter_by(email=field.data).first()):
raise ValidationError('Email already registered')
def validate_username(self, field):
if (field.data != self.user.username
and User.query.filter_by(username=field.data).first()):
raise ValidationError('Username already in use')
def validate_image_file(self, field): def validate_image_file(self, field):
if not field.data.filename.lower().endswith('.jpg' or '.png' or '.jpeg'): if not field.data.filename.lower().endswith('.jpg' or '.png' or '.jpeg'):
raise ValidationError('only .jpg, .png and .jpeg!') raise ValidationError('only .jpg, .png and .jpeg!')
class EditPrivacySettingsForm(FlaskForm):
is_public = BooleanField('Public profile')
show_email = BooleanField('Show email')
show_last_seen = BooleanField('Show last seen')
show_member_since = BooleanField('Show member since')
submit = SubmitField()
from flask import ( from flask import (
abort, abort,
current_app,
flash, flash,
Markup, Markup,
redirect, redirect,
...@@ -8,12 +9,15 @@ from flask import ( ...@@ -8,12 +9,15 @@ from flask import (
url_for url_for
) )
from flask_login import current_user, login_required from flask_login import current_user, login_required
from threading import Thread
import os import os
from app import db from app import db
from app.models import Avatar, User from app.models import Avatar, ProfilePrivacySettings, User
from . import bp from . import bp
from .forms import ( from .forms import (
EditProfileSettingsForm EditPrivacySettingsForm,
EditProfileSettingsForm,
EditPublicProfileInformationForm
) )
@bp.before_request @bp.before_request
...@@ -25,8 +29,12 @@ def before_request(): ...@@ -25,8 +29,12 @@ def before_request():
@bp.route('/<hashid:user_id>') @bp.route('/<hashid:user_id>')
def profile(user_id): def profile(user_id):
user = User.query.get_or_404(user_id) user = User.query.get_or_404(user_id)
user_data = user.to_json_serializeable()
if not user.is_public and user != current_user:
abort(403)
return render_template('profile/profile_page.html.j2', return render_template('profile/profile_page.html.j2',
user=user) user=user,
user_data=user_data)
@bp.route('/<hashid:user_id>/avatars/<hashid:avatar_id>') @bp.route('/<hashid:user_id>/avatars/<hashid:avatar_id>')
def avatar_download(user_id, avatar_id): def avatar_download(user_id, avatar_id):
...@@ -41,6 +49,21 @@ def avatar_download(user_id, avatar_id): ...@@ -41,6 +49,21 @@ def avatar_download(user_id, avatar_id):
mimetype=avatar_file.mimetype mimetype=avatar_file.mimetype
) )
@bp.route('/<hashid:user_id>/avatars/<hashid:avatar_id>', methods=['DELETE'])
def delete_avatar(avatar_id, user_id):
def _delete_avatar(app, avatar_id):
with app.app_context():
avatar_file = Avatar.query.get(avatar_id)
print(avatar_file)
avatar_file.delete()
db.session.commit()
thread = Thread(
target=_delete_avatar,
args=(current_app._get_current_object(), avatar_id)
)
thread.start()
return {}, 202
@bp.route('/<hashid:user_id>/edit-profile', methods=['GET', 'POST']) @bp.route('/<hashid:user_id>/edit-profile', methods=['GET', 'POST'])
def edit_profile(user_id): def edit_profile(user_id):
user = User.query.get_or_404(user_id) user = User.query.get_or_404(user_id)
...@@ -49,24 +72,58 @@ def edit_profile(user_id): ...@@ -49,24 +72,58 @@ def edit_profile(user_id):
data=current_user.to_json_serializeable(), data=current_user.to_json_serializeable(),
prefix='edit-profile-settings-form' prefix='edit-profile-settings-form'
) )
edit_privacy_settings_form = EditPrivacySettingsForm(
data=current_user.to_json_serializeable(),
prefix='edit-privacy-settings-form'
)
edit_public_profile_information_form = EditPublicProfileInformationForm(
data=current_user.to_json_serializeable(),
prefix='edit-public-profile-information-form'
)
if edit_profile_settings_form.validate_on_submit(): if edit_profile_settings_form.validate_on_submit():
if edit_profile_settings_form.avatar.data: current_user.email = edit_profile_settings_form.email.data
current_user.username = edit_profile_settings_form.username.data
db.session.commit()
message = Markup(f'Profile settings updated')
flash(message, 'success')
return redirect(url_for('.profile', user_id=user.id))
if (edit_privacy_settings_form.submit.data
and edit_privacy_settings_form.validate()):
current_user.is_public = edit_privacy_settings_form.is_public.data
if edit_privacy_settings_form.show_email.data:
current_user.add_profile_privacy_setting(ProfilePrivacySettings.SHOW_EMAIL)
else:
current_user.remove_profile_privacy_setting(ProfilePrivacySettings.SHOW_EMAIL)
if edit_privacy_settings_form.show_last_seen.data:
current_user.add_profile_privacy_setting(ProfilePrivacySettings.SHOW_LAST_SEEN)
else:
current_user.remove_profile_privacy_setting(ProfilePrivacySettings.SHOW_LAST_SEEN)
if edit_privacy_settings_form.show_member_since.data:
current_user.add_profile_privacy_setting(ProfilePrivacySettings.SHOW_MEMBER_SINCE)
else:
current_user.remove_profile_privacy_setting(ProfilePrivacySettings.SHOW_MEMBER_SINCE)
db.session.commit()
flash('Your changes have been saved')
return redirect(url_for('.profile', user_id=user.id))
if edit_public_profile_information_form.validate_on_submit():
if edit_public_profile_information_form.avatar.data:
try: try:
Avatar.create(edit_profile_settings_form.avatar.data, user=current_user) Avatar.create(edit_public_profile_information_form.avatar.data, user=current_user)
except (AttributeError, OSError): except (AttributeError, OSError):
abort(500) abort(500)
current_user.email = edit_profile_settings_form.email.data current_user.about_me = edit_public_profile_information_form.about_me.data
current_user.username = edit_profile_settings_form.username.data current_user.location = edit_public_profile_information_form.location.data
current_user.about_me = edit_profile_settings_form.about_me.data current_user.organization = edit_public_profile_information_form.organization.data
current_user.location = edit_profile_settings_form.location.data current_user.website = edit_public_profile_information_form.website.data
current_user.organization = edit_profile_settings_form.organization.data current_user.full_name = edit_public_profile_information_form.full_name.data
current_user.website = edit_profile_settings_form.website.data
current_user.full_name = edit_profile_settings_form.full_name.data
db.session.commit() db.session.commit()
message = Markup(f'Profile settings updated') message = Markup(f'Profile settings updated')
flash(message, 'success') flash(message, 'success')
return redirect(url_for('.profile', user_id=user.id)) return redirect(url_for('.profile', user_id=user.id))
return render_template('profile/edit_profile.html.j2', return render_template('profile/edit_profile.html.j2',
edit_profile_settings_form=edit_profile_settings_form, edit_profile_settings_form=edit_profile_settings_form,
edit_privacy_settings_form=edit_privacy_settings_form,
edit_public_profile_information_form=edit_public_profile_information_form,
user=user, user=user,
title='Edit Profile') title='Edit Profile')
...@@ -73,11 +73,11 @@ class CreateTesseractOCRPipelineJobForm(CreateJobBaseForm): ...@@ -73,11 +73,11 @@ class CreateTesseractOCRPipelineJobForm(CreateJobBaseForm):
if 'methods' in service_info: if 'methods' in service_info:
if 'binarization' in service_info['methods']: if 'binarization' in service_info['methods']:
del self.binarization.render_kw['disabled'] del self.binarization.render_kw['disabled']
if 'ocropus_nlbin_threshold' in service_info['methods']: if 'ocropus_nlbin_threshold' in service_info['methods']:
del self.ocropus_nlbin_threshold.render_kw['disabled'] del self.ocropus_nlbin_threshold.render_kw['disabled']
models = [ models = [
x for x in TesseractOCRPipelineModel.query.order_by(TesseractOCRPipelineModel.title).all() x for x in TesseractOCRPipelineModel.query.order_by(TesseractOCRPipelineModel.title).all()
if version in x.compatible_service_versions and (x.shared == True or x.user == current_user) if version in x.compatible_service_versions and (x.is_public == True or x.user == current_user)
] ]
self.model.choices = [('', 'Choose your option')] self.model.choices = [('', 'Choose your option')]
self.model.choices += [(x.hashid, f'{x.title} [{x.version}]') for x in models] self.model.choices += [(x.hashid, f'{x.title} [{x.version}]') for x in models]
...@@ -157,7 +157,7 @@ class CreateSpacyNLPPipelineJobForm(CreateJobBaseForm): ...@@ -157,7 +157,7 @@ class CreateSpacyNLPPipelineJobForm(CreateJobBaseForm):
del self.encoding_detection.render_kw['disabled'] del self.encoding_detection.render_kw['disabled']
models = [ models = [
x for x in SpaCyNLPPipelineModel.query.order_by(SpaCyNLPPipelineModel.title).all() x for x in SpaCyNLPPipelineModel.query.order_by(SpaCyNLPPipelineModel.title).all()
if version in x.compatible_service_versions and (x.shared == True or x.user == current_user) if version in x.compatible_service_versions and (x.is_public == True or x.user == current_user)
] ]
self.model.choices = [('', 'Choose your option')] self.model.choices = [('', 'Choose your option')]
self.model.choices += [(x.hashid, f'{x.title} [{x.version}]') for x in models] self.model.choices += [(x.hashid, f'{x.title} [{x.version}]') for x in models]
......
...@@ -98,7 +98,7 @@ def tesseract_ocr_pipeline(): ...@@ -98,7 +98,7 @@ def tesseract_ocr_pipeline():
return {}, 201, {'Location': job.url} return {}, 201, {'Location': job.url}
tesseract_ocr_pipeline_models = [ tesseract_ocr_pipeline_models = [
x for x in TesseractOCRPipelineModel.query.all() x for x in TesseractOCRPipelineModel.query.all()
if version in x.compatible_service_versions and (x.shared == True or x.user == current_user) if version in x.compatible_service_versions and (x.is_public == True or x.user == current_user)
] ]
return render_template( return render_template(
'services/tesseract_ocr_pipeline.html.j2', 'services/tesseract_ocr_pipeline.html.j2',
......
...@@ -62,15 +62,3 @@ class EditNotificationSettingsForm(FlaskForm): ...@@ -62,15 +62,3 @@ class EditNotificationSettingsForm(FlaskForm):
for x in UserSettingJobStatusMailNotificationLevel for x in UserSettingJobStatusMailNotificationLevel
] ]
class EditPrivacySettingsForm(FlaskForm):
private_profile = BooleanField(
'Private profile'
)
private_email = BooleanField(
'Private email'
)
only_username = BooleanField(
'Show only username'
)
submit = SubmitField()
from flask import flash, redirect, render_template, url_for from flask import abort, flash, redirect, render_template, url_for
from flask_login import current_user, login_required from flask_login import current_user, login_required
from app import db from app import db
from app.models import UserSettingJobStatusMailNotificationLevel from app.models import ProfilePrivacySettings, UserSettingJobStatusMailNotificationLevel
from . import bp from . import bp
from .forms import ( from .forms import (
ChangePasswordForm, ChangePasswordForm,
EditNotificationSettingsForm, EditNotificationSettingsForm
EditPrivacySettingsForm
) )
...@@ -21,17 +20,13 @@ def settings(): ...@@ -21,17 +20,13 @@ def settings():
data=current_user.to_json_serializeable(), data=current_user.to_json_serializeable(),
prefix='edit-notification-settings-form' prefix='edit-notification-settings-form'
) )
edit_privacy_settings_form = EditPrivacySettingsForm(
data=current_user.to_json_serializeable(),
prefix='edit-privacy-settings-form'
)
if change_password_form.submit.data and change_password_form.validate(): if change_password_form.submit.data and change_password_form.validate():
current_user.password = change_password_form.new_password.data current_user.password = change_password_form.new_password.data
db.session.commit() db.session.commit()
flash('Your changes have been saved') flash('Your changes have been saved')
return redirect(url_for('.index')) return redirect(url_for('.index'))
if (edit_notification_settings_form.submit.data if (edit_notification_settings_form.submit
and edit_notification_settings_form.validate()): and edit_notification_settings_form.validate()):
current_user.setting_job_status_mail_notification_level = ( current_user.setting_job_status_mail_notification_level = (
UserSettingJobStatusMailNotificationLevel[ UserSettingJobStatusMailNotificationLevel[
...@@ -45,6 +40,5 @@ def settings(): ...@@ -45,6 +40,5 @@ def settings():
'settings/settings.html.j2', 'settings/settings.html.j2',
change_password_form=change_password_form, change_password_form=change_password_form,
edit_notification_settings_form=edit_notification_settings_form, edit_notification_settings_form=edit_notification_settings_form,
edit_privacy_settings_form=edit_privacy_settings_form,
title='Settings' title='Settings'
) )
...@@ -34,9 +34,9 @@ class SpaCyNLPPipelineModelList extends RessourceList { ...@@ -34,9 +34,9 @@ class SpaCyNLPPipelineModelList extends RessourceList {
<div class="switch action-switch center-align" data-action="share-request"> <div class="switch action-switch center-align" data-action="share-request">
<span class="share"></span> <span class="share"></span>
<label> <label>
<input type="checkbox" class="shared"> <input type="checkbox" class="is_public">
<span class="lever"></span> <span class="lever"></span>
shared public
</label> </label>
</div> </div>
</td> </td>
...@@ -59,7 +59,7 @@ class SpaCyNLPPipelineModelList extends RessourceList { ...@@ -59,7 +59,7 @@ class SpaCyNLPPipelineModelList extends RessourceList {
'title': spaCyNLPPipelineModel.title, 'title': spaCyNLPPipelineModel.title,
'title-2': spaCyNLPPipelineModel.title, 'title-2': spaCyNLPPipelineModel.title,
'version': spaCyNLPPipelineModel.version, 'version': spaCyNLPPipelineModel.version,
'shared': spaCyNLPPipelineModel.shared ? 'True' : 'False' 'is_public': spaCyNLPPipelineModel.is_public ? 'True' : 'False'
}; };
}, },
sortArgs: ['creation-date', {order: 'desc'}], sortArgs: ['creation-date', {order: 'desc'}],
...@@ -75,7 +75,7 @@ class SpaCyNLPPipelineModelList extends RessourceList { ...@@ -75,7 +75,7 @@ class SpaCyNLPPipelineModelList extends RessourceList {
'title', 'title',
'title-2', 'title-2',
'version', 'version',
{name: 'shared', attr: 'data-checked'} {name: 'is_public', attr: 'data-checked'}
] ]
}; };
...@@ -87,7 +87,7 @@ class SpaCyNLPPipelineModelList extends RessourceList { ...@@ -87,7 +87,7 @@ class SpaCyNLPPipelineModelList extends RessourceList {
init(user) { init(user) {
this._init(user.spacy_nlp_pipeline_models); this._init(user.spacy_nlp_pipeline_models);
if (user.role.name !== ('Administrator' || 'Contributor')) { if (user.role.name !== ('Administrator' || 'Contributor')) {
for (let switchElement of this.listjs.list.querySelectorAll('.shared')) { for (let switchElement of this.listjs.list.querySelectorAll('.is_public')) {
switchElement.setAttribute('disabled', ''); switchElement.setAttribute('disabled', '');
} }
} }
...@@ -134,8 +134,8 @@ class SpaCyNLPPipelineModelList extends RessourceList { ...@@ -134,8 +134,8 @@ class SpaCyNLPPipelineModelList extends RessourceList {
let spaCyNLPPipelineModelId = spaCyNLPPipelineModelElement.dataset.id; let spaCyNLPPipelineModelId = spaCyNLPPipelineModelElement.dataset.id;
switch (action) { switch (action) {
case 'share-request': { case 'share-request': {
let shared = actionSwitchElement.querySelector('input').checked; let is_public = actionSwitchElement.querySelector('input').checked;
Utils.shareSpaCyNLPPipelineModelRequest(this.userId, spaCyNLPPipelineModelId, shared); Utils.shareSpaCyNLPPipelineModelRequest(this.userId, spaCyNLPPipelineModelId, is_public);
break; break;
} }
default: { default: {
......
...@@ -34,9 +34,9 @@ class TesseractOCRPipelineModelList extends RessourceList { ...@@ -34,9 +34,9 @@ class TesseractOCRPipelineModelList extends RessourceList {
<div class="switch action-switch center-align" data-action="share-request"> <div class="switch action-switch center-align" data-action="share-request">
<span class="share"></span> <span class="share"></span>
<label> <label>
<input type="checkbox" class="shared"> <input type="checkbox" class="is_public">
<span class="lever"></span> <span class="lever"></span>
shared public
</label> </label>
</div> </div>
</td> </td>
...@@ -59,7 +59,7 @@ class TesseractOCRPipelineModelList extends RessourceList { ...@@ -59,7 +59,7 @@ class TesseractOCRPipelineModelList extends RessourceList {
'title': tesseractOCRPipelineModel.title, 'title': tesseractOCRPipelineModel.title,
'title-2': tesseractOCRPipelineModel.title, 'title-2': tesseractOCRPipelineModel.title,
'version': tesseractOCRPipelineModel.version, 'version': tesseractOCRPipelineModel.version,
'shared': tesseractOCRPipelineModel.shared ? 'True' : 'False' 'is_public': tesseractOCRPipelineModel.is_public ? 'True' : 'False'
}; };
}, },
sortArgs: ['creation-date', {order: 'desc'}], sortArgs: ['creation-date', {order: 'desc'}],
...@@ -75,7 +75,7 @@ class TesseractOCRPipelineModelList extends RessourceList { ...@@ -75,7 +75,7 @@ class TesseractOCRPipelineModelList extends RessourceList {
'title', 'title',
'title-2', 'title-2',
'version', 'version',
{name: 'shared', attr: 'data-checked'} {name: 'is_public', attr: 'data-checked'}
] ]
}; };
...@@ -87,7 +87,7 @@ class TesseractOCRPipelineModelList extends RessourceList { ...@@ -87,7 +87,7 @@ class TesseractOCRPipelineModelList extends RessourceList {
init (user) { init (user) {
this._init(user.tesseract_ocr_pipeline_models); this._init(user.tesseract_ocr_pipeline_models);
if (user.role.name !== ('Administrator' || 'Contributor')) { if (user.role.name !== ('Administrator' || 'Contributor')) {
for (let switchElement of this.listjs.list.querySelectorAll('.shared')) { for (let switchElement of this.listjs.list.querySelectorAll('.is_public')) {
switchElement.setAttribute('disabled', ''); switchElement.setAttribute('disabled', '');
} }
} }
...@@ -134,8 +134,8 @@ class TesseractOCRPipelineModelList extends RessourceList { ...@@ -134,8 +134,8 @@ class TesseractOCRPipelineModelList extends RessourceList {
let tesseractOCRPipelineModelId = tesseractOCRPipelineModelElement.dataset.id; let tesseractOCRPipelineModelId = tesseractOCRPipelineModelElement.dataset.id;
switch (action) { switch (action) {
case 'share-request': { case 'share-request': {
let shared = actionSwitchElement.querySelector('input').checked; let is_public = actionSwitchElement.querySelector('input').checked;
Utils.shareTesseractOCRPipelineModelRequest(this.userId, tesseractOCRPipelineModelId, shared); Utils.shareTesseractOCRPipelineModelRequest(this.userId, tesseractOCRPipelineModelId, is_public);
break; break;
} }
default: { default: {
......
...@@ -429,11 +429,11 @@ class Utils { ...@@ -429,11 +429,11 @@ class Utils {
}); });
} }
static shareTesseractOCRPipelineModelRequest(userId, tesseractOCRPipelineModelId, shared) { static shareTesseractOCRPipelineModelRequest(userId, tesseractOCRPipelineModelId, is_public) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let tesseractOCRPipelineModel = app.data.users[userId].tesseract_ocr_pipeline_models[tesseractOCRPipelineModelId]; let tesseractOCRPipelineModel = app.data.users[userId].tesseract_ocr_pipeline_models[tesseractOCRPipelineModelId];
let msg = ''; let msg = '';
if (shared) { if (is_public) {
msg = `Model "${tesseractOCRPipelineModel.title}" is now public`; msg = `Model "${tesseractOCRPipelineModel.title}" is now public`;
} else { } else {
msg = `Model "${tesseractOCRPipelineModel.title}" is now private`; msg = `Model "${tesseractOCRPipelineModel.title}" is now private`;
...@@ -453,11 +453,11 @@ class Utils { ...@@ -453,11 +453,11 @@ class Utils {
}); });
} }
static shareSpaCyNLPPipelineModelRequest(userId, spaCyNLPPipelineModelId, shared) { static shareSpaCyNLPPipelineModelRequest(userId, spaCyNLPPipelineModelId, is_public) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let spaCyNLPPipelineModel = app.data.users[userId].spacy_nlp_pipeline_models[spaCyNLPPipelineModelId]; let spaCyNLPPipelineModel = app.data.users[userId].spacy_nlp_pipeline_models[spaCyNLPPipelineModelId];
let msg = ''; let msg = '';
if (shared) { if (is_public) {
msg = `Model "${spaCyNLPPipelineModel.title}" is now public`; msg = `Model "${spaCyNLPPipelineModel.title}" is now public`;
} else { } else {
msg = `Model "${spaCyNLPPipelineModel.title}" is now private`; msg = `Model "${spaCyNLPPipelineModel.title}" is now private`;
......
...@@ -33,7 +33,8 @@ ...@@ -33,7 +33,8 @@
<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.corpus_analysis') }}"><i class="nopaque-icons service-icon" data-service="corpus-analysis"></i>Corpus analysis</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.corpus_analysis') }}"><i class="nopaque-icons 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.settings') }}"><i class="material-icons">settings</i>Settings</a></li> <li><a href="{{ url_for('settings.settings') }}"><i class="material-icons">settings</i>General Settings</a></li>
<li><a href="{{ url_for('profile.edit_profile', user_id=current_user.id) }}"><i class="material-icons left">contact_page</i>Profile settings</a></li>
<li><a href="{{ url_for('auth.logout') }}">Log out</a></li> <li><a href="{{ url_for('auth.logout') }}">Log out</a></li>
{% if current_user.can(Permission.ADMINISTRATE) or current_user.can(Permission.USE_API) %} {% if current_user.can(Permission.ADMINISTRATE) or current_user.can(Permission.USE_API) %}
<li><div class="divider"></div></li> <li><div class="divider"></div></li>
......
...@@ -9,48 +9,186 @@ ...@@ -9,48 +9,186 @@
</div> </div>
<div class="col s12"> <div class="col s12">
<div class="card"> <div class="card">
<form method="POST" enctype="multipart/form-data"> <form method="POST" enctype="multipart/form-data">
<div class="card-content"> <div class="card-content">
{{ edit_profile_settings_form.hidden_tag() }} <div class="row">
<div class="col s6">
{{ edit_profile_settings_form.hidden_tag() }}
{{ wtf.render_field(edit_profile_settings_form.username, material_icon='person') }}
{{ wtf.render_field(edit_profile_settings_form.email, material_icon='email') }}
</div>
</div>
</div>
<div class="card-action">
<div class="right-align">
{{ wtf.render_field(edit_profile_settings_form.submit, material_icon='send') }}
</div>
</div>
</form>
</div>
<form method="POST">
{{ edit_privacy_settings_form.hidden_tag() }}
<div class="card">
<div class="card-content">
<span class="card-title">Privacy settings</span>
<br>
{{ wtf.render_field(edit_privacy_settings_form.is_public, id='public-profile') }}
<br>
<hr>
<br>
{{ wtf.render_field(edit_privacy_settings_form.show_email, data_action='disable', disabled=true) }}
<br>
{{ wtf.render_field(edit_privacy_settings_form.show_last_seen, data_action='disable', disabled=true) }}
<br>
{{ wtf.render_field(edit_privacy_settings_form.show_member_since, data_action='disable', disabled=true) }}
<br>
</div>
<div class="card-action">
<div class="right-align">
{{ wtf.render_field(edit_privacy_settings_form.submit, material_icon='send') }}
</div>
</div>
</div>
</form>
<div class="card">
<form method="POST" enctype="multipart/form-data">
<div class="card-content">
{{ edit_public_profile_information_form.hidden_tag() }}
<div class="row">
<div class="col s12">
<span class="card-title">Public Profile</span>
</div>
</div>
<div class="row"> <div class="row">
<div class="col s5"> <div class="col s5">
<div class="row"> <div class="row">
<div class="col s1"></div> <div class="col s2"></div>
<div class="col s10"> <div class="col s8">
{% if current_user.avatar %} {% if current_user.avatar %}
<img src="{{ url_for('profile.avatar_download', user_id=user.id, avatar_id=current_user.avatar.id) }}" alt="user-image" class="circle responsive-img"> <img src="{{ url_for('profile.avatar_download', user_id=user.id, avatar_id=current_user.avatar.id) }}" alt="user-image" class="circle responsive-img" id="avatar">
{% else %} {% else %}
<img src="{{ url_for('static', filename='images/user_avatar.png') }}" alt="user-image" class="circle responsive-img"> <img src="{{ url_for('static', filename='images/user_avatar.png') }}" alt="user-image" class="circle responsive-img" id="placeholder-avatar">
{% endif %} {% endif %}
</div> </div>
<div class="col s1"></div> <div class="col s2"></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s2">
{{wtf.render_field(edit_profile_settings_form.avatar, accept='image/jpeg, image/png, image/gif', placeholder="Choose an image file")}} <div class="center-align">
<a class="action-button btn-floating red waves-effect waves-light modal-trigger" href="#delete-request" style="margin-top:15px;"><i class="material-icons">delete</i></a>
</div>
</div>
<div class="col s10">
{{wtf.render_field(edit_public_profile_information_form.avatar, accept='image/jpeg, image/png, image/gif', placeholder='Choose an image file', id='avatar-upload', data_action='disable')}}
</div> </div>
</div> </div>
</div> </div>
<div class="col s7"> <div class="col s7">
{{ wtf.render_field(edit_profile_settings_form.username, material_icon='person') }} <p></p>
{{ wtf.render_field(edit_profile_settings_form.email, material_icon='email') }} <br>
{{ wtf.render_field(edit_profile_settings_form.full_name, material_icon='badge') }} {{ wtf.render_field(edit_public_profile_information_form.full_name, material_icon='badge') }}
{{ wtf.render_field(edit_profile_settings_form.about_me, material_icon='description') }} {{ wtf.render_field(edit_public_profile_information_form.about_me, material_icon='description') }}
{{ wtf.render_field(edit_profile_settings_form.website, material_icon='laptop') }} {{ wtf.render_field(edit_public_profile_information_form.website, material_icon='laptop') }}
{{ wtf.render_field(edit_profile_settings_form.organization, material_icon='business') }} {{ wtf.render_field(edit_public_profile_information_form.organization, material_icon='business') }}
{{ wtf.render_field(edit_profile_settings_form.location, material_icon='location_on') }} {{ wtf.render_field(edit_public_profile_information_form.location, material_icon='location_on') }}
</div> </div>
</div> </div>
</div> </div>
<div class="card-action"> <div class="card-action">
<div class="right-align"> <div class="right-align">
{{ wtf.render_field(edit_profile_settings_form.submit, material_icon='send') }} {{ wtf.render_field(edit_public_profile_information_form.submit, material_icon='send') }}
</div> </div>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endblock page_content %} {% endblock page_content %}
{% block modals %}
<div class="modal" id="delete-request">
<div class="modal-content">
<h4>Confirm Avatar deletion</h4>
<p>Do you really want to delete your Avatar?</p>
</div>
<div class="modal-footer">
<a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
<a class="action-button btn modal-close red waves-effect waves-light" data-action="delete">Delete</a>
</div>
</div>
{% endblock modals %}
{% block scripts %}
{{ super() }}
<script>
let publicProfile = document.querySelector('#public-profile');
let disableButtons = document.querySelectorAll('[data-action="disable"]');
let deleteButton = document.querySelector('[data-action="delete"]');
let cancelButton = document.querySelector('[data-action="cancel"]');
let deleteRequestModal = document.querySelector('#delete-request');
let avatar = document.querySelector('#avatar');
let placeholderAvatar = document.querySelector('#placeholder-avatar');
let avatarUpload = document.querySelector('#avatar-upload');
for (let i = 0; i < disableButtons.length; i++) {
disableButtons[i].disabled = !publicProfile.checked;
}
publicProfile.addEventListener('change', function() {
if (publicProfile.checked) {
for (let i = 0; i < disableButtons.length; i++) {
disableButtons[i].disabled = false;
}
} else {
for (let i = 0; i < disableButtons.length; i++) {
disableButtons[i].checked = false;
disableButtons[i].disabled = true;
}
}
});
avatarUpload.addEventListener('change', function() {
let file = this.files[0];
if (avatar){
avatar.src = URL.createObjectURL(file);
} else {
placeholderAvatar.src = URL.createObjectURL(file);
}
});
deleteButton.addEventListener('click', function() {
return new Promise((resolve, reject) => {
let user_id = "{{ current_user.hashid }}";
let avatar_id = "{{ current_user.avatar.hashid }}";
fetch(`/profile/${user_id}/avatars/${avatar_id}`, {method: 'DELETE', headers: {Accept: 'application/json'}})
.then(
(response) => {
if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
app.flash(`Avatar marked for deletion`, 'corpus');
resolve(response);
},
(response) => {
app.flash('Something went wrong', 'error');
reject(response);
}
);
avatar.src = "{{ url_for('static', filename='images/user_avatar.png') }}";
});
});
cancelButton.addEventListener('click', function() {
let modal = M.Modal.getInstance(deleteRequestModal);
modal.close();
});
</script>
{% endblock scripts %}
...@@ -2,96 +2,133 @@ ...@@ -2,96 +2,133 @@
{% import "materialize/wtf.html.j2" as wtf %} {% import "materialize/wtf.html.j2" as wtf %}
{% block page_content %} {% block page_content %}
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<div class="row">
<div class="col s1"></div>
<div class="col s3">
<br>
<br>
{% if user.avatar %}
<img src="{{ url_for('profile.avatar_download', user_id=user.id, avatar_id=user.avatar.id) }}" alt="user-image" class="circle responsive-img">
{% else %}
<img src="{{ url_for('static', filename='images/user_avatar.png') }}" alt="user-image" class="circle responsive-img">
{% endif %}
</div>
<div class="col s1"></div>
<div class="col s7">
<div class="row"> <div class="row">
<div class="col s1"></div> <div class="col s12">
<div class="col s4"> <h3 style="float:left">{{ user.username }}<span class="new badge green" id="public-information-badge" data-badge-caption="custom caption" style="margin-top:20px;"></span></h3>
{% if user.avatar %}
<img src="{{ url_for('profile.avatar_download', user_id=user.id, avatar_id=user.avatar.id) }}" alt="user-image" class="circle responsive-img">
{% else %}
<img src="{{ url_for('static', filename='images/user_avatar.png') }}" alt="user-image" class="circle responsive-img">
{% endif %}
</div> </div>
<div class="col s7"> <div class="col 12">
<h3>{{ user.username }}</h3> {% if user_data['show_last_seen'] %}
<div class="chip">Last seen: {{ user.last_seen.strftime('%Y-%m-%d %H:%M') }}</div> <div class="chip">Last seen: {{ user.last_seen.strftime('%Y-%m-%d %H:%M') }}</div>
{% endif %}
{% if user.location %} {% if user.location %}
<p><span class="material-icons" style="margin-right:20px; margin-top:20px;">location_on</span><i>{{ user.location }}</i></p> <p><span class="material-icons" style="margin-right:20px; margin-top:20px;">location_on</span><i>{{ user.location }}</i></p>
{% endif %} {% endif %}
<p></p> <p></p>
<br> <br>
{% if user.about_me%} {% if user.about_me%}
<div class="card"> <div style="border-left: solid 3px #E91E63; padding-left: 15px;">
<div class="card-content"> <h5>About me</h5>
<span class="card-title">About me</span> <p>{{ user.about_me }}</p>
<p>{{ user.about_me }}</p>
</div>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="row">
<div class="col s1"></div>
<div class="col s6">
<table>
{% if user.full_name %}
<tr>
<td><span class="material-icons">person</span></td>
<td>{{ user.full_name }} </td>
</tr>
{% endif %}
{% if user.email %}
<tr>
<td><span class="material-icons">email</span></td>
<td>{{ user.email }}</td>
</tr>
{% endif %}
{% if user.website %}
<tr>
<td><span class="material-icons">laptop</span></td>
<td><a href="{{ user.website }}">{{ user.website }}</a></td>
</tr>
{% endif %}
{% if user.organization %}
<tr>
<td><span class="material-icons">business</span></td>
<td>{{ user.organization }}</td>
</tr>
{% endif %}
</table>
<br>
<p><i>Member since: {{ user.member_since.strftime('%Y-%m-%d') }}</i></p>
<p></p>
<br>
{% if current_user.is_authenticated and current_user.id == user.id %}
<a class="waves-effect waves-light btn-small" href="{{ url_for('profile.edit_profile', user_id=user.id) }}">Edit profile</a>
{% endif %}
</div>
</div>
</div> </div>
</div> </div>
</div> <p></p>
</div> <br>
<div class="row"> <div class="row">
<div class="col s6"> <div class="col s1"></div>
<div class="card"> <div class="col s8">
<div class="card-content"> <table>
<h4>Groups</h4> {% if user.full_name %}
<tr>
<td><span class="material-icons">person</span></td>
<td>{{ user.full_name }} </td>
</tr>
{% endif %}
{% if user_data['show_email'] %}
<tr>
<td><span class="material-icons">email</span></td>
<td>{{ user.email }}</td>
</tr>
{% endif %}
{% if user.website %}
<tr>
<td><span class="material-icons">laptop</span></td>
<td><a href="{{ user.website }}">{{ user.website }}</a></td>
</tr>
{% endif %}
{% if user.organization %}
<tr>
<td><span class="material-icons">business</span></td>
<td>{{ user.organization }}</td>
</tr>
{% endif %}
</table>
<br>
{% if user_data['show_member_since'] %}
<p><i>Member since: {{ user.member_since.strftime('%Y-%m-%d') }}</i></p>
{% endif %}
<p></p>
<br>
{% if current_user.is_authenticated and current_user.id == user.id %}
<a class="waves-effect waves-light btn-small" href="{{ url_for('profile.edit_profile', user_id=user.id) }}">Edit profile</a>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
<div class="col s6"> </div>
<div class="card"> </div>
<div class="card-content"> </div>
<h4>Public corpora</h4> <div class="row">
<div class="public-corpora-list" data-user-id="{{ user.hashid }}"></div> <div class="col s6">
</div> <div class="card">
</div> <div class="card-content">
<h4>Groups</h4>
</div> </div>
</div> </div>
</div>
<div class="col s6">
<div class="card">
<div class="card-content">
<h4>Public corpora</h4>
{# <div class="public-corpora-list" data-user-id="{{ user.hashid }}"></div> #}
</div>
</div>
</div>
</div> </div>
</div>
{% endblock page_content %} {% endblock page_content %}
{% block scripts %}
{{ super() }}
<script>
let publicInformationBadge = document.querySelector('#public-information-badge');
if ("{{ user }}" == "{{ current_user }}") {
if ("{{ user.is_public }}" == "True") {
publicInformationBadge.dataset.badgeCaption = 'Your profile is public';
publicInformationBadge.classList.add('green');
publicInformationBadge.classList.remove('red');
} else {
publicInformationBadge.dataset.badgeCaption = 'Your profile is private';
publicInformationBadge.classList.add('red');
publicInformationBadge.classList.remove('green');
}
} else {
publicInformationBadge.remove();
}
</script>
{% endblock scripts %}
...@@ -85,6 +85,7 @@ ...@@ -85,6 +85,7 @@
</label> </label>
</div> </div>
</div> </div>
{% endif %}
{% if 'disabled' not in form.ocropus_nlbin_threshold.render_kw or not form.ocropus_nlbin_threshold.render_kw['disabled'] %} {% if 'disabled' not in form.ocropus_nlbin_threshold.render_kw or not form.ocropus_nlbin_threshold.render_kw['disabled'] %}
<div class="col s9 hide" id="create-job-form-ocropus_nlbin_threshold-container"> <div class="col s9 hide" id="create-job-form-ocropus_nlbin_threshold-container">
<br> <br>
...@@ -92,7 +93,6 @@ ...@@ -92,7 +93,6 @@
<p class="range-field">{{ form.ocropus_nlbin_threshold() }}</p> <p class="range-field">{{ form.ocropus_nlbin_threshold() }}</p>
</div> </div>
{% endif %} {% endif %}
{% endif %}
<!-- <!--
Seperate each setting with the following Seperate each setting with the following
<div class="col s12"><p>&nbsp;</p></div> <div class="col s12"><p>&nbsp;</p></div>
......
...@@ -23,24 +23,6 @@ ...@@ -23,24 +23,6 @@
</div> </div>
</div> </div>
</form> </form>
<div class="card">
<div class="card-content">
<span class="card-title">Privacy settings</span>
<br>
{{ wtf.render_field(edit_privacy_settings_form.private_profile) }}
<br>
{{ wtf.render_field(edit_privacy_settings_form.private_email) }}
<br>
{{ wtf.render_field(edit_privacy_settings_form.only_username) }}
<br>
</div>
<div class="card-action">
<div class="right-align">
{{ wtf.render_field(edit_notification_settings_form.submit, material_icon='send') }}
</div>
</div>
</div>
<form method="POST"> <form method="POST">
{{ change_password_form.hidden_tag() }} {{ change_password_form.hidden_tag() }}
......
<div class="row" id="concordance-extension-container">
<div class="col s12">
<div class="card">
<div class="card-content">
<form id="concordance-extension-form">
<div class="row">
<div class="input-field col s12 m9">
<i class="material-icons prefix">search</i>
<input class="validate corpus-analysis-action" id="concordance-extension-form-query" name="query" type="text"
required pattern=".*\S+.*"
placeholder="&lt;ent_type=&quot;PERSON&quot;&gt; []* &lt;/ent_type&gt; []* [simple_pos=&quot;VERB&quot;] :: match.text_publishing_year=&quot;1991&quot;;">
</input>
<label for="concordance-extension-form-query">Query</label>
<span class="error-color-text helper-text hide" id="concordance-extension-error"></span>
<a class="modal-trigger" href="#cql-tutorial-modal" style="margin-left: 40px;"><i class="material-icons" style="font-size: inherit;">help</i>
Corpus Query Language tutorial</a>
<span> | </span>
<a class="modal-trigger" href="#tagsets-modal"><i class="material-icons" style="font-size: inherit;">info</i> Tagsets</a>
</div>
<div class="input-field col s12 m3">
<i class="material-icons prefix">arrow_forward</i>
<input class="validate corpus-analysis-action" id="concordance-extension-form-subcorpus-name" name="subcorpus-name" type="text"
required pattern="^[A-Z][a-z0-9\-]*" value="Last">
</input>
<label for="concordance-extension-form-subcorpus-name">Subcorpus name</label>
</div>
<div class="col s12 m9 l9">
<div class="row">
<div class="input-field col s4 l3">
<i class="material-icons prefix">short_text</i>
<select class="corpus-analysis-action" name="context">
<option value="10" selected>10</option>
<option value="15">15</option>
<option value="20">20</option>
<option value="25">25</option>
<option value="30">30</option>
</select>
<label>Context</label>
</div>
<div class="input-field col s4 l3">
<i class="material-icons prefix">format_list_numbered</i>
<select class="corpus-analysis-action" name="per-page">
<option value="10" selected>10</option>
<option value="15">15</option>
<option value="20">20</option>
<option value="25">25</option>
</select>
<label>Matches per page</label>
</div>
<div class="input-field col s4 l3">
<i class="material-icons prefix">format_shapes</i>
<select name="text-style">
<option value="0">Plain text</option>
<option value="1" selected>Highlight entities</option>
<option value="2">Token text</option>
</select>
<label>Text style</label>
</div>
<div class="input-field col s4 l3">
<i class="material-icons prefix">format_quote</i>
<select name="token-representation">
<option value="lemma">lemma</option>
<option value="pos">pos</option>
<option value="simple_pos">simple_pos</option>
<option value="word" selected>word</option>
</select>
<label>Token representation</label>
</div>
</div>
</div>
<div class="col s12 m3 l3 right-align">
<p class="hide-on-small-only">&nbsp;</p>
<a class="btn waves-effect waves-light modal-trigger" href="#concordance-query-builder" id="concordance-query-builder-button">
<i class="material-icons left">build</i>
Query builder
</a>
<button class="btn waves-effect waves-light corpus-analysis-action" id="concordance-extension-form-submit" type="submit" name="submit">
Send
<i class="material-icons right">send</i>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="col s12">
<div id="concordance-extension-subcorpus-list"></div>
<div class="card">
<div class="card-content">
<div class="progress hide" id="concordance-extension-progress">
<div class="indeterminate"></div>
</div>
<div class="row">
<div class="col s9"><p class="hide" id="concordance-extension-subcorpus-info"></p></div>
<div class="col s3 right-align" id="concordance-extension-subcorpus-actions"></div>
</div>
<table class="highlight">
<thead>
<tr>
<th style="width: 2%;"></th>
<th style="width: 8%;">Source</th>
<th class="right-align" style="width: 22.5%;">Left context</th>
<th class="center-align" style="width: 40%;">KWIC</th>
<th class="left-align" style="width: 22.5%;">Right Context</th>
<th class="left-align" style="width: 5%;"></th>
</tr>
</thead>
<tbody id="concordance-extension-subcorpus-items"></tbody>
</table>
<ul class="pagination hide" id="concordance-extension-subcorpus-pagination"></ul>
</div>
</div>
</div>
</div>
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
<style>
a {color: #FFFFFF;}
</style>
{% block main_attribs %} class="service-scheme" data-service="corpus-analysis" id="corpus-analysis-app-container"{% endblock main_attribs %}
{% block page_content %}
<ul class="row tabs no-autoinit" id="corpus-analysis-app-extension-tabs">
<li class="tab col s3"><a 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="#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>
</ul>
{# <div class="row" id="corpus-analysis-app-overview">
<div class="col s12">
<h1>{{ title }}</h1>
</div>
<div class="col s3">
<div class="card extension-selector hoverable" data-target="concordance-extension-container">
<div class="card-content">
<span class="card-title"><i class="material-icons left">list_alt</i>Concordance</span>
<p>Query your corpus with the CQP query language utilizing a KWIC view.</p>
</div>
</div>
</div>
<div class="col s3">
<div class="card extension-selector hoverable" data-target="reader-extension-container">
<div class="card-content">
<span class="card-title"><i class="material-icons left">chrome_reader_mode</i>Reader</span>
<p>Inspect your corpus in detail with a full text view, including annotations.</p>
</div>
</div>
</div>
</div> #}
{% include "test/analyse_corpus.concordance.html.j2" %}
{% endblock page_content %}
{% block modals %}
{{ super() }}
<div class="modal no-autoinit" id="corpus-analysis-app-init-modal">
<div class="modal-content">
<h4>Initializing session...</h4>
<p>If the loading takes to long or an error occured,
<a onclick="window.location.reload()" href="#">click here</a>
to refresh your session or
<a href="">go back</a>!
</p>
<div class="progress" id="corpus-analysis-app-init-progress">
<div class="indeterminate"></div>
</div>
<p class="error-color-text hide" id="corpus-analysis-app-init-error"></p>
</div>
</div>
<div class="modal" id="cql-tutorial-modal">
<div class="modal-content">
{% with headline_num=4 %}
{% include "main/manual/_08_cqp_query_language.html.j2" %}
{% endwith %}
</div>
</div>
<div class="modal" id="tagsets-modal">
<div class="modal-content">
<h4>Tagsets</h4>
<ul class="tabs">
<li class="tab"><a class="active" href="#simple_pos-tagset">simple_pos</a></li>
<li class="tab"><a href="#english-ent_type-tagset">English ent_type</a></li>
<li class="tab"><a href="#english-pos-tagset">English pos</a></li>
<li class="tab"><a href="#german-ent_type-tagset">German ent_type</a></li>
<li class="tab"><a href="#german-pos-tagset">German pos</a></li>
</ul>
{% include "main/manual/_10_tagsets.html.j2" %}
</div>
</div>
<div class="modal" id="concordance-query-builder">
<div class="modal-content">
<div>
<nav>
<div class="nav-wrapper" id="query-builder-nav">
<a href="#!" class="brand-logo"><i class="material-icons">build</i>Query Builder</a>
<i class="material-icons close right" id="close-query-builder">close</i>
<a class="modal-trigger" href="#query-builder-tutorial-modal" >
<i class="material-icons right tooltipped" id="query-builder-tutorial-info-icon" data-position="bottom" data-tooltip="Click here if you are unsure how to use the Query Builder <br>and want to find out what other options it offers.">help</i>
</a>
</div>
</nav>
</div>
<p></p>
<div id="query-container" class="hide">
<div class="row">
<h6 class="col s2">Your Query:
<a class="modal-trigger" href="#query-builder-tutorial-modal">
<i class="material-icons left" id="general-options-query-builder-tutorial-info-icon">help_outline</i></a>
</h6>
</div>
<div class="row">
<div class="col s10" id="your-query" data-position="bottom" data-tooltip="You can edit your query by deleting individual elements or moving them via drag and drop."></div>
<a class="btn-small waves-effect waves-teal col s1" id="insert-query-button">
<i class="material-icons">send</i>
</a>
</div>
<p><i> Preview:</i></p>
<p id="query-preview"></p>
<br>
</div>
<h6>Use the following options to build your query. If you need help, click on the question mark in the upper right corner!</h6>
<p></p>
<a class="btn-large waves-effect waves-light tooltipped" id="positional-attr-button" data-position="bottom" data-tooltip="Search for any token, for example a word, a lemma or a part-of-speech tag">Add new token to your query</a>
<a class="btn-large waves-effect waves-light tooltipped" id="structural-attr-button" data-position="bottom" data-tooltip="Structure your query with structural attributes, for example sentences, entities or annotate the text">Add structural attributes to your query</a>
<div id="structural-attr" class="hide">
<p></p>
<h6>Which structural attribute do you want to add to your query?<a class="modal-trigger" href="#query-builder-tutorial-modal"><i class="material-icons left" id="add-structural-attribute-tutorial-info-icon">help_outline</i></a></h6>
<p></p>
<div class="row">
<div class="col s12">
<a class="btn-small waves-effect waves-light" id="sentence">sentence</a>
<a class="btn-small waves-effect waves-light" id="entity">entity</a>
<a class="btn-small waves-effect waves-light" id="text-annotation">Meta Data</a>
</div>
</div>
<div id="entity-builder" class="hide">
<p></p>
<br>
<div class="row">
<a class="btn waves-effect waves-light col s4" id="empty-entity">Add Entity of any type</a>
<p class="col s1 l1"></p>
<div class= "input-field col s3">
<select name="englishenttype" id="english-ent-type">
<option value="" disabled selected>English ent_type</option>
<option value="CARDINAL">CARDINAL</option>
<option value="DATE">DATE</option>
<option value="EVENT">EVENT</option>
<option value="FAC">FAC</option>
<option value="GPE">GPE</option>
<option value="LANGUAGE">LANGUAGE</option>
<option value="LAW">LAW</option>
<option value="LOC">LOC</option>
<option value="MONEY">MONEY</option>
<option value="NORP">NORP</option>
<option value="ORDINAL">ORDINAL</option>
<option value="ORG">ORG</option>
<option value="PERCENT">PERCENT</option>
<option value="PERSON">PERSON</option>
<option value="PRODUCT">PRODUCT</option>
<option value="QUANTITY">QUANTITY</option>
<option value="TIME">TIME</option>
<option value="WORK_OF_ART">WORK_OF_ART</option>
</select>
<label>Entity Type</label>
</div>
<div class= "input-field col s3">
<select name="germanenttype" id="german-ent-type">
<option value="" disabled selected>German ent_type</option>
<option value="LOC">LOC</option>
<option value="MISC">MISC</option>
<option value="ORG">ORG</option>
<option value="PER">PER</option>
</select>
</div>
</div>
</div>
<div id="text-annotation-builder" class="hide">
<p></p>
<br>
<div class="row">
<div class= "input-field col s4 l3">
<select name="text-annotation-options" id="text-annotation-options">
<option class="btn-small waves-effect waves-light" value="address">address</option>
<option class="btn-small waves-effect waves-light" value="author">author</option>
<option class="btn-small waves-effect waves-light" value="booktitle">booktitle</option>
<option class="btn-small waves-effect waves-light" value="chapter">chapter</option>
<option class="btn-small waves-effect waves-light" value="editor">editor</option>
<option class="btn-small waves-effect waves-light" value="institution">institution</option>
<option class="btn-small waves-effect waves-light" value="journal">journal</option>
<option class="btn-small waves-effect waves-light" value="pages">pages</option>
<option class="btn-small waves-effect waves-light" value="publisher">publisher</option>
<option class="btn-small waves-effect waves-light" value="publishing_year">publishing year</option>
<option class="btn-small waves-effect waves-light" value="school">school</option>
<option class="btn-small waves-effect waves-light" value="title">title</option>
</select>
<label>Meta data</label>
</div>
<div class= "input-field col s7 l5">
<i class="material-icons prefix">mode_edit</i>
<input placeholder="Type in your text annotation" type="text" id="text-annotation-input">
</div>
<div class="col s1 l1 center-align">
<p class="btn-floating waves-effect waves-light" id="text-annotation-submit">
<i class="material-icons right">send</i>
</p>
</div>
<div class="hide" id="no-value-metadata-message"><i>No value entered!</i></div>
</div>
</div>
</div>
<div id="positional-attr" class="hide">
<p></p>
<div class="row" id="token-kind-selector">
<div class="col s5">
<h6>Which kind of token are you looking for? <a class="modal-trigger" href="#query-builder-tutorial-modal"><i class="material-icons left" id="token-tutorial-info-icon">help_outline</i></a></h6>
</div>
<div class="input-field col s3">
<select id="token-attr">
<option value="word" selected>word</option>
<option value="lemma">lemma</option>
<option value="english-pos">english pos</option>
<option value="german-pos">german pos</option>
<option value="simple-pos-button">simple_pos</option>
<option value="empty-token">empty token</option>
</select>
</div>
</div>
<p></p>
<div id="token-builder-content">
<div class="row" >
<div id="token-query"></div>
<div id="word-builder">
<div class= "input-field col s3 l4">
<i class="material-icons prefix">mode_edit</i>
<input placeholder="Type in your word" type="text" id="word-input">
</div>
</div>
<div id="lemma-builder" class="hide" >
<div class= "input-field col s3 l4">
<i class="material-icons prefix">mode_edit</i>
<input placeholder="Type in your lemma" type="text" id="lemma-input">
</div>
</div>
<div id="english-pos-builder" class="hide">
<div class="col s6 m4 l4">
<div class="row">
<div class= "input-field col s12">
<select name="englishpos" id="english-pos">
<option value="default" disabled selected>English pos tagset</option>
<option value="ADD">email</option>
<option value="AFX">affix</option>
<option value="CC">conjunction, coordinating</option>
<option value="CD">cardinal number</option>
<option value="DT">determiner</option>
<option value="EX">existential there</option>
<option value="FW">foreign word</option>
<option value="HYPH">punctuation mark, hyphen</option>
<option value="IN">conjunction, subordinating or preposition</option>
<option value="JJ">adjective</option>
<option value="JJR">adjective, comparative</option>
<option value="JJS">adjective, superlative</option>
</select>
<label>Part-of-speech tags</label>
</div>
</div>
</div>
</div>
<div id="german-pos-builder" class="hide">
<div class="col s6 m4 l4">
<div class="row">
<div class= "input-field col s12">
<select name="germanpos" id="german-pos">
<option value="default" disabled selected>German pos tagset</option>
<option value="ADJA">adjective, attributive</option>
<option value="ADJD">adjective, adverbial or predicative</option>
<option value="ADV">adverb</option>
<option value="APPO">postposition</option>
<option value="APPR">preposition; circumposition left</option>
<option value="APPRART">preposition with article</option>
<option value="APZR">circumposition right</option>
<option value="ART">definite or indefinite article</option>
</select>
<label>Part-of-speech tags</label>
</div>
</div>
</div>
</div>
<div id="simplepos-builder" class="hide">
<div class="col s6 m4 l4">
<div class="row">
<div class= "input-field col s12">
<select name="simplepos" id="simple-pos">
<option value="default" disabled selected>simple_pos tagset</option>
<option value="ADJ">adjective</option>
<option value="ADP">adposition</option>
<option value="ADV">adverb</option>
<option value="AUX">auxiliary verb</option>
<option value="CONJ">coordinating conjunction</option>
<option value="DET">determiner</option>
<option value="INTJ">interjection</option>
<option value="NOUN">noun</option>
<option value="NUM">numeral</option>
<option value="PART">particle</option>
<option value="PRON">pronoun</option>
<option value="PROPN">proper noun</option>
<option value="PUNCT">punctuation</option>
<option value="SCONJ">subordinating conjunction</option>
<option value="SYM">symbol</option>
<option value="VERB">verb</option>
<option value="X">other</option>
</select>
<label>Simple part-of-speech tags</label>
</div>
</div>
</div>
</div>
<div class="col s1 l1 center-align">
<p class="btn-floating waves-effect waves-light" id="token-submit">
<i class="material-icons right">send</i>
</p>
</div>
<div class="hide" id="no-value-message"><i>No value entered!</i></div>
</div>
<div id="token-edit-options">
<div class="row">
<h6>Options to edit your token: <a class="modal-trigger" href="#query-builder-tutorial-modal"><i class="material-icons left" id="edit-options-tutorial-info-icon">help_outline</i></a></h6>
</div>
<p></p>
<div class="row">
<div id="input-options" class="col s5 m5 l5 xl4">
<a id="wildcard-char" class="btn-small waves-effect waves-light tooltipped" data-position="top" data-tooltip="Look for a variable character (also called wildcard character)">Wildcard character</a>
<a id="option-group" class="btn-small waves-effect waves-light tooltipped" data-position="top" data-tooltip="Find character sequences from a list of options">Option Group</a>
</div>
<div class="col s3 m3 l3 xl3" id="incidence-modifiers-button">
<a class="dropdown-trigger btn-small waves-effect waves-light" href="#" data-target="incidence-modifiers" data-position="top" data-tooltip="Incidence Modifiers are special characters or patterns, <br>which determine how often a character represented previously should occur.">incidence modifiers</a>
</div>
<ul id="incidence-modifiers" class="dropdown-content">
<li><a id="one-or-more" data-token="+" class="tooltipped" data-position ="top" data-tooltip="...occurrences of the character/token before">one or more (+)</a></li>
<li><a id="zero-or-more" data-token="*" class="tooltipped" data-position ="top" data-tooltip="...occurrences of the character/token before">zero or more (*)</a></li>
<li><a id="zero-or-one" data-token="?" class="tooltipped" data-position ="top" data-tooltip="...occurrences of the character/token before">zero or one (?)</a></li>
<li><a id="exactly-n" class="modal-trigger tooltipped" href="#exactlyN" data-token="{n}" class="" data-position ="top" data-tooltip="...occurrences of the character/token before">exactly n ({n})</a></li>
<li><a id="between-n-m" class="modal-trigger tooltipped" href="#betweenNM" data-token="{n,m}" class="" data-position ="top" data-tooltip="...occurrences of the character/token before">between n and m ({n,m})</a></li>
</ul>
<div id="ignore-case-checkbox" class="col s2 m2 l2 xl2">
<p id="ignore-case">
<label>
<input type="checkbox" class="filled-in" />
<span>Ignore Case</span>
</label>
</p>
</div>
<div class="col s2 m2 l2 xl2" id="condition-container">
<a class="btn-small tooltipped waves-effect waves-light" id="or" data-position="bottom" data-tooltip="You can add another condition to your token. <br>At least one must be fulfilled">or</a>
<a class="btn-small tooltipped waves-effect waves-light" id="and" data-position="bottom" data-tooltip="You can add another condition to your token. <br>Both must be fulfilled">and</a>
</div>
</div>
</div>
</div>
<div id="exactlyN" class="modal">
<div class="row modal-content">
<div class="input-field col s10">
<i class="material-icons prefix">mode_edit</i>
<input placeholder="type in a number for 'n'" type="text" id="n-input">
</div>
<div class="col s2">
<p class="btn-floating waves-effect waves-light" id="n-submit">
<i class="material-icons right">send</i>
</p>
</div>
</div>
</div>
<div id="betweenNM" class="modal">
<div class="row modal-content">
<div class= "input-field col s5">
<i class="material-icons prefix">mode_edit</i>
<input placeholder="number for 'n'" type="text" id="n-m-input">
</div>
<div class= "input-field col s5">
<i class="material-icons prefix">mode_edit</i>
<input placeholder="number for 'm'" type="text" id="m-input">
</div>
<div class="col s2">
<p class="btn-floating waves-effect waves-light" id="n-m-submit">
<i class="material-icons right">send</i>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal modal-fixed-footer" id="query-builder-tutorial-modal">
<div class="modal-content" >
<div id="query-builder-tutorial-start"></div>
<ul class="tabs">
<li class="tab"><a class="active" href="#query-builder-tutorial">Query Builder Tutorial</a></li>
{# <li class="tab"><a href="#qb-examples">Examples</a></li> #}
<li class="tab"><a href="#cql-cb-tutorial">Corpus Query Language Tutorial</a></li>
<li class="tab"><a href="#tagsets-cb-tutorial">Tagsets</a></li>
</ul>
<div id="query-builder-tutorial">
{% include "main/manual/_09_query_builder.html.j2" %}
</div>
{# <div id="qb-examples"></div> #}
<div id ="cql-cb-tutorial">
{% with headline_num=4 %}
{% include "main/manual/_08_cqp_query_language.html.j2" %}
{% endwith %}
</div>
<div id="tagsets-cb-tutorial">
<h4>Tagsets</h4>
{% include "main/manual/_10_tagsets.html.j2" %}
</div>
<div class="fixed-action-btn">
<a class="btn-floating btn-large teal" id="scroll-up-button-query-builder-tutorial" href='#query-builder-tutorial-start'>
<i class="large material-icons">arrow_upward</i>
</a>
</div>
</div>
</div>
{% endblock modals %}
{% block scripts %}
{{ super() }}
<script>
const concordanceQueryBuilder = new ConcordanceQueryBuilder()
</script>
{% endblock scripts %}
from flask import Blueprint
bp = Blueprint('test', __name__)
from . import routes
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment