Merge branch 'main' into ms/3212-FEB-purpose-questions

This commit is contained in:
Matt-Spence 2025-03-11 17:01:21 -04:00 committed by GitHub
commit 0e361e60c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 678 additions and 177 deletions

View file

@ -89,52 +89,52 @@ urlpatterns = [
name="members", name="members",
), ),
path( path(
"member/<int:pk>", "member/<int:member_pk>",
views.PortfolioMemberView.as_view(), views.PortfolioMemberView.as_view(),
name="member", name="member",
), ),
path( path(
"member/<int:pk>/delete", "member/<int:member_pk>/delete",
views.PortfolioMemberDeleteView.as_view(), views.PortfolioMemberDeleteView.as_view(),
name="member-delete", name="member-delete",
), ),
path( path(
"member/<int:pk>/permissions", "member/<int:member_pk>/permissions",
views.PortfolioMemberEditView.as_view(), views.PortfolioMemberEditView.as_view(),
name="member-permissions", name="member-permissions",
), ),
path( path(
"member/<int:pk>/domains", "member/<int:member_pk>/domains",
views.PortfolioMemberDomainsView.as_view(), views.PortfolioMemberDomainsView.as_view(),
name="member-domains", name="member-domains",
), ),
path( path(
"member/<int:pk>/domains/edit", "member/<int:member_pk>/domains/edit",
views.PortfolioMemberDomainsEditView.as_view(), views.PortfolioMemberDomainsEditView.as_view(),
name="member-domains-edit", name="member-domains-edit",
), ),
path( path(
"invitedmember/<int:pk>", "invitedmember/<int:invitedmember_pk>",
views.PortfolioInvitedMemberView.as_view(), views.PortfolioInvitedMemberView.as_view(),
name="invitedmember", name="invitedmember",
), ),
path( path(
"invitedmember/<int:pk>/delete", "invitedmember/<int:invitedmember_pk>/delete",
views.PortfolioInvitedMemberDeleteView.as_view(), views.PortfolioInvitedMemberDeleteView.as_view(),
name="invitedmember-delete", name="invitedmember-delete",
), ),
path( path(
"invitedmember/<int:pk>/permissions", "invitedmember/<int:invitedmember_pk>/permissions",
views.PortfolioInvitedMemberEditView.as_view(), views.PortfolioInvitedMemberEditView.as_view(),
name="invitedmember-permissions", name="invitedmember-permissions",
), ),
path( path(
"invitedmember/<int:pk>/domains", "invitedmember/<int:invitedmember_pk>/domains",
views.PortfolioInvitedMemberDomainsView.as_view(), views.PortfolioInvitedMemberDomainsView.as_view(),
name="invitedmember-domains", name="invitedmember-domains",
), ),
path( path(
"invitedmember/<int:pk>/domains/edit", "invitedmember/<int:invitedmember_pk>/domains/edit",
views.PortfolioInvitedMemberDomainsEditView.as_view(), views.PortfolioInvitedMemberDomainsEditView.as_view(),
name="invitedmember-domains-edit", name="invitedmember-domains-edit",
), ),

View file

@ -1,7 +1,13 @@
import logging
import functools import functools
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from registrar.models import Domain, DomainInformation, DomainInvitation, DomainRequest, UserDomainRole from registrar.models import Domain, DomainInformation, DomainInvitation, DomainRequest, UserDomainRole
from registrar.models.portfolio_invitation import PortfolioInvitation
from registrar.models.user_portfolio_permission import UserPortfolioPermission
logger = logging.getLogger(__name__)
# Constants for clarity # Constants for clarity
ALL = "all" ALL = "all"
@ -98,24 +104,38 @@ def _user_has_permission(user, request, rules, **kwargs):
if not user.is_authenticated or user.is_restricted(): if not user.is_authenticated or user.is_restricted():
return False return False
portfolio = request.session.get("portfolio")
# Define permission checks # Define permission checks
permission_checks = [ permission_checks = [
(IS_STAFF, lambda: user.is_staff), (IS_STAFF, lambda: user.is_staff),
(IS_DOMAIN_MANAGER, lambda: _is_domain_manager(user, **kwargs)), (
IS_DOMAIN_MANAGER,
lambda: (not user.is_org_user(request) and _is_domain_manager(user, **kwargs))
or (
user.is_org_user(request)
and _is_domain_manager(user, **kwargs)
and _domain_exists_under_portfolio(portfolio, kwargs.get("domain_pk"))
),
),
(IS_STAFF_MANAGING_DOMAIN, lambda: _is_staff_managing_domain(request, **kwargs)), (IS_STAFF_MANAGING_DOMAIN, lambda: _is_staff_managing_domain(request, **kwargs)),
(IS_PORTFOLIO_MEMBER, lambda: user.is_org_user(request)), (IS_PORTFOLIO_MEMBER, lambda: user.is_org_user(request)),
( (
HAS_PORTFOLIO_DOMAINS_VIEW_ALL, HAS_PORTFOLIO_DOMAINS_VIEW_ALL,
lambda: _has_portfolio_view_all_domains(request, kwargs.get("domain_pk")), lambda: user.is_org_user(request)
and user.has_view_all_domains_portfolio_permission(portfolio)
and _domain_exists_under_portfolio(portfolio, kwargs.get("domain_pk")),
), ),
( (
HAS_PORTFOLIO_DOMAINS_ANY_PERM, HAS_PORTFOLIO_DOMAINS_ANY_PERM,
lambda: user.is_org_user(request) lambda: user.is_org_user(request)
and user.has_any_domains_portfolio_permission(request.session.get("portfolio")), and user.has_any_domains_portfolio_permission(portfolio)
and _domain_exists_under_portfolio(portfolio, kwargs.get("domain_pk")),
), ),
( (
IS_PORTFOLIO_MEMBER_AND_DOMAIN_MANAGER, IS_PORTFOLIO_MEMBER_AND_DOMAIN_MANAGER,
lambda: _is_domain_manager(user, **kwargs) and _is_portfolio_member(request), lambda: _is_domain_manager(user, **kwargs)
and _is_portfolio_member(request)
and _domain_exists_under_portfolio(portfolio, kwargs.get("domain_pk")),
), ),
( (
IS_DOMAIN_MANAGER_AND_NOT_PORTFOLIO_MEMBER, IS_DOMAIN_MANAGER_AND_NOT_PORTFOLIO_MEMBER,
@ -129,34 +149,55 @@ def _user_has_permission(user, request, rules, **kwargs):
( (
HAS_PORTFOLIO_DOMAIN_REQUESTS_ANY_PERM, HAS_PORTFOLIO_DOMAIN_REQUESTS_ANY_PERM,
lambda: user.is_org_user(request) lambda: user.is_org_user(request)
and user.has_any_requests_portfolio_permission(request.session.get("portfolio")), and user.has_any_requests_portfolio_permission(portfolio)
and _domain_request_exists_under_portfolio(portfolio, kwargs.get("domain_request_pk")),
), ),
( (
HAS_PORTFOLIO_DOMAIN_REQUESTS_VIEW_ALL, HAS_PORTFOLIO_DOMAIN_REQUESTS_VIEW_ALL,
lambda: user.is_org_user(request) lambda: user.is_org_user(request)
and user.has_view_all_domain_requests_portfolio_permission(request.session.get("portfolio")), and user.has_view_all_domain_requests_portfolio_permission(portfolio)
and _domain_request_exists_under_portfolio(portfolio, kwargs.get("domain_request_pk")),
), ),
( (
HAS_PORTFOLIO_DOMAIN_REQUESTS_EDIT, HAS_PORTFOLIO_DOMAIN_REQUESTS_EDIT,
lambda: _has_portfolio_domain_requests_edit(user, request, kwargs.get("domain_request_pk")), lambda: _has_portfolio_domain_requests_edit(user, request, kwargs.get("domain_request_pk"))
and _domain_request_exists_under_portfolio(portfolio, kwargs.get("domain_request_pk")),
), ),
( (
HAS_PORTFOLIO_MEMBERS_ANY_PERM, HAS_PORTFOLIO_MEMBERS_ANY_PERM,
lambda: user.is_org_user(request) lambda: user.is_org_user(request)
and ( and (
user.has_view_members_portfolio_permission(request.session.get("portfolio")) user.has_view_members_portfolio_permission(portfolio)
or user.has_edit_members_portfolio_permission(request.session.get("portfolio")) or user.has_edit_members_portfolio_permission(portfolio)
)
and (
# AND rather than OR because these functions return true if the PK is not found.
# This adds support for if the view simply doesn't have said PK.
_member_exists_under_portfolio(portfolio, kwargs.get("member_pk"))
and _member_invitation_exists_under_portfolio(portfolio, kwargs.get("invitedmember_pk"))
), ),
), ),
( (
HAS_PORTFOLIO_MEMBERS_EDIT, HAS_PORTFOLIO_MEMBERS_EDIT,
lambda: user.is_org_user(request) lambda: user.is_org_user(request)
and user.has_edit_members_portfolio_permission(request.session.get("portfolio")), and user.has_edit_members_portfolio_permission(portfolio)
and (
# AND rather than OR because these functions return true if the PK is not found.
# This adds support for if the view simply doesn't have said PK.
_member_exists_under_portfolio(portfolio, kwargs.get("member_pk"))
and _member_invitation_exists_under_portfolio(portfolio, kwargs.get("invitedmember_pk"))
),
), ),
( (
HAS_PORTFOLIO_MEMBERS_VIEW, HAS_PORTFOLIO_MEMBERS_VIEW,
lambda: user.is_org_user(request) lambda: user.is_org_user(request)
and user.has_view_members_portfolio_permission(request.session.get("portfolio")), and user.has_view_members_portfolio_permission(portfolio)
and (
# AND rather than OR because these functions return true if the PK is not found.
# This adds support for if the view simply doesn't have said PK.
_member_exists_under_portfolio(portfolio, kwargs.get("member_pk"))
and _member_invitation_exists_under_portfolio(portfolio, kwargs.get("invitedmember_pk"))
),
), ),
] ]
@ -191,6 +232,70 @@ def _is_domain_manager(user, **kwargs):
return False return False
def _domain_exists_under_portfolio(portfolio, domain_pk):
"""Checks to see if the given domain exists under the provided portfolio.
HELPFUL REMINDER: Watch for typos! Verify that the kwarg key exists before using this function.
Returns True if the pk is falsy. Otherwise, returns a bool if said object exists.
"""
# The view expects this, and the page will throw an error without this if it needs it.
# Thus, if it is none, we are not checking on a specific record and therefore there is nothing to check.
if not domain_pk:
logger.warning(
"_domain_exists_under_portfolio => Could not find domain_pk. "
"This is a non-issue if called from the right context."
)
return True
return Domain.objects.filter(domain_info__portfolio=portfolio, id=domain_pk).exists()
def _domain_request_exists_under_portfolio(portfolio, domain_request_pk):
"""Checks to see if the given domain request exists under the provided portfolio.
HELPFUL REMINDER: Watch for typos! Verify that the kwarg key exists before using this function.
Returns True if the pk is falsy. Otherwise, returns a bool if said object exists.
"""
# The view expects this, and the page will throw an error without this if it needs it.
# Thus, if it is none, we are not checking on a specific record and therefore there is nothing to check.
if not domain_request_pk:
logger.warning(
"_domain_request_exists_under_portfolio => Could not find domain_request_pk. "
"This is a non-issue if called from the right context."
)
return True
return DomainRequest.objects.filter(portfolio=portfolio, id=domain_request_pk).exists()
def _member_exists_under_portfolio(portfolio, member_pk):
"""Checks to see if the given UserPortfolioPermission exists under the provided portfolio.
HELPFUL REMINDER: Watch for typos! Verify that the kwarg key exists before using this function.
Returns True if the pk is falsy. Otherwise, returns a bool if said object exists.
"""
# The view expects this, and the page will throw an error without this if it needs it.
# Thus, if it is none, we are not checking on a specific record and therefore there is nothing to check.
if not member_pk:
logger.warning(
"_member_exists_under_portfolio => Could not find member_pk. "
"This is a non-issue if called from the right context."
)
return True
return UserPortfolioPermission.objects.filter(portfolio=portfolio, id=member_pk).exists()
def _member_invitation_exists_under_portfolio(portfolio, invitedmember_pk):
"""Checks to see if the given PortfolioInvitation exists under the provided portfolio.
HELPFUL REMINDER: Watch for typos! Verify that the kwarg key exists before using this function.
Returns True if the pk is falsy. Otherwise, returns a bool if said object exists.
"""
# The view expects this, and the page will throw an error without this if it needs it.
# Thus, if it is none, we are not checking on a specific record and therefore there is nothing to check.
if not invitedmember_pk:
logger.warning(
"_member_invitation_exists_under_portfolio => Could not find invitedmember_pk. "
"This is a non-issue if called from the right context."
)
return True
return PortfolioInvitation.objects.filter(portfolio=portfolio, id=invitedmember_pk).exists()
def _is_domain_request_creator(user, domain_request_pk): def _is_domain_request_creator(user, domain_request_pk):
"""Checks to see if the user is the creator of a domain request """Checks to see if the user is the creator of a domain request
with domain_request_pk.""" with domain_request_pk."""
@ -286,15 +391,3 @@ def _is_staff_managing_domain(request, **kwargs):
# the user is permissioned, # the user is permissioned,
# and it is in a valid status # and it is in a valid status
return True return True
def _has_portfolio_view_all_domains(request, domain_pk):
"""Returns whether the user in the request can access the domain
via portfolio view all domains permission."""
portfolio = request.session.get("portfolio")
if request.user.has_view_all_domains_portfolio_permission(portfolio):
if Domain.objects.filter(id=domain_pk).exists():
domain = Domain.objects.get(id=domain_pk)
if domain.domain_info.portfolio == portfolio:
return True
return False

View file

@ -348,7 +348,7 @@ class OrganizationContactForm(RegistrarForm):
error_messages={ error_messages={
"required": ("Select the state, territory, or military post where your organization is located.") "required": ("Select the state, territory, or military post where your organization is located.")
}, },
widget=ComboboxWidget, widget=ComboboxWidget(attrs={"required": True}),
) )
zipcode = forms.CharField( zipcode = forms.CharField(
label="Zip code", label="Zip code",

View file

@ -91,9 +91,9 @@
aria-describedby="You have unsaved changes that will be lost." aria-describedby="You have unsaved changes that will be lost."
> >
{% if portfolio_permission %} {% if portfolio_permission %}
{% url 'member-domains' pk=portfolio_permission.id as url %} {% url 'member-domains' member_pk=portfolio_permission.id as url %}
{% else %} {% else %}
{% url 'invitedmember-domains' pk=portfolio_invitation.id as url %} {% url 'invitedmember-domains' invitedmember_pk=portfolio_invitation.id as url %}
{% endif %} {% endif %}
{% include 'includes/modal.html' with modal_heading="Are you sure you want to continue?" modal_description="You have unsaved changes that will be lost." modal_button_url=url modal_button_text="Continue without saving" %} {% include 'includes/modal.html' with modal_heading="Are you sure you want to continue?" modal_description="You have unsaved changes that will be lost." modal_button_url=url modal_button_text="Continue without saving" %}

View file

@ -15,11 +15,11 @@
{% url 'members' as url %} {% url 'members' as url %}
{% if portfolio_permission %} {% if portfolio_permission %}
{% url 'member' pk=portfolio_permission.id as url2 %} {% url 'member' member_pk=portfolio_permission.id as url2 %}
{% url 'member-domains-edit' pk=portfolio_permission.id as url3 %} {% url 'member-domains-edit' member_pk=portfolio_permission.id as url3 %}
{% else %} {% else %}
{% url 'invitedmember' pk=portfolio_invitation.id as url2 %} {% url 'invitedmember' invitedmember_pk=portfolio_invitation.id as url2 %}
{% url 'invitedmember-domains-edit' pk=portfolio_invitation.id as url3 %} {% url 'invitedmember-domains-edit' invitedmember_pk=portfolio_invitation.id as url3 %}
{% endif %} {% endif %}
<nav class="usa-breadcrumb padding-top-0" aria-label="Portfolio member breadcrumb"> <nav class="usa-breadcrumb padding-top-0" aria-label="Portfolio member breadcrumb">
<ol class="usa-breadcrumb__list"> <ol class="usa-breadcrumb__list">

View file

@ -15,11 +15,11 @@
{% url 'members' as url %} {% url 'members' as url %}
{% if portfolio_permission %} {% if portfolio_permission %}
{% url 'member' pk=portfolio_permission.id as url2 %} {% url 'member' member_pk=portfolio_permission.id as url2 %}
{% url 'member-domains' pk=portfolio_permission.id as url3 %} {% url 'member-domains' member_pk=portfolio_permission.id as url3 %}
{% else %} {% else %}
{% url 'invitedmember' pk=portfolio_invitation.id as url2 %} {% url 'invitedmember' invitedmember_pk=portfolio_invitation.id as url2 %}
{% url 'invitedmember-domains' pk=portfolio_invitation.id as url3 %} {% url 'invitedmember-domains' invitedmember_pk=portfolio_invitation.id as url3 %}
{% endif %} {% endif %}
<nav class="usa-breadcrumb padding-top-0" aria-label="Portfolio member breadcrumb"> <nav class="usa-breadcrumb padding-top-0" aria-label="Portfolio member breadcrumb">
<ol class="usa-breadcrumb__list"> <ol class="usa-breadcrumb__list">

View file

@ -20,9 +20,9 @@
<!-- Navigation breadcrumbs --> <!-- Navigation breadcrumbs -->
{% url 'members' as url %} {% url 'members' as url %}
{% if portfolio_permission %} {% if portfolio_permission %}
{% url 'member' pk=portfolio_permission.id as url2 %} {% url 'member' member_pk=portfolio_permission.id as url2 %}
{% else %} {% else %}
{% url 'invitedmember' pk=invitation.id as url2 %} {% url 'invitedmember' invitedmember_pk=invitation.id as url2 %}
{% endif %} {% endif %}
<nav class="usa-breadcrumb padding-top-0 bg-gray-1" aria-label="Portfolio member breadcrumb"> <nav class="usa-breadcrumb padding-top-0 bg-gray-1" aria-label="Portfolio member breadcrumb">
<ol class="usa-breadcrumb__list"> <ol class="usa-breadcrumb__list">

View file

@ -0,0 +1,355 @@
from django.test import Client
from django.urls import reverse
from waffle.testutils import override_flag
from registrar.tests.common import (
MockDbForIndividualTests,
less_console_noise_decorator,
completed_domain_request,
)
from registrar.models import (
DomainRequest,
Portfolio,
UserPortfolioPermission,
PortfolioInvitation,
)
from registrar.models.utility.portfolio_helper import (
UserPortfolioRoleChoices,
UserPortfolioPermissionChoices,
)
from registrar.decorators import (
_domain_exists_under_portfolio,
_domain_request_exists_under_portfolio,
_member_exists_under_portfolio,
_member_invitation_exists_under_portfolio,
)
class TestPortfolioResourceAccess(MockDbForIndividualTests):
"""Test functions that verify resources belong to a portfolio.
More specifically, this function tests our helper utilities in decorators.py"""
def setUp(self):
super().setUp()
# Create portfolios
self.portfolio = Portfolio.objects.create(creator=self.user, organization_name="Test Portfolio")
self.other_portfolio = Portfolio.objects.create(
creator=self.custom_staffuser, organization_name="Other Portfolio"
)
# Create domain requests
self.domain_request = completed_domain_request(name="eggnog.gov", user=self.user, portfolio=self.portfolio)
self.other_domain_request = completed_domain_request(
name="christmas.gov", user=self.tired_user, portfolio=self.other_portfolio
)
# Create domains
self.approved_domain_request_1 = completed_domain_request(
name="done_1.gov",
user=self.tired_user,
portfolio=self.portfolio,
status=DomainRequest.DomainRequestStatus.IN_REVIEW,
)
self.approved_domain_request_2 = completed_domain_request(
name="done_2.gov",
user=self.tired_user,
portfolio=self.other_portfolio,
status=DomainRequest.DomainRequestStatus.IN_REVIEW,
)
self.approved_domain_request_1.approve()
self.approved_domain_request_2.approve()
self.domain = self.approved_domain_request_1.approved_domain
self.other_domain = self.approved_domain_request_2.approved_domain
# Create portfolio permissions
self.user_permission = UserPortfolioPermission.objects.create(
user=self.user, portfolio=self.portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
)
self.other_user_permission = UserPortfolioPermission.objects.create(
user=self.tired_user, portfolio=self.other_portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
)
# Create portfolio invitations
self.portfolio_invitation = PortfolioInvitation.objects.create(
email="invited@example.com",
portfolio=self.portfolio,
roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
status=PortfolioInvitation.PortfolioInvitationStatus.INVITED,
)
self.other_portfolio_invitation = PortfolioInvitation.objects.create(
email="other-invited@example.com",
portfolio=self.other_portfolio,
roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
status=PortfolioInvitation.PortfolioInvitationStatus.INVITED,
)
# Domain request tests
@less_console_noise_decorator
def test_domain_request_exists_under_portfolio_when_pk_is_none(self):
"""Check behavior when the PK is None."""
self.assertTrue(_domain_request_exists_under_portfolio(self.portfolio, None))
@less_console_noise_decorator
def test_domain_request_exists_under_portfolio_when_exists(self):
"""Verify returns True when the domain request exists under the portfolio."""
self.assertTrue(_domain_request_exists_under_portfolio(self.portfolio, self.domain_request.id))
@less_console_noise_decorator
def test_domain_request_exists_under_portfolio_when_not_exists(self):
"""Verify returns False when the domain request does not exist under the portfolio."""
self.assertFalse(_domain_request_exists_under_portfolio(self.portfolio, self.other_domain_request.id))
# Domain tests
@less_console_noise_decorator
def test_domain_exists_under_portfolio_when_pk_is_none(self):
"""Check behavior when the PK is None."""
self.assertTrue(_domain_exists_under_portfolio(self.portfolio, None))
@less_console_noise_decorator
def test_domain_exists_under_portfolio_when_exists(self):
"""Verify returns True when the domain exists under the portfolio."""
self.assertTrue(_domain_exists_under_portfolio(self.portfolio, self.domain.id))
@less_console_noise_decorator
def test_domain_exists_under_portfolio_when_not_exists(self):
"""Verify returns False when the domain does not exist under the portfolio."""
self.assertFalse(_domain_exists_under_portfolio(self.portfolio, self.other_domain.id))
# Member tests
@less_console_noise_decorator
def test_member_exists_under_portfolio_when_pk_is_none(self):
"""Check behavior when the PK is None."""
self.assertTrue(_member_exists_under_portfolio(self.portfolio, None))
@less_console_noise_decorator
def test_member_exists_under_portfolio_when_exists(self):
"""Verify returns True when the member exists under the portfolio."""
self.assertTrue(_member_exists_under_portfolio(self.portfolio, self.user_permission.id))
@less_console_noise_decorator
def test_member_exists_under_portfolio_when_not_exists(self):
"""Verify returns False when the member does not exist under the portfolio."""
self.assertFalse(_member_exists_under_portfolio(self.portfolio, self.other_user_permission.id))
# Member invitation tests
@less_console_noise_decorator
def test_member_invitation_exists_under_portfolio_when_pk_is_none(self):
"""Check behavior when the PK is None."""
self.assertTrue(_member_invitation_exists_under_portfolio(self.portfolio, None))
@less_console_noise_decorator
def test_member_invitation_exists_under_portfolio_when_exists(self):
"""Verify returns True when the member invitation exists under the portfolio."""
self.assertTrue(_member_invitation_exists_under_portfolio(self.portfolio, self.portfolio_invitation.id))
@less_console_noise_decorator
def test_member_invitation_exists_under_portfolio_when_not_exists(self):
"""Verify returns False when the member invitation does not exist under the portfolio."""
self.assertFalse(_member_invitation_exists_under_portfolio(self.portfolio, self.other_portfolio_invitation.id))
class TestPortfolioDomainRequestViewAccess(MockDbForIndividualTests):
"""Tests for domain request views to ensure users can only access domain requests in their portfolio."""
def setUp(self):
super().setUp()
self.client = Client()
self.client.force_login(self.user)
# Create portfolios
self.portfolio = Portfolio.objects.create(creator=self.user, organization_name="Test Portfolio")
self.other_portfolio = Portfolio.objects.create(creator=self.tired_user, organization_name="Other Portfolio")
# Create domain requests
self.domain_request = completed_domain_request(
name="test-domain.gov",
portfolio=self.portfolio,
status=DomainRequest.DomainRequestStatus.STARTED,
user=self.user,
)
self.other_domain_request = completed_domain_request(
name="other-domain.gov",
portfolio=self.other_portfolio,
status=DomainRequest.DomainRequestStatus.STARTED,
user=self.tired_user,
)
# Give user permission to view all requests
self.user_permission = UserPortfolioPermission.objects.create(
user=self.user,
portfolio=self.portfolio,
roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER],
additional_permissions=[UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS],
)
# Setup session for portfolio views
session = self.client.session
session["portfolio"] = self.portfolio
session.save()
@override_flag("organization_feature", active=True)
@override_flag("organization_requests", active=True)
@less_console_noise_decorator
def test_domain_request_view_same_portfolio(self):
"""Test that user can access domain requests in their portfolio."""
# With just the view all permission, access should be denied
response = self.client.get(reverse("edit-domain-request", kwargs={"domain_request_pk": self.domain_request.pk}))
self.assertEqual(response.status_code, 403)
# But with the edit permission, the user should be able to access this domain request
self.user_permission.additional_permissions = [
UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS,
UserPortfolioPermissionChoices.EDIT_REQUESTS,
]
self.user_permission.save()
self.user_permission.refresh_from_db()
response = self.client.get(
reverse("edit-domain-request", kwargs={"domain_request_pk": self.domain_request.pk}), follow=True
)
self.assertEqual(response.status_code, 200)
@override_flag("organization_feature", active=True)
@override_flag("organization_requests", active=True)
@less_console_noise_decorator
def test_domain_request_view_different_portfolio(self):
"""Test that user cannot access domain request not in their portfolio."""
response = self.client.get(
reverse("edit-domain-request", kwargs={"domain_request_pk": self.other_domain_request.pk})
)
self.assertEqual(response.status_code, 403)
@override_flag("organization_feature", active=True)
@override_flag("organization_requests", active=True)
@less_console_noise_decorator
def test_domain_request_viewonly_same_portfolio(self):
"""Test that user can access view-only domain request in their portfolio."""
response = self.client.get(
reverse("domain-request-status-viewonly", kwargs={"domain_request_pk": self.domain_request.pk})
)
self.assertEqual(response.status_code, 200)
@override_flag("organization_feature", active=True)
@override_flag("organization_requests", active=True)
@less_console_noise_decorator
def test_domain_request_viewonly_different_portfolio(self):
"""Test that user cannot access view-only domain request not in their portfolio."""
response = self.client.get(
reverse("domain-request-status-viewonly", kwargs={"domain_request_pk": self.other_domain_request.pk})
)
self.assertEqual(response.status_code, 403)
class TestPortfolioDomainViewAccess(MockDbForIndividualTests):
"""Tests for domain views to ensure users can only access domains in their portfolio."""
def setUp(self):
super().setUp()
self.client = Client()
self.client.force_login(self.user)
# Create portfolios
self.portfolio = Portfolio.objects.create(creator=self.user, organization_name="Test Portfolio")
self.other_portfolio = Portfolio.objects.create(creator=self.tired_user, organization_name="Other Portfolio")
# Create domains through domain requests
self.domain_request = completed_domain_request(
name="test-domain.gov",
portfolio=self.portfolio,
status=DomainRequest.DomainRequestStatus.IN_REVIEW,
user=self.user,
)
self.domain_request.approve()
self.domain = self.domain_request.approved_domain
self.other_domain_request = completed_domain_request(
name="other-domain.gov",
portfolio=self.other_portfolio,
status=DomainRequest.DomainRequestStatus.IN_REVIEW,
user=self.user,
)
self.other_domain_request.approve()
self.other_domain = self.other_domain_request.approved_domain
# Give user permission to view all domains
self.user_permission = UserPortfolioPermission.objects.create(
user=self.user,
portfolio=self.portfolio,
roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER],
additional_permissions=[UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS],
)
# Setup session for portfolio views
session = self.client.session
session["portfolio"] = self.portfolio
session.save()
@override_flag("organization_feature", active=True)
@less_console_noise_decorator
def test_domain_view_same_portfolio(self):
"""Test that user can access domain in their portfolio."""
response = self.client.get(reverse("domain", kwargs={"domain_pk": self.domain.pk}))
self.assertEqual(response.status_code, 200)
@override_flag("organization_feature", active=True)
@less_console_noise_decorator
def test_domain_view_different_portfolio(self):
"""Test that user cannot access domain not in their portfolio."""
response = self.client.get(reverse("domain", kwargs={"domain_pk": self.other_domain.pk}))
self.assertEqual(response.status_code, 403)
class TestPortfolioMemberViewAccess(MockDbForIndividualTests):
"""Tests for member views to ensure users can only access members in their portfolio."""
def setUp(self):
super().setUp()
self.client = Client()
self.client.force_login(self.user)
# Create portfolios
self.portfolio = Portfolio.objects.create(creator=self.user, organization_name="Test Portfolio")
self.other_portfolio = Portfolio.objects.create(creator=self.tired_user, organization_name="Other Portfolio")
# Create portfolio permissions
self.member_permission = UserPortfolioPermission.objects.create(
user=self.meoward_user, portfolio=self.portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER]
)
self.other_member_permission = UserPortfolioPermission.objects.create(
user=self.lebowski_user,
portfolio=self.other_portfolio,
roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER],
)
# Give user permission to view/edit members
self.user_permission = UserPortfolioPermission.objects.create(
user=self.user,
portfolio=self.portfolio,
roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
)
# Setup session for portfolio views
session = self.client.session
session["portfolio"] = self.portfolio
session.save()
@override_flag("organization_feature", active=True)
@override_flag("organization_members", active=True)
@less_console_noise_decorator
def test_member_view_same_portfolio(self):
"""Test that user can access member in their portfolio."""
response = self.client.get(reverse("member", kwargs={"member_pk": self.member_permission.pk}))
self.assertEqual(response.status_code, 200)
@override_flag("organization_feature", active=True)
@override_flag("organization_members", active=True)
@less_console_noise_decorator
def test_member_view_different_portfolio(self):
"""Test that user cannot access member not in their portfolio."""
response = self.client.get(reverse("member", kwargs={"member_pk": self.other_member_permission.pk}))
self.assertEqual(response.status_code, 403)

View file

@ -29,6 +29,8 @@ SAMPLE_KWARGS = {
"user_pk": "1", "user_pk": "1",
"portfolio_id": "1", "portfolio_id": "1",
"user_id": "1", "user_id": "1",
"member_pk": "1",
"invitedmember_pk": "1",
} }
# Our test suite will ignore some namespaces. # Our test suite will ignore some namespaces.

View file

@ -867,7 +867,7 @@ class TestPortfolio(WebTest):
# Verify that the user cannot access the member page # Verify that the user cannot access the member page
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.get(reverse("member", kwargs={"pk": 1}), follow=True) response = self.client.get(reverse("member", kwargs={"member_pk": 1}), follow=True)
# Make sure the page is denied # Make sure the page is denied
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
@ -886,7 +886,7 @@ class TestPortfolio(WebTest):
# Verify that the user cannot access the member page # Verify that the user cannot access the member page
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.get(reverse("member", kwargs={"pk": 1}), follow=True) response = self.client.get(reverse("member", kwargs={"member_pk": 1}), follow=True)
# Make sure the page is denied # Make sure the page is denied
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
@ -909,7 +909,7 @@ class TestPortfolio(WebTest):
# Verify the page can be accessed # Verify the page can be accessed
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.get(reverse("member", kwargs={"pk": permission_obj.pk}), follow=True) response = self.client.get(reverse("member", kwargs={"member_pk": permission_obj.pk}), follow=True)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
# Assert text within the page is correct # Assert text within the page is correct
@ -942,7 +942,7 @@ class TestPortfolio(WebTest):
# Verify the page can be accessed # Verify the page can be accessed
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.get(reverse("member", kwargs={"pk": permission_obj.pk}), follow=True) response = self.client.get(reverse("member", kwargs={"member_pk": permission_obj.pk}), follow=True)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
# Assert text within the page is correct # Assert text within the page is correct
@ -966,7 +966,7 @@ class TestPortfolio(WebTest):
# Verify that the user cannot access the member page # Verify that the user cannot access the member page
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.get(reverse("invitedmember", kwargs={"pk": 1}), follow=True) response = self.client.get(reverse("invitedmember", kwargs={"invitedmember_pk": 1}), follow=True)
# Make sure the page is denied # Make sure the page is denied
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
@ -985,7 +985,7 @@ class TestPortfolio(WebTest):
# Verify that the user cannot access the member page # Verify that the user cannot access the member page
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.get(reverse("invitedmember", kwargs={"pk": 1}), follow=True) response = self.client.get(reverse("invitedmember", kwargs={"invitedmember_pk": 1}), follow=True)
# Make sure the page is denied # Make sure the page is denied
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
@ -1016,7 +1016,9 @@ class TestPortfolio(WebTest):
# Verify the page can be accessed # Verify the page can be accessed
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.get(reverse("invitedmember", kwargs={"pk": portfolio_invitation.pk}), follow=True) response = self.client.get(
reverse("invitedmember", kwargs={"invitedmember_pk": portfolio_invitation.pk}), follow=True
)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
# Assert text within the page is correct # Assert text within the page is correct
@ -1054,7 +1056,9 @@ class TestPortfolio(WebTest):
# Verify the page can be accessed # Verify the page can be accessed
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.get(reverse("invitedmember", kwargs={"pk": portfolio_invitation.pk}), follow=True) response = self.client.get(
reverse("invitedmember", kwargs={"invitedmember_pk": portfolio_invitation.pk}), follow=True
)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
# Assert text within the page is correct # Assert text within the page is correct
@ -1697,7 +1701,7 @@ class TestPortfolioMemberDeleteView(WebTest):
self.client.force_login(self.user) self.client.force_login(self.user)
# We check X_REQUESTED_WITH bc those return JSON responses # We check X_REQUESTED_WITH bc those return JSON responses
response = self.client.post( response = self.client.post(
reverse("member-delete", kwargs={"pk": upp.pk}), HTTP_X_REQUESTED_WITH="XMLHttpRequest" reverse("member-delete", kwargs={"member_pk": upp.pk}), HTTP_X_REQUESTED_WITH="XMLHttpRequest"
) )
self.assertEqual(response.status_code, 400) # Bad request due to active requests self.assertEqual(response.status_code, 400) # Bad request due to active requests
@ -1738,7 +1742,8 @@ class TestPortfolioMemberDeleteView(WebTest):
self.client.force_login(self.user) self.client.force_login(self.user)
# We check X_REQUESTED_WITH bc those return JSON responses # We check X_REQUESTED_WITH bc those return JSON responses
response = self.client.post( response = self.client.post(
reverse("member-delete", kwargs={"pk": admin_perm_user.pk}), HTTP_X_REQUESTED_WITH="XMLHttpRequest" reverse("member-delete", kwargs={"member_pk": admin_perm_user.pk}),
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
) )
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
@ -1795,7 +1800,7 @@ class TestPortfolioMemberDeleteView(WebTest):
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.post( response = self.client.post(
# We check X_REQUESTED_WITH bc those return JSON responses # We check X_REQUESTED_WITH bc those return JSON responses
reverse("member-delete", kwargs={"pk": upp.pk}), reverse("member-delete", kwargs={"member_pk": upp.pk}),
HTTP_X_REQUESTED_WITH="XMLHttpRequest", HTTP_X_REQUESTED_WITH="XMLHttpRequest",
) )
@ -1862,7 +1867,7 @@ class TestPortfolioMemberDeleteView(WebTest):
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.post( response = self.client.post(
# We check X_REQUESTED_WITH bc those return JSON responses # We check X_REQUESTED_WITH bc those return JSON responses
reverse("member-delete", kwargs={"pk": upp.pk}), reverse("member-delete", kwargs={"member_pk": upp.pk}),
HTTP_X_REQUESTED_WITH="XMLHttpRequest", HTTP_X_REQUESTED_WITH="XMLHttpRequest",
) )
@ -1939,7 +1944,7 @@ class TestPortfolioMemberDeleteView(WebTest):
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.post( response = self.client.post(
# We check X_REQUESTED_WITH bc those return JSON responses # We check X_REQUESTED_WITH bc those return JSON responses
reverse("member-delete", kwargs={"pk": upp.pk}), reverse("member-delete", kwargs={"member_pk": upp.pk}),
HTTP_X_REQUESTED_WITH="XMLHttpRequest", HTTP_X_REQUESTED_WITH="XMLHttpRequest",
) )
@ -2000,7 +2005,7 @@ class TestPortfolioMemberDeleteView(WebTest):
with patch("django.contrib.messages.error") as mock_error: with patch("django.contrib.messages.error") as mock_error:
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.post( response = self.client.post(
reverse("member-delete", kwargs={"pk": upp.pk}), reverse("member-delete", kwargs={"member_pk": upp.pk}),
) )
# We don't want to do follow=True in response bc that does automatic redirection # We don't want to do follow=True in response bc that does automatic redirection
@ -2023,7 +2028,7 @@ class TestPortfolioMemberDeleteView(WebTest):
# Location is used for a 3xx HTTP status code to indicate that the URL was redirected # Location is used for a 3xx HTTP status code to indicate that the URL was redirected
# and then confirm that we're still on the Manage Members page # and then confirm that we're still on the Manage Members page
self.assertEqual(response.headers["Location"], reverse("member", kwargs={"pk": upp.pk})) self.assertEqual(response.headers["Location"], reverse("member", kwargs={"member_pk": upp.pk}))
@less_console_noise_decorator @less_console_noise_decorator
@override_flag("organization_feature", active=True) @override_flag("organization_feature", active=True)
@ -2047,7 +2052,7 @@ class TestPortfolioMemberDeleteView(WebTest):
with patch("django.contrib.messages.error") as mock_error: with patch("django.contrib.messages.error") as mock_error:
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.post( response = self.client.post(
reverse("member-delete", kwargs={"pk": admin_perm_user.pk}), reverse("member-delete", kwargs={"member_pk": admin_perm_user.pk}),
) )
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
@ -2066,7 +2071,9 @@ class TestPortfolioMemberDeleteView(WebTest):
# Location is used for a 3xx HTTP status code to indicate that the URL was redirected # Location is used for a 3xx HTTP status code to indicate that the URL was redirected
# and then confirm that we're still on the Manage Members page # and then confirm that we're still on the Manage Members page
self.assertEqual(response.headers["Location"], reverse("member", kwargs={"pk": admin_perm_user.pk})) self.assertEqual(
response.headers["Location"], reverse("member", kwargs={"member_pk": admin_perm_user.pk})
)
class TestPortfolioInvitedMemberDeleteView(WebTest): class TestPortfolioInvitedMemberDeleteView(WebTest):
@ -2125,7 +2132,7 @@ class TestPortfolioInvitedMemberDeleteView(WebTest):
with patch("django.contrib.messages.success") as mock_success: with patch("django.contrib.messages.success") as mock_success:
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.post( response = self.client.post(
reverse("invitedmember-delete", kwargs={"pk": invitation.pk}), reverse("invitedmember-delete", kwargs={"invitedmember_pk": invitation.pk}),
) )
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
@ -2190,7 +2197,7 @@ class TestPortfolioInvitedMemberDeleteView(WebTest):
with patch("django.contrib.messages.success") as mock_success: with patch("django.contrib.messages.success") as mock_success:
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.post( response = self.client.post(
reverse("invitedmember-delete", kwargs={"pk": invitation.pk}), reverse("invitedmember-delete", kwargs={"invitedmember_pk": invitation.pk}),
) )
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
@ -2263,7 +2270,7 @@ class TestPortfolioInvitedMemberDeleteView(WebTest):
with patch("django.contrib.messages.success") as mock_success: with patch("django.contrib.messages.success") as mock_success:
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.post( response = self.client.post(
reverse("invitedmember-delete", kwargs={"pk": invitation.pk}), reverse("invitedmember-delete", kwargs={"invitedmember_pk": invitation.pk}),
) )
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
@ -2365,7 +2372,7 @@ class TestPortfolioMemberDomainsView(TestWithUser, WebTest):
"""Tests that the portfolio member domains view is accessible.""" """Tests that the portfolio member domains view is accessible."""
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.get(reverse("member-domains", kwargs={"pk": self.permission.id})) response = self.client.get(reverse("member-domains", kwargs={"member_pk": self.permission.id}))
# Make sure the page loaded, and that we're on the right page # Make sure the page loaded, and that we're on the right page
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@ -2378,7 +2385,7 @@ class TestPortfolioMemberDomainsView(TestWithUser, WebTest):
"""Tests that the portfolio member domains view is not accessible to user with no perms.""" """Tests that the portfolio member domains view is not accessible to user with no perms."""
self.client.force_login(self.user_no_perms) self.client.force_login(self.user_no_perms)
response = self.client.get(reverse("member-domains", kwargs={"pk": self.permission.id})) response = self.client.get(reverse("member-domains", kwargs={"member_pk": self.permission.id}))
# Make sure the request returns forbidden # Make sure the request returns forbidden
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
@ -2390,7 +2397,7 @@ class TestPortfolioMemberDomainsView(TestWithUser, WebTest):
"""Tests that the portfolio member domains view is not accessible when no authenticated user.""" """Tests that the portfolio member domains view is not accessible when no authenticated user."""
self.client.logout() self.client.logout()
response = self.client.get(reverse("member-domains", kwargs={"pk": self.permission.id})) response = self.client.get(reverse("member-domains", kwargs={"member_pk": self.permission.id}))
# Make sure the request returns redirect to openid login # Make sure the request returns redirect to openid login
self.assertEqual(response.status_code, 302) # Redirect to openid login self.assertEqual(response.status_code, 302) # Redirect to openid login
@ -2403,7 +2410,7 @@ class TestPortfolioMemberDomainsView(TestWithUser, WebTest):
"""Tests that the portfolio member domains view returns not found if user portfolio permission not found.""" """Tests that the portfolio member domains view returns not found if user portfolio permission not found."""
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.get(reverse("member-domains", kwargs={"pk": "0"})) response = self.client.get(reverse("member-domains", kwargs={"member_pk": "0"}))
# Make sure the response is not found # Make sure the response is not found
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
@ -2463,7 +2470,7 @@ class TestPortfolioInvitedMemberDomainsView(TestWithUser, WebTest):
"""Tests that the portfolio invited member domains view is accessible.""" """Tests that the portfolio invited member domains view is accessible."""
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.get(reverse("invitedmember-domains", kwargs={"pk": self.invitation.id})) response = self.client.get(reverse("invitedmember-domains", kwargs={"invitedmember_pk": self.invitation.id}))
# Make sure the page loaded, and that we're on the right page # Make sure the page loaded, and that we're on the right page
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@ -2476,7 +2483,7 @@ class TestPortfolioInvitedMemberDomainsView(TestWithUser, WebTest):
"""Tests that the portfolio invited member domains view is not accessible to user with no perms.""" """Tests that the portfolio invited member domains view is not accessible to user with no perms."""
self.client.force_login(self.user_no_perms) self.client.force_login(self.user_no_perms)
response = self.client.get(reverse("invitedmember-domains", kwargs={"pk": self.invitation.id})) response = self.client.get(reverse("invitedmember-domains", kwargs={"invitedmember_pk": self.invitation.id}))
# Make sure the request returns forbidden # Make sure the request returns forbidden
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
@ -2488,7 +2495,7 @@ class TestPortfolioInvitedMemberDomainsView(TestWithUser, WebTest):
"""Tests that the portfolio invited member domains view is not accessible when no authenticated user.""" """Tests that the portfolio invited member domains view is not accessible when no authenticated user."""
self.client.logout() self.client.logout()
response = self.client.get(reverse("invitedmember-domains", kwargs={"pk": self.invitation.id})) response = self.client.get(reverse("invitedmember-domains", kwargs={"invitedmember_pk": self.invitation.id}))
# Make sure the request returns redirect to openid login # Make sure the request returns redirect to openid login
self.assertEqual(response.status_code, 302) # Redirect to openid login self.assertEqual(response.status_code, 302) # Redirect to openid login
@ -2501,7 +2508,7 @@ class TestPortfolioInvitedMemberDomainsView(TestWithUser, WebTest):
"""Tests that the portfolio invited member domains view returns not found if user is not a member.""" """Tests that the portfolio invited member domains view returns not found if user is not a member."""
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.get(reverse("invitedmember-domains", kwargs={"pk": "0"})) response = self.client.get(reverse("invitedmember-domains", kwargs={"invitedmember_pk": "0"}))
# Make sure the response is not found # Make sure the response is not found
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
@ -2566,7 +2573,7 @@ class TestPortfolioMemberDomainsEditView(TestWithUser, WebTest):
], ],
) )
# Create url to be used in all tests # Create url to be used in all tests
self.url = reverse("member-domains-edit", kwargs={"pk": self.portfolio_permission.pk}) self.url = reverse("member-domains-edit", kwargs={"member_pk": self.portfolio_permission.pk})
def tearDown(self): def tearDown(self):
super().tearDown() super().tearDown()
@ -2584,7 +2591,7 @@ class TestPortfolioMemberDomainsEditView(TestWithUser, WebTest):
"""Tests that the portfolio member domains edit view is accessible.""" """Tests that the portfolio member domains edit view is accessible."""
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.get(reverse("member-domains-edit", kwargs={"pk": self.permission.id})) response = self.client.get(reverse("member-domains-edit", kwargs={"member_pk": self.permission.id}))
# Make sure the page loaded, and that we're on the right page # Make sure the page loaded, and that we're on the right page
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@ -2597,7 +2604,7 @@ class TestPortfolioMemberDomainsEditView(TestWithUser, WebTest):
"""Tests that the portfolio member domains edit view is not accessible to user with no perms.""" """Tests that the portfolio member domains edit view is not accessible to user with no perms."""
self.client.force_login(self.user_no_perms) self.client.force_login(self.user_no_perms)
response = self.client.get(reverse("member-domains-edit", kwargs={"pk": self.permission.id})) response = self.client.get(reverse("member-domains-edit", kwargs={"member_pk": self.permission.id}))
# Make sure the request returns forbidden # Make sure the request returns forbidden
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
@ -2609,7 +2616,7 @@ class TestPortfolioMemberDomainsEditView(TestWithUser, WebTest):
"""Tests that the portfolio member domains edit view is not accessible when no authenticated user.""" """Tests that the portfolio member domains edit view is not accessible when no authenticated user."""
self.client.logout() self.client.logout()
response = self.client.get(reverse("member-domains-edit", kwargs={"pk": self.permission.id})) response = self.client.get(reverse("member-domains-edit", kwargs={"member_pk": self.permission.id}))
# Make sure the request returns redirect to openid login # Make sure the request returns redirect to openid login
self.assertEqual(response.status_code, 302) # Redirect to openid login self.assertEqual(response.status_code, 302) # Redirect to openid login
@ -2623,7 +2630,7 @@ class TestPortfolioMemberDomainsEditView(TestWithUser, WebTest):
portfolio permission not found.""" portfolio permission not found."""
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.get(reverse("member-domains-edit", kwargs={"pk": "0"})) response = self.client.get(reverse("member-domains-edit", kwargs={"member_pk": "0"}))
# Make sure the response is not found # Make sure the response is not found
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
@ -2645,7 +2652,7 @@ class TestPortfolioMemberDomainsEditView(TestWithUser, WebTest):
self.assertEqual(UserDomainRole.objects.filter(user=self.user, role=UserDomainRole.Roles.MANAGER).count(), 3) self.assertEqual(UserDomainRole.objects.filter(user=self.user, role=UserDomainRole.Roles.MANAGER).count(), 3)
# Check for a success message and a redirect # Check for a success message and a redirect
self.assertRedirects(response, reverse("member-domains", kwargs={"pk": self.portfolio_permission.pk})) self.assertRedirects(response, reverse("member-domains", kwargs={"member_pk": self.portfolio_permission.pk}))
messages = list(response.wsgi_request._messages) messages = list(response.wsgi_request._messages)
self.assertEqual(len(messages), 1) self.assertEqual(len(messages), 1)
self.assertEqual(str(messages[0]), "The domain assignment changes have been saved.") self.assertEqual(str(messages[0]), "The domain assignment changes have been saved.")
@ -2681,7 +2688,7 @@ class TestPortfolioMemberDomainsEditView(TestWithUser, WebTest):
self.assertEqual(UserDomainRole.objects.filter(domain=self.domain3, user=self.user).count(), 1) self.assertEqual(UserDomainRole.objects.filter(domain=self.domain3, user=self.user).count(), 1)
# Check for a success message and a redirect # Check for a success message and a redirect
self.assertRedirects(response, reverse("member-domains", kwargs={"pk": self.portfolio_permission.pk})) self.assertRedirects(response, reverse("member-domains", kwargs={"member_pk": self.portfolio_permission.pk}))
messages = list(response.wsgi_request._messages) messages = list(response.wsgi_request._messages)
self.assertEqual(len(messages), 1) self.assertEqual(len(messages), 1)
self.assertEqual(str(messages[0]), "The domain assignment changes have been saved.") self.assertEqual(str(messages[0]), "The domain assignment changes have been saved.")
@ -2706,7 +2713,7 @@ class TestPortfolioMemberDomainsEditView(TestWithUser, WebTest):
self.assertEqual(UserDomainRole.objects.filter(user=self.user).count(), 0) self.assertEqual(UserDomainRole.objects.filter(user=self.user).count(), 0)
# Check for an error message and a redirect # Check for an error message and a redirect
self.assertRedirects(response, reverse("member-domains", kwargs={"pk": self.portfolio_permission.pk})) self.assertRedirects(response, reverse("member-domains", kwargs={"member_pk": self.portfolio_permission.pk}))
messages = list(response.wsgi_request._messages) messages = list(response.wsgi_request._messages)
self.assertEqual(len(messages), 1) self.assertEqual(len(messages), 1)
self.assertEqual( self.assertEqual(
@ -2729,7 +2736,7 @@ class TestPortfolioMemberDomainsEditView(TestWithUser, WebTest):
self.assertEqual(UserDomainRole.objects.filter(user=self.user).count(), 0) self.assertEqual(UserDomainRole.objects.filter(user=self.user).count(), 0)
# Check for an error message and a redirect # Check for an error message and a redirect
self.assertRedirects(response, reverse("member-domains", kwargs={"pk": self.portfolio_permission.pk})) self.assertRedirects(response, reverse("member-domains", kwargs={"member_pk": self.portfolio_permission.pk}))
messages = list(response.wsgi_request._messages) messages = list(response.wsgi_request._messages)
self.assertEqual(len(messages), 1) self.assertEqual(len(messages), 1)
self.assertEqual( self.assertEqual(
@ -2749,7 +2756,7 @@ class TestPortfolioMemberDomainsEditView(TestWithUser, WebTest):
self.assertEqual(UserDomainRole.objects.filter(user=self.user).count(), 0) self.assertEqual(UserDomainRole.objects.filter(user=self.user).count(), 0)
# Check for an info message and a redirect # Check for an info message and a redirect
self.assertRedirects(response, reverse("member-domains", kwargs={"pk": self.portfolio_permission.pk})) self.assertRedirects(response, reverse("member-domains", kwargs={"member_pk": self.portfolio_permission.pk}))
messages = list(response.wsgi_request._messages) messages = list(response.wsgi_request._messages)
self.assertEqual(len(messages), 1) self.assertEqual(len(messages), 1)
self.assertEqual(str(messages[0]), "The domain assignment changes have been saved.") self.assertEqual(str(messages[0]), "The domain assignment changes have been saved.")
@ -2772,7 +2779,9 @@ class TestPortfolioMemberDomainsEditView(TestWithUser, WebTest):
self.assertEqual(UserDomainRole.objects.filter(user=self.user, role=UserDomainRole.Roles.MANAGER).count(), 0) self.assertEqual(UserDomainRole.objects.filter(user=self.user, role=UserDomainRole.Roles.MANAGER).count(), 0)
# Check for an error message and a redirect to edit form # Check for an error message and a redirect to edit form
self.assertRedirects(response, reverse("member-domains-edit", kwargs={"pk": self.portfolio_permission.pk})) self.assertRedirects(
response, reverse("member-domains-edit", kwargs={"member_pk": self.portfolio_permission.pk})
)
messages = list(response.wsgi_request._messages) messages = list(response.wsgi_request._messages)
self.assertEqual(len(messages), 1) self.assertEqual(len(messages), 1)
self.assertEqual( self.assertEqual(
@ -2831,7 +2840,7 @@ class TestPortfolioInvitedMemberEditDomainsView(TestWithUser, WebTest):
UserPortfolioPermissionChoices.EDIT_MEMBERS, UserPortfolioPermissionChoices.EDIT_MEMBERS,
], ],
) )
self.url = reverse("invitedmember-domains-edit", kwargs={"pk": self.invitation.pk}) self.url = reverse("invitedmember-domains-edit", kwargs={"invitedmember_pk": self.invitation.pk})
def tearDown(self): def tearDown(self):
super().tearDown() super().tearDown()
@ -2849,7 +2858,9 @@ class TestPortfolioInvitedMemberEditDomainsView(TestWithUser, WebTest):
"""Tests that the portfolio invited member domains edit view is accessible.""" """Tests that the portfolio invited member domains edit view is accessible."""
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.get(reverse("invitedmember-domains-edit", kwargs={"pk": self.invitation.id})) response = self.client.get(
reverse("invitedmember-domains-edit", kwargs={"invitedmember_pk": self.invitation.id})
)
# Make sure the page loaded, and that we're on the right page # Make sure the page loaded, and that we're on the right page
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@ -2862,7 +2873,9 @@ class TestPortfolioInvitedMemberEditDomainsView(TestWithUser, WebTest):
"""Tests that the portfolio invited member domains edit view is not accessible to user with no perms.""" """Tests that the portfolio invited member domains edit view is not accessible to user with no perms."""
self.client.force_login(self.user_no_perms) self.client.force_login(self.user_no_perms)
response = self.client.get(reverse("invitedmember-domains-edit", kwargs={"pk": self.invitation.id})) response = self.client.get(
reverse("invitedmember-domains-edit", kwargs={"invitedmember_pk": self.invitation.id})
)
# Make sure the request returns forbidden # Make sure the request returns forbidden
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
@ -2874,7 +2887,9 @@ class TestPortfolioInvitedMemberEditDomainsView(TestWithUser, WebTest):
"""Tests that the portfolio invited member domains edit view is not accessible when no authenticated user.""" """Tests that the portfolio invited member domains edit view is not accessible when no authenticated user."""
self.client.logout() self.client.logout()
response = self.client.get(reverse("invitedmember-domains-edit", kwargs={"pk": self.invitation.id})) response = self.client.get(
reverse("invitedmember-domains-edit", kwargs={"invitedmember_pk": self.invitation.id})
)
# Make sure the request returns redirect to openid login # Make sure the request returns redirect to openid login
self.assertEqual(response.status_code, 302) # Redirect to openid login self.assertEqual(response.status_code, 302) # Redirect to openid login
@ -2887,7 +2902,7 @@ class TestPortfolioInvitedMemberEditDomainsView(TestWithUser, WebTest):
"""Tests that the portfolio invited member domains edit view returns not found if user is not a member.""" """Tests that the portfolio invited member domains edit view returns not found if user is not a member."""
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.get(reverse("invitedmember-domains-edit", kwargs={"pk": "0"})) response = self.client.get(reverse("invitedmember-domains-edit", kwargs={"invitedmember_pk": "0"}))
# Make sure the response is not found # Make sure the response is not found
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
@ -2914,7 +2929,9 @@ class TestPortfolioInvitedMemberEditDomainsView(TestWithUser, WebTest):
) )
# Check for a success message and a redirect # Check for a success message and a redirect
self.assertRedirects(response, reverse("invitedmember-domains", kwargs={"pk": self.invitation.pk})) self.assertRedirects(
response, reverse("invitedmember-domains", kwargs={"invitedmember_pk": self.invitation.pk})
)
messages = list(response.wsgi_request._messages) messages = list(response.wsgi_request._messages)
self.assertEqual(len(messages), 1) self.assertEqual(len(messages), 1)
self.assertEqual(str(messages[0]), "The domain assignment changes have been saved.") self.assertEqual(str(messages[0]), "The domain assignment changes have been saved.")
@ -2971,7 +2988,9 @@ class TestPortfolioInvitedMemberEditDomainsView(TestWithUser, WebTest):
) )
# Check for a success message and a redirect # Check for a success message and a redirect
self.assertRedirects(response, reverse("invitedmember-domains", kwargs={"pk": self.invitation.pk})) self.assertRedirects(
response, reverse("invitedmember-domains", kwargs={"invitedmember_pk": self.invitation.pk})
)
@less_console_noise_decorator @less_console_noise_decorator
@override_flag("organization_feature", active=True) @override_flag("organization_feature", active=True)
@ -3015,7 +3034,9 @@ class TestPortfolioInvitedMemberEditDomainsView(TestWithUser, WebTest):
) )
# Check for a success message and a redirect # Check for a success message and a redirect
self.assertRedirects(response, reverse("invitedmember-domains", kwargs={"pk": self.invitation.pk})) self.assertRedirects(
response, reverse("invitedmember-domains", kwargs={"invitedmember_pk": self.invitation.pk})
)
# assert that send_domain_invitation_email is not called # assert that send_domain_invitation_email is not called
mock_send_domain_email.assert_not_called() mock_send_domain_email.assert_not_called()
@ -3035,7 +3056,9 @@ class TestPortfolioInvitedMemberEditDomainsView(TestWithUser, WebTest):
self.assertEqual(DomainInvitation.objects.count(), 0) self.assertEqual(DomainInvitation.objects.count(), 0)
# Check for an error message and a redirect # Check for an error message and a redirect
self.assertRedirects(response, reverse("invitedmember-domains", kwargs={"pk": self.invitation.pk})) self.assertRedirects(
response, reverse("invitedmember-domains", kwargs={"invitedmember_pk": self.invitation.pk})
)
messages = list(response.wsgi_request._messages) messages = list(response.wsgi_request._messages)
self.assertEqual(len(messages), 1) self.assertEqual(len(messages), 1)
self.assertEqual( self.assertEqual(
@ -3058,7 +3081,9 @@ class TestPortfolioInvitedMemberEditDomainsView(TestWithUser, WebTest):
self.assertEqual(DomainInvitation.objects.count(), 0) self.assertEqual(DomainInvitation.objects.count(), 0)
# Check for an error message and a redirect # Check for an error message and a redirect
self.assertRedirects(response, reverse("invitedmember-domains", kwargs={"pk": self.invitation.pk})) self.assertRedirects(
response, reverse("invitedmember-domains", kwargs={"invitedmember_pk": self.invitation.pk})
)
messages = list(response.wsgi_request._messages) messages = list(response.wsgi_request._messages)
self.assertEqual(len(messages), 1) self.assertEqual(len(messages), 1)
self.assertEqual( self.assertEqual(
@ -3078,7 +3103,9 @@ class TestPortfolioInvitedMemberEditDomainsView(TestWithUser, WebTest):
self.assertEqual(DomainInvitation.objects.count(), 0) self.assertEqual(DomainInvitation.objects.count(), 0)
# Check for an info message and a redirect # Check for an info message and a redirect
self.assertRedirects(response, reverse("invitedmember-domains", kwargs={"pk": self.invitation.pk})) self.assertRedirects(
response, reverse("invitedmember-domains", kwargs={"invitedmember_pk": self.invitation.pk})
)
messages = list(response.wsgi_request._messages) messages = list(response.wsgi_request._messages)
self.assertEqual(len(messages), 1) self.assertEqual(len(messages), 1)
self.assertEqual(str(messages[0]), "The domain assignment changes have been saved.") self.assertEqual(str(messages[0]), "The domain assignment changes have been saved.")
@ -3106,7 +3133,9 @@ class TestPortfolioInvitedMemberEditDomainsView(TestWithUser, WebTest):
) )
# Check for an error message and a redirect to edit form # Check for an error message and a redirect to edit form
self.assertRedirects(response, reverse("invitedmember-domains-edit", kwargs={"pk": self.invitation.pk})) self.assertRedirects(
response, reverse("invitedmember-domains-edit", kwargs={"invitedmember_pk": self.invitation.pk})
)
messages = list(response.wsgi_request._messages) messages = list(response.wsgi_request._messages)
self.assertEqual(len(messages), 1) self.assertEqual(len(messages), 1)
self.assertEqual( self.assertEqual(
@ -4081,7 +4110,7 @@ class TestPortfolioMemberEditView(WebTest):
mock_send_update_email.return_value = True mock_send_update_email.return_value = True
response = self.client.post( response = self.client.post(
reverse("member-permissions", kwargs={"pk": basic_permission.id}), reverse("member-permissions", kwargs={"member_pk": basic_permission.id}),
{ {
"role": UserPortfolioRoleChoices.ORGANIZATION_ADMIN, "role": UserPortfolioRoleChoices.ORGANIZATION_ADMIN,
}, },
@ -4144,7 +4173,7 @@ class TestPortfolioMemberEditView(WebTest):
mock_send_update_email.return_value = False mock_send_update_email.return_value = False
response = self.client.post( response = self.client.post(
reverse("member-permissions", kwargs={"pk": basic_permission.id}), reverse("member-permissions", kwargs={"member_pk": basic_permission.id}),
{ {
"role": UserPortfolioRoleChoices.ORGANIZATION_ADMIN, "role": UserPortfolioRoleChoices.ORGANIZATION_ADMIN,
}, },
@ -4211,7 +4240,7 @@ class TestPortfolioMemberEditView(WebTest):
) )
response = self.client.post( response = self.client.post(
reverse("member-permissions", kwargs={"pk": admin_permission.id}), reverse("member-permissions", kwargs={"member_pk": admin_permission.id}),
{ {
"role": UserPortfolioRoleChoices.ORGANIZATION_ADMIN, "role": UserPortfolioRoleChoices.ORGANIZATION_ADMIN,
}, },
@ -4249,7 +4278,7 @@ class TestPortfolioMemberEditView(WebTest):
mock_send_update_email.return_value = True mock_send_update_email.return_value = True
response = self.client.post( response = self.client.post(
reverse("member-permissions", kwargs={"pk": basic_permission.id}), reverse("member-permissions", kwargs={"member_pk": basic_permission.id}),
{ {
"role": UserPortfolioRoleChoices.ORGANIZATION_MEMBER, "role": UserPortfolioRoleChoices.ORGANIZATION_MEMBER,
"domain_permissions": UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS, "domain_permissions": UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS,
@ -4298,7 +4327,7 @@ class TestPortfolioMemberEditView(WebTest):
mock_send_update_email.return_value = True mock_send_update_email.return_value = True
response = self.client.post( response = self.client.post(
reverse("member-permissions", kwargs={"pk": admin_permission.id}), reverse("member-permissions", kwargs={"member_pk": admin_permission.id}),
{ {
"role": UserPortfolioRoleChoices.ORGANIZATION_MEMBER, "role": UserPortfolioRoleChoices.ORGANIZATION_MEMBER,
"domain_permissions": UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS, "domain_permissions": UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS,
@ -4361,7 +4390,7 @@ class TestPortfolioMemberEditView(WebTest):
mock_send_update_email.return_value = False mock_send_update_email.return_value = False
response = self.client.post( response = self.client.post(
reverse("member-permissions", kwargs={"pk": admin_permission.id}), reverse("member-permissions", kwargs={"member_pk": admin_permission.id}),
{ {
"role": UserPortfolioRoleChoices.ORGANIZATION_MEMBER, "role": UserPortfolioRoleChoices.ORGANIZATION_MEMBER,
"domain_permissions": UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS, "domain_permissions": UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS,
@ -4421,7 +4450,7 @@ class TestPortfolioMemberEditView(WebTest):
# Test missing required admin permissions # Test missing required admin permissions
response = self.client.post( response = self.client.post(
reverse("member-permissions", kwargs={"pk": permission.id}), reverse("member-permissions", kwargs={"member_pk": permission.id}),
{ {
"role": UserPortfolioRoleChoices.ORGANIZATION_MEMBER, "role": UserPortfolioRoleChoices.ORGANIZATION_MEMBER,
# Missing required admin fields # Missing required admin fields
@ -4453,7 +4482,7 @@ class TestPortfolioMemberEditView(WebTest):
admin_permission = UserPortfolioPermission.objects.get(user=self.user, portfolio=self.portfolio) admin_permission = UserPortfolioPermission.objects.get(user=self.user, portfolio=self.portfolio)
response = self.client.post( response = self.client.post(
reverse("member-permissions", kwargs={"pk": admin_permission.id}), reverse("member-permissions", kwargs={"member_pk": admin_permission.id}),
{ {
"role": UserPortfolioRoleChoices.ORGANIZATION_MEMBER, "role": UserPortfolioRoleChoices.ORGANIZATION_MEMBER,
"domain_permissions": UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS, "domain_permissions": UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS,
@ -4526,7 +4555,7 @@ class TestPortfolioInvitedMemberEditView(WebTest):
# Test updating invitation permissions # Test updating invitation permissions
response = self.client.post( response = self.client.post(
reverse("invitedmember-permissions", kwargs={"pk": self.invitation.id}), reverse("invitedmember-permissions", kwargs={"invitedmember_pk": self.invitation.id}),
{ {
"role": UserPortfolioRoleChoices.ORGANIZATION_ADMIN, "role": UserPortfolioRoleChoices.ORGANIZATION_ADMIN,
}, },
@ -4569,7 +4598,7 @@ class TestPortfolioInvitedMemberEditView(WebTest):
# Test updating invitation permissions # Test updating invitation permissions
response = self.client.post( response = self.client.post(
reverse("invitedmember-permissions", kwargs={"pk": self.invitation.id}), reverse("invitedmember-permissions", kwargs={"invitedmember_pk": self.invitation.id}),
{ {
"role": UserPortfolioRoleChoices.ORGANIZATION_ADMIN, "role": UserPortfolioRoleChoices.ORGANIZATION_ADMIN,
}, },
@ -4615,7 +4644,7 @@ class TestPortfolioInvitedMemberEditView(WebTest):
# Test updating invitation permissions # Test updating invitation permissions
response = self.client.post( response = self.client.post(
reverse("invitedmember-permissions", kwargs={"pk": self.admin_invitation.id}), reverse("invitedmember-permissions", kwargs={"invitedmember_pk": self.admin_invitation.id}),
{ {
"role": UserPortfolioRoleChoices.ORGANIZATION_MEMBER, "role": UserPortfolioRoleChoices.ORGANIZATION_MEMBER,
"domain_permissions": UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS, "domain_permissions": UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS,
@ -4661,7 +4690,7 @@ class TestPortfolioInvitedMemberEditView(WebTest):
# Test updating invitation permissions # Test updating invitation permissions
response = self.client.post( response = self.client.post(
reverse("invitedmember-permissions", kwargs={"pk": self.admin_invitation.id}), reverse("invitedmember-permissions", kwargs={"invitedmember_pk": self.admin_invitation.id}),
{ {
"role": UserPortfolioRoleChoices.ORGANIZATION_MEMBER, "role": UserPortfolioRoleChoices.ORGANIZATION_MEMBER,
"domain_permissions": UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS, "domain_permissions": UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS,
@ -4707,7 +4736,7 @@ class TestPortfolioInvitedMemberEditView(WebTest):
# Test updating invitation permissions # Test updating invitation permissions
response = self.client.post( response = self.client.post(
reverse("invitedmember-permissions", kwargs={"pk": self.invitation.id}), reverse("invitedmember-permissions", kwargs={"invitedmember_pk": self.invitation.id}),
{ {
"role": UserPortfolioRoleChoices.ORGANIZATION_MEMBER, "role": UserPortfolioRoleChoices.ORGANIZATION_MEMBER,
"domain_permissions": UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS, "domain_permissions": UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS,
@ -4734,7 +4763,7 @@ class TestPortfolioInvitedMemberEditView(WebTest):
# Test updating invitation permissions # Test updating invitation permissions
response = self.client.post( response = self.client.post(
reverse("invitedmember-permissions", kwargs={"pk": self.admin_invitation.id}), reverse("invitedmember-permissions", kwargs={"invitedmember_pk": self.admin_invitation.id}),
{ {
"role": UserPortfolioRoleChoices.ORGANIZATION_ADMIN, "role": UserPortfolioRoleChoices.ORGANIZATION_ADMIN,
}, },

View file

@ -2920,7 +2920,7 @@ class DomainRequestTests(TestWithUser, WebTest):
self.assertEqual(intro_page.status_code, 200) self.assertEqual(intro_page.status_code, 200)
# This user should also be allowed to edit existing ones # This user should also be allowed to edit existing ones
domain_request = completed_domain_request(user=self.user) domain_request = completed_domain_request(user=self.user, portfolio=portfolio)
edit_page = self.app.get( edit_page = self.app.get(
reverse("edit-domain-request", kwargs={"domain_request_pk": domain_request.pk}) reverse("edit-domain-request", kwargs={"domain_request_pk": domain_request.pk})
).follow() ).follow()
@ -3028,7 +3028,9 @@ class DomainRequestTestDifferentStatuses(TestWithUser, WebTest):
roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN], roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
additional_permissions=[UserPortfolioPermissionChoices.EDIT_REQUESTS], additional_permissions=[UserPortfolioPermissionChoices.EDIT_REQUESTS],
) )
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.SUBMITTED, user=self.user) domain_request = completed_domain_request(
status=DomainRequest.DomainRequestStatus.SUBMITTED, user=self.user, portfolio=portfolio
)
domain_request.save() domain_request.save()
detail_page = self.app.get(f"/domain-request/{domain_request.id}") detail_page = self.app.get(f"/domain-request/{domain_request.id}")
@ -3168,13 +3170,17 @@ class TestDomainRequestWizard(TestWithUser, WebTest):
roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN], roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
additional_permissions=[UserPortfolioPermissionChoices.EDIT_REQUESTS], additional_permissions=[UserPortfolioPermissionChoices.EDIT_REQUESTS],
) )
domain_request.portfolio = portfolio
domain_request.save()
domain_request.refresh_from_db()
# Check portfolio-specific breadcrumb # Check portfolio-specific breadcrumb
portfolio_page = self.app.get(f"/domain-request/{domain_request.id}/edit/").follow() portfolio_page = self.app.get(f"/domain-request/{domain_request.id}/edit/").follow()
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
self.assertContains(portfolio_page, "Domain requests") self.assertContains(portfolio_page, "Domain requests")
domain_request.portfolio = None
domain_request.save()
# Clean up portfolio # Clean up portfolio
permission.delete() permission.delete()
portfolio.delete() portfolio.delete()
@ -3301,15 +3307,6 @@ class TestDomainRequestWizard(TestWithUser, WebTest):
- The user does not see the Domain and Domain requests buttons - The user does not see the Domain and Domain requests buttons
""" """
# This should unlock 4 steps by default.
# Purpose, .gov domain, current websites, and requirements for operating
domain_request = completed_domain_request(
status=DomainRequest.DomainRequestStatus.STARTED,
user=self.user,
)
domain_request.anything_else = None
domain_request.save()
federal_agency = FederalAgency.objects.get(agency="Non-Federal Agency") federal_agency = FederalAgency.objects.get(agency="Non-Federal Agency")
# Add a portfolio # Add a portfolio
portfolio = Portfolio.objects.create( portfolio = Portfolio.objects.create(
@ -3327,6 +3324,14 @@ class TestDomainRequestWizard(TestWithUser, WebTest):
], ],
) )
# This should unlock 4 steps by default.
# Purpose, .gov domain, current websites, and requirements for operating
domain_request = completed_domain_request(
status=DomainRequest.DomainRequestStatus.STARTED, user=self.user, portfolio=portfolio
)
domain_request.anything_else = None
domain_request.save()
response = self.app.get(f"/domain-request/{domain_request.id}/edit/") response = self.app.get(f"/domain-request/{domain_request.id}/edit/")
# django-webtest does not handle cookie-based sessions well because it keeps # django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept # resetting the session key on each new request, thus destroying the concept
@ -3371,6 +3376,8 @@ class TestDomainRequestWizard(TestWithUser, WebTest):
self.fail(f"Expected a redirect, but got a different response: {response}") self.fail(f"Expected a redirect, but got a different response: {response}")
# Data cleanup # Data cleanup
domain_request.portfolio = None
domain_request.save()
user_portfolio_permission.delete() user_portfolio_permission.delete()
portfolio.delete() portfolio.delete()
federal_agency.delete() federal_agency.delete()
@ -3435,7 +3442,9 @@ class TestPortfolioDomainRequestViewonly(TestWithUser, WebTest):
user=self.user, portfolio=portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN] user=self.user, portfolio=portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
) )
dummy_user, _ = User.objects.get_or_create(username="testusername123456") dummy_user, _ = User.objects.get_or_create(username="testusername123456")
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.SUBMITTED, user=dummy_user) domain_request = completed_domain_request(
status=DomainRequest.DomainRequestStatus.SUBMITTED, user=dummy_user, portfolio=portfolio
)
domain_request.save() domain_request.save()
detail_page = self.app.get(f"/domain-request/viewonly/{domain_request.id}") detail_page = self.app.get(f"/domain-request/viewonly/{domain_request.id}")

View file

@ -213,9 +213,12 @@ class PortfolioMembersJson(View):
view_only = not user.has_edit_members_portfolio_permission(portfolio) or not user_can_edit_other_users view_only = not user.has_edit_members_portfolio_permission(portfolio) or not user_can_edit_other_users
is_admin = UserPortfolioRoleChoices.ORGANIZATION_ADMIN in (item.get("roles") or []) is_admin = UserPortfolioRoleChoices.ORGANIZATION_ADMIN in (item.get("roles") or [])
action_url = reverse(item["type"], kwargs={"pk": item["id"]})
item_type = item.get("type", "") item_type = item.get("type", "")
if item_type == "invitedmember":
action_url = reverse(item["type"], kwargs={"invitedmember_pk": item["id"]})
else:
action_url = reverse(item["type"], kwargs={"member_pk": item["id"]})
# Ensure domain_info is properly processed for invites - # Ensure domain_info is properly processed for invites -
# we need to un-concatenate the subquery # we need to un-concatenate the subquery

View file

@ -76,9 +76,10 @@ class PortfolioMemberView(DetailView, View):
model = Portfolio model = Portfolio
context_object_name = "portfolio" context_object_name = "portfolio"
template_name = "portfolio_member.html" template_name = "portfolio_member.html"
pk_url_kwarg = "member_pk"
def get(self, request, pk): def get(self, request, member_pk):
portfolio_permission = get_object_or_404(UserPortfolioPermission, pk=pk) portfolio_permission = get_object_or_404(UserPortfolioPermission, pk=member_pk)
member = portfolio_permission.user member = portfolio_permission.user
# We have to explicitely name these with member_ otherwise we'll have conflicts with context preprocessors # We have to explicitely name these with member_ otherwise we'll have conflicts with context preprocessors
@ -102,8 +103,8 @@ class PortfolioMemberView(DetailView, View):
request, request,
self.template_name, self.template_name,
{ {
"edit_url": reverse("member-permissions", args=[pk]), "edit_url": reverse("member-permissions", args=[member_pk]),
"domains_url": reverse("member-domains", args=[pk]), "domains_url": reverse("member-domains", args=[member_pk]),
"portfolio_permission": portfolio_permission, "portfolio_permission": portfolio_permission,
"member": member, "member": member,
"member_has_view_all_requests_portfolio_permission": member_has_view_all_requests_portfolio_permission, "member_has_view_all_requests_portfolio_permission": member_has_view_all_requests_portfolio_permission,
@ -115,22 +116,23 @@ class PortfolioMemberView(DetailView, View):
) )
@grant_access(HAS_PORTFOLIO_MEMBERS_ANY_PERM) @grant_access(HAS_PORTFOLIO_MEMBERS_EDIT)
class PortfolioMemberDeleteView(View): class PortfolioMemberDeleteView(View):
pk_url_kwarg = "member_pk"
def post(self, request, pk): def post(self, request, member_pk):
""" """
Find and delete the portfolio member using the provided primary key (pk). Find and delete the portfolio member using the provided primary key (pk).
Redirect to a success page after deletion (or any other appropriate page). Redirect to a success page after deletion (or any other appropriate page).
""" """
portfolio_member_permission = get_object_or_404(UserPortfolioPermission, pk=pk) portfolio_member_permission = get_object_or_404(UserPortfolioPermission, pk=member_pk)
member = portfolio_member_permission.user member = portfolio_member_permission.user
portfolio = portfolio_member_permission.portfolio portfolio = portfolio_member_permission.portfolio
# Validate if the member can be removed # Validate if the member can be removed
error_message = self._validate_member_removal(request, member, portfolio) error_message = self._validate_member_removal(request, member, portfolio)
if error_message: if error_message:
return self._handle_error_response(request, error_message, pk) return self._handle_error_response(request, error_message, member_pk)
# Attempt to send notification emails # Attempt to send notification emails
self._send_removal_notifications(request, portfolio_member_permission) self._send_removal_notifications(request, portfolio_member_permission)
@ -161,14 +163,14 @@ class PortfolioMemberDeleteView(View):
) )
return None return None
def _handle_error_response(self, request, error_message, pk): def _handle_error_response(self, request, error_message, member_pk):
""" """
Return an error response (JSON or redirect with messages). Return an error response (JSON or redirect with messages).
""" """
if request.headers.get("X-Requested-With") == "XMLHttpRequest": if request.headers.get("X-Requested-With") == "XMLHttpRequest":
return JsonResponse({"error": error_message}, status=400) return JsonResponse({"error": error_message}, status=400)
messages.error(request, error_message) messages.error(request, error_message)
return redirect(reverse("member", kwargs={"pk": pk})) return redirect(reverse("member", kwargs={"member_pk": member_pk}))
def _send_removal_notifications(self, request, portfolio_member_permission): def _send_removal_notifications(self, request, portfolio_member_permission):
""" """
@ -223,9 +225,10 @@ class PortfolioMemberEditView(DetailView, View):
context_object_name = "portfolio" context_object_name = "portfolio"
template_name = "portfolio_member_permissions.html" template_name = "portfolio_member_permissions.html"
form_class = portfolioForms.PortfolioMemberForm form_class = portfolioForms.PortfolioMemberForm
pk_url_kwarg = "member_pk"
def get(self, request, pk): def get(self, request, member_pk):
portfolio_permission = get_object_or_404(UserPortfolioPermission, pk=pk) portfolio_permission = get_object_or_404(UserPortfolioPermission, pk=member_pk)
user = portfolio_permission.user user = portfolio_permission.user
form = self.form_class(instance=portfolio_permission) form = self.form_class(instance=portfolio_permission)
@ -240,8 +243,8 @@ class PortfolioMemberEditView(DetailView, View):
}, },
) )
def post(self, request, pk): def post(self, request, member_pk):
portfolio_permission = get_object_or_404(UserPortfolioPermission, pk=pk) portfolio_permission = get_object_or_404(UserPortfolioPermission, pk=member_pk)
user = portfolio_permission.user user = portfolio_permission.user
form = self.form_class(request.POST, instance=portfolio_permission) form = self.form_class(request.POST, instance=portfolio_permission)
removing_admin_role_on_self = False removing_admin_role_on_self = False
@ -276,7 +279,7 @@ class PortfolioMemberEditView(DetailView, View):
self._handle_exceptions(e) self._handle_exceptions(e)
form.save() form.save()
messages.success(self.request, "The member access and permission changes have been saved.") messages.success(self.request, "The member access and permission changes have been saved.")
return redirect("member", pk=pk) if not removing_admin_role_on_self else redirect("home") return redirect("member", member_pk=member_pk) if not removing_admin_role_on_self else redirect("home")
return render( return render(
request, request,
@ -304,9 +307,10 @@ class PortfolioMemberEditView(DetailView, View):
class PortfolioMemberDomainsView(View): class PortfolioMemberDomainsView(View):
template_name = "portfolio_member_domains.html" template_name = "portfolio_member_domains.html"
pk_url_kwarg = "member_pk"
def get(self, request, pk): def get(self, request, member_pk):
portfolio_permission = get_object_or_404(UserPortfolioPermission, pk=pk) portfolio_permission = get_object_or_404(UserPortfolioPermission, pk=member_pk)
member = portfolio_permission.user member = portfolio_permission.user
return render( return render(
@ -324,9 +328,10 @@ class PortfolioMemberDomainsEditView(DetailView, View):
model = Portfolio model = Portfolio
context_object_name = "portfolio" context_object_name = "portfolio"
template_name = "portfolio_member_domains_edit.html" template_name = "portfolio_member_domains_edit.html"
pk_url_kwarg = "member_pk"
def get(self, request, pk): def get(self, request, member_pk):
portfolio_permission = get_object_or_404(UserPortfolioPermission, pk=pk) portfolio_permission = get_object_or_404(UserPortfolioPermission, pk=member_pk)
member = portfolio_permission.user member = portfolio_permission.user
return render( return render(
@ -338,33 +343,33 @@ class PortfolioMemberDomainsEditView(DetailView, View):
}, },
) )
def post(self, request, pk): def post(self, request, member_pk):
""" """
Handles adding and removing domains for a portfolio member. Handles adding and removing domains for a portfolio member.
""" """
added_domains = request.POST.get("added_domains") added_domains = request.POST.get("added_domains")
removed_domains = request.POST.get("removed_domains") removed_domains = request.POST.get("removed_domains")
portfolio_permission = get_object_or_404(UserPortfolioPermission, pk=pk) portfolio_permission = get_object_or_404(UserPortfolioPermission, pk=member_pk)
member = portfolio_permission.user member = portfolio_permission.user
portfolio = portfolio_permission.portfolio portfolio = portfolio_permission.portfolio
added_domain_ids = self._parse_domain_ids(added_domains, "added domains") added_domain_ids = self._parse_domain_ids(added_domains, "added domains")
if added_domain_ids is None: if added_domain_ids is None:
return redirect(reverse("member-domains", kwargs={"pk": pk})) return redirect(reverse("member-domains", kwargs={"member_pk": member_pk}))
removed_domain_ids = self._parse_domain_ids(removed_domains, "removed domains") removed_domain_ids = self._parse_domain_ids(removed_domains, "removed domains")
if removed_domain_ids is None: if removed_domain_ids is None:
return redirect(reverse("member-domains", kwargs={"pk": pk})) return redirect(reverse("member-domains", kwargs={"member_pk": member_pk}))
if not (added_domain_ids or removed_domain_ids): if not (added_domain_ids or removed_domain_ids):
messages.success(request, "The domain assignment changes have been saved.") messages.success(request, "The domain assignment changes have been saved.")
return redirect(reverse("member-domains", kwargs={"pk": pk})) return redirect(reverse("member-domains", kwargs={"member_pk": member_pk}))
try: try:
self._process_added_domains(added_domain_ids, member, request.user, portfolio) self._process_added_domains(added_domain_ids, member, request.user, portfolio)
self._process_removed_domains(removed_domain_ids, member) self._process_removed_domains(removed_domain_ids, member)
messages.success(request, "The domain assignment changes have been saved.") messages.success(request, "The domain assignment changes have been saved.")
return redirect(reverse("member-domains", kwargs={"pk": pk})) return redirect(reverse("member-domains", kwargs={"member_pk": member_pk}))
except IntegrityError: except IntegrityError:
messages.error( messages.error(
request, request,
@ -372,7 +377,7 @@ class PortfolioMemberDomainsEditView(DetailView, View):
f"please contact {DefaultUserValues.HELP_EMAIL}.", f"please contact {DefaultUserValues.HELP_EMAIL}.",
) )
logger.error("A database error occurred while saving changes.", exc_info=True) logger.error("A database error occurred while saving changes.", exc_info=True)
return redirect(reverse("member-domains-edit", kwargs={"pk": pk})) return redirect(reverse("member-domains-edit", kwargs={"member_pk": member_pk}))
except Exception as e: except Exception as e:
messages.error( messages.error(
request, request,
@ -380,7 +385,7 @@ class PortfolioMemberDomainsEditView(DetailView, View):
f"please contact {DefaultUserValues.HELP_EMAIL}.", f"please contact {DefaultUserValues.HELP_EMAIL}.",
) )
logger.error(f"An unexpected error occurred: {str(e)}", exc_info=True) logger.error(f"An unexpected error occurred: {str(e)}", exc_info=True)
return redirect(reverse("member-domains-edit", kwargs={"pk": pk})) return redirect(reverse("member-domains-edit", kwargs={"member_pk": member_pk}))
def _parse_domain_ids(self, domain_data, domain_type): def _parse_domain_ids(self, domain_data, domain_type):
""" """
@ -437,9 +442,10 @@ class PortfolioInvitedMemberView(DetailView, View):
context_object_name = "portfolio" context_object_name = "portfolio"
template_name = "portfolio_member.html" template_name = "portfolio_member.html"
# form_class = PortfolioInvitedMemberForm # form_class = PortfolioInvitedMemberForm
pk_url_kwarg = "invitedmember_pk"
def get(self, request, pk): def get(self, request, invitedmember_pk):
portfolio_invitation = get_object_or_404(PortfolioInvitation, pk=pk) portfolio_invitation = get_object_or_404(PortfolioInvitation, pk=invitedmember_pk)
# form = self.form_class(instance=portfolio_invitation) # form = self.form_class(instance=portfolio_invitation)
# We have to explicitely name these with member_ otherwise we'll have conflicts with context preprocessors # We have to explicitely name these with member_ otherwise we'll have conflicts with context preprocessors
@ -463,8 +469,8 @@ class PortfolioInvitedMemberView(DetailView, View):
request, request,
self.template_name, self.template_name,
{ {
"edit_url": reverse("invitedmember-permissions", args=[pk]), "edit_url": reverse("invitedmember-permissions", args=[invitedmember_pk]),
"domains_url": reverse("invitedmember-domains", args=[pk]), "domains_url": reverse("invitedmember-domains", args=[invitedmember_pk]),
"portfolio_invitation": portfolio_invitation, "portfolio_invitation": portfolio_invitation,
"member_has_view_all_requests_portfolio_permission": member_has_view_all_requests_portfolio_permission, "member_has_view_all_requests_portfolio_permission": member_has_view_all_requests_portfolio_permission,
"member_has_edit_request_portfolio_permission": member_has_edit_request_portfolio_permission, "member_has_edit_request_portfolio_permission": member_has_edit_request_portfolio_permission,
@ -475,15 +481,16 @@ class PortfolioInvitedMemberView(DetailView, View):
) )
@grant_access(HAS_PORTFOLIO_MEMBERS_ANY_PERM) @grant_access(HAS_PORTFOLIO_MEMBERS_EDIT)
class PortfolioInvitedMemberDeleteView(View): class PortfolioInvitedMemberDeleteView(View):
pk_url_kwarg = "invitedmember_pk"
def post(self, request, pk): def post(self, request, invitedmember_pk):
""" """
Find and delete the portfolio invited member using the provided primary key (pk). Find and delete the portfolio invited member using the provided primary key (pk).
Redirect to a success page after deletion (or any other appropriate page). Redirect to a success page after deletion (or any other appropriate page).
""" """
portfolio_invitation = get_object_or_404(PortfolioInvitation, pk=pk) portfolio_invitation = get_object_or_404(PortfolioInvitation, pk=invitedmember_pk)
try: try:
# if invitation being removed is an admin # if invitation being removed is an admin
@ -527,9 +534,10 @@ class PortfolioInvitedMemberEditView(DetailView, View):
context_object_name = "portfolio" context_object_name = "portfolio"
template_name = "portfolio_member_permissions.html" template_name = "portfolio_member_permissions.html"
form_class = portfolioForms.PortfolioInvitedMemberForm form_class = portfolioForms.PortfolioInvitedMemberForm
pk_url_kwarg = "invitedmember_pk"
def get(self, request, pk): def get(self, request, invitedmember_pk):
portfolio_invitation = get_object_or_404(PortfolioInvitation, pk=pk) portfolio_invitation = get_object_or_404(PortfolioInvitation, pk=invitedmember_pk)
form = self.form_class(instance=portfolio_invitation) form = self.form_class(instance=portfolio_invitation)
return render( return render(
@ -541,8 +549,8 @@ class PortfolioInvitedMemberEditView(DetailView, View):
}, },
) )
def post(self, request, pk): def post(self, request, invitedmember_pk):
portfolio_invitation = get_object_or_404(PortfolioInvitation, pk=pk) portfolio_invitation = get_object_or_404(PortfolioInvitation, pk=invitedmember_pk)
form = self.form_class(request.POST, instance=portfolio_invitation) form = self.form_class(request.POST, instance=portfolio_invitation)
if form.is_valid(): if form.is_valid():
try: try:
@ -568,7 +576,7 @@ class PortfolioInvitedMemberEditView(DetailView, View):
self._handle_exceptions(e) self._handle_exceptions(e)
form.save() form.save()
messages.success(self.request, "The member access and permission changes have been saved.") messages.success(self.request, "The member access and permission changes have been saved.")
return redirect("invitedmember", pk=pk) return redirect("invitedmember", invitedmember_pk=invitedmember_pk)
return render( return render(
request, request,
@ -596,9 +604,10 @@ class PortfolioInvitedMemberEditView(DetailView, View):
class PortfolioInvitedMemberDomainsView(View): class PortfolioInvitedMemberDomainsView(View):
template_name = "portfolio_member_domains.html" template_name = "portfolio_member_domains.html"
pk_url_kwarg = "invitedmember_pk"
def get(self, request, pk): def get(self, request, invitedmember_pk):
portfolio_invitation = get_object_or_404(PortfolioInvitation, pk=pk) portfolio_invitation = get_object_or_404(PortfolioInvitation, pk=invitedmember_pk)
return render( return render(
request, request,
@ -615,9 +624,10 @@ class PortfolioInvitedMemberDomainsEditView(DetailView, View):
model = Portfolio model = Portfolio
context_object_name = "portfolio" context_object_name = "portfolio"
template_name = "portfolio_member_domains_edit.html" template_name = "portfolio_member_domains_edit.html"
pk_url_kwarg = "invitedmember_pk"
def get(self, request, pk): def get(self, request, invitedmember_pk):
portfolio_invitation = get_object_or_404(PortfolioInvitation, pk=pk) portfolio_invitation = get_object_or_404(PortfolioInvitation, pk=invitedmember_pk)
return render( return render(
request, request,
@ -627,33 +637,33 @@ class PortfolioInvitedMemberDomainsEditView(DetailView, View):
}, },
) )
def post(self, request, pk): def post(self, request, invitedmember_pk):
""" """
Handles adding and removing domains for a portfolio invitee. Handles adding and removing domains for a portfolio invitee.
""" """
added_domains = request.POST.get("added_domains") added_domains = request.POST.get("added_domains")
removed_domains = request.POST.get("removed_domains") removed_domains = request.POST.get("removed_domains")
portfolio_invitation = get_object_or_404(PortfolioInvitation, pk=pk) portfolio_invitation = get_object_or_404(PortfolioInvitation, pk=invitedmember_pk)
email = portfolio_invitation.email email = portfolio_invitation.email
portfolio = portfolio_invitation.portfolio portfolio = portfolio_invitation.portfolio
added_domain_ids = self._parse_domain_ids(added_domains, "added domains") added_domain_ids = self._parse_domain_ids(added_domains, "added domains")
if added_domain_ids is None: if added_domain_ids is None:
return redirect(reverse("invitedmember-domains", kwargs={"pk": pk})) return redirect(reverse("invitedmember-domains", kwargs={"invitedmember_pk": invitedmember_pk}))
removed_domain_ids = self._parse_domain_ids(removed_domains, "removed domains") removed_domain_ids = self._parse_domain_ids(removed_domains, "removed domains")
if removed_domain_ids is None: if removed_domain_ids is None:
return redirect(reverse("invitedmember-domains", kwargs={"pk": pk})) return redirect(reverse("invitedmember-domains", kwargs={"invitedmember_pk": invitedmember_pk}))
if not (added_domain_ids or removed_domain_ids): if not (added_domain_ids or removed_domain_ids):
messages.success(request, "The domain assignment changes have been saved.") messages.success(request, "The domain assignment changes have been saved.")
return redirect(reverse("invitedmember-domains", kwargs={"pk": pk})) return redirect(reverse("invitedmember-domains", kwargs={"invitedmember_pk": invitedmember_pk}))
try: try:
self._process_added_domains(added_domain_ids, email, request.user, portfolio) self._process_added_domains(added_domain_ids, email, request.user, portfolio)
self._process_removed_domains(removed_domain_ids, email) self._process_removed_domains(removed_domain_ids, email)
messages.success(request, "The domain assignment changes have been saved.") messages.success(request, "The domain assignment changes have been saved.")
return redirect(reverse("invitedmember-domains", kwargs={"pk": pk})) return redirect(reverse("invitedmember-domains", kwargs={"invitedmember_pk": invitedmember_pk}))
except IntegrityError: except IntegrityError:
messages.error( messages.error(
request, request,
@ -661,7 +671,7 @@ class PortfolioInvitedMemberDomainsEditView(DetailView, View):
f"please contact {DefaultUserValues.HELP_EMAIL}.", f"please contact {DefaultUserValues.HELP_EMAIL}.",
) )
logger.error("A database error occurred while saving changes.", exc_info=True) logger.error("A database error occurred while saving changes.", exc_info=True)
return redirect(reverse("invitedmember-domains-edit", kwargs={"pk": pk})) return redirect(reverse("invitedmember-domains-edit", kwargs={"invitedmember_pk": invitedmember_pk}))
except Exception as e: except Exception as e:
messages.error( messages.error(
request, request,
@ -669,7 +679,7 @@ class PortfolioInvitedMemberDomainsEditView(DetailView, View):
f"please contact {DefaultUserValues.HELP_EMAIL}.", f"please contact {DefaultUserValues.HELP_EMAIL}.",
) )
logger.error(f"An unexpected error occurred: {str(e)}.", exc_info=True) logger.error(f"An unexpected error occurred: {str(e)}.", exc_info=True)
return redirect(reverse("invitedmember-domains-edit", kwargs={"pk": pk})) return redirect(reverse("invitedmember-domains-edit", kwargs={"invitedmember_pk": invitedmember_pk}))
def _parse_domain_ids(self, domain_data, domain_type): def _parse_domain_ids(self, domain_data, domain_type):
""" """
@ -903,7 +913,7 @@ class PortfolioMembersView(View):
return render(request, "portfolio_members.html") return render(request, "portfolio_members.html")
@grant_access(HAS_PORTFOLIO_MEMBERS_ANY_PERM) @grant_access(HAS_PORTFOLIO_MEMBERS_EDIT)
class PortfolioAddMemberView(DetailView, FormMixin): class PortfolioAddMemberView(DetailView, FormMixin):
template_name = "portfolio_members_add_new.html" template_name = "portfolio_members_add_new.html"