use flask flash for notifications

This commit is contained in:
dandds
2018-11-30 15:53:35 -05:00
parent 6509c5a249
commit a2d6d59ca4
31 changed files with 203 additions and 209 deletions

View File

@@ -5,13 +5,14 @@ from flask import current_app as app
from jinja2.exceptions import TemplateNotFound
import pendulum
import os
from werkzeug.exceptions import NotFound
from atst.domain.requests import Requests
from atst.domain.users import Users
from atst.domain.authnid import AuthenticationContext
from atst.domain.audit_log import AuditLog
from atst.domain.auth import logout as _logout
from werkzeug.exceptions import NotFound
from atst.utils.flash import formatted_flash as flash
bp = Blueprint("atst", __name__)
@@ -28,10 +29,9 @@ def root():
redirect_url,
"?{}".format(url.urlencode({"next": request.args.get("next")})),
)
flash("login_next")
return render_template(
"login.html", redirect=bool(request.args.get("next")), redirect_url=redirect_url
)
return render_template("login.html", redirect_url=redirect_url)
@bp.route("/help")

View File

@@ -9,6 +9,7 @@ from atst.domain.invitations import (
WrongUserError as InvitationWrongUserError,
)
from atst.domain.workspaces import WorkspaceError
from atst.utils.flash import formatted_flash as flash
def log_error(e):
@@ -39,7 +40,8 @@ def make_error_pages(app):
# pylint: disable=unused-variable
def session_expired(e):
log_error(e)
url_args = {"sessionExpired": True, "next": request.path}
url_args = {"next": request.path}
flash("session_expired")
if request.method == "POST":
url_args[app.form_cache.PARAM_NAME] = app.form_cache.write(request.form)
return redirect(url_for("atst.root", **url_args))

View File

@@ -13,6 +13,7 @@ from atst.domain.requests import Requests
from atst.domain.exceptions import NotFoundError
from atst.forms.ccpo_review import CCPOReviewForm
from atst.forms.internal_comment import InternalCommentForm
from atst.utils.flash import formatted_flash as flash
def map_ccpo_authorizing(user):
@@ -63,6 +64,7 @@ def submit_approval(request_id):
return redirect(url_for("requests.requests_index"))
else:
flash("form_errors")
return render_approval(request, form)
@@ -94,4 +96,5 @@ def create_internal_comment(request_id):
url_for("requests.approval", request_id=request_id, _anchor="ccpo-notes")
)
else:
flash("form_errors")
return render_approval(request, internal_comment_form=form)

View File

@@ -13,6 +13,7 @@ from atst.domain.requests.financial_verification import (
)
from atst.models.attachment import Attachment
from atst.domain.task_orders import TaskOrders
from atst.utils.flash import formatted_flash as flash
def fv_extended(_http_request):
@@ -201,11 +202,13 @@ def financial_verification(request_id):
g.current_user, request, is_extended=is_extended
).execute()
if request.review_comment:
flash("request_review_comment", {"comment": request.review_comment})
return render_template(
"requests/financial_verification.html",
f=form,
jedi_request=request,
review_comment=request.review_comment,
extended=is_extended,
saved_draft=saved_draft,
)
@@ -236,11 +239,8 @@ def update_financial_verification(request_id):
if updated_request.task_order.verified:
workspace = Requests.auto_approve_and_create_workspace(updated_request)
return redirect(
url_for(
"workspaces.new_project", workspace_id=workspace.id, newWorkspace=True
)
)
flash("new_workspace")
return redirect(url_for("workspaces.new_project", workspace_id=workspace.id))
else:
return redirect(url_for("requests.requests_index", modal="pendingCCPOApproval"))

View File

@@ -5,6 +5,7 @@ from . import requests_bp
from atst.domain.requests import Requests
from atst.models.permissions import Permissions
from atst.forms.data import SERVICE_BRANCHES
from atst.utils.flash import formatted_flash as flash
class RequestsIndex(object):
@@ -99,4 +100,8 @@ class RequestsIndex(object):
@requests_bp.route("/requests", methods=["GET"])
def requests_index():
context = RequestsIndex(g.current_user).execute()
if context.get("num_action_required"):
flash("requests_action_required", {"count": context.get("num_action_required")})
return render_template("requests/index.html", **context)

View File

@@ -124,6 +124,10 @@ class JEDIRequestFlow(object):
},
]
@property
def is_review_screen(self):
return self.screens[-1] == self.current_screen
def create_or_update_request(self):
request_data = self.map_request_data(self.form_section, self.form.data)
if self.request_id:

View File

@@ -13,6 +13,7 @@ from atst.forms.data import (
FUNDING_TYPES,
TASK_ORDER_SOURCES,
)
from atst.utils.flash import formatted_flash as flash
@requests_bp.context_processor
@@ -31,6 +32,9 @@ def option_data():
def requests_form_new(screen):
jedi_flow = JEDIRequestFlow(screen, request=None, current_user=g.current_user)
if jedi_flow.is_review_screen and not jedi_flow.can_submit:
flash("request_incomplete")
return render_template(
"requests/screen-%d.html" % int(screen),
f=jedi_flow.form,
@@ -54,6 +58,12 @@ def requests_form_update(screen=1, request_id=None):
screen, request=request, request_id=request_id, current_user=g.current_user
)
if jedi_flow.is_review_screen and not jedi_flow.can_submit:
flash("request_incomplete")
if request.review_comment:
flash("request_review_comment", {"comment": request.review_comment})
return render_template(
"requests/screen-%d.html" % int(screen),
f=jedi_flow.form,
@@ -63,7 +73,6 @@ def requests_form_update(screen=1, request_id=None):
next_screen=screen + 1,
request_id=request_id,
jedi_request=jedi_flow.request,
review_comment=request.review_comment,
can_submit=jedi_flow.can_submit,
)
@@ -103,6 +112,7 @@ def requests_update(screen=1, request_id=None):
where = "/requests"
return redirect(where)
else:
flash("form_errors")
rerender_args = dict(
f=jedi_flow.form,
data=post_data,

View File

@@ -1,6 +1,7 @@
from flask import Blueprint, render_template, g, request as http_request, redirect
from atst.forms.edit_user import EditUserForm
from atst.domain.users import Users
from atst.utils.flash import formatted_flash as flash
bp = Blueprint("users", __name__)
@@ -10,9 +11,12 @@ bp = Blueprint("users", __name__)
def user():
user = g.current_user
form = EditUserForm(data=user.to_dictionary())
return render_template(
"user/edit.html", next=http_request.args.get("next"), form=form, user=user
)
next_ = http_request.args.get("next")
if next_:
flash("user_must_complete_profile")
return render_template("user/edit.html", next=next_, form=form, user=user)
@bp.route("/user", methods=["POST"])
@@ -20,11 +24,12 @@ def update_user():
user = g.current_user
form = EditUserForm(http_request.form)
next_url = http_request.args.get("next")
rerender_args = {"form": form, "user": user, "next": next_url}
if form.validate():
Users.update(user, form.data)
rerender_args["updated"] = True
flash("user_updated")
if next_url:
return redirect(next_url)
else:
flash("form_errors")
return render_template("user/edit.html", **rerender_args)
return render_template("user/edit.html", form=form, user=user, next=next_url)

View File

@@ -8,6 +8,7 @@ from atst.domain.workspaces import Workspaces
from atst.forms.workspace import WorkspaceForm
from atst.domain.authz import Authorization
from atst.models.permissions import Permissions
from atst.utils.flash import formatted_flash as flash
@workspaces_bp.route("/workspaces")
@@ -33,6 +34,7 @@ def edit_workspace(workspace_id):
url_for("workspaces.workspace_projects", workspace_id=workspace.id)
)
else:
flash("form_errors")
return render_template("workspaces/edit.html", form=form, workspace=workspace)

View File

@@ -4,6 +4,7 @@ from . import workspaces_bp
from atst.domain.workspaces import Workspaces
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):
@@ -40,10 +41,5 @@ def revoke_invitation(workspace_id, token):
def resend_invitation(workspace_id, token):
invite = Invitations.resend(g.current_user, workspace_id, token)
send_invite_email(g.current_user.full_name, invite.token, invite.email)
return redirect(
url_for(
"workspaces.workspace_members",
workspace_id=workspace_id,
resentInvitationTo=invite.user_name,
)
)
flash("resent_workspace_invitation", {"user_name": invite.user_name})
return redirect(url_for("workspaces.workspace_members", workspace_id=workspace_id))

View File

@@ -21,12 +21,13 @@ from atst.domain.authz import Authorization
from atst.models.permissions import Permissions
from atst.domain.invitations import Invitations
from atst.utils.flash import formatted_flash as flash
@workspaces_bp.route("/workspaces/<workspace_id>/members")
def workspace_members(workspace_id):
workspace = Workspaces.get_with_members(g.current_user, workspace_id)
new_member_name = http_request.args.get("newMemberName")
resent_invitation_to = http_request.args.get("resentInvitationTo")
new_member = next(
filter(lambda m: m.user_name == new_member_name, workspace.members), None
)
@@ -51,7 +52,6 @@ def workspace_members(workspace_id):
status_choices=MEMBER_STATUS_CHOICES,
members=members_list,
new_member=new_member,
resent_invitation_to=resent_invitation_to,
)
@@ -76,12 +76,13 @@ def create_member(workspace_id):
invite = Invitations.create(user, new_member, form.data["email"])
send_invite_email(g.current_user.full_name, invite.token, invite.email)
flash(
"new_workspace_member",
{"new_member": new_member, "workspace": workspace},
)
return redirect(
url_for(
"workspaces.workspace_members",
workspace_id=workspace.id,
newMemberName=new_member.user_name,
)
url_for("workspaces.workspace_members", workspace_id=workspace.id)
)
except AlreadyExistsError:
return render_template(
@@ -107,6 +108,10 @@ def view_member(workspace_id, member_id):
form = EditMemberForm(workspace_role=member.role_name)
editable = g.current_user == member.user
can_revoke_access = Workspaces.can_revoke_access_for(workspace, member)
if member.has_dod_id_error:
flash("workspace_member_dod_id_error")
return render_template(
"workspaces/members/edit.html",
workspace=workspace,
@@ -155,13 +160,13 @@ def update_member(workspace_id, member_id):
g.current_user, workspace, member, ids_and_roles
)
flash(
"workspace_role_updated",
{"member_name": member.user_name, "updated_role": new_role_name},
)
return redirect(
url_for(
"workspaces.workspace_members",
workspace_id=workspace.id,
memberName=member.user_name,
updatedRole=new_role_name,
)
url_for("workspaces.workspace_members", workspace_id=workspace.id)
)
else:
return render_template(
@@ -177,10 +182,5 @@ def update_member(workspace_id, member_id):
)
def revoke_access(workspace_id, member_id):
revoked_role = Workspaces.revoke_access(g.current_user, workspace_id, member_id)
return redirect(
url_for(
"workspaces.workspace_members",
workspace_id=workspace_id,
revokedMemberName=revoked_role.user_name,
)
)
flash("revoked_workspace_access", {"member_name": revoked_role.user.full_name})
return redirect(url_for("workspaces.workspace_members", workspace_id=workspace_id))

107
atst/utils/flash.py Normal file
View File

@@ -0,0 +1,107 @@
from flask import flash, render_template_string
MESSAGES = {
"new_workspace_member": {
"title_template": "Member added successfully",
"message_template": """
<p>{{ new_member.user_name }} was successfully invited via email to this workspace. They do not yet have access to any environments.</p>
<p><a href="{{ url_for('workspaces.update_member', workspace_id=workspace.id, member_id=new_member.user_id) }}">Add environment access.</a></p>
""",
"category": "success",
},
"revoked_workspace_access": {
"title_template": "Removed workspace access",
"message_template": """
<p>Removed {{ member_name }} from this workspace.</p>
""",
"category": "success",
},
"resent_workspace_invitation": {
"title_template": "Invitation resent",
"message_template": """
<p>Successfully sent a new invitation to {{ user_name }}.</p>
""",
"category": "success",
},
"workspace_role_updated": {
"title_template": "Workspace role updated successfully",
"message_template": """
<p>{{ member_name }}'s role was successfully updated to {{ updated_role }}</p>
""",
"category": "success",
},
"session_expired": {
"title_template": "Session Expired",
"message_template": """
Your session expired due to inactivity. Please log in again to continue.
""",
"category": "error",
},
"login_next": {
"title_template": "Log in Required.",
"message_template": """
After you log in, you will be redirected to your destination page.
""",
"category": "warning",
},
"new_workspace": {
"title_template": "Workspace created!",
"message_template": """
<p>You are now ready to create projects and environments within the JEDI Cloud.</p>
""",
"category": "success",
},
"workspace_member_dod_id_error": {
"title_template": "CAC ID Error",
"message_template": """
The member attempted to accept this invite, but their CAC ID did not match the CAC ID you specified on the invite. Please confirm that the DOD ID is accurate.
""",
"category": "error",
},
"form_errors": {
"title_template": "There were some errors",
"message_template": "<p>Please see below.</p>",
"category": "error",
},
"user_must_complete_profile": {
"title_template": "You must complete your profile",
"message_template": "<p>Before continuing, you must complete your profile</p>",
"category": "info",
},
"user_updated": {
"title_template": "User information updated.",
"message_template": "",
"category": "success",
},
"request_incomplete": {
"title_template": "Please complete all sections",
"message_template": """
<p>In order to submit your JEDI Cloud request, you'll need to complete all required sections of this form without error. Missing or invalid fields are noted below.</p>
""",
"category": "error",
},
"requests_action_required": {
"title_template": "Action required on {{ count }} requests.",
"message_template": "",
"category": "info",
},
"request_review_comment": {
"title_template": "Changes Requested",
"message_template": """
<p>CCPO has requested changes to your submission with the following notes:
<br>
{{ comment }}
<br>
Please contact info@jedi.cloud or 123-123-4567 for further discussion.</p>
""",
"category": "warning",
},
}
def formatted_flash(message_name, message_args=None):
config = MESSAGES[message_name]
args = message_args or {}
title = render_template_string(config["title_template"], **args)
message = render_template_string(config["message_template"], **args)
flash({"title": title, "message": message}, config["category"])