Merge "Project Groups API"
This commit is contained in:
@@ -11,6 +11,14 @@ Projects
|
||||
.. rest-controller:: storyboard.api.v1.projects:ProjectsController
|
||||
:webprefix: /v1/projects
|
||||
|
||||
Project Groups
|
||||
==============
|
||||
.. rest-controller:: storyboard.api.v1.project_groups:ProjectGroupsController
|
||||
:webprefix: /v1/project_groups
|
||||
|
||||
.. rest-controller:: storyboard.api.v1.project_groups:ProjectsSubcontroller
|
||||
:webprefix: /v1/project_groups/<project_group_id>/projects
|
||||
|
||||
Stories
|
||||
=======
|
||||
.. rest-controller:: storyboard.api.v1.stories:StoriesController
|
||||
|
||||
@@ -13,64 +13,160 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from oslo.config import cfg
|
||||
from pecan import rest
|
||||
from pecan.secure import secure
|
||||
from wsme.exc import ClientSideError
|
||||
from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
import storyboard.api.auth.authorization_checks as checks
|
||||
import storyboard.api.v1.wsme_models as wsme_models
|
||||
from storyboard.api.v1 import base
|
||||
from storyboard.api.v1.projects import Project
|
||||
from storyboard.db.api import project_groups
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class ProjectGroup(base.APIBase):
|
||||
"""Represents a group of projects."""
|
||||
|
||||
name = wtypes.text
|
||||
"""A unique name, used in URLs, identifying the project group. All
|
||||
lowercase, no special characters. Examples: infra, compute.
|
||||
"""
|
||||
|
||||
title = wtypes.text
|
||||
"""The full name of the project group, which can contain spaces, special
|
||||
characters, etc.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
return cls(
|
||||
name="Infra",
|
||||
title="Awesome projects")
|
||||
|
||||
|
||||
class ProjectsSubcontroller(rest.RestController):
|
||||
"""This controller should be used to list, add or remove projects from a
|
||||
Project Group.
|
||||
"""
|
||||
|
||||
@secure(checks.guest)
|
||||
@wsme_pecan.wsexpose([Project], int)
|
||||
def get(self, project_group_id):
|
||||
"""Get projects inside a project group.
|
||||
|
||||
:param project_group_id: An ID of the project group
|
||||
"""
|
||||
|
||||
project_group = project_groups.project_group_get(project_group_id)
|
||||
|
||||
if not project_group:
|
||||
raise ClientSideError("The requested project group does not exist")
|
||||
|
||||
return [Project.from_db_model(project)
|
||||
for project in project_group.projects]
|
||||
|
||||
@secure(checks.superuser)
|
||||
@wsme_pecan.wsexpose([Project], int, int)
|
||||
def put(self, project_group_id, project_id):
|
||||
"""Add a project to a project_group
|
||||
"""
|
||||
|
||||
project_groups.project_group_add_project(project_group_id, project_id)
|
||||
|
||||
project_group = project_groups.project_group_get(project_group_id)
|
||||
|
||||
return [Project.from_db_model(project)
|
||||
for project in project_group.projects]
|
||||
|
||||
@secure(checks.superuser)
|
||||
@wsme_pecan.wsexpose([Project], int, int)
|
||||
def delete(self, project_group_id, project_id):
|
||||
"""Delete a project from a project_group
|
||||
"""
|
||||
project_groups.project_group_delete_project(project_group_id,
|
||||
project_id)
|
||||
|
||||
project_group = project_groups.project_group_get(project_group_id)
|
||||
|
||||
return [Project.from_db_model(project)
|
||||
for project in project_group.projects]
|
||||
|
||||
|
||||
class ProjectGroupsController(rest.RestController):
|
||||
"""REST controller for Project Groups.
|
||||
|
||||
At this moment it provides read-only operations.
|
||||
NOTE: PUT requests should be used to update only top-level fields.
|
||||
The nested fields (projects) should be updated using requests to a
|
||||
/projects subcontroller
|
||||
"""
|
||||
|
||||
@secure(checks.guest)
|
||||
@wsme_pecan.wsexpose(wsme_models.ProjectGroup, int)
|
||||
def get_one(self, id):
|
||||
@wsme_pecan.wsexpose(ProjectGroup, int)
|
||||
def get_one(self, project_group_id):
|
||||
"""Retrieve information about the given project group.
|
||||
|
||||
:param name: project group name.
|
||||
:param project_group_id: project group id.
|
||||
"""
|
||||
group = wsme_models.ProjectGroup.get(id=id)
|
||||
|
||||
group = project_groups.project_group_get(project_group_id)
|
||||
if not group:
|
||||
raise ClientSideError("Project Group %s not found" % id,
|
||||
raise ClientSideError("Project Group %s not found" %
|
||||
project_group_id,
|
||||
status_code=404)
|
||||
return group
|
||||
|
||||
return ProjectGroup.from_db_model(group)
|
||||
|
||||
@secure(checks.guest)
|
||||
@wsme_pecan.wsexpose([wsme_models.ProjectGroup])
|
||||
def get(self):
|
||||
@wsme_pecan.wsexpose([ProjectGroup], int, int)
|
||||
def get(self, marker=None, limit=None):
|
||||
"""Retrieve a list of projects groups."""
|
||||
groups = wsme_models.ProjectGroup.get_all()
|
||||
return groups
|
||||
|
||||
if limit is None:
|
||||
limit = CONF.page_size_default
|
||||
limit = min(CONF.page_size_maximum, max(1, limit))
|
||||
|
||||
groups = project_groups.project_group_get_all(marker=marker,
|
||||
limit=limit)
|
||||
|
||||
return [ProjectGroup.from_db_model(group) for group in groups]
|
||||
|
||||
@secure(checks.superuser)
|
||||
@wsme_pecan.wsexpose(wsme_models.ProjectGroup,
|
||||
body=wsme_models.ProjectGroup)
|
||||
def post(self, group):
|
||||
@wsme_pecan.wsexpose(ProjectGroup, body=ProjectGroup)
|
||||
def post(self, project_group):
|
||||
"""Create a new project group.
|
||||
|
||||
:param group: a project group within the request body.
|
||||
:param project_group: a project group within the request body.
|
||||
"""
|
||||
created_group = wsme_models.ProjectGroup.create(wsme_entry=group)
|
||||
|
||||
created_group = project_groups.project_group_create(
|
||||
project_group.as_dict())
|
||||
|
||||
if not created_group:
|
||||
raise ClientSideError("Could not create ProjectGroup")
|
||||
return created_group
|
||||
|
||||
return ProjectGroup.from_db_model(created_group)
|
||||
|
||||
@secure(checks.superuser)
|
||||
@wsme_pecan.wsexpose(wsme_models.ProjectGroup, int,
|
||||
body=wsme_models.ProjectGroup)
|
||||
def put(self, id, group):
|
||||
@wsme_pecan.wsexpose(ProjectGroup, int, body=ProjectGroup)
|
||||
def put(self, id, project_group):
|
||||
"""Modify this project group.
|
||||
|
||||
:param id: An ID of the project group.
|
||||
:param group: a project group within the request body.
|
||||
:param project_group: a project group within the request body.
|
||||
"""
|
||||
updated_group = wsme_models.ProjectGroup.update("id", id, group)
|
||||
|
||||
updated_group = project_groups.project_group_update(
|
||||
id,
|
||||
project_group.as_dict())
|
||||
|
||||
if not updated_group:
|
||||
raise ClientSideError("Could not update group %s" % id)
|
||||
return updated_group
|
||||
|
||||
return ProjectGroup.from_db_model(updated_group)
|
||||
|
||||
projects = ProjectsSubcontroller()
|
||||
|
||||
@@ -189,26 +189,6 @@ def update_db_model(cls, db_entry, wsme_entry):
|
||||
return db_entry
|
||||
|
||||
|
||||
class ProjectGroup(_Base):
|
||||
"""Represents a group of projects."""
|
||||
|
||||
name = wtypes.text
|
||||
"""A unique name, used in URLs, identifying the project group. All
|
||||
lowercase, no special characters. Examples: infra, compute.
|
||||
"""
|
||||
|
||||
title = wtypes.text
|
||||
"""The full name of the project group, which can contain spaces, special
|
||||
characters, etc.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
return cls(
|
||||
name="Infra",
|
||||
title="Awesome project")
|
||||
|
||||
|
||||
class Permission(_Base):
|
||||
"""Permissions can be associated with users and teams."""
|
||||
pass
|
||||
@@ -254,7 +234,6 @@ class Team(_Base):
|
||||
|
||||
|
||||
SQLALCHEMY_TO_WSME = {
|
||||
sqlalchemy_models.ProjectGroup: ProjectGroup,
|
||||
sqlalchemy_models.Permission: Permission,
|
||||
sqlalchemy_models.Comment: Comment,
|
||||
sqlalchemy_models.StoryTag: StoryTag
|
||||
|
||||
108
storyboard/db/api/project_groups.py
Normal file
108
storyboard/db/api/project_groups.py
Normal file
@@ -0,0 +1,108 @@
|
||||
# Copyright (c) 2014 Mirantis Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from sqlalchemy.orm import subqueryload
|
||||
from wsme.exc import ClientSideError
|
||||
|
||||
from storyboard.common import exception as exc
|
||||
from storyboard.db.api import base as api_base
|
||||
from storyboard.db.api import projects
|
||||
from storyboard.db import models
|
||||
|
||||
|
||||
def _entity_get(id, session=None):
|
||||
if not session:
|
||||
session = api_base.get_session()
|
||||
query = session.query(models.ProjectGroup)\
|
||||
.options(subqueryload(models.ProjectGroup.projects))\
|
||||
.filter_by(id=id)
|
||||
|
||||
return query.first()
|
||||
|
||||
|
||||
def project_group_get(project_group_id):
|
||||
return _entity_get(project_group_id)
|
||||
|
||||
|
||||
def project_group_get_all(marker=None, limit=None, **kwargs):
|
||||
return api_base.entity_get_all(models.ProjectGroup,
|
||||
marker=marker,
|
||||
limit=limit,
|
||||
**kwargs)
|
||||
|
||||
|
||||
def project_group_get_count(**kwargs):
|
||||
return api_base.entity_get_count(models.ProjectGroup, **kwargs)
|
||||
|
||||
|
||||
def project_group_create(values):
|
||||
return api_base.entity_create(models.ProjectGroup, values)
|
||||
|
||||
|
||||
def project_group_update(project_group_id, values):
|
||||
return api_base.entity_update(models.ProjectGroup, project_group_id,
|
||||
values)
|
||||
|
||||
|
||||
def project_group_add_project(project_group_id, project_id):
|
||||
session = api_base.get_session()
|
||||
|
||||
with session.begin():
|
||||
project_group = _entity_get(project_group_id, session)
|
||||
if project_group is None:
|
||||
raise exc.NotFound("%s %s not found"
|
||||
% ("Project Group", project_group_id))
|
||||
|
||||
project = projects.project_get(project_id)
|
||||
if project is None:
|
||||
raise exc.NotFound("%s %s not found"
|
||||
% ("Project", project_id))
|
||||
|
||||
if project_id in [p.id for p in project_group.projects]:
|
||||
raise ClientSideError("The Project %d is already in "
|
||||
"Project Group %d" %
|
||||
(project_id, project_group_id))
|
||||
|
||||
project_group.projects.append(project)
|
||||
session.add(project_group)
|
||||
|
||||
return project_group
|
||||
|
||||
|
||||
def project_group_delete_project(project_group_id, project_id):
|
||||
session = api_base.get_session()
|
||||
|
||||
with session.begin():
|
||||
project_group = _entity_get(project_group_id, session)
|
||||
if project_group is None:
|
||||
raise exc.NotFound("%s %s not found"
|
||||
% ("Project Group", project_group_id))
|
||||
|
||||
project = projects.project_get(project_id)
|
||||
if project is None:
|
||||
raise exc.NotFound("%s %s not found"
|
||||
% ("Project", project_id))
|
||||
|
||||
if project_id not in [p.id for p in project_group.projects]:
|
||||
raise ClientSideError("The Project %d is not in "
|
||||
"Project Group %d" %
|
||||
(project_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
|
||||
Reference in New Issue
Block a user