Exemplo n.º 1
0
    def get_one(self, story_id, event_id):
        """Retrieve details about one event.

        Example::

          curl https://my.example.org/api/v1/stories/11/events/15994

        :param story_id: An ID of the story. It stays in params as a
                         placeholder so that pecan knows where to match an
                         incoming value. It will stay unused, as far as events
                         have their own unique ids.
        :param event_id: An ID of the event.
        """

        event = events_api.event_get(event_id,
                                     current_user=request.current_user_id)

        if event:
            wsme_event = wmodels.TimeLineEvent.from_db_model(event)
            wsme_event = wmodels.TimeLineEvent.resolve_event_values(wsme_event)
            return wsme_event
        else:
            raise exc.NotFound(_("Event %s not found") % event_id)
Exemplo n.º 2
0
    def delete(self, worklist_id):
        """Archive this worklist.
           Though this uses the DELETE command, the worklist is not deleted.
           Archived worklists remain viewable at the designated URL, but
           are not returned in search results nor appear on your dashboard.

        Example::

          curl https://my.example.org/api/v1/worklists/30 -X DELETE \\
          -H 'Authorization: Bearer MY_ACCESS_TOKEN'

        :param worklist_id: The ID of the worklist to be archived.

        """
        worklist = worklists_api.get(worklist_id)
        original = copy.deepcopy(worklist)
        user_id = request.current_user_id
        if not worklists_api.editable(worklist, user_id):
            raise exc.NotFound(_("Worklist %s not found") % worklist_id)

        updated = worklists_api.update(worklist_id, {"archived": True})

        post_timeline_events(original, updated)
Exemplo n.º 3
0
    def delete(self, worklist_id, filter_id):
        """Delete a filter from a worklist.

        Example::

          TODO

        :param worklist_id: The ID of the worklist.
        :param filter_id: The ID of the filter to be deleted.

        """
        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)

        filter = serialize_filter(worklists_api.get_filter(filter_id))

        events_api.worklist_filters_changed_event(worklist_id,
                                                  user_id,
                                                  removed=filter)

        worklists_api.delete_filter(filter_id)
Exemplo n.º 4
0
    def delete(self, task_id):
        """Delete this task.

        Example::

          curl https://my.example.org/api/v1/tasks/27 -X DELETE \\
          -H 'Authorization: Bearer MY_ACCESS_TOKEN'

        :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)

        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)
Exemplo n.º 5
0
    def put(self, project_id, project):
        """Modify this project.

        .. note:: This command is only available to Admin users.

        Example::

          curl https://my.example.org/api/v1/projects/10 -X PUT \\
          -H 'Authorization: Bearer MY_ACCESS_TOKEN' \\
          -H 'Content-Type: application/json;charset=UTF-8' \\
          --data-binary '{"name":"test-project-update",\\
                          "description":"An updated test project"}'

        :param project_id: An ID of the project.
        :param project: A project within the request body.
        """
        result = projects_api.project_update(project_id,
                                             project.as_dict(omit_unset=True))

        if result:
            return wmodels.Project.from_db_model(result)
        else:
            raise exc.NotFound(_("Project %s not found") % project_id)
Exemplo n.º 6
0
    def put(self, story_id, task_id, task):
        """Modify this task.

        Example::

          curl 'https://my.example.org/api/v1/stories/19/tasks/19' -X PUT \\
          -H 'Authorization: Bearer MY_ACCESS_TOKEN' \\
          -H 'Content-Type: application/json;charset=UTF-8' \\
          --data-binary '{"title":"Task Foio","project_id":153,"key":"todo"}'

        :param story_id: An ID of the story.
        :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)

        if original_task.story_id != story_id:
            abort(400, _("URL story_id and task.story_id do not match"))

        if task.story_id and original_task.story_id != task.story_id:
            abort(
                400,
                _("the story_id of a task cannot be changed through this API"),
            )

        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)
Exemplo n.º 7
0
    def put(self, story_id, task_id, task):
        """Modify this task.

        :param story_id: An ID of the story.
        :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)

        if original_task.story_id != story_id:
            abort(400, _("URL story_id and task.story_id do not match"))

        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)
Exemplo n.º 8
0
def _story_build_query(title=None,
                       description=None,
                       assignee_id=None,
                       creator_id=None,
                       project_group_id=None,
                       project_id=None,
                       updated_since=None,
                       tags=None,
                       board_id=None,
                       worklist_id=None,
                       tags_filter_type='all',
                       current_user=None,
                       session=None):
    # First build a standard story query.
    query = api_base.model_query(models.Story.id, session=session).distinct()

    # Apply basic filters
    query = api_base.apply_query_filters(query=query,
                                         model=models.Story,
                                         title=title,
                                         description=description,
                                         creator_id=creator_id)
    if updated_since:
        query = query.filter(models.Story.updated_at > updated_since)

    # Filter out stories that the current user can't see
    query = api_base.filter_private_stories(query, current_user)

    # Filtering by tags
    if tags:
        if tags_filter_type == 'all':
            for tag in tags:
                query = query.filter(models.Story.tags.any(name=tag))
        elif tags_filter_type == 'any':
            query = query.filter(
                models.Story.tags.any(models.StoryTag.name.in_(tags)))
        else:
            raise exc.NotFound("Tags filter not found.")

    # Are we filtering by project group?
    if project_group_id:
        query = query.join(
            (models.Task, models.Task.story_id == models.Story.id))
        query = query.join(models.Project, models.project_group_mapping,
                           models.ProjectGroup)
        query = query.filter(models.ProjectGroup.id == project_group_id)

    # Are we filtering by task?
    if assignee_id or project_id:
        if not project_group_id:  # We may already have joined this table
            query = query.join(
                (models.Task, models.Task.story_id == models.Story.id))
        if assignee_id:
            query = query.filter(models.Task.assignee_id == assignee_id)
        if project_id:
            query = query.filter(models.Task.project_id == project_id)

    if worklist_id or board_id:
        query = query.outerjoin(
            (models.WorklistItem,
             models.WorklistItem.item_id == models.Story.id))
        query = query.filter(models.WorklistItem.item_type == "story")
        query = query.outerjoin(models.Worklist)

    # Filter by worklist
    if worklist_id:
        query = query.filter(models.Worklist.id == worklist_id)
        query = api_base.filter_private_worklists(query,
                                                  current_user,
                                                  hide_lanes=False)

    # Filter by board
    if board_id:
        query = query.outerjoin(models.BoardWorklist, models.Board)
        query = api_base.filter_private_boards(query, current_user)
        query = query.filter(models.Board.id == board_id)
        query = api_base.filter_private_worklists(query,
                                                  current_user,
                                                  hide_lanes=False)

    return query.distinct()
Exemplo n.º 9
0
    def put(self,
            id,
            item_id,
            list_position,
            list_id=None,
            display_due_date=None):
        """Update a WorklistItem.

        Example::

          TODO

        This method also updates the positions of other items in affected
        worklists, if necessary.

        :param id: The ID of the worklist.
        :param item_id: The ID of the worklist_item to be moved.
        :param display_due_date: The ID of the due date displayed on 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)
        card = worklists_api.get_item_by_id(item_id)
        if card is None:
            raise exc.NotFound(
                _("Item %s seems to have been deleted, "
                  "try refreshing your page.") % item_id)

        item = None
        if card.item_type == 'story':
            item = stories_api.story_get(card.item_id,
                                         current_user=request.current_user_id)
        elif card.item_type == 'task':
            item = tasks_api.task_get(card.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)

        old = {
            "worklist_id": card.list_id,
            "item_id": card.item_id,
            "item_title": item.title,
            "item_type": card.item_type,
            "position": card.list_position,
            "due_date_id": card.display_due_date
        }

        new = {
            "item_id": card.item_id,
            "item_title": item.title,
            "item_type": card.item_type
        }

        if list_position != card.list_position and list_position is not None:
            new['position'] = list_position
        if list_id != card.list_id and list_id is not None:
            new['worklist_id'] = list_id

        worklists_api.move_item(id, item_id, list_position, list_id)

        if display_due_date is not None:
            if display_due_date == -1:
                display_due_date = None
            update_dict = {'display_due_date': display_due_date}
            worklists_api.update_item(item_id, update_dict)
            new['due_date_id'] = display_due_date

        updated = {"old": old, "new": new}
        events_api.worklist_contents_changed_event(id,
                                                   user_id,
                                                   updated=updated)

        updated = worklists_api.get_item_by_id(item_id)
        result = wmodels.WorklistItem.from_db_model(updated)
        result.resolve_due_date(updated)
        return result
Exemplo n.º 10
0
    def put(self, id, due_date):
        """Modify a due date.

        :param id: The ID of the due date to edit.
        :param due_date: The new due date within the request body.

        """
        if not due_dates_api.assignable(due_dates_api.get(id),
                                        request.current_user_id):
            raise exc.NotFound(_("Due date %s not found") % id)

        original_due_date = due_dates_api.get(id)

        due_date_dict = due_date.as_dict(omit_unset=True)
        editing = any(prop in due_date_dict
                      for prop in ('name', 'date', 'private'))
        if editing and not due_dates_api.editable(original_due_date,
                                                  request.current_user_id):
            raise exc.NotFound(_("Due date %s not found") % id)

        if due_date.creator_id \
                and due_date.creator_id != original_due_date.creator_id:
            abort(400, _("You can't select the creator of a due date."))

        if 'tasks' in due_date_dict:
            tasks = due_date_dict.pop('tasks')
            db_tasks = []
            for task in tasks:
                db_tasks.append(tasks_api.task_get(
                    task.id, current_user=request.current_user_id))
            due_date_dict['tasks'] = db_tasks

        if 'stories' in due_date_dict:
            stories = due_date_dict.pop('stories')
            db_stories = []
            for story in stories:
                db_stories.append(stories_api.story_get_simple(
                    story.id, current_user=request.current_user_id))
            due_date_dict['stories'] = db_stories

        board = None
        worklist = None
        if 'board_id' in due_date_dict:
            board = boards_api.get(due_date_dict['board_id'])

        if 'worklist_id' in due_date_dict:
            worklist = worklists_api.get(due_date_dict['worklist_id'])

        updated_due_date = due_dates_api.update(id, due_date_dict)

        if board:
            updated_due_date.boards.append(board)

        if worklist:
            updated_due_date.worklists.append(worklist)

        if due_dates_api.visible(updated_due_date, request.current_user_id):
            due_date_model = wmodels.DueDate.from_db_model(updated_due_date)
            due_date_model.resolve_items(updated_due_date)
            due_date_model.resolve_permissions(updated_due_date,
                                               request.current_user_id)
            return due_date_model
        else:
            raise exc.NotFound(_("Due date %s not found") % id)
Exemplo n.º 11
0
    def put(self, story_id, story):
        """Modify this story.

        Example::

          curl 'https://my.example.org/api/v1/stories/19' -X PUT \\
          -H 'Authorization: Bearer MY_ACCESS_TOKEN' \\
          -H 'Content-Type: application/json;charset=UTF-8' \\
          --data-binary '{"title":"Modified","description":"New description."}'

        :param story_id: An ID of the story.
        :param story: A story within the request body.
        """
        user_id = request.current_user_id

        # 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 change story type to %s.") %
                story.story_type_id)

        original_story = stories_api.story_get_simple(story_id,
                                                      current_user=user_id)

        if not original_story:
            raise exc.NotFound(_("Story %s not found") % story_id)

        if story.creator_id and story.creator_id != original_story.creator_id:
            abort(400, _("You can't change author of story."))

        story_dict = story.as_dict(omit_unset=True)
        stories_api.story_check_story_type_id(story_dict)

        if not stories_api.story_can_mutate(original_story,
                                            story.story_type_id):
            abort(400, _("Can't change story type."))

        # This is not the place to update tags, including them in
        # story_dict causes the story/tag relationship to attempt to
        # update with a list of unicode strings rather than objects
        # from the database.
        if 'tags' in story_dict:
            story_dict.pop('tags')

        users = story_dict.get("users")
        teams = story_dict.get("teams")

        private = story_dict.get("private", original_story.private)
        if private:
            # If trying to make a story private with no permissions set, add
            # the user making the change to the permission so that at least
            # the story isn't lost to everyone.
            if not users and not teams and not original_story.permissions:
                users = [
                    wmodels.User.from_db_model(users_api.user_get(user_id))
                ]

            original_teams = None
            original_users = None
            if original_story.permissions:
                original_teams = original_story.permissions[0].teams
                original_users = original_story.permissions[0].users

            # Don't allow both permission lists to be deliberately emptied
            # on a private story, to make sure the story remains visible to
            # at least someone.
            valid = True
            if users == [] and teams == []:
                valid = False
            elif users == [] and (original_teams == [] and not teams):
                valid = False
            elif teams == [] and (original_users == [] and not users):
                valid = False
            if not valid and original_story.private:
                abort(400,
                      _("Can't make a private story have no users or teams"))

            # If the story doesn't already have permissions, create them.
            if not original_story.permissions:
                stories_api.create_permission(original_story, users, teams)

        updated_story = stories_api.story_update(story_id,
                                                 story_dict,
                                                 current_user=user_id)

        # If the story is private and already has some permissions, update
        # them as needed. This is done after updating the story in case the
        # request is trying to both update some story fields and also remove
        # the user making the change from the ACL.
        if private and original_story.permissions:
            stories_api.update_permission(updated_story, users, teams)

        events_api.story_details_changed_event(story_id, user_id,
                                               updated_story.title)

        return create_story_wmodel(updated_story)
Exemplo n.º 12
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
Exemplo n.º 13
0
def _story_build_query(title=None,
                       description=None,
                       assignee_id=None,
                       creator_id=None,
                       project_group_id=None,
                       project_id=None,
                       tags=None,
                       tags_filter_type='all',
                       current_user=None):
    # First build a standard story query.
    query = api_base.model_query(models.Story.id).distinct()

    # Apply basic filters
    query = api_base.apply_query_filters(query=query,
                                         model=models.Story,
                                         title=title,
                                         description=description,
                                         creator_id=creator_id)

    # Filter out stories that the current user can't see
    query = query.outerjoin(models.story_permissions, models.Permission,
                            models.user_permissions, models.User)
    if current_user:
        query = query.filter(
            or_(
                and_(models.User.id == current_user,
                     models.Story.private == true()),
                models.Story.private == false()))
    else:
        query = query.filter(models.Story.private == false())

    # Filtering by tags
    if tags:
        if tags_filter_type == 'all':
            for tag in tags:
                query = query.filter(models.Story.tags.any(name=tag))
        elif tags_filter_type == 'any':
            query = query.filter(
                models.Story.tags.any(models.StoryTag.name.in_(tags)))
        else:
            raise exc.NotFound("Tags filter not found.")

    # Are we filtering by project group?
    if project_group_id:
        query = query.join(
            (models.Task, models.Task.story_id == models.Story.id))
        query = query.join(models.Project, models.project_group_mapping,
                           models.ProjectGroup)
        query = query.filter(models.ProjectGroup.id == project_group_id)

    # Are we filtering by task?
    if assignee_id or project_id:
        if not project_group_id:  # We may already have joined this table
            query = query.join(
                (models.Task, models.Task.story_id == models.Story.id))
        if assignee_id:
            query = query.filter(models.Task.assignee_id == assignee_id)
        if project_id:
            query = query.filter(models.Task.project_id == project_id)

    return query
Exemplo n.º 14
0
    def put(self, story_id, story):
        """Modify this story.

        :param story_id: An ID of the 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 change story type to %s.") %
                story.story_type_id)

        original_story = stories_api.story_get_simple(
            story_id, current_user=request.current_user_id)

        if not original_story:
            raise exc.NotFound(_("Story %s not found") % story_id)

        if story.creator_id and story.creator_id != original_story.creator_id:
            abort(400, _("You can't change author of story."))

        story_dict = story.as_dict(omit_unset=True)
        stories_api.story_check_story_type_id(story_dict)

        if not stories_api.story_can_mutate(original_story,
                                            story.story_type_id):
            abort(400, _("Can't change story type."))

        # This is not the place to update tags, including them in
        # story_dict causes the story/tag relationship to attempt to
        # update with a list of unicode strings rather than objects
        # from the database.
        if 'tags' in story_dict:
            story_dict.pop('tags')

        users = story_dict.get("users", [])
        ids = [user.id for user in users]
        if story.private:
            if request.current_user_id not in ids \
                    and not original_story.permissions:
                users.append(
                    wmodels.User.from_db_model(
                        users_api.user_get(request.current_user_id)))
            if not original_story.permissions:
                stories_api.create_permission(original_story, users)

        updated_story = stories_api.story_update(
            story_id, story_dict, current_user=request.current_user_id)

        if users == [] and updated_story.private:
            abort(400, _("Can't make a private story with no users"))

        if story.private:
            stories_api.update_permission(updated_story, users)

        user_id = request.current_user_id
        events_api.story_details_changed_event(story_id, user_id,
                                               updated_story.title)

        return create_story_wmodel(updated_story)
Exemplo n.º 15
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)

    # If the target position is "outside" the list, override it
    if list_position > worklist.items.count():
        list_position = worklist.items.count()

    # 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_item(archived.id, {'archived': False})
        # Move the newly unarchived card into position, and move other cards
        # to compensate for the move
        move_item(archived.id, list_position)
        return archived

    # 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_item(archived.id, {'archived': False})
            # Move the newly unarchived card into position, and move other
            # cards to compensate for the move
            move_item(archived.id, list_position, new_list_id=worklist_id)
            return archived

    # 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
            })

    card_dict = {
        'list_id': worklist_id,
        'item_id': item_id,
        'item_type': item_type,
        'list_position': 99999  # Initialise the card "outside" the list
    }
    card = api_base.entity_create(models.WorklistItem, card_dict)

    # Move the card into position, and move other cards to compensate
    card = move_item(card.id, list_position)

    if worklist.items is None:
        worklist.items = [card]
    else:
        worklist.items.append(card)

    return card