예제 #1
0
파일: base.py 프로젝트: with-heart/tessera
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
예제 #2
0
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()
예제 #3
0
파일: field.py 프로젝트: with-heart/tessera
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)
예제 #4
0
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)
예제 #5
0
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)
예제 #6
0
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
예제 #7
0
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)
예제 #8
0
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
예제 #9
0
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)
예제 #10
0
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)
예제 #11
0
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)
예제 #12
0
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)
예제 #13
0
파일: field.py 프로젝트: with-heart/tessera
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
예제 #14
0
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')
예제 #15
0
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)
예제 #16
0
파일: field.py 프로젝트: with-heart/tessera
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"),
예제 #17
0
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)