class RnkCITATIONLOG(db.Model): """Represents a RnkCITATIONLOG record.""" __tablename__ = 'rnkCITATIONLOG' id = db.Column(db.Integer(11, unsigned=True), primary_key=True, autoincrement=True, nullable=False) citee = db.Column(db.Integer(10, unsigned=True), nullable=False) citer = db.Column(db.Integer(10, unsigned=True), nullable=False) type = db.Column(db.Enum('added', 'removed', name='rnkcitationlog_type'), nullable=True) action_date = db.Column(db.DateTime, nullable=False) __table_args__ = (db.Index('citee', citee), db.Index('citer', citer), db.Model.__table_args__)
class PidLog(db.Model): """Audit log of actions happening to persistent identifiers. This model is primarily used through PersistentIdentifier.log and rarely created manually. """ __tablename__ = 'pidLOG' __table_args__ = ( db.Index('idx_action', 'action'), ) id = db.Column(db.Integer(15, unsigned=True), primary_key=True) """Id of persistent identifier entry.""" id_pid = db.Column( db.Integer(15, unsigned=True), db.ForeignKey(PersistentIdentifier.id), nullable=True, ) """PID.""" timestamp = db.Column(db.DateTime(), nullable=False, default=datetime.now) """Creation datetime of entry.""" action = db.Column(db.String(10), nullable=False) """Action identifier.""" message = db.Column(db.Text(), nullable=False) """Log message.""" # Relationship pid = db.relationship("PersistentIdentifier", backref="logs")
class BskRECORDCOMMENT(db.Model): """Represents a BskRECORDCOMMENT record.""" __tablename__ = 'bskRECORDCOMMENT' id = db.Column(db.Integer(15, unsigned=True), nullable=False, primary_key=True, autoincrement=True) id_bibrec_or_bskEXTREC = db.Column(db.Integer(16), nullable=False, server_default='0') id_bskBASKET = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(BskBASKET.id), nullable=False, server_default='0') id_user = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(User.id), nullable=False, server_default='0') title = db.Column(db.String(255), nullable=False, server_default='') body = db.Column(db.Text, nullable=False) date_creation = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00', index=True) priority = db.Column(db.Integer(15), nullable=False, server_default='0') in_reply_to_id_bskRECORDCOMMENT = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(id), nullable=False, server_default='0') reply_order_cached_data = db.Column(db.Binary, nullable=True) in_reply_to = db.relationship('BskRECORDCOMMENT') basket = db.relationship(BskBASKET, backref='RECORDCOMMENTs') user = db.relationship(User) __table_args__ = (db.Index('bskRECORDCOMMENT_reply_order_cached_data', reply_order_cached_data, mysql_length=[40]), db.Model.__table_args__)
class RnkCITATIONDICT(db.Model): """Represent a RnkCITATIONDICT record.""" __tablename__ = 'rnkCITATIONDICT' citee = db.Column(db.Integer(10, unsigned=True), primary_key=True) citer = db.Column(db.Integer(10, unsigned=True), primary_key=True) last_updated = db.Column(db.DateTime, nullable=False) __table_args__ = (db.Index('rnkCITATIONDICT_reverse', citer, citee), db.Model.__table_args__)
def do_upgrade(): """ Implement your upgrades here """ m = db.MetaData(bind=db.engine) m.reflect() tpid = db.Table( 'pid', m, db.Column('id', db.Integer(15, unsigned=True), primary_key=True, nullable=False), db.Column('type', db.String(length=6), nullable=False), db.Column('pid', db.String(length=255), nullable=False), db.Column('status', db.Char(length=1), nullable=False), db.Column('created', db.DateTime(), nullable=False), db.Column('last_modified', db.DateTime(), nullable=False), db.Index('uidx_type_pid', 'type', 'pid', unique=True), db.Index('idx_status', 'status'), mysql_engine='MyISAM', ) tpidlog = db.Table( 'pidLOG', m, db.Column('id', db.Integer(15, unsigned=True), primary_key=True, nullable=False), db.Column('id_pid', db.Integer(15, unsigned=True), ForeignKey('pid.id')), db.Column('timestamp', DateTime(), nullable=False), db.Column('action', db.String(length=10), nullable=False), db.Column('message', Text(), nullable=False), db.Index('idx_action', 'action'), mysql_engine='MyISAM', ) tpidregistry = db.Table( 'pidREGISTRY', m, db.Column('object_type', db.String(length=3), primary_key=True, nullable=False), db.Column('object_id', db.String(length=255), nullable=False), db.Column('id_pid', db.Integer(15, unsigned=True), ForeignKey('pid.id'), primary_key=True, nullable=False), db.Index('idx_type_id', 'object_type', 'object_id'), mysql_engine='MyISAM', ) tpid.create() tpidlog.create() tpidregistry.create()
class AccARGUMENT(db.Model): """Represent an authorization argument.""" __tablename__ = 'accARGUMENT' id = db.Column(db.Integer(15), primary_key=True, autoincrement=True) keyword = db.Column(db.String(32), nullable=True) value = db.Column(db.String(255), nullable=True) __table_args__ = (db.Index('KEYVAL', keyword, value), db.Model.__table_args__) def __repr__(self): return "{0.keyword}={0.value}".format(self)
class UserEXT(db.Model): """Represent a UserEXT record.""" __tablename__ = 'userEXT' id = db.Column(db.VARBINARY(255), primary_key=True, nullable=False) method = db.Column(db.String(50), primary_key=True, nullable=False) id_user = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(User.id), nullable=False) user = db.relationship(User, backref="external_identifiers") __table_args__ = (db.Index('id_user', id_user, method, unique=True), db.Model.__table_args__)
class SeqSTORE(db.Model): """Represents a SeqSTORE record.""" __tablename__ = 'seqSTORE' id = db.Column(db.Integer(15, unsigned=True), primary_key=True, nullable=False, autoincrement=True) seq_name = db.Column(db.String(15)) seq_value = db.Column(db.String(20)) __table_args__ = (db.Index('seq_name_value', seq_name, seq_value, unique=True), db.Model.__table_args__)
class Bibdocmoreinfo(db.Model): """Represents a Bibdocmoreinfo record.""" __tablename__ = 'bibdocmoreinfo' id = db.Column(db.MediumInteger(9, unsigned=True), primary_key=True) id_bibdoc = db.Column(db.MediumInteger(9, unsigned=True), db.ForeignKey(Bibdoc.id), nullable=True) version = db.Column(db.TinyInteger(4, unsigned=True), nullable=True) format = db.Column(db.String(50), nullable=True) id_rel = db.Column(db.MediumInteger(9, unsigned=True), nullable=True) namespace = db.Column(db.Char(25), nullable=True) data_key = db.Column(db.Char(25)) data_value = db.Column(db.LargeBinary) __table_args__ = (db.Index('bibdocmoreinfo_key', id_bibdoc, version, format, id_rel, namespace, data_key), db.Model.__table_args__)
class Usergroup(db.Model): """Represent a Usergroup record.""" def __str__(self): """Return string representation.""" return "%s <%s>" % (self.name, self.description) __tablename__ = 'usergroup' JOIN_POLICIES = { 'VISIBLEOPEN': 'VO', 'VISIBLEMAIL': 'VM', 'INVISIBLEOPEN': 'IO', 'INVISIBLEMAIL': 'IM', 'VISIBLEEXTERNAL': 'VE', } LOGIN_METHODS = { 'INTERNAL': 'INTERNAL', 'EXTERNAL': 'EXTERNAL', } id = db.Column(db.Integer(15, unsigned=True), nullable=False, primary_key=True, autoincrement=True) name = db.Column(db.String(255), nullable=False, server_default='', unique=True, index=True) description = db.Column(db.Text, nullable=True) join_policy = db.Column(ChoiceType(map(lambda (k, v): (v, k), JOIN_POLICIES.items()), impl=db.CHAR(2)), nullable=False, server_default='') login_method = db.Column(ChoiceType( map(lambda (k, v): (v, k), LOGIN_METHODS.items())), nullable=False, server_default='INTERNAL') # FIXME Unique(login_method(70), name) __table_args__ = (db.Index('login_method_name', 'login_method', 'name', mysql_length={ 'login_method': 60, 'name': 255 }), db.Model.__table_args__) @classmethod def filter_visible(cls): """Return query object with filtered out invisible groups.""" visible = filter(lambda k: k[0] == 0, cls.JOIN_POLICIES.values()) assert len(visible) > 1 # if implementation chage use == instead of in return cls.query.filter(cls.join_policy.in_(visible)) @property def login_method_is_external(self): """Return True if the group is external.""" return self.login_method == Usergroup.LOGIN_METHODS['EXTERNAL'] def join(self, user, status=None): """Join user to group. :param user: User to add into the group. :param status: status of user """ # if I want to join another user from the group if (user.id != current_user.get_id() and # I need to be an admin of the group not self.is_admin(current_user.get_id())): raise AccountSecurityError( 'Not enough right to ' 'add user "{0}" from group "{1}"'.format( user.nickname, self.name)) # join group self.users.append( UserUsergroup( id_user=user.id, user_status=status or self.new_user_status, )) try: db.session.commit() except Exception: db.session.rollback() raise def leave(self, user): """Remove user from group. :param user: User to remove from the group. """ # if I want to remove another user from the group if (user.id != current_user.get_id() and # I need to be an admin of the group not self.is_admin(current_user.get_id())): raise AccountSecurityError( 'Not enough right to ' 'remove user "{0}" from group "{1}"'.format( user.nickname, self.name)) # check that I'm not the last admin before leaving the group. if self.is_admin(user.id) and self.admins.count() == 1: raise IntegrityUsergroupError('User can leave the group ' 'without admins, please delete the ' 'group if you want to leave.') # leave the group UserUsergroup.query.filter_by( id_usergroup=self.id, id_user=user.id, ).delete() try: db.session.commit() except Exception: db.session.rollback() raise def is_admin(self, id_user): """Return True if the user is an admin of the group.""" return db.session.query( self.admins.filter( UserUsergroup.id_user == id_user).exists()).scalar() def get_users_not_in_this_group(self, nickname=None, email=None, limit=None): """Return users that not joined this group.""" # base query query = User.query.outerjoin(User.usergroups).filter( User.id.notin_( db.select([UserUsergroup.id_user], UserUsergroup.id_usergroup == self.id))) # additional optional filters if nickname: query = query.filter(User.nickname.like(nickname)) if email: query = query.filter(User.email.like(email)) if limit: query = query.limit(limit) # return results return query @property def new_user_status(self): """Return user status for new user.""" if not self.join_policy.code.endswith('O'): return UserUsergroup.USER_STATUS['PENDING'] return UserUsergroup.USER_STATUS['MEMBER']
class CmtRECORDCOMMENT(db.Model): """Represents a CmtRECORDCOMMENT record.""" __tablename__ = 'cmtRECORDCOMMENT' id = db.Column(db.Integer(15, unsigned=True), nullable=False, primary_key=True, autoincrement=True) id_bibrec = db.Column(db.MediumInteger(8, unsigned=True), db.ForeignKey(Bibrec.id), nullable=False, server_default='0') id_user = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(User.id), nullable=False, server_default='0') title = db.Column(db.String(255), nullable=False, server_default='') body = db.Column(db.Text, nullable=False) date_creation = db.Column(db.DateTime, nullable=False, server_default='1900-01-01 00:00:00') star_score = db.Column(db.TinyInteger(5, unsigned=True), nullable=False, server_default='0') nb_votes_yes = db.Column(db.Integer(10), nullable=False, server_default='0') nb_votes_total = db.Column(db.Integer(10, unsigned=True), nullable=False, server_default='0') nb_abuse_reports = db.Column(db.Integer(10), nullable=False, server_default='0') status = db.Column(db.Char(2), nullable=False, index=True, server_default='ok') round_name = db.Column(db.String(255), nullable=False, server_default='') restriction = db.Column(db.String(50), nullable=False, server_default='') in_reply_to_id_cmtRECORDCOMMENT = db.Column(db.Integer(15, unsigned=True), db.ForeignKey(id), nullable=False, server_default='0') reply_order_cached_data = db.Column(db.Binary, nullable=True) bibrec = db.relationship(Bibrec, backref='recordcomments') user = db.relationship(User, backref='recordcomments') replies = db.relationship('CmtRECORDCOMMENT', backref=db.backref('parent', remote_side=[id], order_by=date_creation)) @property def is_deleted(self): """Check if is deleted.""" return self.status != 'ok' def is_collapsed(self, id_user): """Return true if the comment is collapsed by user.""" return CmtCOLLAPSED.query.filter( db.and_(CmtCOLLAPSED.id_bibrec == self.id_bibrec, CmtCOLLAPSED.id_cmtRECORDCOMMENT == self.id, CmtCOLLAPSED.id_user == id_user)).count() > 0 @session_manager def collapse(self, id_user): """Collapse comment beloging to user.""" c = CmtCOLLAPSED(id_bibrec=self.id_bibrec, id_cmtRECORDCOMMENT=self.id, id_user=id_user) db.session.add(c) db.session.commit() def expand(self, id_user): """Expand comment beloging to user.""" CmtCOLLAPSED.query.filter( db.and_(CmtCOLLAPSED.id_bibrec == self.id_bibrec, CmtCOLLAPSED.id_cmtRECORDCOMMENT == self.id, CmtCOLLAPSED.id_user == id_user)).delete( synchronize_session=False) __table_args__ = (db.Index('cmtRECORDCOMMENT_reply_order_cached_data', reply_order_cached_data, mysql_length=40), db.Model.__table_args__) @classmethod def count(cls, *criteria, **filters): """Count how many comments.""" return cls.query.filter(*criteria).filter_by(**filters).count()
class PersistentIdentifier(db.Model): """Store and register persistent identifiers. Assumptions: * Persistent identifiers can be represented as a string of max 255 chars. * An object has many persistent identifiers. * A persistent identifier has one and only one object. """ __tablename__ = 'pidSTORE' __table_args__ = ( db.Index('uidx_type_pid', 'pid_type', 'pid_value', unique=True), db.Index('idx_status', 'status'), db.Index('idx_object', 'object_type', 'object_value'), ) id = db.Column(db.Integer(15, unsigned=True), primary_key=True) """Id of persistent identifier entry.""" pid_type = db.Column(db.String(6), nullable=False) """Persistent Identifier Schema.""" pid_value = db.Column(db.String(length=255), nullable=False) """Persistent Identifier.""" pid_provider = db.Column(db.String(length=255), nullable=False) """Persistent Identifier Provider""" status = db.Column(db.CHAR(length=1), nullable=False) """Status of persistent identifier, e.g. registered, reserved, deleted.""" object_type = db.Column(db.String(3), nullable=True) """Object Type - e.g. rec for record.""" object_value = db.Column(db.String(length=255), nullable=True) """Object ID - e.g. a record id.""" created = db.Column(db.DateTime(), nullable=False, default=datetime.now) """Creation datetime of entry.""" last_modified = db.Column( db.DateTime(), nullable=False, default=datetime.now, onupdate=datetime.now ) """Last modification datetime of entry.""" # # Class methods # @classmethod def create(cls, pid_type, pid_value, pid_provider='', provider=None): """Internally reserve a new persistent identifier. A provider for the given persistent identifier type must exists. By default the system will choose a provider according to the pid type. If desired, the default system provider can be overridden via the provider keyword argument. Return PID object if successful otherwise None. """ # Ensure provider exists if provider is None: provider = PidProvider.create(pid_type, pid_value, pid_provider) if not provider: raise Exception( "No provider found for %s:%s (%s)" % ( pid_type, pid_value, pid_provider) ) try: obj = cls(pid_type=provider.pid_type, pid_value=provider.create_new_pid(pid_value), pid_provider=pid_provider, status=cfg['PIDSTORE_STATUS_NEW']) obj._provider = provider db.session.add(obj) db.session.commit() obj.log("CREATE", "Created") return obj except SQLAlchemyError: db.session.rollback() obj.log("CREATE", "Failed to created. Already exists.") return None @classmethod def get(cls, pid_type, pid_value, pid_provider='', provider=None): """Get persistent identifier. Return None if not found. """ pid_value = to_unicode(pid_value) obj = cls.query.filter_by( pid_type=pid_type, pid_value=pid_value, pid_provider=pid_provider ).first() if obj: obj._provider = provider return obj else: return None # # Instance methods # def has_object(self, object_type, object_value): """Determine if this PID is assigned to a specific object.""" if object_type not in cfg['PIDSTORE_OBJECT_TYPES']: raise Exception("Invalid object type %s." % object_type) object_value = to_unicode(object_value) return self.object_type == object_type and \ self.object_value == object_value def get_provider(self): """Get the provider for this type of persistent identifier.""" if self._provider is None: self._provider = PidProvider.create( self.pid_type, self.pid_value, self.pid_provider ) return self._provider def assign(self, object_type, object_value, overwrite=False): """Assign this persistent identifier to a given object. Note, the persistent identifier must first have been reserved. Also, if an exsiting object is already assigned to the pid, it will raise an exception unless overwrite=True. """ if object_type not in cfg['PIDSTORE_OBJECT_TYPES']: raise Exception("Invalid object type %s." % object_type) object_value = to_unicode(object_value) if not self.id: raise Exception( "You must first create the persistent identifier before you " "can assign objects to it." ) if self.is_deleted(): raise Exception( "You cannot assign objects to a deleted persistent identifier." ) # Check for an existing object assigned to this pid existing_obj_id = self.get_assigned_object(object_type) if existing_obj_id and existing_obj_id != object_value: if not overwrite: raise Exception( "Persistent identifier is already assigned to another " "object" ) else: self.log( "ASSIGN", "Unassigned object %s:%s (overwrite requested)" % ( self.object_type, self.object_value) ) self.object_type = None self.object_value = None elif existing_obj_id and existing_obj_id == object_value: # The object is already assigned to this pid. return True self.object_type = object_type self.object_value = object_value db.session.commit() self.log("ASSIGN", "Assigned object %s:%s" % (self.object_type, self.object_value)) return True def update(self, with_deleted=False, *args, **kwargs): """Update the persistent identifier with the provider..""" if self.is_new() or self.is_reserved(): raise Exception( "Persistent identifier has not yet been registered." ) if not with_deleted and self.is_deleted(): raise Exception("Persistent identifier has been deleted.") provider = self.get_provider() if provider is None: self.log("UPDATE", "No provider found.") raise Exception("No provider found.") if provider.update(self, *args, **kwargs): if with_deleted and self.is_deleted(): self.status = cfg['PIDSTORE_STATUS_REGISTERED'] db.session.commit() return True return False def reserve(self, *args, **kwargs): """Reserve the persistent identifier with the provider. Note, the reserve method may be called multiple times, even if it was already reserved. """ if not (self.is_new() or self.is_reserved()): raise Exception( "Persistent identifier has already been registered." ) provider = self.get_provider() if provider is None: self.log("RESERVE", "No provider found.") raise Exception("No provider found.") if provider.reserve(self, *args, **kwargs): self.status = cfg['PIDSTORE_STATUS_RESERVED'] db.session.commit() return True return False def register(self, *args, **kwargs): """Register the persistent identifier with the provider.""" if self.is_registered() or self.is_deleted(): raise Exception( "Persistent identifier has already been registered." ) provider = self.get_provider() if provider is None: self.log("REGISTER", "No provider found.") raise Exception("No provider found.") if provider.register(self, *args, **kwargs): self.status = cfg['PIDSTORE_STATUS_REGISTERED'] db.session.commit() return True return False def delete(self, *args, **kwargs): """Delete the persistent identifier.""" if self.is_new(): # New persistent identifier which haven't been registered yet. Just # delete it completely but keep log) # Remove links to log entries (but otherwise leave the log entries) PidLog.query.filter_by(id_pid=self.id).update({'id_pid': None}) db.session.delete(self) self.log("DELETE", "Unregistered PID successfully deleted") else: provider = self.get_provider() if not provider.delete(self, *args, **kwargs): return False self.status = cfg['PIDSTORE_STATUS_DELETED'] db.session.commit() return True def sync_status(self, *args, **kwargs): """Synchronize persistent identifier status. Used when the provider uses an external service, which might have been modified outside of our system. """ provider = self.get_provider() result = provider.sync_status(self, *args, **kwargs) db.session.commit() return result def get_assigned_object(self, object_type=None): """Return an assigned object.""" if object_type is not None and self.object_type == object_type: return self.object_value return None def is_registered(self): """Return true if the persistent identifier has been registered.""" return self.status == cfg['PIDSTORE_STATUS_REGISTERED'] def is_deleted(self): """Return true if the persistent identifier has been deleted.""" return self.status == cfg['PIDSTORE_STATUS_DELETED'] def is_new(self): """Return true if the PIDhas not yet been registered or reserved.""" return self.status == cfg['PIDSTORE_STATUS_NEW'] def is_reserved(self): """Return true if the PID has not yet been reserved.""" return self.status == cfg['PIDSTORE_STATUS_RESERVED'] def log(self, action, message): """Store action and message in log.""" if self.pid_type and self.pid_value: message = "[%s:%s] %s" % (self.pid_type, self.pid_value, message) p = PidLog(id_pid=self.id, action=action, message=message) db.session.add(p) db.session.commit()