portfolio member permissions

This commit is contained in:
David Kennedy 2025-02-12 09:53:55 -05:00
parent e4021b76c1
commit 908e71e3ae
No known key found for this signature in database
GPG key ID: 6528A5386E66B96B
7 changed files with 90 additions and 228 deletions

View file

@ -18,6 +18,8 @@ HAS_PORTFOLIO_DOMAINS_VIEW_ALL = "has_portfolio_domains_view_all"
HAS_PORTFOLIO_DOMAIN_REQUESTS_ANY_PERM = "has_portfolio_domain_requests_any_perm" HAS_PORTFOLIO_DOMAIN_REQUESTS_ANY_PERM = "has_portfolio_domain_requests_any_perm"
HAS_PORTFOLIO_DOMAIN_REQUESTS_VIEW_ALL = "has_portfolio_domain_requests_view_all" HAS_PORTFOLIO_DOMAIN_REQUESTS_VIEW_ALL = "has_portfolio_domain_requests_view_all"
HAS_PORTFOLIO_DOMAIN_REQUESTS_EDIT = "has_portfolio_domain_requests_edit" HAS_PORTFOLIO_DOMAIN_REQUESTS_EDIT = "has_portfolio_domain_requests_edit"
HAS_PORTFOLIO_MEMBERS_ANY_PERM = "has_portfolio_members_any_perm"
HAS_PORTFOLIO_MEMBERS_EDIT = "has_portfolio_members_edit"
def grant_access(*rules): def grant_access(*rules):
@ -142,6 +144,22 @@ def _user_has_permission(user, request, rules, **kwargs):
print(has_permission) print(has_permission)
conditions_met.append(has_permission) conditions_met.append(has_permission)
if not any(conditions_met) and HAS_PORTFOLIO_MEMBERS_ANY_PERM in rules:
portfolio = request.session.get("portfolio")
has_permission = user.is_org_user(request) and (
user.has_view_members_portfolio_permission(portfolio) or
user.has_edit_members_portfolio_permission(portfolio)
)
conditions_met.append(has_permission)
if not any(conditions_met) and HAS_PORTFOLIO_MEMBERS_EDIT in rules:
portfolio = request.session.get("portfolio")
has_permission = (
user.is_org_user(request) and
user.has_edit_members_portfolio_permission(portfolio)
)
conditions_met.append(has_permission)
return any(conditions_met) return any(conditions_met)

View file

@ -4,37 +4,38 @@ from django.http import JsonResponse
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.views import View from django.views import View
from registrar.decorators import HAS_PORTFOLIO_MEMBERS_ANY_PERM, grant_access
from registrar.models import UserDomainRole, Domain, DomainInformation, User from registrar.models import UserDomainRole, Domain, DomainInformation, User
from django.urls import reverse from django.urls import reverse
from django.db.models import Q from django.db.models import Q
from registrar.models.domain_invitation import DomainInvitation from registrar.models.domain_invitation import DomainInvitation
from registrar.views.utility.mixins import PortfolioMemberDomainsPermission
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class PortfolioMemberDomainsJson(PortfolioMemberDomainsPermission, View): @grant_access(HAS_PORTFOLIO_MEMBERS_ANY_PERM)
class PortfolioMemberDomainsJson(View):
def get(self, request): def get(self, request):
"""Given the current request, """Given the current request,
get all domains that are associated with the portfolio, or get all domains that are associated with the portfolio, or
associated with the member/invited member""" associated with the member/invited member"""
domain_ids = self.get_domain_ids_from_request(request) domain_ids = self._get_domain_ids_from_request(request)
objects = Domain.objects.filter(id__in=domain_ids).select_related("domain_info__sub_organization") objects = Domain.objects.filter(id__in=domain_ids).select_related("domain_info__sub_organization")
unfiltered_total = objects.count() unfiltered_total = objects.count()
objects = self.apply_search(objects, request) objects = self._apply_search(objects, request)
objects = self.apply_sorting(objects, request) objects = self._apply_sorting(objects, request)
paginator = Paginator(objects, self.get_page_size(request)) paginator = Paginator(objects, self._get_page_size(request))
page_number = request.GET.get("page") page_number = request.GET.get("page")
page_obj = paginator.get_page(page_number) page_obj = paginator.get_page(page_number)
member_id = request.GET.get("member_id") member_id = request.GET.get("member_id")
domains = [self.serialize_domain(domain, member_id, request.user) for domain in page_obj.object_list] domains = [self._serialize_domain(domain, member_id, request.user) for domain in page_obj.object_list]
return JsonResponse( return JsonResponse(
{ {
@ -48,7 +49,7 @@ class PortfolioMemberDomainsJson(PortfolioMemberDomainsPermission, View):
} }
) )
def get_page_size(self, request): def _get_page_size(self, request):
"""Gets the page size. """Gets the page size.
If member_only, need to return the entire result set every time, so need If member_only, need to return the entire result set every time, so need
@ -65,7 +66,7 @@ class PortfolioMemberDomainsJson(PortfolioMemberDomainsPermission, View):
# later # later
return 1000 return 1000
def get_domain_ids_from_request(self, request): def _get_domain_ids_from_request(self, request):
"""Get domain ids from request. """Get domain ids from request.
request.get.email - email address of invited member request.get.email - email address of invited member
@ -100,13 +101,13 @@ class PortfolioMemberDomainsJson(PortfolioMemberDomainsPermission, View):
logger.warning("Invalid search criteria, returning empty results list") logger.warning("Invalid search criteria, returning empty results list")
return [] return []
def apply_search(self, queryset, request): def _apply_search(self, queryset, request):
search_term = request.GET.get("search_term") search_term = request.GET.get("search_term")
if search_term: if search_term:
queryset = queryset.filter(Q(name__icontains=search_term)) queryset = queryset.filter(Q(name__icontains=search_term))
return queryset return queryset
def apply_sorting(self, queryset, request): def _apply_sorting(self, queryset, request):
# Get the sorting parameters from the request # Get the sorting parameters from the request
sort_by = request.GET.get("sort_by", "name") sort_by = request.GET.get("sort_by", "name")
order = request.GET.get("order", "asc") order = request.GET.get("order", "asc")
@ -141,7 +142,7 @@ class PortfolioMemberDomainsJson(PortfolioMemberDomainsPermission, View):
return queryset return queryset
def serialize_domain(self, domain, member_id, user): def _serialize_domain(self, domain, member_id, user):
suborganization_name = None suborganization_name = None
try: try:
domain_info = domain.domain_info domain_info = domain.domain_info
@ -176,7 +177,7 @@ class PortfolioMemberDomainsJson(PortfolioMemberDomainsPermission, View):
"state": domain.state, "state": domain.state,
"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={"domain_pk": domain.id}),
"action_label": ("View" if view_only else "Manage"), "action_label": ("View" if view_only else "Manage"),
"svg_icon": ("visibility" if view_only else "settings"), "svg_icon": ("visibility" if view_only else "settings"),
"domain_info__sub_organization": suborganization_name, "domain_info__sub_organization": suborganization_name,

View file

@ -6,16 +6,17 @@ from django.contrib.postgres.aggregates import ArrayAgg
from django.urls import reverse from django.urls import reverse
from django.views import View from django.views import View
from registrar.decorators import HAS_PORTFOLIO_MEMBERS_ANY_PERM, grant_access
from registrar.models.domain_invitation import DomainInvitation from registrar.models.domain_invitation import DomainInvitation
from registrar.models.portfolio_invitation import PortfolioInvitation from registrar.models.portfolio_invitation import PortfolioInvitation
from registrar.models.user_portfolio_permission import UserPortfolioPermission from registrar.models.user_portfolio_permission import UserPortfolioPermission
from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices
from registrar.views.utility.mixins import PortfolioMembersPermission
from registrar.models.utility.orm_helper import ArrayRemoveNull from registrar.models.utility.orm_helper import ArrayRemoveNull
from django.contrib.postgres.aggregates import StringAgg from django.contrib.postgres.aggregates import StringAgg
class PortfolioMembersJson(PortfolioMembersPermission, View): @grant_access(HAS_PORTFOLIO_MEMBERS_ANY_PERM)
class PortfolioMembersJson(View):
def get(self, request): def get(self, request):
"""Fetch members (permissions and invitations) for the given portfolio.""" """Fetch members (permissions and invitations) for the given portfolio."""
@ -236,7 +237,7 @@ class PortfolioMembersJson(PortfolioMembersPermission, View):
), ),
# split domain_info array values into ids to form urls, and names # split domain_info array values into ids to form urls, and names
"domain_urls": [ "domain_urls": [
reverse("domain", kwargs={"pk": domain_info.split(":")[0]}) for domain_info in domain_info_list reverse("domain", kwargs={"domain_pk": domain_info.split(":")[0]}) for domain_info in domain_info_list
], ],
"domain_names": [domain_info.split(":")[1] for domain_info in domain_info_list], "domain_names": [domain_info.split(":")[1] for domain_info in domain_info_list],
"is_admin": is_admin, "is_admin": is_admin,

View file

@ -5,20 +5,26 @@ from django.http import Http404, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse from django.urls import reverse
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.views.generic import DetailView
from django.contrib import messages from django.contrib import messages
from registrar.decorators import ( from registrar.decorators import (
HAS_PORTFOLIO_DOMAIN_REQUESTS_ANY_PERM, HAS_PORTFOLIO_DOMAIN_REQUESTS_ANY_PERM,
HAS_PORTFOLIO_DOMAINS_ANY_PERM, HAS_PORTFOLIO_DOMAINS_ANY_PERM,
HAS_PORTFOLIO_MEMBERS_ANY_PERM,
HAS_PORTFOLIO_MEMBERS_EDIT,
IS_PORTFOLIO_MEMBER, IS_PORTFOLIO_MEMBER,
grant_access, grant_access,
) )
from registrar.forms import portfolio as portfolioForms from registrar.forms import portfolio as portfolioForms
from registrar.models import Portfolio, User from registrar.models import (
from registrar.models.domain import Domain Domain,
from registrar.models.domain_invitation import DomainInvitation DomainInvitation,
from registrar.models.portfolio_invitation import PortfolioInvitation Portfolio,
from registrar.models.user_domain_role import UserDomainRole PortfolioInvitation,
from registrar.models.user_portfolio_permission import UserPortfolioPermission User,
UserDomainRole,
UserPortfolioPermission
)
from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices
from registrar.utility.email import EmailSendingError from registrar.utility.email import EmailSendingError
from registrar.utility.email_invitations import ( from registrar.utility.email_invitations import (
@ -29,15 +35,6 @@ from registrar.utility.email_invitations import (
) )
from registrar.utility.errors import MissingEmailError from registrar.utility.errors import MissingEmailError
from registrar.utility.enums import DefaultUserValues from registrar.utility.enums import DefaultUserValues
from registrar.views.utility.mixins import PortfolioMemberPermission
from registrar.views.utility.permission_views import (
PortfolioBasePermissionView,
PortfolioMemberDomainsPermissionView,
PortfolioMemberDomainsEditPermissionView,
PortfolioMemberEditPermissionView,
PortfolioMemberPermissionView,
PortfolioMembersPermissionView,
)
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
from django.db import IntegrityError from django.db import IntegrityError
@ -71,8 +68,10 @@ class PortfolioDomainRequestsView(View):
return render(request, "portfolio_requests.html") return render(request, "portfolio_requests.html")
class PortfolioMemberView(PortfolioMemberPermissionView, View): @grant_access(HAS_PORTFOLIO_MEMBERS_ANY_PERM)
class PortfolioMemberView(DetailView, View):
model = Portfolio
context_object_name = "portfolio"
template_name = "portfolio_member.html" template_name = "portfolio_member.html"
def get(self, request, pk): def get(self, request, pk):
@ -113,7 +112,8 @@ class PortfolioMemberView(PortfolioMemberPermissionView, View):
) )
class PortfolioMemberDeleteView(PortfolioMemberPermission, View): @grant_access(HAS_PORTFOLIO_MEMBERS_ANY_PERM)
class PortfolioMemberDeleteView(View):
def post(self, request, pk): def post(self, request, pk):
""" """
@ -190,8 +190,10 @@ class PortfolioMemberDeleteView(PortfolioMemberPermission, View):
messages.warning(self.request, "Could not send email notification to existing organization admins.") messages.warning(self.request, "Could not send email notification to existing organization admins.")
class PortfolioMemberEditView(PortfolioMemberEditPermissionView, View): @grant_access(HAS_PORTFOLIO_MEMBERS_EDIT)
class PortfolioMemberEditView(DetailView, View):
model = 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
@ -265,7 +267,8 @@ class PortfolioMemberEditView(PortfolioMemberEditPermissionView, View):
messages.warning(self.request, "Could not send email notification to existing organization admins.") messages.warning(self.request, "Could not send email notification to existing organization admins.")
class PortfolioMemberDomainsView(PortfolioMemberDomainsPermissionView, View): @grant_access(HAS_PORTFOLIO_MEMBERS_ANY_PERM)
class PortfolioMemberDomainsView(View):
template_name = "portfolio_member_domains.html" template_name = "portfolio_member_domains.html"
@ -283,8 +286,10 @@ class PortfolioMemberDomainsView(PortfolioMemberDomainsPermissionView, View):
) )
class PortfolioMemberDomainsEditView(PortfolioMemberDomainsEditPermissionView, View): @grant_access(HAS_PORTFOLIO_MEMBERS_EDIT)
class PortfolioMemberDomainsEditView(DetailView, View):
model = Portfolio
context_object_name = "portfolio"
template_name = "portfolio_member_domains_edit.html" template_name = "portfolio_member_domains_edit.html"
def get(self, request, pk): def get(self, request, pk):
@ -393,8 +398,10 @@ class PortfolioMemberDomainsEditView(PortfolioMemberDomainsEditPermissionView, V
UserDomainRole.objects.filter(domain_id__in=removed_domain_ids, user=member).delete() UserDomainRole.objects.filter(domain_id__in=removed_domain_ids, user=member).delete()
class PortfolioInvitedMemberView(PortfolioMemberPermissionView, View): @grant_access(HAS_PORTFOLIO_MEMBERS_ANY_PERM)
class PortfolioInvitedMemberView(DetailView, View):
model = Portfolio
context_object_name = "portfolio"
template_name = "portfolio_member.html" template_name = "portfolio_member.html"
# form_class = PortfolioInvitedMemberForm # form_class = PortfolioInvitedMemberForm
@ -435,7 +442,8 @@ class PortfolioInvitedMemberView(PortfolioMemberPermissionView, View):
) )
class PortfolioInvitedMemberDeleteView(PortfolioMemberPermission, View): @grant_access(HAS_PORTFOLIO_MEMBERS_ANY_PERM)
class PortfolioInvitedMemberDeleteView(View):
def post(self, request, pk): def post(self, request, pk):
""" """
@ -478,8 +486,10 @@ class PortfolioInvitedMemberDeleteView(PortfolioMemberPermission, View):
messages.warning(self.request, "Could not send email notification to existing organization admins.") messages.warning(self.request, "Could not send email notification to existing organization admins.")
class PortfolioInvitedMemberEditView(PortfolioMemberEditPermissionView, View): @grant_access(HAS_PORTFOLIO_MEMBERS_EDIT)
class PortfolioInvitedMemberEditView(DetailView, View):
model = 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
@ -547,7 +557,8 @@ class PortfolioInvitedMemberEditView(PortfolioMemberEditPermissionView, View):
messages.warning(self.request, "Could not send email notification to existing organization admins.") messages.warning(self.request, "Could not send email notification to existing organization admins.")
class PortfolioInvitedMemberDomainsView(PortfolioMemberDomainsPermissionView, View): @grant_access(HAS_PORTFOLIO_MEMBERS_ANY_PERM)
class PortfolioInvitedMemberDomainsView(View):
template_name = "portfolio_member_domains.html" template_name = "portfolio_member_domains.html"
@ -562,9 +573,11 @@ class PortfolioInvitedMemberDomainsView(PortfolioMemberDomainsPermissionView, Vi
}, },
) )
@grant_access(HAS_PORTFOLIO_MEMBERS_EDIT)
class PortfolioInvitedMemberDomainsEditView(DetailView, View):
class PortfolioInvitedMemberDomainsEditView(PortfolioMemberDomainsEditPermissionView, View): model = Portfolio
context_object_name = "portfolio"
template_name = "portfolio_member_domains_edit.html" template_name = "portfolio_member_domains_edit.html"
def get(self, request, pk): def get(self, request, pk):
@ -749,7 +762,8 @@ class PortfolioNoDomainRequestsView(View):
return context return context
class PortfolioOrganizationView(PortfolioBasePermissionView, FormMixin): @grant_access(IS_PORTFOLIO_MEMBER)
class PortfolioOrganizationView(DetailView, FormMixin):
""" """
View to handle displaying and updating the portfolio's organization details. View to handle displaying and updating the portfolio's organization details.
""" """
@ -811,7 +825,8 @@ class PortfolioOrganizationView(PortfolioBasePermissionView, FormMixin):
return reverse("organization") return reverse("organization")
class PortfolioSeniorOfficialView(PortfolioBasePermissionView, FormMixin): @grant_access(IS_PORTFOLIO_MEMBER)
class PortfolioSeniorOfficialView(DetailView, FormMixin):
""" """
View to handle displaying and updating the portfolio's senior official details. View to handle displaying and updating the portfolio's senior official details.
For now, this view is readonly. For now, this view is readonly.
@ -842,7 +857,8 @@ class PortfolioSeniorOfficialView(PortfolioBasePermissionView, FormMixin):
return self.render_to_response(self.get_context_data(form=form)) return self.render_to_response(self.get_context_data(form=form))
class PortfolioMembersView(PortfolioMembersPermissionView, View): @grant_access(HAS_PORTFOLIO_MEMBERS_ANY_PERM)
class PortfolioMembersView(View):
template_name = "portfolio_members.html" template_name = "portfolio_members.html"
@ -851,10 +867,13 @@ class PortfolioMembersView(PortfolioMembersPermissionView, View):
return render(request, "portfolio_members.html") return render(request, "portfolio_members.html")
class PortfolioAddMemberView(PortfolioMembersPermissionView, FormMixin): @grant_access(HAS_PORTFOLIO_MEMBERS_ANY_PERM)
class PortfolioAddMemberView(DetailView, FormMixin):
template_name = "portfolio_members_add_new.html" template_name = "portfolio_members_add_new.html"
form_class = portfolioForms.PortfolioNewMemberForm form_class = portfolioForms.PortfolioNewMemberForm
model = Portfolio
context_object_name = "portfolio"
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
"""Handle GET requests to display the form.""" """Handle GET requests to display the form."""

View file

@ -1,7 +1,4 @@
from .steps_helper import StepsHelper from .steps_helper import StepsHelper
from .always_404 import always_404 from .always_404 import always_404
from .permission_views import (
PortfolioMembersPermission,
)
from .api_views import get_senior_official_from_federal_agency_json from .api_views import get_senior_official_from_federal_agency_json

View file

@ -202,110 +202,3 @@ class UserProfilePermission(PermissionsLoginMixin):
return False return False
return True return True
class PortfolioBasePermission(PermissionsLoginMixin):
"""Permission mixin that redirects to portfolio pages if user
has access, otherwise 403"""
def has_permission(self):
"""Check if this user has access to this portfolio.
The user is in self.request.user and the portfolio can be looked
up from the portfolio's primary key in self.kwargs["pk"]
"""
if not self.request.user.is_authenticated:
return False
return self.request.user.is_org_user(self.request)
class PortfolioMembersPermission(PortfolioBasePermission):
"""Permission mixin that allows access to portfolio members pages if user
has access, otherwise 403"""
def has_permission(self):
"""Check if this user has access to members for this portfolio.
The user is in self.request.user and the portfolio can be looked
up from the portfolio's primary key in self.kwargs["pk"]"""
portfolio = self.request.session.get("portfolio")
if not self.request.user.has_view_members_portfolio_permission(
portfolio
) and not self.request.user.has_edit_members_portfolio_permission(portfolio):
return False
return super().has_permission()
class PortfolioMemberPermission(PortfolioBasePermission):
"""Permission mixin that allows access to portfolio member or invited member pages if user
has access, otherwise 403"""
def has_permission(self):
"""Check if this user has access to members or invited members for this portfolio.
The user is in self.request.user and the portfolio can be looked
up from the portfolio's primary key in self.kwargs["pk"]"""
portfolio = self.request.session.get("portfolio")
if not self.request.user.has_view_members_portfolio_permission(
portfolio
) and not self.request.user.has_edit_members_portfolio_permission(portfolio):
return False
return super().has_permission()
class PortfolioMemberEditPermission(PortfolioBasePermission):
"""Permission mixin that allows access to portfolio member or invited member pages if user
has access to edit, otherwise 403"""
def has_permission(self):
"""Check if this user has access to members or invited members for this portfolio.
The user is in self.request.user and the portfolio can be looked
up from the portfolio's primary key in self.kwargs["pk"]"""
portfolio = self.request.session.get("portfolio")
if not self.request.user.has_edit_members_portfolio_permission(portfolio):
return False
return super().has_permission()
class PortfolioMemberDomainsPermission(PortfolioBasePermission):
"""Permission mixin that allows access to portfolio member or invited member domains pages if user
has access to edit, otherwise 403"""
def has_permission(self):
"""Check if this user has access to member or invited member domains for this portfolio.
The user is in self.request.user and the portfolio can be looked
up from the portfolio's primary key in self.kwargs["pk"]"""
portfolio = self.request.session.get("portfolio")
if not self.request.user.has_view_members_portfolio_permission(
portfolio
) and not self.request.user.has_edit_members_portfolio_permission(portfolio):
return False
return super().has_permission()
class PortfolioMemberDomainsEditPermission(PortfolioBasePermission):
"""Permission mixin that allows access to portfolio member or invited member domains edit pages if user
has access to edit, otherwise 403"""
def has_permission(self):
"""Check if this user has access to member or invited member domains for this portfolio.
The user is in self.request.user and the portfolio can be looked
up from the portfolio's primary key in self.kwargs["pk"]"""
portfolio = self.request.session.get("portfolio")
if not self.request.user.has_edit_members_portfolio_permission(portfolio):
return False
return super().has_permission()

View file

@ -7,13 +7,7 @@ from registrar.models import Portfolio
from registrar.models.user import User from registrar.models.user import User
from .mixins import ( from .mixins import (
PortfolioMemberDomainsPermission,
PortfolioMemberDomainsEditPermission,
PortfolioMemberEditPermission,
UserProfilePermission, UserProfilePermission,
PortfolioBasePermission,
PortfolioMembersPermission,
PortfolioMemberPermission,
) )
import logging import logging
@ -37,64 +31,3 @@ class UserProfilePermissionView(UserProfilePermission, DetailView, abc.ABC):
@abc.abstractmethod @abc.abstractmethod
def template_name(self): def template_name(self):
raise NotImplementedError raise NotImplementedError
class PortfolioBasePermissionView(PortfolioBasePermission, DetailView, abc.ABC):
"""Abstract base view for portfolio views that enforces permissions.
This abstract view cannot be instantiated. Actual views must specify
`template_name`.
"""
# DetailView property for what model this is viewing
model = Portfolio
# variable name in template context for the model object
context_object_name = "portfolio"
# Abstract property enforces NotImplementedError on an attribute.
@property
@abc.abstractmethod
def template_name(self):
raise NotImplementedError
class PortfolioMembersPermissionView(PortfolioMembersPermission, PortfolioBasePermissionView, abc.ABC):
"""Abstract base view for portfolio members views that enforces permissions.
This abstract view cannot be instantiated. Actual views must specify
`template_name`.
"""
class PortfolioMemberPermissionView(PortfolioMemberPermission, PortfolioBasePermissionView, abc.ABC):
"""Abstract base view for portfolio member views that enforces permissions.
This abstract view cannot be instantiated. Actual views must specify
`template_name`.
"""
class PortfolioMemberEditPermissionView(PortfolioMemberEditPermission, PortfolioBasePermissionView, abc.ABC):
"""Abstract base view for portfolio member edit views that enforces permissions.
This abstract view cannot be instantiated. Actual views must specify
`template_name`.
"""
class PortfolioMemberDomainsPermissionView(PortfolioMemberDomainsPermission, PortfolioBasePermissionView, abc.ABC):
"""Abstract base view for portfolio member domains views that enforces permissions.
This abstract view cannot be instantiated. Actual views must specify
`template_name`.
"""
class PortfolioMemberDomainsEditPermissionView(
PortfolioMemberDomainsEditPermission, PortfolioBasePermissionView, abc.ABC
):
"""Abstract base view for portfolio member domains edit views that enforces permissions.
This abstract view cannot be instantiated. Actual views must specify
`template_name`.
"""