def story_remove_tag(story_id, tag_name, current_user=None): session = api_base.get_session() with session.begin(subtransactions=True): story = story_get_simple(story_id, session=session, current_user=current_user) if not story: raise exc.NotFound( _("%(name)s %(id)s not found") % { 'name': "Story", 'id': story_id }) if tag_name not in [t.name for t in story.tags]: raise exc.NotFound( _("The Story %(story_id)d has " "no tag %(tag)s") % { 'story_id': story_id, 'tag': tag_name }) tag = [t for t in story.tags if t.name == tag_name][0] story.tags.remove(tag) story.updated_at = datetime.datetime.now(tz=pytz.utc) session.add(story) session.expunge(story)
def put(self, milestone_id, milestone): """Modify this milestone. :param milestone_id: An ID of the milestone. :param milestone: A milestone within the request body. """ milestone_dict = milestone.as_dict(omit_unset=True) if milestone.expiration_date: abort(400, _("Can't change expiration date.")) if "expired" in six.iterkeys(milestone_dict): if milestone_dict["expired"]: milestone_dict["expiration_date"] = datetime.now(tz=pytz.utc) else: milestone_dict["expiration_date"] = None if milestone.branch_id: original_milestone = milestones_api.milestone_get(milestone_id) if not original_milestone: raise exc.NotFound(_("Milestone %s not found") % milestone_id) if milestone.branch_id != original_milestone.branch_id: abort(400, _("You can't associate milestone %s " "with another branch.") % milestone_id) result = milestones_api.milestone_update(milestone_id, milestone_dict) if result: return wmodels.Milestone.from_db_model(result) else: raise exc.NotFound(_("Milestone %s not found") % milestone_id)
def project_group_add_project(project_group_id, project_id): session = api_base.get_session() with session.begin(subtransactions=True): project_group = _entity_get(project_group_id, session) if project_group is None: raise exc.NotFound(_("%(name)s %(id)s not found") % {'name': "Project Group", 'id': project_group_id}) project = projects.project_get(project_id) if project is None: raise exc.NotFound(_("%(name)s %(id)s not found") % {'name': "Project", 'id': project_id}) if project_id in [p.id for p in project_group.projects]: raise ClientSideError(_("The Project %(id)d is already in " "Project Group %(group_id)d") % {'id': project_id, 'group_id': project_group_id}) project_group.projects.append(project) session.add(project_group) return project_group
def _connect(self): """This method connects to RabbitMQ, establishes a channel, declares the storyboard exchange if it doesn't yet exist, and executes any post-connection hooks that an extending class may have registered. """ # If the closing flag is set, just exit. if self._closing: return # If a timer is set, kill it. if self._timer: LOG.debug(_('Clearing timer...')) self._timer.cancel() self._timer = None # Create the connection LOG.info(_LI('Connecting to %s'), self._connection_parameters.host) self._connection = pika.BlockingConnection(self._connection_parameters) # Create a channel LOG.debug(_('Creating a new channel')) self._channel = self._connection.channel() self._channel.confirm_delivery() # Declare the exchange LOG.debug(_('Declaring exchange %s'), self._exchange_name) self._channel.exchange_declare(exchange=self._exchange_name, exchange_type='topic', durable=True, auto_delete=False) # Set the open flag and execute any connection hooks. self._open = True self._execute_open_hooks()
def story_add_tag(story_id, tag_name, current_user=None): session = api_base.get_session() with session.begin(subtransactions=True): # Get a tag or create a new one tag = story_tags.tag_get_by_name(tag_name, session=session) if not tag: tag = story_tags.tag_create({"name": tag_name}) story = story_get_simple( story_id, session=session, current_user=current_user) if not story: raise exc.NotFound(_("%(name)s %(id)s not found") % {'name': "Story", 'id': story_id}) if tag_name in [t.name for t in story.tags]: raise exc.DBDuplicateEntry( _("The Story %(id)d already has a tag %(tag)s") % {'id': story_id, 'tag': tag_name}) story.tags.append(tag) story.updated_at = datetime.datetime.now(tz=pytz.utc) session.add(story) session.expunge(story)
def project_group_delete_project(project_group_id, project_id): session = api_base.get_session() with session.begin(subtransactions=True): project_group = _entity_get(project_group_id, session) if project_group is None: raise exc.NotFound(_("%(name)s %(id)s not found") % {'name': "Project Group", 'id': project_group_id}) project = projects.project_get(project_id) if project is None: raise exc.NotFound(_("%(name)s %(id)s not found") % {'name': "Project", 'id': project_id}) if project_id not in [p.id for p in project_group.projects]: raise ClientSideError(_("The Project %(id)d is not in " "Project Group %(group_id)d") % {'id': project_id, 'group_id': project_group_id}) project_entry = [p for p in project_group.projects if p.id == project_id][0] project_group.projects.remove(project_entry) session.add(project_group) return project_group
def project_group_delete_project(project_group_id, project_id): session = api_base.get_session() with session.begin(subtransactions=True): project_group = _entity_get(project_group_id, session) if project_group is None: raise exc.NotFound( _("%(name)s %(id)s not found") % { 'name': "Project Group", 'id': project_group_id }) project = projects.project_get(project_id) if project is None: raise exc.NotFound( _("%(name)s %(id)s not found") % { 'name': "Project", 'id': project_id }) if project_id not in [p.id for p in project_group.projects]: raise ClientSideError( _("The Project %(id)d is not in " "Project Group %(group_id)d") % { 'id': project_id, 'group_id': project_group_id }) project_entry = [ p for p in project_group.projects if p.id == project_id ][0] project_group.projects.remove(project_entry) session.add(project_group) return project_group
def team_delete_user(team_id, user_id): session = api_base.get_session() with session.begin(subtransactions=True): team = _entity_get(team_id, session) if team is None: raise exc.NotFound(_("Team %s not found") % team_id) user = users.user_get(user_id) if user is None: raise exc.NotFound(_("User %s not found") % user_id) if user_id not in [u.id for u in team.users]: raise ClientSideError( _("The User %(user_id)d is not in " "Team %(team_id)d") % { 'user_id': user_id, 'team_id': team_id }) user_entry = [u for u in team.users if u.id == user_id][0] team.users.remove(user_entry) session.add(team) return team
def story_add_tag(story_id, tag_name, current_user=None): session = api_base.get_session() with session.begin(subtransactions=True): # Get a tag or create a new one tag = story_tags.tag_get_by_name(tag_name, session=session) if not tag: tag = story_tags.tag_create({"name": tag_name}) story = story_get_simple(story_id, session=session, current_user=current_user) if not story: raise exc.NotFound( _("%(name)s %(id)s not found") % { 'name': "Story", 'id': story_id }) if tag_name in [t.name for t in story.tags]: raise exc.DBDuplicateEntry( _("The Story %(id)d already has a tag %(tag)s") % { 'id': story_id, 'tag': tag_name }) story.tags.append(tag) story.updated_at = datetime.datetime.now(tz=pytz.utc) session.add(story) session.expunge(story)
def delete(self, story_id, task_id): """Delete this task. Example:: curl 'https://my.example.org/api/v1/stories/11/tasks/28' -X DELETE \\ -H 'Authorization: Bearer MY_ACCESS_TOKEN' :param story_id: An ID of the story. :param task_id: An ID of the task. """ original_task = copy.deepcopy( tasks_api.task_get(task_id, current_user=request.current_user_id)) if not original_task: raise exc.NotFound(_("Task %s not found.") % task_id) if original_task.story_id != story_id: abort(400, _("URL story_id and task.story_id do not match")) events_api.task_deleted_event( story_id=original_task.story_id, task_id=original_task.id, task_title=original_task.title, author_id=request.current_user_id) tasks_api.task_delete(task_id)
def project_group_add_project(project_group_id, project_id): session = api_base.get_session() with session.begin(subtransactions=True): project_group = _entity_get(project_group_id, session) if project_group is None: raise exc.NotFound( _("%(name)s %(id)s not found") % { 'name': "Project Group", 'id': project_group_id }) project = projects.project_get(project_id) if project is None: raise exc.NotFound( _("%(name)s %(id)s not found") % { 'name': "Project", 'id': project_id }) if project_id in [p.id for p in project_group.projects]: raise ClientSideError( _("The Project %(id)d is already in " "Project Group %(group_id)d") % { 'id': project_id, 'group_id': project_group_id }) project_group.projects.append(project) session.add(project_group) return project_group
def post(self, story): """Create a new story. Example:: curl 'https://my.example.org/api/v1/stories' \\ -H 'Authorization: Bearer MY_ACCESS_TOKEN' \\ -H 'Content-Type: application/json;charset=UTF-8' \\ --data-binary '{"title":"Test Story","description":"A test story."}' :param story: A story within the request body. """ # Reject private story types while ACL is not created. if (story.story_type_id and (story.story_type_id == 3 or story.story_type_id == 4)): abort(400, _("Now you can't add story with type %s.") % story.story_type_id) story_dict = story.as_dict() user_id = request.current_user_id if story.creator_id and story.creator_id != user_id: abort(400, _("You can't select author of story.")) story_dict.update({"creator_id": user_id}) if not stories_api.story_can_create_story(story.story_type_id): abort(400, _("Can't create story of this type.")) if "tags" not in story_dict or not story_dict["tags"]: story_dict["tags"] = [] # We can't set due dates when creating stories at the moment. if "due_dates" in story_dict: del story_dict['due_dates'] users = None teams = None # We make sure that a user cannot remove all users and teams # from the permissions list for a story # This should be reworked so that users can be removed if there # are teams, and vice versa if "teams" in story_dict: teams = story_dict.pop("teams") if teams is None: teams = [] if "users" in story_dict: users = story_dict.pop("users") if users is None or (users == [] and teams == []): users = [wmodels.User.from_db_model(users_api.user_get(user_id))] created_story = stories_api.story_create(story_dict) events_api.story_created_event(created_story.id, user_id, story.title) if story.private: stories_api.create_permission(created_story, users, teams) return wmodels.Story.from_db_model(created_story)
def put(self, worklist_id, permission): """Update a permission of the worklist. Example:: TODO This takes a dict in the form:: { "codename": "my-permission", "users": [ 1, 2, 3 ] } The given codename must match an existing permission's codename. :param worklist_id: The ID of the worklist. :param permission: The new contents of the permission. """ user_id = request.current_user_id worklist = worklists_api.get(worklist_id) old = None for perm in worklist.permissions: if perm.codename == permission['codename']: old = perm if old is None: raise exc.NotFound( _("Permission with codename %s not found") % permission['codename']) old_users = {user.id: user.full_name for user in old.users} 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} added = [{ id: name } for id, name in six.iteritems(new_users) if id not in old_users] removed = [{ id: name } for id, name in six.iteritems(old_users) if id not in new_users] if added or removed: events_api.worklist_permissions_changed_event( worklist_id, user_id, updated.id, updated.codename, added, removed) return updated.codename else: raise exc.NotFound(_("Worklist %s not found") % worklist_id)
def add_item(worklist_id, item_id, item_type, list_position, current_user=None): worklist = _worklist_get(worklist_id) if worklist is None: raise exc.NotFound(_("Worklist %s not found") % worklist_id) # Check if this item has an archived card in this worklist to restore archived = get_item_by_item_id( worklist, item_type, item_id, archived=True) if archived: update = { 'archived': False, 'list_position': list_position } api_base.entity_update(models.WorklistItem, archived.id, update) return worklist # If this worklist is a lane, check if the item has an archived card # somewhere in the board to restore if is_lane(worklist): board = boards.get_from_lane(worklist) archived = boards.get_card(board, item_type, item_id, archived=True) if archived: update = { 'archived': False, 'list_id': worklist_id, 'list_position': list_position } api_base.entity_update(models.WorklistItem, archived.id, update) return worklist # Create a new card if item_type == 'story': item = stories_api.story_get(item_id, current_user=current_user) elif item_type == 'task': item = tasks_api.task_get(item_id, current_user=current_user) else: raise ClientSideError(_("An item in a worklist must be either a " "story or a task")) if item is None: raise exc.NotFound(_("%(type)s %(id)s not found") % {'type': item_type, 'id': item_id}) item_dict = { 'list_id': worklist_id, 'item_id': item_id, 'item_type': item_type, 'list_position': list_position } worklist_item = api_base.entity_create(models.WorklistItem, item_dict) if worklist.items is None: worklist.items = [worklist_item] else: worklist.items.append(worklist_item) return worklist
def _publish(self, payload): """Publishes a payload to the passed exchange. If it encounters a failure, will store the payload for later. :param Payload payload: The payload to send. """ LOG.debug( _("Sending message to %(name)s [%(topic)s]") % { 'name': self._exchange_name, 'topic': payload.topic }) # First check, are we closing? if self._closing: LOG.warning(_LW("Cannot send message, publisher is closing.")) if payload not in self._pending: self._pending.append(payload) return # Second check, are we open? if not self._open: LOG.debug(_("Cannot send message, publisher is connecting.")) if payload not in self._pending: self._pending.append(payload) self._reconnect() return # Third check, are we in a sane state? This should never happen, # but just in case... if not self._connection or not self._channel: LOG.error( _LE("Cannot send message, publisher is " "an unexpected state.")) if payload not in self._pending: self._pending.append(payload) self._reconnect() return # Try to send a message. If we fail, schedule a reconnect and store # the message. try: self._channel.basic_publish( self._exchange_name, payload.topic, json.dumps(payload.payload, ensure_ascii=False), self._properties) if payload in self._pending: self._pending.remove(payload) return True except (ConnectionClosed, AttributeError) as cc: LOG.warning(_LW("Attempted to send message on closed connection.")) LOG.debug(cc) self._open = False if payload not in self._pending: self._pending.append(payload) self._reconnect() return False
def project_group_delete(project_group_id): project_group = project_group_get(project_group_id) if not project_group: raise exc.NotFound(_('Project group not found.')) if len(project_group.projects) > 0: raise exc.NotEmpty(_('Project group must be empty.')) api_base.entity_hard_delete(models.ProjectGroup, project_group_id)
def team_delete(team_id): team = team_get(team_id) if not team: raise exc.NotFound(_('Team not found.')) if len(team.users) > 0: raise exc.NotEmpty(_('Team must be empty.')) api_base.entity_hard_delete(models.Team, team_id)
def _publish(self, payload): """Publishes a payload to the passed exchange. If it encounters a failure, will store the payload for later. :param Payload payload: The payload to send. """ LOG.debug(_("Sending message to %(name)s [%(topic)s]") % {'name': self._exchange_name, 'topic': payload.topic}) # First check, are we closing? if self._closing: LOG.warning(_LW("Cannot send message, publisher is closing.")) if payload not in self._pending: self._pending.append(payload) return # Second check, are we open? if not self._open: LOG.debug(_("Cannot send message, publisher is connecting.")) if payload not in self._pending: self._pending.append(payload) self._reconnect() return # Third check, are we in a sane state? This should never happen, # but just in case... if not self._connection or not self._channel: LOG.error(_LE("Cannot send message, publisher is " "an unexpected state.")) if payload not in self._pending: self._pending.append(payload) self._reconnect() return # Try to send a message. If we fail, schedule a reconnect and store # the message. try: self._channel.basic_publish(self._exchange_name, payload.topic, json.dumps(payload.payload, ensure_ascii=False), self._properties) if payload in self._pending: self._pending.remove(payload) return True except (ConnectionClosed, AttributeError) as cc: LOG.warning(_LW("Attempted to send message on closed connection.")) LOG.debug(cc) self._open = False if payload not in self._pending: self._pending.append(payload) self._reconnect() return False
def task_is_valid_put(task, original_task): """Check that task can be update. """ # Check that creator_id of task can't be changed. if task.creator_id and task.creator_id != original_task.creator_id: abort(400, _("You can't change author of task.")) # Set project_id if it isn't in request. if not task.project_id: task.project_id = original_task.project_id # Set branch_id if it isn't in request. if not task.branch_id: task.branch_id = original_task.branch_id # Check that branch is valid for this task. If project_id was changed, # task will be associated with master branch of this project, because # client doesn't support branches. if task.project_id == original_task.project_id: branch_is_valid(task) else: task.branch_id = branches_api.branch_get_master_branch( task.project_id ).id # Check that task ready to associate with milestone if milestone_id in # request. if task.milestone_id: if original_task.status != 'merged' and task.status != 'merged': abort(400, _("Milestones can only be associated with merged tasks")) if (original_task.status == 'merged' and task.status and task.status != 'merged'): abort(400, _("Milestones can only be associated with merged tasks")) elif 'milestone_id' in task.as_dict(omit_unset=True): return task # Set milestone id if task status isn't 'merged' or if original task # has milestone_id. if task.status and task.status != 'merged': task.milestone_id = None elif not task.milestone_id and original_task.milestone_id: task.milestone_id = original_task.milestone_id # Check that milestone is valid for this task. if task.milestone_id: milestone_is_valid(task) return task
def post(self, subscription): """Create a new subscription. Note: target_id is the same value as the story_id of a story. Example:: curl https://my.example.org/api/v1/subscriptions \\ -H 'Authorization: Bearer MY_ACCESS_TOKEN' \\ -H 'Content-Type: application/json;charset=UTF-8' \\ --data-binary '{"target_type":"story","target_id":8}' :param subscription: A subscription within the request body. """ # Data sanity check - are all fields set? if not subscription.target_type or not subscription.target_id: abort( 400, _('You are missing either the target_type or the' ' target_id')) # Sanity check on user_id current_user = user_api.user_get(request.current_user_id) if not subscription.user_id: subscription.user_id = request.current_user_id elif subscription.user_id != request.current_user_id \ and not current_user.is_superuser: abort(403, _("You can only subscribe to resources on your own.")) # Data sanity check: The resource must exist. resource = subscription_api.subscription_get_resource( target_type=subscription.target_type, target_id=subscription.target_id, current_user=request.current_user_id) if not resource: abort(400, _('You cannot subscribe to a nonexistent resource.')) # Data sanity check: The subscription cannot be duplicated for this # user. existing = subscription_api.subscription_get_all( target_type=[ subscription.target_type, ], target_id=subscription.target_id, user_id=subscription.user_id) if existing: abort(409, _('You are already subscribed to this resource.')) result = subscription_api.subscription_create(subscription.as_dict()) return Subscription.from_db_model(result)
def put(self, id, worklist): """Modify this worklist. Example:: TODO :param id: The ID of the worklist. :param worklist: A worklist within the request body. """ user_id = request.current_user_id if not worklists_api.editable(worklists_api.get(id), user_id): raise exc.NotFound(_("Worklist %s not found") % id) story_cache = { story.id: story for story in stories_api.story_get_all(worklist_id=id, current_user=user_id) } task_cache = { task.id: task for task in tasks_api.task_get_all(worklist_id=id, current_user=user_id) } # We don't use this endpoint to update the worklist's contents if worklist.items != wtypes.Unset: del worklist.items # We don't use this endpoint to update the worklist's filters either if worklist.filters != wtypes.Unset: del worklist.filters worklist_dict = worklist.as_dict(omit_unset=True) original = copy.deepcopy(worklists_api.get(id)) updated_worklist = worklists_api.update(id, worklist_dict) post_timeline_events(original, updated_worklist) if worklists_api.visible(updated_worklist, user_id): worklist_model = wmodels.Worklist.from_db_model(updated_worklist) worklist_model.resolve_items(updated_worklist, story_cache, task_cache) worklist_model.resolve_permissions(updated_worklist) return worklist_model else: raise exc.NotFound(_("Worklist %s not found"))
def get(self, worklist_id): """Get items inside a worklist. Example:: curl https://my.example.org/api/v1/worklists/49/items :param worklist_id: The ID of the worklist. """ worklist = worklists_api.get(worklist_id) user_id = request.current_user_id if not worklist or not worklists_api.visible(worklist, user_id): raise exc.NotFound(_("Worklist %s not found") % worklist_id) if worklist.automatic: return [ wmodels.WorklistItem(**item) for item in worklists_api.filter_items(worklist) ] if worklist.items is None: return [] worklist.items.order_by(models.WorklistItem.list_position) visible_items = worklists_api.get_visible_items( worklist, current_user=request.current_user_id) return [ wmodels.WorklistItem.from_db_model(item) for item in visible_items ]
def post(self, worklist_id, filter): """Create a new filter for the worklist. Example:: TODO :param worklist_id: The ID of the worklist to set the filter on. :param filter: The filter to set. """ worklist = worklists_api.get(worklist_id) user_id = request.current_user_id if not worklists_api.editable(worklist, user_id): raise exc.NotFound(_("Worklist %s not found") % worklist_id) created = worklists_api.create_filter(worklist_id, filter.as_dict()) added = serialize_filter(created) events_api.worklist_filters_changed_event(worklist_id, user_id, added=added) model = wmodels.WorklistFilter.from_db_model(created) model.resolve_criteria(created) return model
def put(self, worklist_id, filter_id, filter): """Update a filter on the worklist. Example:: TODO :param worklist_id: The ID of the worklist. :param filter_id: The ID of the filter to be updated. :param filter: The new contents of the filter. """ worklist = worklists_api.get(worklist_id) user_id = request.current_user_id if not worklists_api.editable(worklist, user_id): raise exc.NotFound(_("Worklist %s not found") % worklist_id) old = serialize_filter(worklists_api.get_filter(filter_id)) update_dict = filter.as_dict(omit_unset=True) updated = worklists_api.update_filter(filter_id, update_dict) changes = {"old": old, "new": serialize_filter(updated)} events_api.worklist_filters_changed_event(worklist_id, user_id, updated=changes) updated_model = wmodels.WorklistFilter.from_db_model(updated) updated_model.resolve_criteria(updated) return updated_model
def post(self, worklist_id, permission): """Add a new permission to the worklist. Example:: TODO :param worklist_id: The ID of the worklist. :param permission: The dict to use to create the permission. """ user_id = request.current_user_id if worklists_api.editable(worklists_api.get(worklist_id), user_id): created = worklists_api.create_permission(worklist_id, permission) users = [{user.id: user.full_name} for user in created.users] events_api.worklist_permission_created_event(worklist_id, user_id, created.id, created.codename, users) return created.codename else: raise exc.NotFound(_("Worklist %s not found") % worklist_id)
def paginate_query(query, model, limit, sort_key, marker=None, offset=None, sort_dir=None, sort_dirs=None): if offset is not None: # If we are doing offset-based pagination, don't set a # limit or a marker. # FIXME: Eventually the webclient should be smart enough # to do marker-based pagination, at which point this will # be unnecessary. start, end = (offset, offset + limit) limit, marker = (None, None) try: sorted_query = utils_paginate_query(query=query, model=model, limit=limit, sort_keys=[sort_key], marker=marker, sort_dir=sort_dir, sort_dirs=sort_dirs) if offset is not None: return sorted_query.slice(start, end) return sorted_query except ValueError as ve: raise exc.DBValueError(message=str(ve)) except InvalidSortKey: raise exc.DBInvalidSortKey(_("Invalid sort_field [%s]") % sort_key)
def entity_hard_delete(kls, entity_id, session=None): if not session: session = get_session() try: with session.begin(subtransactions=True): query = model_query(kls, session) entity = query.filter_by(id=entity_id).first() if entity is None: raise exc.NotFound(_("%(name)s %(id)s not found") % {'name': kls.__name__, 'id': entity_id}) session.delete(entity) except db_exc.DBReferenceError as re: raise exc.DBReferenceError(object_name=kls.__name__, value=re.constraint, key=re.key) except db_exc.DBConnectionError: raise exc.DBConnectionError() except db_exc.ColumnError: raise exc.ColumnError() except db_exc.DBDeadlock: raise exc.DBDeadLock() except db_exc.DBInvalidUnicodeParameter: raise exc.DBInvalidUnicodeParameter()
def get_one(self, worklist_id): """Retrieve details about one worklist. Example:: curl https://my.example.org/api/v1/worklists/27 :param worklist_id: The ID of the worklist. """ worklist = worklists_api.get(worklist_id) user_id = request.current_user_id story_cache = {story.id: story for story in stories_api.story_get_all( worklist_id=worklist_id, current_user=user_id)} task_cache = {task.id: task for task in tasks_api.task_get_all( worklist_id=worklist_id, current_user=user_id)} if worklist and worklists_api.visible(worklist, user_id): worklist_model = wmodels.Worklist.from_db_model(worklist) worklist_model.resolve_items(worklist, story_cache, task_cache) worklist_model.resolve_permissions(worklist) worklist_model.resolve_filters(worklist) return worklist_model else: raise exc.NotFound(_("Worklist %s not found") % worklist_id)
def get_one(self, worklist_id): """Retrieve details about one worklist. Example:: curl https://my.example.org/api/v1/worklists/27 :param worklist_id: The ID of the worklist. """ worklist = worklists_api.get(worklist_id) user_id = request.current_user_id story_cache = { story.id: story for story in stories_api.story_get_all(worklist_id=worklist_id, current_user=user_id) } task_cache = { task.id: task for task in tasks_api.task_get_all(worklist_id=worklist_id, current_user=user_id) } if worklist and worklists_api.visible(worklist, user_id): worklist_model = wmodels.Worklist.from_db_model(worklist) worklist_model.resolve_items(worklist, story_cache, task_cache) worklist_model.resolve_permissions(worklist) worklist_model.resolve_filters(worklist) return worklist_model else: raise exc.NotFound(_("Worklist %s not found") % worklist_id)
def get(self, worklist_id): """Get items inside a worklist. Example:: curl https://my.example.org/api/v1/worklists/49/items :param worklist_id: The ID of the worklist. """ worklist = worklists_api.get(worklist_id) user_id = request.current_user_id if not worklist or not worklists_api.visible(worklist, user_id): raise exc.NotFound(_("Worklist %s not found") % worklist_id) if worklist.automatic: return [wmodels.WorklistItem(**item) for item in worklists_api.filter_items(worklist)] if worklist.items is None: return [] worklist.items.order_by(models.WorklistItem.list_position) visible_items = worklists_api.get_visible_items( worklist, current_user=request.current_user_id) return [ wmodels.WorklistItem.from_db_model(item) for item in visible_items ]
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 boards_api.editable(board, user_id): raise exc.NotFound(_("Board %s not found") % id) # We use copy here because we only need to check changes # to the related objects, just the board's own attributes. # Also, deepcopy trips up on the lanes' backrefs. original = copy.copy(board) updated = boards_api.update(id, {"archived": True}) post_timeline_events(original, updated) for lane in board.lanes: original = copy.deepcopy(worklists_api.get(lane.worklist.id)) worklists_api.update(lane.worklist.id, {"archived": True}) if not original.archived: events_api.worklist_details_changed_event( lane.worklist.id, user_id, 'archived', original.archived, True)
def put(self, worklist_id, filter_id, filter): """Update a filter on the worklist. Example:: TODO :param worklist_id: The ID of the worklist. :param filter_id: The ID of the filter to be updated. :param filter: The new contents of the filter. """ worklist = worklists_api.get(worklist_id) user_id = request.current_user_id if not worklists_api.editable(worklist, user_id): raise exc.NotFound(_("Worklist %s not found") % worklist_id) old = serialize_filter(worklists_api.get_filter(filter_id)) update_dict = filter.as_dict(omit_unset=True) updated = worklists_api.update_filter(filter_id, update_dict) changes = { "old": old, "new": serialize_filter(updated) } events_api.worklist_filters_changed_event(worklist_id, user_id, updated=changes) updated_model = wmodels.WorklistFilter.from_db_model(updated) updated_model.resolve_criteria(updated) return updated_model
def get_all(self, story_id=None, event_type=None, marker=None, offset=None, limit=None, sort_field=None, sort_dir=None): """Retrieve all events that have happened under specified story. Example:: curl https://my.example.org/api/v1/stories/11/events :param story_id: Filter events by story ID. :param event_type: A selection of event types to get. :param marker: The resource id where the page should begin. :param offset: The offset to start the page at. :param limit: The number of events to retrieve. :param sort_field: The name of the field to sort on. :param sort_dir: Sort direction for results (asc, desc). """ current_user = request.current_user_id # Boundary check on limit. if limit is not None: limit = max(0, limit) # Sanity check on event types. if event_type: for r_type in event_type: if r_type not in event_types.ALL: msg = _('Invalid event_type requested. Event type must be ' 'one of the following: %s') msg = msg % (', '.join(event_types.ALL),) abort(400, msg) # Resolve the marker record. marker_event = None if marker is not None: marker_event = events_api.event_get(marker) event_count = events_api.events_get_count(story_id=story_id, event_type=event_type, current_user=current_user) events = events_api.events_get_all(story_id=story_id, event_type=event_type, marker=marker_event, offset=offset, limit=limit, sort_field=sort_field, sort_dir=sort_dir, current_user=current_user) # Apply the query response headers. if limit: response.headers['X-Limit'] = str(limit) response.headers['X-Total'] = str(event_count) if marker_event: response.headers['X-Marker'] = str(marker_event.id) if offset is not None: response.headers['X-Offset'] = str(offset) return [wmodels.TimeLineEvent.resolve_event_values( wmodels.TimeLineEvent.from_db_model(event)) for event in events]
def put(self, task_id, task): """Modify this task. Example:: curl https://my.example.org/api/v1/tasks -X PUT \\ -H 'Authorization: Bearer MY_ACCESS_TOKEN' \\ -H 'Content-Type: application/json;charset=UTF-8' \\ --data-binary '{"task_id":27,"status":"merged"}' :param task_id: An ID of the task. :param task: A task within the request body. """ original_task = copy.deepcopy( tasks_api.task_get(task_id, current_user=request.current_user_id)) if not original_task: raise exc.NotFound(_("Task %s not found.") % task_id) task = task_is_valid_put(task, original_task) updated_task = tasks_api.task_update(task_id, task.as_dict( omit_unset=True)) post_timeline_events(original_task, updated_task) return wmodels.Task.from_db_model(updated_task)
def subscribe(): try: log.register_options(CONF) except cfg.ArgsAlreadyParsedError: pass log.setup(CONF, 'storyboard') CONF(project='storyboard') CONF.register_opts(NOTIFICATION_OPTS, "notifications") subscriber = Subscriber(CONF.notifications) subscriber.start() manager = enabled.EnabledExtensionManager( namespace='storyboard.plugin.worker', check_func=check_enabled, invoke_on_load=True, invoke_args=(CONF,) ) while subscriber.started: (method, properties, body) = subscriber.get() if not method or not properties: LOG.debug(_("No messages available, sleeping for 5 seconds.")) time.sleep(5) continue manager.map(handle_event, body) # Ack the message subscriber.ack(method.delivery_tag)
class DBMigrationError(DBException): """Migration error exception This exception wraps the same exception from database. """ message = _("migrations could not be completed successfully")
def subscribe(): try: log.register_options(CONF) except cfg.ArgsAlreadyParsedError: pass log.setup(CONF, 'storyboard') CONF(project='storyboard') CONF.register_opts(NOTIFICATION_OPTS, "notifications") subscriber = Subscriber(CONF.notifications) subscriber.start() manager = enabled.EnabledExtensionManager( namespace='storyboard.plugin.worker', check_func=check_enabled, invoke_on_load=True, invoke_args=(CONF, )) while subscriber.started: (method, properties, body) = subscriber.get() if not method or not properties: LOG.debug(_("No messages available, sleeping for 5 seconds.")) time.sleep(5) continue manager.map(handle_event, body) # Ack the message subscriber.ack(method.delivery_tag)
class ColumnError(DBException): """Column error exception This exception wraps the same exception from database. """ message = _("Column is invalid or not found")
class DBDeadLock(DBException): """Deadlock exception This exception wraps the same exception from database. """ message = _("Database in dead lock")
class DBConnectionError(DBException): """Connection error exception This exception wraps the same exception from database. """ message = _("Connection to database failed.")
class DBValueError(DBException): """Value error exception This exception wraps standard ValueError exception. """ message = _("Unknown value.")
def decorate(self, *args, **kwargs): try: return func(self, *args, **kwargs) except exc.OAuthException as o_exc: # Extract the parameters error = o_exc.error error_description = o_exc.msg or _("No details available.") # If we have a redirect URL, build the error redirect. if o_exc.redirect_uri: # Split the redirect_url apart parts = urlparse(o_exc.redirect_uri) # Add the error and error_description if parts.query: params = urlparse.parse_qsl(parts.query) else: params = [] params.append(('error', error)) params.append(('error_description', error_description)) # Overwrite the old query params and reconstruct the URL parts_list = list(parts) parts_list[4] = urlencode(params) location = urlunparse(parts_list) redirect(location) else: error_body = { 'error': error, 'error_description': error_description } response.json = error_body abort(o_exc.code, error_description, json_body=error_body)
class DBInvalidSortKey(DBException): """Invalid sortkey error exception This exception wraps the same exception from database. """ message = _("Invalid sort field")
class DBReferenceError(DBException): """Reference error exception This exception wraps the same exception from database. """ message = _("Foreign key error.") def __init__(self, message=None, object_name=None, value=None, key=None, status_code=None): """Constructor for duplicate entry exception :param : message: This message will be shown after exception raised. :param : object_name: This parameter is name of object, in which exception was raised. :param: value: Invalid value. :param : key: Field with invalid value. :param : status_code: code of exception. If object_name or value or key is not 'None', to message will be appended with new message with information about object name or invalid value or field with invalid value. """ super(DBReferenceError, self).__init__(message=message, status_code=status_code) db_message = None if object_name or value or key: db_message_list = [] if object_name: db_message_list.append( _("Error in object")) db_message_list.append(_("\'%s\'.") % object_name) if value or key: db_message_list.append(_("Field")) if key: db_message_list.append(_("\'%s\'") % key) if value: db_message_list.append(_("value")) db_message_list.append(_("\'%s\'") % value) db_message_list.append(_("is invalid.")) db_message = " ".join(db_message_list) if db_message: message_list = [] if message: message_list.append(message) else: message_list.append(self.message) message_list.append(db_message) self.msg = " ".join(message_list)
def post(self, subscription): """Create a new subscription. Note: target_id is the same value as the story_id of a story. Example:: curl https://my.example.org/api/v1/subscriptions \\ -H 'Authorization: Bearer MY_ACCESS_TOKEN' \\ -H 'Content-Type: application/json;charset=UTF-8' \\ --data-binary '{"target_type":"story","target_id":8}' :param subscription: A subscription within the request body. """ # Data sanity check - are all fields set? if not subscription.target_type or not subscription.target_id: abort(400, _('You are missing either the target_type or the' ' target_id')) # Sanity check on user_id current_user = user_api.user_get(request.current_user_id) if not subscription.user_id: subscription.user_id = request.current_user_id elif subscription.user_id != request.current_user_id \ and not current_user.is_superuser: abort(403, _("You can only subscribe to resources on your own.")) # Data sanity check: The resource must exist. resource = subscription_api.subscription_get_resource( target_type=subscription.target_type, target_id=subscription.target_id, current_user=request.current_user_id) if not resource: abort(400, _('You cannot subscribe to a nonexistent resource.')) # Data sanity check: The subscription cannot be duplicated for this # user. existing = subscription_api.subscription_get_all( target_type=[subscription.target_type, ], target_id=subscription.target_id, user_id=subscription.user_id) if existing: abort(409, _('You are already subscribed to this resource.')) result = subscription_api.subscription_create(subscription.as_dict()) return Subscription.from_db_model(result)
def branch_get_master_branch(project_id): query = api_base.model_query(models.Branch) query = query.filter_by(project_id=project_id, name='master').first() if not query: raise exc.NotFound(_("Master branch of project %d not found.") % project_id) return query
def get(self, story_id, comment_id): """Return any historical versions of this comment. :param story_id: The ID of the story. :param comment_id: The ID of the comment to inspect history of. """ comment = comments_api.comment_get(comment_id) if comment is None: raise exc.NotFound(_("Comment %s not found") % comment_id) # Check that the user can actually see the relevant story story = stories_api.story_get_simple( comment.event[0].story_id, current_user=request.current_user_id) if story is None: raise exc.NotFound(_("Comment %s not found") % comment_id) return [wmodels.Comment.from_db_model(old_comment) for old_comment in comment.history]
def _assert_can_access(self, user_id, token_entity=None): current_user = user_api.user_get(request.current_user_id) if not user_id: abort(400, _("user_id is missing.")) # The user must be logged in. if not current_user: abort(401, _("You must log in to do this.")) # If the impacted user is not the current user, the current user must # be an admin. if not current_user.is_superuser and current_user.id != user_id: abort(403, _("You are not admin and can't do this.")) # The path-based impacted user and the user found in the entity must # be identical. No PUT /users/1/tokens { user_id: 2 } if token_entity and token_entity.user_id != user_id: abort(403, _("token_entity.user_id or user_id is wrong."))
def post(self, id, item_id, item_type, list_position): """Add an item to a worklist. Example:: TODO :param id: The ID of the worklist. :param item_id: The ID of the item. :param item_type: The type of the item (i.e. "story" or "task"). :param list_position: The position in the list to add the item. """ user_id = request.current_user_id if not worklists_api.editable_contents(worklists_api.get(id), user_id): raise exc.NotFound(_("Worklist %s not found") % id) item = None if item_type == 'story': item = stories_api.story_get( item_id, current_user=request.current_user_id) elif item_type == 'task': item = tasks_api.task_get( item_id, current_user=request.current_user_id) if item is None: raise exc.NotFound(_("Item %s refers to a non-existent task or " "story.") % item_id) card = worklists_api.add_item( id, item_id, item_type, list_position, current_user=request.current_user_id) added = { "worklist_id": id, "item_id": item_id, "item_title": item.title, "item_type": item_type, "position": card.list_position } events_api.worklist_contents_changed_event(id, user_id, added=added) return wmodels.WorklistItem.from_db_model(card)
def get_one(self, story_id, task_id): """Retrieve details about one task. Example:: curl https://my.example.org/api/v1/stories/11/tasks/2691 :param story_id: An ID of the story. :param task_id: An ID of the task. """ task = tasks_api.task_get( task_id, current_user=request.current_user_id) if task: if task.story_id != story_id: abort(400, _("URL story_id and task.story_id do not match")) return wmodels.Task.from_db_model(task) else: raise exc.NotFound(_("Task %s not found") % task_id)
def post(self, due_date): """Create a new due date. :param due_date: A due date within the request body. """ due_date_dict = due_date.as_dict() user_id = request.current_user_id if due_date.creator_id and due_date.creator_id != user_id: abort(400, _("You can't select the creator of a due date.")) due_date_dict.update({'creator_id': user_id}) board_id = due_date_dict.pop('board_id') worklist_id = due_date_dict.pop('worklist_id') if 'stories' in due_date_dict: del due_date_dict['stories'] if 'tasks' in due_date_dict: del due_date_dict['tasks'] owners = due_date_dict.pop('owners') users = due_date_dict.pop('users') if not owners: owners = [user_id] if not users: users = [] created_due_date = due_dates_api.create(due_date_dict) if board_id is not None: date = due_dates_api.get(created_due_date.id) date.boards.append(boards_api.get(board_id)) if worklist_id is not None: date = due_dates_api.get(created_due_date.id) date.worklists.append(worklists_api.get(worklist_id)) edit_permission = { 'name': 'edit_due_date_%d' % created_due_date.id, 'codename': 'edit_date', 'users': owners } assign_permission = { 'name': 'assign_due_date_%d' % created_due_date.id, 'codename': 'assign_date', 'users': users } due_dates_api.create_permission(created_due_date.id, edit_permission) due_dates_api.create_permission(created_due_date.id, assign_permission) created_due_date = due_dates_api.get(created_due_date.id) due_date_model = wmodels.DueDate.from_db_model(created_due_date) due_date_model.resolve_items(created_due_date) due_date_model.resolve_permissions(created_due_date, request.current_user_id) return due_date_model
def put(self, id, worklist): """Modify this worklist. Example:: TODO :param id: The ID of the worklist. :param worklist: A worklist within the request body. """ user_id = request.current_user_id if not worklists_api.editable(worklists_api.get(id), user_id): raise exc.NotFound(_("Worklist %s not found") % id) story_cache = {story.id: story for story in stories_api.story_get_all( worklist_id=id, current_user=user_id)} task_cache = {task.id: task for task in tasks_api.task_get_all( worklist_id=id, current_user=user_id)} # We don't use this endpoint to update the worklist's contents if worklist.items != wtypes.Unset: del worklist.items # We don't use this endpoint to update the worklist's filters either if worklist.filters != wtypes.Unset: del worklist.filters worklist_dict = worklist.as_dict(omit_unset=True) original = copy.deepcopy(worklists_api.get(id)) updated_worklist = worklists_api.update(id, worklist_dict) post_timeline_events(original, updated_worklist) if worklists_api.visible(updated_worklist, user_id): worklist_model = wmodels.Worklist.from_db_model(updated_worklist) worklist_model.resolve_items( updated_worklist, story_cache, task_cache) worklist_model.resolve_permissions(updated_worklist) return worklist_model else: raise exc.NotFound(_("Worklist %s not found"))