Small tweaks for adding a new application member:

- raise specific invitation type if invite not found in invitation domain classes
- more terse assignments of defaults in invitation service, smh
- terser margin expression for inline input fields
- sass formatting
- use translation for cancel link
- oxford comma for app team management permission explanation
- do not format environment roles with hyphens for role selection
- generalize some additional methods in the invitation domain base class
- use plain atst.models import path
This commit is contained in:
dandds 2019-04-30 14:49:53 -04:00
parent 5db4d9bab3
commit 4f304d747e
10 changed files with 23 additions and 30 deletions

View File

@ -4,6 +4,7 @@ from sqlalchemy.orm.exc import NoResultFound
from atst.database import db from atst.database import db
from atst.models import ApplicationInvitation, InvitationStatus, PortfolioInvitation from atst.models import ApplicationInvitation, InvitationStatus, PortfolioInvitation
from atst.domain.portfolio_roles import PortfolioRoles from atst.domain.portfolio_roles import PortfolioRoles
from atst.domain.application_roles import ApplicationRoles
from .exceptions import NotFoundError from .exceptions import NotFoundError
@ -40,6 +41,7 @@ class InvitationError(Exception):
class BaseInvitations(object): class BaseInvitations(object):
model = None model = None
role_domain_class = None
# number of minutes a given invitation is considered valid # number of minutes a given invitation is considered valid
EXPIRATION_LIMIT_MINUTES = 360 EXPIRATION_LIMIT_MINUTES = 360
@ -48,7 +50,7 @@ class BaseInvitations(object):
try: try:
invite = db.session.query(cls.model).filter_by(token=token).one() invite = db.session.query(cls.model).filter_by(token=token).one()
except NoResultFound: except NoResultFound:
raise NotFoundError("invite") raise NotFoundError(cls.model.__tablename__)
return invite return invite
@ -86,7 +88,7 @@ class BaseInvitations(object):
elif invite.is_pending: # pragma: no branch elif invite.is_pending: # pragma: no branch
cls._update_status(invite, InvitationStatus.ACCEPTED) cls._update_status(invite, InvitationStatus.ACCEPTED)
PortfolioRoles.enable(invite.role) cls.role_domain_class.enable(invite.role)
return invite return invite
@classmethod @classmethod
@ -109,11 +111,11 @@ class BaseInvitations(object):
return cls._update_status(invite, InvitationStatus.REVOKED) return cls._update_status(invite, InvitationStatus.REVOKED)
@classmethod @classmethod
def lookup_by_portfolio_and_user(cls, portfolio, user): def lookup_by_resource_and_user(cls, resource, user):
role = PortfolioRoles.get(portfolio.id, user.id) role = cls.role_domain_class.get(resource.id, user.id)
if role.latest_invitation is None: if role.latest_invitation is None:
raise NotFoundError("invitation") raise NotFoundError(cls.model.__tablename__)
return role.latest_invitation return role.latest_invitation
@ -127,7 +129,9 @@ class BaseInvitations(object):
class PortfolioInvitations(BaseInvitations): class PortfolioInvitations(BaseInvitations):
model = PortfolioInvitation model = PortfolioInvitation
role_domain_class = PortfolioRoles
class ApplicationInvitations(BaseInvitations): class ApplicationInvitations(BaseInvitations):
model = ApplicationInvitation model = ApplicationInvitation
role_domain_class = ApplicationRoles

View File

@ -3,7 +3,7 @@ from wtforms.fields import FormField, FieldList, HiddenField, BooleanField
from .forms import BaseForm from .forms import BaseForm
from .member import NewForm as BaseNewMemberForm from .member import NewForm as BaseNewMemberForm
from .data import FORMATTED_ENV_ROLES as ENV_ROLES from .data import ENV_ROLES
from atst.forms.fields import SelectField from atst.forms.fields import SelectField
from atst.domain.permission_sets import PermissionSets from atst.domain.permission_sets import PermissionSets
from atst.utils.localization import translate from atst.utils.localization import translate

View File

@ -218,6 +218,3 @@ REQUIRED_DISTRIBUTIONS = [
] ]
ENV_ROLES = [(role.value, role.value) for role in CSPRole] + [(None, "No access")] ENV_ROLES = [(role.value, role.value) for role in CSPRole] + [(None, "No access")]
FORMATTED_ENV_ROLES = [
(role.value, "- {} -".format(role.value)) for role in CSPRole
] + [(None, "- No Access -")]

View File

@ -8,7 +8,7 @@ from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.domain.permission_sets import PermissionSets from atst.domain.permission_sets import PermissionSets
from atst.domain.exceptions import AlreadyExistsError from atst.domain.exceptions import AlreadyExistsError
from atst.forms.application_member import NewForm as NewMemberForm from atst.forms.application_member import NewForm as NewMemberForm
from atst.models.permissions import Permissions from atst.models import Permissions
from atst.services.invitation import Invitation as InvitationService from atst.services.invitation import Invitation as InvitationService
from atst.utils.flash import formatted_flash as flash from atst.utils.flash import formatted_flash as flash
from atst.utils.localization import translate from atst.utils.localization import translate

View File

@ -57,7 +57,7 @@ def resend_invite(task_order_id):
if not officer: if not officer:
raise NotFoundError("officer") raise NotFoundError("officer")
invitation = PortfolioInvitations.lookup_by_portfolio_and_user(portfolio, officer) invitation = PortfolioInvitations.lookup_by_resource_and_user(portfolio, officer)
if not invitation: if not invitation:
raise NotFoundError("invitation") raise NotFoundError("invitation")

View File

@ -57,26 +57,18 @@ class Invitation:
if isinstance(member, PortfolioRole): if isinstance(member, PortfolioRole):
self.email_template = ( self.email_template = (
"emails/portfolio/invitation.txt" self.email_template or "emails/portfolio/invitation.txt"
if self.email_template is None
else self.email_template
) )
self.subject = ( self.subject = (
"{} has invited you to a JEDI cloud portfolio" self.subject or "{} has invited you to a JEDI cloud portfolio"
if self.subject is None
else self.subject
) )
self.domain_class = PortfolioInvitations self.domain_class = PortfolioInvitations
elif isinstance(member, ApplicationRole): elif isinstance(member, ApplicationRole):
self.email_template = ( self.email_template = (
"emails/application/invitation.txt" self.email_template or "emails/application/invitation.txt"
if self.email_template is None
else self.email_template
) )
self.subject = ( self.subject = (
"{} has invited you to a JEDI cloud application" self.subject or "{} has invited you to a JEDI cloud application"
if self.subject is None
else self.subject
) )
self.domain_class = ApplicationInvitations self.domain_class = ApplicationInvitations

View File

@ -180,10 +180,10 @@
} }
.input__inline-fields { .input__inline-fields {
margin: 1rem 0 1rem 0; margin: 1rem 0;
&.input__inline-fields--indented { &.input__inline-fields--indented {
margin-left: 4*$gap; margin-left: $gap*4;
} }
&> fieldset.usa-input__choices label { &> fieldset.usa-input__choices label {

View File

@ -37,7 +37,7 @@
v-bind:disabled="invalid" v-bind:disabled="invalid"
class='action-group__action usa-button' class='action-group__action usa-button'
value='Next'> value='Next'>
<a class='action-group__action icon-link icon-link--default' v-on:click="closeModal('{{ new_port_mem }}')">Cancel</a> <a class='action-group__action icon-link icon-link--default' v-on:click="closeModal('{{ new_port_mem }}')">{{ "common.cancel" | translate }}</a>
</div> </div>
{% endset %} {% endset %}
{% set step_two %} {% set step_two %}
@ -117,7 +117,7 @@
class='action-group__action usa-button' class='action-group__action usa-button'
form="add-app-mem" form="add-app-mem"
value='Invite member'> value='Invite member'>
<a class='action-group__action icon-link icon-link--default' v-on:click="closeModal('{{ new_port_mem }}')">Cancel</a> <a class='action-group__action icon-link icon-link--default' v-on:click="closeModal('{{ new_port_mem }}')">{{ "common.cancel" | translate }}</a>
<input <input
type='button' type='button'
v-on:click="previous()" v-on:click="previous()"

View File

@ -152,9 +152,9 @@ def test_lookup_by_user_and_portfolio():
ws_role = PortfolioRoleFactory.create(user=user, portfolio=portfolio) ws_role = PortfolioRoleFactory.create(user=user, portfolio=portfolio)
invite = PortfolioInvitations.create(portfolio.owner, ws_role, user.email) invite = PortfolioInvitations.create(portfolio.owner, ws_role, user.email)
assert PortfolioInvitations.lookup_by_portfolio_and_user(portfolio, user) == invite assert PortfolioInvitations.lookup_by_resource_and_user(portfolio, user) == invite
with pytest.raises(NotFoundError): with pytest.raises(NotFoundError):
PortfolioInvitations.lookup_by_portfolio_and_user( PortfolioInvitations.lookup_by_resource_and_user(
portfolio, UserFactory.create() portfolio, UserFactory.create()
) )

View File

@ -444,7 +444,7 @@ portfolios:
manage_perms: 'Manage permissions for {application_name}' manage_perms: 'Manage permissions for {application_name}'
manage_envs: 'Allow member to <strong>add</strong> and <strong>rename environments</strong> within the application.' manage_envs: 'Allow member to <strong>add</strong> and <strong>rename environments</strong> within the application.'
delete_envs: 'Allow member to <strong>delete environments</strong> within the application.' delete_envs: 'Allow member to <strong>delete environments</strong> within the application.'
manage_team: 'Allow member to <strong>add, update</strong> and <strong>remove members</strong> from the application team.' manage_team: 'Allow member to <strong>add, update,</strong> and <strong>remove members</strong> from the application team.'
index: index:
empty: empty:
start_button: Start a new JEDI portfolio start_button: Start a new JEDI portfolio