Edit workspace #160300728
This commit is contained in:
dandds 2018-09-17 15:45:35 -04:00 committed by GitHub
commit f984c95060
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 202 additions and 9 deletions

View File

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

View File

@ -27,7 +27,7 @@ class Workspaces(object):
return ScopedWorkspace(user, workspace)
@classmethod
def get_for_update(cls, user, workspace_id):
def get_for_update_projects(cls, user, workspace_id):
workspace = WorkspacesQuery.get(workspace_id)
Authorization.check_workspace_permission(
user, workspace, Permissions.ADD_APPLICATION_IN_WORKSPACE, "add project"
@ -35,6 +35,18 @@ class Workspaces(object):
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
def get_by_request(cls, request):
return WorkspacesQuery.get_by_request(request)
@ -98,3 +110,10 @@ class Workspaces(object):
workspace_role = WorkspacesQuery.create_workspace_role(user, role, workspace)
WorkspacesQuery.add_and_commit(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
View 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",
)
],
)

View File

@ -20,6 +20,7 @@ class Permissions(object):
VIEW_ASSIGNED_ATAT_ROLE_CONFIGURATIONS = "view_assigned_atat_role_configurations"
VIEW_ASSIGNED_CSP_ROLE_CONFIGURATIONS = "view_assigned_csp_role_configurations"
EDIT_WORKSPACE_INFORMATION = "edit_workspace_information"
DEACTIVATE_WORKSPACE = "deactivate_workspace"
VIEW_ATAT_PERMISSIONS = "view_atat_permissions"
TRANSFER_OWNERSHIP_OF_WORKSPACE = "transfer_ownership_of_workspace"

View File

@ -17,6 +17,7 @@ from atst.domain.workspace_users import WorkspaceUsers
from atst.forms.new_project import NewProjectForm
from atst.forms.new_member import NewMemberForm
from atst.forms.edit_member import EditMemberForm
from atst.forms.workspace import WorkspaceForm
from atst.domain.authz import Authorization
from atst.models.permissions import Permissions
@ -50,12 +51,32 @@ def 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")
def workspace_projects(workspace_id):
workspace = Workspaces.get(g.current_user, workspace_id)
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>")
def show_workspace(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")
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()
return render_template(
"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"])
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)
if form.validate():
@ -130,7 +151,7 @@ def create_project(workspace_id):
@bp.route("/workspaces/<workspace_id>/projects/<project_id>/edit")
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)
form = NewProjectForm(
name=project.name,

View File

@ -78,5 +78,11 @@ export default {
match: /[0-9]{2}\w?$/,
unmask: [],
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'
},
}

View File

@ -194,6 +194,7 @@
&--validation {
&--anything,
&--workspaceName,
&--email {
input {
max-width: 30em;

View File

@ -35,5 +35,15 @@
href=url_for("workspaces.workspace_reports", workspace_id=workspace.id),
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>
</nav>

View 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 %}

View File

@ -63,16 +63,16 @@ def test_workspaces_get_ensures_user_is_in_workspace(workspace, workspace_owner)
Workspaces.get(outside_user, workspace.id)
def test_get_for_update_allows_owner(workspace, workspace_owner):
Workspaces.get_for_update(workspace_owner, workspace.id)
def test_get_for_update_projects_allows_owner(workspace, workspace_owner):
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()
WorkspaceUsers.add(developer, workspace.id, "developer")
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):
@ -234,3 +234,19 @@ def test_for_user_returns_all_workspaces_for_ccpo(workspace, workspace_owner):
sams_workspaces = Workspaces.for_user(sam)
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)

View File

@ -1,3 +1,5 @@
from flask import url_for
from tests.factories import UserFactory, WorkspaceFactory
from atst.domain.workspaces import Workspaces
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()
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"