A lot of database changes and add options to change notification level

This commit is contained in:
Patrick Jentsch 2020-04-27 13:50:54 +02:00
parent 76e7d65017
commit 565274fce1
12 changed files with 149 additions and 42 deletions

View File

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

View File

@ -106,13 +106,18 @@ class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
# Fields
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)
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'))
username = db.Column(db.String(64), unique=True, index=True)
# Setting Fields
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
corpora = db.relationship('Corpus', backref='creator', lazy='dynamic',
cascade='save-update, merge, delete')
@ -205,6 +210,10 @@ class User(UserMixin, db.Model):
"""
return self.can(Permission.ADMIN)
def ping(self):
self.last_seen = datetime.utcnow()
db.session.add(self)
def delete(self):
"""
Delete the user and its corpora and jobs from database and filesystem.

View File

@ -1,6 +1,6 @@
from flask_wtf import FlaskForm
from wtforms import (BooleanField, PasswordField, StringField, SubmitField,
ValidationError)
from wtforms import (BooleanField, PasswordField, SelectField, StringField,
SubmitField, ValidationError)
from wtforms.validators import DataRequired, Email, EqualTo
@ -11,6 +11,20 @@ class EditEmailForm(FlaskForm):
class EditGeneralSettingsForm(FlaskForm):
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')

View File

@ -11,8 +11,7 @@ from .. import db
def settings():
edit_email_form = EditEmailForm(prefix='edit-email-form')
edit_general_settings_form = EditGeneralSettingsForm(
prefix='edit-general-settings-form'
)
prefix='edit-general-settings-form')
edit_password_form = EditPasswordForm(prefix='edit-password-form',
user=current_user)
# Check if edit_email_form is submitted and valid
@ -25,7 +24,12 @@ def settings():
# Check if edit_settings_form is submitted and valid
if (edit_general_settings_form.save_settings.data
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.commit()
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
edit_email_form.email.data = current_user.email
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(
'profile/settings.html.j2',
edit_email_form=edit_email_form,

View File

@ -43,13 +43,6 @@ nopaque.socket.init = function() {
var patch;
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);
for (let subscriber of nopaque.corporaSubscribers) {subscriber._update(patch);}
});
@ -58,14 +51,17 @@ nopaque.socket.init = function() {
var patch;
patch = JSON.parse(msg);
for (operation of patch) {
/* "/jobId/valueName" -> ["jobId", "valueName"] */
pathArray = operation.path.split("/").slice(1);
if (operation.op === "replace" && pathArray[1] === "status") {
nopaque.flash(`<i class="left material-icons">work</i>[<a href="/jobs/${pathArray[0]}">${nopaque.jobs[pathArray[0]].title}</a>] New status: ${operation.value}`);
nopaque.jobs = jsonpatch.apply_patch(nopaque.jobs, patch);
if (["all", "end"].includes(nopaque.user.settings.jobStatusSiteNotifications)) {
for (operation of patch) {
/* "/jobId/valueName" -> ["jobId", "valueName"] */
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(`<i class="left material-icons">work</i>[<a href="/jobs/${pathArray[0]}">${nopaque.jobs[pathArray[0]].title}</a>] New status: ${operation.value}`);
}
}
}
nopaque.jobs = jsonpatch.apply_patch(nopaque.jobs, patch);
for (let subscriber of nopaque.jobsSubscribers) {subscriber._update(patch);}
});

View File

@ -15,8 +15,9 @@
<li>Username: {{ user.username }}</li>
<li>Email: {{ user.email }}</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>Last seen: {{ user.last_seen.strftime('%m/%d/%Y, %H:%M:%S %p') }}</li>
<li>Role ID: {{ user.role_id }}</li>
<li>Permissions as Int: {{ user.role.permissions }}</li>
<li>Role name: {{ user.role.name }}</li>

View File

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

View File

@ -29,12 +29,15 @@
{% endmacro %}
{% macro render_boolean_field(field) %}
{% set label = kwargs.pop('label', True) %}
<div class="switch">
{% if 'material_icon' in kwargs %}
<i class="material-icons prefix">{{ kwargs.pop('material_icon') }}</i>
{% endif %}
<label>
{% if label %}
{{ field.label.text }}
{% endif %}
{{ field(*args, **kwargs) }}
<span class="lever"></span>
</label>
@ -44,12 +47,6 @@
</div>
{% endmacro %}
{% macro render_decimal_range_field(field) %}
<p class="range-field">
{{ field(*args, **kwargs) }}
</p>
{% endmacro %}
{% macro render_file_field(field) %}
{% set placeholder = kwargs.pop('placeholder', '') %}
<div class="file-field input-field">
@ -64,12 +61,15 @@
{% endmacro %}
{% macro render_generic_field(field) %}
{% set label = kwargs.pop('label', True) %}
<div class="input-field">
{% if 'material_icon' in kwargs %}
<i class="material-icons prefix">{{ kwargs.pop('material_icon') }}</i>
{% endif %}
{{ field(*args, **kwargs) }}
{% if label %}
{{ field.label }}
{% endif %}
{% for error in field.errors %}
<span class="helper-text red-text">{{ error }}</span>
{% 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.lists.js') }}"></script>
<script>
nopaque.user.isAuthenticated = {{ current_user.is_authenticated|tojson }};
nopaque.user.settings.darkMode = {{ (current_user.is_authenticated and current_user.setting_dark_mode)|tojson }};
nopaque.flashedMessages = {{ get_flashed_messages(with_categories=true)|tojson }};
{% if current_user.is_authenticated %}
nopaque.user.isAuthenticated = true;
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>
</head>
<body>

View File

@ -27,16 +27,21 @@
<div class="col s12 divider"></div>
<div class="col s12"><p>&nbsp;</p></div>
<div class="col s9">
<p><i class="material-icons left">notifications</i>Email notifications</p>
<p class="light">Receive emails when a job completes.</p>
<p><i class="material-icons left">notifications</i>Job status site notifications</p>
<p class="light">Receive site notifications about job status changes.</p>
</div>
<div class="col s3 right-align">
<div class="switch">
<label>
<input disabled type="checkbox">
<span class="lever"></span>
</label>
</div>
{{ M.render_field(edit_general_settings_form.job_status_site_notifications, label=False) }}
</div>
<div class="col s12"><p>&nbsp;</p></div>
<div class="col s12 divider"></div>
<div class="col s12"><p>&nbsp;</p></div>
<div class="col s9">
<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 s3 right-align">
{{ M.render_field(edit_general_settings_form.job_status_mail_notifications, label=False) }}
</div>
<!--
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 ###