diff --git a/app/admin/routes.py b/app/admin/routes.py index 90d9034db4040fe0c3c24ce70d5583fec1403b03..2f50b99980fe505710c5847e9afe093f2a2e5b1d 100644 --- a/app/admin/routes.py +++ b/app/admin/routes.py @@ -5,9 +5,9 @@ from app import db, hashids from app.decorators import admin_required from app.models import Role, User, UserSettingJobStatusMailNotificationLevel from app.settings.forms import ( - EditProfileSettingsForm, EditNotificationSettingsForm ) +from app.profile.forms import EditProfileSettingsForm from . import bp from .forms import AdminEditUserForm diff --git a/app/models.py b/app/models.py index 1e2cfb32185887db14eda044dc99738084177d6d..7927c81f7ae7b722978eef9e309482cd78905dd4 100644 --- a/app/models.py +++ b/app/models.py @@ -244,7 +244,17 @@ class Token(db.Model): yesterday = datetime.utcnow() - timedelta(days=1) 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): __tablename__ = 'users' # Primary key @@ -269,6 +279,12 @@ class User(HashidMixin, UserMixin, db.Model): organization = db.Column(db.String(128)) # Backrefs: role: Role # Relationships + avatar = db.relationship( + 'Avatar', + backref='user', + cascade='all, delete-orphan', + uselist=False + ) tesseract_ocr_pipeline_models = db.relationship( 'TesseractOCRPipelineModel', backref='user', @@ -299,7 +315,7 @@ class User(HashidMixin, UserMixin, db.Model): cascade='all, delete-orphan', lazy='dynamic' ) - + def __init__(self, **kwargs): super().__init__(**kwargs) if self.role is not None: @@ -497,6 +513,11 @@ class User(HashidMixin, UserMixin, db.Model): ), 'member_since': f'{self.member_since.isoformat()}Z', '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': \ self.setting_job_status_mail_notification_level.name } diff --git a/app/profile/forms.py b/app/profile/forms.py index bcb9daf9c2cc57ffec56de918b18898af2a069e1..20bd4ad4b9ed93ca6de61211aa37c73310d6edd8 100644 --- a/app/profile/forms.py +++ b/app/profile/forms.py @@ -16,7 +16,7 @@ from app.models import User from app.auth import USERNAME_REGEX class EditProfileSettingsForm(FlaskForm): - user_avatar = FileField( + avatar = FileField( 'Image File' ) email = StringField( diff --git a/app/profile/routes.py b/app/profile/routes.py index 8001ddb44cc9acb3a64f23362eae42e6094bb658..4702dcb7ca5d573cfec733bc4c7a2b037d806e9b 100644 --- a/app/profile/routes.py +++ b/app/profile/routes.py @@ -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 +import os from app import db -from app.models import User +from app.models import Avatar, User from . import bp from .forms import ( EditProfileSettingsForm ) -@bp.route('') +@bp.before_request @login_required -def profile(): - user_image = 'static/images/user_avatar.png' - 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) +def before_request(): + pass -@bp.route('/edit') -@login_required -def edit_profile(): - edit_profile_settings_form = EditProfileSettingsForm( + +@bp.route('/<hashid:user_id>') +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( current_user, data=current_user.to_json_serializeable(), prefix='edit-profile-settings-form' - ) - if (edit_profile_settings_form.submit.data - and edit_profile_settings_form.validate()): + ) + if edit_profile_settings_form.validate_on_submit(): + 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.username = edit_profile_settings_form.username.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.full_name = edit_profile_settings_form.full_name.data db.session.commit() - flash('Your changes have been saved') - return redirect(url_for('.profile.edit_profile')) - return render_template('profile/edit_profile.html.j2', - edit_profile_settings_form=edit_profile_settings_form, - title='Edit Profile') + message = Markup(f'Profile settings updated') + flash(message, 'success') + return redirect(url_for('.profile', user_id=user.id)) + return render_template('profile/edit_profile.html.j2', + edit_profile_settings_form=edit_profile_settings_form, + user=user, + title='Edit Profile') diff --git a/app/settings/forms.py b/app/settings/forms.py index 25bb5f1f1a28ff78eb8476c78de304da3396f988..335f73d30887dc0423b3a6b648b9ed4aea2eb1ec 100644 --- a/app/settings/forms.py +++ b/app/settings/forms.py @@ -47,79 +47,6 @@ class ChangePasswordForm(FlaskForm): if not self.user.verify_password(field.data): 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): job_status_mail_notification_level = SelectField( 'Job status mail notification level', @@ -136,7 +63,14 @@ class EditNotificationSettingsForm(FlaskForm): ] class EditPrivacySettingsForm(FlaskForm): - public_profile = BooleanField( - 'Public profile' + private_profile = BooleanField( + 'Private profile' + ) + private_email = BooleanField( + 'Private email' ) + only_username = BooleanField( + 'Show only username' + ) + submit = SubmitField() diff --git a/app/static/js/RessourceLists/PublicCorporaList.js b/app/static/js/RessourceLists/PublicCorporaList.js new file mode 100644 index 0000000000000000000000000000000000000000..2171396eae562ebecc4aa4de8655560592ed7565 --- /dev/null +++ b/app/static/js/RessourceLists/PublicCorporaList.js @@ -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); + } +} diff --git a/app/static/js/RessourceLists/RessourceList.js b/app/static/js/RessourceLists/RessourceList.js index 871a1e2f89cb6286d860ce559da81fbba43453a5..5af7a2319627d24d1e408d008e44c52d6c485d23 100644 --- a/app/static/js/RessourceLists/RessourceList.js +++ b/app/static/js/RessourceLists/RessourceList.js @@ -10,6 +10,7 @@ class RessourceList { JobList.autoInit(); JobInputList.autoInit(); JobResultList.autoInit(); + PublicCorporaList.autoInit(); SpaCyNLPPipelineModelList.autoInit(); TesseractOCRPipelineModelList.autoInit(); UserList.autoInit(); diff --git a/app/templates/_navbar.html.j2 b/app/templates/_navbar.html.j2 index e2d4db6452116377e4e007a86e0674ea63373b7a..d90118b8548cb41d0fe72ab0681400df7f611b5c 100644 --- a/app/templates/_navbar.html.j2 +++ b/app/templates/_navbar.html.j2 @@ -29,7 +29,8 @@ <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> {% 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><a href="{{ url_for('auth.logout') }}">Log out</a></li> {% else %} diff --git a/app/templates/_scripts.html.j2 b/app/templates/_scripts.html.j2 index 7cc8a8f80de6738d86e3c86585835fe2a578bf73..8ff90c330be20f1b379d824765a3b8cfb7f6f279 100644 --- a/app/templates/_scripts.html.j2 +++ b/app/templates/_scripts.html.j2 @@ -24,6 +24,7 @@ 'js/RessourceLists/JobList.js', 'js/RessourceLists/JobInputList.js', 'js/RessourceLists/JobResultList.js', + 'js/RessourceLists/PublicCorporaList.js', 'js/RessourceLists/SpacyNLPPipelineModelList.js', 'js/RessourceLists/TesseractOCRPipelineModelList.js', 'js/RessourceLists/UserList.js' diff --git a/app/templates/_sidenav.html.j2 b/app/templates/_sidenav.html.j2 index f87f701eef5280b732ce9bf31be3cbf0ebb9a314..1aff7f8950e34d65b6e4cd3d3d89fce00b637f16 100644 --- a/app/templates/_sidenav.html.j2 +++ b/app/templates/_sidenav.html.j2 @@ -4,7 +4,7 @@ <div class="background primary-color"></div> <div class="row"> <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> </a> <div class="col s10"> @@ -14,12 +14,13 @@ </div> </div> </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.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', _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='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><div class="divider"></div></li> <li><a class="subheader">Processes & Services</a></li> diff --git a/app/templates/main/dashboard.html.j2 b/app/templates/main/dashboard.html.j2 index ec03609d0c41a56020d0c0a134793512a8cfd255..e0354ea624eff3230e0a2d392fb2d6a93ca4ba45 100644 --- a/app/templates/main/dashboard.html.j2 +++ b/app/templates/main/dashboard.html.j2 @@ -42,6 +42,25 @@ </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> {% endblock page_content %} diff --git a/app/templates/profile/edit_profile.html.j2 b/app/templates/profile/edit_profile.html.j2 index 3a5ce381becc5e59f14412091e0ab7ad7c379ee8..1f4bd0e7d032c3ae817d2470e1949f28d0c25c87 100644 --- a/app/templates/profile/edit_profile.html.j2 +++ b/app/templates/profile/edit_profile.html.j2 @@ -5,20 +5,33 @@ <div class="container"> <div class="row"> <div class="col s12"> - <h1 id="title">{{ title }}</h1> - </div> + <h1 id="title">{{ title }}</h1> + </div> + <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"> + {{ edit_profile_settings_form.hidden_tag() }} <div class="row"> - <div class="col s1"></div> - <div class="col s3"> - <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')}} + <div class="col s5"> + <div class="row"> + <div class="col s1"></div> + <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"> + {% endif %} + </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 s1"></div> <div class="col s7"> {{ wtf.render_field(edit_profile_settings_form.username, material_icon='person') }} {{ wtf.render_field(edit_profile_settings_form.email, material_icon='email') }} @@ -35,8 +48,8 @@ {{ wtf.render_field(edit_profile_settings_form.submit, material_icon='send') }} </div> </div> - </div> - </form> + </form> + </div> </div> </div> </div> diff --git a/app/templates/profile/profile_page.html.j2 b/app/templates/profile/profile_page.html.j2 index fd4648aaf706f18fb842e65200210a4fdfa9c084..dbcd78cad576d67d08c65a04203131efbea64bdc 100644 --- a/app/templates/profile/profile_page.html.j2 +++ b/app/templates/profile/profile_page.html.j2 @@ -9,27 +9,26 @@ <div class="card-content"> <div class="row"> <div class="col s1"></div> - <div class="col s3"> - {% if about_me %} - <img src="{{ user_image }}" alt="user-image" class="circle responsive-img" style="margin-top:50px;"> + <div class="col s4"> + {% 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="{{ 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 %} </div> - <div class="col s1"></div> <div class="col s7"> - <h3>{{ user_name }}</h3> - <div class="chip">Last seen: {{ last_seen }}</div> - {% if location %} - <p><span class="material-icons" style="margin-right:20px; margin-top:20px;">location_on</span><i>{{ location }}</i></p> + <h3>{{ user.username }}</h3> + <div class="chip">Last seen: {{ user.last_seen.strftime('%Y-%m-%d %H:%M') }}</div> + {% if user.location %} + <p><span class="material-icons" style="margin-right:20px; margin-top:20px;">location_on</span><i>{{ user.location }}</i></p> {% endif %} <p></p> <br> - {% if about_me%} + {% if user.about_me%} <div class="card"> <div class="card-content"> <span class="card-title">About me</span> - <p>{{ about_me }}</p> + <p>{{ user.about_me }}</p> </div> </div> {% endif %} @@ -39,36 +38,38 @@ <div class="col s1"></div> <div class="col s6"> <table> - {% if full_name %} + {% if user.full_name %} <tr> <td><span class="material-icons">person</span></td> - <td>{{ full_name }} </td> + <td>{{ user.full_name }} </td> </tr> {% endif %} - {% if email %} + {% if user.email %} <tr> <td><span class="material-icons">email</span></td> - <td>{{ email }}</td> + <td>{{ user.email }}</td> </tr> {% endif %} - {% if website %} + {% if user.website %} <tr> <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> {% endif %} - {% if organization %} + {% if user.organization %} <tr> <td><span class="material-icons">business</span></td> - <td>{{ organization }}</td> + <td>{{ user.organization }}</td> </tr> {% endif %} </table> <br> - <p><i>Member since: {{ member_since }}</i></p> + <p><i>Member since: {{ user.member_since.strftime('%Y-%m-%d') }}</i></p> <p></p> <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> @@ -77,9 +78,20 @@ </div> <div class="row"> <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> {% endblock page_content %} diff --git a/app/templates/settings/settings.html.j2 b/app/templates/settings/settings.html.j2 index b33ab0a1c8dabb861cecec5e242c4a800da90a5a..f272e63a8cb745e4f370cf4d647fcf39eaca4cf1 100644 --- a/app/templates/settings/settings.html.j2 +++ b/app/templates/settings/settings.html.j2 @@ -27,7 +27,13 @@ <div class="card"> <div class="card-content"> <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 class="card-action"> <div class="right-align"> diff --git a/migrations/versions/ef6a275f8079_.py b/migrations/versions/ef6a275f8079_.py new file mode 100644 index 0000000000000000000000000000000000000000..56fc6469424d70d337e90d6340c1dcef2ea74a0f --- /dev/null +++ b/migrations/versions/ef6a275f8079_.py @@ -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') diff --git a/nopaque.py b/nopaque.py index cf1b1decc1aff9cbc86824da143717a74802793e..6ecb2cf09a7259979bbe2690faf7a5a04b7e36a8 100644 --- a/nopaque.py +++ b/nopaque.py @@ -5,6 +5,7 @@ eventlet.monkey_patch() from app import cli, create_app, db, scheduler, socketio # noqa from app.models import ( + Avatar, Corpus, CorpusFile, Job, @@ -34,6 +35,7 @@ def make_context() -> Dict[str, Any]: def make_shell_context() -> Dict[str, Any]: ''' Adds variables to the shell context. ''' return { + 'Avatar': Avatar, 'Corpus': Corpus, 'CorpusFile': CorpusFile, 'db': db,