mirror of
				https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
				synced 2025-11-03 20:02:47 +00:00 
			
		
		
		
	First working mail notification system
This commit is contained in:
		
							
								
								
									
										171
									
								
								app/models.py
									
									
									
									
									
								
							
							
						
						
									
										171
									
								
								app/models.py
									
									
									
									
									
								
							@@ -8,11 +8,11 @@ from . import db, login_manager
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Permission:
 | 
			
		||||
    """
 | 
			
		||||
    '''
 | 
			
		||||
    Defines User permissions as integers by the power of 2. User permission
 | 
			
		||||
    can be evaluated using the bitwise operator &. 3 equals to CREATE_JOB and
 | 
			
		||||
    DELETE_JOB and so on.
 | 
			
		||||
    """
 | 
			
		||||
    '''
 | 
			
		||||
    MANAGE_CORPORA = 1
 | 
			
		||||
    MANAGE_JOBS = 2
 | 
			
		||||
    # PERMISSION_NAME = 4
 | 
			
		||||
@@ -21,10 +21,10 @@ class Permission:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Role(db.Model):
 | 
			
		||||
    """
 | 
			
		||||
    '''
 | 
			
		||||
    Model for the different roles Users can have. Is a one-to-many
 | 
			
		||||
    relationship. A Role can be associated with many User rows.
 | 
			
		||||
    """
 | 
			
		||||
    '''
 | 
			
		||||
    __tablename__ = 'roles'
 | 
			
		||||
    # Primary key
 | 
			
		||||
    id = db.Column(db.Integer, primary_key=True)
 | 
			
		||||
@@ -47,46 +47,46 @@ class Role(db.Model):
 | 
			
		||||
            self.permissions = 0
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        String representation of the Role. For human readability.
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        return '<Role %r>' % self.name
 | 
			
		||||
 | 
			
		||||
    def add_permission(self, perm):
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        Add new permission to Role. Input is a Permission.
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        if not self.has_permission(perm):
 | 
			
		||||
            self.permissions += perm
 | 
			
		||||
 | 
			
		||||
    def remove_permission(self, perm):
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        Removes permission from a Role. Input a Permission.
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        if self.has_permission(perm):
 | 
			
		||||
            self.permissions -= perm
 | 
			
		||||
 | 
			
		||||
    def reset_permissions(self):
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        Resets permissions to zero. Zero equals no permissions at all.
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        self.permissions = 0
 | 
			
		||||
 | 
			
		||||
    def has_permission(self, perm):
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        Checks if a Role has a specific Permission. Does this with the bitwise
 | 
			
		||||
        operator.
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        return self.permissions & perm == perm
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def insert_roles():
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        Inserts roles into the database. This has to be executed befor Users
 | 
			
		||||
        are added to the database. Otherwiese Users will not have a Role
 | 
			
		||||
        assigned to them. Order of the roles dictionary determines the ID of
 | 
			
		||||
        each role. Users have the ID 1 and Administrators have the ID 2.
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        roles = {'User': [Permission.MANAGE_CORPORA, Permission.MANAGE_JOBS],
 | 
			
		||||
                 'Administrator': [Permission.MANAGE_CORPORA,
 | 
			
		||||
                                   Permission.MANAGE_JOBS, Permission.ADMIN]}
 | 
			
		||||
@@ -104,9 +104,9 @@ class Role(db.Model):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class User(UserMixin, db.Model):
 | 
			
		||||
    """
 | 
			
		||||
    '''
 | 
			
		||||
    Model for Users that are registered to Opaque.
 | 
			
		||||
    """
 | 
			
		||||
    '''
 | 
			
		||||
    __tablename__ = 'users'
 | 
			
		||||
    # Primary key
 | 
			
		||||
    id = db.Column(db.Integer, primary_key=True)
 | 
			
		||||
@@ -148,9 +148,9 @@ class User(UserMixin, db.Model):
 | 
			
		||||
                'jobs': {job.id: job.to_dict() for job in self.jobs}}
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        String representation of the User. For human readability.
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        return '<User %r>' % self.username
 | 
			
		||||
 | 
			
		||||
    def __init__(self, **kwargs):
 | 
			
		||||
@@ -162,25 +162,25 @@ class User(UserMixin, db.Model):
 | 
			
		||||
                self.role = Role.query.filter_by(default=True).first()
 | 
			
		||||
 | 
			
		||||
    def generate_confirmation_token(self, expiration=3600):
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        Generates a confirmation token for user confirmation via email.
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        s = TimedJSONWebSignatureSerializer(current_app.config['SECRET_KEY'],
 | 
			
		||||
                                            expiration)
 | 
			
		||||
        return s.dumps({'confirm': self.id}).decode('utf-8')
 | 
			
		||||
 | 
			
		||||
    def generate_reset_token(self, expiration=3600):
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        Generates a reset token for password reset via email.
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        s = TimedJSONWebSignatureSerializer(current_app.config['SECRET_KEY'],
 | 
			
		||||
                                            expiration)
 | 
			
		||||
        return s.dumps({'reset': self.id}).decode('utf-8')
 | 
			
		||||
 | 
			
		||||
    def confirm(self, token):
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        Confirms User if the given token is valid and not expired.
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        s = TimedJSONWebSignatureSerializer(current_app.config['SECRET_KEY'])
 | 
			
		||||
        try:
 | 
			
		||||
            data = s.loads(token.encode('utf-8'))
 | 
			
		||||
@@ -194,9 +194,9 @@ class User(UserMixin, db.Model):
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def reset_password(token, new_password):
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        Resets password for User if the given token is valid and not expired.
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        s = TimedJSONWebSignatureSerializer(current_app.config['SECRET_KEY'])
 | 
			
		||||
        try:
 | 
			
		||||
            data = s.loads(token.encode('utf-8'))
 | 
			
		||||
@@ -221,16 +221,16 @@ class User(UserMixin, db.Model):
 | 
			
		||||
        return check_password_hash(self.password_hash, password)
 | 
			
		||||
 | 
			
		||||
    def can(self, perm):
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        Checks if a User with its current role can doe something. Checks if the
 | 
			
		||||
        associated role actually has the needed Permission.
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        return self.role is not None and self.role.has_permission(perm)
 | 
			
		||||
 | 
			
		||||
    def is_administrator(self):
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        Checks if User has Admin permissions.
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        return self.can(Permission.ADMIN)
 | 
			
		||||
 | 
			
		||||
    def ping(self):
 | 
			
		||||
@@ -238,9 +238,9 @@ class User(UserMixin, db.Model):
 | 
			
		||||
        db.session.add(self)
 | 
			
		||||
 | 
			
		||||
    def delete(self):
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        Delete the user and its corpora and jobs from database and filesystem.
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        for job in self.jobs:
 | 
			
		||||
            job.delete()
 | 
			
		||||
        for corpus in self.corpora:
 | 
			
		||||
@@ -250,9 +250,9 @@ class User(UserMixin, db.Model):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AnonymousUser(AnonymousUserMixin):
 | 
			
		||||
    """
 | 
			
		||||
    '''
 | 
			
		||||
    Model replaces the default AnonymousUser.
 | 
			
		||||
    """
 | 
			
		||||
    '''
 | 
			
		||||
 | 
			
		||||
    def can(self, permissions):
 | 
			
		||||
        return False
 | 
			
		||||
@@ -262,9 +262,9 @@ class AnonymousUser(AnonymousUserMixin):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class JobInput(db.Model):
 | 
			
		||||
    """
 | 
			
		||||
    '''
 | 
			
		||||
    Class to define JobInputs.
 | 
			
		||||
    """
 | 
			
		||||
    '''
 | 
			
		||||
    __tablename__ = 'job_inputs'
 | 
			
		||||
    # Primary key
 | 
			
		||||
    id = db.Column(db.Integer, primary_key=True)
 | 
			
		||||
@@ -275,9 +275,9 @@ class JobInput(db.Model):
 | 
			
		||||
    filename = db.Column(db.String(255))
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        String representation of the JobInput. For human readability.
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        return '<JobInput %r>' % self.filename
 | 
			
		||||
 | 
			
		||||
    def to_dict(self):
 | 
			
		||||
@@ -287,9 +287,9 @@ class JobInput(db.Model):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class JobResult(db.Model):
 | 
			
		||||
    """
 | 
			
		||||
    '''
 | 
			
		||||
    Class to define JobResults.
 | 
			
		||||
    """
 | 
			
		||||
    '''
 | 
			
		||||
    __tablename__ = 'job_results'
 | 
			
		||||
    # Primary key
 | 
			
		||||
    id = db.Column(db.Integer, primary_key=True)
 | 
			
		||||
@@ -300,9 +300,9 @@ class JobResult(db.Model):
 | 
			
		||||
    filename = db.Column(db.String(255))
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        String representation of the JobResult. For human readability.
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        return '<JobResult %r>' % self.filename
 | 
			
		||||
 | 
			
		||||
    def to_dict(self):
 | 
			
		||||
@@ -312,9 +312,9 @@ class JobResult(db.Model):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Job(db.Model):
 | 
			
		||||
    """
 | 
			
		||||
    '''
 | 
			
		||||
    Class to define Jobs.
 | 
			
		||||
    """
 | 
			
		||||
    '''
 | 
			
		||||
    __tablename__ = 'jobs'
 | 
			
		||||
    # Primary key
 | 
			
		||||
    id = db.Column(db.Integer, primary_key=True)
 | 
			
		||||
@@ -341,31 +341,34 @@ class Job(db.Model):
 | 
			
		||||
                             cascade='save-update, merge, delete')
 | 
			
		||||
    results = db.relationship('JobResult', backref='job', lazy='dynamic',
 | 
			
		||||
                              cascade='save-update, merge, delete')
 | 
			
		||||
    notifications_data = db.relationship('NotificationData',
 | 
			
		||||
                                         backref='job',
 | 
			
		||||
                                         lazy='dynamic',
 | 
			
		||||
                                         cascade='save-update, merge, delete')
 | 
			
		||||
    notification_data = db.relationship('NotificationData',
 | 
			
		||||
                                        cascade='save-update, merge, delete',
 | 
			
		||||
                                        uselist=False,
 | 
			
		||||
                                        back_populates='job')  # One-to-One relationship
 | 
			
		||||
    notification_email_data = db.relationship('NotificationEmailData',
 | 
			
		||||
                                              cascade='save-update, merge, delete',
 | 
			
		||||
                                              back_populates='job')
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        String representation of the Job. For human readability.
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        return '<Job %r>' % self.title
 | 
			
		||||
 | 
			
		||||
    def create_secure_filename(self):
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        Takes the job.title string nad cratesa a secure filename from this.
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        self.secure_filename = secure_filename(self.title)
 | 
			
		||||
 | 
			
		||||
    def delete(self):
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        Delete the job and its inputs and results from the database.
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        for input in self.inputs:
 | 
			
		||||
            db.session.delete(input)
 | 
			
		||||
        for result in self.results:
 | 
			
		||||
            db.session.delete(result)
 | 
			
		||||
            db.session.delete(result) # TODO: shouldn't this happen through the cascade option?
 | 
			
		||||
        db.session.delete(self)
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
 | 
			
		||||
@@ -389,27 +392,53 @@ class Job(db.Model):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NotificationData(db.Model):
 | 
			
		||||
    """
 | 
			
		||||
    '''
 | 
			
		||||
    Class to define notification data used for sending a notification mail with
 | 
			
		||||
    nopaque_notify.
 | 
			
		||||
    """
 | 
			
		||||
    __tablename__ = 'notifications_data'
 | 
			
		||||
    '''
 | 
			
		||||
    __tablename__ = 'notification_data'
 | 
			
		||||
    # Primary key
 | 
			
		||||
    id = db.Column(db.Integer, primary_key=True)
 | 
			
		||||
    # Foreign Key
 | 
			
		||||
    job_id = db.Column(db.Integer, db.ForeignKey('jobs.id'))
 | 
			
		||||
    # relationships
 | 
			
		||||
    job = db.relationship('Job', back_populates='notification_data')
 | 
			
		||||
    # Fields
 | 
			
		||||
    notified_on_submitted = db.Column(db.Boolean, default=False)
 | 
			
		||||
    notified_on_queued = db.Column(db.Boolean, default=False)
 | 
			
		||||
    notified_on_running = db.Column(db.Boolean, default=False)
 | 
			
		||||
    notified_on_complete = db.Column(db.Boolean, default=False)
 | 
			
		||||
    notified_on_canceling = db.Column(db.Boolean, default=False)
 | 
			
		||||
    notified_on = db.Column(db.String(16), default=None)
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        '''
 | 
			
		||||
        String representation of the NotificationData. For human readability.
 | 
			
		||||
        '''
 | 
			
		||||
        return '<NotificationData %r>' % self.id  # TODO: Why not .format()?
 | 
			
		||||
 | 
			
		||||
    def to_dict(self):
 | 
			
		||||
        return {'id': self.id,
 | 
			
		||||
                'job_id': self.job_id,
 | 
			
		||||
                'job': self.job,
 | 
			
		||||
                'notified': self.notified}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NotificationEmailData(db.Model):
 | 
			
		||||
    '''
 | 
			
		||||
    Class to define data that will be used to send a corresponding Notification
 | 
			
		||||
    via email.
 | 
			
		||||
    '''
 | 
			
		||||
    __tablename__ = 'notification_email_data'
 | 
			
		||||
    # Primary Key
 | 
			
		||||
    id = db.Column(db.Integer, primary_key=True)
 | 
			
		||||
    # Foreign Key
 | 
			
		||||
    job_id = db.Column(db.Integer, db.ForeignKey('jobs.id'))
 | 
			
		||||
    # relationships
 | 
			
		||||
    job = db.relationship('Job', back_populates='notification_email_data')
 | 
			
		||||
    notify_status = db.Column(db.String(16), default=None)
 | 
			
		||||
    creation_date = db.Column(db.DateTime(), default=datetime.utcnow)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CorpusFile(db.Model):
 | 
			
		||||
    """
 | 
			
		||||
    '''
 | 
			
		||||
    Class to define Files.
 | 
			
		||||
    """
 | 
			
		||||
    '''
 | 
			
		||||
    __tablename__ = 'corpus_files'
 | 
			
		||||
    # Primary key
 | 
			
		||||
    id = db.Column(db.Integer, primary_key=True)
 | 
			
		||||
@@ -455,9 +484,9 @@ class CorpusFile(db.Model):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Corpus(db.Model):
 | 
			
		||||
    """
 | 
			
		||||
    '''
 | 
			
		||||
    Class to define a corpus.
 | 
			
		||||
    """
 | 
			
		||||
    '''
 | 
			
		||||
    __tablename__ = 'corpora'
 | 
			
		||||
    # Primary key
 | 
			
		||||
    id = db.Column(db.Integer, primary_key=True)
 | 
			
		||||
@@ -492,9 +521,9 @@ class Corpus(db.Model):
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        String representation of the corpus. For human readability.
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        return '<Corpus %r>' % self.title
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user