reorganize task order routes

This commit is contained in:
dandds
2019-04-19 10:15:29 -04:00
parent 849c5d4b58
commit 782a532c32
31 changed files with 1424 additions and 1564 deletions

View File

@@ -6,7 +6,6 @@ portfolios_bp = Blueprint("portfolios", __name__)
from . import index
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

View File

@@ -21,19 +21,10 @@ def send_invite_email(owner_name, token, new_member_email):
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 in task_order.officers:
return redirect(
url_for(
"portfolios.view_task_order",
portfolio_id=task_order.portfolio_id,
task_order_id=task_order.id,
)
url_for("task_orders.view_task_order", task_order_id=task_order.id)
)
return redirect(

View File

@@ -1,321 +0,0 @@
from collections import defaultdict
from flask import g, redirect, render_template, url_for, request as http_request
from . import portfolios_bp
from atst.database import db
from atst.domain.authz import Authorization
from atst.domain.exceptions import NotFoundError, NoAccessError
from atst.domain.invitations import Invitations
from atst.domain.portfolios import Portfolios
from atst.domain.task_orders import TaskOrders, DD254s
from atst.utils.localization import translate
from atst.forms.dd_254 import DD254Form
from atst.forms.ko_review import KOReviewForm
from atst.forms.officers import EditTaskOrderOfficersForm
from atst.models.task_order import Status as TaskOrderStatus
from atst.utils.flash import formatted_flash as flash
from atst.services.invitation import (
update_officer_invitations,
OFFICER_INVITATIONS,
Invitation as InvitationService,
)
from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions
@portfolios_bp.route("/portfolios/<portfolio_id>/task_orders")
@user_can(Permissions.VIEW_PORTFOLIO_FUNDING, message="view portfolio funding")
def portfolio_funding(portfolio_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
task_orders_by_status = defaultdict(list)
serialize_task_order = lambda task_order: {
key: getattr(task_order, key)
for key in [
"id",
"budget",
"time_created",
"start_date",
"end_date",
"display_status",
"days_to_expiration",
"balance",
]
}
for task_order in portfolio.task_orders:
serialized_task_order = serialize_task_order(task_order)
serialized_task_order["url"] = url_for(
"portfolios.view_task_order",
portfolio_id=portfolio.id,
task_order_id=task_order.id,
)
task_orders_by_status[task_order.status].append(serialized_task_order)
active_task_orders = task_orders_by_status.get(TaskOrderStatus.ACTIVE, [])
total_balance = sum([task_order["balance"] for task_order in active_task_orders])
return render_template(
"portfolios/task_orders/index.html",
pending_task_orders=(
task_orders_by_status.get(TaskOrderStatus.STARTED, [])
+ task_orders_by_status.get(TaskOrderStatus.PENDING, [])
),
active_task_orders=active_task_orders,
expired_task_orders=task_orders_by_status.get(TaskOrderStatus.EXPIRED, []),
total_balance=total_balance,
)
@portfolios_bp.route("/portfolios/<portfolio_id>/task_order/<task_order_id>")
@user_can(Permissions.VIEW_TASK_ORDER_DETAILS, message="view task order details")
def view_task_order(portfolio_id, task_order_id):
task_order = TaskOrders.get(task_order_id, portfolio_id=portfolio_id)
to_form_complete = TaskOrders.all_sections_complete(task_order)
dd_254_complete = DD254s.is_complete(task_order.dd_254)
return render_template(
"portfolios/task_orders/show.html",
dd_254_complete=dd_254_complete,
is_cor=Authorization.is_cor(g.current_user, task_order),
is_ko=Authorization.is_ko(g.current_user, task_order),
is_so=Authorization.is_so(g.current_user, task_order),
is_to_signed=TaskOrders.is_signed_by_ko(task_order),
task_order=task_order,
to_form_complete=to_form_complete,
user=g.current_user,
)
def wrap_check_is_ko_or_cor(user, task_order_id=None, portfolio_id=None, **_kwargs):
task_order = TaskOrders.get(task_order_id, portfolio_id=portfolio_id)
Authorization.check_is_ko_or_cor(user, task_order)
return True
@portfolios_bp.route("/portfolios/<portfolio_id>/task_order/<task_order_id>/review")
@user_can(
None,
override=wrap_check_is_ko_or_cor,
message="view contracting officer review form",
)
def ko_review(portfolio_id, task_order_id):
task_order = TaskOrders.get(task_order_id, portfolio_id=portfolio_id)
if TaskOrders.all_sections_complete(task_order):
return render_template(
"/portfolios/task_orders/review.html",
task_order=task_order,
form=KOReviewForm(obj=task_order),
)
else:
raise NoAccessError("task_order")
@portfolios_bp.route(
"/portfolios/<portfolio_id>/task_order/<task_order_id>/resend_invite",
methods=["POST"],
)
@user_can(
Permissions.EDIT_TASK_ORDER_DETAILS, message="resend task order officer invites"
)
def resend_invite(portfolio_id, task_order_id):
invite_type = http_request.args.get("invite_type")
if invite_type not in OFFICER_INVITATIONS:
raise NotFoundError("invite_type")
invite_type_info = OFFICER_INVITATIONS[invite_type]
task_order = TaskOrders.get(task_order_id, portfolio_id=portfolio_id)
portfolio = Portfolios.get(g.current_user, portfolio_id)
officer = getattr(task_order, invite_type_info["role"])
if not officer:
raise NotFoundError("officer")
invitation = Invitations.lookup_by_portfolio_and_user(portfolio, officer)
if not invitation:
raise NotFoundError("invitation")
if not invitation.can_resend:
raise NoAccessError("invitation")
Invitations.revoke(token=invitation.token)
invite_service = InvitationService(
g.current_user,
invitation.portfolio_role,
invitation.email,
subject=invite_type_info["subject"],
email_template=invite_type_info["template"],
)
invite_service.invite()
flash(
"invitation_resent",
officer_type=translate(
"common.officer_helpers.underscore_to_friendly.{}".format(
invite_type_info["role"]
)
),
)
return redirect(
url_for(
"portfolios.task_order_invitations",
portfolio_id=portfolio_id,
task_order_id=task_order_id,
)
)
@portfolios_bp.route(
"/portfolios/<portfolio_id>/task_order/<task_order_id>/review", methods=["POST"]
)
@user_can(
None, override=wrap_check_is_ko_or_cor, message="submit contracting officer review"
)
def submit_ko_review(portfolio_id, task_order_id, form=None):
task_order = TaskOrders.get(task_order_id, portfolio_id=portfolio_id)
form_data = {**http_request.form, **http_request.files}
form = KOReviewForm(form_data)
if form.validate():
TaskOrders.update(task_order=task_order, **form.data)
if Authorization.is_ko(g.current_user, task_order) and TaskOrders.can_ko_sign(
task_order
):
return redirect(
url_for("task_orders.signature_requested", task_order_id=task_order_id)
)
else:
return redirect(
url_for(
"portfolios.view_task_order",
task_order_id=task_order_id,
portfolio_id=portfolio_id,
)
)
else:
return render_template(
"/portfolios/task_orders/review.html", task_order=task_order, form=form
)
@portfolios_bp.route(
"/portfolios/<portfolio_id>/task_order/<task_order_id>/invitations"
)
@user_can(
Permissions.EDIT_TASK_ORDER_DETAILS, message="view task order invitations page"
)
def task_order_invitations(portfolio_id, task_order_id):
task_order = TaskOrders.get(task_order_id, portfolio_id=portfolio_id)
form = EditTaskOrderOfficersForm(obj=task_order)
if TaskOrders.all_sections_complete(task_order):
return render_template(
"portfolios/task_orders/invitations.html",
task_order=task_order,
form=form,
user=g.current_user,
)
else:
raise NotFoundError("task_order")
@portfolios_bp.route(
"/portfolios/<portfolio_id>/task_order/<task_order_id>/invitations",
methods=["POST"],
)
@user_can(Permissions.EDIT_TASK_ORDER_DETAILS, message="edit task order invitations")
def edit_task_order_invitations(portfolio_id, task_order_id):
task_order = TaskOrders.get(task_order_id, portfolio_id=portfolio_id)
form = EditTaskOrderOfficersForm(formdata=http_request.form, obj=task_order)
if form.validate():
form.populate_obj(task_order)
db.session.add(task_order)
db.session.commit()
update_officer_invitations(g.current_user, task_order)
return redirect(
url_for(
"portfolios.task_order_invitations",
portfolio_id=portfolio_id,
task_order_id=task_order.id,
)
)
else:
return (
render_template(
"portfolios/task_orders/invitations.html",
task_order=task_order,
form=form,
),
400,
)
def so_review_form(task_order):
if task_order.dd_254:
dd_254 = task_order.dd_254
form = DD254Form(obj=dd_254)
form.required_distribution.data = dd_254.required_distribution
return form
else:
so = task_order.officer_dictionary("security_officer")
form_data = {
"certifying_official": "{}, {}".format(
so.get("last_name", ""), so.get("first_name", "")
),
"co_phone": so.get("phone_number", ""),
}
return DD254Form(data=form_data)
def wrap_check_is_so(user, task_order_id=None, portfolio_id=None, **_kwargs):
task_order = TaskOrders.get(task_order_id, portfolio_id=portfolio_id)
Authorization.check_is_so(user, task_order)
return True
@portfolios_bp.route("/portfolios/<portfolio_id>/task_order/<task_order_id>/dd254")
@user_can(None, override=wrap_check_is_so, message="view security officer review form")
def so_review(portfolio_id, task_order_id):
task_order = TaskOrders.get(task_order_id, portfolio_id=portfolio_id)
form = so_review_form(task_order)
return render_template(
"portfolios/task_orders/so_review.html", form=form, task_order=task_order
)
@portfolios_bp.route(
"/portfolios/<portfolio_id>/task_order/<task_order_id>/dd254", methods=["POST"]
)
@user_can(
None, override=wrap_check_is_so, message="submit security officer review form"
)
def submit_so_review(portfolio_id, task_order_id):
task_order = TaskOrders.get(task_order_id, portfolio_id=portfolio_id)
form = DD254Form(http_request.form)
if form.validate():
TaskOrders.add_dd_254(task_order, form.data)
# TODO: will redirect to download, sign, upload page
return redirect(
url_for(
"portfolios.view_task_order",
portfolio_id=task_order.portfolio.id,
task_order_id=task_order.id,
)
)
else:
return render_template(
"portfolios/task_orders/so_review.html", form=form, task_order=task_order
)

View File

@@ -2,7 +2,12 @@ from flask import Blueprint
task_orders_bp = Blueprint("task_orders", __name__)
from . import new
from . import index
from . import invite
from . import new
from . import invitations
from . import officer_reviews
from . import signing
from . import downloads
from atst.utils.context_processors import portfolio as portfolio_context_processor
task_orders_bp.context_processor(portfolio_context_processor)

View File

@@ -0,0 +1,56 @@
from io import BytesIO
from flask import Response, current_app as app
from . import task_orders_bp
from atst.domain.task_orders import TaskOrders
from atst.domain.exceptions import NotFoundError
from atst.utils.docx import Docx
from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions
@task_orders_bp.route("/task_orders/<task_order_id>/download_summary")
@user_can(Permissions.VIEW_TASK_ORDER_DETAILS, message="download task order summary")
def download_summary(task_order_id):
task_order = TaskOrders.get(task_order_id)
byte_str = BytesIO()
Docx.render(byte_str, data=task_order.to_dictionary())
filename = "{}.docx".format(task_order.portfolio_name)
return Response(
byte_str,
headers={"Content-Disposition": "attachment; filename={}".format(filename)},
mimetype="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
)
def send_file(attachment):
generator = app.csp.files.download(attachment.object_name)
return Response(
generator,
headers={
"Content-Disposition": "attachment; filename={}".format(attachment.filename)
},
)
@task_orders_bp.route("/task_orders/<task_order_id>/csp_estimate")
@user_can(
Permissions.VIEW_TASK_ORDER_DETAILS,
message="download task order cloud service provider estimate",
)
def download_csp_estimate(task_order_id):
task_order = TaskOrders.get(task_order_id)
if task_order.csp_estimate:
return send_file(task_order.csp_estimate)
else:
raise NotFoundError("task_order CSP estimate")
@task_orders_bp.route("/task_orders/<task_order_id>/pdf")
@user_can(Permissions.VIEW_TASK_ORDER_DETAILS, message="download task order PDF")
def download_task_order_pdf(task_order_id):
task_order = TaskOrders.get(task_order_id)
if task_order.pdf:
return send_file(task_order.pdf)
else:
raise NotFoundError("task_order pdf")

View File

@@ -1,56 +1,74 @@
from io import BytesIO
from flask import Response, current_app as app
from collections import defaultdict
from flask import g, render_template, url_for
from . import task_orders_bp
from atst.domain.task_orders import TaskOrders
from atst.domain.exceptions import NotFoundError
from atst.utils.docx import Docx
from atst.domain.authz import Authorization
from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions
from atst.domain.portfolios import Portfolios
from atst.domain.task_orders import TaskOrders, DD254s
from atst.models import Permissions
from atst.models.task_order import Status as TaskOrderStatus
@task_orders_bp.route("/task_orders/download_summary/<task_order_id>")
@user_can(Permissions.VIEW_TASK_ORDER_DETAILS, message="download task order summary")
def download_summary(task_order_id):
@task_orders_bp.route("/task_orders/<task_order_id>")
@user_can(Permissions.VIEW_TASK_ORDER_DETAILS, message="view task order details")
def view_task_order(task_order_id):
task_order = TaskOrders.get(task_order_id)
byte_str = BytesIO()
Docx.render(byte_str, data=task_order.to_dictionary())
filename = "{}.docx".format(task_order.portfolio_name)
return Response(
byte_str,
headers={"Content-Disposition": "attachment; filename={}".format(filename)},
mimetype="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
to_form_complete = TaskOrders.all_sections_complete(task_order)
dd_254_complete = DD254s.is_complete(task_order.dd_254)
return render_template(
"portfolios/task_orders/show.html",
dd_254_complete=dd_254_complete,
is_cor=Authorization.is_cor(g.current_user, task_order),
is_ko=Authorization.is_ko(g.current_user, task_order),
is_so=Authorization.is_so(g.current_user, task_order),
is_to_signed=TaskOrders.is_signed_by_ko(task_order),
task_order=task_order,
to_form_complete=to_form_complete,
user=g.current_user,
)
def send_file(attachment):
generator = app.csp.files.download(attachment.object_name)
return Response(
generator,
headers={
"Content-Disposition": "attachment; filename={}".format(attachment.filename)
},
def serialize_task_order(task_order):
return {
key: getattr(task_order, key)
for key in [
"id",
"budget",
"time_created",
"start_date",
"end_date",
"display_status",
"days_to_expiration",
"balance",
]
}
@task_orders_bp.route("/portfolios/<portfolio_id>/task_orders")
@user_can(Permissions.VIEW_PORTFOLIO_FUNDING, message="view portfolio funding")
def portfolio_funding(portfolio_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
task_orders_by_status = defaultdict(list)
for task_order in portfolio.task_orders:
serialized_task_order = serialize_task_order(task_order)
serialized_task_order["url"] = url_for(
"task_orders.view_task_order", task_order_id=task_order.id
)
task_orders_by_status[task_order.status].append(serialized_task_order)
active_task_orders = task_orders_by_status.get(TaskOrderStatus.ACTIVE, [])
total_balance = sum([task_order["balance"] for task_order in active_task_orders])
return render_template(
"portfolios/task_orders/index.html",
pending_task_orders=(
task_orders_by_status.get(TaskOrderStatus.STARTED, [])
+ task_orders_by_status.get(TaskOrderStatus.PENDING, [])
),
active_task_orders=active_task_orders,
expired_task_orders=task_orders_by_status.get(TaskOrderStatus.EXPIRED, []),
total_balance=total_balance,
)
@task_orders_bp.route("/task_orders/csp_estimate/<task_order_id>")
@user_can(
Permissions.VIEW_TASK_ORDER_DETAILS,
message="download task order cloud service provider estimate",
)
def download_csp_estimate(task_order_id):
task_order = TaskOrders.get(task_order_id)
if task_order.csp_estimate:
return send_file(task_order.csp_estimate)
else:
raise NotFoundError("task_order CSP estimate")
@task_orders_bp.route("/task_orders/pdf/<task_order_id>")
@user_can(Permissions.VIEW_TASK_ORDER_DETAILS, message="download task order PDF")
def download_task_order_pdf(task_order_id):
task_order = TaskOrders.get(task_order_id)
if task_order.pdf:
return send_file(task_order.pdf)
else:
raise NotFoundError("task_order pdf")

View File

@@ -0,0 +1,132 @@
from flask import g, redirect, render_template, url_for, request as http_request
from . import task_orders_bp
from atst.domain.task_orders import TaskOrders
from atst.utils.flash import formatted_flash as flash
from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions
from atst.database import db
from atst.domain.exceptions import NotFoundError, NoAccessError
from atst.domain.invitations import Invitations
from atst.domain.portfolios import Portfolios
from atst.utils.localization import translate
from atst.forms.officers import EditTaskOrderOfficersForm
from atst.services.invitation import (
update_officer_invitations,
OFFICER_INVITATIONS,
Invitation as InvitationService,
)
@task_orders_bp.route("/task_orders/<task_order_id>/invite", methods=["POST"])
@user_can(Permissions.EDIT_TASK_ORDER_DETAILS, message="invite task order officers")
def invite(task_order_id):
task_order = TaskOrders.get(task_order_id)
if TaskOrders.all_sections_complete(task_order):
update_officer_invitations(g.current_user, task_order)
portfolio = task_order.portfolio
flash("task_order_congrats", portfolio=portfolio)
return redirect(
url_for("task_orders.view_task_order", task_order_id=task_order.id)
)
else:
flash("task_order_incomplete")
return redirect(
url_for("task_orders.new", screen=4, task_order_id=task_order.id)
)
@task_orders_bp.route("/task_orders/<task_order_id>/resend_invite", methods=["POST"])
@user_can(
Permissions.EDIT_TASK_ORDER_DETAILS, message="resend task order officer invites"
)
def resend_invite(task_order_id):
invite_type = http_request.args.get("invite_type")
if invite_type not in OFFICER_INVITATIONS:
raise NotFoundError("invite_type")
invite_type_info = OFFICER_INVITATIONS[invite_type]
task_order = TaskOrders.get(task_order_id)
portfolio = Portfolios.get(g.current_user, task_order.portfolio_id)
officer = getattr(task_order, invite_type_info["role"])
if not officer:
raise NotFoundError("officer")
invitation = Invitations.lookup_by_portfolio_and_user(portfolio, officer)
if not invitation:
raise NotFoundError("invitation")
if not invitation.can_resend:
raise NoAccessError("invitation")
Invitations.revoke(token=invitation.token)
invite_service = InvitationService(
g.current_user,
invitation.portfolio_role,
invitation.email,
subject=invite_type_info["subject"],
email_template=invite_type_info["template"],
)
invite_service.invite()
flash(
"invitation_resent",
officer_type=translate(
"common.officer_helpers.underscore_to_friendly.{}".format(
invite_type_info["role"]
)
),
)
return redirect(url_for("task_orders.invitations", task_order_id=task_order_id))
@task_orders_bp.route("/task_orders/<task_order_id>/invitations")
@user_can(
Permissions.EDIT_TASK_ORDER_DETAILS, message="view task order invitations page"
)
def invitations(task_order_id):
task_order = TaskOrders.get(task_order_id)
form = EditTaskOrderOfficersForm(obj=task_order)
if TaskOrders.all_sections_complete(task_order):
return render_template(
"portfolios/task_orders/invitations.html",
task_order=task_order,
form=form,
user=g.current_user,
)
else:
raise NotFoundError("task_order")
@task_orders_bp.route("/task_orders/<task_order_id>/invitations/edit", methods=["POST"])
@user_can(Permissions.EDIT_TASK_ORDER_DETAILS, message="edit task order invitations")
def invitations_edit(task_order_id):
task_order = TaskOrders.get(task_order_id)
form = EditTaskOrderOfficersForm(formdata=http_request.form, obj=task_order)
if form.validate():
form.populate_obj(task_order)
db.session.add(task_order)
db.session.commit()
update_officer_invitations(g.current_user, task_order)
return redirect(url_for("task_orders.invitations", task_order_id=task_order.id))
else:
return (
render_template(
"portfolios/task_orders/invitations.html",
task_order=task_order,
form=form,
),
400,
)

View File

@@ -1,31 +0,0 @@
from flask import redirect, url_for, g
from . import task_orders_bp
from atst.domain.task_orders import TaskOrders
from atst.utils.flash import formatted_flash as flash
from atst.services.invitation import update_officer_invitations
from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions
@task_orders_bp.route("/task_orders/invite/<task_order_id>", methods=["POST"])
@user_can(Permissions.EDIT_TASK_ORDER_DETAILS, message="invite task order officers")
def invite(task_order_id):
task_order = TaskOrders.get(task_order_id)
if TaskOrders.all_sections_complete(task_order):
update_officer_invitations(g.current_user, task_order)
portfolio = task_order.portfolio
flash("task_order_congrats", portfolio=portfolio)
return redirect(
url_for(
"portfolios.view_task_order",
portfolio_id=task_order.portfolio_id,
task_order_id=task_order.id,
)
)
else:
flash("task_order_incomplete")
return redirect(
url_for("task_orders.new", screen=4, task_order_id=task_order.id)
)

View File

@@ -305,9 +305,7 @@ def new(screen, task_order_id=None, portfolio_id=None):
if http_request.args.get("ko_edit"):
template_args["ko_edit"] = True
template_args["next"] = url_for(
"portfolios.ko_review",
portfolio_id=workflow.task_order.portfolio.id,
task_order_id=task_order_id,
"task_orders.ko_review", task_order_id=task_order_id
)
url_args["next"] = template_args["next"]

View File

@@ -0,0 +1,117 @@
from flask import g, redirect, render_template, url_for, request as http_request
from . import task_orders_bp
from atst.domain.authz import Authorization
from atst.domain.exceptions import NoAccessError
from atst.domain.task_orders import TaskOrders
from atst.forms.dd_254 import DD254Form
from atst.forms.ko_review import KOReviewForm
from atst.domain.authz.decorator import user_can_access_decorator as user_can
def wrap_check_is_ko_or_cor(user, task_order_id=None, **_kwargs):
task_order = TaskOrders.get(task_order_id)
Authorization.check_is_ko_or_cor(user, task_order)
return True
@task_orders_bp.route("/task_orders/<task_order_id>/review")
@user_can(
None,
override=wrap_check_is_ko_or_cor,
message="view contracting officer review form",
)
def ko_review(task_order_id):
task_order = TaskOrders.get(task_order_id)
if TaskOrders.all_sections_complete(task_order):
return render_template(
"/portfolios/task_orders/review.html",
task_order=task_order,
form=KOReviewForm(obj=task_order),
)
else:
raise NoAccessError("task_order")
@task_orders_bp.route("/task_orders/<task_order_id>/review", methods=["POST"])
@user_can(
None, override=wrap_check_is_ko_or_cor, message="submit contracting officer review"
)
def submit_ko_review(task_order_id, form=None):
task_order = TaskOrders.get(task_order_id)
form_data = {**http_request.form, **http_request.files}
form = KOReviewForm(form_data)
if form.validate():
TaskOrders.update(task_order=task_order, **form.data)
if Authorization.is_ko(g.current_user, task_order) and TaskOrders.can_ko_sign(
task_order
):
return redirect(
url_for("task_orders.signature_requested", task_order_id=task_order_id)
)
else:
return redirect(
url_for("task_orders.view_task_order", task_order_id=task_order_id)
)
else:
return render_template(
"/portfolios/task_orders/review.html", task_order=task_order, form=form
)
def so_review_form(task_order):
if task_order.dd_254:
dd_254 = task_order.dd_254
form = DD254Form(obj=dd_254)
form.required_distribution.data = dd_254.required_distribution
return form
else:
so = task_order.officer_dictionary("security_officer")
form_data = {
"certifying_official": "{}, {}".format(
so.get("last_name", ""), so.get("first_name", "")
),
"co_phone": so.get("phone_number", ""),
}
return DD254Form(data=form_data)
def wrap_check_is_so(user, task_order_id=None, **_kwargs):
task_order = TaskOrders.get(task_order_id)
Authorization.check_is_so(user, task_order)
return True
@task_orders_bp.route("/task_orders/<task_order_id>/dd254")
@user_can(None, override=wrap_check_is_so, message="view security officer review form")
def so_review(task_order_id):
task_order = TaskOrders.get(task_order_id)
form = so_review_form(task_order)
return render_template(
"portfolios/task_orders/so_review.html", form=form, task_order=task_order
)
@task_orders_bp.route("/task_orders/<task_order_id>/dd254", methods=["POST"])
@user_can(
None, override=wrap_check_is_so, message="submit security officer review form"
)
def submit_so_review(task_order_id):
task_order = TaskOrders.get(task_order_id)
form = DD254Form(http_request.form)
if form.validate():
TaskOrders.add_dd_254(task_order, form.data)
# TODO: will redirect to download, sign, upload page
return redirect(
url_for("task_orders.view_task_order", task_order_id=task_order.id)
)
else:
return render_template(
"portfolios/task_orders/so_review.html", form=form, task_order=task_order
)

View File

@@ -72,11 +72,7 @@ def record_signature(task_order_id):
flash("task_order_signed")
return redirect(
url_for(
"portfolios.view_task_order",
portfolio_id=task_order.portfolio_id,
task_order_id=task_order.id,
)
url_for("task_orders.view_task_order", task_order_id=task_order.id)
)
else:
return (