diff --git a/app/auth/views.py b/app/auth/views.py index 9470480f..6b87f744 100644 --- a/app/auth/views.py +++ b/app/auth/views.py @@ -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']) diff --git a/app/models.py b/app/models.py index e5c29953..7866ef3c 100644 --- a/app/models.py +++ b/app/models.py @@ -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. diff --git a/app/profile/forms.py b/app/profile/forms.py index 60257805..14517e29 100644 --- a/app/profile/forms.py +++ b/app/profile/forms.py @@ -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') diff --git a/app/profile/views.py b/app/profile/views.py index 89d127df..f156715f 100644 --- a/app/profile/views.py +++ b/app/profile/views.py @@ -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, diff --git a/app/static/js/nopaque.js b/app/static/js/nopaque.js index 2cba2c1b..d2c6ddbf 100644 --- a/app/static/js/nopaque.js +++ b/app/static/js/nopaque.js @@ -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(`book[${nopaque.corpora[pathArray[0]].title}] 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(`work[${nopaque.jobs[pathArray[0]].title}] 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(`work[${nopaque.jobs[pathArray[0]].title}] New status: ${operation.value}`); + } } } - nopaque.jobs = jsonpatch.apply_patch(nopaque.jobs, patch); for (let subscriber of nopaque.jobsSubscribers) {subscriber._update(patch);} }); diff --git a/app/templates/admin/user.html.j2 b/app/templates/admin/user.html.j2 index 15de85da..f6825976 100644 --- a/app/templates/admin/user.html.j2 +++ b/app/templates/admin/user.html.j2 @@ -15,8 +15,9 @@
- {{ field(*args, **kwargs) }} -
-{% endmacro %} - {% macro render_file_field(field) %} {% set placeholder = kwargs.pop('placeholder', '') %}
notificationsEmail notifications
-Receive emails when a job completes.
+notificationsJob status site notifications
+Receive site notifications about job status changes.
notificationsJob status mail notifications
+Receive mail notifications about job status changes.
+