mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-21 18:25:58 +02:00
Merge pull request #2527 from cisagov/dk/2379-portfolio-domain-readonly-permissions
Issue #2379: portfolio domain readonly view
This commit is contained in:
commit
eb8c7a16c1
18 changed files with 144 additions and 95 deletions
|
@ -241,7 +241,6 @@ TEMPLATES = [
|
||||||
"registrar.context_processors.is_demo_site",
|
"registrar.context_processors.is_demo_site",
|
||||||
"registrar.context_processors.is_production",
|
"registrar.context_processors.is_production",
|
||||||
"registrar.context_processors.org_user_status",
|
"registrar.context_processors.org_user_status",
|
||||||
"registrar.context_processors.add_portfolio_to_context",
|
|
||||||
"registrar.context_processors.add_path_to_context",
|
"registrar.context_processors.add_path_to_context",
|
||||||
"registrar.context_processors.add_has_profile_feature_flag_to_context",
|
"registrar.context_processors.add_has_profile_feature_flag_to_context",
|
||||||
"registrar.context_processors.portfolio_permissions",
|
"registrar.context_processors.portfolio_permissions",
|
||||||
|
|
|
@ -26,7 +26,6 @@ from registrar.views.domain_request import Step
|
||||||
from registrar.views.domain_requests_json import get_domain_requests_json
|
from registrar.views.domain_requests_json import get_domain_requests_json
|
||||||
from registrar.views.domains_json import get_domains_json
|
from registrar.views.domains_json import get_domains_json
|
||||||
from registrar.views.utility import always_404
|
from registrar.views.utility import always_404
|
||||||
from registrar.views.portfolios import PortfolioDomainsView, PortfolioDomainRequestsView, PortfolioOrganizationView
|
|
||||||
from api.views import available, get_current_federal, get_current_full
|
from api.views import available, get_current_federal, get_current_full
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,19 +60,19 @@ for step, view in [
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", views.index, name="home"),
|
path("", views.index, name="home"),
|
||||||
path(
|
path(
|
||||||
"portfolio/<int:portfolio_id>/domains/",
|
"domains/",
|
||||||
PortfolioDomainsView.as_view(),
|
views.PortfolioDomainsView.as_view(),
|
||||||
name="portfolio-domains",
|
name="domains",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"portfolio/<int:portfolio_id>/domain_requests/",
|
"requests/",
|
||||||
PortfolioDomainRequestsView.as_view(),
|
views.PortfolioDomainRequestsView.as_view(),
|
||||||
name="portfolio-domain-requests",
|
name="domain-requests",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"portfolio/<int:portfolio_id>/organization/",
|
"organization/",
|
||||||
PortfolioOrganizationView.as_view(),
|
views.PortfolioOrganizationView.as_view(),
|
||||||
name="portfolio-organization",
|
name="organization",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"admin/logout/",
|
"admin/logout/",
|
||||||
|
|
|
@ -50,10 +50,6 @@ def org_user_status(request):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def add_portfolio_to_context(request):
|
|
||||||
return {"portfolio": getattr(request, "portfolio", None)}
|
|
||||||
|
|
||||||
|
|
||||||
def add_path_to_context(request):
|
def add_path_to_context(request):
|
||||||
return {"path": getattr(request, "path", None)}
|
return {"path": getattr(request, "path", None)}
|
||||||
|
|
||||||
|
@ -70,11 +66,15 @@ def portfolio_permissions(request):
|
||||||
"has_base_portfolio_permission": False,
|
"has_base_portfolio_permission": False,
|
||||||
"has_domains_portfolio_permission": False,
|
"has_domains_portfolio_permission": False,
|
||||||
"has_domain_requests_portfolio_permission": False,
|
"has_domain_requests_portfolio_permission": False,
|
||||||
|
"portfolio": None,
|
||||||
|
"has_organization_feature_flag": False,
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
"has_base_portfolio_permission": request.user.has_base_portfolio_permission(),
|
"has_base_portfolio_permission": request.user.has_base_portfolio_permission(),
|
||||||
"has_domains_portfolio_permission": request.user.has_domains_portfolio_permission(),
|
"has_domains_portfolio_permission": request.user.has_domains_portfolio_permission(),
|
||||||
"has_domain_requests_portfolio_permission": request.user.has_domain_requests_portfolio_permission(),
|
"has_domain_requests_portfolio_permission": request.user.has_domain_requests_portfolio_permission(),
|
||||||
|
"portfolio": request.user.portfolio,
|
||||||
|
"has_organization_feature_flag": flag_is_active(request, "organization_feature"),
|
||||||
}
|
}
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# Handles cases where request.user might not exist
|
# Handles cases where request.user might not exist
|
||||||
|
@ -82,4 +82,6 @@ def portfolio_permissions(request):
|
||||||
"has_base_portfolio_permission": False,
|
"has_base_portfolio_permission": False,
|
||||||
"has_domains_portfolio_permission": False,
|
"has_domains_portfolio_permission": False,
|
||||||
"has_domain_requests_portfolio_permission": False,
|
"has_domain_requests_portfolio_permission": False,
|
||||||
|
"portfolio": None,
|
||||||
|
"has_organization_feature_flag": False,
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,10 +149,10 @@ class CheckPortfolioMiddleware:
|
||||||
request.portfolio = portfolio
|
request.portfolio = portfolio
|
||||||
|
|
||||||
if request.user.has_domains_portfolio_permission():
|
if request.user.has_domains_portfolio_permission():
|
||||||
portfolio_redirect = reverse("portfolio-domains", kwargs={"portfolio_id": portfolio.id})
|
portfolio_redirect = reverse("domains")
|
||||||
else:
|
else:
|
||||||
# View organization is the lowest access
|
# View organization is the lowest access
|
||||||
portfolio_redirect = reverse("portfolio-organization", kwargs={"portfolio_id": portfolio.id})
|
portfolio_redirect = reverse("organization")
|
||||||
|
|
||||||
return HttpResponseRedirect(portfolio_redirect)
|
return HttpResponseRedirect(portfolio_redirect)
|
||||||
|
|
||||||
|
|
|
@ -40,39 +40,50 @@
|
||||||
|
|
||||||
{% include "includes/domain_dates.html" %}
|
{% include "includes/domain_dates.html" %}
|
||||||
|
|
||||||
|
{% if is_portfolio_user and not is_domain_manager %}
|
||||||
|
<div class="usa-alert usa-alert--info usa-alert--slim">
|
||||||
|
<div class="usa-alert__body">
|
||||||
|
<p class="usa-alert__text ">
|
||||||
|
To manage information for this domain, you must add yourself as a domain manager.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
{% url 'domain-dns-nameservers' pk=domain.id as url %}
|
{% url 'domain-dns-nameservers' pk=domain.id as url %}
|
||||||
{% if domain.nameservers|length > 0 %}
|
{% if domain.nameservers|length > 0 %}
|
||||||
{% include "includes/summary_item.html" with title='DNS name servers' domains='true' value=domain.nameservers list='true' edit_link=url editable=domain.is_editable %}
|
{% include "includes/summary_item.html" with title='DNS name servers' domains='true' value=domain.nameservers list='true' edit_link=url editable=is_editable %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if domain.is_editable %}
|
{% if is_editable %}
|
||||||
<h2 class="margin-top-3"> DNS name servers </h2>
|
<h2 class="margin-top-3"> DNS name servers </h2>
|
||||||
<p> No DNS name servers have been added yet. Before your domain can be used we’ll need information about your domain name servers.</p>
|
<p> No DNS name servers have been added yet. Before your domain can be used we’ll need information about your domain name servers.</p>
|
||||||
<a class="usa-button margin-bottom-1" href="{{url}}"> Add DNS name servers </a>
|
<a class="usa-button margin-bottom-1" href="{{url}}"> Add DNS name servers </a>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% include "includes/summary_item.html" with title='DNS name servers' domains='true' value='' edit_link=url editable=domain.is_editable %}
|
{% include "includes/summary_item.html" with title='DNS name servers' domains='true' value='' edit_link=url editable=is_editable %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% url 'domain-org-name-address' pk=domain.id as url %}
|
{% url 'domain-org-name-address' pk=domain.id as url %}
|
||||||
{% include "includes/summary_item.html" with title='Organization name and mailing address' value=domain.domain_info address='true' edit_link=url editable=domain.is_editable %}
|
{% include "includes/summary_item.html" with title='Organization name and mailing address' value=domain.domain_info address='true' edit_link=url editable=is_editable %}
|
||||||
|
|
||||||
{% url 'domain-senior-official' pk=domain.id as url %}
|
{% url 'domain-senior-official' pk=domain.id as url %}
|
||||||
{% include "includes/summary_item.html" with title='Senior official' value=domain.domain_info.senior_official contact='true' edit_link=url editable=domain.is_editable %}
|
{% include "includes/summary_item.html" with title='Senior official' value=domain.domain_info.senior_official contact='true' edit_link=url editable=is_editable %}
|
||||||
|
|
||||||
{# Conditionally display profile #}
|
{# Conditionally display profile #}
|
||||||
{% if not has_profile_feature_flag %}
|
{% if not has_profile_feature_flag %}
|
||||||
{% url 'domain-your-contact-information' pk=domain.id as url %}
|
{% url 'domain-your-contact-information' pk=domain.id as url %}
|
||||||
{% include "includes/summary_item.html" with title='Your contact information' value=request.user contact='true' edit_link=url editable=domain.is_editable %}
|
{% include "includes/summary_item.html" with title='Your contact information' value=request.user contact='true' edit_link=url editable=is_editable %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% url 'domain-security-email' pk=domain.id as url %}
|
{% url 'domain-security-email' pk=domain.id as url %}
|
||||||
{% if security_email is not None and security_email not in hidden_security_emails%}
|
{% if security_email is not None and security_email not in hidden_security_emails%}
|
||||||
{% include "includes/summary_item.html" with title='Security email' value=security_email edit_link=url editable=domain.is_editable %}
|
{% include "includes/summary_item.html" with title='Security email' value=security_email edit_link=url editable=is_editable %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% include "includes/summary_item.html" with title='Security email' value='None provided' edit_link=url editable=domain.is_editable %}
|
{% include "includes/summary_item.html" with title='Security email' value='None provided' edit_link=url editable=is_editable %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% url 'domain-users' pk=domain.id as url %}
|
{% url 'domain-users' pk=domain.id as url %}
|
||||||
{% include "includes/summary_item.html" with title='Domain managers' users='true' list=True value=domain.permissions.all edit_link=url editable=domain.is_editable %}
|
{% include "includes/summary_item.html" with title='Domain managers' users='true' list=True value=domain.permissions.all edit_link=url editable=is_editable %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {# domain_content #}
|
{% endblock %} {# domain_content #}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{% if domain.is_editable %}
|
{% if is_editable %}
|
||||||
<li class="usa-sidenav__item">
|
<li class="usa-sidenav__item">
|
||||||
{% url 'domain-dns' pk=domain.id as url %}
|
{% url 'domain-dns' pk=domain.id as url %}
|
||||||
<a href="{{ url }}" {% if request.path|startswith:url %}class="usa-current"{% endif %}>
|
<a href="{{ url }}" {% if request.path|startswith:url %}class="usa-current"{% endif %}>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
{% load custom_filters %}
|
||||||
|
|
||||||
<header class="usa-header usa-header--extended">
|
<header class="usa-header usa-header--extended">
|
||||||
<div class="usa-navbar">
|
<div class="usa-navbar">
|
||||||
|
@ -14,8 +15,8 @@
|
||||||
<ul class="usa-nav__primary usa-accordion">
|
<ul class="usa-nav__primary usa-accordion">
|
||||||
{% if has_domains_portfolio_permission %}
|
{% if has_domains_portfolio_permission %}
|
||||||
<li class="usa-nav__primary-item">
|
<li class="usa-nav__primary-item">
|
||||||
{% url 'portfolio-domains' portfolio.id as url %}
|
{% url 'domains' as url %}
|
||||||
<a href="{{ url }}" class="usa-nav-link{% if request.path == url %} usa-current{% endif %}">
|
<a href="{{ url }}" class="usa-nav-link{% if 'domain'|in_path:request.path %} usa-current{% endif %}">
|
||||||
Domains
|
Domains
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -27,8 +28,8 @@
|
||||||
</li>
|
</li>
|
||||||
{% if has_domain_requests_portfolio_permission %}
|
{% if has_domain_requests_portfolio_permission %}
|
||||||
<li class="usa-nav__primary-item">
|
<li class="usa-nav__primary-item">
|
||||||
{% url 'portfolio-domain-requests' portfolio.id as url %}
|
{% url 'domain-requests' as url %}
|
||||||
<a href="{{ url }}" class="usa-nav-link{% if request.path == url %} usa-current{% endif %}">
|
<a href="{{ url }}" class="usa-nav-link{% if 'request'|in_path:request.path %} usa-current{% endif %}">
|
||||||
Domain requests
|
Domain requests
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -39,7 +40,7 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="usa-nav__primary-item">
|
<li class="usa-nav__primary-item">
|
||||||
{% url 'portfolio-organization' portfolio.id as url %}
|
{% url 'organization' as url %}
|
||||||
<!-- Move the padding from the a to the span so that the descenders do not get cut off -->
|
<!-- Move the padding from the a to the span so that the descenders do not get cut off -->
|
||||||
<a href="{{ url }}" class="usa-nav-link padding-y-0">
|
<a href="{{ url }}" class="usa-nav-link padding-y-0">
|
||||||
<span class="ellipsis ellipsis--23 ellipsis--desktop-50 padding-y-1 desktop:padding-y-2">
|
<span class="ellipsis ellipsis--23 ellipsis--desktop-50 padding-y-1 desktop:padding-y-2">
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<nav aria-label="Domain sections">
|
<nav aria-label="Domain sections">
|
||||||
<ul class="usa-sidenav">
|
<ul class="usa-sidenav">
|
||||||
<li class="usa-sidenav__item">
|
<li class="usa-sidenav__item">
|
||||||
{% url 'portfolio-organization' portfolio_id=portfolio.id as url %}
|
{% url 'organization' as url %}
|
||||||
<a href="{{ url }}"
|
<a href="{{ url }}"
|
||||||
{% if request.path == url %}class="usa-current"{% endif %}
|
{% if request.path == url %}class="usa-current"{% endif %}
|
||||||
>
|
>
|
||||||
|
|
|
@ -145,3 +145,8 @@ def format_phone(value):
|
||||||
phone_number = PhoneNumber.from_string(value)
|
phone_number = PhoneNumber.from_string(value)
|
||||||
return phone_number.as_national
|
return phone_number.as_national
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def in_path(url, path):
|
||||||
|
return url in path
|
||||||
|
|
|
@ -6,6 +6,7 @@ from django.urls import reverse
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
from api.tests.common import less_console_noise_decorator
|
from api.tests.common import less_console_noise_decorator
|
||||||
|
from registrar.models.portfolio import Portfolio
|
||||||
from .common import MockEppLib, MockSESClient, create_user # type: ignore
|
from .common import MockEppLib, MockSESClient, create_user # type: ignore
|
||||||
from django_webtest import WebTest # type: ignore
|
from django_webtest import WebTest # type: ignore
|
||||||
import boto3_mocking # type: ignore
|
import boto3_mocking # type: ignore
|
||||||
|
@ -138,6 +139,7 @@ class TestWithDomainPermissions(TestWithUser):
|
||||||
Host.objects.all().delete()
|
Host.objects.all().delete()
|
||||||
Domain.objects.all().delete()
|
Domain.objects.all().delete()
|
||||||
UserDomainRole.objects.all().delete()
|
UserDomainRole.objects.all().delete()
|
||||||
|
Portfolio.objects.all().delete()
|
||||||
except ValueError: # pass if already deleted
|
except ValueError: # pass if already deleted
|
||||||
pass
|
pass
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
|
@ -310,6 +312,33 @@ class TestDomainDetail(TestDomainOverview):
|
||||||
self.assertContains(detail_page, "noinformation.gov")
|
self.assertContains(detail_page, "noinformation.gov")
|
||||||
self.assertContains(detail_page, "Domain missing domain information")
|
self.assertContains(detail_page, "Domain missing domain information")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_domain_readonly_on_detail_page(self):
|
||||||
|
"""Test that a domain, which is part of a portfolio, but for which the user is not a domain manager,
|
||||||
|
properly displays read only"""
|
||||||
|
|
||||||
|
portfolio, _ = Portfolio.objects.get_or_create(organization_name="Test org", creator=self.user)
|
||||||
|
# need to create a different user than self.user because the user needs permission assignments
|
||||||
|
user = get_user_model().objects.create(
|
||||||
|
first_name="Test",
|
||||||
|
last_name="User",
|
||||||
|
email="bogus@example.gov",
|
||||||
|
phone="8003111234",
|
||||||
|
title="test title",
|
||||||
|
portfolio=portfolio,
|
||||||
|
portfolio_roles=[User.UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
|
||||||
|
)
|
||||||
|
domain, _ = Domain.objects.get_or_create(name="bogusdomain.gov")
|
||||||
|
DomainInformation.objects.get_or_create(creator=user, domain=domain, portfolio=portfolio)
|
||||||
|
self.client.force_login(user)
|
||||||
|
detail_page = self.client.get(f"/domain/{domain.id}")
|
||||||
|
# Check that alert message displays properly
|
||||||
|
self.assertContains(
|
||||||
|
detail_page, "To manage information for this domain, you must add yourself as a domain manager."
|
||||||
|
)
|
||||||
|
# Check that user does not have option to Edit domain
|
||||||
|
self.assertNotContains(detail_page, "Edit")
|
||||||
|
|
||||||
|
|
||||||
class TestDomainManagers(TestDomainOverview):
|
class TestDomainManagers(TestDomainOverview):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
|
|
@ -181,7 +181,8 @@ class GetDomainsJsonTest(TestWithUser, WebTest):
|
||||||
# Check svg_icon
|
# Check svg_icon
|
||||||
svg_icon_expected = (
|
svg_icon_expected = (
|
||||||
"visibility"
|
"visibility"
|
||||||
if expected_domains[i].state
|
if not user_domain_role_exists
|
||||||
|
or expected_domains[i].state
|
||||||
in [
|
in [
|
||||||
Domain.State.DELETED,
|
Domain.State.DELETED,
|
||||||
Domain.State.ON_HOLD,
|
Domain.State.ON_HOLD,
|
||||||
|
|
|
@ -111,9 +111,7 @@ class TestPortfolio(WebTest):
|
||||||
with override_flag("organization_feature", active=True):
|
with override_flag("organization_feature", active=True):
|
||||||
# This will redirect the user to the portfolio page.
|
# This will redirect the user to the portfolio page.
|
||||||
# Follow implicity checks if our redirect is working.
|
# Follow implicity checks if our redirect is working.
|
||||||
response = self.app.get(
|
response = self.app.get(reverse("domains"), status=403)
|
||||||
reverse("portfolio-domains", kwargs={"portfolio_id": self.portfolio.pk}), status=403
|
|
||||||
)
|
|
||||||
# Assert the response is a 403 Forbidden
|
# Assert the response is a 403 Forbidden
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
@ -127,9 +125,7 @@ class TestPortfolio(WebTest):
|
||||||
with override_flag("organization_feature", active=True):
|
with override_flag("organization_feature", active=True):
|
||||||
# This will redirect the user to the portfolio page.
|
# This will redirect the user to the portfolio page.
|
||||||
# Follow implicity checks if our redirect is working.
|
# Follow implicity checks if our redirect is working.
|
||||||
response = self.app.get(
|
response = self.app.get(reverse("domain-requests"), status=403)
|
||||||
reverse("portfolio-domain-requests", kwargs={"portfolio_id": self.portfolio.pk}), status=403
|
|
||||||
)
|
|
||||||
# Assert the response is a 403 Forbidden
|
# Assert the response is a 403 Forbidden
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
@ -143,9 +139,7 @@ class TestPortfolio(WebTest):
|
||||||
with override_flag("organization_feature", active=True):
|
with override_flag("organization_feature", active=True):
|
||||||
# This will redirect the user to the portfolio page.
|
# This will redirect the user to the portfolio page.
|
||||||
# Follow implicity checks if our redirect is working.
|
# Follow implicity checks if our redirect is working.
|
||||||
response = self.app.get(
|
response = self.app.get(reverse("organization"), status=403)
|
||||||
reverse("portfolio-organization", kwargs={"portfolio_id": self.portfolio.pk}), status=403
|
|
||||||
)
|
|
||||||
# Assert the response is a 403 Forbidden
|
# Assert the response is a 403 Forbidden
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
@ -169,12 +163,8 @@ class TestPortfolio(WebTest):
|
||||||
self.assertContains(portfolio_page, self.portfolio.organization_name)
|
self.assertContains(portfolio_page, self.portfolio.organization_name)
|
||||||
self.assertNotContains(portfolio_page, "<h1>Organization</h1>")
|
self.assertNotContains(portfolio_page, "<h1>Organization</h1>")
|
||||||
self.assertContains(portfolio_page, '<h1 id="domains-header">Domains</h1>')
|
self.assertContains(portfolio_page, '<h1 id="domains-header">Domains</h1>')
|
||||||
self.assertContains(
|
self.assertContains(portfolio_page, reverse("domains"))
|
||||||
portfolio_page, reverse("portfolio-domains", kwargs={"portfolio_id": self.portfolio.pk})
|
self.assertContains(portfolio_page, reverse("domain-requests"))
|
||||||
)
|
|
||||||
self.assertContains(
|
|
||||||
portfolio_page, reverse("portfolio-domain-requests", kwargs={"portfolio_id": self.portfolio.pk})
|
|
||||||
)
|
|
||||||
|
|
||||||
# reducing portfolio permissions to just VIEW_PORTFOLIO, which should remove domains
|
# reducing portfolio permissions to just VIEW_PORTFOLIO, which should remove domains
|
||||||
# and domain requests from nav
|
# and domain requests from nav
|
||||||
|
@ -187,12 +177,8 @@ class TestPortfolio(WebTest):
|
||||||
self.assertContains(portfolio_page, self.portfolio.organization_name)
|
self.assertContains(portfolio_page, self.portfolio.organization_name)
|
||||||
self.assertContains(portfolio_page, "<h1>Organization</h1>")
|
self.assertContains(portfolio_page, "<h1>Organization</h1>")
|
||||||
self.assertNotContains(portfolio_page, '<h1 id="domains-header">Domains</h1>')
|
self.assertNotContains(portfolio_page, '<h1 id="domains-header">Domains</h1>')
|
||||||
self.assertNotContains(
|
self.assertNotContains(portfolio_page, reverse("domains"))
|
||||||
portfolio_page, reverse("portfolio-domains", kwargs={"portfolio_id": self.portfolio.pk})
|
self.assertNotContains(portfolio_page, reverse("domain-requests"))
|
||||||
)
|
|
||||||
self.assertNotContains(
|
|
||||||
portfolio_page, reverse("portfolio-domain-requests", kwargs={"portfolio_id": self.portfolio.pk})
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestPortfolioOrganization(TestPortfolio):
|
class TestPortfolioOrganization(TestPortfolio):
|
||||||
|
@ -209,7 +195,7 @@ class TestPortfolioOrganization(TestPortfolio):
|
||||||
self.user.save()
|
self.user.save()
|
||||||
self.user.refresh_from_db()
|
self.user.refresh_from_db()
|
||||||
|
|
||||||
page = self.app.get(reverse("portfolio-organization", kwargs={"portfolio_id": self.portfolio.pk}))
|
page = self.app.get(reverse("organization"))
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
page, "The name of your federal agency will be publicly listed as the domain registrant."
|
page, "The name of your federal agency will be publicly listed as the domain registrant."
|
||||||
)
|
)
|
||||||
|
@ -228,7 +214,7 @@ class TestPortfolioOrganization(TestPortfolio):
|
||||||
|
|
||||||
self.portfolio.organization_name = "Hotel California"
|
self.portfolio.organization_name = "Hotel California"
|
||||||
self.portfolio.save()
|
self.portfolio.save()
|
||||||
page = self.app.get(reverse("portfolio-organization", kwargs={"portfolio_id": self.portfolio.pk}))
|
page = self.app.get(reverse("organization"))
|
||||||
# Once in the sidenav, once in the main nav, once in the form
|
# Once in the sidenav, once in the main nav, once in the form
|
||||||
self.assertContains(page, "Hotel California", count=3)
|
self.assertContains(page, "Hotel California", count=3)
|
||||||
|
|
||||||
|
@ -246,9 +232,7 @@ class TestPortfolioOrganization(TestPortfolio):
|
||||||
|
|
||||||
self.portfolio.address_line1 = "1600 Penn Ave"
|
self.portfolio.address_line1 = "1600 Penn Ave"
|
||||||
self.portfolio.save()
|
self.portfolio.save()
|
||||||
portfolio_org_name_page = self.app.get(
|
portfolio_org_name_page = self.app.get(reverse("organization"))
|
||||||
reverse("portfolio-organization", kwargs={"portfolio_id": self.portfolio.pk})
|
|
||||||
)
|
|
||||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
portfolio_org_name_page.form["address_line1"] = "6 Downing st"
|
portfolio_org_name_page.form["address_line1"] = "6 Downing st"
|
||||||
|
|
|
@ -17,3 +17,4 @@ from .domain import (
|
||||||
from .user_profile import UserProfileView, FinishProfileSetupView
|
from .user_profile import UserProfileView, FinishProfileSetupView
|
||||||
from .health import *
|
from .health import *
|
||||||
from .index import *
|
from .index import *
|
||||||
|
from .portfolios import *
|
||||||
|
|
|
@ -170,6 +170,17 @@ class DomainView(DomainBaseView):
|
||||||
context["security_email"] = security_email
|
context["security_email"] = security_email
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
def can_access_domain_via_portfolio(self, pk):
|
||||||
|
"""Most views should not allow permission to portfolio users.
|
||||||
|
If particular views allow permissions, they will need to override
|
||||||
|
this function."""
|
||||||
|
if self.request.user.has_domains_portfolio_permission():
|
||||||
|
if Domain.objects.filter(id=pk).exists():
|
||||||
|
domain = Domain.objects.get(id=pk)
|
||||||
|
if domain.domain_info.portfolio == self.request.user.portfolio:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def in_editable_state(self, pk):
|
def in_editable_state(self, pk):
|
||||||
"""Override in_editable_state from DomainPermission
|
"""Override in_editable_state from DomainPermission
|
||||||
Allow detail page to be viewable"""
|
Allow detail page to be viewable"""
|
||||||
|
|
|
@ -124,7 +124,7 @@ def serialize_domain(domain, user):
|
||||||
|
|
||||||
# Check if there is a UserDomainRole for this domain and user
|
# Check if there is a UserDomainRole for this domain and user
|
||||||
user_domain_role_exists = UserDomainRole.objects.filter(domain_id=domain.id, user=user).exists()
|
user_domain_role_exists = UserDomainRole.objects.filter(domain_id=domain.id, user=user).exists()
|
||||||
|
view_only = not user_domain_role_exists or domain.state in [Domain.State.DELETED, Domain.State.ON_HOLD]
|
||||||
return {
|
return {
|
||||||
"id": domain.id,
|
"id": domain.id,
|
||||||
"name": domain.name,
|
"name": domain.name,
|
||||||
|
@ -133,11 +133,7 @@ def serialize_domain(domain, user):
|
||||||
"state_display": domain.state_display(),
|
"state_display": domain.state_display(),
|
||||||
"get_state_help_text": domain.get_state_help_text(),
|
"get_state_help_text": domain.get_state_help_text(),
|
||||||
"action_url": reverse("domain", kwargs={"pk": domain.id}),
|
"action_url": reverse("domain", kwargs={"pk": domain.id}),
|
||||||
"action_label": (
|
"action_label": ("View" if view_only else "Manage"),
|
||||||
"View"
|
"svg_icon": ("visibility" if view_only else "settings"),
|
||||||
if not user_domain_role_exists or domain.state in [Domain.State.DELETED, Domain.State.ON_HOLD]
|
|
||||||
else "Manage"
|
|
||||||
),
|
|
||||||
"svg_icon": ("visibility" if domain.state in [Domain.State.DELETED, Domain.State.ON_HOLD] else "settings"),
|
|
||||||
"suborganization": suborganization_name,
|
"suborganization": suborganization_name,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.http import Http404
|
||||||
|
from django.shortcuts import render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from registrar.forms.portfolio import PortfolioOrgAddressForm
|
from registrar.forms.portfolio import PortfolioOrgAddressForm
|
||||||
|
@ -9,7 +10,6 @@ from registrar.views.utility.permission_views import (
|
||||||
PortfolioDomainsPermissionView,
|
PortfolioDomainsPermissionView,
|
||||||
PortfolioBasePermissionView,
|
PortfolioBasePermissionView,
|
||||||
)
|
)
|
||||||
from waffle.decorators import flag_is_active
|
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
from django.views.generic.edit import FormMixin
|
from django.views.generic.edit import FormMixin
|
||||||
|
|
||||||
|
@ -21,33 +21,18 @@ class PortfolioDomainsView(PortfolioDomainsPermissionView, View):
|
||||||
|
|
||||||
template_name = "portfolio_domains.html"
|
template_name = "portfolio_domains.html"
|
||||||
|
|
||||||
def get(self, request, portfolio_id):
|
def get(self, request):
|
||||||
context = {}
|
return render(request, "portfolio_domains.html")
|
||||||
|
|
||||||
if self.request.user.is_authenticated:
|
|
||||||
context["has_profile_feature_flag"] = flag_is_active(request, "profile_feature")
|
|
||||||
context["has_organization_feature_flag"] = flag_is_active(request, "organization_feature")
|
|
||||||
portfolio = get_object_or_404(Portfolio, id=portfolio_id)
|
|
||||||
context["portfolio"] = portfolio
|
|
||||||
|
|
||||||
return render(request, "portfolio_domains.html", context)
|
|
||||||
|
|
||||||
|
|
||||||
class PortfolioDomainRequestsView(PortfolioDomainRequestsPermissionView, View):
|
class PortfolioDomainRequestsView(PortfolioDomainRequestsPermissionView, View):
|
||||||
|
|
||||||
template_name = "portfolio_requests.html"
|
template_name = "portfolio_requests.html"
|
||||||
|
|
||||||
def get(self, request, portfolio_id):
|
def get(self, request):
|
||||||
context = {}
|
|
||||||
|
|
||||||
if self.request.user.is_authenticated:
|
if self.request.user.is_authenticated:
|
||||||
context["has_profile_feature_flag"] = flag_is_active(request, "profile_feature")
|
|
||||||
context["has_organization_feature_flag"] = flag_is_active(request, "organization_feature")
|
|
||||||
portfolio = get_object_or_404(Portfolio, id=portfolio_id)
|
|
||||||
context["portfolio"] = portfolio
|
|
||||||
request.session["new_request"] = True
|
request.session["new_request"] = True
|
||||||
|
return render(request, "portfolio_requests.html")
|
||||||
return render(request, "portfolio_requests.html", context)
|
|
||||||
|
|
||||||
|
|
||||||
class PortfolioOrganizationView(PortfolioBasePermissionView, FormMixin):
|
class PortfolioOrganizationView(PortfolioBasePermissionView, FormMixin):
|
||||||
|
@ -63,14 +48,14 @@ class PortfolioOrganizationView(PortfolioBasePermissionView, FormMixin):
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
"""Add additional context data to the template."""
|
"""Add additional context data to the template."""
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
# no need to add portfolio to request context here
|
|
||||||
context["has_profile_feature_flag"] = flag_is_active(self.request, "profile_feature")
|
|
||||||
context["has_organization_feature_flag"] = flag_is_active(self.request, "organization_feature")
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_object(self, queryset=None):
|
def get_object(self, queryset=None):
|
||||||
"""Get the portfolio object based on the URL parameter."""
|
"""Get the portfolio object based on the request user."""
|
||||||
return get_object_or_404(Portfolio, id=self.kwargs.get("portfolio_id"))
|
portfolio = self.request.user.portfolio
|
||||||
|
if portfolio is None:
|
||||||
|
raise Http404("No organization found for this user")
|
||||||
|
return portfolio
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
"""Include the instance in the form kwargs."""
|
"""Include the instance in the form kwargs."""
|
||||||
|
@ -107,4 +92,4 @@ class PortfolioOrganizationView(PortfolioBasePermissionView, FormMixin):
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
"""Redirect to the overview page for the portfolio."""
|
"""Redirect to the overview page for the portfolio."""
|
||||||
return reverse("portfolio-organization", kwargs={"portfolio_id": self.object.pk})
|
return reverse("organization")
|
||||||
|
|
|
@ -184,11 +184,17 @@ class DomainPermission(PermissionsLoginMixin):
|
||||||
|
|
||||||
# user needs to have a role on the domain
|
# user needs to have a role on the domain
|
||||||
if not UserDomainRole.objects.filter(user=self.request.user, domain__id=pk).exists():
|
if not UserDomainRole.objects.filter(user=self.request.user, domain__id=pk).exists():
|
||||||
return False
|
return self.can_access_domain_via_portfolio(pk)
|
||||||
|
|
||||||
# if we need to check more about the nature of role, do it here.
|
# if we need to check more about the nature of role, do it here.
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def can_access_domain_via_portfolio(self, pk):
|
||||||
|
"""Most views should not allow permission to portfolio users.
|
||||||
|
If particular views allow access to the domain pages, they will need to override
|
||||||
|
this function."""
|
||||||
|
return False
|
||||||
|
|
||||||
def in_editable_state(self, pk):
|
def in_editable_state(self, pk):
|
||||||
"""Is the domain in an editable state"""
|
"""Is the domain in an editable state"""
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,9 @@ class DomainPermissionView(DomainPermission, DetailView, abc.ABC):
|
||||||
context["is_analyst_or_superuser"] = user.has_perm("registrar.analyst_access_permission") or user.has_perm(
|
context["is_analyst_or_superuser"] = user.has_perm("registrar.analyst_access_permission") or user.has_perm(
|
||||||
"registrar.full_access_permission"
|
"registrar.full_access_permission"
|
||||||
)
|
)
|
||||||
|
context["is_domain_manager"] = UserDomainRole.objects.filter(user=user, domain=self.object).exists()
|
||||||
|
context["is_portfolio_user"] = self.can_access_domain_via_portfolio(self.object.pk)
|
||||||
|
context["is_editable"] = self.is_editable()
|
||||||
# Stored in a variable for the linter
|
# Stored in a variable for the linter
|
||||||
action = "analyst_action"
|
action = "analyst_action"
|
||||||
action_location = "analyst_action_location"
|
action_location = "analyst_action_location"
|
||||||
|
@ -54,6 +57,22 @@ class DomainPermissionView(DomainPermission, DetailView, abc.ABC):
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
def is_editable(self):
|
||||||
|
"""Returns whether domain is editable in the context of the view"""
|
||||||
|
logger.info("checking if is_editable")
|
||||||
|
domain_editable = self.object.is_editable()
|
||||||
|
if not domain_editable:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# if user is domain manager or analyst or admin, return True
|
||||||
|
if (
|
||||||
|
self.can_access_other_user_domains(self.object.id)
|
||||||
|
or UserDomainRole.objects.filter(user=self.request.user, domain=self.object).exists()
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
# Abstract property enforces NotImplementedError on an attribute.
|
# Abstract property enforces NotImplementedError on an attribute.
|
||||||
@property
|
@property
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue