diff --git a/storyboard/api/v1/boards.py b/storyboard/api/v1/boards.py index a5a10266..36541fc2 100644 --- a/storyboard/api/v1/boards.py +++ b/storyboard/api/v1/boards.py @@ -33,6 +33,7 @@ from storyboard.common import exception as exc from storyboard.db.api import boards as boards_api from storyboard.db.api import stories as stories_api from storyboard.db.api import tasks as tasks_api +from storyboard.db.api import teams as teams_api from storyboard.db.api import timeline_events as events_api from storyboard.db.api import users as users_api from storyboard.db.api import worklists as worklists_api @@ -159,11 +160,13 @@ class PermissionsController(rest.RestController): created = boards_api.create_permission(board_id, permission) users = [{user.id: user.full_name} for user in created.users] + teams = [{team.id: team.name} for team in created.teams] events_api.board_permission_created_event(board_id, user_id, created.id, created.codename, - users) + users, + teams) return created else: @@ -193,15 +196,22 @@ class PermissionsController(rest.RestController): % permission['codename']) old_users = {user.id: user.full_name for user in old.users} + old_teams = {team.id: team.name for team in old.teams} if boards_api.editable(board, user_id): updated = boards_api.update_permission(board_id, permission) new_users = {user.id: user.full_name for user in updated.users} + new_teams = {team.id: team.name for team in updated.teams} added = [{id: name} for id, name in six.iteritems(new_users) if id not in old_users] + added.extend([{id: name} for id, name in six.iteritems(new_teams) + if id not in old_teams]) removed = [{id: name} for id, name in six.iteritems(old_users) if id not in new_users] + removed.extend([{id: name} + for id, name in six.iteritems(old_teams) + if id not in new_teams]) if added or removed: events_api.board_permissions_changed_event(board.id, @@ -210,6 +220,7 @@ class PermissionsController(rest.RestController): updated.codename, added, removed) + return updated.codename else: raise exc.NotFound(_("Board %s not found") % board_id) @@ -328,10 +339,18 @@ class BoardsController(rest.RestController): lanes = board_dict.pop('lanes') or [] owners = board_dict.pop('owners') users = board_dict.pop('users') - if not owners: + team_owners = board_dict.pop('team_owners') + team_users = board_dict.pop('team_users') + if not owners and not team_owners: owners = [user_id] + if not owners and team_owners: + owners = [] if not users: users = [] + if not team_owners: + team_owners = [] + if not team_users: + team_users = [] # We can't set due dates when creating boards at the moment. if 'due_dates' in board_dict: @@ -352,29 +371,37 @@ class BoardsController(rest.RestController): edit_permission = { 'name': 'edit_board_%d' % created_board.id, 'codename': 'edit_board', - 'users': owners + 'users': owners, + 'teams': team_owners } move_permission = { 'name': 'move_cards_%d' % created_board.id, 'codename': 'move_cards', - 'users': users + 'users': users, + 'teams': team_users } edit = boards_api.create_permission(created_board.id, edit_permission) move = boards_api.create_permission(created_board.id, move_permission) event_owners = [{id: users_api.user_get(id).full_name} for id in owners] + event_team_owners = [{id: teams_api.team_get(id).name} + for id in owners] event_users = [{id: users_api.user_get(id).full_name} for id in users] + event_team_users = [{id: teams_api.team_get(id).name} + for id in users] events_api.board_permission_created_event(created_board.id, user_id, edit.id, edit.codename, - event_owners) + event_owners, + event_team_owners) events_api.board_permission_created_event(created_board.id, user_id, move.id, move.codename, - event_users) + event_users, + event_team_users) return wmodels.Board.from_db_model(created_board) diff --git a/storyboard/api/v1/wmodels.py b/storyboard/api/v1/wmodels.py index 82e894f7..d6a91156 100644 --- a/storyboard/api/v1/wmodels.py +++ b/storyboard/api/v1/wmodels.py @@ -801,6 +801,12 @@ class Worklist(base.APIBase): users = wtypes.ArrayType(int) """A list of the IDs of the users who can move items in the worklist.""" + team_owners = wtypes.ArrayType(int) + """A list of the IDs of the teams who have full permissions.""" + + team_users = wtypes.ArrayType(int) + """A list of the IDs of the teams who can move items in the worklist.""" + items = wtypes.ArrayType(WorklistItem) """The items in the worklist.""" @@ -857,8 +863,10 @@ class Worklist(base.APIBase): @nodoc def resolve_permissions(self, worklist): - self.owners = worklists_api.get_owners(worklist) - self.users = worklists_api.get_users(worklist) + self.owners = worklists_api.get_user_owners(worklist) + self.users = worklists_api.get_user_users(worklist) + self.team_owners = worklists_api.get_team_owners(worklist) + self.team_users = worklists_api.get_team_users(worklist) @nodoc def resolve_filters(self, worklist): @@ -955,6 +963,12 @@ class Board(base.APIBase): users = wtypes.ArrayType(int) """A list of the IDs of the users who can move cards in the board.""" + team_owners = wtypes.ArrayType(int) + """A list of the IDs of the teams who have full permissions.""" + + team_users = wtypes.ArrayType(int) + """A list of the IDs of the teams who can move cards in the board.""" + @classmethod def sample(cls): return cls( @@ -982,7 +996,9 @@ class Board(base.APIBase): items=[]))], due_dates=[], owners=[1], - users=[]) + users=[], + team_owners=[1, 2], + team_users=[]) @nodoc def resolve_lanes(self, board, story_cache={}, task_cache={}, @@ -1011,5 +1027,7 @@ class Board(base.APIBase): @nodoc def resolve_permissions(self, board): """Resolve the permissions groups of the board.""" - self.owners = boards_api.get_owners(board) - self.users = boards_api.get_users(board) + self.owners = boards_api.get_user_owners(board) + self.users = boards_api.get_user_users(board) + self.team_owners = boards_api.get_team_owners(board) + self.team_users = boards_api.get_team_users(board) diff --git a/storyboard/api/v1/worklists.py b/storyboard/api/v1/worklists.py index 2b7170a4..2024cd4c 100644 --- a/storyboard/api/v1/worklists.py +++ b/storyboard/api/v1/worklists.py @@ -32,6 +32,7 @@ from storyboard.common import decorators from storyboard.common import exception as exc from storyboard.db.api import stories as stories_api from storyboard.db.api import tasks as tasks_api +from storyboard.db.api import teams as teams_api from storyboard.db.api import timeline_events as events_api from storyboard.db.api import users as users_api from storyboard.db.api import worklists as worklists_api @@ -137,11 +138,13 @@ class PermissionsController(rest.RestController): created = worklists_api.create_permission(worklist_id, permission) users = [{user.id: user.full_name} for user in created.users] + teams = [{team.id: team.name} for team in created.teams] events_api.worklist_permission_created_event(worklist_id, user_id, created.id, created.codename, - users) + users, + teams) return created.codename else: @@ -166,6 +169,10 @@ class PermissionsController(rest.RestController): 1, 2, 3 + ], + "teams": [ + 1, + 6 ] } @@ -189,16 +196,23 @@ class PermissionsController(rest.RestController): % permission['codename']) old_users = {user.id: user.full_name for user in old.users} + old_teams = {team.id: team.name for team in old.teams} if worklists_api.editable(worklist, user_id): updated = worklists_api.update_permission( worklist_id, permission) new_users = {user.id: user.full_name for user in updated.users} + new_teams = {team.id: team.name for team in updated.teams} added = [{id: name} for id, name in six.iteritems(new_users) if id not in old_users] + added.extend([{id: name} for id, name in six.iteritems(new_teams) + if id not in old_teams]) removed = [{id: name} for id, name in six.iteritems(old_users) if id not in new_users] + removed.extend([{id: name} + for id, name in six.iteritems(old_teams) + if id not in new_teams]) if added or removed: events_api.worklist_permissions_changed_event( @@ -746,10 +760,18 @@ class WorklistsController(rest.RestController): filters = worklist_dict.pop('filters') or [] owners = worklist_dict.pop('owners') users = worklist_dict.pop('users') - if not owners: + team_owners = worklist_dict.pop('team_owners') + team_users = worklist_dict.pop('team_users') + if not owners and not team_owners: owners = [user_id] + if not owners and team_owners: + owners = [] if not users: users = [] + if not team_owners: + team_owners = [] + if not team_users: + team_users = [] created_worklist = worklists_api.create(worklist_dict) events_api.worklist_created_event(created_worklist.id, @@ -759,12 +781,14 @@ class WorklistsController(rest.RestController): edit_permission = { 'name': 'edit_worklist_%d' % created_worklist.id, 'codename': 'edit_worklist', - 'users': owners + 'users': owners, + 'teams': team_owners } move_permission = { 'name': 'move_items_%d' % created_worklist.id, 'codename': 'move_items', - 'users': users + 'users': users, + 'teams': team_users } edit = worklists_api.create_permission( created_worklist.id, edit_permission) @@ -773,19 +797,24 @@ class WorklistsController(rest.RestController): event_owners = [{id: users_api.user_get(id).full_name} for id in owners] + event_team_owners = [{id: teams_api.team_get(id).name} + for id in owners] event_users = [{id: users_api.user_get(id).full_name} for id in users] - + event_team_users = [{id: teams_api.team_get(id).name} + for id in users] events_api.worklist_permission_created_event(created_worklist.id, user_id, edit.id, edit.codename, - event_owners) + event_owners, + event_team_owners) events_api.worklist_permission_created_event(created_worklist.id, user_id, move.id, move.codename, - event_users) + event_users, + event_team_users) if worklist_dict['automatic']: for filter in filters: diff --git a/storyboard/db/api/base.py b/storyboard/db/api/base.py index d35cb6ac..5d5b0fee 100644 --- a/storyboard/db/api/base.py +++ b/storyboard/db/api/base.py @@ -464,6 +464,13 @@ def filter_private_worklists(query, current_user, hide_lanes=True): models.User.id == current_user ) ), + models.Board.permissions.any( + models.Permission.teams.any( + models.Team.users.any( + models.User.id == current_user + ) + ) + ), models.Board.private == false(), models.Board.id.is_(None) ) @@ -476,6 +483,13 @@ def filter_private_worklists(query, current_user, hide_lanes=True): models.User.id == current_user ) ), + models.Worklist.permissions.any( + models.Permission.teams.any( + models.Team.users.any( + models.User.id == current_user + ) + ) + ), models.Worklist.private == false(), models.Worklist.id.is_(None) ) @@ -519,6 +533,13 @@ def filter_private_boards(query, current_user): models.User.id == current_user ) ), + models.Board.permissions.any( + models.Permission.teams.any( + models.Team.users.any( + models.User.id == current_user + ) + ) + ), models.Board.private == false(), models.Board.id.is_(None) ) diff --git a/storyboard/db/api/boards.py b/storyboard/db/api/boards.py index 06ce07b5..3375a427 100644 --- a/storyboard/db/api/boards.py +++ b/storyboard/db/api/boards.py @@ -18,6 +18,7 @@ from wsme.exc import ClientSideError from storyboard._i18n import _ from storyboard.db.api import base as api_base +from storyboard.db.api import teams as teams_api from storyboard.db.api import users as users_api from storyboard.db import models @@ -212,35 +213,54 @@ def has_card(board, item_type, item_id): return False -def get_owners(board): +def get_user_owners(board): for permission in board.permissions: if permission.codename == 'edit_board': return [user.id for user in permission.users] -def get_users(board): +def get_user_users(board): for permission in board.permissions: if permission.codename == 'move_cards': return [user.id for user in permission.users] +def get_team_owners(board): + for permission in board.permissions: + if permission.codename == 'edit_board': + return [team.id for team in permission.teams] + + +def get_team_users(board): + for permission in board.permissions: + if permission.codename == 'move_cards': + return [team.id for team in permission.teams] + + def get_permissions(board, user_id): user = users_api.user_get(user_id) if user is not None: + valid_permissions = set(user.permissions) + for team in user.teams: + valid_permissions.update(team.permissions) return [permission.codename for permission in board.permissions - if permission in user.permissions] + if permission in valid_permissions] return [] def create_permission(board_id, permission_dict, session=None): board = _board_get(board_id, session=session) users = permission_dict.pop('users') + teams = permission_dict.pop('teams') permission = api_base.entity_create( models.Permission, permission_dict, session=session) board.permissions.append(permission) for user_id in users: user = users_api.user_get(user_id, session=session) user.permissions.append(permission) + for team_id in teams: + team = teams_api.team_get(team_id, session=session) + team.permissions.append(permission) return permission @@ -250,11 +270,14 @@ def update_permission(board_id, permission_dict): for permission in board.permissions: if permission.codename == permission_dict['codename']: id = permission.id - users = permission_dict.pop('users') - permission_dict['users'] = [] - for user_id in users: - user = users_api.user_get(user_id) - permission_dict['users'].append(user) + users = permission_dict.pop('users', None) + teams = permission_dict.pop('teams', None) + if users is not None: + permission_dict['users'] = [users_api.user_get(user_id) + for user_id in users] + if teams is not None: + permission_dict['teams'] = [teams_api.team_get(team_id) + for team_id in teams] if id is None: raise ClientSideError(_("Permission %s does not exist") diff --git a/storyboard/db/api/timeline_events.py b/storyboard/db/api/timeline_events.py index 9ee4713e..2522f1f2 100644 --- a/storyboard/db/api/timeline_events.py +++ b/storyboard/db/api/timeline_events.py @@ -339,13 +339,14 @@ def worklist_details_changed_event(worklist_id, author_id, updated, old, new): def worklist_permission_created_event(worklist_id, author_id, permission_id, - codename, users): + codename, users, teams): event_info = { "worklist_id": worklist_id, "permission_id": permission_id, "author_id": author_id, "codename": codename, - "users": users + "users": users, + "teams": teams } return event_create({ @@ -445,13 +446,14 @@ def board_details_changed_event(board_id, author_id, updated, old, new): def board_permission_created_event(board_id, author_id, permission_id, - codename, users): + codename, users, teams): event_info = { "board_id": board_id, "permission_id": permission_id, "author_id": author_id, "codename": codename, - "users": users + "users": users, + "teams": teams } return event_create({ diff --git a/storyboard/db/api/worklists.py b/storyboard/db/api/worklists.py index d26205bb..1dc35bcf 100644 --- a/storyboard/db/api/worklists.py +++ b/storyboard/db/api/worklists.py @@ -23,6 +23,7 @@ from storyboard.db.api import base as api_base from storyboard.db.api import boards from storyboard.db.api import stories as stories_api from storyboard.db.api import tasks as tasks_api +from storyboard.db.api import teams as teams_api from storyboard.db.api import users as users_api from storyboard.db import models @@ -365,35 +366,54 @@ def is_lane(worklist): return False -def get_owners(worklist): +def get_user_owners(worklist): for permission in worklist.permissions: if permission.codename == 'edit_worklist': return [user.id for user in permission.users] -def get_users(worklist): +def get_user_users(worklist): for permission in worklist.permissions: if permission.codename == 'move_items': return [user.id for user in permission.users] +def get_team_owners(worklist): + for permission in worklist.permissions: + if permission.codename == 'edit_worklist': + return [team.id for team in permission.teams] + + +def get_team_users(worklist): + for permission in worklist.permissions: + if permission.codename == 'move_items': + return [team.id for team in permission.teams] + + def get_permissions(worklist, user_id): user = users_api.user_get(user_id) if user is not None: + valid_permissions = set(user.permissions) + for team in user.teams: + valid_permissions.update(team.permissions) return [permission.codename for permission in worklist.permissions - if permission in user.permissions] + if permission in valid_permissions] return [] def create_permission(worklist_id, permission_dict, session=None): worklist = _worklist_get(worklist_id, session=session) users = permission_dict.pop('users') + teams = permission_dict.pop('teams') permission = api_base.entity_create( models.Permission, permission_dict, session=session) worklist.permissions.append(permission) for user_id in users: user = users_api.user_get(user_id, session=session) user.permissions.append(permission) + for team_id in teams: + team = teams_api.team_get(team_id, session=session) + team.permissions.append(permission) return permission @@ -403,11 +423,14 @@ def update_permission(worklist_id, permission_dict): for permission in worklist.permissions: if permission.codename == permission_dict['codename']: id = permission.id - users = permission_dict.pop('users') - permission_dict['users'] = [] - for user_id in users: - user = users_api.user_get(user_id) - permission_dict['users'].append(user) + users = permission_dict.pop('users', None) + teams = permission_dict.pop('teams', None) + if users is not None: + permission_dict['users'] = [users_api.user_get(user_id) + for user_id in users] + if teams is not None: + permission_dict['teams'] = [teams_api.team_get(team_id) + for team_id in teams] if id is None: raise ClientSideError(_("Permission %s does not exist") diff --git a/storyboard/db/models.py b/storyboard/db/models.py index 15e9c18a..12265ad5 100644 --- a/storyboard/db/models.py +++ b/storyboard/db/models.py @@ -643,7 +643,8 @@ class Board(FullText, ModelBuilder, Base): private = Column(Boolean, default=False) archived = Column(Boolean, default=False) lanes = relationship(BoardWorklist, backref='board') - permissions = relationship("Permission", secondary="board_permissions") + permissions = relationship("Permission", secondary="board_permissions", + backref="boards") _public_fields = ["id", "title", "description", "creator_id", "project_id", "permission_id", "private", "archived"]