mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2024-11-15 01:05:42 +00:00
Merge branch 'development' into public-corpora
This commit is contained in:
commit
f056a15ba8
@ -5,9 +5,9 @@ from app import db, hashids
|
|||||||
from app.decorators import admin_required
|
from app.decorators import admin_required
|
||||||
from app.models import Role, User, UserSettingJobStatusMailNotificationLevel
|
from app.models import Role, User, UserSettingJobStatusMailNotificationLevel
|
||||||
from app.settings.forms import (
|
from app.settings.forms import (
|
||||||
EditProfileSettingsForm,
|
|
||||||
EditNotificationSettingsForm
|
EditNotificationSettingsForm
|
||||||
)
|
)
|
||||||
|
from app.profile.forms import EditProfileSettingsForm
|
||||||
from . import bp
|
from . import bp
|
||||||
from .forms import AdminEditUserForm
|
from .forms import AdminEditUserForm
|
||||||
|
|
||||||
|
@ -244,6 +244,16 @@ class Token(db.Model):
|
|||||||
yesterday = datetime.utcnow() - timedelta(days=1)
|
yesterday = datetime.utcnow() - timedelta(days=1)
|
||||||
Token.query.filter(Token.refresh_expiration < yesterday).delete()
|
Token.query.filter(Token.refresh_expiration < yesterday).delete()
|
||||||
|
|
||||||
|
class Avatar(HashidMixin, FileMixin, db.Model):
|
||||||
|
__tablename__ = 'avatars'
|
||||||
|
# Primary key
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
# Foreign keys
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
return os.path.join(self.user.path, 'avatar')
|
||||||
|
|
||||||
class User(HashidMixin, UserMixin, db.Model):
|
class User(HashidMixin, UserMixin, db.Model):
|
||||||
__tablename__ = 'users'
|
__tablename__ = 'users'
|
||||||
@ -269,6 +279,12 @@ class User(HashidMixin, UserMixin, db.Model):
|
|||||||
organization = db.Column(db.String(128))
|
organization = db.Column(db.String(128))
|
||||||
# Backrefs: role: Role
|
# Backrefs: role: Role
|
||||||
# Relationships
|
# Relationships
|
||||||
|
avatar = db.relationship(
|
||||||
|
'Avatar',
|
||||||
|
backref='user',
|
||||||
|
cascade='all, delete-orphan',
|
||||||
|
uselist=False
|
||||||
|
)
|
||||||
tesseract_ocr_pipeline_models = db.relationship(
|
tesseract_ocr_pipeline_models = db.relationship(
|
||||||
'TesseractOCRPipelineModel',
|
'TesseractOCRPipelineModel',
|
||||||
backref='user',
|
backref='user',
|
||||||
@ -497,6 +513,11 @@ class User(HashidMixin, UserMixin, db.Model):
|
|||||||
),
|
),
|
||||||
'member_since': f'{self.member_since.isoformat()}Z',
|
'member_since': f'{self.member_since.isoformat()}Z',
|
||||||
'username': self.username,
|
'username': self.username,
|
||||||
|
'full_name': self.full_name,
|
||||||
|
'about_me': self.about_me,
|
||||||
|
'website': self.website,
|
||||||
|
'location': self.location,
|
||||||
|
'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
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ from app.models import User
|
|||||||
from app.auth import USERNAME_REGEX
|
from app.auth import USERNAME_REGEX
|
||||||
|
|
||||||
class EditProfileSettingsForm(FlaskForm):
|
class EditProfileSettingsForm(FlaskForm):
|
||||||
user_avatar = FileField(
|
avatar = FileField(
|
||||||
'Image File'
|
'Image File'
|
||||||
)
|
)
|
||||||
email = StringField(
|
email = StringField(
|
||||||
|
@ -1,51 +1,60 @@
|
|||||||
from flask import flash, redirect, render_template, url_for
|
from flask import (
|
||||||
|
abort,
|
||||||
|
flash,
|
||||||
|
Markup,
|
||||||
|
redirect,
|
||||||
|
render_template,
|
||||||
|
send_from_directory,
|
||||||
|
url_for
|
||||||
|
)
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
|
import os
|
||||||
from app import db
|
from app import db
|
||||||
from app.models import User
|
from app.models import Avatar, User
|
||||||
from . import bp
|
from . import bp
|
||||||
from .forms import (
|
from .forms import (
|
||||||
EditProfileSettingsForm
|
EditProfileSettingsForm
|
||||||
)
|
)
|
||||||
|
|
||||||
@bp.route('')
|
@bp.before_request
|
||||||
@login_required
|
@login_required
|
||||||
def profile():
|
def before_request():
|
||||||
user_image = 'static/images/user_avatar.png'
|
pass
|
||||||
user_name = current_user.username
|
|
||||||
last_seen = f'{current_user.last_seen.strftime("%Y-%m-%d %H:%M")}'
|
|
||||||
location = 'Bielefeld'
|
|
||||||
about_me = '''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,
|
|
||||||
no sea takimat'''
|
|
||||||
full_name = 'Inga Kirschnick'
|
|
||||||
email = current_user.email
|
|
||||||
website = 'https://nopaque.uni-bielefeld.de'
|
|
||||||
organization = 'Universität Bielefeld'
|
|
||||||
member_since = f'{current_user.member_since.strftime("%Y-%m-%d")}'
|
|
||||||
return render_template('profile/profile_page.html.j2',
|
|
||||||
user_image=user_image,
|
|
||||||
user_name=user_name,
|
|
||||||
last_seen=last_seen,
|
|
||||||
location=location,
|
|
||||||
about_me=about_me,
|
|
||||||
full_name=full_name,
|
|
||||||
email=email,
|
|
||||||
website=website,
|
|
||||||
organization=organization,
|
|
||||||
member_since=member_since)
|
|
||||||
|
|
||||||
@bp.route('/edit')
|
|
||||||
@login_required
|
@bp.route('/<hashid:user_id>')
|
||||||
def edit_profile():
|
def profile(user_id):
|
||||||
|
user = User.query.get_or_404(user_id)
|
||||||
|
return render_template('profile/profile_page.html.j2',
|
||||||
|
user=user)
|
||||||
|
|
||||||
|
@bp.route('/<hashid:user_id>/avatars/<hashid:avatar_id>')
|
||||||
|
def avatar_download(user_id, avatar_id):
|
||||||
|
avatar_file = Avatar.query.filter_by(user_id = user_id, id = avatar_id).first_or_404()
|
||||||
|
if not (avatar_file and avatar_file.filename):
|
||||||
|
abort(404)
|
||||||
|
return send_from_directory(
|
||||||
|
os.path.dirname(avatar_file.path),
|
||||||
|
os.path.basename(avatar_file.path),
|
||||||
|
as_attachment=True,
|
||||||
|
attachment_filename=avatar_file.filename,
|
||||||
|
mimetype=avatar_file.mimetype
|
||||||
|
)
|
||||||
|
|
||||||
|
@bp.route('/<hashid:user_id>/edit-profile', methods=['GET', 'POST'])
|
||||||
|
def edit_profile(user_id):
|
||||||
|
user = User.query.get_or_404(user_id)
|
||||||
edit_profile_settings_form = EditProfileSettingsForm(
|
edit_profile_settings_form = EditProfileSettingsForm(
|
||||||
current_user,
|
current_user,
|
||||||
data=current_user.to_json_serializeable(),
|
data=current_user.to_json_serializeable(),
|
||||||
prefix='edit-profile-settings-form'
|
prefix='edit-profile-settings-form'
|
||||||
)
|
)
|
||||||
if (edit_profile_settings_form.submit.data
|
if edit_profile_settings_form.validate_on_submit():
|
||||||
and edit_profile_settings_form.validate()):
|
if edit_profile_settings_form.avatar.data:
|
||||||
|
try:
|
||||||
|
Avatar.create(edit_profile_settings_form.avatar.data, user=current_user)
|
||||||
|
except (AttributeError, OSError):
|
||||||
|
abort(500)
|
||||||
current_user.email = edit_profile_settings_form.email.data
|
current_user.email = edit_profile_settings_form.email.data
|
||||||
current_user.username = edit_profile_settings_form.username.data
|
current_user.username = edit_profile_settings_form.username.data
|
||||||
current_user.about_me = edit_profile_settings_form.about_me.data
|
current_user.about_me = edit_profile_settings_form.about_me.data
|
||||||
@ -54,8 +63,10 @@ def edit_profile():
|
|||||||
current_user.website = edit_profile_settings_form.website.data
|
current_user.website = edit_profile_settings_form.website.data
|
||||||
current_user.full_name = edit_profile_settings_form.full_name.data
|
current_user.full_name = edit_profile_settings_form.full_name.data
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash('Your changes have been saved')
|
message = Markup(f'Profile settings updated')
|
||||||
return redirect(url_for('.profile.edit_profile'))
|
flash(message, 'success')
|
||||||
|
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,
|
||||||
|
user=user,
|
||||||
title='Edit Profile')
|
title='Edit Profile')
|
||||||
|
@ -47,79 +47,6 @@ class ChangePasswordForm(FlaskForm):
|
|||||||
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 EditProfileSettingsForm(FlaskForm):
|
|
||||||
user_avatar = FileField(
|
|
||||||
'Image File'
|
|
||||||
)
|
|
||||||
email = StringField(
|
|
||||||
'E-Mail',
|
|
||||||
validators=[InputRequired(), Length(max=254), Email()]
|
|
||||||
)
|
|
||||||
username = StringField(
|
|
||||||
'Username',
|
|
||||||
validators=[
|
|
||||||
InputRequired(),
|
|
||||||
Length(max=64),
|
|
||||||
Regexp(
|
|
||||||
USERNAME_REGEX,
|
|
||||||
message=(
|
|
||||||
'Usernames must have only letters, numbers, dots or '
|
|
||||||
'underscores'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
full_name = StringField(
|
|
||||||
'Full name',
|
|
||||||
validators=[Length(max=128)]
|
|
||||||
)
|
|
||||||
about_me = TextAreaField(
|
|
||||||
'About me',
|
|
||||||
validators=[
|
|
||||||
Length(max=254)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
website = StringField(
|
|
||||||
'Website',
|
|
||||||
validators=[
|
|
||||||
Length(max=254)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
organization = StringField(
|
|
||||||
'Organization',
|
|
||||||
validators=[
|
|
||||||
Length(max=128)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
location = StringField(
|
|
||||||
'Location',
|
|
||||||
validators=[
|
|
||||||
Length(max=128)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
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):
|
|
||||||
if not field.data.filename.lower().endswith('.jpg' or '.png' or '.jpeg'):
|
|
||||||
raise ValidationError('only .jpg, .png and .jpeg!')
|
|
||||||
|
|
||||||
|
|
||||||
class EditNotificationSettingsForm(FlaskForm):
|
class EditNotificationSettingsForm(FlaskForm):
|
||||||
job_status_mail_notification_level = SelectField(
|
job_status_mail_notification_level = SelectField(
|
||||||
'Job status mail notification level',
|
'Job status mail notification level',
|
||||||
@ -136,7 +63,14 @@ class EditNotificationSettingsForm(FlaskForm):
|
|||||||
]
|
]
|
||||||
|
|
||||||
class EditPrivacySettingsForm(FlaskForm):
|
class EditPrivacySettingsForm(FlaskForm):
|
||||||
public_profile = BooleanField(
|
private_profile = BooleanField(
|
||||||
'Public profile'
|
'Private profile'
|
||||||
)
|
)
|
||||||
|
private_email = BooleanField(
|
||||||
|
'Private email'
|
||||||
|
)
|
||||||
|
only_username = BooleanField(
|
||||||
|
'Show only username'
|
||||||
|
)
|
||||||
|
|
||||||
submit = SubmitField()
|
submit = SubmitField()
|
||||||
|
70
app/static/js/RessourceLists/PublicCorporaList.js
Normal file
70
app/static/js/RessourceLists/PublicCorporaList.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
class PublicCorporaList extends RessourceList {
|
||||||
|
static instances = [];
|
||||||
|
|
||||||
|
static getInstance(elem) {
|
||||||
|
return PublicCorporaList.instances.find((instance) => {
|
||||||
|
return instance.listjs.list === elem;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static autoInit() {
|
||||||
|
for (let publicCorporaListElement of document.querySelectorAll('.public-corpora-list:not(.no-autoinit)')) {
|
||||||
|
new PublicCorporaList(publicCorporaListElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static options = {
|
||||||
|
initialHtmlGenerator: (id) => {
|
||||||
|
return `
|
||||||
|
<div class="input-field">
|
||||||
|
<i class="material-icons prefix">search</i>
|
||||||
|
<input id="${id}-search" class="search" type="search"></input>
|
||||||
|
<label for="${id}-search">Search corpus</label>
|
||||||
|
</div>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="list"></tbody>
|
||||||
|
</table>
|
||||||
|
<ul class="pagination"></ul>
|
||||||
|
`.trim();
|
||||||
|
},
|
||||||
|
item: `
|
||||||
|
<tr class="clickable hoverable">
|
||||||
|
<td><a class="btn-floating disabled"><i class="material-icons service-color darken" data-service="corpus-analysis">book</i></a></td>
|
||||||
|
<td><b class="title"></b></td>
|
||||||
|
<td><i class="description"></i></td>
|
||||||
|
</tr>
|
||||||
|
`.trim(),
|
||||||
|
ressourceMapper: (corpus) => {
|
||||||
|
return {
|
||||||
|
'id': corpus.id,
|
||||||
|
'creation-date': corpus.creation_date,
|
||||||
|
'description': corpus.description,
|
||||||
|
'title': corpus.title
|
||||||
|
};
|
||||||
|
},
|
||||||
|
sortArgs: ['creation-date', {order: 'desc'}],
|
||||||
|
valueNames: [
|
||||||
|
{data: ['id']},
|
||||||
|
{data: ['creation-date']},
|
||||||
|
'description',
|
||||||
|
'title'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(listElement, options = {}) {
|
||||||
|
super(listElement, {...PublicCorporaList.options, ...options});
|
||||||
|
PublicCorporaList.instances.push(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
init(user) {
|
||||||
|
this._init(user.corpora.is_public);
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ class RessourceList {
|
|||||||
JobList.autoInit();
|
JobList.autoInit();
|
||||||
JobInputList.autoInit();
|
JobInputList.autoInit();
|
||||||
JobResultList.autoInit();
|
JobResultList.autoInit();
|
||||||
|
PublicCorporaList.autoInit();
|
||||||
SpaCyNLPPipelineModelList.autoInit();
|
SpaCyNLPPipelineModelList.autoInit();
|
||||||
TesseractOCRPipelineModelList.autoInit();
|
TesseractOCRPipelineModelList.autoInit();
|
||||||
UserList.autoInit();
|
UserList.autoInit();
|
||||||
|
@ -29,7 +29,8 @@
|
|||||||
<ul class="dropdown-content" id="nav-more-dropdown">
|
<ul class="dropdown-content" id="nav-more-dropdown">
|
||||||
<li><a href="{{ url_for('main.user_manual') }}"><i class="material-icons left">help</i>Manual</a></li>
|
<li><a href="{{ url_for('main.user_manual') }}"><i class="material-icons left">help</i>Manual</a></li>
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
<li><a href="{{ url_for('settings.settings') }}"><i class="material-icons left">settings</i>Settings</a></li>
|
<li><a href="{{ url_for('settings.settings') }}"><i class="material-icons left">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 class="divider" tabindex="-1"></li>
|
<li class="divider" tabindex="-1"></li>
|
||||||
<li><a href="{{ url_for('auth.logout') }}">Log out</a></li>
|
<li><a href="{{ url_for('auth.logout') }}">Log out</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
'js/RessourceLists/JobList.js',
|
'js/RessourceLists/JobList.js',
|
||||||
'js/RessourceLists/JobInputList.js',
|
'js/RessourceLists/JobInputList.js',
|
||||||
'js/RessourceLists/JobResultList.js',
|
'js/RessourceLists/JobResultList.js',
|
||||||
|
'js/RessourceLists/PublicCorporaList.js',
|
||||||
'js/RessourceLists/SpacyNLPPipelineModelList.js',
|
'js/RessourceLists/SpacyNLPPipelineModelList.js',
|
||||||
'js/RessourceLists/TesseractOCRPipelineModelList.js',
|
'js/RessourceLists/TesseractOCRPipelineModelList.js',
|
||||||
'js/RessourceLists/UserList.js'
|
'js/RessourceLists/UserList.js'
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="background primary-color"></div>
|
<div class="background primary-color"></div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s2">
|
<div class="col s2">
|
||||||
<a href="{{ url_for('profile.profile') }}">
|
<a href="{{ url_for('profile.profile', user_id=current_user.id) }}">
|
||||||
<i class="material-icons" style="color:white; font-size:3em; margin-top: 25px; margin-left:-15px;">account_circle</i></div>
|
<i class="material-icons" style="color:white; font-size:3em; margin-top: 25px; margin-left:-15px;">account_circle</i></div>
|
||||||
</a>
|
</a>
|
||||||
<div class="col s10">
|
<div class="col s10">
|
||||||
@ -14,12 +14,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="{{ url_for('main.index') }}">nopaque</a></li>
|
{# <li><a href="{{ url_for('main.index') }}">nopaque</a></li> #}
|
||||||
<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="{{ url_for('main.user_manual') }}"><i class="material-icons">help</i>Manual</a></li>
|
<li><a href="{{ url_for('main.user_manual') }}"><i class="material-icons">help</i>Manual</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-icons">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-icons">J</i>My Jobs</a></li>
|
||||||
|
<li><a href="{{ url_for('main.dashboard', _anchor='social') }}" style="padding-left: 47px;"><i class="material-icons">groups</i>Social</a></li>
|
||||||
<li><a href="{{ url_for('contributions.contributions') }}"><i class="material-icons">new_label</i>Contribute</a></li>
|
<li><a href="{{ url_for('contributions.contributions') }}"><i class="material-icons">new_label</i>Contribute</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>
|
||||||
|
@ -42,6 +42,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col s12" id="social">
|
||||||
|
<h3>Social</h3>
|
||||||
|
<div class="col s6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title">Other users and groups</span>
|
||||||
|
<p>Find other users and follow them to see their corpora and groups.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col s6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title">Public corpora</span>
|
||||||
|
<p>Find public corpora</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock page_content %}
|
{% endblock page_content %}
|
||||||
|
@ -7,18 +7,31 @@
|
|||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
<h1 id="title">{{ title }}</h1>
|
<h1 id="title">{{ title }}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
<form method="POST">
|
|
||||||
{{ edit_profile_settings_form.hidden_tag() }}
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
<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 s5">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s1"></div>
|
<div class="col s1"></div>
|
||||||
<div class="col s3">
|
<div class="col s10">
|
||||||
|
{% 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">
|
||||||
|
{% 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">
|
||||||
{{wtf.render_field(edit_profile_settings_form.user_avatar, accept='image/*', class='file-path validate')}}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col s1"></div>
|
<div class="col s1"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12">
|
||||||
|
{{wtf.render_field(edit_profile_settings_form.avatar, accept='image/jpeg, image/png, image/gif', placeholder="Choose an image file")}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="col s7">
|
<div class="col s7">
|
||||||
{{ wtf.render_field(edit_profile_settings_form.username, material_icon='person') }}
|
{{ wtf.render_field(edit_profile_settings_form.username, material_icon='person') }}
|
||||||
{{ wtf.render_field(edit_profile_settings_form.email, material_icon='email') }}
|
{{ wtf.render_field(edit_profile_settings_form.email, material_icon='email') }}
|
||||||
@ -35,9 +48,9 @@
|
|||||||
{{ wtf.render_field(edit_profile_settings_form.submit, material_icon='send') }}
|
{{ wtf.render_field(edit_profile_settings_form.submit, material_icon='send') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock page_content %}
|
{% endblock page_content %}
|
||||||
|
@ -9,27 +9,26 @@
|
|||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s1"></div>
|
<div class="col s1"></div>
|
||||||
<div class="col s3">
|
<div class="col s4">
|
||||||
{% if about_me %}
|
{% if user.avatar %}
|
||||||
<img src="{{ user_image }}" alt="user-image" class="circle responsive-img" style="margin-top:50px;">
|
<img src="{{ url_for('profile.avatar_download', user_id=user.id, avatar_id=user.avatar.id) }}" alt="user-image" class="circle responsive-img">
|
||||||
{% else %}
|
{% else %}
|
||||||
<img src="{{ user_image }}" alt="user-image" class="circle responsive-img">
|
<img src="{{ url_for('static', filename='images/user_avatar.png') }}" alt="user-image" class="circle responsive-img">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col s1"></div>
|
|
||||||
<div class="col s7">
|
<div class="col s7">
|
||||||
<h3>{{ user_name }}</h3>
|
<h3>{{ user.username }}</h3>
|
||||||
<div class="chip">Last seen: {{ last_seen }}</div>
|
<div class="chip">Last seen: {{ user.last_seen.strftime('%Y-%m-%d %H:%M') }}</div>
|
||||||
{% if location %}
|
{% if user.location %}
|
||||||
<p><span class="material-icons" style="margin-right:20px; margin-top:20px;">location_on</span><i>{{ 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 about_me%}
|
{% if user.about_me%}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<span class="card-title">About me</span>
|
<span class="card-title">About me</span>
|
||||||
<p>{{ about_me }}</p>
|
<p>{{ user.about_me }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -39,36 +38,38 @@
|
|||||||
<div class="col s1"></div>
|
<div class="col s1"></div>
|
||||||
<div class="col s6">
|
<div class="col s6">
|
||||||
<table>
|
<table>
|
||||||
{% if full_name %}
|
{% if user.full_name %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="material-icons">person</span></td>
|
<td><span class="material-icons">person</span></td>
|
||||||
<td>{{ full_name }} </td>
|
<td>{{ user.full_name }} </td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if email %}
|
{% if user.email %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="material-icons">email</span></td>
|
<td><span class="material-icons">email</span></td>
|
||||||
<td>{{ email }}</td>
|
<td>{{ user.email }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if website %}
|
{% if user.website %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="material-icons">laptop</span></td>
|
<td><span class="material-icons">laptop</span></td>
|
||||||
<td><a href="{{ website }}">{{ website }}</a></td>
|
<td><a href="{{ user.website }}">{{ user.website }}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if organization %}
|
{% if user.organization %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="material-icons">business</span></td>
|
<td><span class="material-icons">business</span></td>
|
||||||
<td>{{ organization }}</td>
|
<td>{{ user.organization }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
<br>
|
<br>
|
||||||
<p><i>Member since: {{ member_since }}</i></p>
|
<p><i>Member since: {{ user.member_since.strftime('%Y-%m-%d') }}</i></p>
|
||||||
<p></p>
|
<p></p>
|
||||||
<br>
|
<br>
|
||||||
<a class="waves-effect waves-light btn-small" href="{{ url_for('settings.settings') }}">Edit profile</a>
|
{% 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>
|
||||||
@ -77,9 +78,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s6">
|
<div class="col s6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<h4>Groups</h4>
|
||||||
|
</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>
|
||||||
</div>
|
</div>
|
||||||
{% endblock page_content %}
|
{% endblock page_content %}
|
||||||
|
@ -27,7 +27,13 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<span class="card-title">Privacy settings</span>
|
<span class="card-title">Privacy settings</span>
|
||||||
{{ wtf.render_field(edit_privacy_settings_form.public_profile) }}
|
<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>
|
||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
<div class="right-align">
|
<div class="right-align">
|
||||||
|
31
migrations/versions/ef6a275f8079_.py
Normal file
31
migrations/versions/ef6a275f8079_.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
"""Add avatar table and conncect it to users
|
||||||
|
|
||||||
|
Revision ID: ef6a275f8079
|
||||||
|
Revises: 4820fa2e3ee2
|
||||||
|
Create Date: 2022-12-01 14:23:22.688572
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
revision = 'ef6a275f8079'
|
||||||
|
down_revision = '4820fa2e3ee2'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.create_table('avatars',
|
||||||
|
sa.Column('creation_date', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('filename', sa.String(length=255), 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.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
op.drop_table('avatars')
|
@ -5,6 +5,7 @@ eventlet.monkey_patch()
|
|||||||
|
|
||||||
from app import cli, create_app, db, scheduler, socketio # noqa
|
from app import cli, create_app, db, scheduler, socketio # noqa
|
||||||
from app.models import (
|
from app.models import (
|
||||||
|
Avatar,
|
||||||
Corpus,
|
Corpus,
|
||||||
CorpusFile,
|
CorpusFile,
|
||||||
Job,
|
Job,
|
||||||
@ -34,6 +35,7 @@ def make_context() -> Dict[str, Any]:
|
|||||||
def make_shell_context() -> Dict[str, Any]:
|
def make_shell_context() -> Dict[str, Any]:
|
||||||
''' Adds variables to the shell context. '''
|
''' Adds variables to the shell context. '''
|
||||||
return {
|
return {
|
||||||
|
'Avatar': Avatar,
|
||||||
'Corpus': Corpus,
|
'Corpus': Corpus,
|
||||||
'CorpusFile': CorpusFile,
|
'CorpusFile': CorpusFile,
|
||||||
'db': db,
|
'db': db,
|
||||||
|
Loading…
Reference in New Issue
Block a user