workspace -> portfolio everywhere

This commit is contained in:
dandds
2019-01-11 09:58:00 -05:00
parent 3fc323d785
commit d3d36822df
122 changed files with 2156 additions and 2129 deletions

View File

@@ -0,0 +1,41 @@
from flask import Blueprint, request as http_request, g, render_template
portfolios_bp = Blueprint("portfolios", __name__)
from . import index
from . import applications
from . import members
from . import invitations
from . import task_orders
from atst.domain.exceptions import UnauthorizedError
from atst.domain.portfolios import Portfolios
from atst.domain.authz import Authorization
from atst.models.permissions import Permissions
@portfolios_bp.context_processor
def portfolio():
portfolios = Portfolios.for_user(g.current_user)
portfolio = None
if "portfolio_id" in http_request.view_args:
try:
portfolio = Portfolios.get(
g.current_user, http_request.view_args["portfolio_id"]
)
portfolios = [ws for ws in portfolios if not ws.id == portfolio.id]
except UnauthorizedError:
pass
def user_can(permission):
if portfolio:
return Authorization.has_portfolio_permission(
g.current_user, portfolio, permission
)
return False
return {
"portfolio": portfolio,
"portfolios": portfolios,
"permissions": Permissions,
"user_can": user_can,
}

View File

@@ -0,0 +1,102 @@
from flask import (
current_app as app,
g,
redirect,
render_template,
request as http_request,
url_for,
)
from . import portfolios_bp
from atst.domain.environment_roles import EnvironmentRoles
from atst.domain.exceptions import UnauthorizedError
from atst.domain.applications import Applications
from atst.domain.portfolios import Portfolios
from atst.forms.application import NewApplicationForm, ApplicationForm
@portfolios_bp.route("/portfolios/<portfolio_id>/applications")
def portfolio_applications(portfolio_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
return render_template("portfolios/applications/index.html", portfolio=portfolio)
@portfolios_bp.route("/portfolios/<portfolio_id>/applications/new")
def new_application(portfolio_id):
portfolio = Portfolios.get_for_update_applications(g.current_user, portfolio_id)
form = NewApplicationForm()
return render_template(
"portfolios/applications/new.html", portfolio=portfolio, form=form
)
@portfolios_bp.route("/portfolios/<portfolio_id>/applications/new", methods=["POST"])
def create_application(portfolio_id):
portfolio = Portfolios.get_for_update_applications(g.current_user, portfolio_id)
form = NewApplicationForm(http_request.form)
if form.validate():
application_data = form.data
Applications.create(
g.current_user,
portfolio,
application_data["name"],
application_data["description"],
application_data["environment_names"],
)
return redirect(
url_for("portfolios.portfolio_applications", portfolio_id=portfolio.id)
)
else:
return render_template(
"portfolios/applications/new.html", portfolio=portfolio, form=form
)
@portfolios_bp.route("/portfolios/<portfolio_id>/applications/<application_id>/edit")
def edit_application(portfolio_id, application_id):
portfolio = Portfolios.get_for_update_applications(g.current_user, portfolio_id)
application = Applications.get(g.current_user, portfolio, application_id)
form = ApplicationForm(name=application.name, description=application.description)
return render_template(
"portfolios/applications/edit.html",
portfolio=portfolio,
application=application,
form=form,
)
@portfolios_bp.route(
"/portfolios/<portfolio_id>/applications/<application_id>/edit", methods=["POST"]
)
def update_application(portfolio_id, application_id):
portfolio = Portfolios.get_for_update_applications(g.current_user, portfolio_id)
application = Applications.get(g.current_user, portfolio, application_id)
form = ApplicationForm(http_request.form)
if form.validate():
application_data = form.data
Applications.update(g.current_user, portfolio, application, application_data)
return redirect(
url_for("portfolios.portfolio_applications", portfolio_id=portfolio.id)
)
else:
return render_template(
"portfolios/applications/edit.html",
portfolio=portfolio,
application=application,
form=form,
)
@portfolios_bp.route("/portfolios/<portfolio_id>/environments/<environment_id>/access")
def access_environment(portfolio_id, environment_id):
env_role = EnvironmentRoles.get(g.current_user.id, environment_id)
if not env_role:
raise UnauthorizedError(
g.current_user, "access environment {}".format(environment_id)
)
else:
token = app.csp.cloud.get_access_token(env_role)
return redirect(url_for("atst.csp_environment_access", token=token))

View File

@@ -0,0 +1,102 @@
from datetime import date, timedelta
from flask import render_template, request as http_request, g, redirect, url_for
from . import portfolios_bp
from atst.domain.reports import Reports
from atst.domain.portfolios import Portfolios
from atst.domain.audit_log import AuditLog
from atst.domain.authz import Authorization
from atst.domain.common import Paginator
from atst.forms.portfolio import PortfolioForm
from atst.models.permissions import Permissions
@portfolios_bp.route("/portfolios")
def portfolios():
portfolios = Portfolios.for_user(g.current_user)
return render_template("portfolios/index.html", page=5, portfolios=portfolios)
@portfolios_bp.route("/portfolios/<portfolio_id>/edit")
def portfolio(portfolio_id):
portfolio = Portfolios.get_for_update_information(g.current_user, portfolio_id)
form = PortfolioForm(data={"name": portfolio.name})
return render_template("portfolios/edit.html", form=form, portfolio=portfolio)
@portfolios_bp.route("/portfolios/<portfolio_id>/edit", methods=["POST"])
def edit_portfolio(portfolio_id):
portfolio = Portfolios.get_for_update_information(g.current_user, portfolio_id)
form = PortfolioForm(http_request.form)
if form.validate():
Portfolios.update(portfolio, form.data)
return redirect(
url_for("portfolios.portfolio_applications", portfolio_id=portfolio.id)
)
else:
return render_template("portfolios/edit.html", form=form, portfolio=portfolio)
@portfolios_bp.route("/portfolios/<portfolio_id>")
def show_portfolio(portfolio_id):
return redirect(
url_for("portfolios.portfolio_applications", portfolio_id=portfolio_id)
)
@portfolios_bp.route("/portfolios/<portfolio_id>/reports")
def portfolio_reports(portfolio_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
Authorization.check_portfolio_permission(
g.current_user,
portfolio,
Permissions.VIEW_USAGE_DOLLARS,
"view portfolio reports",
)
today = date.today()
month = http_request.args.get("month", today.month)
year = http_request.args.get("year", today.year)
current_month = date(int(year), int(month), 15)
prev_month = current_month - timedelta(days=28)
two_months_ago = prev_month - timedelta(days=28)
expiration_date = (
portfolio.legacy_task_order and portfolio.legacy_task_order.expiration_date
)
if expiration_date:
remaining_difference = expiration_date - today
remaining_days = remaining_difference.days
else:
remaining_days = None
return render_template(
"portfolios/reports/index.html",
cumulative_budget=Reports.cumulative_budget(portfolio),
portfolio_totals=Reports.portfolio_totals(portfolio),
monthly_totals=Reports.monthly_totals(portfolio),
jedi_request=portfolio.request,
legacy_task_order=portfolio.legacy_task_order,
current_month=current_month,
prev_month=prev_month,
two_months_ago=two_months_ago,
expiration_date=expiration_date,
remaining_days=remaining_days,
)
@portfolios_bp.route("/portfolios/<portfolio_id>/activity")
def portfolio_activity(portfolio_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
pagination_opts = Paginator.get_pagination_opts(http_request)
audit_events = AuditLog.get_portfolio_events(
g.current_user, portfolio, pagination_opts
)
return render_template(
"portfolios/activity/index.html",
portfolio_name=portfolio.name,
portfolio_id=portfolio_id,
audit_events=audit_events,
)

View File

@@ -0,0 +1,64 @@
from flask import g, redirect, url_for, render_template
from . import portfolios_bp
from atst.domain.portfolios import Portfolios
from atst.domain.invitations import Invitations
from atst.queue import queue
from atst.utils.flash import formatted_flash as flash
def send_invite_email(owner_name, token, new_member_email):
body = render_template("emails/invitation.txt", owner=owner_name, token=token)
queue.send_mail(
[new_member_email],
"{} has invited you to a JEDI Cloud Portfolio".format(owner_name),
body,
)
@portfolios_bp.route("/portfolios/invitations/<token>", methods=["GET"])
def accept_invitation(token):
invite = Invitations.accept(g.current_user, token)
# TODO: this will eventually redirect to different places depending on
# whether the user is an officer for the TO and what kind of officer they
# are. It will also have to manage cases like:
# - the logged-in user has multiple roles on the TO (e.g., KO and COR)
# - the logged-in user has officer roles on multiple unsigned TOs
for task_order in invite.portfolio.task_orders:
if g.current_user == task_order.contracting_officer:
return redirect(
url_for("task_orders.new", screen=4, task_order_id=task_order.id)
)
elif g.current_user == task_order.contracting_officer_representative:
return redirect(
url_for("task_orders.new", screen=4, task_order_id=task_order.id)
)
elif g.current_user == task_order.security_officer:
return redirect(
url_for("task_orders.new", screen=4, task_order_id=task_order.id)
)
return redirect(
url_for("portfolios.show_portfolio", portfolio_id=invite.portfolio.id)
)
@portfolios_bp.route(
"/portfolios/<portfolio_id>/invitations/<token>/revoke", methods=["POST"]
)
def revoke_invitation(portfolio_id, token):
portfolio = Portfolios.get_for_update_member(g.current_user, portfolio_id)
Invitations.revoke(token)
return redirect(url_for("portfolios.portfolio_members", portfolio_id=portfolio.id))
@portfolios_bp.route(
"/portfolios/<portfolio_id>/invitations/<token>/resend", methods=["POST"]
)
def resend_invitation(portfolio_id, token):
invite = Invitations.resend(g.current_user, portfolio_id, token)
send_invite_email(g.current_user.full_name, invite.token, invite.email)
flash("resend_portfolio_invitation", user_name=invite.user_name)
return redirect(url_for("portfolios.portfolio_members", portfolio_id=portfolio_id))

View File

@@ -0,0 +1,185 @@
import re
from flask import render_template, request as http_request, g, redirect, url_for
from . import portfolios_bp
from atst.domain.exceptions import AlreadyExistsError
from atst.domain.applications import Applications
from atst.domain.portfolios import Portfolios
from atst.domain.portfolio_roles import PortfolioRoles, MEMBER_STATUS_CHOICES
from atst.domain.environments import Environments
from atst.domain.environment_roles import EnvironmentRoles
from atst.services.invitation import Invitation as InvitationService
from atst.forms.new_member import NewMemberForm
from atst.forms.edit_member import EditMemberForm
from atst.forms.data import (
ENVIRONMENT_ROLES,
ENV_ROLE_MODAL_DESCRIPTION,
WORKSPACE_ROLE_DEFINITIONS,
)
from atst.domain.authz import Authorization
from atst.models.permissions import Permissions
from atst.utils.flash import formatted_flash as flash
@portfolios_bp.route("/portfolios/<portfolio_id>/members")
def portfolio_members(portfolio_id):
portfolio = Portfolios.get_with_members(g.current_user, portfolio_id)
new_member_name = http_request.args.get("newMemberName")
new_member = next(
filter(lambda m: m.user_name == new_member_name, portfolio.members), None
)
members_list = [
{
"name": k.user_name,
"status": k.display_status,
"id": k.user_id,
"role": k.role_displayname,
"num_env": k.num_environment_roles,
"edit_link": url_for(
"portfolios.view_member", portfolio_id=portfolio.id, member_id=k.user_id
),
}
for k in portfolio.members
]
return render_template(
"portfolios/members/index.html",
portfolio=portfolio,
role_choices=WORKSPACE_ROLE_DEFINITIONS,
status_choices=MEMBER_STATUS_CHOICES,
members=members_list,
new_member=new_member,
)
@portfolios_bp.route("/portfolios/<portfolio_id>/members/new")
def new_member(portfolio_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
form = NewMemberForm()
return render_template(
"portfolios/members/new.html", portfolio=portfolio, form=form
)
@portfolios_bp.route("/portfolios/<portfolio_id>/members/new", methods=["POST"])
def create_member(portfolio_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
form = NewMemberForm(http_request.form)
if form.validate():
try:
member = Portfolios.create_member(g.current_user, portfolio, form.data)
invite_service = InvitationService(
g.current_user, member, form.data.get("email")
)
invite_service.invite()
flash("new_portfolio_member", new_member=new_member, portfolio=portfolio)
return redirect(
url_for("portfolios.portfolio_members", portfolio_id=portfolio.id)
)
except AlreadyExistsError:
return render_template(
"error.html", message="There was an error processing your request."
)
else:
return render_template(
"portfolios/members/new.html", portfolio=portfolio, form=form
)
@portfolios_bp.route("/portfolios/<portfolio_id>/members/<member_id>/member_edit")
def view_member(portfolio_id, member_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
Authorization.check_portfolio_permission(
g.current_user,
portfolio,
Permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE,
"edit this portfolio user",
)
member = PortfolioRoles.get(portfolio_id, member_id)
applications = Applications.get_all(g.current_user, member, portfolio)
form = EditMemberForm(portfolio_role=member.role_name)
editable = g.current_user == member.user
can_revoke_access = Portfolios.can_revoke_access_for(portfolio, member)
if member.has_dod_id_error:
flash("portfolio_member_dod_id_error")
return render_template(
"portfolios/members/edit.html",
portfolio=portfolio,
member=member,
applications=applications,
form=form,
choices=ENVIRONMENT_ROLES,
env_role_modal_description=ENV_ROLE_MODAL_DESCRIPTION,
EnvironmentRoles=EnvironmentRoles,
editable=editable,
can_revoke_access=can_revoke_access,
)
@portfolios_bp.route(
"/portfolios/<portfolio_id>/members/<member_id>/member_edit", methods=["POST"]
)
def update_member(portfolio_id, member_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
Authorization.check_portfolio_permission(
g.current_user,
portfolio,
Permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE,
"edit this portfolio user",
)
member = PortfolioRoles.get(portfolio_id, member_id)
ids_and_roles = []
form_dict = http_request.form.to_dict()
for entry in form_dict:
if re.match("env_", entry):
env_id = entry[4:]
env_role = form_dict[entry] or None
ids_and_roles.append({"id": env_id, "role": env_role})
form = EditMemberForm(http_request.form)
if form.validate():
new_role_name = None
if form.data["portfolio_role"] != member.role.name:
member = Portfolios.update_member(
g.current_user, portfolio, member, form.data["portfolio_role"]
)
new_role_name = member.role_displayname
flash(
"portfolio_role_updated",
member_name=member.user_name,
updated_role=new_role_name,
)
updated_roles = Environments.update_environment_roles(
g.current_user, portfolio, member, ids_and_roles
)
if updated_roles:
flash("environment_access_changed")
return redirect(
url_for("portfolios.portfolio_members", portfolio_id=portfolio.id)
)
else:
return render_template(
"portfolios/members/edit.html",
form=form,
portfolio=portfolio,
member=member,
)
@portfolios_bp.route(
"/portfolios/<portfolio_id>/members/<member_id>/revoke_access", methods=["POST"]
)
def revoke_access(portfolio_id, member_id):
revoked_role = Portfolios.revoke_access(g.current_user, portfolio_id, member_id)
flash("revoked_portfolio_access", member_name=revoked_role.user.full_name)
return redirect(url_for("portfolios.portfolio_members", portfolio_id=portfolio_id))

View File

@@ -0,0 +1,20 @@
from flask import g, render_template
from . import portfolios_bp
from atst.domain.task_orders import TaskOrders
from atst.domain.portfolios import Portfolios
@portfolios_bp.route("/portfolios/<portfolio_id>/task_orders")
def portfolio_task_orders(portfolio_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
return render_template("portfolios/task_orders/index.html", portfolio=portfolio)
@portfolios_bp.route("/portfolios/<portfolio_id>/task_order/<task_order_id>")
def view_task_order(portfolio_id, task_order_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
task_order = TaskOrders.get(task_order_id)
return render_template(
"portfolios/task_orders/show.html", portfolio=portfolio, task_order=task_order
)