diff --git a/doc/source/conf.py b/doc/source/conf.py index 7e5ca83c..a2ca3008 100755 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -79,3 +79,17 @@ latex_documents = [ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} + + +def skip_methods(app, what, name, obj, skip, options): + # We have a blacklist because wsme's autodoc extension doesn't like + # inherited attributes. + blacklist = ('id', 'created_at', 'updated_at') + if hasattr(obj, 'nodoc') or not hasattr(obj, 'datatype')\ + or name in blacklist: + return True + return False + + +def setup(app): + app.connect('autodoc-skip-member', skip_methods) diff --git a/doc/source/webapi/v1.rst b/doc/source/webapi/v1.rst index a783ae5e..d94812b6 100644 --- a/doc/source/webapi/v1.rst +++ b/doc/source/webapi/v1.rst @@ -132,42 +132,85 @@ Permissions Object model ############ -Project -======= -.. autotype:: storyboard.api.v1.projects.Project - :members: - - Story ===== -.. autotype:: storyboard.api.v1.stories.Story - :members: - - -Comment -======= -.. autotype:: storyboard.api.v1.timeline.Comment - :members: - - -Timeline event -============== -.. autotype:: storyboard.api.v1.timeline.TimeLineEvent +.. autotype:: storyboard.api.v1.wmodels.Story :members: Task ==== -.. autotype:: storyboard.api.v1.tasks.Task +.. autotype:: storyboard.api.v1.wmodels.Task :members: +Project +======= +.. autotype:: storyboard.api.v1.wmodels.Project + :members: + + +ProjectGroup +============ +.. autotype:: storyboard.api.v1.wmodels.ProjectGroup + :members: + + +Comment +======= +.. autotype:: storyboard.api.v1.wmodels.Comment + :members: + + +Timeline event +============== +.. autotype:: storyboard.api.v1.wmodels.TimeLineEvent + :members: + + +Due Date +======== +.. autotype:: storyboard.api.v1.wmodels.DueDate + :members: + + +Board +===== +.. autotype:: storyboard.api.v1.wmodels.Board + :members: + + +Worklist +======== +.. autotype:: storyboard.api.v1.wmodels.Worklist + :members: + + +WorklistItem +============ +.. autotype:: storyboard.api.v1.wmodels.WorklistItem + :members: + + +WorklistFilter +============== +.. autotype:: storyboard.api.v1.wmodels.WorklistFilter + :members: + + +FilterCriterion +=============== +.. autotype:: storyboard.api.v1.wmodels.FilterCriterion + :members: + + User ==== -.. autotype:: storyboard.api.v1.users.User +.. autotype:: storyboard.api.v1.wmodels.User :members: + SystemInfo ========== -.. autotype:: storyboard.api.v1.system_info.SystemInfo +.. autotype:: storyboard.api.v1.wmodels.SystemInfo :members: diff --git a/storyboard/api/v1/wmodels.py b/storyboard/api/v1/wmodels.py index a010fc09..6229fdb9 100644 --- a/storyboard/api/v1/wmodels.py +++ b/storyboard/api/v1/wmodels.py @@ -32,10 +32,14 @@ from storyboard.db.api import worklists as worklists_api from storyboard.db import models +def nodoc(obj): + """Add a flag which autodoc can use to decide whether to skip a member.""" + obj.nodoc = True + return obj + + class Comment(base.APIBase): - """Any user may leave comments for stories. Also comments api is used by - gerrit to leave service comments. - """ + """Any user may leave comments on stories.""" content = wtypes.text """The content of the comment.""" @@ -46,6 +50,15 @@ class Comment(base.APIBase): in_reply_to = int """The ID of the parent comment, if any.""" + @classmethod + def sample(cls): + return cls( + id=5, + created_at=datetime.fromtimestamp(1321009871), + content="A sample comment body.", + is_active=True, + in_reply_to=2) + class SystemInfo(base.APIBase): """Represents the system information for Storyboard @@ -93,6 +106,8 @@ class Project(base.APIBase): @classmethod def sample(cls): return cls( + id=3, + created_at=datetime.fromtimestamp(1321009871), name="StoryBoard", description="This is an awesome project.", is_active=True, @@ -116,6 +131,8 @@ class ProjectGroup(base.APIBase): @classmethod def sample(cls): return cls( + id=1, + created_at=datetime.fromtimestamp(1321009871), name="Infra", title="Awesome projects") @@ -125,6 +142,10 @@ class TaskStatusCount(base.APIBase): key = wtypes.text count = int + @classmethod + def sample(cls): + return cls(key='todo', count=2) + class User(base.APIBase): """Represents a user.""" @@ -133,13 +154,14 @@ class User(base.APIBase): """Full (Display) name.""" openid = wtypes.text - """The unique identifier, returned by an OpneId provider""" + """The unique identifier, returned by an OpenId provider""" email = wtypes.text """Email Address.""" # Todo(nkonovalov): use teams to define superusers is_superuser = bool + """Whether or not the user has superuser permissions.""" last_login = datetime """Date of the last login.""" @@ -150,6 +172,7 @@ class User(base.APIBase): @classmethod def sample(cls): return cls( + id=3, full_name="Bart Simpson", openid="https://login.launchpad.net/+id/Abacaba", email="skinnerstinks@springfield.net", @@ -191,23 +214,29 @@ class Story(base.APIBase): """Tag list assigned to this story.""" due_dates = wtypes.ArrayType(int) + """List of IDs of Due Dates which are related to this story.""" private = bool + """Whether or not this story is private.""" users = wtypes.ArrayType(User) + """The set of users with permission to see this story if it is private.""" @classmethod def sample(cls): return cls( + id=425, + created_at=datetime.fromtimestamp(1321009871), title="Use Storyboard to manage Storyboard", description="We should use Storyboard to manage Storyboard.", is_bug=False, creator_id=1, - task_statuses=[TaskStatusCount], + task_statuses=[TaskStatusCount(key='todo', count=2)], story_type_id=1, status="active", tags=["t1", "t2"]) + @nodoc def summarize_task_statuses(self, story_summary): """Populate the task_statuses array.""" self.task_statuses = [] @@ -216,6 +245,7 @@ class Story(base.APIBase): key=task_status, count=getattr(story_summary, task_status)) self.task_statuses.append(task_count) + @nodoc def resolve_users(self, story): """Resolve the people who can see the story.""" self.users = [User.from_db_model(user) @@ -274,6 +304,22 @@ class Task(base.APIBase): """The ID of corresponding Milestone""" due_dates = wtypes.ArrayType(int) + """The IDs of due dates related to this task.""" + + @classmethod + def sample(cls): + return cls( + title="My really important task.", + status="todo", + creator_id=1, + story_id=3456, + link="Some notes about the task, this name is bad.", + project_id=1, + assignee_id=1, + priority="medium", + branch_id=1, + milestone_id=1, + due_dates=[1, 2, 3]) class Branch(base.APIBase): @@ -343,18 +389,15 @@ class Milestone(base.APIBase): class Team(base.APIBase): - """The Team is a group od Users with a fixed set of permissions. - """ + """The Team is a group of Users with a fixed set of permissions.""" name = NameType() """The Team unique name. This name will be displayed in the URL. At least 3 alphanumeric symbols. Minus and dot symbols are allowed as - separators. - """ + separators.""" description = wtypes.text - """Details about the team. - """ + """Details about the team.""" @classmethod def sample(cls): @@ -387,6 +430,14 @@ class TimeLineEvent(base.APIBase): comment = Comment """The resolved comment.""" + @classmethod + def sample(cls): + return cls( + id=45543, + event_type="story_created", + event_info='{"story_id": 100, "story_title": "A story"}', + story_id=100) + @staticmethod def resolve_event_values(event): if event.comment_id: @@ -503,6 +554,15 @@ class FilterCriterion(base.APIBase): field = wtypes.text """The field to filter by.""" + @classmethod + def sample(cls): + return cls( + title='TaskStatus', + filter_id=1, + negative=True, + value='merged', + field='status') + class WorklistFilter(base.APIBase): """Represents a set of criteria to filter items using AND.""" @@ -516,10 +576,25 @@ class WorklistFilter(base.APIBase): filter_criteria = wtypes.ArrayType(FilterCriterion) """The list of criteria to apply.""" + @nodoc def resolve_criteria(self, filter): self.filter_criteria = [FilterCriterion.from_db_model(criterion) for criterion in filter.criteria] + @classmethod + def sample(cls): + return cls( + type='Task', + list_id=1, + filter_criteria=[FilterCriterion( + type='Task', + title='TaskStatus', + filter_id=1, + negative=True, + value='merged', + field='status') + ]) + class DueDate(base.APIBase): """Represents a due date for tasks/stories.""" @@ -567,6 +642,23 @@ class DueDate(base.APIBase): assignable = bool """Whether or not the due date is assignable by the request sender.""" + @classmethod + def sample(cls): + return cls( + id=1, + name='A really great deadline', + date=datetime(2016, 5, 30, 10, 10), + private=False, + creator_id=3, + owners=[3], + users=[], + tasks=[], + stories=[], + count=0, + editable=True, + assignable=True) + + @nodoc def resolve_count_in_board(self, due_date, board): self.count = 0 for lane in board.lanes: @@ -575,6 +667,7 @@ class DueDate(base.APIBase): if card.display_due_date == due_date.id: self.count += 1 + @nodoc def resolve_items(self, due_date): """Resolve the various lists for the due date.""" stories, tasks = due_dates_api.get_visible_items( @@ -584,6 +677,7 @@ class DueDate(base.APIBase): for story in stories] self.count = len(self.tasks) + len(self.stories) + @nodoc def resolve_permissions(self, due_date, user=None): """Resolve the permissions groups of the due date.""" self.owners = due_dates_api.get_owners(due_date) @@ -617,11 +711,26 @@ class WorklistItem(base.APIBase): """The ID of the due date displayed on this item.""" resolved_due_date = DueDate - """The due date displayed on this item.""" + """The Due Date displayed on this item.""" task = Task - story = Story + """The Task referred to by this list item.""" + story = Story + """The Story referred to by this list item.""" + + @classmethod + def sample(cls): + return cls( + id=1, + list_id=1, + item_id=1, + item_type="story", + list_position=0, + archived=False, + display_due_date=None) + + @nodoc def resolve_due_date(self, worklist_item): due_date = due_dates_api.get(worklist_item.display_due_date) resolved = None @@ -629,6 +738,7 @@ class WorklistItem(base.APIBase): resolved = DueDate.from_db_model(due_date) self.resolved_due_date = resolved + @nodoc def resolve_item(self, item): user_id = request.current_user_id if item.item_type == 'story': @@ -689,6 +799,21 @@ class Worklist(base.APIBase): items = wtypes.ArrayType(WorklistItem) """The items in the worklist.""" + @classmethod + def sample(cls): + return cls( + id=1, + title="My great worklist", + creator_id=1, + private=False, + archived=False, + automatic=False, + filters=[], + owners=[1], + users=[2], + items=[]) + + @nodoc def resolve_items(self, worklist): """Resolve the contents of this worklist.""" self.items = [] @@ -698,6 +823,7 @@ class Worklist(base.APIBase): else: self._resolve_set_items(worklist, user_id) + @nodoc def _resolve_automatic_items(self, worklist, user_id): for item in worklists_api.filter_items(worklist): item_model = WorklistItem(**item) @@ -708,6 +834,7 @@ class Worklist(base.APIBase): self.items.append(item_model) self.items.sort(key=lambda x: x.list_position) + @nodoc def _resolve_set_items(self, worklist, user_id): for item in worklist.items: if item.archived: @@ -720,10 +847,12 @@ class Worklist(base.APIBase): self.items.append(item_model) self.items.sort(key=lambda x: x.list_position) + @nodoc def resolve_permissions(self, worklist): self.owners = worklists_api.get_owners(worklist) self.users = worklists_api.get_users(worklist) + @nodoc def resolve_filters(self, worklist): self.filters = [] for filter in worklist.filters: @@ -747,6 +876,26 @@ class Lane(base.APIBase): position = int """The position of the lane in the board.""" + @classmethod + def sample(cls): + return cls( + id=1, + board_id=23, + list_id=10, + position=1, + worklist=Worklist( + id=1, + title="My great worklist", + creator_id=1, + private=False, + archived=False, + automatic=False, + filters=[], + owners=[1], + users=[2], + items=[])) + + @nodoc def resolve_list(self, lane, resolve_items=True): """Resolve the worklist which represents the lane.""" self.worklist = Worklist.from_db_model(lane.worklist) @@ -797,6 +946,36 @@ class Board(base.APIBase): users = wtypes.ArrayType(int) """A list of the IDs of the users who can move cards in the board.""" + @classmethod + def sample(cls): + return cls( + id=1, + title="StoryBoard development", + description="Board for tracking StoryBoard development.", + creator_id=1, + private=False, + archived=False, + lanes=[Lane( + id=1, + board_id=23, + list_id=10, + position=1, + worklist=Worklist( + id=1, + title="My great worklist", + creator_id=1, + private=False, + archived=False, + automatic=False, + filters=[], + owners=[1], + users=[2], + items=[]))], + due_dates=[], + owners=[1], + users=[]) + + @nodoc def resolve_lanes(self, board, resolve_items=True): """Resolve the lanes of the board.""" self.lanes = [] @@ -806,6 +985,7 @@ class Board(base.APIBase): self.lanes.append(lane_model) self.lanes.sort(key=lambda x: x.position) + @nodoc def resolve_due_dates(self, board): self.due_dates = [] for due_date in board.due_dates: @@ -817,6 +997,7 @@ class Board(base.APIBase): due_date_model.resolve_count_in_board(due_date, board) self.due_dates.append(due_date_model) + @nodoc def resolve_permissions(self, board): """Resolve the permissions groups of the board.""" self.owners = boards_api.get_owners(board)