diff --git a/app/admin/routes.py b/app/admin/routes.py index 79f05e19..f1178398 100644 --- a/app/admin/routes.py +++ b/app/admin/routes.py @@ -1,6 +1,6 @@ from app import db, hashids from app.decorators import admin_required -from app.models import JobStatusMailNotificationLevel, Role, User +from app.models import Role, User, UserSettingJobStatusMailNotificationLevel from app.settings import tasks as settings_tasks from app.settings.forms import ( EditGeneralSettingsForm, @@ -102,7 +102,7 @@ def edit_user(user_id): and edit_notification_settings_form.validate() ): user.setting_job_status_mail_notification_level = \ - JobStatusMailNotificationLevel[ + UserSettingJobStatusMailNotificationLevel[ edit_notification_settings_form.job_status_mail_notification_level.data # noqa ] db.session.commit() diff --git a/app/events/sqlalchemy.py b/app/events/sqlalchemy.py index dd88a0fe..31ffcb20 100644 --- a/app/events/sqlalchemy.py +++ b/app/events/sqlalchemy.py @@ -7,9 +7,10 @@ from app.models import ( JobInput, JobResult, JobStatus, - JobStatusMailNotificationLevel + UserSettingJobStatusMailNotificationLevel ) from datetime import datetime +from enum import Enum ############################################################################### @@ -33,8 +34,8 @@ def ressource_after_delete(mapper, connection, ressource): @db.event.listens_for(JobResult, 'after_insert') def ressource_after_insert_handler(mapper, connection, ressource): value = ressource.to_dict(backrefs=False, relationships=False) - for relationship in mapper.relationships: - value[relationship.key] = {} + for attr in mapper.relationships: + value[attr.key] = {} jsonpatch = [ {'op': 'add', 'path': ressource.jsonpatch_path, 'value': value} ] @@ -50,49 +51,40 @@ def ressource_after_insert_handler(mapper, connection, ressource): def ressource_after_update_handler(mapper, connection, ressource): jsonpatch = [] for attr in db.inspect(ressource).attrs: - # Don't handle changes in relationship fields if attr.key in mapper.relationships: continue - # Check if their are changes for the current field - history = attr.load_history() - if not history.has_changes(): + if not attr.load_history().has_changes(): continue - if isinstance(history.added[0], datetime): - # In order to be JSON serializable, DateTime attributes must be - # converted to a string - attr_name = attr.key - value = history.added[0].isoformat() + 'Z' - elif attr.key.endswith('_enum_value'): - # Handling fake enum attributes - attr_name = attr.key[:-11] - value = getattr(ressource, attr_name).name + if isinstance(attr.value, datetime): + value = attr.value.isoformat() + 'Z' + elif isinstance(attr.value, Enum): + value = attr.value.name else: - attr_name = attr.key - value = history.added[0] + value = attr.value jsonpatch.append( { 'op': 'replace', - 'path': f'{ressource.jsonpatch_path}/{attr_name}', + 'path': f'{ressource.jsonpatch_path}/{attr.key}', 'value': value } ) - # Job status update notification if it changed and wanted by the user - if isinstance(ressource, Job) and attr_name == 'status': - if ressource.user.setting_job_status_mail_notification_level == JobStatusMailNotificationLevel.NONE: # noqa - pass - elif ( - ressource.user.setting_job_status_mail_notification_level == JobStatusMailNotificationLevel.END # noqa - and ressource.status not in [JobStatus.COMPLETED, JobStatus.FAILED] # noqa - ): - pass - else: - msg = create_message( - ressource.user.email, - f'Status update for your Job "{ressource.title}"', - 'tasks/email/notification', - job=ressource - ) - mail.send(msg) + if isinstance(ressource, Job) and attr.key == 'status': + _job_status_email_handler(ressource) if jsonpatch: room = f'users.{ressource.user_hashid}' socketio.emit('users.patch', jsonpatch, room=room) + + +def _job_status_email_handler(job): + if job.user.setting_job_status_mail_notification_level == UserSettingJobStatusMailNotificationLevel.NONE: # noqa + return + if job.user.setting_job_status_mail_notification_level == UserSettingJobStatusMailNotificationLevel.END: # noqa + if job.status not in [JobStatus.COMPLETED, JobStatus.FAILED]: + return + msg = create_message( + job.user.email, + f'Status update for your Job "{job.title}"', + 'tasks/email/notification', + job=job + ) + mail.send(msg) diff --git a/app/models.py b/app/models.py index 034edc91..55423d96 100644 --- a/app/models.py +++ b/app/models.py @@ -17,33 +17,23 @@ import xml.etree.ElementTree as ET import yaml -class CorpusStatus(IntEnum): - UNPREPARED = 1 - SUBMITTED = 2 - QUEUED = 3 - BUILDING = 4 - BUILT = 5 - FAILED = 6 - STARTING_ANALYSIS_SESSION = 7 - RUNNING_ANALYSIS_SESSION = 8 - CANCELING_ANALYSIS_SESSION = 9 +class IntEnumProxy(db.TypeDecorator): + impl = db.Integer + def __init__(self, enumtype, *args, **kwargs): + super().__init__(*args, **kwargs) + self._enumtype = enumtype -class JobStatus(IntEnum): - INITIALIZING = 1 - SUBMITTED = 2 - QUEUED = 3 - RUNNING = 4 - CANCELING = 5 - CANCELED = 6 - COMPLETED = 7 - FAILED = 8 + def process_bind_param(self, value, dialect): + if isinstance(value, self._enumtype): + return value.value + elif isinstance(value, int): + return value + else: + return TypeError() - -class JobStatusMailNotificationLevel(IntEnum): - NONE = 1 - END = 2 - ALL = 3 + def process_result_value(self, value, dialect): + return self._enumtype(value) class Permission(IntEnum): @@ -143,6 +133,12 @@ class Role(HashidMixin, db.Model): db.session.commit() +class UserSettingJobStatusMailNotificationLevel(IntEnum): + NONE = 1 + END = 2 + ALL = 3 + + class User(HashidMixin, UserMixin, db.Model): __tablename__ = 'users' # Primary key @@ -159,10 +155,9 @@ class User(HashidMixin, UserMixin, db.Model): token_expiration = db.Column(db.DateTime) username = db.Column(db.String(64), unique=True, index=True) setting_dark_mode = db.Column(db.Boolean, default=False) - setting_job_status_mail_notification_level_enum_value = db.Column( - 'setting_job_status_mail_notification_level', - db.Integer, - default=2 + setting_job_status_mail_notification_level = db.Column( + IntEnumProxy(UserSettingJobStatusMailNotificationLevel), + default=UserSettingJobStatusMailNotificationLevel.END ) # Backrefs: role: Role # Relationships @@ -214,19 +209,6 @@ class User(HashidMixin, UserMixin, db.Model): return os.path.join( current_app.config.get('NOPAQUE_DATA_DIR'), 'users', str(self.id)) - @property - def setting_job_status_mail_notification_level(self): - return JobStatusMailNotificationLevel( - self.setting_job_status_mail_notification_level_enum_value - ) - - @setting_job_status_mail_notification_level.setter - def setting_job_status_mail_notification_level(self, enum_member): - if not isinstance(enum_member, JobStatusMailNotificationLevel): - return TypeError() - self.setting_job_status_mail_notification_level_enum_value = \ - enum_member.value - def can(self, permission): return self.role.has_permission(permission) @@ -553,6 +535,17 @@ class JobResult(FileMixin, HashidMixin, db.Model): return self.job.user_id +class JobStatus(IntEnum): + INITIALIZING = 1 + SUBMITTED = 2 + QUEUED = 3 + RUNNING = 4 + CANCELING = 5 + CANCELED = 6 + COMPLETED = 7 + FAILED = 8 + + class Job(HashidMixin, db.Model): ''' Class to define Jobs. @@ -573,7 +566,10 @@ class Job(HashidMixin, db.Model): ''' service_args = db.Column(db.String(255)) service_version = db.Column(db.String(16)) - status_enum_value = db.Column('status', db.Integer, default=1) + status = db.Column( + IntEnumProxy(JobStatus), + default=JobStatus.INITIALIZING + ) title = db.Column(db.String(32)) # Backrefs: user: User # Relationships @@ -601,16 +597,6 @@ class Job(HashidMixin, db.Model): def path(self): return os.path.join(self.user.path, 'jobs', str(self.id)) - @property - def status(self): - return JobStatus(self.status_enum_value) - - @status.setter - def status(self, enum_member): - if not isinstance(enum_member, JobStatus): - return TypeError() - self.status_enum_value = enum_member.value - @property def url(self): return url_for('jobs.job', job_id=self.id) @@ -780,6 +766,18 @@ class CorpusFile(FileMixin, HashidMixin, db.Model): return dict_corpus_file +class CorpusStatus(IntEnum): + UNPREPARED = 1 + SUBMITTED = 2 + QUEUED = 3 + BUILDING = 4 + BUILT = 5 + FAILED = 6 + STARTING_ANALYSIS_SESSION = 7 + RUNNING_ANALYSIS_SESSION = 8 + CANCELING_ANALYSIS_SESSION = 9 + + class Corpus(HashidMixin, db.Model): ''' Class to define a corpus. @@ -793,7 +791,10 @@ class Corpus(HashidMixin, db.Model): creation_date = db.Column(db.DateTime(), default=datetime.utcnow) description = db.Column(db.String(255)) last_edited_date = db.Column(db.DateTime(), default=datetime.utcnow) - status_enum_value = db.Column('status', db.Integer, default=1) + status = db.Column( + IntEnumProxy(CorpusStatus), + default=CorpusStatus.UNPREPARED + ) title = db.Column(db.String(32)) num_analysis_sessions = db.Column(db.Integer, default=0) num_tokens = db.Column(db.Integer, default=0) @@ -824,16 +825,6 @@ class Corpus(HashidMixin, db.Model): def path(self): return os.path.join(self.user.path, 'corpora', str(self.id)) - @property - def status(self): - return CorpusStatus(self.status_enum_value) - - @status.setter - def status(self, enum_member): - if not isinstance(enum_member, CorpusStatus): - return TypeError() - self.status_enum_value = enum_member.value - @property def url(self): return url_for('corpora.corpus', corpus_id=self.id) diff --git a/app/settings/forms.py b/app/settings/forms.py index 03d7f243..f17c5f89 100644 --- a/app/settings/forms.py +++ b/app/settings/forms.py @@ -1,5 +1,5 @@ from app.auth import USERNAME_REGEX -from app.models import JobStatusMailNotificationLevel, User +from app.models import User, UserSettingJobStatusMailNotificationLevel from flask_wtf import FlaskForm from wtforms import ( BooleanField, @@ -96,5 +96,5 @@ class EditNotificationSettingsForm(FlaskForm): super().__init__(*args, **kwargs) self.job_status_mail_notification_level.choices += [ (enum_member.name, enum_member.name.capitalize()) - for enum_member in JobStatusMailNotificationLevel + for enum_member in UserSettingJobStatusMailNotificationLevel ] diff --git a/app/settings/routes.py b/app/settings/routes.py index c59a55f2..8d828e33 100644 --- a/app/settings/routes.py +++ b/app/settings/routes.py @@ -8,7 +8,7 @@ from .forms import ( EditNotificationSettingsForm ) from .. import db -from ..models import JobStatusMailNotificationLevel +from ..models import UserSettingJobStatusMailNotificationLevel @bp.route('', methods=['GET', 'POST']) @@ -57,7 +57,7 @@ def index(): and edit_notification_settings_form.validate() ): current_user.setting_job_status_mail_notification_level = \ - JobStatusMailNotificationLevel[ + UserSettingJobStatusMailNotificationLevel[ edit_notification_settings_form.job_status_mail_notification_level.data # noqa ] db.session.commit()