initial implementation

This commit is contained in:
David Kennedy 2024-07-31 21:42:11 -04:00
parent 06a5803bba
commit fe5af50100
No known key found for this signature in database
GPG key ID: 6528A5386E66B96B
13 changed files with 94 additions and 65 deletions

View file

@ -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",

View file

@ -25,7 +25,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
@ -60,19 +59,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/",

View file

@ -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,
} }

View file

@ -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)

View file

@ -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 well need information about your domain name servers.</p> <p> No DNS name servers have been added yet. Before your domain can be used well 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 #}

View file

@ -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 %}>

View file

@ -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">

View file

@ -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

View file

@ -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 *

View file

@ -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"""

View file

@ -1,11 +1,9 @@
from django.shortcuts import get_object_or_404, render from django.shortcuts import render
from registrar.models.portfolio import Portfolio
from registrar.views.utility.permission_views import ( from registrar.views.utility.permission_views import (
PortfolioDomainRequestsPermissionView, PortfolioDomainRequestsPermissionView,
PortfolioDomainsPermissionView, PortfolioDomainsPermissionView,
PortfolioBasePermissionView, PortfolioBasePermissionView,
) )
from waffle.decorators import flag_is_active
from django.views.generic import View from django.views.generic import View
@ -13,46 +11,23 @@ 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, View): class PortfolioOrganizationView(PortfolioBasePermissionView, View):
template_name = "portfolio_organization.html" template_name = "portfolio_organization.html"
def get(self, request, portfolio_id): def get(self, request):
context = {} return render(request, "portfolio_organization.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_organization.html", context)

View file

@ -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 permissions, 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"""

View file

@ -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