29428572cc
This commit adds a model and migration for storing links to and metadata about attachments in the StoryBoard database. Change-Id: Ib2e291f5cc80222b226627d076ba3e1f5b88a454 Story: 2000679 Task: 3146
733 lines
25 KiB
Python
733 lines
25 KiB
Python
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
|
# Copyright 2013 Thierry Carrez <thierry@openstack.org>
|
|
# Copyright 2015-2016 Codethink Ltd.
|
|
# Copyright 2019 Adam Coldrick
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
"""
|
|
SQLAlchemy Models for storing storyboard
|
|
"""
|
|
|
|
import datetime
|
|
import pytz
|
|
import six
|
|
import six.moves.urllib.parse as urlparse
|
|
|
|
from oslo_config import cfg
|
|
from oslo_db.sqlalchemy import models
|
|
from sqlalchemy import Boolean
|
|
from sqlalchemy import Column
|
|
from sqlalchemy.dialects.mysql import MEDIUMTEXT
|
|
from sqlalchemy import Enum
|
|
from sqlalchemy.ext import declarative
|
|
from sqlalchemy import ForeignKey
|
|
from sqlalchemy import Integer
|
|
from sqlalchemy.orm.collections import attribute_mapped_collection
|
|
from sqlalchemy.orm import backref, relationship
|
|
from sqlalchemy import schema
|
|
from sqlalchemy import select
|
|
import sqlalchemy.sql.expression as expr
|
|
import sqlalchemy.sql.functions as func
|
|
from sqlalchemy import String
|
|
from sqlalchemy import Table
|
|
from sqlalchemy import Unicode
|
|
from sqlalchemy import UnicodeText
|
|
from sqlalchemy_fulltext import FullText
|
|
|
|
from storyboard.common import event_types
|
|
from storyboard.db.decorators import UTCDateTime
|
|
|
|
CONF = cfg.CONF
|
|
|
|
|
|
def table_args():
|
|
engine_name = urlparse.urlparse(cfg.CONF.database_connection).scheme
|
|
if engine_name == 'mysql':
|
|
return {'mysql_engine': cfg.CONF.mysql_engine,
|
|
'mysql_charset': "utf8mb4"}
|
|
return None
|
|
|
|
# # CUSTOM TYPES
|
|
|
|
# A mysql medium text type.
|
|
MYSQL_MEDIUM_TEXT = UnicodeText().with_variant(MEDIUMTEXT(), 'mysql')
|
|
|
|
|
|
class CommonLength(object):
|
|
top_large_length = 255
|
|
top_middle_length = 100
|
|
top_short_length = 50
|
|
lower_large_length = 5
|
|
lower_middle_length = 3
|
|
lower_short_length = 1
|
|
name_length = 30
|
|
|
|
|
|
class IdMixin(object):
|
|
id = Column(Integer, primary_key=True)
|
|
|
|
|
|
class UTCTimestampMixin(object):
|
|
"""A Database model mixin that automatically manages our creation and
|
|
updating timestamps. This mixin was copied from oslo_db, and adapted to
|
|
use our own internal UTCDateTime type decorator.
|
|
"""
|
|
created_at = Column(UTCDateTime,
|
|
default=lambda: datetime.datetime.now(tz=pytz.utc))
|
|
updated_at = Column(UTCDateTime,
|
|
onupdate=lambda: datetime.datetime.now(tz=pytz.utc))
|
|
|
|
|
|
class StoriesBase(UTCTimestampMixin,
|
|
IdMixin,
|
|
models.ModelBase):
|
|
metadata = None
|
|
|
|
@declarative.declared_attr
|
|
def __tablename__(cls):
|
|
# NOTE(jkoelker) use the pluralized name of the class as the table
|
|
return cls.__name__.lower() + 's'
|
|
|
|
def as_dict(self):
|
|
d = {}
|
|
for c in self.__table__.columns:
|
|
d[c.name] = self[c.name]
|
|
return d
|
|
|
|
|
|
Base = declarative.declarative_base(cls=StoriesBase)
|
|
|
|
|
|
class ModelBuilder(object):
|
|
def __init__(self, **kwargs):
|
|
super(ModelBuilder, self).__init__()
|
|
|
|
if kwargs:
|
|
for key in kwargs:
|
|
if key in self:
|
|
self[key] = kwargs[key]
|
|
|
|
|
|
user_permissions = Table(
|
|
'user_permissions', Base.metadata,
|
|
Column('user_id', Integer, ForeignKey('users.id')),
|
|
Column('permission_id', Integer, ForeignKey('permissions.id')),
|
|
)
|
|
|
|
team_permissions = Table(
|
|
'team_permissions', Base.metadata,
|
|
Column('team_id', Integer, ForeignKey('teams.id')),
|
|
Column('permission_id', Integer, ForeignKey('permissions.id')),
|
|
)
|
|
|
|
team_membership = Table(
|
|
'team_membership', Base.metadata,
|
|
Column('user_id', Integer, ForeignKey('users.id')),
|
|
Column('team_id', Integer, ForeignKey('teams.id')),
|
|
)
|
|
|
|
|
|
class User(FullText, ModelBuilder, Base):
|
|
__table_args__ = (
|
|
schema.UniqueConstraint('email', name='uniq_user_email'),
|
|
)
|
|
|
|
__fulltext_columns__ = ['full_name', 'email']
|
|
|
|
full_name = Column(Unicode(CommonLength.top_large_length), nullable=True)
|
|
email = Column(String(CommonLength.top_middle_length))
|
|
openid = Column(String(CommonLength.top_large_length))
|
|
is_staff = Column(Boolean, default=False)
|
|
is_active = Column(Boolean, default=True)
|
|
is_superuser = Column(Boolean, default=False)
|
|
last_login = Column(UTCDateTime)
|
|
teams = relationship("Team", secondary="team_membership")
|
|
permissions = relationship(
|
|
"Permission", secondary="user_permissions", backref="users")
|
|
enable_login = Column(Boolean, default=True)
|
|
|
|
preferences = relationship("UserPreference",
|
|
collection_class=attribute_mapped_collection(
|
|
'key'
|
|
),
|
|
cascade="all, delete-orphan")
|
|
|
|
_public_fields = ["id", "openid", "full_name", "email", "last_login",
|
|
"enable_login", "is_superuser"]
|
|
|
|
|
|
class UserPreference(ModelBuilder, Base):
|
|
__tablename__ = 'user_preferences'
|
|
|
|
_TASK_TYPES = ('string', 'int', 'bool', 'float')
|
|
|
|
user_id = Column(Integer, ForeignKey('users.id'))
|
|
key = Column(Unicode(CommonLength.top_middle_length))
|
|
value = Column(Unicode(CommonLength.top_large_length))
|
|
type = Column(Enum(*_TASK_TYPES), default='string')
|
|
|
|
@property
|
|
def cast_value(self):
|
|
try:
|
|
cast_func = {
|
|
'float': lambda x: float(x),
|
|
'int': lambda x: int(x),
|
|
'bool': lambda x: bool(x == 'True'),
|
|
'string': lambda x: six.text_type(x)
|
|
}[self.type]
|
|
|
|
return cast_func(self.value)
|
|
except ValueError:
|
|
return self.value
|
|
|
|
@cast_value.setter
|
|
def cast_value(self, value):
|
|
if isinstance(value, bool):
|
|
self.type = 'bool'
|
|
elif isinstance(value, int):
|
|
self.type = 'int'
|
|
elif isinstance(value, float):
|
|
self.type = 'float'
|
|
else:
|
|
self.type = 'string'
|
|
|
|
self.value = six.text_type(value)
|
|
|
|
_public_fields = ["id", "key", "value", "type"]
|
|
|
|
|
|
class Team(ModelBuilder, Base):
|
|
__table_args__ = (
|
|
schema.UniqueConstraint('name', name='uniq_team_name'),
|
|
)
|
|
name = Column(Unicode(CommonLength.top_middle_length))
|
|
security = Column(Boolean, default=False)
|
|
users = relationship("User", secondary="team_membership")
|
|
permissions = relationship("Permission", secondary="team_permissions",
|
|
backref="teams")
|
|
|
|
|
|
project_teams = Table(
|
|
'project_teams', Base.metadata,
|
|
Column('project_id', Integer, ForeignKey('projects.id'), nullable=False),
|
|
Column('team_id', Integer, ForeignKey('teams.id'), nullable=False)
|
|
)
|
|
|
|
|
|
project_group_mapping = Table(
|
|
'project_group_mapping', Base.metadata,
|
|
Column('project_id', Integer, ForeignKey('projects.id')),
|
|
Column('project_group_id', Integer, ForeignKey('project_groups.id')),
|
|
)
|
|
|
|
|
|
may_mutate_to = Table(
|
|
'may_mutate_to', Base.metadata,
|
|
Column('story_type_id_from', Integer, ForeignKey('story_types.id'),
|
|
nullable=False),
|
|
Column('story_type_id_to', Integer, ForeignKey('story_types.id'),
|
|
nullable=False),
|
|
schema.UniqueConstraint('story_type_id_from', 'story_type_id_to')
|
|
)
|
|
|
|
|
|
class StoryType(ModelBuilder, Base):
|
|
__tablename__ = 'story_types'
|
|
|
|
name = Column(String(CommonLength.top_middle_length))
|
|
icon = Column(String(CommonLength.top_middle_length))
|
|
restricted = Column(Boolean, default=False)
|
|
private = Column(Boolean, default=False)
|
|
visible = Column(Boolean, default=True)
|
|
|
|
_public_fields = ["id", "name", "icon", "restricted", "private",
|
|
"visible"]
|
|
|
|
|
|
class Permission(ModelBuilder, Base):
|
|
__table_args__ = (
|
|
schema.UniqueConstraint('name', name='uniq_permission_name'),
|
|
)
|
|
name = Column(Unicode(CommonLength.top_short_length))
|
|
codename = Column(Unicode(CommonLength.top_large_length))
|
|
|
|
|
|
# TODO(mordred): Do we really need name and title?
|
|
class Project(FullText, ModelBuilder, Base):
|
|
"""Represents a software project."""
|
|
|
|
__table_args__ = (
|
|
schema.UniqueConstraint('name', name='uniq_project_name'),
|
|
)
|
|
|
|
__fulltext_columns__ = ['name', 'description']
|
|
|
|
name = Column(String(CommonLength.top_middle_length))
|
|
description = Column(UnicodeText())
|
|
teams = relationship(Team, secondary="project_teams", backref="projects")
|
|
tasks = relationship('Task', backref='project')
|
|
branches = relationship('Branch', backref='project')
|
|
repo_url = Column(String(CommonLength.top_large_length), default=None)
|
|
is_active = Column(Boolean, default=True)
|
|
project_groups = relationship("ProjectGroup",
|
|
secondary="project_group_mapping")
|
|
autocreate_branches = Column(Boolean, default=False)
|
|
|
|
_public_fields = ["id", "name", "description", "tasks", "repo_url",
|
|
"autocreate_branches"]
|
|
|
|
|
|
class ProjectGroup(ModelBuilder, Base):
|
|
__tablename__ = 'project_groups'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint('name', name='uniq_group_name'),
|
|
)
|
|
|
|
name = Column(String(CommonLength.top_middle_length))
|
|
title = Column(Unicode(CommonLength.top_large_length))
|
|
projects = relationship("Project", secondary="project_group_mapping")
|
|
|
|
_public_fields = ["id", "name", "title", "projects"]
|
|
|
|
|
|
story_storytags = Table(
|
|
'story_storytags', Base.metadata,
|
|
Column('story_id', Integer, ForeignKey('stories.id')),
|
|
Column('storytag_id', Integer, ForeignKey('storytags.id')),
|
|
)
|
|
|
|
|
|
class Story(FullText, ModelBuilder, Base):
|
|
__tablename__ = 'stories'
|
|
|
|
__fulltext_columns__ = ['title', 'description']
|
|
|
|
creator_id = Column(Integer, ForeignKey('users.id'))
|
|
story_type_id = Column(Integer, ForeignKey('story_types.id'),
|
|
default=1)
|
|
creator = relationship(User, primaryjoin=creator_id == User.id)
|
|
title = Column(Unicode(CommonLength.top_large_length))
|
|
description = Column(UnicodeText())
|
|
is_bug = Column(Boolean, default=True)
|
|
private = Column(Boolean, default=False)
|
|
security = Column(Boolean, default=False)
|
|
tasks = relationship('Task', backref='story',
|
|
cascade="all, delete-orphan")
|
|
events = relationship('TimeLineEvent', backref='story',
|
|
cascade="all, delete-orphan")
|
|
tags = relationship('StoryTag', secondary='story_storytags')
|
|
permissions = relationship('Permission', secondary='story_permissions')
|
|
|
|
_public_fields = ["id", "creator_id", "title", "description", "is_bug",
|
|
"tasks", "events", "tags"]
|
|
|
|
|
|
class Task(FullText, ModelBuilder, Base):
|
|
__fulltext_columns__ = ['title']
|
|
|
|
TASK_STATUSES = {'todo': 'Todo',
|
|
'merged': 'Merged',
|
|
'invalid': 'Invalid',
|
|
'review': 'Review',
|
|
'inprogress': 'Progress'}
|
|
|
|
_TASK_PRIORITIES = ('low', 'medium', 'high')
|
|
|
|
creator_id = Column(Integer, ForeignKey('users.id'))
|
|
title = Column(Unicode(CommonLength.top_large_length), nullable=True)
|
|
link = Column(UnicodeText())
|
|
status = Column(Enum(*TASK_STATUSES.keys()), default='todo')
|
|
story_id = Column(Integer, ForeignKey('stories.id'))
|
|
project_id = Column(Integer, ForeignKey('projects.id'))
|
|
assignee_id = Column(Integer, ForeignKey('users.id'), nullable=True)
|
|
assignee = relationship('User', foreign_keys=[assignee_id])
|
|
branch_id = Column(Integer, ForeignKey('branches.id'), nullable=True)
|
|
milestone_id = Column(Integer, ForeignKey('milestones.id'), nullable=True)
|
|
priority = Column(Enum(*_TASK_PRIORITIES), default='medium')
|
|
|
|
_public_fields = ["id", "creator_id", "title", "status", "story_id",
|
|
"project_id", "assignee_id", "priority", "branch_id",
|
|
"milestone_id"]
|
|
|
|
|
|
class Attachment(FullText, ModelBuilder, Base):
|
|
__tablename__ = 'attachments'
|
|
__fulltext_columns__ = ['name']
|
|
|
|
name = Column(Unicode(CommonLength.top_large_length), nullable=False)
|
|
link = Column(UnicodeText(), nullable=False)
|
|
creator_id = Column(
|
|
Integer, ForeignKey('users.id'), nullable=False, index=True)
|
|
story_id = Column(
|
|
Integer, ForeignKey('stories.id'), nullable=False, index=True)
|
|
|
|
_public_fields = ["id", "name", "link", "creator_id", "story_id"]
|
|
|
|
|
|
story_permissions = Table(
|
|
'story_permissions', Base.metadata,
|
|
Column('story_id', Integer, ForeignKey('stories.id')),
|
|
Column('permission_id', Integer, ForeignKey('permissions.id')),
|
|
)
|
|
|
|
|
|
class Branch(ModelBuilder, Base):
|
|
__tablename__ = 'branches'
|
|
|
|
__table_args__ = (
|
|
schema.UniqueConstraint('name', 'project_id', name='branch_un_constr'),
|
|
)
|
|
|
|
name = Column(String(CommonLength.top_middle_length))
|
|
tasks = relationship('Task', backref='branch')
|
|
milestones = relationship('Milestone', backref='branch')
|
|
project_id = Column(Integer, ForeignKey('projects.id'))
|
|
expired = Column(Boolean, default=False)
|
|
expiration_date = Column(UTCDateTime, default=None)
|
|
autocreated = Column(Boolean, default=False)
|
|
restricted = Column(Boolean, default=False)
|
|
|
|
_public_fields = ["id", "name", "project_id", "expired",
|
|
"expiration_date", "autocreated"]
|
|
|
|
|
|
class Milestone(ModelBuilder, Base):
|
|
__tablename__ = 'milestones'
|
|
|
|
__table_args__ = (
|
|
schema.UniqueConstraint('name', 'branch_id',
|
|
name='milestone_un_constr'),
|
|
)
|
|
|
|
name = Column(String(CommonLength.top_middle_length))
|
|
tasks = relationship('Task', backref='milestone')
|
|
branch_id = Column(Integer, ForeignKey('branches.id'))
|
|
expired = Column(Boolean, default=False)
|
|
expiration_date = Column(UTCDateTime, default=None)
|
|
|
|
_public_fields = ["id", "name", "branch_id", "expired", "expiration_date"]
|
|
|
|
|
|
class StoryTag(ModelBuilder, Base):
|
|
__tablename__ = 'storytags'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint('name', name='uniq_story_tags_name'),
|
|
)
|
|
name = Column(String(CommonLength.top_short_length))
|
|
stories = relationship('Story', secondary='story_storytags')
|
|
|
|
|
|
# Authorization models
|
|
|
|
class AuthorizationCode(ModelBuilder, Base):
|
|
code = Column(Unicode(CommonLength.top_middle_length), nullable=False)
|
|
state = Column(Unicode(CommonLength.top_middle_length), nullable=False)
|
|
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
|
|
expires_in = Column(Integer, nullable=False, default=300)
|
|
|
|
|
|
class AccessToken(ModelBuilder, Base):
|
|
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
|
|
access_token = Column(Unicode(CommonLength.top_middle_length),
|
|
nullable=False)
|
|
expires_in = Column(Integer, nullable=False)
|
|
expires_at = Column(UTCDateTime, nullable=False)
|
|
refresh_token = relationship("RefreshToken",
|
|
uselist=False,
|
|
cascade="all, delete-orphan",
|
|
backref="access_token",
|
|
passive_updates=False,
|
|
passive_deletes=False)
|
|
|
|
|
|
class RefreshToken(ModelBuilder, Base):
|
|
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
|
|
access_token_id = Column(Integer,
|
|
ForeignKey('accesstokens.id'),
|
|
nullable=False)
|
|
refresh_token = Column(Unicode(CommonLength.top_middle_length),
|
|
nullable=False)
|
|
expires_in = Column(Integer, nullable=False)
|
|
expires_at = Column(UTCDateTime, nullable=False)
|
|
|
|
|
|
def _story_build_summary_query():
|
|
# first create a subquery for task statuses
|
|
select_items = []
|
|
select_items.append(Story)
|
|
select_items.append(
|
|
expr.case(
|
|
[(func.sum(Task.status.in_(
|
|
['todo', 'inprogress', 'review'])) > 0,
|
|
'active'),
|
|
((func.sum(Task.status == 'merged')) > 0, 'merged')],
|
|
else_='invalid'
|
|
).label('status')
|
|
)
|
|
for task_status in Task.TASK_STATUSES:
|
|
select_items.append(expr.cast(
|
|
func.sum(Task.status == task_status), Integer
|
|
).label(task_status))
|
|
select_items.append(expr.null().label('task_statuses'))
|
|
|
|
result = select(select_items, None,
|
|
expr.Join(Story, Task, onclause=Story.id == Task.story_id,
|
|
isouter=True)) \
|
|
.group_by(Story.id) \
|
|
.alias('story_summary')
|
|
|
|
return result
|
|
|
|
|
|
class StorySummary(Base):
|
|
__table__ = _story_build_summary_query()
|
|
tags = relationship('StoryTag', secondary='story_storytags')
|
|
due_dates = relationship('DueDate', secondary='story_due_dates')
|
|
permissions = relationship('Permission', secondary='story_permissions')
|
|
|
|
def as_dict(self):
|
|
d = super(StorySummary, self).as_dict()
|
|
d["tags"] = [t.name for t in self.tags]
|
|
|
|
return d
|
|
|
|
_public_fields = ["id", "creator_id", "title", "description", "is_bug",
|
|
"tasks", "comments", "tags", "status", "due_dates",
|
|
"task_statuses"]
|
|
|
|
|
|
# Time-line models
|
|
|
|
class TimeLineEvent(ModelBuilder, Base):
|
|
__tablename__ = 'events'
|
|
|
|
story_id = Column(Integer, ForeignKey('stories.id'), nullable=True)
|
|
worklist_id = Column(Integer, ForeignKey('worklists.id'), nullable=True)
|
|
board_id = Column(Integer, ForeignKey('boards.id'), nullable=True)
|
|
comment_id = Column(Integer, ForeignKey('comments.id'), nullable=True)
|
|
comment = relationship('Comment', backref='event')
|
|
author_id = Column(Integer, ForeignKey('users.id'), nullable=True)
|
|
|
|
event_type = Column(Enum(*event_types.ALL), nullable=False)
|
|
|
|
# this info field should contain additional fields to describe the event
|
|
# ex. {'old_status': 'Todo', 'new_status': 'In progress'}
|
|
# or {'old_assignee_id': 1, 'new_assignee_id': 42}
|
|
event_info = Column(UnicodeText(), nullable=True)
|
|
|
|
|
|
class Comment(FullText, ModelBuilder, Base):
|
|
__fulltext_columns__ = ['content']
|
|
|
|
id = Column(Integer, primary_key=True)
|
|
content = Column(MYSQL_MEDIUM_TEXT)
|
|
is_active = Column(Boolean, default=True)
|
|
in_reply_to = Column(Integer, ForeignKey('comments.id'))
|
|
parent = relationship('Comment', remote_side=[id], backref='children')
|
|
|
|
|
|
class HistoricalComment(FullText, ModelBuilder, Base):
|
|
__tablename__ = 'comments_history'
|
|
__fulltext_columns__ = ['content']
|
|
|
|
content = Column(MYSQL_MEDIUM_TEXT)
|
|
comment_id = Column(Integer, ForeignKey('comments.id'), nullable=False)
|
|
current = relationship(Comment, backref='history')
|
|
|
|
|
|
# Subscription and notifications
|
|
|
|
class Subscription(ModelBuilder, Base):
|
|
_SUBSCRIPTION_TARGETS = ('task', 'story', 'project', 'project_group',
|
|
'worklist')
|
|
|
|
user_id = Column(Integer, ForeignKey('users.id'))
|
|
target_type = Column(Enum(*_SUBSCRIPTION_TARGETS))
|
|
|
|
# Cant use foreign key here as it depends on the type
|
|
target_id = Column(Integer)
|
|
|
|
_public_fields = ["id", "target_type", "target_id", "user_id"]
|
|
|
|
|
|
class SubscriptionEvents(ModelBuilder, Base):
|
|
__tablename__ = 'subscription_events'
|
|
|
|
subscriber_id = Column(Integer, ForeignKey('users.id'))
|
|
author_id = Column(Integer, ForeignKey('users.id'))
|
|
event_type = Column(Unicode(CommonLength.top_middle_length),
|
|
nullable=False)
|
|
event_info = Column(UnicodeText(), nullable=True)
|
|
|
|
|
|
# Worklists and boards
|
|
|
|
class WorklistItem(ModelBuilder, Base):
|
|
__tablename__ = "worklist_items"
|
|
|
|
_ITEM_TYPES = ("story", "task")
|
|
|
|
list_id = Column(Integer, ForeignKey('worklists.id'), nullable=True)
|
|
list_position = Column(Integer, nullable=False)
|
|
item_type = Column(Enum(*_ITEM_TYPES), nullable=False)
|
|
item_id = Column(Integer, nullable=False)
|
|
display_due_date = Column(Integer,
|
|
ForeignKey('due_dates.id'),
|
|
nullable=True)
|
|
archived = Column(Boolean, default=False)
|
|
list = relationship('Worklist', backref=backref('items', lazy="dynamic"))
|
|
|
|
_public_fields = ["id", "list_id", "list_position", "item_type",
|
|
"item_id"]
|
|
|
|
|
|
class FilterCriterion(FullText, ModelBuilder, Base):
|
|
__tablename__ = "filter_criteria"
|
|
__fulltext_columns__ = ['title']
|
|
|
|
title = Column(Unicode(CommonLength.top_middle_length), nullable=False)
|
|
value = Column(Unicode(CommonLength.top_short_length), nullable=False)
|
|
field = Column(Unicode(CommonLength.top_short_length), nullable=False)
|
|
negative = Column(Boolean, default=False, nullable=False)
|
|
filter_id = Column(Integer, ForeignKey('worklist_filters.id'),
|
|
nullable=False)
|
|
filter = relationship('WorklistFilter', backref='criteria')
|
|
|
|
_public_fields = ["id", "title", "value", "field", "negative",
|
|
"filter_id"]
|
|
|
|
|
|
class WorklistFilter(ModelBuilder, Base):
|
|
__tablename__ = "worklist_filters"
|
|
|
|
type = Column(Unicode(CommonLength.top_short_length), nullable=False)
|
|
list_id = Column(Integer, ForeignKey('worklists.id'), nullable=False)
|
|
|
|
_public_fields = ["id", "list_id", "type"]
|
|
|
|
|
|
class Worklist(FullText, ModelBuilder, Base):
|
|
__tablename__ = "worklists"
|
|
__fulltext_columns__ = ['title']
|
|
|
|
title = Column(Unicode(CommonLength.top_middle_length), nullable=True)
|
|
creator_id = Column(Integer, ForeignKey('users.id'))
|
|
project_id = Column(Integer, ForeignKey('projects.id'))
|
|
private = Column(Boolean, default=False)
|
|
archived = Column(Boolean, default=False)
|
|
automatic = Column(Boolean, default=False)
|
|
filters = relationship(WorklistFilter)
|
|
permissions = relationship("Permission", secondary="worklist_permissions")
|
|
boards = relationship("Board", secondary="board_worklists",
|
|
backref="worklists")
|
|
|
|
_public_fields = ["id", "title", "creator_id", "project_id",
|
|
"permission_id", "private", "archived", "automatic"]
|
|
|
|
|
|
class BoardWorklist(ModelBuilder, Base):
|
|
__tablename__ = 'board_worklists'
|
|
|
|
board_id = Column(Integer, ForeignKey('boards.id'))
|
|
list_id = Column(Integer, ForeignKey('worklists.id'))
|
|
position = Column(Integer)
|
|
worklist = relationship(Worklist)
|
|
|
|
_public_fields = ["id", "board_id", "list_id", "position"]
|
|
|
|
|
|
class Board(FullText, ModelBuilder, Base):
|
|
__tablename__ = "boards"
|
|
__fulltext_columns__ = ['title', 'description']
|
|
|
|
title = Column(Unicode(CommonLength.top_middle_length), nullable=False)
|
|
description = Column(UnicodeText(), nullable=True)
|
|
creator_id = Column(Integer, ForeignKey('users.id'))
|
|
project_id = Column(Integer, ForeignKey('projects.id'))
|
|
private = Column(Boolean, default=False)
|
|
archived = Column(Boolean, default=False)
|
|
lanes = relationship(BoardWorklist, backref='board')
|
|
permissions = relationship("Permission", secondary="board_permissions",
|
|
backref="boards")
|
|
|
|
_public_fields = ["id", "title", "description", "creator_id",
|
|
"project_id", "permission_id", "private", "archived"]
|
|
|
|
board_permissions = Table(
|
|
'board_permissions', Base.metadata,
|
|
Column('board_id', Integer, ForeignKey('boards.id')),
|
|
Column('permission_id', Integer, ForeignKey('permissions.id')),
|
|
)
|
|
|
|
worklist_permissions = Table(
|
|
'worklist_permissions', Base.metadata,
|
|
Column('worklist_id', Integer, ForeignKey('worklists.id')),
|
|
Column('permission_id', Integer, ForeignKey('permissions.id')),
|
|
)
|
|
|
|
|
|
class DueDate(FullText, ModelBuilder, Base):
|
|
__tablename__ = "due_dates"
|
|
__fulltext_columns__ = ['name']
|
|
|
|
name = Column(Unicode(CommonLength.top_middle_length), nullable=True)
|
|
date = Column(UTCDateTime)
|
|
private = Column(Boolean, default=False)
|
|
creator_id = Column(Integer, ForeignKey('users.id'))
|
|
permissions = relationship('Permission', secondary='due_date_permissions')
|
|
tasks = relationship('Task',
|
|
secondary='task_due_dates',
|
|
backref='due_dates',
|
|
lazy="dynamic")
|
|
stories = relationship('Story',
|
|
secondary='story_due_dates',
|
|
backref='due_dates',
|
|
lazy="dynamic")
|
|
boards = relationship('Board',
|
|
secondary='board_due_dates',
|
|
backref='due_dates')
|
|
worklists = relationship('Worklist',
|
|
secondary='worklist_due_dates',
|
|
backref='due_dates')
|
|
|
|
due_date_permissions = Table(
|
|
'due_date_permissions', Base.metadata,
|
|
Column('due_date_id', Integer, ForeignKey('due_dates.id')),
|
|
Column('permission_id', Integer, ForeignKey('permissions.id')),
|
|
)
|
|
|
|
task_due_dates = Table(
|
|
'task_due_dates', Base.metadata,
|
|
Column('task_id', Integer, ForeignKey('tasks.id')),
|
|
Column('due_date_id', Integer, ForeignKey('due_dates.id')),
|
|
)
|
|
|
|
story_due_dates = Table(
|
|
'story_due_dates', Base.metadata,
|
|
Column('story_id', Integer, ForeignKey('stories.id')),
|
|
Column('due_date_id', Integer, ForeignKey('due_dates.id')),
|
|
)
|
|
|
|
board_due_dates = Table(
|
|
'board_due_dates', Base.metadata,
|
|
Column('board_id', Integer, ForeignKey('boards.id')),
|
|
Column('due_date_id', Integer, ForeignKey('due_dates.id')),
|
|
)
|
|
|
|
worklist_due_dates = Table(
|
|
'worklist_due_dates', Base.metadata,
|
|
Column('worklist_id', Integer, ForeignKey('worklists.id')),
|
|
Column('due_date_id', Integer, ForeignKey('due_dates.id')),
|
|
)
|