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,9 +17,12 @@ 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 \
and request.endpoint \
and request.blueprint != 'auth' \
and request.endpoint != 'static':
return redirect(url_for('auth.unconfirmed')) return redirect(url_for('auth.unconfirmed'))

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

@ -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);
nopaque.jobs = jsonpatch.apply_patch(nopaque.jobs, patch);
if (["all", "end"].includes(nopaque.user.settings.jobStatusSiteNotifications)) {
for (operation of patch) { for (operation of patch) {
/* "/jobId/valueName" -> ["jobId", "valueName"] */ /* "/jobId/valueName" -> ["jobId", "valueName"] */
pathArray = operation.path.split("/").slice(1); pathArray = operation.path.split("/").slice(1);
if (operation.op === "replace" && pathArray[1] === "status") { 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.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);} for (let subscriber of nopaque.jobsSubscribers) {subscriber._update(patch);}
}); });

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

@ -27,16 +27,21 @@
<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 s9">
<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 s3 right-align">
<div class="switch"> {{ M.render_field(edit_general_settings_form.job_status_site_notifications, label=False) }}
<label>
<input disabled type="checkbox">
<span class="lever"></span>
</label>
</div> </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> </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 ###