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