Route for adding new application member
- domain method for creating a new application member - ApplicationInvitations domain class - nested form for adding a new user that holds user data, application permission sets, and environment roles - Invitation service can infer invitation type based on role it's given - new invitation email templates
This commit is contained in:
parent
054f6b80b9
commit
ade77e6b91
@ -2,8 +2,10 @@ from sqlalchemy.orm.exc import NoResultFound
|
|||||||
|
|
||||||
from atst.database import db
|
from atst.database import db
|
||||||
from . import BaseDomainClass
|
from . import BaseDomainClass
|
||||||
|
from atst.domain.application_roles import ApplicationRoles
|
||||||
from atst.domain.environments import Environments
|
from atst.domain.environments import Environments
|
||||||
from atst.domain.exceptions import NotFoundError
|
from atst.domain.exceptions import NotFoundError
|
||||||
|
from atst.domain.users import Users
|
||||||
from atst.models.application import Application
|
from atst.models.application import Application
|
||||||
from atst.models.environment import Environment
|
from atst.models.environment import Environment
|
||||||
from atst.models.environment_role import EnvironmentRole
|
from atst.models.environment_role import EnvironmentRole
|
||||||
@ -72,3 +74,30 @@ class Applications(BaseDomainClass):
|
|||||||
|
|
||||||
db.session.add(application)
|
db.session.add(application)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_member(
|
||||||
|
cls, application, user_data, permission_sets=None, environment_roles_data=None
|
||||||
|
):
|
||||||
|
permission_sets = [] if permission_sets is None else permission_sets
|
||||||
|
environment_roles_data = (
|
||||||
|
[] if environment_roles_data is None else environment_roles_data
|
||||||
|
)
|
||||||
|
|
||||||
|
user = Users.get_or_create_by_dod_id(
|
||||||
|
user_data["dod_id"],
|
||||||
|
first_name=user_data["first_name"],
|
||||||
|
last_name=user_data["last_name"],
|
||||||
|
phone_number=user_data.get("phone_number"),
|
||||||
|
email=user_data["email"],
|
||||||
|
)
|
||||||
|
|
||||||
|
application_role = ApplicationRoles.create(user, application, permission_sets)
|
||||||
|
|
||||||
|
for env_role_data in environment_roles_data:
|
||||||
|
role = env_role_data.get("role")
|
||||||
|
if role:
|
||||||
|
environment = Environments.get(env_role_data.get("environment_id"))
|
||||||
|
Environments.add_member(environment, user, env_role_data.get("role"))
|
||||||
|
|
||||||
|
return application_role
|
||||||
|
@ -2,7 +2,7 @@ import datetime
|
|||||||
from sqlalchemy.orm.exc import NoResultFound
|
from sqlalchemy.orm.exc import NoResultFound
|
||||||
|
|
||||||
from atst.database import db
|
from atst.database import db
|
||||||
from atst.models import InvitationStatus, PortfolioInvitation
|
from atst.models import ApplicationInvitation, InvitationStatus, PortfolioInvitation
|
||||||
from atst.domain.portfolio_roles import PortfolioRoles
|
from atst.domain.portfolio_roles import PortfolioRoles
|
||||||
|
|
||||||
from .exceptions import NotFoundError
|
from .exceptions import NotFoundError
|
||||||
@ -127,3 +127,7 @@ class BaseInvitations(object):
|
|||||||
|
|
||||||
class PortfolioInvitations(BaseInvitations):
|
class PortfolioInvitations(BaseInvitations):
|
||||||
model = PortfolioInvitation
|
model = PortfolioInvitation
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationInvitations(BaseInvitations):
|
||||||
|
model = ApplicationInvitation
|
||||||
|
41
atst/forms/application_member.py
Normal file
41
atst/forms/application_member.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
from wtforms.fields import FormField, FieldList, HiddenField, BooleanField
|
||||||
|
|
||||||
|
from .forms import BaseForm
|
||||||
|
from .member import NewForm as BaseNewMemberForm
|
||||||
|
from .data import ENV_ROLES
|
||||||
|
from atst.forms.fields import SelectField
|
||||||
|
from atst.domain.permission_sets import PermissionSets
|
||||||
|
|
||||||
|
|
||||||
|
class EnvironmentForm(BaseForm):
|
||||||
|
environment_id = HiddenField()
|
||||||
|
environment_name = HiddenField()
|
||||||
|
role = SelectField(environment_name, choices=ENV_ROLES, default=None)
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionsForm(BaseForm):
|
||||||
|
perms_env_mgmt = BooleanField(None, default=False)
|
||||||
|
perms_team_mgmt = BooleanField(None, default=False)
|
||||||
|
perms_del_env = BooleanField(None, default=False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self):
|
||||||
|
_data = super().data
|
||||||
|
perm_sets = []
|
||||||
|
|
||||||
|
if _data["perms_env_mgmt"]:
|
||||||
|
perm_sets.append(PermissionSets.EDIT_APPLICATION_ENVIRONMENTS)
|
||||||
|
|
||||||
|
if _data["perms_team_mgmt"]:
|
||||||
|
perm_sets.append(PermissionSets.EDIT_APPLICATION_TEAM)
|
||||||
|
|
||||||
|
if _data["perms_del_env"]:
|
||||||
|
perm_sets.append(PermissionSets.DELETE_APPLICATION_ENVIRONMENTS)
|
||||||
|
|
||||||
|
return perm_sets
|
||||||
|
|
||||||
|
|
||||||
|
class NewForm(BaseForm):
|
||||||
|
user_data = FormField(BaseNewMemberForm)
|
||||||
|
permission_sets = FormField(PermissionsForm)
|
||||||
|
environment_roles = FieldList(FormField(EnvironmentForm))
|
@ -1,3 +1,4 @@
|
|||||||
|
from atst.models import CSPRole
|
||||||
from atst.utils.localization import translate, translate_duration
|
from atst.utils.localization import translate, translate_duration
|
||||||
from atst.models.environment_role import CSPRole
|
from atst.models.environment_role import CSPRole
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ from .permission_set import PermissionSet
|
|||||||
from .user import User
|
from .user import User
|
||||||
from .portfolio_role import PortfolioRole, Status as PortfolioRoleStatus
|
from .portfolio_role import PortfolioRole, Status as PortfolioRoleStatus
|
||||||
from .application_role import ApplicationRole, Status as ApplicationRoleStatus
|
from .application_role import ApplicationRole, Status as ApplicationRoleStatus
|
||||||
from .environment_role import EnvironmentRole
|
from .environment_role import EnvironmentRole, CSPRole
|
||||||
from .portfolio import Portfolio
|
from .portfolio import Portfolio
|
||||||
from .application import Application
|
from .application import Application
|
||||||
from .environment import Environment
|
from .environment import Environment
|
||||||
|
@ -5,6 +5,7 @@ from sqlalchemy.dialects.postgresql import UUID
|
|||||||
from atst.models import Base, types, mixins
|
from atst.models import Base, types, mixins
|
||||||
from atst.models.permissions import Permissions
|
from atst.models.permissions import Permissions
|
||||||
from atst.models.portfolio_invitation import PortfolioInvitation
|
from atst.models.portfolio_invitation import PortfolioInvitation
|
||||||
|
from atst.models.application_invitation import ApplicationInvitation
|
||||||
|
|
||||||
|
|
||||||
users_permission_sets = Table(
|
users_permission_sets = Table(
|
||||||
@ -39,6 +40,13 @@ class User(
|
|||||||
"PortfolioInvitation", foreign_keys=PortfolioInvitation.inviter_id
|
"PortfolioInvitation", foreign_keys=PortfolioInvitation.inviter_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
application_invitations = relationship(
|
||||||
|
"ApplicationInvitation", foreign_keys=ApplicationInvitation.user_id
|
||||||
|
)
|
||||||
|
sent_application_invitations = relationship(
|
||||||
|
"ApplicationInvitation", foreign_keys=ApplicationInvitation.inviter_id
|
||||||
|
)
|
||||||
|
|
||||||
email = Column(String)
|
email = Column(String)
|
||||||
dod_id = Column(String, unique=True, nullable=False)
|
dod_id = Column(String, unique=True, nullable=False)
|
||||||
first_name = Column(String)
|
first_name = Column(String)
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
from flask import render_template
|
from flask import render_template, request as http_request, g, url_for, redirect
|
||||||
|
|
||||||
|
|
||||||
from . import applications_bp
|
from . import applications_bp
|
||||||
from atst.domain.environments import Environments
|
from atst.domain.environments import Environments
|
||||||
from atst.domain.applications import Applications
|
from atst.domain.applications import Applications
|
||||||
from atst.domain.authz.decorator import user_can_access_decorator as user_can
|
from atst.domain.authz.decorator import user_can_access_decorator as user_can
|
||||||
from atst.models.permissions import Permissions
|
|
||||||
from atst.domain.permission_sets import PermissionSets
|
from atst.domain.permission_sets import PermissionSets
|
||||||
|
from atst.forms.application_member import NewForm as NewMemberForm
|
||||||
|
from atst.models.permissions import Permissions
|
||||||
|
from atst.services.invitation import Invitation as InvitationService
|
||||||
|
from atst.utils.flash import formatted_flash as flash
|
||||||
from atst.utils.localization import translate
|
from atst.utils.localization import translate
|
||||||
|
|
||||||
|
|
||||||
@ -47,3 +50,45 @@ def team(application_id):
|
|||||||
application=application,
|
application=application,
|
||||||
environment_users=environment_users,
|
environment_users=environment_users,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@applications_bp.route("/application/<application_id>/members/new", methods=["POST"])
|
||||||
|
@user_can(
|
||||||
|
Permissions.CREATE_APPLICATION_MEMBER, message="create new application member"
|
||||||
|
)
|
||||||
|
def create_member(application_id):
|
||||||
|
application = Applications.get(application_id)
|
||||||
|
form = NewMemberForm(http_request.form)
|
||||||
|
|
||||||
|
if form.validate():
|
||||||
|
try:
|
||||||
|
member = Applications.create_member(
|
||||||
|
application,
|
||||||
|
form.user_data.data,
|
||||||
|
permission_sets=form.permission_sets.data,
|
||||||
|
environment_roles_data=form.environment_roles.data,
|
||||||
|
)
|
||||||
|
|
||||||
|
invite_service = InvitationService(
|
||||||
|
g.current_user, member, form.user_data.data.get("email")
|
||||||
|
)
|
||||||
|
invite_service.invite()
|
||||||
|
|
||||||
|
flash("new_portfolio_member", new_member=member)
|
||||||
|
|
||||||
|
except AlreadyExistsError:
|
||||||
|
return render_template(
|
||||||
|
"error.html", message="There was an error processing your request."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
# TODO: flash error message
|
||||||
|
|
||||||
|
return redirect(
|
||||||
|
url_for(
|
||||||
|
"applications.team",
|
||||||
|
application_id=application_id,
|
||||||
|
fragment="application-members",
|
||||||
|
_anchor="application-members",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
@ -9,7 +9,9 @@ from atst.models.permissions import Permissions
|
|||||||
|
|
||||||
|
|
||||||
def send_invite_email(owner_name, token, new_member_email):
|
def send_invite_email(owner_name, token, new_member_email):
|
||||||
body = render_template("emails/invitation.txt", owner=owner_name, token=token)
|
body = render_template(
|
||||||
|
"emails/portfolio/invitation.txt", owner=owner_name, token=token
|
||||||
|
)
|
||||||
queue.send_mail(
|
queue.send_mail(
|
||||||
[new_member_email],
|
[new_member_email],
|
||||||
"{} has invited you to a JEDI Cloud Portfolio".format(owner_name),
|
"{} has invited you to a JEDI Cloud Portfolio".format(owner_name),
|
||||||
|
@ -1,25 +1,26 @@
|
|||||||
from flask import render_template
|
from flask import render_template
|
||||||
|
|
||||||
from atst.domain.invitations import PortfolioInvitations
|
from atst.domain.invitations import PortfolioInvitations, ApplicationInvitations
|
||||||
from atst.queue import queue
|
from atst.queue import queue
|
||||||
from atst.domain.task_orders import TaskOrders
|
from atst.domain.task_orders import TaskOrders
|
||||||
from atst.domain.portfolio_roles import PortfolioRoles
|
from atst.domain.portfolio_roles import PortfolioRoles
|
||||||
|
from atst.models import ApplicationRole, PortfolioRole
|
||||||
|
|
||||||
OFFICER_INVITATIONS = {
|
OFFICER_INVITATIONS = {
|
||||||
"ko_invite": {
|
"ko_invite": {
|
||||||
"role": "contracting_officer",
|
"role": "contracting_officer",
|
||||||
"subject": "Review a task order",
|
"subject": "Review a task order",
|
||||||
"template": "emails/invitation.txt",
|
"template": "emails/portfolio/invitation.txt",
|
||||||
},
|
},
|
||||||
"cor_invite": {
|
"cor_invite": {
|
||||||
"role": "contracting_officer_representative",
|
"role": "contracting_officer_representative",
|
||||||
"subject": "Help with a task order",
|
"subject": "Help with a task order",
|
||||||
"template": "emails/invitation.txt",
|
"template": "emails/portfolio/invitation.txt",
|
||||||
},
|
},
|
||||||
"so_invite": {
|
"so_invite": {
|
||||||
"role": "security_officer",
|
"role": "security_officer",
|
||||||
"subject": "Review security for a task order",
|
"subject": "Review security for a task order",
|
||||||
"template": "emails/invitation.txt",
|
"template": "emails/portfolio/invitation.txt",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,20 +48,38 @@ def update_officer_invitations(user, task_order):
|
|||||||
|
|
||||||
|
|
||||||
class Invitation:
|
class Invitation:
|
||||||
def __init__(
|
def __init__(self, inviter, member, email, subject="", email_template=None):
|
||||||
self,
|
|
||||||
inviter,
|
|
||||||
member,
|
|
||||||
email,
|
|
||||||
subject="{} has invited you to a JEDI cloud portfolio",
|
|
||||||
email_template="emails/invitation.txt",
|
|
||||||
):
|
|
||||||
self.inviter = inviter
|
self.inviter = inviter
|
||||||
self.member = member
|
self.member = member
|
||||||
self.email = email
|
self.email = email
|
||||||
self.subject = subject
|
self.subject = subject
|
||||||
self.email_template = email_template
|
self.email_template = email_template
|
||||||
|
|
||||||
|
if isinstance(member, PortfolioRole):
|
||||||
|
self.email_template = (
|
||||||
|
"emails/portfolio/invitation.txt"
|
||||||
|
if self.email_template is None
|
||||||
|
else self.email_template
|
||||||
|
)
|
||||||
|
self.subject = (
|
||||||
|
"{} has invited you to a JEDI cloud portfolio"
|
||||||
|
if self.subject is None
|
||||||
|
else self.subject
|
||||||
|
)
|
||||||
|
self.domain_class = PortfolioInvitations
|
||||||
|
elif isinstance(member, ApplicationRole):
|
||||||
|
self.email_template = (
|
||||||
|
"emails/application/invitation.txt"
|
||||||
|
if self.email_template is None
|
||||||
|
else self.email_template
|
||||||
|
)
|
||||||
|
self.subject = (
|
||||||
|
"{} has invited you to a JEDI cloud application"
|
||||||
|
if self.subject is None
|
||||||
|
else self.subject
|
||||||
|
)
|
||||||
|
self.domain_class = ApplicationInvitations
|
||||||
|
|
||||||
def invite(self):
|
def invite(self):
|
||||||
invite = self._create_invite()
|
invite = self._create_invite()
|
||||||
self._send_invite_email(invite.token)
|
self._send_invite_email(invite.token)
|
||||||
@ -68,7 +87,7 @@ class Invitation:
|
|||||||
return invite
|
return invite
|
||||||
|
|
||||||
def _create_invite(self):
|
def _create_invite(self):
|
||||||
return PortfolioInvitations.create(self.inviter, self.member, self.email)
|
return self.domain_class.create(self.inviter, self.member, self.email)
|
||||||
|
|
||||||
def _send_invite_email(self, token):
|
def _send_invite_email(self, token):
|
||||||
body = render_template(
|
body = render_template(
|
||||||
|
@ -161,6 +161,13 @@ MESSAGES = {
|
|||||||
""",
|
""",
|
||||||
"category": "success",
|
"category": "success",
|
||||||
},
|
},
|
||||||
|
"new_application_member": {
|
||||||
|
"title_template": translate("flash.success"),
|
||||||
|
"message_template": """
|
||||||
|
<p>{{ "flash.new_application_member" | translate({ "user_name": new_member.user_name }) }}</p>
|
||||||
|
""",
|
||||||
|
"category": "success",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
10
templates/emails/application/invitation.txt
Normal file
10
templates/emails/application/invitation.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{% extends "emails/base.txt" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
Join this JEDI Cloud Application
|
||||||
|
{{ owner }} has invited you to join a JEDI Cloud Application. Login now to view or use your JEDI Cloud resources.
|
||||||
|
|
||||||
|
{# url_for("application.accept_invitation", token=token, _external=True) #}
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -1,7 +1,4 @@
|
|||||||
Join this JEDI Cloud Portfolio
|
{% block content %}{% endblock %}
|
||||||
{{ owner }} has invited you to join a JEDI Cloud Portfolio. Login now to view or use your JEDI Cloud resources.
|
|
||||||
|
|
||||||
{{ url_for("portfolios.accept_invitation", token=token, _external=True) }}
|
|
||||||
|
|
||||||
What is JEDI Cloud?
|
What is JEDI Cloud?
|
||||||
JEDI Cloud is a DoD enterprise-wide solution for commercial cloud services.
|
JEDI Cloud is a DoD enterprise-wide solution for commercial cloud services.
|
10
templates/emails/portfolio/invitation.txt
Normal file
10
templates/emails/portfolio/invitation.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{% extends "emails/base.txt" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
Join this JEDI Cloud Portfolio
|
||||||
|
{{ owner }} has invited you to join a JEDI Cloud Portfolio. Login now to view or use your JEDI Cloud resources.
|
||||||
|
|
||||||
|
{{ url_for("portfolios.accept_invitation", token=token, _external=True) }}
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -1,7 +1,9 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from atst.models import CSPRole
|
||||||
from atst.domain.applications import Applications
|
from atst.domain.applications import Applications
|
||||||
|
from atst.domain.permission_sets import PermissionSets
|
||||||
from atst.domain.exceptions import NotFoundError
|
from atst.domain.exceptions import NotFoundError
|
||||||
|
|
||||||
from tests.factories import (
|
from tests.factories import (
|
||||||
@ -100,3 +102,29 @@ def test_delete_application(session):
|
|||||||
|
|
||||||
# changes are flushed
|
# changes are flushed
|
||||||
assert not session.dirty
|
assert not session.dirty
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_member():
|
||||||
|
application = ApplicationFactory.create()
|
||||||
|
env1 = EnvironmentFactory.create(application=application)
|
||||||
|
env2 = EnvironmentFactory.create(application=application)
|
||||||
|
user_data = UserFactory.dictionary()
|
||||||
|
permission_set_names = [PermissionSets.EDIT_APPLICATION_TEAM]
|
||||||
|
|
||||||
|
member_role = Applications.create_member(
|
||||||
|
application,
|
||||||
|
user_data,
|
||||||
|
permission_set_names,
|
||||||
|
environment_roles_data=[
|
||||||
|
{"environment_id": env1.id, "role": CSPRole.BASIC_ACCESS.value},
|
||||||
|
{"environment_id": env2.id, "role": None},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert member_role.user.dod_id == user_data["dod_id"]
|
||||||
|
# view application AND edit application team
|
||||||
|
assert len(member_role.permission_sets) == 2
|
||||||
|
|
||||||
|
env_roles = member_role.user.environment_roles
|
||||||
|
assert len(env_roles) == 1
|
||||||
|
assert env_roles[0].environment == env1
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|
||||||
from tests.factories import PortfolioFactory, ApplicationFactory
|
from tests.factories import PortfolioFactory, ApplicationFactory, UserFactory
|
||||||
|
|
||||||
|
|
||||||
def test_application_team(client, user_session):
|
def test_application_team(client, user_session):
|
||||||
@ -12,3 +12,43 @@ def test_application_team(client, user_session):
|
|||||||
response = client.get(url_for("applications.team", application_id=application.id))
|
response = client.get(url_for("applications.team", application_id=application.id))
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_member(client, user_session):
|
||||||
|
user = UserFactory.create()
|
||||||
|
application = ApplicationFactory.create(
|
||||||
|
environments=[{"name": "Naboo"}, {"name": "Endor"}]
|
||||||
|
)
|
||||||
|
env = application.environments[0]
|
||||||
|
|
||||||
|
user_session(application.portfolio.owner)
|
||||||
|
|
||||||
|
response = client.post(
|
||||||
|
url_for("applications.create_member", application_id=application.id),
|
||||||
|
data={
|
||||||
|
"user_data-first_name": user.first_name,
|
||||||
|
"user_data-last_name": user.last_name,
|
||||||
|
"user_data-dod_id": user.dod_id,
|
||||||
|
"user_data-email": user.email,
|
||||||
|
"environment_roles-0-environment_id": env.id,
|
||||||
|
"environment_roles-0-environment_name": env.name,
|
||||||
|
"environment_roles-0-role": "Basic Access",
|
||||||
|
"permission_sets-perms_env_mgmt": True,
|
||||||
|
"permission_sets-perms_team_mgmt": True,
|
||||||
|
"permission_sets-perms_del_env": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 302
|
||||||
|
expected_url = url_for(
|
||||||
|
"applications.team",
|
||||||
|
application_id=application.id,
|
||||||
|
fragment="application-members",
|
||||||
|
_anchor="application-members",
|
||||||
|
_external=True,
|
||||||
|
)
|
||||||
|
assert response.location == expected_url
|
||||||
|
assert len(user.application_roles) == 1
|
||||||
|
assert user.application_roles[0].application == application
|
||||||
|
assert len(user.environment_roles) == 1
|
||||||
|
assert user.environment_roles[0].environment == env
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
from tests.factories import UserFactory, PortfolioFactory, PortfolioRoleFactory
|
from tests.factories import (
|
||||||
|
ApplicationFactory,
|
||||||
|
ApplicationRoleFactory,
|
||||||
|
UserFactory,
|
||||||
|
PortfolioFactory,
|
||||||
|
PortfolioRoleFactory,
|
||||||
|
)
|
||||||
|
|
||||||
from atst.services.invitation import Invitation
|
from atst.services.invitation import Invitation
|
||||||
|
|
||||||
|
|
||||||
def test_invite_member(queue):
|
def test_invite_portfolio_member(queue):
|
||||||
inviter = UserFactory.create()
|
inviter = UserFactory.create()
|
||||||
new_member = UserFactory.create()
|
new_member = UserFactory.create()
|
||||||
portfolio = PortfolioFactory.create(owner=inviter)
|
portfolio = PortfolioFactory.create(owner=inviter)
|
||||||
@ -12,3 +18,14 @@ def test_invite_member(queue):
|
|||||||
new_invitation = invite_service.invite()
|
new_invitation = invite_service.invite()
|
||||||
assert new_invitation == new_member.portfolio_invitations[0]
|
assert new_invitation == new_member.portfolio_invitations[0]
|
||||||
assert len(queue.get_queue()) == 1
|
assert len(queue.get_queue()) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_invite_application_member(queue):
|
||||||
|
inviter = UserFactory.create()
|
||||||
|
new_member = UserFactory.create()
|
||||||
|
application = ApplicationFactory.create()
|
||||||
|
member = ApplicationRoleFactory.create(user=new_member, application=application)
|
||||||
|
invite_service = Invitation(inviter, member, new_member.email)
|
||||||
|
new_invitation = invite_service.invite()
|
||||||
|
assert new_invitation == new_member.application_invitations[0]
|
||||||
|
assert len(queue.get_queue()) == 1
|
||||||
|
@ -65,6 +65,7 @@ flash:
|
|||||||
next_steps: Review next steps below
|
next_steps: Review next steps below
|
||||||
portfolio_home: Go to my portfolio home page
|
portfolio_home: Go to my portfolio home page
|
||||||
success: Success!
|
success: Success!
|
||||||
|
new_application_member: 'You have successfully invited {user_name} to the team.'
|
||||||
footer:
|
footer:
|
||||||
about_link_text: Joint Enterprise Defense Infrastructure
|
about_link_text: Joint Enterprise Defense Infrastructure
|
||||||
browser_support: JEDI Cloud supported on these web browsers
|
browser_support: JEDI Cloud supported on these web browsers
|
||||||
|
Loading…
x
Reference in New Issue
Block a user