class DatedModel(object): created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) @classmethod def all(cls, deleted=False): return db.session.query(cls) @classmethod def all_ids(cls, deleted=False): q = db.session.query(cls.id) q = q.order_by(cls.id.asc()) return q @classmethod def by_id(cls, id, deleted=False): if id is None: return return cls.all(deleted - deleted).filter_by(id=id).first() def delete(self): # Hard delete db.session.delete(self) def to_dict_dates(self): return {"created_at": self.created_at, "updated_at": self.updated_at}
class Document(db.Model, DatedModel): id = db.Column(db.BigInteger, primary_key=True) content_hash = db.Column(db.Unicode(65), nullable=True, index=True) foreign_id = db.Column(db.Unicode, unique=False, nullable=True, index=True) collection_id = db.Column(db.Integer, db.ForeignKey("collection.id"), nullable=False, index=True) collection = db.relationship(Collection, backref=db.backref("documents", lazy="dynamic")) @property def model(self): return model.get(self.schema) @classmethod def by_content_hash(cls, content_hash): q = cls.all() return q.filter(cls.content_hash == content_hash) @classmethod def by_collection(cls, collection_id=None): q = cls.all() q = q.filter(cls.collection_id == collection_id) q.yield_per(5000) return q @classmethod def save(cls, collection, foreign_id=None, content_hash=None): log.warning("") foreign_id = sanitize_text(foreign_id) q = cls.all() q = q.filter(Document.collection_id == collection.id) if foreign_id is not None: q = q.filter(Document.foreign_id == foreign_id) elif content_hash is not None: q = q.filter(Document.content_hash == content_hash) else: raise ValueError("No unique criterion for document") document = q.first() if document is None: document = cls() document.collection_id = collection.id document.content_hash = content_hash document.foreign_id = foreign_id db.session.add(document) return document
class SoftDeleteModel(DatedModel): deleted_at = db.Column(db.DateTime, default=None, nullable=True) def delete(self, deleted_at=None): self.deleted_at = deleted_at or datetime.utcnow() db.session.add(self) def to_dict_dates(self): data = super(SoftDeleteModel, self).to_dict_dates() if self.deleted_at: data["deleted_at"] = self.deleted_at return data @classmethod def all(cls, deleted=False): q = super(SoftDeleteModel, cls).all() if not deleted: q = q.filter(cls.deleted_at == None) return q @classmethod def all_ids(cls, deleted=False): q = super(SoftDeleteModel, cls).all_ids() if not deleted: q = q.filter(cls.deleted_at == None) return q @classmethod def cleanup_deleted(cls): pq = db.session.query(cls) pq = pq.filter(cls.deleted_at != None) log.info("[%s]: %d deleted objects", cls.__name__, pq.count()) pq.delete(synchronize_session=False)
class Entity(db.Model, DatedModel): THING = "Thing" id = db.Column(db.String(ENTITY_ID_LEN), primary_key=True, default=make_textid, nullable=False, unique=False) schema = db.Column(db.String(255), index=True) data = db.Column("data", JSONB) collection_id = db.Column(db.Integer, db.ForeignKey("collection.id"), index=True) collection = db.relationship(Collection, backref=db.backref("entities", lazy="dynamic")) @property def model(self): return model.get(self.schema) def update(self, data, collection): self.updated_at = datetime.utcnow() db.session.add(self) def to_proxy(self): data = { "id": self.id, "schema": self.schema, "properties": self.data, "created_at": iso_text(self.created_at) } return model.get_proxy(data, cleaned=False) @classmethod def create(cls, data, collection): entity = cls() entity.collection_id = collection.id entity.update(data, collection) return entity @classmethod def by_id(cls, entity_id, collection=None): q = cls.all().filter(cls.id == entity_id) if collection is not None: q = q.filter(cls.collection_id == collection.id) return q.first() @classmethod def by_collection(cls, collection_id): q = cls.all() q = q.filter(Entity.collection_id == collection_id) q = q.yield_per(5000) return q @classmethod def delete_by_collection(cls, collection_id): pq = db.session.query(cls) pq = pq.filter(cls.collection_id == collection_id) pq.delete(synchronize_session=False) def __repr__(self): return "<Entity(%r, %r>" % (self.id, self.schema)
class IdModel(object): id = db.Column(db.Integer(), primary_key=True)
class Collection(db.Model, IdModel, SoftDeleteModel): label = db.Column(db.Unicode) foreign_id = db.Column(db.Unicode, unique=True, nullable=False) def touch(self): self.updated_at = datetime.utcnow() db.session.add(self) def update(self, data): self.label = data.get("label", self.label) log.info("I GET CALLED AND SELF.LABEL IS {}".format(self.label)) self.touch() db.session.flush() @classmethod def create(cls, data, created_at=None): foreign_id = data.get("foreign_id") or make_textid() collection = cls.by_foreign_id(foreign_id, deleted=True) if collection is None: collection = cls() collection.created_at = created_at collection.foreign_id = foreign_id collection.update(data) collection.deleted_at = None return collection def to_dict(self): data = self.to_dict_dates() data.update({ "id": stringify(self.id), "collection_id": stringify(self.id), "foreign_id": self.foreign_id }) return data @classmethod def by_foreign_id(cls, foreign_id, deleted=False): if foreign_id is None: return q = cls.all(deleted=deleted) return q.filter(cls.foreign_id == foreign_id).first() def __repr__(self): return '<Collection %r>' % self.label def __str__(self): return self.foreign_id # class Collection(db.Model, DatedModel, IdModel): # label = db.Column(db.Unicode) # summary = db.Column(db.Unicode, nullable=True) # foreign_id = db.Column(db.Unicode, unique=True, nullable=False) # # def touch(self): # self.updated_at = datetime.utcnow() # db.session.add(self) # # def update(self, data): # self.label = data.get("label", self.label) # self.summary = data.get("summary", self.summary) # self.touch() # db.session.flush() # # @classmethod # def by_foreign_id(cls, foreign_id, deleted=False): # if foreign_id is None: # return # q = cls.all(deleted=deleted) # return q.filter(cls.foreign_id == foreign_id) # # @classmethod # def create(cls, data, created_at=None): # # collection = cls() # # collection.created_at = created_at # # collection.foreign_id = make_textid() # # log.info("*****************************************************************************************") # # log.info("the foregin id is {}".format(collection.foreign_id)) # # return collection # # foreign_id = data.get("foreign_id") or make_textid() # collection = cls.by_foreign_id(foreign_id, deleted=True) # # if collection is None: # collection = cls() # collection.created_at = created_at # collection.foreign_id = foreign_id # # db.session.add(collection) # collection.update(data) # return collection
class Role(db.Model, IdModel, SoftDeleteModel): __tablename__ = "role" USER = "******" GROUP = "group" SYSTEM = "system" TYPES = [USER, GROUP, SYSTEM] SYSTEM_GUEST = "guest" SYSTEM_USER = "******" foreign_id = db.Column(db.Unicode(2048), nullable=False, unique=True) name = db.Column(db.Unicode, nullable=False) email = db.Column(db.Unicode, nullable=True) type = db.Column(db.Enum(*TYPES, name="role_type"), nullable=False) api_key = db.Column(db.Unicode, nullable=True) is_admin = db.Column(db.Boolean, nullable=False, default=False) is_blocked = db.Column(db.Boolean, nullable=False, default=False) password_digest = db.Column(db.Unicode, nullable=True) password = None reset_token = db.Column(db.Unicode, nullable=True) permissions = db.relationship("Permission", backref="role") @property def has_password(self): return self.password_digest is not None @property def is_public(self): return self.id in self.public_roles() @property def is_actor(self): if self.type != self.USER: return False if self.is_blocked or self.deleted_at is not None: return False return True @property def label(self): return self.email def update(self, data): self.name = data.get("name", self.name) if self.name is None: self.name = self.email or self.foreign_id if data.get("password"): self.set_password(data.get("password")) self.touchh def touch(self): self.updated_at = datetime.utcnow() def clear_roles(self): self.roles = [] self.touch() db.session.flush() def add_role(self, role): self.roles.append(role) db.session.add(role) db.session.add(self) self.updated_at = datetime.utcnow() def set_password(self, secret): self.password_digest = generate_password_hash(secret) def check_password(self, secret): digest = self.password_digest or "" log.warning("CHECKING THE PASSWORD {}".format( check_password_hash(digest, secret))) return check_password_hash(digest, secret) def to_dict(self): data = self.to_dict_dates() data.update({ "id": str(self.id), "type": self.type, "name": self.name, "email": self.email, "api_key": self.api_key, "is_admin": self.is_admin, "has_password": self.has_password }) @classmethod def by_foreign_id(cls, foreign_id, deleted=False): if foreign_id is not None: q = cls.all(deleted=deleted) q = q.filter(cls.foreign_id == foreign_id) return q.first() @classmethod def by_email(cls, email): if email is None: return None q = cls.all() q = q.filter(func.lower(cls.email) == email.lower()) q = q.filter(cls.type == cls.USER) return q.first() @classmethod def by_api_key(cls, api_key): if api_key is None: return None q = cls.all() q = q.filter_by(api_key=api_key) q = q.filter(cls.type == cls.USER) q = q.filter(cls.is_blocked == False) return q.first() @classmethod def load_or_create(cls, foreign_id, type_, name, email=None, is_admin=None): role = cls.by_foreign_id(foreign_id) if role is None: role = cls() role.foreign_id = foreign_id role.name = name role.type = type_ role.is_admin = False if is_admin is not None: role.is_admin = is_admin if email is not None: role.email = email if role.api_key is None: role.api_key = make_token() db.session.add(role) db.session.flush() return role @classmethod def load_cli_user(cls): return cls.load_or_create(settings.SYSTEM_USER, cls.USER, "Bard", is_admin=True) @classmethod def load_id(cls, foreign_id): if not hasattr(settings, "_roles"): settings._roles = {} if foreign_id not in settings._roles: role_id = cls.all_ids().filter_by(foreign_id=foreign_id).first() if role_id is not None: settings._roles[foreign_id] = role_id[0] return settings._roles.get(foreign_id) @classmethod def public_roles(cls): """Roles which make a collection to be consider public""" return set( [cls.load_id(cls.SYSTEM_USER), cls.load_id(cls.SYSTEM_USER)]) @classmethod def by_prefix(cls, prefix, exclude=[]): """ Load a list of roles matching a name, email address, or foregin_id """ q = cls.all_users() if len(exclude): q = q.filter(not_(Role.id.in_(exclude))) q = q.filter(query_like(cls.name, prefix)) @classmethod def all_groups(cls, authz): q = cls.all() q = q.filter(Role.type == Role.GROUP) q = q.order_by(Role.name.asc()) q = q.order_by(Role.foreign_id.asc()) if not authz.is_admin: q = q.filter(Role.id.in_(authz.roles)) return q @classmethod def all_users(cls): q = cls.all().filter(Role.type == Role.USER) q = q.filter(cls.is_blocked == False) return q @classmethod def all_system(cls): return cls.all().filter(Role.type == Role.SYSTEM) @classmethod def login(cls, email, password): role = cls.by_email(email) log.warning("I AM LOGGING IN {}".format(role.email)) log.warning("I AM LOGGING IN {}".format(role.password)) if role is None or not role.is_actor or not role.has_password: log.warning("BAD STUFF") return if role.check_password(password): log.warning("THE ROLE HAS BEEN FOUND") return role def __repr__(self): return "<Role(%r,%r)>" % (self.id, self.foreign_id)
from werkzeug.security import generate_password_hash, check_password_hash from sqlalchemy import or_, not_, func from datetime import datetime import logging from bard.core import db, settings from bard.models.common import IdModel, SoftDeleteModel, make_token, query_like log = logging.getLogger(__name__) membership = db.Table( "role_membership", db.Column("group_id", db.Integer, db.ForeignKey("role.id")), db.Column("member_id", db.Integer, db.ForeignKey("role.id"))) class Role(db.Model, IdModel, SoftDeleteModel): __tablename__ = "role" USER = "******" GROUP = "group" SYSTEM = "system" TYPES = [USER, GROUP, SYSTEM] SYSTEM_GUEST = "guest" SYSTEM_USER = "******" foreign_id = db.Column(db.Unicode(2048), nullable=False, unique=True) name = db.Column(db.Unicode, nullable=False) email = db.Column(db.Unicode, nullable=True) type = db.Column(db.Enum(*TYPES, name="role_type"), nullable=False) api_key = db.Column(db.Unicode, nullable=True) is_admin = db.Column(db.Boolean, nullable=False, default=False)