Merge pull request #294 from dod-ccpo/edit-workspace-#160300728
Edit workspace #160300728
This commit is contained in:
commit
f984c95060
@ -0,0 +1,43 @@
|
|||||||
|
"""add edit workspace information permission
|
||||||
|
|
||||||
|
Revision ID: 4c425f17bfe8
|
||||||
|
Revises: 2572be7fb7fc
|
||||||
|
Create Date: 2018-09-17 13:14:38.781744
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
|
from atst.models.role import Role
|
||||||
|
from atst.models.permissions import Permissions
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '4c425f17bfe8'
|
||||||
|
down_revision = '2572be7fb7fc'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
session = Session(bind=op.get_bind())
|
||||||
|
|
||||||
|
owner_and_admin = session.query(Role).filter(Role.name.in_(["owner", "admin"])).all()
|
||||||
|
for role in owner_and_admin:
|
||||||
|
role.add_permission(Permissions.EDIT_WORKSPACE_INFORMATION)
|
||||||
|
session.add(role)
|
||||||
|
|
||||||
|
session.flush()
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
session = Session(bind=op.get_bind())
|
||||||
|
|
||||||
|
owner_and_admin = session.query(Role).filter(Role.name.in_(["owner", "admin"])).all()
|
||||||
|
for role in owner_and_ccpo:
|
||||||
|
role.remove_permission(Permissions.EDIT_WORKSPACE_INFORMATION)
|
||||||
|
session.add(role)
|
||||||
|
|
||||||
|
session.flush()
|
||||||
|
session.commit()
|
@ -27,7 +27,7 @@ class Workspaces(object):
|
|||||||
return ScopedWorkspace(user, workspace)
|
return ScopedWorkspace(user, workspace)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_for_update(cls, user, workspace_id):
|
def get_for_update_projects(cls, user, workspace_id):
|
||||||
workspace = WorkspacesQuery.get(workspace_id)
|
workspace = WorkspacesQuery.get(workspace_id)
|
||||||
Authorization.check_workspace_permission(
|
Authorization.check_workspace_permission(
|
||||||
user, workspace, Permissions.ADD_APPLICATION_IN_WORKSPACE, "add project"
|
user, workspace, Permissions.ADD_APPLICATION_IN_WORKSPACE, "add project"
|
||||||
@ -35,6 +35,18 @@ class Workspaces(object):
|
|||||||
|
|
||||||
return workspace
|
return workspace
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_for_update_information(cls, user, workspace_id):
|
||||||
|
workspace = WorkspacesQuery.get(workspace_id)
|
||||||
|
Authorization.check_workspace_permission(
|
||||||
|
user,
|
||||||
|
workspace,
|
||||||
|
Permissions.EDIT_WORKSPACE_INFORMATION,
|
||||||
|
"update workspace information",
|
||||||
|
)
|
||||||
|
|
||||||
|
return workspace
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_by_request(cls, request):
|
def get_by_request(cls, request):
|
||||||
return WorkspacesQuery.get_by_request(request)
|
return WorkspacesQuery.get_by_request(request)
|
||||||
@ -98,3 +110,10 @@ class Workspaces(object):
|
|||||||
workspace_role = WorkspacesQuery.create_workspace_role(user, role, workspace)
|
workspace_role = WorkspacesQuery.create_workspace_role(user, role, workspace)
|
||||||
WorkspacesQuery.add_and_commit(workspace_role)
|
WorkspacesQuery.add_and_commit(workspace_role)
|
||||||
return workspace_role
|
return workspace_role
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update(cls, workspace, new_data):
|
||||||
|
if "name" in new_data:
|
||||||
|
workspace.name = new_data["name"]
|
||||||
|
|
||||||
|
WorkspacesQuery.add_and_commit(workspace)
|
||||||
|
17
atst/forms/workspace.py
Normal file
17
atst/forms/workspace.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from wtforms.fields import StringField
|
||||||
|
from wtforms.validators import Length
|
||||||
|
|
||||||
|
from .forms import ValidatedForm
|
||||||
|
|
||||||
|
|
||||||
|
class WorkspaceForm(ValidatedForm):
|
||||||
|
name = StringField(
|
||||||
|
"Workspace Name",
|
||||||
|
validators=[
|
||||||
|
Length(
|
||||||
|
min=4,
|
||||||
|
max=50,
|
||||||
|
message="Workspace names must be at least 4 and not more than 50 characters",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
@ -20,6 +20,7 @@ class Permissions(object):
|
|||||||
VIEW_ASSIGNED_ATAT_ROLE_CONFIGURATIONS = "view_assigned_atat_role_configurations"
|
VIEW_ASSIGNED_ATAT_ROLE_CONFIGURATIONS = "view_assigned_atat_role_configurations"
|
||||||
VIEW_ASSIGNED_CSP_ROLE_CONFIGURATIONS = "view_assigned_csp_role_configurations"
|
VIEW_ASSIGNED_CSP_ROLE_CONFIGURATIONS = "view_assigned_csp_role_configurations"
|
||||||
|
|
||||||
|
EDIT_WORKSPACE_INFORMATION = "edit_workspace_information"
|
||||||
DEACTIVATE_WORKSPACE = "deactivate_workspace"
|
DEACTIVATE_WORKSPACE = "deactivate_workspace"
|
||||||
VIEW_ATAT_PERMISSIONS = "view_atat_permissions"
|
VIEW_ATAT_PERMISSIONS = "view_atat_permissions"
|
||||||
TRANSFER_OWNERSHIP_OF_WORKSPACE = "transfer_ownership_of_workspace"
|
TRANSFER_OWNERSHIP_OF_WORKSPACE = "transfer_ownership_of_workspace"
|
||||||
|
@ -17,6 +17,7 @@ from atst.domain.workspace_users import WorkspaceUsers
|
|||||||
from atst.forms.new_project import NewProjectForm
|
from atst.forms.new_project import NewProjectForm
|
||||||
from atst.forms.new_member import NewMemberForm
|
from atst.forms.new_member import NewMemberForm
|
||||||
from atst.forms.edit_member import EditMemberForm
|
from atst.forms.edit_member import EditMemberForm
|
||||||
|
from atst.forms.workspace import WorkspaceForm
|
||||||
from atst.domain.authz import Authorization
|
from atst.domain.authz import Authorization
|
||||||
from atst.models.permissions import Permissions
|
from atst.models.permissions import Permissions
|
||||||
|
|
||||||
@ -50,12 +51,32 @@ def workspaces():
|
|||||||
return render_template("workspaces/index.html", page=5, workspaces=workspaces)
|
return render_template("workspaces/index.html", page=5, workspaces=workspaces)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/workspaces/<workspace_id>/edit")
|
||||||
|
def workspace(workspace_id):
|
||||||
|
workspace = Workspaces.get_for_update_information(g.current_user, workspace_id)
|
||||||
|
form = WorkspaceForm(data={"name": workspace.name})
|
||||||
|
return render_template("workspaces/edit.html", form=form, workspace=workspace)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/workspaces/<workspace_id>/projects")
|
@bp.route("/workspaces/<workspace_id>/projects")
|
||||||
def workspace_projects(workspace_id):
|
def workspace_projects(workspace_id):
|
||||||
workspace = Workspaces.get(g.current_user, workspace_id)
|
workspace = Workspaces.get(g.current_user, workspace_id)
|
||||||
return render_template("workspaces/projects/index.html", workspace=workspace)
|
return render_template("workspaces/projects/index.html", workspace=workspace)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/workspaces/<workspace_id>/edit", methods=["POST"])
|
||||||
|
def edit_workspace(workspace_id):
|
||||||
|
workspace = Workspaces.get_for_update_information(g.current_user, workspace_id)
|
||||||
|
form = WorkspaceForm(http_request.form)
|
||||||
|
if form.validate():
|
||||||
|
Workspaces.update(workspace, form.data)
|
||||||
|
return redirect(
|
||||||
|
url_for("workspaces.workspace_projects", workspace_id=workspace.id)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return render_template("workspaces/edit.html", form=form, workspace=workspace)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/workspaces/<workspace_id>")
|
@bp.route("/workspaces/<workspace_id>")
|
||||||
def show_workspace(workspace_id):
|
def show_workspace(workspace_id):
|
||||||
return redirect(url_for("workspaces.workspace_projects", workspace_id=workspace_id))
|
return redirect(url_for("workspaces.workspace_projects", workspace_id=workspace_id))
|
||||||
@ -98,7 +119,7 @@ def workspace_reports(workspace_id):
|
|||||||
|
|
||||||
@bp.route("/workspaces/<workspace_id>/projects/new")
|
@bp.route("/workspaces/<workspace_id>/projects/new")
|
||||||
def new_project(workspace_id):
|
def new_project(workspace_id):
|
||||||
workspace = Workspaces.get_for_update(g.current_user, workspace_id)
|
workspace = Workspaces.get_for_update_projects(g.current_user, workspace_id)
|
||||||
form = NewProjectForm()
|
form = NewProjectForm()
|
||||||
return render_template(
|
return render_template(
|
||||||
"workspaces/projects/new.html", workspace=workspace, form=form
|
"workspaces/projects/new.html", workspace=workspace, form=form
|
||||||
@ -107,7 +128,7 @@ def new_project(workspace_id):
|
|||||||
|
|
||||||
@bp.route("/workspaces/<workspace_id>/projects/new", methods=["POST"])
|
@bp.route("/workspaces/<workspace_id>/projects/new", methods=["POST"])
|
||||||
def create_project(workspace_id):
|
def create_project(workspace_id):
|
||||||
workspace = Workspaces.get_for_update(g.current_user, workspace_id)
|
workspace = Workspaces.get_for_update_projects(g.current_user, workspace_id)
|
||||||
form = NewProjectForm(http_request.form)
|
form = NewProjectForm(http_request.form)
|
||||||
|
|
||||||
if form.validate():
|
if form.validate():
|
||||||
@ -130,7 +151,7 @@ def create_project(workspace_id):
|
|||||||
|
|
||||||
@bp.route("/workspaces/<workspace_id>/projects/<project_id>/edit")
|
@bp.route("/workspaces/<workspace_id>/projects/<project_id>/edit")
|
||||||
def edit_project(workspace_id, project_id):
|
def edit_project(workspace_id, project_id):
|
||||||
workspace = Workspaces.get_for_update(g.current_user, workspace_id)
|
workspace = Workspaces.get_for_update_projects(g.current_user, workspace_id)
|
||||||
project = Projects.get(g.current_user, workspace, project_id)
|
project = Projects.get(g.current_user, workspace, project_id)
|
||||||
form = NewProjectForm(
|
form = NewProjectForm(
|
||||||
name=project.name,
|
name=project.name,
|
||||||
|
@ -78,5 +78,11 @@ export default {
|
|||||||
match: /[0-9]{2}\w?$/,
|
match: /[0-9]{2}\w?$/,
|
||||||
unmask: [],
|
unmask: [],
|
||||||
validationError: 'Please enter a valid BA Code. Note that it should be two digits, followed by a letter.'
|
validationError: 'Please enter a valid BA Code. Note that it should be two digits, followed by a letter.'
|
||||||
}
|
},
|
||||||
|
workspaceName: {
|
||||||
|
mask: false,
|
||||||
|
match: /^.{4,50}$/,
|
||||||
|
unmask: [],
|
||||||
|
validationError: 'Workspace names must be at least 4 and not more than 50 characters'
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -194,6 +194,7 @@
|
|||||||
&--validation {
|
&--validation {
|
||||||
|
|
||||||
&--anything,
|
&--anything,
|
||||||
|
&--workspaceName,
|
||||||
&--email {
|
&--email {
|
||||||
input {
|
input {
|
||||||
max-width: 30em;
|
max-width: 30em;
|
||||||
|
@ -35,5 +35,15 @@
|
|||||||
href=url_for("workspaces.workspace_reports", workspace_id=workspace.id),
|
href=url_for("workspaces.workspace_reports", workspace_id=workspace.id),
|
||||||
active=request.url_rule.rule.startswith('/workspaces/<workspace_id>/reports')
|
active=request.url_rule.rule.startswith('/workspaces/<workspace_id>/reports')
|
||||||
) }}
|
) }}
|
||||||
|
|
||||||
|
{% if user_can(permissions.EDIT_WORKSPACE_INFORMATION) %}
|
||||||
|
{{ SidenavItem(
|
||||||
|
"Workspace Settings",
|
||||||
|
href=url_for("workspaces.workspace", workspace_id=workspace.id),
|
||||||
|
active=request.url_rule.rule.startswith('/workspaces/<workspace_id>/edit'),
|
||||||
|
subnav=None
|
||||||
|
) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
43
templates/workspaces/edit.html
Normal file
43
templates/workspaces/edit.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{% extends "workspaces/base.html" %}
|
||||||
|
|
||||||
|
{% from "components/icon.html" import Icon %}
|
||||||
|
{% from "components/alert.html" import Alert %}
|
||||||
|
{% from "components/text_input.html" import TextInput %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block workspace_content %}
|
||||||
|
|
||||||
|
{% if form.errors %}
|
||||||
|
{{ Alert('There were some errors',
|
||||||
|
message="<p>Please see below.</p>",
|
||||||
|
level='error'
|
||||||
|
) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="POST" action="{{ url_for('workspaces.edit_workspace', workspace_id=workspace.id) }}" autocomplete="false">
|
||||||
|
{{ form.csrf_token }}
|
||||||
|
|
||||||
|
<div class="panel">
|
||||||
|
|
||||||
|
<div class="panel__heading">
|
||||||
|
<h1>Workspace Settings</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel__content">
|
||||||
|
{{ TextInput(form.name, validation="workspaceName") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class='action-group'>
|
||||||
|
<button type="submit" class="usa-button usa-button-big usa-button-primary" tabindex="0">Save</button>
|
||||||
|
<a href='{{ url_for("workspaces.workspace_projects", workspace_id=workspace.id) }}' class='action-group__action icon-link'>
|
||||||
|
{{ Icon('x') }}
|
||||||
|
<span>Cancel</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -63,16 +63,16 @@ def test_workspaces_get_ensures_user_is_in_workspace(workspace, workspace_owner)
|
|||||||
Workspaces.get(outside_user, workspace.id)
|
Workspaces.get(outside_user, workspace.id)
|
||||||
|
|
||||||
|
|
||||||
def test_get_for_update_allows_owner(workspace, workspace_owner):
|
def test_get_for_update_projects_allows_owner(workspace, workspace_owner):
|
||||||
Workspaces.get_for_update(workspace_owner, workspace.id)
|
Workspaces.get_for_update_projects(workspace_owner, workspace.id)
|
||||||
|
|
||||||
|
|
||||||
def test_get_for_update_blocks_developer(workspace):
|
def test_get_for_update_projects_blocks_developer(workspace):
|
||||||
developer = UserFactory.create()
|
developer = UserFactory.create()
|
||||||
WorkspaceUsers.add(developer, workspace.id, "developer")
|
WorkspaceUsers.add(developer, workspace.id, "developer")
|
||||||
|
|
||||||
with pytest.raises(UnauthorizedError):
|
with pytest.raises(UnauthorizedError):
|
||||||
Workspaces.get_for_update(developer, workspace.id)
|
Workspaces.get_for_update_projects(developer, workspace.id)
|
||||||
|
|
||||||
|
|
||||||
def test_can_create_workspace_user(workspace, workspace_owner):
|
def test_can_create_workspace_user(workspace, workspace_owner):
|
||||||
@ -234,3 +234,19 @@ def test_for_user_returns_all_workspaces_for_ccpo(workspace, workspace_owner):
|
|||||||
|
|
||||||
sams_workspaces = Workspaces.for_user(sam)
|
sams_workspaces = Workspaces.for_user(sam)
|
||||||
assert len(sams_workspaces) == 2
|
assert len(sams_workspaces) == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_for_update_information():
|
||||||
|
workspace_owner = UserFactory.create()
|
||||||
|
workspace = Workspaces.create(RequestFactory.create(creator=workspace_owner))
|
||||||
|
owner_ws = Workspaces.get_for_update_information(workspace_owner, workspace.id)
|
||||||
|
assert workspace == owner_ws
|
||||||
|
|
||||||
|
admin = UserFactory.create()
|
||||||
|
Workspaces.add_member(workspace, admin, "admin")
|
||||||
|
admin_ws = Workspaces.get_for_update_information(admin, workspace.id)
|
||||||
|
assert workspace == admin_ws
|
||||||
|
|
||||||
|
ccpo = UserFactory.from_atat_role("ccpo")
|
||||||
|
with pytest.raises(UnauthorizedError):
|
||||||
|
Workspaces.get_for_update_information(ccpo, workspace.id)
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from flask import url_for
|
||||||
|
|
||||||
from tests.factories import UserFactory, WorkspaceFactory
|
from tests.factories import UserFactory, WorkspaceFactory
|
||||||
from atst.domain.workspaces import Workspaces
|
from atst.domain.workspaces import Workspaces
|
||||||
from atst.models.workspace_user import WorkspaceUser
|
from atst.models.workspace_user import WorkspaceUser
|
||||||
@ -51,3 +53,17 @@ def test_user_without_permission_has_no_add_member_link(client, user_session):
|
|||||||
'href="/workspaces/{}/members/new"'.format(workspace.id).encode()
|
'href="/workspaces/{}/members/new"'.format(workspace.id).encode()
|
||||||
not in response.data
|
not in response.data
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_workspace_name(client, user_session):
|
||||||
|
user = UserFactory.create()
|
||||||
|
workspace = WorkspaceFactory.create()
|
||||||
|
Workspaces._create_workspace_role(user, workspace, "admin")
|
||||||
|
user_session(user)
|
||||||
|
response = client.post(
|
||||||
|
url_for("workspaces.edit_workspace", workspace_id=workspace.id),
|
||||||
|
data={"name": "a cool new name"},
|
||||||
|
follow_redirects=True,
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert workspace.name == "a cool new name"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user