def test_delete_story(self):
        # This test uses mock_data
        story_id = 1
        # Verify that we can look up a story with tasks and events
        story = stories_api.story_get_simple(story_id)
        self.assertIsNotNone(story)
        tasks = tasks_api.task_get_all(story_id=story_id)
        self.assertEqual(len(tasks), 3)
        task_ids = [t.id for t in tasks]
        events = events_api.events_get_all(story_id=story_id)
        self.assertEqual(len(events), 3)
        event_ids = [e.id for e in events]

        # Delete the story
        stories_api.story_delete(story_id)
        story = stories_api.story_get_simple(story_id)
        self.assertIsNone(story)
        # Verify that the story's tasks were deleted
        tasks = tasks_api.task_get_all(story_id=story_id)
        self.assertEqual(len(tasks), 0)
        for tid in task_ids:
            task = tasks_api.task_get(task_id=tid)
            self.assertIsNone(task)
        # And the events
        events = events_api.events_get_all(story_id=story_id)
        self.assertEqual(len(events), 0)
        for eid in event_ids:
            event = events_api.event_get(event_id=eid)
            self.assertIsNone(event)
Example #2
0
    def test_delete_story(self):
        # This test uses mock_data
        story_id = 1
        # Verify that we can look up a story with tasks and events
        story = stories_api.story_get_simple(story_id)
        self.assertIsNotNone(story)
        tasks = tasks_api.task_get_all(story_id=story_id)
        self.assertEqual(len(tasks), 3)
        task_ids = [t.id for t in tasks]
        events = events_api.events_get_all(story_id=story_id)
        self.assertEqual(len(events), 3)
        event_ids = [e.id for e in events]

        # Delete the story
        stories_api.story_delete(story_id)
        story = stories_api.story_get_simple(story_id)
        self.assertIsNone(story)
        # Verify that the story's tasks were deleted
        tasks = tasks_api.task_get_all(story_id=story_id)
        self.assertEqual(len(tasks), 0)
        for tid in task_ids:
            task = tasks_api.task_get(task_id=tid)
            self.assertIsNone(task)
        # And the events
        events = events_api.events_get_all(story_id=story_id)
        self.assertEqual(len(events), 0)
        for eid in event_ids:
            event = events_api.event_get(event_id=eid)
            self.assertIsNone(event)
Example #3
0
def is_visible(event, user_id, session=None):
    if event is None:
        return False
    if 'worklist_contents' in event.event_type:
        event_info = json.loads(event.event_info)
        if event_info['updated'] is not None:
            info = event_info['updated']['old']
        elif event_info['removed'] is not None:
            info = event_info['removed']
        elif event_info['added'] is not None:
            info = event_info['added']
        else:
            return True

        if info.get('item_type') == 'story':
            story = stories_api.story_get_simple(info['item_id'],
                                                 current_user=user_id,
                                                 session=session)
            if story is None:
                return False
        elif info.get('item_type') == 'task':
            task = tasks_api.task_get(info['item_id'],
                                      current_user=user_id,
                                      session=session)
            if task is None:
                return False
    return True
def is_visible(event, user_id, session=None):
    if event is None:
        return False
    if 'worklist_contents' in event.event_type:
        event_info = json.loads(event.event_info)
        if event_info['updated'] is not None:
            info = event_info['updated']['old']
        elif event_info['removed'] is not None:
            info = event_info['removed']
        elif event_info['added'] is not None:
            info = event_info['added']
        else:
            return True

        if info.get('item_type') == 'story':
            story = stories_api.story_get_simple(
                info['item_id'], current_user=user_id, session=session)
            if story is None:
                return False
        elif info.get('item_type') == 'task':
            task = tasks_api.task_get(
                info['item_id'], current_user=user_id, session=session)
            if task is None:
                return False
    return True
Example #5
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)
Example #6
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]
Example #7
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)

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

        updated_story = stories_api.story_update(
            story_id,
            story_dict)

        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)
Example #8
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
        ]
Example #9
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)

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

        updated_story = stories_api.story_update(
            story_id,
            story_dict)

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

        return wmodels.Story.from_db_model(updated_story)
Example #10
0
    def get_one(self, story_id, comment_id):
        """Retrieve details about one comment.

        Example::

          curl https://my.example.org/api/v1/stories/11/comments/6834

        :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
                         comments have their own unique ids.
        :param comment_id: An ID of the comment.
        """
        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(comment)
Example #11
0
    def get_one(self, story_id, comment_id):
        """Retrieve details about one comment.

        Example::

          curl https://my.example.org/api/v1/stories/11/comments/6834

        :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
                         comments have their own unique ids.
        :param comment_id: An ID of the comment.
        """
        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(comment)
Example #12
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)
Example #13
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)
Example #14
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)
Example #15
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)