Beispiel #1
0
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)
Beispiel #2
0
    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
Beispiel #4
0
    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()
Beispiel #5
0
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
Beispiel #7
0
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
Beispiel #8
0
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
Beispiel #9
0
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)
Beispiel #10
0
    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)
Beispiel #11
0
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
Beispiel #12
0
    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)
Beispiel #13
0
    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)
Beispiel #14
0
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
Beispiel #15
0
    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)
Beispiel #17
0
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)
Beispiel #18
0
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)
Beispiel #19
0
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)
Beispiel #20
0
    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
Beispiel #21
0
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
Beispiel #22
0
    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)
Beispiel #23
0
    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"))
Beispiel #24
0
    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
        ]
Beispiel #25
0
    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
Beispiel #26
0
    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
Beispiel #27
0
    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)
Beispiel #28
0
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)
Beispiel #29
0
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()
Beispiel #30
0
    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
Beispiel #31
0
    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)
Beispiel #32
0
    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)
Beispiel #33
0
    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
        ]
Beispiel #34
0
    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)
Beispiel #35
0
    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
Beispiel #36
0
    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]
Beispiel #37
0
    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)
Beispiel #39
0
class DBMigrationError(DBException):
    """Migration error exception

    This exception wraps the same exception from database.
    """

    message = _("migrations could not be completed successfully")
Beispiel #40
0
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)
Beispiel #41
0
class ColumnError(DBException):
    """Column error exception

    This exception wraps the same exception from database.
    """

    message = _("Column is invalid or not found")
Beispiel #42
0
class DBDeadLock(DBException):
    """Deadlock exception

    This exception wraps the same exception from database.
    """

    message = _("Database in dead lock")
Beispiel #43
0
class DBConnectionError(DBException):
    """Connection error exception

    This exception wraps the same exception from database.
    """

    message = _("Connection to database failed.")
Beispiel #44
0
class DBValueError(DBException):
    """Value error exception

    This exception wraps standard ValueError exception.
    """

    message = _("Unknown value.")
Beispiel #45
0
    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)
Beispiel #46
0
class DBInvalidSortKey(DBException):
    """Invalid sortkey error exception

    This exception wraps the same exception from database.
    """

    message = _("Invalid sort field")
Beispiel #47
0
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)
Beispiel #49
0
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
Beispiel #50
0
    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."))
Beispiel #52
0
    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)
Beispiel #53
0
    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)
Beispiel #54
0
    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
Beispiel #55
0
    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"))