Add an API endpoint for boards
Add an API endpoint for managing boards and their contents. Basic CRUD functionality is exposed on `/v1/boards`. Also add a `board_id` parameter to the `/v1/worklists` endpoint to allow easy access to the worklists in a given board. Change-Id: I24c434d2f36650edcdf7005b2b704be08ed4a4c2
This commit is contained in:
parent
8723b48cf7
commit
f7c2d2320c
241
storyboard/api/v1/boards.py
Normal file
241
storyboard/api/v1/boards.py
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
# Copyright (c) 2015 Codethink Limited
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
from pecan import abort
|
||||||
|
from pecan import expose
|
||||||
|
from pecan import request
|
||||||
|
from pecan import response
|
||||||
|
from pecan import rest
|
||||||
|
from pecan.secure import secure
|
||||||
|
from wsme import types as wtypes
|
||||||
|
import wsmeext.pecan as wsme_pecan
|
||||||
|
|
||||||
|
from storyboard.api.auth import authorization_checks as checks
|
||||||
|
from storyboard.api.v1 import wmodels
|
||||||
|
from storyboard.common import decorators
|
||||||
|
from storyboard.common import exception as exc
|
||||||
|
from storyboard.db.api import boards as boards_api
|
||||||
|
from storyboard.db.api import worklists as worklists_api
|
||||||
|
from storyboard.openstack.common.gettextutils import _ # noqa
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
def visible(board, user=None):
|
||||||
|
if not board:
|
||||||
|
return False
|
||||||
|
if user and board.private:
|
||||||
|
# TODO(SotK): Permissions
|
||||||
|
return user == board.creator_id
|
||||||
|
return not board.private
|
||||||
|
|
||||||
|
|
||||||
|
def editable(board, user=None):
|
||||||
|
if not board:
|
||||||
|
return False
|
||||||
|
if not user:
|
||||||
|
return False
|
||||||
|
# TODO(SotK): Permissions
|
||||||
|
return user == board.creator_id
|
||||||
|
|
||||||
|
|
||||||
|
def get_lane(list_id, board):
|
||||||
|
for lane in board['lanes']:
|
||||||
|
if lane.list_id == list_id:
|
||||||
|
return lane
|
||||||
|
|
||||||
|
|
||||||
|
def update_lanes(board_dict, board_id):
|
||||||
|
if 'lanes' not in board_dict:
|
||||||
|
return
|
||||||
|
board = boards_api.get(board_id)
|
||||||
|
new_list_ids = [lane.list_id for lane in board_dict['lanes']]
|
||||||
|
existing_list_ids = [lane.list_id for lane in board.lanes]
|
||||||
|
for lane in board.lanes:
|
||||||
|
if lane.list_id in new_list_ids:
|
||||||
|
new_lane = get_lane(lane.list_id, board_dict)
|
||||||
|
if lane.position != new_lane.position:
|
||||||
|
boards_api.update_lane(
|
||||||
|
board_id, lane, new_lane.as_dict(omit_unset=True))
|
||||||
|
for lane in board_dict['lanes']:
|
||||||
|
if lane.list_id not in existing_list_ids:
|
||||||
|
boards_api.add_lane(board_id, lane.as_dict(omit_unset=True))
|
||||||
|
|
||||||
|
board = boards_api.get(board_id)
|
||||||
|
del board_dict['lanes']
|
||||||
|
|
||||||
|
|
||||||
|
class BoardsController(rest.RestController):
|
||||||
|
"""Manages operations on boards."""
|
||||||
|
|
||||||
|
@decorators.db_exceptions
|
||||||
|
@secure(checks.guest)
|
||||||
|
@wsme_pecan.wsexpose(wmodels.Board, int)
|
||||||
|
def get_one(self, id):
|
||||||
|
"""Retrieve details about one board.
|
||||||
|
|
||||||
|
:param id: The ID of the board.
|
||||||
|
|
||||||
|
"""
|
||||||
|
board = boards_api.get(id)
|
||||||
|
|
||||||
|
user_id = request.current_user_id
|
||||||
|
if visible(board, user_id):
|
||||||
|
board_model = wmodels.Board.from_db_model(board)
|
||||||
|
board_model.lanes = [wmodels.Lane.from_db_model(lane)
|
||||||
|
for lane in board.lanes]
|
||||||
|
return board_model
|
||||||
|
else:
|
||||||
|
raise exc.NotFound(_("Board %s not found") % id)
|
||||||
|
|
||||||
|
@decorators.db_exceptions
|
||||||
|
@secure(checks.guest)
|
||||||
|
@wsme_pecan.wsexpose([wmodels.Board], wtypes.text, int, int,
|
||||||
|
bool, wtypes.text, wtypes.text)
|
||||||
|
def get_all(self, title=None, creator_id=None, project_id=None,
|
||||||
|
archived=False, sort_field='id', sort_dir='asc'):
|
||||||
|
"""Retrieve definitions of all of the boards.
|
||||||
|
|
||||||
|
:param title: A string to filter the title by.
|
||||||
|
:param creator_id: Filter boards by their creator.
|
||||||
|
:param project_id: Filter boards by project ID.
|
||||||
|
:param archived: Filter boards by whether they are archived or not.
|
||||||
|
:param sort_field: The name of the field to sort on.
|
||||||
|
:param sort_dir: Sort direction for results (asc, desc).
|
||||||
|
|
||||||
|
"""
|
||||||
|
boards = boards_api.get_all(title=title,
|
||||||
|
creator_id=creator_id,
|
||||||
|
project_id=project_id,
|
||||||
|
sort_field=sort_field,
|
||||||
|
sort_dir=sort_dir)
|
||||||
|
|
||||||
|
visible_boards = []
|
||||||
|
user_id = request.current_user_id
|
||||||
|
for board in boards:
|
||||||
|
if visible(board, user_id) and board.archived == archived:
|
||||||
|
board_model = wmodels.Board.from_db_model(board)
|
||||||
|
board_model.lanes = [wmodels.Lane.from_db_model(lane)
|
||||||
|
for lane in board.lanes]
|
||||||
|
visible_boards.append(board_model)
|
||||||
|
|
||||||
|
# Apply the query response headers
|
||||||
|
response.headers['X-Total'] = str(len(visible_boards))
|
||||||
|
|
||||||
|
return visible_boards
|
||||||
|
|
||||||
|
@decorators.db_exceptions
|
||||||
|
@secure(checks.authenticated)
|
||||||
|
@wsme_pecan.wsexpose(wmodels.Board, body=wmodels.Board)
|
||||||
|
def post(self, board):
|
||||||
|
"""Create a new board.
|
||||||
|
|
||||||
|
:param board: A board within the request body.
|
||||||
|
|
||||||
|
"""
|
||||||
|
board_dict = board.as_dict()
|
||||||
|
user_id = request.current_user_id
|
||||||
|
|
||||||
|
if board.creator_id and board.creator_id != user_id:
|
||||||
|
abort(400, _("You can't select the creator of a board."))
|
||||||
|
board_dict.update({"creator_id": user_id})
|
||||||
|
lanes = board_dict.pop('lanes')
|
||||||
|
|
||||||
|
created_board = boards_api.create(board_dict)
|
||||||
|
for lane in lanes:
|
||||||
|
boards_api.add_lane(created_board.id, lane.as_dict())
|
||||||
|
|
||||||
|
return wmodels.Board.from_db_model(created_board)
|
||||||
|
|
||||||
|
@decorators.db_exceptions
|
||||||
|
@secure(checks.authenticated)
|
||||||
|
@wsme_pecan.wsexpose(wmodels.Board, int, body=wmodels.Board)
|
||||||
|
def put(self, id, board):
|
||||||
|
"""Modify this board.
|
||||||
|
|
||||||
|
:param id: The ID of the board.
|
||||||
|
:param board: The new board within the request body.
|
||||||
|
|
||||||
|
"""
|
||||||
|
user_id = request.current_user_id
|
||||||
|
if not editable(boards_api.get(id), user_id):
|
||||||
|
raise exc.NotFound(_("Board %s not found") % id)
|
||||||
|
|
||||||
|
board_dict = board.as_dict(omit_unset=True)
|
||||||
|
update_lanes(board_dict, id)
|
||||||
|
boards_api.update(id, board_dict)
|
||||||
|
|
||||||
|
if visible(board, user_id):
|
||||||
|
board_model = wmodels.Board.from_db_model(board)
|
||||||
|
if board.lanes:
|
||||||
|
board_model.lanes = [wmodels.Lane.from_db_model(lane)
|
||||||
|
for lane in board.lanes]
|
||||||
|
return board_model
|
||||||
|
else:
|
||||||
|
raise exc.NotFound(_("Board %s not found") % id)
|
||||||
|
|
||||||
|
@decorators.db_exceptions
|
||||||
|
@secure(checks.authenticated)
|
||||||
|
@wsme_pecan.wsexpose(None, int, status_code=204)
|
||||||
|
def delete(self, id):
|
||||||
|
"""Archive this board.
|
||||||
|
|
||||||
|
:param id: The ID of the board to be archived.
|
||||||
|
|
||||||
|
"""
|
||||||
|
board = boards_api.get(id)
|
||||||
|
user_id = request.current_user_id
|
||||||
|
if not editable(board, user_id):
|
||||||
|
raise exc.NotFound(_("Board %s not found") % id)
|
||||||
|
|
||||||
|
board_dict = wmodels.Board.from_db_model(board).as_dict(
|
||||||
|
omit_unset=True)
|
||||||
|
board_dict['lanes'] = board.lanes
|
||||||
|
board_dict.update({"archived": True})
|
||||||
|
boards_api.update(id, board_dict)
|
||||||
|
|
||||||
|
for lane in board_dict['lanes']:
|
||||||
|
worklist = lane.worklist
|
||||||
|
worklist_dict = wmodels.Worklist.from_db_model(worklist).as_dict(
|
||||||
|
omit_unset=True)
|
||||||
|
worklist_dict.update({'archived': True})
|
||||||
|
worklists_api.update(worklist.id, worklist_dict)
|
||||||
|
|
||||||
|
@decorators.db_exceptions
|
||||||
|
@secure(checks.guest)
|
||||||
|
@wsme_pecan.wsexpose(wtypes.DictType(str, bool), int)
|
||||||
|
def permissions(self, id):
|
||||||
|
"""Get the permissions the current user has for the board.
|
||||||
|
|
||||||
|
:param id: The ID of the board to check permissions for.
|
||||||
|
|
||||||
|
"""
|
||||||
|
board = boards_api.get(id)
|
||||||
|
user_id = request.current_user_id
|
||||||
|
return {
|
||||||
|
'edit_board': editable(board, user_id),
|
||||||
|
'move_cards': editable(board, user_id) # TODO(SotK): check lanes
|
||||||
|
}
|
||||||
|
|
||||||
|
@expose()
|
||||||
|
def _route(self, args, request):
|
||||||
|
if request.method == 'GET' and len(args) > 1:
|
||||||
|
if args[1] == "permissions":
|
||||||
|
# Request to a permissions endpoint
|
||||||
|
return self.permissions, [args[0]]
|
||||||
|
|
||||||
|
return super(BoardsController, self)._route(args, request)
|
@ -14,6 +14,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from storyboard.api.v1.auth import AuthController
|
from storyboard.api.v1.auth import AuthController
|
||||||
|
from storyboard.api.v1.boards import BoardsController
|
||||||
from storyboard.api.v1.branches import BranchesController
|
from storyboard.api.v1.branches import BranchesController
|
||||||
from storyboard.api.v1.milestones import MilestonesController
|
from storyboard.api.v1.milestones import MilestonesController
|
||||||
from storyboard.api.v1.project_groups import ProjectGroupsController
|
from storyboard.api.v1.project_groups import ProjectGroupsController
|
||||||
@ -46,5 +47,6 @@ class V1Controller(object):
|
|||||||
subscription_events = SubscriptionEventsController()
|
subscription_events = SubscriptionEventsController()
|
||||||
systeminfo = SystemInfoController()
|
systeminfo = SystemInfoController()
|
||||||
worklists = WorklistsController()
|
worklists = WorklistsController()
|
||||||
|
boards = BoardsController()
|
||||||
|
|
||||||
openid = AuthController()
|
openid = AuthController()
|
||||||
|
@ -508,3 +508,44 @@ class WorklistItem(base.APIBase):
|
|||||||
|
|
||||||
list_position = int
|
list_position = int
|
||||||
"""The position of this item in the Worklist."""
|
"""The position of this item in the Worklist."""
|
||||||
|
|
||||||
|
|
||||||
|
class Lane(base.APIBase):
|
||||||
|
"""Represents a lane in a kanban board."""
|
||||||
|
|
||||||
|
board_id = int
|
||||||
|
"""The ID of the board containing the lane."""
|
||||||
|
|
||||||
|
list_id = int
|
||||||
|
"""The ID of the worklist which represents the lane."""
|
||||||
|
|
||||||
|
position = int
|
||||||
|
"""The position of the lane in the board."""
|
||||||
|
|
||||||
|
|
||||||
|
class Board(base.APIBase):
|
||||||
|
"""Represents a kanban board made up of worklists."""
|
||||||
|
|
||||||
|
title = wtypes.text
|
||||||
|
"""The title of the board."""
|
||||||
|
|
||||||
|
description = wtypes.text
|
||||||
|
"""The description of the board."""
|
||||||
|
|
||||||
|
creator_id = int
|
||||||
|
"""The ID of the User who created this board."""
|
||||||
|
|
||||||
|
project_id = int
|
||||||
|
"""The ID of the Project this board is associated with."""
|
||||||
|
|
||||||
|
permission_id = int
|
||||||
|
"""The ID of the Permission which defines who can edit this board."""
|
||||||
|
|
||||||
|
private = bool
|
||||||
|
"""A flag to identify whether this is a private or public board."""
|
||||||
|
|
||||||
|
archived = bool
|
||||||
|
"""A flag to identify whether or not the board has been archived."""
|
||||||
|
|
||||||
|
lanes = wtypes.ArrayType(Lane)
|
||||||
|
"""A list containing the representions of the lanes in this board."""
|
||||||
|
@ -33,7 +33,10 @@ from storyboard.openstack.common.gettextutils import _ # noqa
|
|||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
def visible(worklist, user=None):
|
def visible(worklist, user=None, hide_lanes=False):
|
||||||
|
if hide_lanes:
|
||||||
|
if worklists_api.is_lane(worklist):
|
||||||
|
return False
|
||||||
if not worklist:
|
if not worklist:
|
||||||
return False
|
return False
|
||||||
if user and worklist.private:
|
if user and worklist.private:
|
||||||
@ -153,23 +156,29 @@ class WorklistsController(rest.RestController):
|
|||||||
@decorators.db_exceptions
|
@decorators.db_exceptions
|
||||||
@secure(checks.guest)
|
@secure(checks.guest)
|
||||||
@wsme_pecan.wsexpose([wmodels.Worklist], wtypes.text, int, int,
|
@wsme_pecan.wsexpose([wmodels.Worklist], wtypes.text, int, int,
|
||||||
bool, wtypes.text, wtypes.text)
|
bool, bool, wtypes.text, wtypes.text, int)
|
||||||
def get_all(self, title=None, creator_id=None, project_id=None,
|
def get_all(self, title=None, creator_id=None, project_id=None,
|
||||||
archived=False, sort_field='id', sort_dir='asc'):
|
archived=False, hide_lanes=True, sort_field='id',
|
||||||
|
sort_dir='asc', board_id=None):
|
||||||
"""Retrieve definitions of all of the worklists.
|
"""Retrieve definitions of all of the worklists.
|
||||||
|
|
||||||
:param title: A string to filter the title by.
|
:param title: A string to filter the title by.
|
||||||
:param creator_id: Filter worklists by their creator.
|
:param creator_id: Filter worklists by their creator.
|
||||||
:param project_id: Filter worklists by project ID.
|
:param project_id: Filter worklists by project ID.
|
||||||
:param archived: Filter worklists by whether they are archived or not.
|
:param archived: Filter worklists by whether they are archived or not.
|
||||||
|
:param hide_lanes: If true, don't return worklists which are lanes in
|
||||||
|
a board.
|
||||||
:param sort_field: The name of the field to sort on.
|
:param sort_field: The name of the field to sort on.
|
||||||
:param sort_dir: Sort direction for results (asc, desc).
|
:param sort_dir: Sort direction for results (asc, desc).
|
||||||
|
:param board_id: Get all worklists in the board with this id. Other
|
||||||
|
filters are not applied.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
worklists = worklists_api.get_all(title=title,
|
worklists = worklists_api.get_all(title=title,
|
||||||
creator_id=creator_id,
|
creator_id=creator_id,
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
archived=archived,
|
archived=archived,
|
||||||
|
board_id=board_id,
|
||||||
sort_field=sort_field,
|
sort_field=sort_field,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir)
|
||||||
|
|
||||||
@ -177,7 +186,7 @@ class WorklistsController(rest.RestController):
|
|||||||
visible_worklists = [wmodels.Worklist.from_db_model(w)
|
visible_worklists = [wmodels.Worklist.from_db_model(w)
|
||||||
for w in worklists
|
for w in worklists
|
||||||
if w.archived == archived
|
if w.archived == archived
|
||||||
and visible(w, user_id)]
|
and visible(w, user_id, hide_lanes)]
|
||||||
|
|
||||||
# Apply the query response headers
|
# Apply the query response headers
|
||||||
response.headers['X-Total'] = str(len(visible_worklists))
|
response.headers['X-Total'] = str(len(visible_worklists))
|
||||||
@ -218,7 +227,8 @@ class WorklistsController(rest.RestController):
|
|||||||
if not editable(worklists_api.get(id), user_id):
|
if not editable(worklists_api.get(id), user_id):
|
||||||
raise exc.NotFound(_("Worklist %s not found") % id)
|
raise exc.NotFound(_("Worklist %s not found") % id)
|
||||||
|
|
||||||
updated_worklist = worklists_api.update(id, worklist.as_dict())
|
updated_worklist = worklists_api.update(
|
||||||
|
id, worklist.as_dict(omit_unset=True))
|
||||||
|
|
||||||
return wmodels.Worklist.from_db_model(updated_worklist)
|
return wmodels.Worklist.from_db_model(updated_worklist)
|
||||||
|
|
||||||
|
84
storyboard/db/api/boards.py
Normal file
84
storyboard/db/api/boards.py
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
# Copyright (c) 2015 Codethink Limited
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from sqlalchemy.orm import subqueryload
|
||||||
|
from wsme.exc import ClientSideError
|
||||||
|
|
||||||
|
from storyboard.common import exception as exc
|
||||||
|
from storyboard.db.api import base as api_base
|
||||||
|
from storyboard.db import models
|
||||||
|
from storyboard.openstack.common.gettextutils import _ # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def _board_get(id, session=None):
|
||||||
|
if not session:
|
||||||
|
session = api_base.get_session()
|
||||||
|
query = session.query(models.Board).options(
|
||||||
|
subqueryload(models.Board.lanes)).filter_by(id=id)
|
||||||
|
|
||||||
|
return query.first()
|
||||||
|
|
||||||
|
|
||||||
|
def get(id):
|
||||||
|
return _board_get(id)
|
||||||
|
|
||||||
|
|
||||||
|
def get_all(title=None, creator_id=None, project_id=None,
|
||||||
|
sort_field=None, sort_dir=None, **kwargs):
|
||||||
|
return api_base.entity_get_all(models.Board,
|
||||||
|
title=title,
|
||||||
|
creator_id=creator_id,
|
||||||
|
project_id=project_id,
|
||||||
|
sort_field=sort_field,
|
||||||
|
sort_dir=sort_dir,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def create(values):
|
||||||
|
board = api_base.entity_create(models.Board, values)
|
||||||
|
return board
|
||||||
|
|
||||||
|
|
||||||
|
def update(id, values):
|
||||||
|
return api_base.entity_update(models.Board, id, values)
|
||||||
|
|
||||||
|
|
||||||
|
def add_lane(board_id, lane_dict):
|
||||||
|
board = _board_get(board_id)
|
||||||
|
if board is None:
|
||||||
|
raise exc.NotFound(_("Board %s not found") % board_id)
|
||||||
|
|
||||||
|
# Make sure we're adding the lane to the right board
|
||||||
|
lane_dict['board_id'] = board_id
|
||||||
|
|
||||||
|
if lane_dict.get('list_id') is None:
|
||||||
|
raise ClientSideError(_("A lane must have a worklist_id."))
|
||||||
|
|
||||||
|
if lane_dict.get('position') is None:
|
||||||
|
lane_dict['position'] = len(board.lanes)
|
||||||
|
|
||||||
|
api_base.entity_create(models.BoardWorklist, lane_dict)
|
||||||
|
|
||||||
|
return board
|
||||||
|
|
||||||
|
|
||||||
|
def update_lane(board_id, lane, new_lane):
|
||||||
|
# Make sure we aren't messing up the board ID
|
||||||
|
new_lane['board_id'] = board_id
|
||||||
|
|
||||||
|
if new_lane.get('list_id') is None:
|
||||||
|
raise ClientSideError(_("A lane must have a worklist_id."))
|
||||||
|
|
||||||
|
api_base.entity_update(models.BoardWorklist, lane.id, new_lane)
|
@ -18,6 +18,7 @@ from wsme.exc import ClientSideError
|
|||||||
|
|
||||||
from storyboard.common import exception as exc
|
from storyboard.common import exception as exc
|
||||||
from storyboard.db.api import base as api_base
|
from storyboard.db.api import base as api_base
|
||||||
|
from storyboard.db.api import boards
|
||||||
from storyboard.db.api import stories
|
from storyboard.db.api import stories
|
||||||
from storyboard.db.api import tasks
|
from storyboard.db.api import tasks
|
||||||
from storyboard.db import models
|
from storyboard.db import models
|
||||||
@ -38,7 +39,12 @@ def get(worklist_id):
|
|||||||
|
|
||||||
|
|
||||||
def get_all(title=None, creator_id=None, project_id=None,
|
def get_all(title=None, creator_id=None, project_id=None,
|
||||||
sort_field=None, sort_dir=None, **kwargs):
|
board_id=None, sort_field=None, sort_dir=None,
|
||||||
|
**kwargs):
|
||||||
|
if board_id is not None:
|
||||||
|
board = boards.get(board_id)
|
||||||
|
return [lane.worklist for lane in board.lanes]
|
||||||
|
|
||||||
return api_base.entity_get_all(models.Worklist,
|
return api_base.entity_get_all(models.Worklist,
|
||||||
title=title,
|
title=title,
|
||||||
creator_id=creator_id,
|
creator_id=creator_id,
|
||||||
@ -154,3 +160,11 @@ def remove_item(worklist_id, item_id):
|
|||||||
session.delete(item)
|
session.delete(item)
|
||||||
|
|
||||||
return worklist
|
return worklist
|
||||||
|
|
||||||
|
|
||||||
|
def is_lane(worklist):
|
||||||
|
lanes = api_base.entity_get_all(models.BoardWorklist,
|
||||||
|
list_id=worklist.id)
|
||||||
|
if lanes:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
@ -35,7 +35,8 @@ class_mappings = {'task': [models.Task, wmodels.Task],
|
|||||||
'branch': [models.Branch, wmodels.Branch],
|
'branch': [models.Branch, wmodels.Branch],
|
||||||
'milestone': [models.Milestone, wmodels.Milestone],
|
'milestone': [models.Milestone, wmodels.Milestone],
|
||||||
'tag': [models.StoryTag, wmodels.Tag],
|
'tag': [models.StoryTag, wmodels.Tag],
|
||||||
'worklist': [models.Worklist, wmodels.Worklist]}
|
'worklist': [models.Worklist, wmodels.Worklist],
|
||||||
|
'board': [models.Board, wmodels.Board]}
|
||||||
|
|
||||||
|
|
||||||
class NotificationHook(hooks.PecanHook):
|
class NotificationHook(hooks.PecanHook):
|
||||||
@ -155,6 +156,7 @@ class NotificationHook(hooks.PecanHook):
|
|||||||
'systeminfo': 'systeminfo',
|
'systeminfo': 'systeminfo',
|
||||||
'openid': 'openid',
|
'openid': 'openid',
|
||||||
'worklists': 'worklist',
|
'worklists': 'worklist',
|
||||||
|
'boards': 'board',
|
||||||
|
|
||||||
# Second level resources
|
# Second level resources
|
||||||
'comments': 'comment'
|
'comments': 'comment'
|
||||||
|
Loading…
Reference in New Issue
Block a user