class UserRoleEntity(db.Model, CRUDMixin): """ Stores the user-role mapping """ __tablename__ = 'UserRole' id = db.Column("urID", db.Integer, primary_key=True) user_id = db.Column("usrID", db.Integer, db.ForeignKey('User.usrID', ondelete='CASCADE'), nullable=False) role_id = db.Column("rolID", db.Integer, db.ForeignKey('Role.rolID', ondelete='CASCADE'), nullable=False) added_at = db.Column('urAddedAt', db.DateTime(), nullable=False, server_default='0000-00-00 00:00:00') role = db.relationship('RoleEntity', uselist=False) user = db.relationship('UserEntity', uselist=False) def get_id(self): """ return the unicode of the primary key value """ return unicode(self.id) def __repr__(self): return "<UserRoleEntity (\n\t" \ "urID: {0.id}, \n\t" \ " {0.user!r}, \n\t" \ " {0.role!r}, \n" \ " {0.added_at}, \n" \ ")>".format(self)
class SubjectFileEntity(db.Model, CRUDMixin): """ Stores the file metadata """ __tablename__ = 'SubjectFile' id = db.Column("sfID", db.Integer, primary_key=True) subject_id = db.Column("sbjID", db.Integer, db.ForeignKey('Subject.sbjID'), nullable=False) event_id = db.Column("evtID", db.Integer, db.ForeignKey('Event.evtID'), nullable=False) file_name = db.Column("sfFileName", db.String(255), nullable=False) file_check_sum = db.Column("sfFileCheckSum", db.String(32), nullable=False) file_size = db.Column("sfFileSize", db.String(255), nullable=False) uploaded_at = db.Column("sfUploadedAt", db.DateTime(), nullable=False, server_default='0000-00-00 00:00:00') user_id = db.Column("usrID", db.Integer, db.ForeignKey('User.usrID'), nullable=False) # @OneToOne subject = db.relationship('SubjectEntity', uselist=False, lazy='joined') event = db.relationship('EventEntity', uselist=False, lazy='joined') user = db.relationship('UserEntity', uselist=False, lazy='joined') def get_full_path(self, prefix): """ Build the full path using the database info and the prefix @TODO: implement the naming convention """ return os.path.join(prefix, self.file_name) def __repr__(self): return "<SubjectFileEntity (sfID: {0.id}, sbjID: {0.subject_id})>" \ "usrID: {0.user_id}".format(self) def serialize(self): """Return object data for jsonification """ return { 'id': self.id, 'file_name': self.file_name, 'file_check_sum': self.file_check_sum, 'file_size': self.file_size, 'uploaded_at': dump_datetime(self.uploaded_at), 'subject_id': self.subject_id, 'event_id': self.event_id, 'user_id': self.user_id, 'user_name': self.user.get_name(), # 'subject': self.subject.serialize(), # 'event': self.event.serialize(), # 'user': self.user.serialize(), }
class WebSessionEntity(db.Model, CRUDMixin): """Store web session details""" __tablename__ = 'WebSession' id = db.Column('webID', db.Integer, primary_key=True) session_id = db.Column('webSessID', db.String(255), nullable=False, default='') user_id = db.Column('usrID', db.Integer, db.ForeignKey('User.usrID'), nullable=False, default=0) ip = db.Column('webIP', db.String(15), nullable=False, default='') date_time = db.Column('webDateTime', db.DateTime, nullable=False, default=datetime.datetime(datetime.MINYEAR, 1, 1)) user_agent_id = db.Column('uaID', db.Integer, db.ForeignKey('UserAgent.uaID'), nullable=False) # @OneToMany user_agent = db.relationship(UserAgentEntity, lazy='joined') user = db.relationship(UserEntity, lazy='joined') @staticmethod def get_by_session_id(session_id): """ Search helper: WHERE webSessID = ???""" return WebSessionEntity.query.filter_by(session_id=session_id).first() def __repr__(self): """ Return a friendly object representation """ return "<WebSessionEntity (webID: '{0.id}', webSessID: {0.session_id},"\ " usrID: {0.user_id}, webIP: {0.ip})>".format(self)
class SubjectFileEntity(db.Model, CRUDMixin): """ Stores the uploaded file metadata """ __tablename__ = 'SubjectFile' id = db.Column("sfID", db.Integer, primary_key=True) subject_id = db.Column("sbjID", db.Integer, db.ForeignKey('Subject.sbjID'), nullable=False) event_id = db.Column("evtID", db.Integer, db.ForeignKey('Event.evtID'), nullable=False) file_name = db.Column("sfFileName", db.String(255), nullable=False) file_check_sum = db.Column("sfFileCheckSum", db.String(32), nullable=False) file_size = db.Column("sfFileSize", db.String(255), nullable=False) uploaded_at = db.Column("sfUploadedAt", db.DateTime, nullable=False, server_default='0000-00-00 00:00:00') user_id = db.Column("usrID", db.Integer, db.ForeignKey('User.usrID'), nullable=False) # @OneToOne subject = db.relationship('SubjectEntity', uselist=False, lazy='joined') event = db.relationship('EventEntity', uselist=False, lazy='joined') user = db.relationship('UserEntity', uselist=False, lazy='joined') def create_folder(self, directory): """ Create folder if it does not exist """ success = True if not os.path.exists(directory): try: os.makedirs(directory) except Exception as exc: print "Failed due: {}".format(exc) success = False return success @classmethod def get_convention_file_name(cls, date_and_time, subject_id, file_name): """ Concatenate the pieces to obtain a fiendly file name. @TODO: check if we need to need the "site ID" and how to obtain it. Original convention: 20120101_0123_SiteID_A_SubjectID_B_Sequence123_xyz.jpg Actual implementation (does not keep track of sequences): 20120101_0123_site_subject_B_xyz.jpg """ date_part = date_and_time.strftime("%Y%m%d") time_part = date_and_time.strftime("%H%M") file_convention = "{}_{}_site_subject_{}_{}".format( date_part, time_part, subject_id, file_name) return file_convention def get_full_path(self, prefix): """ Build the full path using the database info and the prefix @TODO: implement the naming convention 20120101_0123_SiteIDA_SubjectIDB_Sequence123_xyz.jpg """ subject_dir = os.path.join(prefix, "subject_{}".format(self.subject.redcap_id)) success = self.create_folder(subject_dir) assert success file_convention = SubjectFileEntity.get_convention_file_name( self.uploaded_at, self.subject.redcap_id, self.file_name) full_path = os.path.join(subject_dir, file_convention) return full_path def __repr__(self): """ Return a friendly object representation """ return "<SubjectFileEntity (sfID: {0.id}, sbjID: {0.subject_id}, " \ "usrID: {0.user_id}, sfFileName: {0.file_name}>)".format(self) def serialize(self): """Return object data for jsonification """ # @TODO: add information about download counts return { 'id': self.id, 'file_name': self.file_name, 'file_check_sum': self.file_check_sum, 'file_size': self.file_size, 'uploaded_at': utils.localize_est_datetime(self.uploaded_at), 'subject_id': self.subject_id, 'event_id': self.event_id, 'user_id': self.user_id, 'user_name': self.user.get_name(), # 'subject': self.subject.serialize(), # 'event': self.event.serialize(), # 'user': self.user.serialize(), }
class LogEntity(db.Model, CRUDMixin): """ Keep track of important user actions """ __tablename__ = 'Log' id = db.Column('logID', db.Integer, primary_key=True) type_id = db.Column('logtID', db.Integer, db.ForeignKey('LogType.logtID'), nullable=False) web_session_id = db.Column('webID', db.Integer, db.ForeignKey('WebSession.webID'), nullable=False) date_time = db.Column('logDateTime', db.DateTime, nullable=False, server_default='0000-00-00 00:00:00') details = db.Column('logDetails', db.Text, nullable=False) # @OneToOne log_type = db.relationship(LogTypeEntity, uselist=False, lazy='joined') web_session = db.relationship(WebSessionEntity, uselist=False, lazy='joined') @staticmethod def get_logs(per_page=25, page_num=1): """ Helper for formating the event details """ def item_from_entity(entity): return { 'id': entity.id, 'user_email': entity.web_session.user.email if entity.web_session.user is not None else '', 'type': entity.log_type.type, 'details': entity.details, 'web_session_ip': entity.web_session.ip, 'date_time': utils.localize_est_datetime(entity.date_time), } pagination = LogEntity.query.paginate(page_num, per_page, False) items = map(item_from_entity, pagination.items) return items, pagination.pages @staticmethod def _log(log_type, session_id, details=''): """ Helper for logging """ logt = LogTypeEntity.query.filter_by(type=log_type).first() if logt is None: app.logger.error("Developer error. Invalid log type: {}" .format(log_type)) return web_session = WebSessionEntity.get_by_session_id(session_id) if web_session is None: app.logger.error("Developer error. Invalid session id: {}" .format(session_id)) return LogEntity.create(log_type=logt, date_time=datetime.datetime.now(), details=details, web_session=web_session) @staticmethod def account_created(session_id, details=''): """ Log account creation """ LogEntity._log(LOG_TYPE_ACCOUNT_CREATED, session_id, details) @staticmethod def login(session_id, details=''): """ Log successful login """ LogEntity._log(LOG_TYPE_LOGIN, session_id, details) @staticmethod def logout(session_id, details=''): """ Log logout click """ LogEntity._log(LOG_TYPE_LOGOUT, session_id, details) @staticmethod def login_error(session_id, details=''): """ Log failed login """ LogEntity._log(LOG_TYPE_LOGIN_ERROR, session_id, details) @staticmethod def file_uploaded(session_id, details=''): """ Log file upload """ LogEntity._log(LOG_TYPE_FILE_UPLOADED, session_id, details) @staticmethod def file_downloaded(session_id, details=''): """ Log file download """ LogEntity._log(LOG_TYPE_FILE_DOWNLOADED, session_id, details) @staticmethod def account_modified(session_id, details=''): """ Log account changes """ LogEntity._log(LOG_TYPE_ACCOUNT_MODIFIED, session_id, details) @staticmethod def redcap_subjects_imported(session_id, details=''): """ Log it """ LogEntity._log(LOG_TYPE_REDCAP_SUBJECTS_IMPORTED, session_id, details) @staticmethod def redcap_events_imported(session_id, details=''): """ Log it """ LogEntity._log(LOG_TYPE_REDCAP_EVENTS_IMPORTED, session_id, details) def __repr__(self): """ Return a friendly object representation """ return "<LogEntity(logID: {0.id}, "\ "logtID: {0.type_id}" \ "webID: {0.web_session_id}, "\ "date_time: {0.date_time})>".format(self)