Merge branch 'development' into public-corpora

This commit is contained in:
Patrick Jentsch 2022-12-07 14:05:21 +01:00
commit f056a15ba8
16 changed files with 282 additions and 159 deletions

View File

@ -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

View File

@ -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
} }

View File

@ -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(

View File

@ -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')

View File

@ -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()

View 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);
}
}

View File

@ -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();

View File

@ -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 %}

View File

@ -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'

View File

@ -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>

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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">

View 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')

View File

@ -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,