class Base(db.Model): """Base class that all models are derived from. It defines the id, created_at, and updated_at fields for all models. """ __abstract__ = True id = db.Column(db.Integer, primary_key=True) created_at = db.Column(db.DateTime, default=db.func.current_timestamp()) updated_at = db.Column(db.DateTime, default=db.func.current_timestamp(), onupdate=db.func.current_timestamp()) def to_json(self, ignoreFields=[]): """This method will convert a class to a JSON serializable dict while ignoring any fields passed into it via ignoreFields""" s = self.__dict__ jsn = {} for k, v in s.items(): # If the key starts with _ assume it's private and skip it # or if it has _id we don't want to see it # or if it's in the ignoreFields we were passed skip it. if k.startswith("_") or "_id" in k or k in ignoreFields: continue if isinstance(v, Base): jsn[to_camel_case(k)] = v.to_json() else: jsn[to_camel_case(k)] = v return jsn
class Status(Base): name = db.Column(db.String(100), nullable=False) status_type = db.Column(db.Enum("TODO", "IN_PROGRESS", "DONE", name='status_types'), nullable=False) next_statuses = db.relationship('Status', secondary=status_relationships, primaryjoin="Status.id == status_relationships.c.status_id", secondaryjoin="Status.id == status_relationships.c.next_status_id", backref='previous_statuses', lazy='dynamic') tickets = db.relationship('Ticket', backref='status', lazy='dynamic') def __init__(self, *, name, status_type=0): self.name = name self.status_type = status_type def to_json(self): return super().to_json(ignoreFields=["created_at", "updated_at"]) def get_next(self): return self.next_statuses.all() def get_previous(self): return self.previous_statuses.all()
class Field(Base): name = db.Column(db.String(100), nullable=False) data_type = db.Column(db.Enum("INTEGER", "FLOAT", "STRING", "TEXT", name="data_types"), nullable=False)
class Team(Base): """Team is a container for projects. When changing the name for a team use set_name as this properly updates dependent fields for Team. YOU WILL HAVE A BAD TIME IF YOU DO team.name = SOME_NAME. """ name = db.Column(db.String(120), nullable=False, unique=True) url_slug = db.Column(db.String(150), nullable=False, unique=True) icon = db.Column(db.String(150)) team_lead_id = db.Column(db.Integer, db.ForeignKey('user.id')) projects = db.relationship('Project', backref='team', lazy='dynamic') members = db.relationship('Membership', backref='team', lazy='dynamic') def __init__(self, *, name, icon=""): self.name = name self.url_slug = name.lower().replace(" ", "-") self.icon = icon def set_name(self, name): self.name = name self.url_slug = name.lower().replace(" ", "-") def from_json(json): validate(json, team_schema) t = Team(name=json["name"], icone=json.get("icon", "")) un = json.get("project_lead", {}).get("username", "") lead = User.query.filter_by(username=un).first() t.team_lead = lead return t def get_by_name_or_stub(name): t = Team.query.filter(or_(Team.name == name, Team.url_stub == name)).first() if t == None: raise AppError(status_code=404, message="That team does not exist") return t def from_json(json): validate(json, team_schema) t = Team(name=json["name"], icone=json.get("icon", "")) un = json.get("project_lead", {}).get("username", "") lead = User.query.filter_by(username=un).first() t.team_lead = lead return t def __repr__(self): return "<Team %r>" % (self.name)
class Team(Base): """Team is a container for projects. When changing the name for a team use set_name as this properly updates dependent fields for Team. YOU WILL HAVE A BAD TIME IF YOU DO team.name = SOME_NAME. """ name = db.Column(db.String(120), nullable=False, unique=True) url_slug = db.Column(db.String(150), nullable=False, unique=True) icon = db.Column(db.String(150)) team_lead_id = db.Column(db.Integer, db.ForeignKey('users.id')) projects = db.relationship('Project', backref='team', lazy='dynamic') members = db.relationship('Membership', backref='team', lazy='dynamic') def __init__(self, *, name, icon=''): self.name = name self.url_slug = name.lower().replace(' ', '-') self.icon = icon def set_name(self, name): self.name = name self.url_slug = name.lower().replace(' ', '-') def from_json(json): validate(json, team_create_schema) t = Team(name=json['name'], icon=json.get('icon', '')) un = json.get('project_lead', {}).get('username', '') lead = User.query.filter_by(username=un).first() t.team_lead = lead return t def to_json(self): return super().to_json(ignoreFields=["updated_at"]) def get_by_name_or_stub(name): t = Team.query.\ options(joinedload(Team.team_lead)).\ filter(or_(Team.name == name, Team.url_slug == name)).first() print(t.team_lead) if t == None: raise AppError(status_code=404, message='That team does not exist') return t def __repr__(self): return '<Team %r>' % (self.name)
class Base(db.Model): """Base class that all models are derived from. It defines the id, created_at, and updated_at fields for all models. """ __abstract__ = True id = db.Column(db.Integer, primary_key=True) created_at = db.Column(db.DateTime, default=db.func.current_timestamp()) updated_at = db.Column(db.DateTime, default=db.func.current_timestamp(), onupdate=db.func.current_timestamp()) def to_json(self): """This drops the internal sqlalchemy field which won't JSONify""" s = self.__dict__ s.pop('_sa_instance_state', None) return s
class Ticket(Base): """A ticket is a unit of work for a project, be it a bug or support ticket.""" ticket_key = db.Column(db.String(100), nullable=False, unique=True) # I mean jesus christ how many digits summary = db.Column(db.String(250), nullable=False) description = db.Column(db.Text()) assignee_id = db.Column(db.Integer, db.ForeignKey('users.id')) reporter_id = db.Column(db.Integer, db.ForeignKey('users.id')) project_id = db.Column(db.Integer, db.ForeignKey('project.id')) status_id = db.Column(db.Integer, db.ForeignKey('status.id')) fields = db.relationship('FieldValue', backref='ticket') comments = db.relationship('Comment', backref='ticket', lazy='dynamic') def __init__(self, *, ticket_key, summary, description, assignee_id=None, reporter_id=None): self.ticket_key = ticket_key self.summary = summary self.description = description self.assignee_id = assignee_id self.reporter_id = reporter_id def get_by_id(i, preload=''): tk = Ticket.query.\ options(joinedload(Ticket.status)).\ options(joinedload(Ticket.reporter)).\ options(joinedload(Ticket.assignee)).\ filter(Ticket.id == i) return tk.first() def get_by_key(team_slug, pkey, ticket_key, preload=''): tk = Ticket.query.\ options(joinedload(Ticket.status)).\ options(joinedload(Ticket.reporter)).\ options(joinedload(Ticket.assignee)).\ join(Ticket.project).\ join(Project.team).\ filter(Team.url_slug == team_slug).\ filter(Project.pkey == pkey).\ filter(Ticket.ticket_key == ticket_key) if 'project' in preload.lower(): tk = tk.options(joinedload(Ticket.project)) return tk.first() def from_json(pkey, json): validate(json, ticket_schema) prjct = Project.get_by_key(pkey) r = User.get_by_username_or_id(json.get("reporter", {}).get("username", "")) a = User.get_by_username_or_id(json.get("assignee", {}).get("username", "")) tk = Ticket(summary=json['summary'], description=json['description'], ticket_key=prjct.pkey + "-" + str(len(prjct.tickets) + 1), reporter_id=r.id, project_id=prjct.id) if a != None: tk.assignee_id = a.id return tk def __repr__(self): return '<Ticket %r>' % (self.ticket_key)
class Comment(Base): """A comment on a ticket""" author_id = db.Column(db.Integer, db.ForeignKey('users.id')) ticket_id = db.Column(db.Integer, db.ForeignKey('ticket.id')) body = db.Column(db.Text()) def __init__(self, ticket=None, author=None, *, body): self.ticket = ticket self.author = author self.body = body def to_json(self): s = super().to_json() s.pop('author_id', None) s.pop('ticket_id', None) return s def from_json(json): validate(json, comment_schema) u = User.get_by_username_or_id(j['author']['username']) c = Comment(body=json['body'], author=u) return c def get_all(team_slug, pkey, ticket_key): cmts = Comment.query.\ options(joinedload(Comment.author)).\ join(Comment.ticket).\ join(Ticket.project).\ join(Project.team).\ filter(Ticket.ticket_key == ticket_key).\ filter(Project.pkey == pkey).\ filter(Team.url_slug == team_slug).\ all() return cmts def get_for_ticket(ticket): cmts = Comment.query.\ join(Comment.ticket).\ filter(Comment.ticket_id == ticket.id) return cmts
class Ticket(Base): """A ticket is a unit of work for a project, be it a bug or support ticket.""" ticket_key = db.Column(db.String(100), nullable=False, unique=True) # I mean jesus christ how many digits summary = db.Column(db.String(250), nullable=False) description = db.Column(db.Text()) assignee_id = db.Column(db.Integer, db.ForeignKey('user.id')) reporter_id = db.Column(db.Integer, db.ForeignKey('user.id')) project_id = db.Column(db.Integer, db.ForeignKey('project.id')) def __init__(self, *, ticket_key, summary, description, assignee_id=None, reporter_id=None): self.ticket_key = ticket_key self.summary = summary self.description = description self.assignee_id = assignee_id self.reporter_id = reporter_id def __repr__(self): return "<Ticket %r>" % (self.ticket_key)
class Project(Base): """Project is a container for tickets.""" pkey = db.Column(db.String(6), nullable=False, unique=True) name = db.Column(db.String(250), nullable=False) repo = db.Column(db.String(250)) homepage = db.Column(db.String(250)) project_lead_id = db.Column(db.Integer, db.ForeignKey('user.id')) team_id = db.Column(db.Integer, db.ForeignKey('team.id')) tickets = db.relationship('Ticket', backref='project', lazy='dynamic') members = db.relationship('Membership', backref='project', lazy='dynamic') def __init__(self, *, pkey, name, repo='', homepage=''): self.pkey = pkey.upper() self.name = name self.repo = repo self.homepage = homepage def get_by_key(team_slug, pkey, preload=False): p = Project.query.\ join(Project.team).\ filter(Team.url_slug == team_slug).\ filter(Project.pkey == pkey).\ first() if p == None: raise AppError(status_code=404, message="That project does not exist.") return p def __repr__(self): return "<Project %r>" % (self.pkey)
class Membership(Base): """Membership is used to control access and permissions for a project or team. Permission levels are stored as Integers and there are three permission levels. 0 = User 1 = Contributor 2 = Administrator """ team_id = db.Column(db.Integer, db.ForeignKey('team.id')) project_id = db.Column(db.Integer, db.ForeignKey('project.id')) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) permission_level = db.Column(db.Integer) def __init__(self, perm): self.permission_level = perm def get_project_memberships(team_slug, pkey): m = Membership.query.\ join(Membership.project).\ join(Membership.user).\ join(Project.team).\ filter(Team.url_slug == url_slug).\ filter(Project.pkey == pkey).\ all() if m == None: raise AppError(status_code=404, message="Project or team not found.") return m def __repr__(self): return "<Membership %r %r %r %r>" % ( self.team_id, self.project_id, self.user_id, self.permission_level)
class Project(Base): """Project is a container for tickets.""" pkey = db.Column(db.String(6), nullable=False, unique=True) name = db.Column(db.String(250), nullable=False) repo = db.Column(db.String(250)) homepage = db.Column(db.String(250)) project_lead_id = db.Column(db.Integer, db.ForeignKey('users.id')) team_id = db.Column(db.Integer, db.ForeignKey('team.id')) tickets = db.relationship('Ticket', backref='project') members = db.relationship('Membership', backref='project', lazy='dynamic') def __init__(self, *, pkey, name, repo='', homepage=''): self.pkey = pkey.upper() self.name = name self.repo = repo self.homepage = homepage def get_by_id(i, preload=''): p = Project.query.\ options(joinedload(Project.project_lead)).\ filter(Project.id == i).\ first() return p def get_by_key(team_slug, pkey, preload=''): p = Project.query.\ options(joinedload(Project.project_lead)).\ join(Project.team).\ filter(Team.url_slug == team_slug).\ filter(Project.pkey == pkey).\ first() return p def from_json(team_slug, jsn): validate(jsn, project_schema) t = Team.get_by_slug(team_slug) p = Project(pkey=jsn["pkey"], name=jsn["name"], homepage=jsn.get("homepage", ""), repo=jsn.get("repo", "")) p.team = t return p def to_json(self): return super().to_json(ignoreFields=["updated_at"]) def __repr__(self): return "<Project %r>" % (self.pkey)
class FieldValue(Base): field_id = db.Column('field_id', db.Integer, db.ForeignKey('field.id'), nullable=False) ticket_id = db.Column('ticket_id', db.Integer, db.ForeignKey('ticket.id'), nullable=False) text_value = db.Column(db.Text()) string_value = db.Column(db.String(250)) float_value = db.Column(db.Float) integer_value = db.Column(db.Integer) def validate_value(self): if ((self.field.data_type == DataTypes.INTEGER and type(self.value) is not int) or (self.field.data_type == DataTypes.FLOAT and type(self.value) is not float) or (self.field.data_type == DataTypes.TEXT and type(self.value) is not str) or (self.field.data_type == DataTypes.STRING and type(self.value) is not str)): raise AppError(status_code=400, message='Invalid type for the field: ' + self.name) def set_value(self): self.validate_value() if self.field.data_type == DataTypes.INTEGER: self.integer_value = self.value elif self.field.data_type == DataTypes.FLOAT: self.float_value = self.value elif self.field.data_type == DataTypes.TEXT: self.text_value = self.value elif self.field.data_type == DataTypes.STRING: self.string_value = self.value else: raise AppError(status_code=500, message='Uknown error setting field value') def from_json(jsn): parent_field = Field.query.filter_by(name=jsn.get("name", "")) if parent_field == None: raise AppError(status_code=404, message="No field with that name") fv = FieldValue(name=jsn.get("name"), value=jsn.get("value")) fv.set_value() return fv
import enum from tessera import db from sqlalchemy import CheckConstraint from tessera.models.v1 import Base # This table shows us our workflow, we query our next statuses by getting all # rows with a given status_id and then we can find our previous statuses by # getting all the rows with next_status_id = our id. status_relationships = db.Table( 'status_relationships', db.Column('status_id', db.Integer, db.ForeignKey('status.id'), nullable=False), db.Column('next_status_id', db.Integer, db.ForeignKey('status.id'), nullable=False), db.PrimaryKeyConstraint('status_id', 'next_status_id') ) class Status(Base): name = db.Column(db.String(100), nullable=False) status_type = db.Column(db.Enum("TODO", "IN_PROGRESS", "DONE", name='status_types'), nullable=False) next_statuses = db.relationship('Status', secondary=status_relationships, primaryjoin="Status.id == status_relationships.c.status_id", secondaryjoin="Status.id == status_relationships.c.next_status_id", backref='previous_statuses', lazy='dynamic') tickets = db.relationship('Ticket', backref='status', lazy='dynamic')
class User(Base): """User represents a user of our application.""" __tablename__ = "users" full_name = db.Column(db.String(250), nullable=False) email = db.Column(db.String(128), nullable=False, unique=True) # TODO: Move this into memberships is_admin = db.Column(db.Boolean) username = db.Column(db.String(128), nullable=False, unique=True) password = db.Column(db.String(192), nullable=False) comments_author_of = db.relationship('Comment', backref='author', lazy='dynamic') projects_lead_of = db.relationship('Project', backref='project_lead', lazy='dynamic') teams_lead_of = db.relationship('Team', backref='team_lead', lazy='dynamic') memberships = db.relationship('Membership', backref='user', lazy='dynamic') assigned_tickets = db.relationship( 'Ticket', backref='assignee', lazy='dynamic', primaryjoin='Ticket.assignee_id == User.id') reported_tickets = db.relationship( 'Ticket', backref='reporter', lazy='dynamic', primaryjoin='Ticket.reporter_id == User.id') teams = association_proxy('membership', 'team') projects = association_proxy('membership', 'project') def __init__(self, *, username, email, password, full_name, is_admin=False): self.full_name = full_name self.username = username self.email = email self.is_admin = is_admin self.set_password(password) def get_by_username_or_id(param): u = User.query.\ filter(User.username == param or User.id == param).\ first() return u def from_json(json): validate(json, user_signup_schema) u = User(username=json['username'], password=json['password'], full_name=json['fullName'], email=json['email']) return u def to_json(self): return super().to_json( ignoreFields=["password", "is_admin", "created_at", "updated_at"]) def update(self, json): validate(self, user_schema) self.username = json.get('username', self.username) self.full_name = json.get('fullName', self.full_name) self.email = json.get('email', self.email) if json.get('password', None) != None: self.set_password(json['password']) def set_password(self, pw): self.password = generate_password_hash(pw) def check_password(self, pw): return check_password_hash(self.password, pw) def __repr__(self): return '<User %r>' % (self.username)
from tessera import db from tessera.lib import AppError from tessera.models.v1.base import Base class DataTypes(enum.Enum): INTEGER = "INTEGER" FLOAT = "FLOAT" STRING = "STRING" TEXT = "TEXT" project_field_schema = db.Table( 'project_field_schema', db.Column('field_id', db.Integer, db.ForeignKey('field.id'), nullable=False), db.Column('project_id', db.Integer, db.ForeignKey('project.id'), nullable=False), db.PrimaryKeyConstraint('field_id', 'project_id')) class Field(Base): name = db.Column(db.String(100), nullable=False) data_type = db.Column(db.Enum("INTEGER", "FLOAT", "STRING", "TEXT", name="data_types"),
class User(Base): """User represents a user of our application.""" full_name = db.Column(db.String(250), nullable=False) email = db.Column(db.String(128), nullable=False, unique=True) # TODO: Move this into memberships is_admin = db.Column(db.Boolean) username = db.Column(db.String(128), nullable=False, unique=True) password = db.Column(db.String(192), nullable=False) projects_lead_of = db.relationship('Project', backref='project_lead', lazy='dynamic') teams_lead_of = db.relationship('Team', backref='team_lead', lazy='dynamic') memberships = db.relationship('Membership', backref='user', lazy='dynamic') assigned_tickets = db.relationship( 'Ticket', backref='assignee', lazy='dynamic', primaryjoin="Ticket.assignee_id == User.id") reported_tickets = db.relationship( 'Ticket', backref='reporter', lazy='dynamic', primaryjoin="Ticket.reporter_id == User.id") teams = association_proxy('membership', 'team') projects = association_proxy('membership', 'project') def __init__(self, *, username, email, password, full_name, is_admin=False): self.full_name = full_name self.username = username self.email = email self.is_admin = is_admin self.set_password(password) def get_by_username_or_id(param): try: i = int(param) u = User.query.filter(User.id == i).first() except: u = User.query.filter_by(username=param).first() if u == None: raise AppError(status_code=404, message="User not found.") return u def from_json(json): validate(json, user_schema) u = User(username=json["username"], password=json["password"], full_name=json["fullName"], email=json["email"]) return u def to_json(self): """Extends base class to_json to drop password as well.""" s = super().to_json() s.pop('password', None) return s def update(self, json): self.username = json.get("username", self.username) self.full_name = json.get("fullName", self.full_name) self.email = json.get("email", self.email) if json.get("password", None) != None: self.set_password(json["password"]) def set_password(self, pw): self.password = generate_password_hash(pw) def check_password(self, pw): return check_password_hash(self.password, pw) def __repr__(self): return "<User %r>" % (self.username)