Merge branch 'development' of gitlab.ub.uni-bielefeld.de:sfb1288inf/opaque into development

This commit is contained in:
Stephan Porada 2020-04-27 16:16:59 +02:00
commit eb5e5c3253
18 changed files with 185 additions and 78 deletions

View File

@ -17,10 +17,13 @@ def before_request():
Checks if a user is unconfirmed when visiting specific sites. Redirects to Checks if a user is unconfirmed when visiting specific sites. Redirects to
unconfirmed view if user is unconfirmed. unconfirmed view if user is unconfirmed.
""" """
if (current_user.is_authenticated and not current_user.confirmed if current_user.is_authenticated:
and request.blueprint != 'auth' current_user.ping()
and request.endpoint != 'static'): if not current_user.confirmed \
return redirect(url_for('auth.unconfirmed')) and request.endpoint \
and request.blueprint != 'auth' \
and request.endpoint != 'static':
return redirect(url_for('auth.unconfirmed'))
@auth.route('/login', methods=['GET', 'POST']) @auth.route('/login', methods=['GET', 'POST'])

View File

@ -26,13 +26,12 @@ def add_corpus():
try: try:
os.makedirs(dir) os.makedirs(dir)
except OSError: except OSError:
flash('[ERROR]: Could not add corpus!') flash('[ERROR]: Could not add corpus!', 'corpus')
corpus.delete() corpus.delete()
else: else:
corpus_url = url_for('corpora.corpus', corpus_id=corpus.id) url = url_for('corpora.corpus', corpus_id=corpus.id)
flash('<i class="left material-icons">book</i>' flash('[<a href="{}">{}</a>] added'.format(url, corpus.title),
'[<a href="{}">{}</a>] added'.format(corpus_url, 'corpus')
corpus.title))
return redirect(url_for('corpora.corpus', corpus_id=corpus.id)) return redirect(url_for('corpora.corpus', corpus_id=corpus.id))
return render_template('corpora/add_corpus.html.j2', return render_template('corpora/add_corpus.html.j2',
add_corpus_form=add_corpus_form, add_corpus_form=add_corpus_form,
@ -82,7 +81,7 @@ def delete_corpus(corpus_id):
if not (corpus.creator == current_user or current_user.is_administrator()): if not (corpus.creator == current_user or current_user.is_administrator()):
abort(403) abort(403)
tasks.delete_corpus(corpus_id) tasks.delete_corpus(corpus_id)
flash('Corpus deleted!') flash('Corpus deleted!', 'corpus')
return redirect(url_for('main.dashboard')) return redirect(url_for('main.dashboard'))
@ -121,7 +120,7 @@ def add_corpus_file(corpus_id):
db.session.add(corpus_file) db.session.add(corpus_file)
corpus.status = 'unprepared' corpus.status = 'unprepared'
db.session.commit() db.session.commit()
flash('Corpus file added!') flash('Corpus file added!', 'corpus')
return make_response( return make_response(
{'redirect_url': url_for('corpora.corpus', corpus_id=corpus.id)}, {'redirect_url': url_for('corpora.corpus', corpus_id=corpus.id)},
201) 201)
@ -141,7 +140,7 @@ def delete_corpus_file(corpus_id, corpus_file_id):
or current_user.is_administrator()): or current_user.is_administrator()):
abort(403) abort(403)
tasks.delete_corpus_file(corpus_file_id) tasks.delete_corpus_file(corpus_file_id)
flash('Corpus file deleted!') flash('Corpus file deleted!', 'corpus')
return redirect(url_for('corpora.corpus', corpus_id=corpus_id)) return redirect(url_for('corpora.corpus', corpus_id=corpus_id))
@ -188,7 +187,7 @@ def edit_corpus_file(corpus_id, corpus_file_id):
corpus_file.title = edit_corpus_file_form.title.data corpus_file.title = edit_corpus_file_form.title.data
corpus.status = 'unprepared' corpus.status = 'unprepared'
db.session.commit() db.session.commit()
flash('Corpus file edited!') flash('Corpus file edited!', 'corpus')
return redirect(url_for('corpora.corpus', corpus_id=corpus_id)) return redirect(url_for('corpora.corpus', corpus_id=corpus_id))
# If no form is submitted or valid, fill out fields with current values # If no form is submitted or valid, fill out fields with current values
edit_corpus_file_form.address.data = corpus_file.address edit_corpus_file_form.address.data = corpus_file.address
@ -217,7 +216,7 @@ def prepare_corpus(corpus_id):
abort(403) abort(403)
if corpus.files.all(): if corpus.files.all():
tasks.build_corpus(corpus_id) tasks.build_corpus(corpus_id)
flash('Corpus gets build now.') flash('Corpus gets build now.', 'corpus')
else: else:
flash('Can not build corpus, please add corpus file(s).') flash('Can not build corpus, please add corpus file(s).', 'corpus')
return redirect(url_for('corpora.corpus', corpus_id=corpus_id)) return redirect(url_for('corpora.corpus', corpus_id=corpus_id))

View File

@ -16,6 +16,7 @@ def create_message(recipient, subject, template, **kwargs):
@background @background
def send(app, msg): def send(msg, *args, **kwargs):
app = kwargs['app']
with app.app_context(): with app.app_context():
mail.send(msg) mail.send(msg)

View File

@ -23,7 +23,7 @@ def delete_job(job_id):
if not (job.creator == current_user or current_user.is_administrator()): if not (job.creator == current_user or current_user.is_administrator()):
abort(403) abort(403)
tasks.delete_job(job_id) tasks.delete_job(job_id)
flash('Job has been deleted!') flash('Job has been deleted!', 'job')
return redirect(url_for('main.dashboard')) return redirect(url_for('main.dashboard'))

View File

@ -106,13 +106,18 @@ class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
# Fields # Fields
confirmed = db.Column(db.Boolean, default=False) confirmed = db.Column(db.Boolean, default=False)
last_seen = db.Column(db.DateTime(), default=datetime.utcnow)
email = db.Column(db.String(254), unique=True, index=True) email = db.Column(db.String(254), unique=True, index=True)
password_hash = db.Column(db.String(128)) password_hash = db.Column(db.String(128))
registration_date = db.Column(db.DateTime(), default=datetime.utcnow) member_since = db.Column(db.DateTime(), default=datetime.utcnow)
role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
username = db.Column(db.String(64), unique=True, index=True) username = db.Column(db.String(64), unique=True, index=True)
# Setting Fields # Setting Fields
setting_dark_mode = db.Column(db.Boolean, default=False) setting_dark_mode = db.Column(db.Boolean, default=False)
setting_job_status_mail_notifications = db.Column(db.String(16),
default='end')
setting_job_status_site_notifications = db.Column(db.String(16),
default='all')
# Relationships # Relationships
corpora = db.relationship('Corpus', backref='creator', lazy='dynamic', corpora = db.relationship('Corpus', backref='creator', lazy='dynamic',
cascade='save-update, merge, delete') cascade='save-update, merge, delete')
@ -205,6 +210,10 @@ class User(UserMixin, db.Model):
""" """
return self.can(Permission.ADMIN) return self.can(Permission.ADMIN)
def ping(self):
self.last_seen = datetime.utcnow()
db.session.add(self)
def delete(self): def delete(self):
""" """
Delete the user and its corpora and jobs from database and filesystem. Delete the user and its corpora and jobs from database and filesystem.

View File

@ -1,6 +1,6 @@
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import (BooleanField, PasswordField, StringField, SubmitField, from wtforms import (BooleanField, PasswordField, SelectField, StringField,
ValidationError) SubmitField, ValidationError)
from wtforms.validators import DataRequired, Email, EqualTo from wtforms.validators import DataRequired, Email, EqualTo
@ -11,6 +11,20 @@ class EditEmailForm(FlaskForm):
class EditGeneralSettingsForm(FlaskForm): class EditGeneralSettingsForm(FlaskForm):
dark_mode = BooleanField('Dark mode') dark_mode = BooleanField('Dark mode')
job_status_mail_notifications = SelectField(
'Job status mail notifications',
choices=[('', 'Choose your option'),
('all', 'Notify on all status changes'),
('end', 'Notify only when a job ended'),
('none', 'No status update notifications')],
validators=[DataRequired()])
job_status_site_notifications = SelectField(
'Job status site notifications',
choices=[('', 'Choose your option'),
('all', 'Notify on all status changes'),
('end', 'Notify only when a job ended'),
('none', 'No status update notifications')],
validators=[DataRequired()])
save_settings = SubmitField('Save settings') save_settings = SubmitField('Save settings')

View File

@ -11,8 +11,7 @@ from .. import db
def settings(): def settings():
edit_email_form = EditEmailForm(prefix='edit-email-form') edit_email_form = EditEmailForm(prefix='edit-email-form')
edit_general_settings_form = EditGeneralSettingsForm( edit_general_settings_form = EditGeneralSettingsForm(
prefix='edit-general-settings-form' prefix='edit-general-settings-form')
)
edit_password_form = EditPasswordForm(prefix='edit-password-form', edit_password_form = EditPasswordForm(prefix='edit-password-form',
user=current_user) user=current_user)
# Check if edit_email_form is submitted and valid # Check if edit_email_form is submitted and valid
@ -25,7 +24,12 @@ def settings():
# Check if edit_settings_form is submitted and valid # Check if edit_settings_form is submitted and valid
if (edit_general_settings_form.save_settings.data if (edit_general_settings_form.save_settings.data
and edit_general_settings_form.validate_on_submit()): and edit_general_settings_form.validate_on_submit()):
current_user.setting_dark_mode = edit_general_settings_form.dark_mode.data current_user.setting_dark_mode = \
edit_general_settings_form.dark_mode.data
current_user.setting_job_status_mail_notifications = \
edit_general_settings_form.job_status_mail_notifications.data
current_user.setting_job_status_site_notifications = \
edit_general_settings_form.job_status_site_notifications.data
db.session.add(current_user) db.session.add(current_user)
db.session.commit() db.session.commit()
flash('Your settings have been updated.') flash('Your settings have been updated.')
@ -41,6 +45,10 @@ def settings():
# If no form is submitted or valid, fill out fields with current values # If no form is submitted or valid, fill out fields with current values
edit_email_form.email.data = current_user.email edit_email_form.email.data = current_user.email
edit_general_settings_form.dark_mode.data = current_user.setting_dark_mode edit_general_settings_form.dark_mode.data = current_user.setting_dark_mode
edit_general_settings_form.job_status_site_notifications.data = \
current_user.setting_job_status_site_notifications
edit_general_settings_form.job_status_mail_notifications.data = \
current_user.setting_job_status_mail_notifications
return render_template( return render_template(
'profile/settings.html.j2', 'profile/settings.html.j2',
edit_email_form=edit_email_form, edit_email_form=edit_email_form,

View File

@ -61,7 +61,7 @@ def service(service):
os.makedirs(absolut_dir) os.makedirs(absolut_dir)
except OSError: except OSError:
job.delete() job.delete()
flash('Internal Server Error') flash('Internal Server Error', 'job')
return make_response({'redirect_url': url_for('services.service', return make_response({'redirect_url': url_for('services.service',
service=service)}, service=service)},
500) 500)
@ -74,9 +74,8 @@ def service(service):
db.session.add(job_input) db.session.add(job_input)
job.status = 'submitted' job.status = 'submitted'
db.session.commit() db.session.commit()
job_url = url_for('jobs.job', job_id=job.id) url = url_for('jobs.job', job_id=job.id)
flash('<i class="left material-icons">work</i>' flash('[<a href="{}">{}</a>] added'.format(url, job.title), 'job')
'[<a href="{}">{}</a>] added'.format(job_url, job.title))
return make_response( return make_response(
{'redirect_url': url_for('jobs.job', job_id=job.id)}, 201) {'redirect_url': url_for('jobs.job', job_id=job.id)}, 201)
return render_template('services/{}.html.j2'.format(service), return render_template('services/{}.html.j2'.format(service),

View File

@ -67,7 +67,7 @@ class CorpusAnalysisClient {
} }
} else { } else {
errorText = `Error ${response.payload.code} - ${response.payload.msg}`; errorText = `Error ${response.payload.code} - ${response.payload.msg}`;
nopaque.flash("error", errorText); nopaque.flash(errorText, "error");
if (this.displays.query.errorContainer != undefined) { if (this.displays.query.errorContainer != undefined) {
this.displays.query.errorContainer.innerHTML = `<p class="red-text">`+ this.displays.query.errorContainer.innerHTML = `<p class="red-text">`+
`<i class="material-icons tiny">error</i> ${errorText}</p>`; `<i class="material-icons tiny">error</i> ${errorText}</p>`;

View File

@ -43,13 +43,6 @@ nopaque.socket.init = function() {
var patch; var patch;
patch = JSON.parse(msg); patch = JSON.parse(msg);
for (operation of patch) {
/* "/corpusId/valueName" -> ["corpusId", "valueName"] */
pathArray = operation.path.split("/").slice(1);
if (operation.op === "replace" && pathArray[1] === "status") {
nopaque.flash(`<i class="left material-icons">book</i>[<a href="/jobs/${pathArray[0]}">${nopaque.corpora[pathArray[0]].title}</a>] New status: ${operation.value}`);
}
}
nopaque.corpora = jsonpatch.apply_patch(nopaque.corpora, patch); nopaque.corpora = jsonpatch.apply_patch(nopaque.corpora, patch);
for (let subscriber of nopaque.corporaSubscribers) {subscriber._update(patch);} for (let subscriber of nopaque.corporaSubscribers) {subscriber._update(patch);}
}); });
@ -58,14 +51,17 @@ nopaque.socket.init = function() {
var patch; var patch;
patch = JSON.parse(msg); patch = JSON.parse(msg);
for (operation of patch) { nopaque.jobs = jsonpatch.apply_patch(nopaque.jobs, patch);
/* "/jobId/valueName" -> ["jobId", "valueName"] */ if (["all", "end"].includes(nopaque.user.settings.jobStatusSiteNotifications)) {
pathArray = operation.path.split("/").slice(1); for (operation of patch) {
if (operation.op === "replace" && pathArray[1] === "status") { /* "/jobId/valueName" -> ["jobId", "valueName"] */
nopaque.flash(`<i class="left material-icons">work</i>[<a href="/jobs/${pathArray[0]}">${nopaque.jobs[pathArray[0]].title}</a>] New status: ${operation.value}`); pathArray = operation.path.split("/").slice(1);
if (operation.op === "replace" && pathArray[1] === "status") {
if (nopaque.user.settings.jobStatusSiteNotifications === "end" && !["complete", "failed"].includes(operation.value)) {continue;}
nopaque.flash(`[<a href="/jobs/${pathArray[0]}">${nopaque.jobs[pathArray[0]].title}</a>] New status: ${operation.value}`, "job");
}
} }
} }
nopaque.jobs = jsonpatch.apply_patch(nopaque.jobs, patch);
for (let subscriber of nopaque.jobsSubscribers) {subscriber._update(patch);} for (let subscriber of nopaque.jobsSubscribers) {subscriber._update(patch);}
}); });
@ -188,23 +184,28 @@ nopaque.flash = function() {
message = arguments[0]; message = arguments[0];
break; break;
case 2: case 2:
category = arguments[0]; message = arguments[0];
message = arguments[1]; category = arguments[1];
break; break;
default: default:
console.error("Usage: nopaque.flash(message) or nopaque.flash(category, message)") console.error("Usage: nopaque.flash(message) or nopaque.flash(message, category)")
} }
switch (category) { switch (category) {
case "corpus":
message = `<i class="left material-icons">book</i>${message}`;
break;
case "error": case "error":
classes = "red"; message = `<i class="left material-icons red-text">error</i>${message}`;
break;
case "job":
message = `<i class="left material-icons">work</i>${message}`;
break; break;
default: default:
classes = ""; message = `<i class="left material-icons">notifications</i>${message}`;
} }
toast = M.toast({classes: classes, toast = M.toast({html: `<span>${message}</span>
html: `<span>${message}</span>
<button data-action="close" class="btn-flat toast-action white-text"> <button data-action="close" class="btn-flat toast-action white-text">
<i class="material-icons">close</i> <i class="material-icons">close</i>
</button>`}); </button>`});
@ -229,7 +230,8 @@ document.addEventListener("DOMContentLoaded", function() {
nopaque.Forms.init(); nopaque.Forms.init();
nopaque.Navigation.init(); nopaque.Navigation.init();
while (nopaque.flashedMessages.length) { while (nopaque.flashedMessages.length) {
nopaque.flash(...nopaque.flashedMessages.shift()); flashedMessage = nopaque.flashedMessages.shift();
nopaque.flash(flashedMessage[1], flashedMessage[0]);
} }
if (nopaque.user.isAuthenticated) { if (nopaque.user.isAuthenticated) {
if (nopaque.user.settings.darkMode) { if (nopaque.user.settings.darkMode) {

View File

@ -378,7 +378,7 @@ class ResultsList extends List {
if (expertModeSwitchElement.checked) { if (expertModeSwitchElement.checked) {
this.expertModeOn("query-display"); // page holds new result rows, so add new tooltips this.expertModeOn("query-display"); // page holds new result rows, so add new tooltips
} }
nopaque.flash("Updated matches per page.") nopaque.flash("Updated matches per page.", "corpus")
} catch (e) { } catch (e) {
// console.log(e); // console.log(e);
// console.log("resultsList has no results right now."); // console.log("resultsList has no results right now.");
@ -394,7 +394,7 @@ class ResultsList extends List {
let rc; let rc;
try { try {
if (event.type === "change") { if (event.type === "change") {
nopaque.flash("Updated context per match!"); nopaque.flash("Updated context per match!", "corpus");
} }
} catch (e) { } catch (e) {
} finally { } finally {

View File

@ -15,8 +15,9 @@
<li>Username: {{ user.username }}</li> <li>Username: {{ user.username }}</li>
<li>Email: {{ user.email }}</li> <li>Email: {{ user.email }}</li>
<li>ID: {{ user.id }}</li> <li>ID: {{ user.id }}</li>
<li>Registration date: {{ user.registration_date.strftime('%m/%d/%Y, %H:%M:%S %p') }}</li> <li>Member sinse: {{ user.member_since.strftime('%m/%d/%Y, %H:%M:%S %p') }}</li>
<li>Confirmed status: {{ user.confirmed }}</li> <li>Confirmed status: {{ user.confirmed }}</li>
<li>Last seen: {{ user.last_seen.strftime('%m/%d/%Y, %H:%M:%S %p') }}</li>
<li>Role ID: {{ user.role_id }}</li> <li>Role ID: {{ user.role_id }}</li>
<li>Permissions as Int: {{ user.role.permissions }}</li> <li>Permissions as Int: {{ user.role.permissions }}</li>
<li>Role name: {{ user.role.name }}</li> <li>Role name: {{ user.role.name }}</li>

View File

@ -175,7 +175,6 @@
for (let operation of patch) { for (let operation of patch) {
/* "/jobId/valueName" -> ["jobId", "valueName"] */ /* "/jobId/valueName" -> ["jobId", "valueName"] */
console.log(operation.value);
pathArray = operation.path.split("/").slice(1); pathArray = operation.path.split("/").slice(1);
if (pathArray[0] != this.jobId) {continue;} if (pathArray[0] != this.jobId) {continue;}
switch(operation.op) { switch(operation.op) {

View File

@ -29,12 +29,15 @@
{% endmacro %} {% endmacro %}
{% macro render_boolean_field(field) %} {% macro render_boolean_field(field) %}
{% set label = kwargs.pop('label', True) %}
<div class="switch"> <div class="switch">
{% if 'material_icon' in kwargs %} {% if 'material_icon' in kwargs %}
<i class="material-icons prefix">{{ kwargs.pop('material_icon') }}</i> <i class="material-icons prefix">{{ kwargs.pop('material_icon') }}</i>
{% endif %} {% endif %}
<label> <label>
{% if label %}
{{ field.label.text }} {{ field.label.text }}
{% endif %}
{{ field(*args, **kwargs) }} {{ field(*args, **kwargs) }}
<span class="lever"></span> <span class="lever"></span>
</label> </label>
@ -44,12 +47,6 @@
</div> </div>
{% endmacro %} {% endmacro %}
{% macro render_decimal_range_field(field) %}
<p class="range-field">
{{ field(*args, **kwargs) }}
</p>
{% endmacro %}
{% macro render_file_field(field) %} {% macro render_file_field(field) %}
{% set placeholder = kwargs.pop('placeholder', '') %} {% set placeholder = kwargs.pop('placeholder', '') %}
<div class="file-field input-field"> <div class="file-field input-field">
@ -64,12 +61,15 @@
{% endmacro %} {% endmacro %}
{% macro render_generic_field(field) %} {% macro render_generic_field(field) %}
{% set label = kwargs.pop('label', True) %}
<div class="input-field"> <div class="input-field">
{% if 'material_icon' in kwargs %} {% if 'material_icon' in kwargs %}
<i class="material-icons prefix">{{ kwargs.pop('material_icon') }}</i> <i class="material-icons prefix">{{ kwargs.pop('material_icon') }}</i>
{% endif %} {% endif %}
{{ field(*args, **kwargs) }} {{ field(*args, **kwargs) }}
{% if label %}
{{ field.label }} {{ field.label }}
{% endif %}
{% for error in field.errors %} {% for error in field.errors %}
<span class="helper-text red-text">{{ error }}</span> <span class="helper-text red-text">{{ error }}</span>
{% endfor %} {% endfor %}

View File

@ -49,9 +49,15 @@
<script src="{{ url_for('static', filename='js/nopaque.js') }}"></script> <script src="{{ url_for('static', filename='js/nopaque.js') }}"></script>
<script src="{{ url_for('static', filename='js/nopaque.lists.js') }}"></script> <script src="{{ url_for('static', filename='js/nopaque.lists.js') }}"></script>
<script> <script>
nopaque.user.isAuthenticated = {{ current_user.is_authenticated|tojson }}; {% if current_user.is_authenticated %}
nopaque.user.settings.darkMode = {{ (current_user.is_authenticated and current_user.setting_dark_mode)|tojson }}; nopaque.user.isAuthenticated = true;
nopaque.flashedMessages = {{ get_flashed_messages(with_categories=true)|tojson }}; nopaque.user.settings.darkMode = {{ current_user.setting_dark_mode|tojson }};
nopaque.user.settings.jobStatusMailNotifications = {{ current_user.setting_job_status_mail_notifications|tojson }};
nopaque.user.settings.jobStatusSiteNotifications = {{ current_user.setting_job_status_site_notifications|tojson }};
{% else %}
nopaque.user.isAuthenticated = false;
{% endif %}
nopaque.flashedMessages = {{ get_flashed_messages(with_categories=True)|tojson }};
</script> </script>
</head> </head>
<body> <body>

View File

@ -16,27 +16,27 @@
<p class="light">Activate dark mode to ease your eyes.</p> <p class="light">Activate dark mode to ease your eyes.</p>
</div> </div>
<div class="col s3 right-align"> <div class="col s3 right-align">
<div class="switch"> {{ M.render_field(edit_general_settings_form.dark_mode, label=False) }}
<label>
{{ edit_general_settings_form.dark_mode() }}
<span class="lever"></span>
</label>
</div>
</div> </div>
<div class="col s12"><p>&nbsp;</p></div> <div class="col s12"><p>&nbsp;</p></div>
<div class="col s12 divider"></div> <div class="col s12 divider"></div>
<div class="col s12"><p>&nbsp;</p></div> <div class="col s12"><p>&nbsp;</p></div>
<div class="col s9"> <div class="col s12 m8">
<p><i class="material-icons left">notifications</i>Email notifications</p> <p><i class="material-icons left">notifications</i>Job status site notifications</p>
<p class="light">Receive emails when a job completes.</p> <p class="light">Receive site notifications about job status changes.</p>
</div> </div>
<div class="col s3 right-align"> <div class="col s12 m4 right-align">
<div class="switch"> {{ M.render_field(edit_general_settings_form.job_status_site_notifications, label=False) }}
<label> </div>
<input disabled type="checkbox"> <div class="col s12"><p>&nbsp;</p></div>
<span class="lever"></span> <div class="col s12 divider"></div>
</label> <div class="col s12"><p>&nbsp;</p></div>
</div> <div class="col s12 m8">
<p><i class="material-icons left">notifications</i>Job status mail notifications</p>
<p class="light">Receive mail notifications about job status changes.</p>
</div>
<div class="col s12 m4 right-align">
{{ M.render_field(edit_general_settings_form.job_status_mail_notifications, label=False) }}
</div> </div>
<!-- <!--
Seperate each setting with the following two elements Seperate each setting with the following two elements

View File

@ -0,0 +1,30 @@
"""empty message
Revision ID: 099037c4aa06
Revises: 66253783654f
Create Date: 2020-04-27 09:17:15.039728
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '099037c4aa06'
down_revision = '66253783654f'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('users', sa.Column('last_seen', sa.DateTime(), nullable=True))
op.add_column('users', sa.Column('setting_site_job_status_notifications', sa.String(length=16), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('users', 'setting_site_job_status_notifications')
op.drop_column('users', 'last_seen')
# ### end Alembic commands ###

View File

@ -0,0 +1,36 @@
"""empty message
Revision ID: 49a42c69e523
Revises: 099037c4aa06
Create Date: 2020-04-27 11:18:32.999099
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '49a42c69e523'
down_revision = '099037c4aa06'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('users', sa.Column('member_since', sa.DateTime(), nullable=True))
op.add_column('users', sa.Column('setting_job_status_mail_notifications', sa.String(length=16), nullable=True))
op.add_column('users', sa.Column('setting_job_status_site_notifications', sa.String(length=16), nullable=True))
op.drop_column('users', 'setting_site_job_status_notifications')
op.drop_column('users', 'registration_date')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('users', sa.Column('registration_date', postgresql.TIMESTAMP(), autoincrement=False, nullable=True))
op.add_column('users', sa.Column('setting_site_job_status_notifications', sa.VARCHAR(length=16), autoincrement=False, nullable=True))
op.drop_column('users', 'setting_job_status_site_notifications')
op.drop_column('users', 'setting_job_status_mail_notifications')
op.drop_column('users', 'member_since')
# ### end Alembic commands ###