mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-11 03:59:41 +02:00
Merge pull request #3617 from cisagov/dk/3601-omb-analyst
#3601: OMB analyst [MIGRATIONS]
This commit is contained in:
commit
0d8881f70c
24 changed files with 1875 additions and 251 deletions
|
@ -75,6 +75,19 @@ from django.utils.translation import gettext_lazy as _
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ImportExportRegistrarModelAdmin(ImportExportModelAdmin):
|
||||||
|
|
||||||
|
def has_import_permission(self, request):
|
||||||
|
return request.user.has_perm("registrar.analyst_access_permission") or request.user.has_perm(
|
||||||
|
"registrar.full_access_permission"
|
||||||
|
)
|
||||||
|
|
||||||
|
def has_export_permission(self, request):
|
||||||
|
return request.user.has_perm("registrar.analyst_access_permission") or request.user.has_perm(
|
||||||
|
"registrar.full_access_permission"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FsmModelResource(resources.ModelResource):
|
class FsmModelResource(resources.ModelResource):
|
||||||
"""ModelResource is extended to support importing of tables which
|
"""ModelResource is extended to support importing of tables which
|
||||||
have FSMFields. ModelResource is extended with the following changes
|
have FSMFields. ModelResource is extended with the following changes
|
||||||
|
@ -465,7 +478,7 @@ class DomainRequestAdminForm(forms.ModelForm):
|
||||||
# only set the available transitions if the user is not restricted
|
# only set the available transitions if the user is not restricted
|
||||||
# from editing the domain request; otherwise, the form will be
|
# from editing the domain request; otherwise, the form will be
|
||||||
# readonly and the status field will not have a widget
|
# readonly and the status field will not have a widget
|
||||||
if not domain_request.creator.is_restricted():
|
if not domain_request.creator.is_restricted() and "status" in self.fields:
|
||||||
self.fields["status"].widget.choices = available_transitions
|
self.fields["status"].widget.choices = available_transitions
|
||||||
|
|
||||||
def get_custom_field_transitions(self, instance, field):
|
def get_custom_field_transitions(self, instance, field):
|
||||||
|
@ -919,7 +932,7 @@ class ListHeaderAdmin(AuditedAdmin, OrderableFieldsMixin):
|
||||||
return filters
|
return filters
|
||||||
|
|
||||||
|
|
||||||
class MyUserAdmin(BaseUserAdmin, ImportExportModelAdmin):
|
class MyUserAdmin(BaseUserAdmin, ImportExportRegistrarModelAdmin):
|
||||||
"""Custom user admin class to use our inlines."""
|
"""Custom user admin class to use our inlines."""
|
||||||
|
|
||||||
resource_classes = [UserResource]
|
resource_classes = [UserResource]
|
||||||
|
@ -1224,7 +1237,7 @@ class HostResource(resources.ModelResource):
|
||||||
model = models.Host
|
model = models.Host
|
||||||
|
|
||||||
|
|
||||||
class MyHostAdmin(AuditedAdmin, ImportExportModelAdmin):
|
class MyHostAdmin(AuditedAdmin, ImportExportRegistrarModelAdmin):
|
||||||
"""Custom host admin class to use our inlines."""
|
"""Custom host admin class to use our inlines."""
|
||||||
|
|
||||||
resource_classes = [HostResource]
|
resource_classes = [HostResource]
|
||||||
|
@ -1242,7 +1255,7 @@ class HostIpResource(resources.ModelResource):
|
||||||
model = models.HostIP
|
model = models.HostIP
|
||||||
|
|
||||||
|
|
||||||
class HostIpAdmin(AuditedAdmin, ImportExportModelAdmin):
|
class HostIpAdmin(AuditedAdmin, ImportExportRegistrarModelAdmin):
|
||||||
"""Custom host ip admin class"""
|
"""Custom host ip admin class"""
|
||||||
|
|
||||||
resource_classes = [HostIpResource]
|
resource_classes = [HostIpResource]
|
||||||
|
@ -1257,7 +1270,7 @@ class ContactResource(resources.ModelResource):
|
||||||
model = models.Contact
|
model = models.Contact
|
||||||
|
|
||||||
|
|
||||||
class ContactAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
class ContactAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||||
"""Custom contact admin class to add search."""
|
"""Custom contact admin class to add search."""
|
||||||
|
|
||||||
resource_classes = [ContactResource]
|
resource_classes = [ContactResource]
|
||||||
|
@ -1391,6 +1404,59 @@ class SeniorOfficialAdmin(ListHeaderAdmin):
|
||||||
# in autocomplete_fields for Senior Official
|
# in autocomplete_fields for Senior Official
|
||||||
ordering = ["first_name", "last_name"]
|
ordering = ["first_name", "last_name"]
|
||||||
|
|
||||||
|
readonly_fields = []
|
||||||
|
|
||||||
|
# Even though this is empty, I will leave it as a stub for easy changes in the future
|
||||||
|
# rather than strip it out of our logic.
|
||||||
|
analyst_readonly_fields = [] # type: ignore
|
||||||
|
|
||||||
|
omb_analyst_readonly_fields = [
|
||||||
|
"first_name",
|
||||||
|
"last_name",
|
||||||
|
"title",
|
||||||
|
"phone",
|
||||||
|
"email",
|
||||||
|
"federal_agency",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_readonly_fields(self, request, obj=None):
|
||||||
|
"""Set the read-only state on form elements.
|
||||||
|
We have conditions that determine which fields are read-only:
|
||||||
|
admin user permissions and analyst (cisa or omb) status, so
|
||||||
|
we'll use the baseline readonly_fields and extend it as needed.
|
||||||
|
"""
|
||||||
|
readonly_fields = list(self.readonly_fields)
|
||||||
|
|
||||||
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
|
return readonly_fields
|
||||||
|
# Return restrictive Read-only fields for OMB analysts
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
readonly_fields.extend([field for field in self.omb_analyst_readonly_fields])
|
||||||
|
return readonly_fields
|
||||||
|
# Return restrictive Read-only fields for analysts and
|
||||||
|
# users who might not belong to groups
|
||||||
|
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
||||||
|
return readonly_fields
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
"""Restrict queryset based on user permissions."""
|
||||||
|
qs = super().get_queryset(request)
|
||||||
|
|
||||||
|
# Check if user is in OMB analysts group
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
return qs.filter(federal_agency__federal_type=BranchChoices.EXECUTIVE)
|
||||||
|
|
||||||
|
return qs # Return full queryset if the user doesn't have the restriction
|
||||||
|
|
||||||
|
def has_view_permission(self, request, obj=None):
|
||||||
|
"""Restrict view permissions based on group membership and model attributes."""
|
||||||
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
|
return True
|
||||||
|
if obj:
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
return obj.federal_agency and obj.federal_agency.federal_type == BranchChoices.EXECUTIVE
|
||||||
|
return super().has_view_permission(request, obj)
|
||||||
|
|
||||||
|
|
||||||
class WebsiteResource(resources.ModelResource):
|
class WebsiteResource(resources.ModelResource):
|
||||||
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
||||||
|
@ -1400,7 +1466,7 @@ class WebsiteResource(resources.ModelResource):
|
||||||
model = models.Website
|
model = models.Website
|
||||||
|
|
||||||
|
|
||||||
class WebsiteAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
class WebsiteAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||||
"""Custom website admin class."""
|
"""Custom website admin class."""
|
||||||
|
|
||||||
resource_classes = [WebsiteResource]
|
resource_classes = [WebsiteResource]
|
||||||
|
@ -1501,7 +1567,7 @@ class UserPortfolioPermissionAdmin(ListHeaderAdmin):
|
||||||
obj.delete() # Calls the overridden delete method on each instance
|
obj.delete() # Calls the overridden delete method on each instance
|
||||||
|
|
||||||
|
|
||||||
class UserDomainRoleAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
class UserDomainRoleAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||||
"""Custom user domain role admin class."""
|
"""Custom user domain role admin class."""
|
||||||
|
|
||||||
resource_classes = [UserDomainRoleResource]
|
resource_classes = [UserDomainRoleResource]
|
||||||
|
@ -1684,6 +1750,63 @@ class DomainInvitationAdmin(BaseInvitationAdmin):
|
||||||
# Override for the delete confirmation page on the domain table (bulk delete action)
|
# Override for the delete confirmation page on the domain table (bulk delete action)
|
||||||
delete_selected_confirmation_template = "django/admin/domain_invitation_delete_selected_confirmation.html"
|
delete_selected_confirmation_template = "django/admin/domain_invitation_delete_selected_confirmation.html"
|
||||||
|
|
||||||
|
def get_annotated_queryset(self, queryset):
|
||||||
|
return queryset.annotate(
|
||||||
|
converted_generic_org_type=Case(
|
||||||
|
# When portfolio is present, use its value instead
|
||||||
|
When(
|
||||||
|
domain__domain_info__portfolio__isnull=False,
|
||||||
|
then=F("domain__domain_info__portfolio__organization_type"),
|
||||||
|
),
|
||||||
|
# Otherwise, return the natively assigned value
|
||||||
|
default=F("domain__domain_info__generic_org_type"),
|
||||||
|
),
|
||||||
|
converted_federal_type=Case(
|
||||||
|
# When portfolio is present, use its value instead
|
||||||
|
When(
|
||||||
|
Q(domain__domain_info__portfolio__isnull=False)
|
||||||
|
& Q(domain__domain_info__portfolio__federal_agency__isnull=False),
|
||||||
|
then=F("domain__domain_info__portfolio__federal_agency__federal_type"),
|
||||||
|
),
|
||||||
|
# Otherwise, return the federal agency's federal_type
|
||||||
|
default=F("domain__domain_info__federal_agency__federal_type"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
"""Restrict queryset based on user permissions."""
|
||||||
|
qs = super().get_queryset(request)
|
||||||
|
|
||||||
|
# Check if user is in OMB analysts group
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
annotated_qs = self.get_annotated_queryset(qs)
|
||||||
|
return annotated_qs.filter(
|
||||||
|
converted_generic_org_type=DomainRequest.OrganizationChoices.FEDERAL,
|
||||||
|
converted_federal_type=BranchChoices.EXECUTIVE,
|
||||||
|
)
|
||||||
|
|
||||||
|
return qs # Return full queryset if the user doesn't have the restriction
|
||||||
|
|
||||||
|
def has_view_permission(self, request, obj=None):
|
||||||
|
"""Restrict view permissions based on group membership and model attributes."""
|
||||||
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
|
return True
|
||||||
|
if obj:
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
return (
|
||||||
|
obj.domain.domain_info.converted_generic_org_type == DomainRequest.OrganizationChoices.FEDERAL
|
||||||
|
and obj.domain.domain_info.converted_federal_type == BranchChoices.EXECUTIVE
|
||||||
|
)
|
||||||
|
return super().has_view_permission(request, obj)
|
||||||
|
|
||||||
|
# Select domain invitations to change -> Domain invitations
|
||||||
|
def changelist_view(self, request, extra_context=None):
|
||||||
|
if extra_context is None:
|
||||||
|
extra_context = {}
|
||||||
|
extra_context["tabtitle"] = "Domain invitations"
|
||||||
|
# Get the filtered values
|
||||||
|
return super().changelist_view(request, extra_context=extra_context)
|
||||||
|
|
||||||
def change_view(self, request, object_id, form_url="", extra_context=None):
|
def change_view(self, request, object_id, form_url="", extra_context=None):
|
||||||
"""Override the change_view to add the invitation obj for the change_form_object_tools template"""
|
"""Override the change_view to add the invitation obj for the change_form_object_tools template"""
|
||||||
|
|
||||||
|
@ -1905,7 +2028,7 @@ class DomainInformationResource(resources.ModelResource):
|
||||||
model = models.DomainInformation
|
model = models.DomainInformation
|
||||||
|
|
||||||
|
|
||||||
class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
class DomainInformationAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||||
"""Customize domain information admin class."""
|
"""Customize domain information admin class."""
|
||||||
|
|
||||||
class GenericOrgFilter(admin.SimpleListFilter):
|
class GenericOrgFilter(admin.SimpleListFilter):
|
||||||
|
@ -2182,6 +2305,47 @@ class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
"is_policy_acknowledged",
|
"is_policy_acknowledged",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Read only that we'll leverage for OMB Analysts
|
||||||
|
omb_analyst_readonly_fields = [
|
||||||
|
"federal_agency",
|
||||||
|
"creator",
|
||||||
|
"about_your_organization",
|
||||||
|
"anything_else",
|
||||||
|
"cisa_representative_first_name",
|
||||||
|
"cisa_representative_last_name",
|
||||||
|
"cisa_representative_email",
|
||||||
|
"domain_request",
|
||||||
|
"notes",
|
||||||
|
"senior_official",
|
||||||
|
"organization_type",
|
||||||
|
"organization_name",
|
||||||
|
"state_territory",
|
||||||
|
"address_line1",
|
||||||
|
"address_line2",
|
||||||
|
"city",
|
||||||
|
"zipcode",
|
||||||
|
"urbanization",
|
||||||
|
"portfolio_organization_type",
|
||||||
|
"portfolio_federal_type",
|
||||||
|
"portfolio_organization_name",
|
||||||
|
"portfolio_federal_agency",
|
||||||
|
"portfolio_state_territory",
|
||||||
|
"portfolio_address_line1",
|
||||||
|
"portfolio_address_line2",
|
||||||
|
"portfolio_city",
|
||||||
|
"portfolio_zipcode",
|
||||||
|
"portfolio_urbanization",
|
||||||
|
"organization_type",
|
||||||
|
"federal_type",
|
||||||
|
"federal_agency",
|
||||||
|
"tribe_name",
|
||||||
|
"federally_recognized_tribe",
|
||||||
|
"state_recognized_tribe",
|
||||||
|
"about_your_organization",
|
||||||
|
"portfolio",
|
||||||
|
"sub_organization",
|
||||||
|
]
|
||||||
|
|
||||||
# For each filter_horizontal, init in admin js initFilterHorizontalWidget
|
# For each filter_horizontal, init in admin js initFilterHorizontalWidget
|
||||||
# to activate the edit/delete/view buttons
|
# to activate the edit/delete/view buttons
|
||||||
filter_horizontal = ("other_contacts",)
|
filter_horizontal = ("other_contacts",)
|
||||||
|
@ -2210,6 +2374,10 @@ class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
|
|
||||||
if request.user.has_perm("registrar.full_access_permission"):
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
return readonly_fields
|
return readonly_fields
|
||||||
|
# Return restrictive Read-only fields for OMB analysts
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
readonly_fields.extend([field for field in self.omb_analyst_readonly_fields])
|
||||||
|
return readonly_fields
|
||||||
# Return restrictive Read-only fields for analysts and
|
# Return restrictive Read-only fields for analysts and
|
||||||
# users who might not belong to groups
|
# users who might not belong to groups
|
||||||
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
||||||
|
@ -2226,6 +2394,38 @@ class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
use_sort = db_field.name != "senior_official"
|
use_sort = db_field.name != "senior_official"
|
||||||
return super().formfield_for_foreignkey(db_field, request, use_admin_sort_fields=use_sort, **kwargs)
|
return super().formfield_for_foreignkey(db_field, request, use_admin_sort_fields=use_sort, **kwargs)
|
||||||
|
|
||||||
|
def get_annotated_queryset(self, queryset):
|
||||||
|
return queryset.annotate(
|
||||||
|
conv_generic_org_type=Case(
|
||||||
|
# When portfolio is present, use its value instead
|
||||||
|
When(portfolio__isnull=False, then=F("portfolio__organization_type")),
|
||||||
|
# Otherwise, return the natively assigned value
|
||||||
|
default=F("generic_org_type"),
|
||||||
|
),
|
||||||
|
conv_federal_type=Case(
|
||||||
|
# When portfolio is present, use its value instead
|
||||||
|
When(
|
||||||
|
Q(portfolio__isnull=False) & Q(portfolio__federal_agency__isnull=False),
|
||||||
|
then=F("portfolio__federal_agency__federal_type"),
|
||||||
|
),
|
||||||
|
# Otherwise, return the federal_type from federal agency
|
||||||
|
default=F("federal_agency__federal_type"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
"""Custom get_queryset to filter by portfolio if portfolio is in the
|
||||||
|
request params."""
|
||||||
|
qs = super().get_queryset(request)
|
||||||
|
# Check if user is in OMB analysts group
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
annotated_qs = self.get_annotated_queryset(qs)
|
||||||
|
return annotated_qs.filter(
|
||||||
|
conv_generic_org_type=DomainRequest.OrganizationChoices.FEDERAL,
|
||||||
|
conv_federal_type=BranchChoices.EXECUTIVE,
|
||||||
|
)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
class DomainRequestResource(FsmModelResource):
|
class DomainRequestResource(FsmModelResource):
|
||||||
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
||||||
|
@ -2235,7 +2435,7 @@ class DomainRequestResource(FsmModelResource):
|
||||||
model = models.DomainRequest
|
model = models.DomainRequest
|
||||||
|
|
||||||
|
|
||||||
class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
class DomainRequestAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||||
"""Custom domain requests admin class."""
|
"""Custom domain requests admin class."""
|
||||||
|
|
||||||
resource_classes = [DomainRequestResource]
|
resource_classes = [DomainRequestResource]
|
||||||
|
@ -2293,7 +2493,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
class FederalTypeFilter(admin.SimpleListFilter):
|
class FederalTypeFilter(admin.SimpleListFilter):
|
||||||
"""Custom Federal Type filter that accomodates portfolio feature.
|
"""Custom Federal Type filter that accomodates portfolio feature.
|
||||||
If we have a portfolio, use the portfolio's federal type. If not, use the
|
If we have a portfolio, use the portfolio's federal type. If not, use the
|
||||||
organization in the Domain Request object."""
|
organization in the Domain Request object's federal agency."""
|
||||||
|
|
||||||
title = "federal type"
|
title = "federal type"
|
||||||
parameter_name = "converted_federal_types"
|
parameter_name = "converted_federal_types"
|
||||||
|
@ -2334,7 +2534,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
if self.value():
|
if self.value():
|
||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
Q(portfolio__federal_agency__federal_type=self.value())
|
Q(portfolio__federal_agency__federal_type=self.value())
|
||||||
| Q(portfolio__isnull=True, federal_type=self.value())
|
| Q(portfolio__isnull=True, federal_agency__federal_type=self.value())
|
||||||
)
|
)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
@ -2749,6 +2949,62 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
"cisa_representative_email",
|
"cisa_representative_email",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Read only that we'll leverage for OMB Analysts
|
||||||
|
omb_analyst_readonly_fields = [
|
||||||
|
"federal_agency",
|
||||||
|
"creator",
|
||||||
|
"about_your_organization",
|
||||||
|
"requested_domain",
|
||||||
|
"approved_domain",
|
||||||
|
"alternative_domains",
|
||||||
|
"purpose",
|
||||||
|
"no_other_contacts_rationale",
|
||||||
|
"anything_else",
|
||||||
|
"is_policy_acknowledged",
|
||||||
|
"cisa_representative_first_name",
|
||||||
|
"cisa_representative_last_name",
|
||||||
|
"cisa_representative_email",
|
||||||
|
"status",
|
||||||
|
"investigator",
|
||||||
|
"notes",
|
||||||
|
"senior_official",
|
||||||
|
"organization_type",
|
||||||
|
"organization_name",
|
||||||
|
"state_territory",
|
||||||
|
"address_line1",
|
||||||
|
"address_line2",
|
||||||
|
"city",
|
||||||
|
"zipcode",
|
||||||
|
"urbanization",
|
||||||
|
"portfolio_organization_type",
|
||||||
|
"portfolio_federal_type",
|
||||||
|
"portfolio_organization_name",
|
||||||
|
"portfolio_federal_agency",
|
||||||
|
"portfolio_state_territory",
|
||||||
|
"portfolio_address_line1",
|
||||||
|
"portfolio_address_line2",
|
||||||
|
"portfolio_city",
|
||||||
|
"portfolio_zipcode",
|
||||||
|
"portfolio_urbanization",
|
||||||
|
"is_election_board",
|
||||||
|
"organization_type",
|
||||||
|
"federal_type",
|
||||||
|
"federal_agency",
|
||||||
|
"tribe_name",
|
||||||
|
"federally_recognized_tribe",
|
||||||
|
"state_recognized_tribe",
|
||||||
|
"about_your_organization",
|
||||||
|
"rejection_reason",
|
||||||
|
"rejection_reason_email",
|
||||||
|
"action_needed_reason",
|
||||||
|
"action_needed_reason_email",
|
||||||
|
"portfolio",
|
||||||
|
"sub_organization",
|
||||||
|
"requested_suborganization",
|
||||||
|
"suborganization_city",
|
||||||
|
"suborganization_state_territory",
|
||||||
|
]
|
||||||
|
|
||||||
autocomplete_fields = [
|
autocomplete_fields = [
|
||||||
"approved_domain",
|
"approved_domain",
|
||||||
"requested_domain",
|
"requested_domain",
|
||||||
|
@ -2989,6 +3245,10 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
|
|
||||||
if request.user.has_perm("registrar.full_access_permission"):
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
return readonly_fields
|
return readonly_fields
|
||||||
|
# Return restrictive Read-only fields for OMB analysts
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
readonly_fields.extend([field for field in self.omb_analyst_readonly_fields])
|
||||||
|
return readonly_fields
|
||||||
# Return restrictive Read-only fields for analysts and
|
# Return restrictive Read-only fields for analysts and
|
||||||
# users who might not belong to groups
|
# users who might not belong to groups
|
||||||
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
||||||
|
@ -3172,6 +3432,25 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
use_sort = db_field.name != "senior_official"
|
use_sort = db_field.name != "senior_official"
|
||||||
return super().formfield_for_foreignkey(db_field, request, use_admin_sort_fields=use_sort, **kwargs)
|
return super().formfield_for_foreignkey(db_field, request, use_admin_sort_fields=use_sort, **kwargs)
|
||||||
|
|
||||||
|
def get_annotated_queryset(self, queryset):
|
||||||
|
return queryset.annotate(
|
||||||
|
conv_generic_org_type=Case(
|
||||||
|
# When portfolio is present, use its value instead
|
||||||
|
When(portfolio__isnull=False, then=F("portfolio__organization_type")),
|
||||||
|
# Otherwise, return the natively assigned value
|
||||||
|
default=F("generic_org_type"),
|
||||||
|
),
|
||||||
|
conv_federal_type=Case(
|
||||||
|
# When portfolio is present, use its value instead
|
||||||
|
When(
|
||||||
|
Q(portfolio__isnull=False) & Q(portfolio__federal_agency__isnull=False),
|
||||||
|
then=F("portfolio__federal_agency__federal_type"),
|
||||||
|
),
|
||||||
|
# Otherwise, return federal type from federal agency
|
||||||
|
default=F("federal_agency__federal_type"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
"""Custom get_queryset to filter by portfolio if portfolio is in the
|
"""Custom get_queryset to filter by portfolio if portfolio is in the
|
||||||
request params."""
|
request params."""
|
||||||
|
@ -3181,8 +3460,39 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
if portfolio_id:
|
if portfolio_id:
|
||||||
# Further filter the queryset by the portfolio
|
# Further filter the queryset by the portfolio
|
||||||
qs = qs.filter(portfolio=portfolio_id)
|
qs = qs.filter(portfolio=portfolio_id)
|
||||||
|
# Check if user is in OMB analysts group
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
annotated_qs = self.get_annotated_queryset(qs)
|
||||||
|
return annotated_qs.filter(
|
||||||
|
conv_generic_org_type=DomainRequest.OrganizationChoices.FEDERAL,
|
||||||
|
conv_federal_type=BranchChoices.EXECUTIVE,
|
||||||
|
)
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
def has_view_permission(self, request, obj=None):
|
||||||
|
"""Restrict view permissions based on group membership and model attributes."""
|
||||||
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
|
return True
|
||||||
|
if obj:
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
return (
|
||||||
|
obj.converted_generic_org_type == DomainRequest.OrganizationChoices.FEDERAL
|
||||||
|
and obj.converted_federal_type == BranchChoices.EXECUTIVE
|
||||||
|
)
|
||||||
|
return super().has_view_permission(request, obj)
|
||||||
|
|
||||||
|
def has_change_permission(self, request, obj=None):
|
||||||
|
"""Restrict update permissions based on group membership and model attributes."""
|
||||||
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
|
return True
|
||||||
|
if obj:
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
return (
|
||||||
|
obj.converted_generic_org_type == DomainRequest.OrganizationChoices.FEDERAL
|
||||||
|
and obj.converted_federal_type == BranchChoices.EXECUTIVE
|
||||||
|
)
|
||||||
|
return super().has_change_permission(request, obj)
|
||||||
|
|
||||||
def get_search_results(self, request, queryset, search_term):
|
def get_search_results(self, request, queryset, search_term):
|
||||||
# Call the parent's method to apply default search logic
|
# Call the parent's method to apply default search logic
|
||||||
base_queryset, use_distinct = super().get_search_results(request, queryset, search_term)
|
base_queryset, use_distinct = super().get_search_results(request, queryset, search_term)
|
||||||
|
@ -3202,6 +3512,15 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
|
|
||||||
return combined_queryset, use_distinct
|
return combined_queryset, use_distinct
|
||||||
|
|
||||||
|
def get_form(self, request, obj=None, **kwargs):
|
||||||
|
"""Pass the 'is_omb_analyst' attribute to the form."""
|
||||||
|
form = super().get_form(request, obj, **kwargs)
|
||||||
|
|
||||||
|
# Store attribute in the form for template access
|
||||||
|
form.show_contact_as_plain_text = request.user.groups.filter(name="omb_analysts_group").exists()
|
||||||
|
|
||||||
|
return form
|
||||||
|
|
||||||
|
|
||||||
class TransitionDomainAdmin(ListHeaderAdmin):
|
class TransitionDomainAdmin(ListHeaderAdmin):
|
||||||
"""Custom transition domain admin class."""
|
"""Custom transition domain admin class."""
|
||||||
|
@ -3233,6 +3552,16 @@ class DomainInformationInline(admin.StackedInline):
|
||||||
template = "django/admin/includes/domain_info_inline_stacked.html"
|
template = "django/admin/includes/domain_info_inline_stacked.html"
|
||||||
model = models.DomainInformation
|
model = models.DomainInformation
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""Initialize the admin class and define a default value for is_omb_analyst."""
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.is_omb_analyst = False # Default value in case it's accessed before being set
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
"""Ensure self.is_omb_analyst is set early."""
|
||||||
|
self.is_omb_analyst = request.user.groups.filter(name="omb_analysts_group").exists()
|
||||||
|
return super().get_queryset(request)
|
||||||
|
|
||||||
# Define methods to display fields from the related portfolio
|
# Define methods to display fields from the related portfolio
|
||||||
def portfolio_senior_official(self, obj) -> Optional[SeniorOfficial]:
|
def portfolio_senior_official(self, obj) -> Optional[SeniorOfficial]:
|
||||||
return obj.portfolio.senior_official if obj.portfolio and obj.portfolio.senior_official else None
|
return obj.portfolio.senior_official if obj.portfolio and obj.portfolio.senior_official else None
|
||||||
|
@ -3300,6 +3629,7 @@ class DomainInformationInline(admin.StackedInline):
|
||||||
fieldsets = copy.deepcopy(list(DomainInformationAdmin.fieldsets))
|
fieldsets = copy.deepcopy(list(DomainInformationAdmin.fieldsets))
|
||||||
readonly_fields = copy.deepcopy(DomainInformationAdmin.readonly_fields)
|
readonly_fields = copy.deepcopy(DomainInformationAdmin.readonly_fields)
|
||||||
analyst_readonly_fields = copy.deepcopy(DomainInformationAdmin.analyst_readonly_fields)
|
analyst_readonly_fields = copy.deepcopy(DomainInformationAdmin.analyst_readonly_fields)
|
||||||
|
omb_analyst_readonly_fields = copy.deepcopy(DomainInformationAdmin.omb_analyst_readonly_fields)
|
||||||
autocomplete_fields = copy.deepcopy(DomainInformationAdmin.autocomplete_fields)
|
autocomplete_fields = copy.deepcopy(DomainInformationAdmin.autocomplete_fields)
|
||||||
|
|
||||||
def get_domain_managers(self, obj):
|
def get_domain_managers(self, obj):
|
||||||
|
@ -3320,11 +3650,15 @@ class DomainInformationInline(admin.StackedInline):
|
||||||
if not domain_managers:
|
if not domain_managers:
|
||||||
return "No domain managers found."
|
return "No domain managers found."
|
||||||
|
|
||||||
domain_manager_details = "<table><thead><tr><th>UID</th><th>Name</th><th>Email</th></tr></thead><tbody>"
|
domain_manager_details = "<table><thead><tr>"
|
||||||
|
if not self.is_omb_analyst:
|
||||||
|
domain_manager_details += "<th>UID</th>"
|
||||||
|
domain_manager_details += "<th>Name</th><th>Email</th></tr></thead><tbody>"
|
||||||
for domain_manager in domain_managers:
|
for domain_manager in domain_managers:
|
||||||
full_name = domain_manager.get_formatted_name()
|
full_name = domain_manager.get_formatted_name()
|
||||||
change_url = reverse("admin:registrar_user_change", args=[domain_manager.pk])
|
change_url = reverse("admin:registrar_user_change", args=[domain_manager.pk])
|
||||||
domain_manager_details += "<tr>"
|
domain_manager_details += "<tr>"
|
||||||
|
if not self.is_omb_analyst:
|
||||||
domain_manager_details += f'<td><a href="{change_url}">{escape(domain_manager.username)}</a>'
|
domain_manager_details += f'<td><a href="{change_url}">{escape(domain_manager.username)}</a>'
|
||||||
domain_manager_details += f"<td>{escape(full_name)}</td>"
|
domain_manager_details += f"<td>{escape(full_name)}</td>"
|
||||||
domain_manager_details += f"<td>{escape(domain_manager.email)}</td>"
|
domain_manager_details += f"<td>{escape(domain_manager.email)}</td>"
|
||||||
|
@ -3357,7 +3691,8 @@ class DomainInformationInline(admin.StackedInline):
|
||||||
|
|
||||||
superuser_perm = request.user.has_perm("registrar.full_access_permission")
|
superuser_perm = request.user.has_perm("registrar.full_access_permission")
|
||||||
analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
|
analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
|
||||||
if analyst_perm and not superuser_perm:
|
omb_analyst_perm = request.user.groups.filter(name="omb_analysts_group").exists()
|
||||||
|
if (analyst_perm or omb_analyst_perm) and not superuser_perm:
|
||||||
return True
|
return True
|
||||||
return super().has_change_permission(request, obj)
|
return super().has_change_permission(request, obj)
|
||||||
|
|
||||||
|
@ -3431,6 +3766,23 @@ class DomainInformationInline(admin.StackedInline):
|
||||||
|
|
||||||
return modified_fieldsets
|
return modified_fieldsets
|
||||||
|
|
||||||
|
def get_form(self, request, obj=None, **kwargs):
|
||||||
|
"""Pass the 'is_omb_analyst' attribute to the form."""
|
||||||
|
form = super().get_form(request, obj, **kwargs)
|
||||||
|
|
||||||
|
# Store attribute in the form for template access
|
||||||
|
self.is_omb_analyst = request.user.groups.filter(name="omb_analysts_group").exists()
|
||||||
|
form.show_contact_as_plain_text = self.is_omb_analyst
|
||||||
|
form.is_omb_analyst = self.is_omb_analyst
|
||||||
|
|
||||||
|
return form
|
||||||
|
|
||||||
|
def get_formset(self, request, obj=None, **kwargs):
|
||||||
|
"""Attach request to the formset so that it can be available in the form"""
|
||||||
|
formset = super().get_formset(request, obj, **kwargs)
|
||||||
|
formset.form.request = request # Attach request to form
|
||||||
|
return formset
|
||||||
|
|
||||||
|
|
||||||
class DomainResource(FsmModelResource):
|
class DomainResource(FsmModelResource):
|
||||||
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
||||||
|
@ -3440,7 +3792,7 @@ class DomainResource(FsmModelResource):
|
||||||
model = models.Domain
|
model = models.Domain
|
||||||
|
|
||||||
|
|
||||||
class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
class DomainAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||||
"""Custom domain admin class to add extra buttons."""
|
"""Custom domain admin class to add extra buttons."""
|
||||||
|
|
||||||
resource_classes = [DomainResource]
|
resource_classes = [DomainResource]
|
||||||
|
@ -3552,7 +3904,7 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
if self.value():
|
if self.value():
|
||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
Q(domain_info__portfolio__federal_type=self.value())
|
Q(domain_info__portfolio__federal_type=self.value())
|
||||||
| Q(domain_info__portfolio__isnull=True, domain_info__federal_type=self.value())
|
| Q(domain_info__portfolio__isnull=True, domain_info__federal_agency__federal_type=self.value())
|
||||||
)
|
)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
@ -3579,7 +3931,7 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
Q(domain_info__portfolio__isnull=False) & Q(domain_info__portfolio__federal_agency__isnull=False),
|
Q(domain_info__portfolio__isnull=False) & Q(domain_info__portfolio__federal_agency__isnull=False),
|
||||||
then=F("domain_info__portfolio__federal_agency__federal_type"),
|
then=F("domain_info__portfolio__federal_agency__federal_type"),
|
||||||
),
|
),
|
||||||
# Otherwise, return the natively assigned value
|
# Otherwise, return federal type from federal agency
|
||||||
default=F("domain_info__federal_agency__federal_type"),
|
default=F("domain_info__federal_agency__federal_type"),
|
||||||
),
|
),
|
||||||
converted_organization_name=Case(
|
converted_organization_name=Case(
|
||||||
|
@ -4006,8 +4358,10 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
# Fixes a bug wherein users which are only is_staff
|
# Fixes a bug wherein users which are only is_staff
|
||||||
# can access 'change' when GET,
|
# can access 'change' when GET,
|
||||||
# but cannot access this page when it is a request of type POST.
|
# but cannot access this page when it is a request of type POST.
|
||||||
if request.user.has_perm("registrar.full_access_permission") or request.user.has_perm(
|
if (
|
||||||
"registrar.analyst_access_permission"
|
request.user.has_perm("registrar.full_access_permission")
|
||||||
|
or request.user.has_perm("registrar.analyst_access_permission")
|
||||||
|
or request.user.groups.filter(name="omb_analysts_group").exists()
|
||||||
):
|
):
|
||||||
return True
|
return True
|
||||||
return super().has_change_permission(request, obj)
|
return super().has_change_permission(request, obj)
|
||||||
|
@ -4022,8 +4376,37 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
if portfolio_id:
|
if portfolio_id:
|
||||||
# Further filter the queryset by the portfolio
|
# Further filter the queryset by the portfolio
|
||||||
qs = qs.filter(domain_info__portfolio=portfolio_id)
|
qs = qs.filter(domain_info__portfolio=portfolio_id)
|
||||||
|
# Check if user is in OMB analysts group
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
return qs.filter(
|
||||||
|
converted_generic_org_type=DomainRequest.OrganizationChoices.FEDERAL,
|
||||||
|
converted_federal_type=BranchChoices.EXECUTIVE,
|
||||||
|
)
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
def has_view_permission(self, request, obj=None):
|
||||||
|
"""Restrict view permissions based on group membership and model attributes."""
|
||||||
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
|
return True
|
||||||
|
if obj:
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
return (
|
||||||
|
obj.domain_info.converted_generic_org_type == DomainRequest.OrganizationChoices.FEDERAL
|
||||||
|
and obj.domain_info.converted_federal_type == BranchChoices.EXECUTIVE
|
||||||
|
)
|
||||||
|
return super().has_view_permission(request, obj)
|
||||||
|
|
||||||
|
def get_form(self, request, obj=None, **kwargs):
|
||||||
|
"""Pass the 'is_omb_analyst' attribute to the form."""
|
||||||
|
form = super().get_form(request, obj, **kwargs)
|
||||||
|
|
||||||
|
# Store attribute in the form for template access
|
||||||
|
is_omb_analyst = request.user.groups.filter(name="omb_analysts_group").exists()
|
||||||
|
form.show_contact_as_plain_text = is_omb_analyst
|
||||||
|
form.is_omb_analyst = is_omb_analyst
|
||||||
|
|
||||||
|
return form
|
||||||
|
|
||||||
|
|
||||||
class DraftDomainResource(resources.ModelResource):
|
class DraftDomainResource(resources.ModelResource):
|
||||||
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
||||||
|
@ -4033,7 +4416,7 @@ class DraftDomainResource(resources.ModelResource):
|
||||||
model = models.DraftDomain
|
model = models.DraftDomain
|
||||||
|
|
||||||
|
|
||||||
class DraftDomainAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
class DraftDomainAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||||
"""Custom draft domain admin class."""
|
"""Custom draft domain admin class."""
|
||||||
|
|
||||||
resource_classes = [DraftDomainResource]
|
resource_classes = [DraftDomainResource]
|
||||||
|
@ -4145,7 +4528,7 @@ class PublicContactResource(resources.ModelResource):
|
||||||
self.after_save_instance(instance, using_transactions, dry_run)
|
self.after_save_instance(instance, using_transactions, dry_run)
|
||||||
|
|
||||||
|
|
||||||
class PublicContactAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
class PublicContactAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||||
"""Custom PublicContact admin class."""
|
"""Custom PublicContact admin class."""
|
||||||
|
|
||||||
resource_classes = [PublicContactResource]
|
resource_classes = [PublicContactResource]
|
||||||
|
@ -4200,6 +4583,11 @@ class PortfolioAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
_meta = Meta()
|
_meta = Meta()
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""Initialize the admin class and define a default value for is_omb_analyst."""
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.is_omb_analyst = False # Default value in case it's accessed before being set
|
||||||
|
|
||||||
change_form_template = "django/admin/portfolio_change_form.html"
|
change_form_template = "django/admin/portfolio_change_form.html"
|
||||||
fieldsets = [
|
fieldsets = [
|
||||||
# created_on is the created_at field
|
# created_on is the created_at field
|
||||||
|
@ -4281,6 +4669,19 @@ class PortfolioAdmin(ListHeaderAdmin):
|
||||||
# rather than strip it out of our logic.
|
# rather than strip it out of our logic.
|
||||||
analyst_readonly_fields = [] # type: ignore
|
analyst_readonly_fields = [] # type: ignore
|
||||||
|
|
||||||
|
omb_analyst_readonly_fields = [
|
||||||
|
"notes",
|
||||||
|
"organization_type",
|
||||||
|
"organization_name",
|
||||||
|
"federal_agency",
|
||||||
|
"state_territory",
|
||||||
|
"address_line1",
|
||||||
|
"address_line2",
|
||||||
|
"city",
|
||||||
|
"zipcode",
|
||||||
|
"urbanization",
|
||||||
|
]
|
||||||
|
|
||||||
def get_admin_users(self, obj):
|
def get_admin_users(self, obj):
|
||||||
# Filter UserPortfolioPermission objects related to the portfolio
|
# Filter UserPortfolioPermission objects related to the portfolio
|
||||||
admin_permissions = self.get_user_portfolio_permission_admins(obj)
|
admin_permissions = self.get_user_portfolio_permission_admins(obj)
|
||||||
|
@ -4366,6 +4767,8 @@ class PortfolioAdmin(ListHeaderAdmin):
|
||||||
"""Returns the number of administrators for this portfolio"""
|
"""Returns the number of administrators for this portfolio"""
|
||||||
admin_count = len(self.get_user_portfolio_permission_admins(obj))
|
admin_count = len(self.get_user_portfolio_permission_admins(obj))
|
||||||
if admin_count > 0:
|
if admin_count > 0:
|
||||||
|
if self.is_omb_analyst:
|
||||||
|
return format_html(f"{admin_count} administrators")
|
||||||
url = reverse("admin:registrar_userportfoliopermission_changelist") + f"?portfolio={obj.id}"
|
url = reverse("admin:registrar_userportfoliopermission_changelist") + f"?portfolio={obj.id}"
|
||||||
# Create a clickable link with the count
|
# Create a clickable link with the count
|
||||||
return format_html(f'<a href="{url}">{admin_count} admins</a>')
|
return format_html(f'<a href="{url}">{admin_count} admins</a>')
|
||||||
|
@ -4377,6 +4780,8 @@ class PortfolioAdmin(ListHeaderAdmin):
|
||||||
"""Returns the number of basic members for this portfolio"""
|
"""Returns the number of basic members for this portfolio"""
|
||||||
member_count = len(self.get_user_portfolio_permission_non_admins(obj))
|
member_count = len(self.get_user_portfolio_permission_non_admins(obj))
|
||||||
if member_count > 0:
|
if member_count > 0:
|
||||||
|
if self.is_omb_analyst:
|
||||||
|
return format_html(f"{member_count} members")
|
||||||
url = reverse("admin:registrar_userportfoliopermission_changelist") + f"?portfolio={obj.id}"
|
url = reverse("admin:registrar_userportfoliopermission_changelist") + f"?portfolio={obj.id}"
|
||||||
# Create a clickable link with the count
|
# Create a clickable link with the count
|
||||||
return format_html(f'<a href="{url}">{member_count} basic members</a>')
|
return format_html(f'<a href="{url}">{member_count} basic members</a>')
|
||||||
|
@ -4422,12 +4827,35 @@ class PortfolioAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
if request.user.has_perm("registrar.full_access_permission"):
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
return readonly_fields
|
return readonly_fields
|
||||||
|
# Return restrictive Read-only fields for OMB analysts
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
readonly_fields.extend([field for field in self.omb_analyst_readonly_fields])
|
||||||
|
return readonly_fields
|
||||||
# Return restrictive Read-only fields for analysts and
|
# Return restrictive Read-only fields for analysts and
|
||||||
# users who might not belong to groups
|
# users who might not belong to groups
|
||||||
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
||||||
return readonly_fields
|
return readonly_fields
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
"""Restrict queryset based on user permissions."""
|
||||||
|
qs = super().get_queryset(request)
|
||||||
|
|
||||||
|
# Check if user is in OMB analysts group
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
self.is_omb_analyst = True
|
||||||
|
return qs.filter(federal_agency__federal_type=BranchChoices.EXECUTIVE)
|
||||||
|
|
||||||
|
return qs # Return full queryset if the user doesn't have the restriction
|
||||||
|
|
||||||
|
def has_view_permission(self, request, obj=None):
|
||||||
|
"""Restrict view permissions based on group membership and model attributes."""
|
||||||
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
|
return True
|
||||||
|
if obj:
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
return obj.federal_type == BranchChoices.EXECUTIVE
|
||||||
|
return super().has_view_permission(request, obj)
|
||||||
|
|
||||||
def change_view(self, request, object_id, form_url="", extra_context=None):
|
def change_view(self, request, object_id, form_url="", extra_context=None):
|
||||||
"""Add related suborganizations and domain groups.
|
"""Add related suborganizations and domain groups.
|
||||||
Add the summary for the portfolio members field (list of members that link to change_forms)."""
|
Add the summary for the portfolio members field (list of members that link to change_forms)."""
|
||||||
|
@ -4472,6 +4900,17 @@ class PortfolioAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
super().save_model(request, obj, form, change)
|
super().save_model(request, obj, form, change)
|
||||||
|
|
||||||
|
def get_form(self, request, obj=None, **kwargs):
|
||||||
|
"""Pass the 'is_omb_analyst' attribute to the form."""
|
||||||
|
form = super().get_form(request, obj, **kwargs)
|
||||||
|
|
||||||
|
# Store attribute in the form for template access
|
||||||
|
self.is_omb_analyst = request.user.groups.filter(name="omb_analysts_group").exists()
|
||||||
|
form.show_contact_as_plain_text = self.is_omb_analyst
|
||||||
|
form.is_omb_analyst = self.is_omb_analyst
|
||||||
|
|
||||||
|
return form
|
||||||
|
|
||||||
|
|
||||||
class FederalAgencyResource(resources.ModelResource):
|
class FederalAgencyResource(resources.ModelResource):
|
||||||
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
||||||
|
@ -4481,13 +4920,66 @@ class FederalAgencyResource(resources.ModelResource):
|
||||||
model = models.FederalAgency
|
model = models.FederalAgency
|
||||||
|
|
||||||
|
|
||||||
class FederalAgencyAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
class FederalAgencyAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||||
list_display = ["agency"]
|
list_display = ["agency"]
|
||||||
search_fields = ["agency"]
|
search_fields = ["agency"]
|
||||||
search_help_text = "Search by federal agency."
|
search_help_text = "Search by federal agency."
|
||||||
ordering = ["agency"]
|
ordering = ["agency"]
|
||||||
resource_classes = [FederalAgencyResource]
|
resource_classes = [FederalAgencyResource]
|
||||||
|
|
||||||
|
# Readonly fields for analysts and superusers
|
||||||
|
readonly_fields = []
|
||||||
|
|
||||||
|
# Read only that we'll leverage for CISA Analysts
|
||||||
|
analyst_readonly_fields = [] # type: ignore
|
||||||
|
|
||||||
|
# Read only that we'll leverage for OMB Analysts
|
||||||
|
omb_analyst_readonly_fields = [
|
||||||
|
"agency",
|
||||||
|
"federal_type",
|
||||||
|
"acronym",
|
||||||
|
"is_fceb",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
"""Restrict queryset based on user permissions."""
|
||||||
|
qs = super().get_queryset(request)
|
||||||
|
|
||||||
|
# Check if user is in OMB analysts group
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
return qs.filter(
|
||||||
|
federal_type=BranchChoices.EXECUTIVE,
|
||||||
|
)
|
||||||
|
|
||||||
|
return qs # Return full queryset if the user doesn't have the restriction
|
||||||
|
|
||||||
|
def has_view_permission(self, request, obj=None):
|
||||||
|
"""Restrict view permissions based on group membership and model attributes."""
|
||||||
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
|
return True
|
||||||
|
if obj:
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
return obj.federal_type == BranchChoices.EXECUTIVE
|
||||||
|
return super().has_view_permission(request, obj)
|
||||||
|
|
||||||
|
def get_readonly_fields(self, request, obj=None):
|
||||||
|
"""Set the read-only state on form elements.
|
||||||
|
We have 2 conditions that determine which fields are read-only:
|
||||||
|
admin user permissions and the domain request creator's status, so
|
||||||
|
we'll use the baseline readonly_fields and extend it as needed.
|
||||||
|
"""
|
||||||
|
readonly_fields = list(self.readonly_fields)
|
||||||
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
|
return readonly_fields
|
||||||
|
# Return restrictive Read-only fields for OMB analysts
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
readonly_fields.extend([field for field in self.omb_analyst_readonly_fields])
|
||||||
|
return readonly_fields
|
||||||
|
# Return restrictive Read-only fields for analysts and
|
||||||
|
# users who might not belong to groups
|
||||||
|
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
||||||
|
return readonly_fields
|
||||||
|
|
||||||
|
|
||||||
class UserGroupAdmin(AuditedAdmin):
|
class UserGroupAdmin(AuditedAdmin):
|
||||||
"""Overwrite the generated UserGroup admin class"""
|
"""Overwrite the generated UserGroup admin class"""
|
||||||
|
@ -4537,11 +5029,11 @@ class WaffleFlagAdmin(FlagAdmin):
|
||||||
return super().changelist_view(request, extra_context=extra_context)
|
return super().changelist_view(request, extra_context=extra_context)
|
||||||
|
|
||||||
|
|
||||||
class DomainGroupAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
class DomainGroupAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||||
list_display = ["name", "portfolio"]
|
list_display = ["name", "portfolio"]
|
||||||
|
|
||||||
|
|
||||||
class SuborganizationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
class SuborganizationAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||||
|
|
||||||
list_display = ["name", "portfolio"]
|
list_display = ["name", "portfolio"]
|
||||||
autocomplete_fields = [
|
autocomplete_fields = [
|
||||||
|
@ -4552,6 +5044,38 @@ class SuborganizationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
|
|
||||||
change_form_template = "django/admin/suborg_change_form.html"
|
change_form_template = "django/admin/suborg_change_form.html"
|
||||||
|
|
||||||
|
readonly_fields = []
|
||||||
|
|
||||||
|
# Even though this is empty, I will leave it as a stub for easy changes in the future
|
||||||
|
# rather than strip it out of our logic.
|
||||||
|
analyst_readonly_fields = [] # type: ignore
|
||||||
|
|
||||||
|
omb_analyst_readonly_fields = [
|
||||||
|
"name",
|
||||||
|
"portfolio",
|
||||||
|
"city",
|
||||||
|
"state_territory",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_readonly_fields(self, request, obj=None):
|
||||||
|
"""Set the read-only state on form elements.
|
||||||
|
We have conditions that determine which fields are read-only:
|
||||||
|
admin user permissions and analyst (cisa or omb) status, so
|
||||||
|
we'll use the baseline readonly_fields and extend it as needed.
|
||||||
|
"""
|
||||||
|
readonly_fields = list(self.readonly_fields)
|
||||||
|
|
||||||
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
|
return readonly_fields
|
||||||
|
# Return restrictive Read-only fields for OMB analysts
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
readonly_fields.extend([field for field in self.omb_analyst_readonly_fields])
|
||||||
|
return readonly_fields
|
||||||
|
# Return restrictive Read-only fields for analysts and
|
||||||
|
# users who might not belong to groups
|
||||||
|
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
||||||
|
return readonly_fields
|
||||||
|
|
||||||
def change_view(self, request, object_id, form_url="", extra_context=None):
|
def change_view(self, request, object_id, form_url="", extra_context=None):
|
||||||
"""Add suborg's related domains and requests to context"""
|
"""Add suborg's related domains and requests to context"""
|
||||||
obj = self.get_object(request, object_id)
|
obj = self.get_object(request, object_id)
|
||||||
|
@ -4569,6 +5093,30 @@ class SuborganizationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
extra_context = {"domain_requests": domain_requests, "domains": domains}
|
extra_context = {"domain_requests": domain_requests, "domains": domains}
|
||||||
return super().change_view(request, object_id, form_url, extra_context)
|
return super().change_view(request, object_id, form_url, extra_context)
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
"""Custom get_queryset to filter for OMB analysts."""
|
||||||
|
qs = super().get_queryset(request)
|
||||||
|
# Check if user is in OMB analysts group
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
return qs.filter(
|
||||||
|
portfolio__organization_type=DomainRequest.OrganizationChoices.FEDERAL,
|
||||||
|
portfolio__federal_agency__federal_type=BranchChoices.EXECUTIVE,
|
||||||
|
)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
def has_view_permission(self, request, obj=None):
|
||||||
|
"""Restrict view permissions based on group membership and model attributes."""
|
||||||
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
|
return True
|
||||||
|
if obj:
|
||||||
|
if request.user.groups.filter(name="omb_analysts_group").exists():
|
||||||
|
return (
|
||||||
|
obj.portfolio
|
||||||
|
and obj.portfolio.federal_agency
|
||||||
|
and obj.portfolio.federal_agency.federal_type == BranchChoices.EXECUTIVE
|
||||||
|
)
|
||||||
|
return super().has_view_permission(request, obj)
|
||||||
|
|
||||||
|
|
||||||
class AllowedEmailAdmin(ListHeaderAdmin):
|
class AllowedEmailAdmin(ListHeaderAdmin):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -105,8 +105,10 @@ export function initApprovedDomain() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusToCheck = "approved";
|
const statusToCheck = "approved"; // when checking against a select
|
||||||
|
const readonlyStatusToCheck = "Approved"; // when checking against a readonly div display value
|
||||||
const statusSelect = document.getElementById("id_status");
|
const statusSelect = document.getElementById("id_status");
|
||||||
|
const statusField = document.querySelector("field-status");
|
||||||
const sessionVariableName = "showApprovedDomain";
|
const sessionVariableName = "showApprovedDomain";
|
||||||
let approvedDomainFormGroup = document.querySelector(".field-approved_domain");
|
let approvedDomainFormGroup = document.querySelector(".field-approved_domain");
|
||||||
|
|
||||||
|
@ -120,11 +122,24 @@ export function initApprovedDomain() {
|
||||||
|
|
||||||
// Handle showing/hiding the related fields on page load.
|
// Handle showing/hiding the related fields on page load.
|
||||||
function initializeFormGroups() {
|
function initializeFormGroups() {
|
||||||
let isStatus = statusSelect.value == statusToCheck;
|
// Status is either in a select or in a readonly div. Both
|
||||||
|
// cases are handled below.
|
||||||
|
let isStatus = false;
|
||||||
|
if (statusSelect) {
|
||||||
|
isStatus = statusSelect.value == statusToCheck;
|
||||||
|
} else {
|
||||||
|
// statusSelect does not exist, indicating readonly
|
||||||
|
if (statusField) {
|
||||||
|
let readonlyDiv = statusField.querySelector("div.readonly");
|
||||||
|
let readonlyStatusText = readonlyDiv.textContent.trim();
|
||||||
|
isStatus = readonlyStatusText == readonlyStatusToCheck;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initial handling of these groups.
|
// Initial handling of these groups.
|
||||||
updateFormGroupVisibility(isStatus);
|
updateFormGroupVisibility(isStatus);
|
||||||
|
|
||||||
|
if (statusSelect) {
|
||||||
// Listen to change events and handle rejectionReasonFormGroup display, then save status to session storage
|
// Listen to change events and handle rejectionReasonFormGroup display, then save status to session storage
|
||||||
statusSelect.addEventListener('change', () => {
|
statusSelect.addEventListener('change', () => {
|
||||||
// Show the approved if the status is what we expect.
|
// Show the approved if the status is what we expect.
|
||||||
|
@ -132,6 +147,7 @@ export function initApprovedDomain() {
|
||||||
updateFormGroupVisibility(isStatus);
|
updateFormGroupVisibility(isStatus);
|
||||||
addOrRemoveSessionBoolean(sessionVariableName, isStatus);
|
addOrRemoveSessionBoolean(sessionVariableName, isStatus);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Listen to Back/Forward button navigation and handle approvedDomainFormGroup display based on session storage
|
// Listen to Back/Forward button navigation and handle approvedDomainFormGroup display based on session storage
|
||||||
// When you navigate using forward/back after changing status but not saving, when you land back on the DA page the
|
// When you navigate using forward/back after changing status but not saving, when you land back on the DA page the
|
||||||
|
@ -322,6 +338,7 @@ class CustomizableEmailBase {
|
||||||
* @property {HTMLElement} modalConfirm - The confirm button in the modal.
|
* @property {HTMLElement} modalConfirm - The confirm button in the modal.
|
||||||
* @property {string} apiUrl - The API URL for fetching email content.
|
* @property {string} apiUrl - The API URL for fetching email content.
|
||||||
* @property {string} statusToCheck - The status to check against. Used for show/hide on textAreaFormGroup/dropdownFormGroup.
|
* @property {string} statusToCheck - The status to check against. Used for show/hide on textAreaFormGroup/dropdownFormGroup.
|
||||||
|
* @property {string} readonlyStatusToCheck - The status to check against when readonly. Used for show/hide on textAreaFormGroup/dropdownFormGroup.
|
||||||
* @property {string} sessionVariableName - The session variable name. Used for show/hide on textAreaFormGroup/dropdownFormGroup.
|
* @property {string} sessionVariableName - The session variable name. Used for show/hide on textAreaFormGroup/dropdownFormGroup.
|
||||||
* @property {string} apiErrorMessage - The error message that the ajax call returns.
|
* @property {string} apiErrorMessage - The error message that the ajax call returns.
|
||||||
*/
|
*/
|
||||||
|
@ -338,6 +355,7 @@ class CustomizableEmailBase {
|
||||||
this.textAreaFormGroup = config.textAreaFormGroup;
|
this.textAreaFormGroup = config.textAreaFormGroup;
|
||||||
this.dropdownFormGroup = config.dropdownFormGroup;
|
this.dropdownFormGroup = config.dropdownFormGroup;
|
||||||
this.statusToCheck = config.statusToCheck;
|
this.statusToCheck = config.statusToCheck;
|
||||||
|
this.readonlyStatusToCheck = config.readonlyStatusToCheck;
|
||||||
this.sessionVariableName = config.sessionVariableName;
|
this.sessionVariableName = config.sessionVariableName;
|
||||||
|
|
||||||
// Non-configurable variables
|
// Non-configurable variables
|
||||||
|
@ -363,11 +381,22 @@ class CustomizableEmailBase {
|
||||||
|
|
||||||
// Handle showing/hiding the related fields on page load.
|
// Handle showing/hiding the related fields on page load.
|
||||||
initializeFormGroups() {
|
initializeFormGroups() {
|
||||||
let isStatus = this.statusSelect.value == this.statusToCheck;
|
let isStatus = false;
|
||||||
|
if (this.statusSelect) {
|
||||||
|
isStatus = this.statusSelect.value == this.statusToCheck;
|
||||||
|
} else {
|
||||||
|
// statusSelect does not exist, indicating readonly
|
||||||
|
if (this.dropdownFormGroup) {
|
||||||
|
let readonlyDiv = this.dropdownFormGroup.querySelector("div.readonly");
|
||||||
|
let readonlyStatusText = readonlyDiv.textContent.trim();
|
||||||
|
isStatus = readonlyStatusText == this.readonlyStatusToCheck;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initial handling of these groups.
|
// Initial handling of these groups.
|
||||||
this.updateFormGroupVisibility(isStatus);
|
this.updateFormGroupVisibility(isStatus);
|
||||||
|
|
||||||
|
if (this.statusSelect) {
|
||||||
// Listen to change events and handle rejectionReasonFormGroup display, then save status to session storage
|
// Listen to change events and handle rejectionReasonFormGroup display, then save status to session storage
|
||||||
this.statusSelect.addEventListener('change', () => {
|
this.statusSelect.addEventListener('change', () => {
|
||||||
// Show the action needed field if the status is what we expect.
|
// Show the action needed field if the status is what we expect.
|
||||||
|
@ -376,6 +405,7 @@ class CustomizableEmailBase {
|
||||||
this.updateFormGroupVisibility(isStatus);
|
this.updateFormGroupVisibility(isStatus);
|
||||||
addOrRemoveSessionBoolean(this.sessionVariableName, isStatus);
|
addOrRemoveSessionBoolean(this.sessionVariableName, isStatus);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Listen to Back/Forward button navigation and handle rejectionReasonFormGroup display based on session storage
|
// Listen to Back/Forward button navigation and handle rejectionReasonFormGroup display based on session storage
|
||||||
// When you navigate using forward/back after changing status but not saving, when you land back on the DA page the
|
// When you navigate using forward/back after changing status but not saving, when you land back on the DA page the
|
||||||
|
@ -403,6 +433,7 @@ class CustomizableEmailBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeDropdown() {
|
initializeDropdown() {
|
||||||
|
if (this.dropdown) {
|
||||||
this.dropdown.addEventListener("change", () => {
|
this.dropdown.addEventListener("change", () => {
|
||||||
let reason = this.dropdown.value;
|
let reason = this.dropdown.value;
|
||||||
if (this.initialDropdownValue !== this.dropdown.value || this.initialEmailValue !== this.textarea.value) {
|
if (this.initialDropdownValue !== this.dropdown.value || this.initialEmailValue !== this.textarea.value) {
|
||||||
|
@ -431,8 +462,11 @@ class CustomizableEmailBase {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
initializeModalConfirm() {
|
initializeModalConfirm() {
|
||||||
|
// When the modal confirm button is present, add a listener
|
||||||
|
if (this.modalConfirm) {
|
||||||
this.modalConfirm.addEventListener("click", () => {
|
this.modalConfirm.addEventListener("click", () => {
|
||||||
this.textarea.removeAttribute('readonly');
|
this.textarea.removeAttribute('readonly');
|
||||||
this.textarea.focus();
|
this.textarea.focus();
|
||||||
|
@ -440,8 +474,11 @@ class CustomizableEmailBase {
|
||||||
hideElement(this.modalTrigger);
|
hideElement(this.modalTrigger);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
initializeDirectEditButton() {
|
initializeDirectEditButton() {
|
||||||
|
// When the direct edit button is present, add a listener
|
||||||
|
if (this.directEditButton) {
|
||||||
this.directEditButton.addEventListener("click", () => {
|
this.directEditButton.addEventListener("click", () => {
|
||||||
this.textarea.removeAttribute('readonly');
|
this.textarea.removeAttribute('readonly');
|
||||||
this.textarea.focus();
|
this.textarea.focus();
|
||||||
|
@ -449,12 +486,13 @@ class CustomizableEmailBase {
|
||||||
hideElement(this.modalTrigger);
|
hideElement(this.modalTrigger);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isEmailAlreadySent() {
|
isEmailAlreadySent() {
|
||||||
return this.lastSentEmailContent.value.replace(/\s+/g, '') === this.textarea.value.replace(/\s+/g, '');
|
return this.lastSentEmailContent.value.replace(/\s+/g, '') === this.textarea.value.replace(/\s+/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUserInterface(reason=this.dropdown.value, excluded_reasons=["other"]) {
|
updateUserInterface(reason, excluded_reasons=["other"]) {
|
||||||
if (!reason) {
|
if (!reason) {
|
||||||
// No reason selected, we will set the label to "Email", show the "Make a selection" placeholder, hide the trigger, textarea, hide the help text
|
// No reason selected, we will set the label to "Email", show the "Make a selection" placeholder, hide the trigger, textarea, hide the help text
|
||||||
this.showPlaceholderNoReason();
|
this.showPlaceholderNoReason();
|
||||||
|
@ -468,6 +506,7 @@ class CustomizableEmailBase {
|
||||||
|
|
||||||
// Helper function that makes overriding the readonly textarea easy
|
// Helper function that makes overriding the readonly textarea easy
|
||||||
showReadonlyTextarea() {
|
showReadonlyTextarea() {
|
||||||
|
if (this.textarea && this.textareaPlaceholder) {
|
||||||
// A triggering selection is selected, all hands on board:
|
// A triggering selection is selected, all hands on board:
|
||||||
this.textarea.setAttribute('readonly', true);
|
this.textarea.setAttribute('readonly', true);
|
||||||
showElement(this.textarea);
|
showElement(this.textarea);
|
||||||
|
@ -487,6 +526,7 @@ class CustomizableEmailBase {
|
||||||
this.formLabel.innerHTML = "Email:";
|
this.formLabel.innerHTML = "Email:";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function that makes overriding the placeholder reason easy
|
// Helper function that makes overriding the placeholder reason easy
|
||||||
showPlaceholderNoReason() {
|
showPlaceholderNoReason() {
|
||||||
|
@ -516,9 +556,10 @@ class customActionNeededEmail extends CustomizableEmailBase {
|
||||||
lastSentEmailContent: document.getElementById("last-sent-action-needed-email-content"),
|
lastSentEmailContent: document.getElementById("last-sent-action-needed-email-content"),
|
||||||
modalConfirm: document.getElementById("action-needed-reason__confirm-edit-email"),
|
modalConfirm: document.getElementById("action-needed-reason__confirm-edit-email"),
|
||||||
apiUrl: document.getElementById("get-action-needed-email-for-user-json")?.value || null,
|
apiUrl: document.getElementById("get-action-needed-email-for-user-json")?.value || null,
|
||||||
textAreaFormGroup: document.querySelector('.field-action_needed_reason'),
|
textAreaFormGroup: document.querySelector('.field-action_needed_reason_email'),
|
||||||
dropdownFormGroup: document.querySelector('.field-action_needed_reason_email'),
|
dropdownFormGroup: document.querySelector('.field-action_needed_reason'),
|
||||||
statusToCheck: "action needed",
|
statusToCheck: "action needed",
|
||||||
|
readonlyStatusToCheck: "Action needed",
|
||||||
sessionVariableName: "showActionNeededReason",
|
sessionVariableName: "showActionNeededReason",
|
||||||
apiErrorMessage: "Error when attempting to grab action needed email: "
|
apiErrorMessage: "Error when attempting to grab action needed email: "
|
||||||
}
|
}
|
||||||
|
@ -529,7 +570,15 @@ class customActionNeededEmail extends CustomizableEmailBase {
|
||||||
// Hide/show the email fields depending on the current status
|
// Hide/show the email fields depending on the current status
|
||||||
this.initializeFormGroups();
|
this.initializeFormGroups();
|
||||||
// Setup the textarea, edit button, helper text
|
// Setup the textarea, edit button, helper text
|
||||||
this.updateUserInterface();
|
let reason = null;
|
||||||
|
if (this.dropdown) {
|
||||||
|
reason = this.dropdown.value;
|
||||||
|
} else if (this.dropdownFormGroup && this.dropdownFormGroup.querySelector("div.readonly")) {
|
||||||
|
if (this.dropdownFormGroup.querySelector("div.readonly").textContent) {
|
||||||
|
reason = this.dropdownFormGroup.querySelector("div.readonly").textContent.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.updateUserInterface(reason);
|
||||||
this.initializeDropdown();
|
this.initializeDropdown();
|
||||||
this.initializeModalConfirm();
|
this.initializeModalConfirm();
|
||||||
this.initializeDirectEditButton();
|
this.initializeDirectEditButton();
|
||||||
|
@ -560,12 +609,6 @@ export function initActionNeededEmail() {
|
||||||
// Initialize UI
|
// Initialize UI
|
||||||
const customEmail = new customActionNeededEmail();
|
const customEmail = new customActionNeededEmail();
|
||||||
|
|
||||||
// Check that every variable was setup correctly
|
|
||||||
const nullItems = Object.entries(customEmail.config).filter(([key, value]) => value === null).map(([key]) => key);
|
|
||||||
if (nullItems.length > 0) {
|
|
||||||
console.error(`Failed to load customActionNeededEmail(). Some variables were null: ${nullItems.join(", ")}`)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
customEmail.loadActionNeededEmail()
|
customEmail.loadActionNeededEmail()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -581,6 +624,7 @@ class customRejectedEmail extends CustomizableEmailBase {
|
||||||
textAreaFormGroup: document.querySelector('.field-rejection_reason'),
|
textAreaFormGroup: document.querySelector('.field-rejection_reason'),
|
||||||
dropdownFormGroup: document.querySelector('.field-rejection_reason_email'),
|
dropdownFormGroup: document.querySelector('.field-rejection_reason_email'),
|
||||||
statusToCheck: "rejected",
|
statusToCheck: "rejected",
|
||||||
|
readonlyStatusToCheck: "Rejected",
|
||||||
sessionVariableName: "showRejectionReason",
|
sessionVariableName: "showRejectionReason",
|
||||||
errorMessage: "Error when attempting to grab rejected email: "
|
errorMessage: "Error when attempting to grab rejected email: "
|
||||||
};
|
};
|
||||||
|
@ -589,7 +633,15 @@ class customRejectedEmail extends CustomizableEmailBase {
|
||||||
|
|
||||||
loadRejectedEmail() {
|
loadRejectedEmail() {
|
||||||
this.initializeFormGroups();
|
this.initializeFormGroups();
|
||||||
this.updateUserInterface();
|
let reason = null;
|
||||||
|
if (this.dropdown) {
|
||||||
|
reason = this.dropdown.value;
|
||||||
|
} else if (this.dropdownFormGroup && this.dropdownFormGroup.querySelector("div.readonly")) {
|
||||||
|
if (this.dropdownFormGroup.querySelector("div.readonly").textContent) {
|
||||||
|
reason = this.dropdownFormGroup.querySelector("div.readonly").textContent.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.updateUserInterface(reason);
|
||||||
this.initializeDropdown();
|
this.initializeDropdown();
|
||||||
this.initializeModalConfirm();
|
this.initializeModalConfirm();
|
||||||
this.initializeDirectEditButton();
|
this.initializeDirectEditButton();
|
||||||
|
@ -600,7 +652,7 @@ class customRejectedEmail extends CustomizableEmailBase {
|
||||||
this.showPlaceholder("Email:", "Select a rejection reason to see email");
|
this.showPlaceholder("Email:", "Select a rejection reason to see email");
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUserInterface(reason=this.dropdown.value, excluded_reasons=[]) {
|
updateUserInterface(reason, excluded_reasons=[]) {
|
||||||
super.updateUserInterface(reason, excluded_reasons);
|
super.updateUserInterface(reason, excluded_reasons);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -619,12 +671,6 @@ export function initRejectedEmail() {
|
||||||
|
|
||||||
// Initialize UI
|
// Initialize UI
|
||||||
const customEmail = new customRejectedEmail();
|
const customEmail = new customRejectedEmail();
|
||||||
// Check that every variable was setup correctly
|
|
||||||
const nullItems = Object.entries(customEmail.config).filter(([key, value]) => value === null).map(([key]) => key);
|
|
||||||
if (nullItems.length > 0) {
|
|
||||||
console.error(`Failed to load customRejectedEmail(). Some variables were null: ${nullItems.join(", ")}`)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
customEmail.loadRejectedEmail()
|
customEmail.loadRejectedEmail()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -648,7 +694,6 @@ function handleSuborgFieldsAndButtons() {
|
||||||
|
|
||||||
// Ensure that every variable is present before proceeding
|
// Ensure that every variable is present before proceeding
|
||||||
if (!requestedSuborganizationField || !suborganizationCity || !suborganizationStateTerritory || !rejectButton) {
|
if (!requestedSuborganizationField || !suborganizationCity || !suborganizationStateTerritory || !rejectButton) {
|
||||||
console.warn("handleSuborganizationSelection() => Could not find required fields.")
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,9 @@ export function handlePortfolioSelection(
|
||||||
suborgDropdownSelector="#id_sub_organization"
|
suborgDropdownSelector="#id_sub_organization"
|
||||||
) {
|
) {
|
||||||
// These dropdown are select2 fields so they must be interacted with via jquery
|
// These dropdown are select2 fields so they must be interacted with via jquery
|
||||||
|
// In the event that these fields are readonly, need a variable to reference their row
|
||||||
const portfolioDropdown = django.jQuery(portfolioDropdownSelector);
|
const portfolioDropdown = django.jQuery(portfolioDropdownSelector);
|
||||||
|
const portfolioField = document.querySelector(".field-portfolio");
|
||||||
const suborganizationDropdown = django.jQuery(suborgDropdownSelector);
|
const suborganizationDropdown = django.jQuery(suborgDropdownSelector);
|
||||||
const suborganizationField = document.querySelector(".field-sub_organization");
|
const suborganizationField = document.querySelector(".field-sub_organization");
|
||||||
const requestedSuborganizationField = document.querySelector(".field-requested_suborganization");
|
const requestedSuborganizationField = document.querySelector(".field-requested_suborganization");
|
||||||
|
@ -394,17 +396,33 @@ export function handlePortfolioSelection(
|
||||||
* - Various global field elements (e.g., `suborganizationField`, `seniorOfficialField`, `portfolioOrgTypeFieldSet`) are used.
|
* - Various global field elements (e.g., `suborganizationField`, `seniorOfficialField`, `portfolioOrgTypeFieldSet`) are used.
|
||||||
*/
|
*/
|
||||||
function updatePortfolioFieldsDisplay() {
|
function updatePortfolioFieldsDisplay() {
|
||||||
|
let portfolio_id = null;
|
||||||
|
let portfolio_selected = false;
|
||||||
|
// portfolio will be either readonly or a dropdown, handle both cases
|
||||||
|
if (portfolioDropdown.length) { // need to test length since the query will always be defined, even if not in DOM
|
||||||
// Retrieve the selected portfolio ID
|
// Retrieve the selected portfolio ID
|
||||||
let portfolio_id = portfolioDropdown.val();
|
portfolio_id = portfolioDropdown.val();
|
||||||
|
|
||||||
if (portfolio_id) {
|
if (portfolio_id) {
|
||||||
|
portfolio_selected = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// get readonly field value
|
||||||
|
let portfolio = portfolioField.querySelector(".readonly").innerText;
|
||||||
|
if (portfolio != "-") {
|
||||||
|
portfolio_selected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (portfolio_selected) {
|
||||||
// A portfolio is selected - update suborganization dropdown and show/hide relevant fields
|
// A portfolio is selected - update suborganization dropdown and show/hide relevant fields
|
||||||
|
|
||||||
|
if (portfolio_id) {
|
||||||
// Update suborganization dropdown for the selected portfolio
|
// Update suborganization dropdown for the selected portfolio
|
||||||
updateSubOrganizationDropdown(portfolio_id);
|
updateSubOrganizationDropdown(portfolio_id);
|
||||||
|
}
|
||||||
|
|
||||||
// Show fields relevant to a selected portfolio
|
// Show fields relevant to a selected portfolio
|
||||||
showElement(suborganizationField);
|
if (suborganizationField) showElement(suborganizationField);
|
||||||
hideElement(seniorOfficialField);
|
hideElement(seniorOfficialField);
|
||||||
showElement(portfolioSeniorOfficialField);
|
showElement(portfolioSeniorOfficialField);
|
||||||
|
|
||||||
|
@ -427,7 +445,7 @@ export function handlePortfolioSelection(
|
||||||
// No portfolio is selected - reverse visibility of fields
|
// No portfolio is selected - reverse visibility of fields
|
||||||
|
|
||||||
// Hide suborganization field as no portfolio is selected
|
// Hide suborganization field as no portfolio is selected
|
||||||
hideElement(suborganizationField);
|
if (suborganizationField) hideElement(suborganizationField);
|
||||||
|
|
||||||
// Show fields that are relevant when no portfolio is selected
|
// Show fields that are relevant when no portfolio is selected
|
||||||
showElement(seniorOfficialField);
|
showElement(seniorOfficialField);
|
||||||
|
@ -468,10 +486,22 @@ export function handlePortfolioSelection(
|
||||||
* This function ensures the form dynamically reflects whether a specific suborganization is being selected or requested.
|
* This function ensures the form dynamically reflects whether a specific suborganization is being selected or requested.
|
||||||
*/
|
*/
|
||||||
function updateSuborganizationFieldsDisplay() {
|
function updateSuborganizationFieldsDisplay() {
|
||||||
let portfolio_id = portfolioDropdown.val();
|
let portfolio_selected = false;
|
||||||
|
// portfolio will be either readonly or a dropdown, handle both cases
|
||||||
|
if (portfolioDropdown.length) { // need to test length since the query will always be defined, even if not in DOM
|
||||||
|
// Retrieve the selected portfolio ID
|
||||||
|
if (portfolioDropdown.val()) {
|
||||||
|
portfolio_selected = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// get readonly field value
|
||||||
|
if (portfolioField.querySelector(".readonly").innerText != "-") {
|
||||||
|
portfolio_selected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
let suborganization_id = suborganizationDropdown.val();
|
let suborganization_id = suborganizationDropdown.val();
|
||||||
|
|
||||||
if (portfolio_id && !suborganization_id) {
|
if (portfolio_selected && !suborganization_id) {
|
||||||
// Show suborganization request fields
|
// Show suborganization request fields
|
||||||
if (requestedSuborganizationField) showElement(requestedSuborganizationField);
|
if (requestedSuborganizationField) showElement(requestedSuborganizationField);
|
||||||
if (suborganizationCity) showElement(suborganizationCity);
|
if (suborganizationCity) showElement(suborganizationCity);
|
||||||
|
|
|
@ -21,6 +21,8 @@ function handlePortfolioFields(){
|
||||||
const federalTypeField = document.querySelector(".field-federal_type");
|
const federalTypeField = document.querySelector(".field-federal_type");
|
||||||
const urbanizationField = document.querySelector(".field-urbanization");
|
const urbanizationField = document.querySelector(".field-urbanization");
|
||||||
const stateTerritoryDropdown = document.getElementById("id_state_territory");
|
const stateTerritoryDropdown = document.getElementById("id_state_territory");
|
||||||
|
const stateTerritoryField = document.querySelector(".field-state_territory");
|
||||||
|
const stateTerritoryReadonly = stateTerritoryField.querySelector(".readonly");
|
||||||
const seniorOfficialAddUrl = document.getElementById("senior-official-add-url").value;
|
const seniorOfficialAddUrl = document.getElementById("senior-official-add-url").value;
|
||||||
const seniorOfficialApi = document.getElementById("senior_official_from_agency_json_url").value;
|
const seniorOfficialApi = document.getElementById("senior_official_from_agency_json_url").value;
|
||||||
const federalPortfolioApi = document.getElementById("federal_and_portfolio_types_from_agency_json_url").value;
|
const federalPortfolioApi = document.getElementById("federal_and_portfolio_types_from_agency_json_url").value;
|
||||||
|
@ -85,9 +87,9 @@ function handlePortfolioFields(){
|
||||||
* 2. else show org name, hide federal agency, hide federal type if applicable
|
* 2. else show org name, hide federal agency, hide federal type if applicable
|
||||||
*/
|
*/
|
||||||
function handleOrganizationTypeChange() {
|
function handleOrganizationTypeChange() {
|
||||||
if (organizationTypeDropdown && organizationNameField) {
|
if (organizationTypeField && organizationNameField) {
|
||||||
let selectedValue = organizationTypeDropdown.value;
|
let selectedValue = organizationTypeDropdown ? organizationTypeDropdown.value : organizationTypeReadonly.innerText;
|
||||||
if (selectedValue === "federal") {
|
if (selectedValue === "federal" || selectedValue === "Federal") {
|
||||||
hideElement(organizationNameField);
|
hideElement(organizationNameField);
|
||||||
showElement(federalAgencyField);
|
showElement(federalAgencyField);
|
||||||
if (federalTypeField) {
|
if (federalTypeField) {
|
||||||
|
@ -207,8 +209,8 @@ function handlePortfolioFields(){
|
||||||
* Handle urbanization
|
* Handle urbanization
|
||||||
*/
|
*/
|
||||||
function handleStateTerritoryChange() {
|
function handleStateTerritoryChange() {
|
||||||
let selectedValue = stateTerritoryDropdown.value;
|
let selectedValue = stateTerritoryDropdown ? stateTerritoryDropdown.value : stateTerritoryReadonly.innerText;
|
||||||
if (selectedValue === "PR") {
|
if (selectedValue === "PR" || selectedValue === "Puerto Rico (PR)") {
|
||||||
showElement(urbanizationField)
|
showElement(urbanizationField)
|
||||||
} else {
|
} else {
|
||||||
hideElement(urbanizationField)
|
hideElement(urbanizationField)
|
||||||
|
@ -265,7 +267,7 @@ function handlePortfolioFields(){
|
||||||
* Initializes necessary data and display configurations for the portfolio fields.
|
* Initializes necessary data and display configurations for the portfolio fields.
|
||||||
*/
|
*/
|
||||||
function initializePortfolioSettings() {
|
function initializePortfolioSettings() {
|
||||||
if (urbanizationField && stateTerritoryDropdown) {
|
if (urbanizationField && stateTerritoryField) {
|
||||||
handleStateTerritoryChange();
|
handleStateTerritoryChange();
|
||||||
}
|
}
|
||||||
handleOrganizationTypeChange();
|
handleOrganizationTypeChange();
|
||||||
|
@ -285,10 +287,12 @@ function handlePortfolioFields(){
|
||||||
handleStateTerritoryChange();
|
handleStateTerritoryChange();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (organizationTypeDropdown) {
|
||||||
organizationTypeDropdown.addEventListener("change", function() {
|
organizationTypeDropdown.addEventListener("change", function() {
|
||||||
handleOrganizationTypeChange();
|
handleOrganizationTypeChange();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Run initial setup functions
|
// Run initial setup functions
|
||||||
initializePortfolioSettings();
|
initializePortfolioSettings();
|
||||||
|
|
|
@ -12,6 +12,9 @@ logger = logging.getLogger(__name__)
|
||||||
# Constants for clarity
|
# Constants for clarity
|
||||||
ALL = "all"
|
ALL = "all"
|
||||||
IS_STAFF = "is_staff"
|
IS_STAFF = "is_staff"
|
||||||
|
IS_CISA_ANALYST = "is_cisa_analyst"
|
||||||
|
IS_OMB_ANALYST = "is_omb_analyst"
|
||||||
|
IS_FULL_ACCESS = "is_full_access"
|
||||||
IS_DOMAIN_MANAGER = "is_domain_manager"
|
IS_DOMAIN_MANAGER = "is_domain_manager"
|
||||||
IS_DOMAIN_REQUEST_CREATOR = "is_domain_request_creator"
|
IS_DOMAIN_REQUEST_CREATOR = "is_domain_request_creator"
|
||||||
IS_STAFF_MANAGING_DOMAIN = "is_staff_managing_domain"
|
IS_STAFF_MANAGING_DOMAIN = "is_staff_managing_domain"
|
||||||
|
@ -108,6 +111,9 @@ def _user_has_permission(user, request, rules, **kwargs):
|
||||||
# Define permission checks
|
# Define permission checks
|
||||||
permission_checks = [
|
permission_checks = [
|
||||||
(IS_STAFF, lambda: user.is_staff),
|
(IS_STAFF, lambda: user.is_staff),
|
||||||
|
(IS_CISA_ANALYST, lambda: user.has_perm("registrar.analyst_access_permission")),
|
||||||
|
(IS_OMB_ANALYST, lambda: user.groups.filter(name="omb_analysts_group").exists()),
|
||||||
|
(IS_FULL_ACCESS, lambda: user.has_perm("registrar.full_access_permission")),
|
||||||
(
|
(
|
||||||
IS_DOMAIN_MANAGER,
|
IS_DOMAIN_MANAGER,
|
||||||
lambda: (not user.is_org_user(request) and _is_domain_manager(user, **kwargs))
|
lambda: (not user.is_org_user(request) and _is_domain_manager(user, **kwargs))
|
||||||
|
|
38
src/registrar/migrations/0143_create_groups_v18.py
Normal file
38
src/registrar/migrations/0143_create_groups_v18.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
|
||||||
|
# It is dependent on 0079 (which populates federal agencies)
|
||||||
|
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
|
||||||
|
# in the user_group model then:
|
||||||
|
# [NOT RECOMMENDED]
|
||||||
|
# step 1: docker-compose exec app ./manage.py migrate --fake registrar 0035_contenttypes_permissions
|
||||||
|
# step 2: docker-compose exec app ./manage.py migrate registrar 0036_create_groups
|
||||||
|
# step 3: fake run the latest migration in the migrations list
|
||||||
|
# [RECOMMENDED]
|
||||||
|
# Alternatively:
|
||||||
|
# step 1: duplicate the migration that loads data
|
||||||
|
# step 2: docker-compose exec app ./manage.py migrate
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from registrar.models import UserGroup
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
# For linting: RunPython expects a function reference,
|
||||||
|
# so let's give it one
|
||||||
|
def create_groups(apps, schema_editor) -> Any:
|
||||||
|
UserGroup.create_cisa_analyst_group(apps, schema_editor)
|
||||||
|
UserGroup.create_omb_analyst_group(apps, schema_editor)
|
||||||
|
UserGroup.create_full_access_group(apps, schema_editor)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0142_domainrequest_feb_naming_requirements_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(
|
||||||
|
create_groups,
|
||||||
|
reverse_code=migrations.RunPython.noop,
|
||||||
|
atomic=True,
|
||||||
|
),
|
||||||
|
]
|
|
@ -449,7 +449,9 @@ class DomainInformation(TimeStampedModel):
|
||||||
def converted_federal_type(self):
|
def converted_federal_type(self):
|
||||||
if self.portfolio:
|
if self.portfolio:
|
||||||
return self.portfolio.federal_type
|
return self.portfolio.federal_type
|
||||||
return self.federal_type
|
elif self.federal_agency:
|
||||||
|
return self.federal_agency.federal_type
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def converted_senior_official(self):
|
def converted_senior_official(self):
|
||||||
|
|
|
@ -1505,7 +1505,9 @@ class DomainRequest(TimeStampedModel):
|
||||||
def converted_federal_type(self):
|
def converted_federal_type(self):
|
||||||
if self.portfolio:
|
if self.portfolio:
|
||||||
return self.portfolio.federal_type
|
return self.portfolio.federal_type
|
||||||
return self.federal_type
|
elif self.federal_agency:
|
||||||
|
return self.federal_agency.federal_type
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def converted_address_line1(self):
|
def converted_address_line1(self):
|
||||||
|
|
|
@ -141,6 +141,99 @@ class UserGroup(Group):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error creating analyst permissions group: {e}")
|
logger.error(f"Error creating analyst permissions group: {e}")
|
||||||
|
|
||||||
|
def create_omb_analyst_group(apps, schema_editor):
|
||||||
|
"""This method gets run from a data migration."""
|
||||||
|
|
||||||
|
# Hard to pass self to these methods as the calls from migrations
|
||||||
|
# are only expecting apps and schema_editor, so we'll just define
|
||||||
|
# apps, schema_editor in the local scope instead
|
||||||
|
OMB_ANALYST_GROUP_PERMISSIONS = [
|
||||||
|
{
|
||||||
|
"app_label": "registrar",
|
||||||
|
"model": "domainrequest",
|
||||||
|
"permissions": ["change_domainrequest"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"app_label": "registrar",
|
||||||
|
"model": "domain",
|
||||||
|
"permissions": ["view_domain"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"app_label": "registrar",
|
||||||
|
"model": "domaininvitation",
|
||||||
|
"permissions": ["view_domaininvitation"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"app_label": "registrar",
|
||||||
|
"model": "federalagency",
|
||||||
|
"permissions": ["view_federalagency"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"app_label": "registrar",
|
||||||
|
"model": "portfolio",
|
||||||
|
"permissions": ["view_portfolio"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"app_label": "registrar",
|
||||||
|
"model": "suborganization",
|
||||||
|
"permissions": ["view_suborganization"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"app_label": "registrar",
|
||||||
|
"model": "seniorofficial",
|
||||||
|
"permissions": ["view_seniorofficial"],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Avoid error: You can't execute queries until the end
|
||||||
|
# of the 'atomic' block.
|
||||||
|
# From django docs:
|
||||||
|
# https://docs.djangoproject.com/en/4.2/topics/migrations/#data-migrations
|
||||||
|
# We can’t import the Person model directly as it may be a newer
|
||||||
|
# version than this migration expects. We use the historical version.
|
||||||
|
ContentType = apps.get_model("contenttypes", "ContentType")
|
||||||
|
Permission = apps.get_model("auth", "Permission")
|
||||||
|
UserGroup = apps.get_model("registrar", "UserGroup")
|
||||||
|
|
||||||
|
logger.info("Going to create the OMB Analyst Group")
|
||||||
|
try:
|
||||||
|
omb_analysts_group, _ = UserGroup.objects.get_or_create(
|
||||||
|
name="omb_analysts_group",
|
||||||
|
)
|
||||||
|
|
||||||
|
omb_analysts_group.permissions.clear()
|
||||||
|
|
||||||
|
for permission in OMB_ANALYST_GROUP_PERMISSIONS:
|
||||||
|
app_label = permission["app_label"]
|
||||||
|
model_name = permission["model"]
|
||||||
|
permissions = permission["permissions"]
|
||||||
|
|
||||||
|
# Retrieve the content type for the app and model
|
||||||
|
content_type = ContentType.objects.get(app_label=app_label, model=model_name)
|
||||||
|
|
||||||
|
# Retrieve the permissions based on their codenames
|
||||||
|
permissions = Permission.objects.filter(content_type=content_type, codename__in=permissions)
|
||||||
|
|
||||||
|
# Assign the permissions to the group
|
||||||
|
omb_analysts_group.permissions.add(*permissions)
|
||||||
|
|
||||||
|
# Convert the permissions QuerySet to a list of codenames
|
||||||
|
permission_list = list(permissions.values_list("codename", flat=True))
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
app_label
|
||||||
|
+ " | "
|
||||||
|
+ model_name
|
||||||
|
+ " | "
|
||||||
|
+ ", ".join(permission_list)
|
||||||
|
+ " added to group "
|
||||||
|
+ omb_analysts_group.name
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.debug("OMB Analyst permissions added to group " + omb_analysts_group.name)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error creating analyst permissions group: {e}")
|
||||||
|
|
||||||
def create_full_access_group(apps, schema_editor):
|
def create_full_access_group(apps, schema_editor):
|
||||||
"""This method gets run from a data migration."""
|
"""This method gets run from a data migration."""
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,7 @@
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% if perms.registrar.analyst_access_permission or perms.full_access_permission %}
|
||||||
<div class="module">
|
<div class="module">
|
||||||
<table class="width-full">
|
<table class="width-full">
|
||||||
<caption class="text-bold">Analytics</caption>
|
<caption class="text-bold">Analytics</caption>
|
||||||
|
@ -78,6 +79,7 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>{% translate 'You don’t have permission to view or edit anything.' %}</p>
|
<p>{% translate 'You don’t have permission to view or edit anything.' %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -11,13 +11,15 @@
|
||||||
{% block field_sets %}
|
{% block field_sets %}
|
||||||
<div class="display-flex flex-row flex-justify submit-row">
|
<div class="display-flex flex-row flex-justify submit-row">
|
||||||
<div class="flex-align-self-start button-list-mobile">
|
<div class="flex-align-self-start button-list-mobile">
|
||||||
|
{% if not adminform.form.is_omb_analyst %}
|
||||||
<input id="manageDomainSubmitButton" type="submit" value="Manage domain" name="_edit_domain">
|
<input id="manageDomainSubmitButton" type="submit" value="Manage domain" name="_edit_domain">
|
||||||
{# Dja has margin styles defined on inputs as is. Lets work with it, rather than fight it. #}
|
{# Dja has margin styles defined on inputs as is. Lets work with it, rather than fight it. #}
|
||||||
<span class="mini-spacer"></span>
|
<span class="mini-spacer"></span>
|
||||||
<input type="submit" value="Get registry status" name="_get_status">
|
<input type="submit" value="Get registry status" name="_get_status">
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="desktop:flex-align-self-end">
|
<div class="desktop:flex-align-self-end">
|
||||||
{% if original.state != original.State.DELETED %}
|
{% if original.state != original.State.DELETED and not adminform.form.is_omb_analyst %}
|
||||||
<a class="text-middle" href="#toggle-extend-expiration-alert" aria-controls="toggle-extend-expiration-alert" data-open-modal>
|
<a class="text-middle" href="#toggle-extend-expiration-alert" aria-controls="toggle-extend-expiration-alert" data-open-modal>
|
||||||
Extend expiration date
|
Extend expiration date
|
||||||
</a>
|
</a>
|
||||||
|
@ -31,9 +33,11 @@
|
||||||
<input type="submit" value="Remove hold" name="_remove_client_hold" class="custom-link-button">
|
<input type="submit" value="Remove hold" name="_remove_client_hold" class="custom-link-button">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if original.state == original.State.READY or original.state == original.State.ON_HOLD %}
|
{% if original.state == original.State.READY or original.state == original.State.ON_HOLD %}
|
||||||
|
{% if not adminform.form.is_omb_analyst %}
|
||||||
<span class="margin-left-05 margin-right-05 text-middle"> | </span>
|
<span class="margin-left-05 margin-right-05 text-middle"> | </span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if original.state != original.State.DELETED %}
|
{% endif %}
|
||||||
|
{% if original.state != original.State.DELETED and not adminform.form.is_omb_analyst %}
|
||||||
<a class="text-middle" href="#toggle-remove-from-registry" aria-controls="toggle-remove-from-registry" data-open-modal>
|
<a class="text-middle" href="#toggle-remove-from-registry" aria-controls="toggle-remove-from-registry" data-open-modal>
|
||||||
Remove from registry
|
Remove from registry
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -6,7 +6,11 @@
|
||||||
|
|
||||||
{% if show_formatted_name %}
|
{% if show_formatted_name %}
|
||||||
{% if user.get_formatted_name %}
|
{% if user.get_formatted_name %}
|
||||||
|
{% if adminform.form.show_contact_as_plain_text %}
|
||||||
|
{{ user.get_formatted_name }}
|
||||||
|
{% else %}
|
||||||
<a class="contact_info_name" href="{% url 'admin:registrar_contact_change' user.id %}">{{ user.get_formatted_name }}</a>
|
<a class="contact_info_name" href="{% url 'admin:registrar_contact_change' user.id %}">{{ user.get_formatted_name }}</a>
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
None
|
None
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -69,7 +69,11 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html)
|
||||||
{% elif field.field.name == "portfolio_senior_official" %}
|
{% elif field.field.name == "portfolio_senior_official" %}
|
||||||
<div class="readonly">
|
<div class="readonly">
|
||||||
{% if original_object.portfolio.senior_official %}
|
{% if original_object.portfolio.senior_official %}
|
||||||
|
{% if adminform.form.show_contact_as_plain_text %}
|
||||||
|
{{ field.contents|striptags }}
|
||||||
|
{% else %}
|
||||||
<a href="{% url 'admin:registrar_seniorofficial_change' original_object.portfolio.senior_official.id %}">{{ field.contents }}</a>
|
<a href="{% url 'admin:registrar_seniorofficial_change' original_object.portfolio.senior_official.id %}">{{ field.contents }}</a>
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
No senior official found.<br>
|
No senior official found.<br>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -78,7 +82,11 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html)
|
||||||
{% if all_contacts.count > 2 %}
|
{% if all_contacts.count > 2 %}
|
||||||
<div class="readonly">
|
<div class="readonly">
|
||||||
{% for contact in all_contacts %}
|
{% for contact in all_contacts %}
|
||||||
|
{% if adminform.form.show_contact_as_plain_text %}
|
||||||
|
{{ contact.get_formatted_name }}{% if not forloop.last %}, {% endif %}
|
||||||
|
{% else %}
|
||||||
<a href="{% url 'admin:registrar_contact_change' contact.id %}">{{ contact.get_formatted_name }}</a>{% if not forloop.last %}, {% endif %}
|
<a href="{% url 'admin:registrar_contact_change' contact.id %}">{{ contact.get_formatted_name }}</a>{% if not forloop.last %}, {% endif %}
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -153,6 +161,10 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html)
|
||||||
<p>No additional members found.</p>
|
<p>No additional members found.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% elif field.field.name == "creator" and adminform.form.show_contact_as_plain_text %}
|
||||||
|
<div class="readonly">{{ field.contents|striptags }}</div>
|
||||||
|
{% elif field.field.name == "senior_official" and adminform.form.show_contact_as_plain_text %}
|
||||||
|
<div class="readonly">{{ field.contents|striptags }}</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="readonly">{{ field.contents }}</div>
|
<div class="readonly">{{ field.contents }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -16,7 +16,11 @@
|
||||||
{% for admin in admins %}
|
{% for admin in admins %}
|
||||||
{% url 'admin:registrar_userportfoliopermission_change' admin.pk as url %}
|
{% url 'admin:registrar_userportfoliopermission_change' admin.pk as url %}
|
||||||
<tr>
|
<tr>
|
||||||
|
{% if adminform.form.is_omb_analyst %}
|
||||||
|
<td>{{ admin.user.get_formatted_name }}</td>
|
||||||
|
{% else %}
|
||||||
<td><a href={{url}}>{{ admin.user.get_formatted_name}}</a></td>
|
<td><a href={{url}}>{{ admin.user.get_formatted_name}}</a></td>
|
||||||
|
{% endif %}
|
||||||
<td>{{ admin.user.title }}</td>
|
<td>{{ admin.user.title }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if admin.user.email %}
|
{% if admin.user.email %}
|
||||||
|
|
|
@ -30,6 +30,9 @@
|
||||||
<a href={{ url }}>No senior official found. Create one now.</a>
|
<a href={{ url }}>No senior official found. Create one now.</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% elif field.field.name == "creator" and adminform.form.show_contact_as_plain_text %}
|
||||||
|
<div class="readonly">{{ field.contents|striptags }}</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="readonly">{{ field.contents }}</div>
|
<div class="readonly">{{ field.contents }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -1010,6 +1010,27 @@ def create_user(**kwargs):
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
def create_omb_analyst_user(**kwargs):
|
||||||
|
"""Creates a analyst user with is_staff=True and the group cisa_analysts_group"""
|
||||||
|
User = get_user_model()
|
||||||
|
p = "userpass"
|
||||||
|
user = User.objects.create_user(
|
||||||
|
username=kwargs.get("username", "ombanalystuser"),
|
||||||
|
email=kwargs.get("email", "ombanalyst@example.com"),
|
||||||
|
first_name=kwargs.get("first_name", "first"),
|
||||||
|
last_name=kwargs.get("last_name", "last"),
|
||||||
|
is_staff=kwargs.get("is_staff", True),
|
||||||
|
title=kwargs.get("title", "title"),
|
||||||
|
password=kwargs.get("password", p),
|
||||||
|
phone=kwargs.get("phone", "8003111234"),
|
||||||
|
)
|
||||||
|
# Retrieve the group or create it if it doesn't exist
|
||||||
|
group, _ = UserGroup.objects.get_or_create(name="omb_analysts_group")
|
||||||
|
# Add the user to the group
|
||||||
|
user.groups.set([group])
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
def create_test_user():
|
def create_test_user():
|
||||||
username = "test_user"
|
username = "test_user"
|
||||||
first_name = "First"
|
first_name = "First"
|
||||||
|
|
|
@ -3,6 +3,7 @@ from django.utils import timezone
|
||||||
from django.test import TestCase, RequestFactory, Client
|
from django.test import TestCase, RequestFactory, Client
|
||||||
from django.contrib.admin.sites import AdminSite
|
from django.contrib.admin.sites import AdminSite
|
||||||
from registrar import models
|
from registrar import models
|
||||||
|
from registrar.utility.constants import BranchChoices
|
||||||
from registrar.utility.email import EmailSendingError
|
from registrar.utility.email import EmailSendingError
|
||||||
from registrar.utility.errors import MissingEmailError
|
from registrar.utility.errors import MissingEmailError
|
||||||
from waffle.testutils import override_flag
|
from waffle.testutils import override_flag
|
||||||
|
@ -57,6 +58,7 @@ from .common import (
|
||||||
MockDbForSharedTests,
|
MockDbForSharedTests,
|
||||||
AuditedAdminMockData,
|
AuditedAdminMockData,
|
||||||
completed_domain_request,
|
completed_domain_request,
|
||||||
|
create_omb_analyst_user,
|
||||||
create_test_user,
|
create_test_user,
|
||||||
generic_domain_object,
|
generic_domain_object,
|
||||||
less_console_noise,
|
less_console_noise,
|
||||||
|
@ -136,18 +138,25 @@ class TestDomainInvitationAdmin(WebTest):
|
||||||
csrf_checks = False
|
csrf_checks = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(self):
|
def setUpClass(cls):
|
||||||
super().setUpClass()
|
super().setUpClass()
|
||||||
self.site = AdminSite()
|
cls.site = AdminSite()
|
||||||
self.factory = RequestFactory()
|
cls.factory = RequestFactory()
|
||||||
self.superuser = create_superuser()
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
self.superuser = create_superuser()
|
||||||
|
self.cisa_analyst = create_user()
|
||||||
|
self.omb_analyst = create_omb_analyst_user()
|
||||||
self.admin = ListHeaderAdmin(model=DomainInvitationAdmin, admin_site=AdminSite())
|
self.admin = ListHeaderAdmin(model=DomainInvitationAdmin, admin_site=AdminSite())
|
||||||
self.domain = Domain.objects.create(name="example.com")
|
self.domain = Domain.objects.create(name="example.com")
|
||||||
|
self.fed_agency = FederalAgency.objects.create(
|
||||||
|
agency="New FedExec Agency", federal_type=BranchChoices.EXECUTIVE
|
||||||
|
)
|
||||||
self.portfolio = Portfolio.objects.create(organization_name="new portfolio", creator=self.superuser)
|
self.portfolio = Portfolio.objects.create(organization_name="new portfolio", creator=self.superuser)
|
||||||
DomainInformation.objects.create(domain=self.domain, portfolio=self.portfolio, creator=self.superuser)
|
self.domain_info = DomainInformation.objects.create(
|
||||||
|
domain=self.domain, portfolio=self.portfolio, creator=self.superuser
|
||||||
|
)
|
||||||
"""Create a client object"""
|
"""Create a client object"""
|
||||||
self.client = Client(HTTP_HOST="localhost:8080")
|
self.client = Client(HTTP_HOST="localhost:8080")
|
||||||
self.client.force_login(self.superuser)
|
self.client.force_login(self.superuser)
|
||||||
|
@ -159,10 +168,124 @@ class TestDomainInvitationAdmin(WebTest):
|
||||||
DomainInvitation.objects.all().delete()
|
DomainInvitation.objects.all().delete()
|
||||||
DomainInformation.objects.all().delete()
|
DomainInformation.objects.all().delete()
|
||||||
Portfolio.objects.all().delete()
|
Portfolio.objects.all().delete()
|
||||||
|
self.fed_agency.delete()
|
||||||
Domain.objects.all().delete()
|
Domain.objects.all().delete()
|
||||||
Contact.objects.all().delete()
|
Contact.objects.all().delete()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_analyst_view(self):
|
||||||
|
"""Ensure regular analysts can view domain invitations."""
|
||||||
|
invitation = DomainInvitation.objects.create(email="test@example.com", domain=self.domain)
|
||||||
|
self.client.force_login(self.cisa_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_domaininvitation_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, invitation.email)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_view_non_feb_domain(self):
|
||||||
|
"""Ensure OMB analysts cannot view non-federal domains."""
|
||||||
|
invitation = DomainInvitation.objects.create(email="test@example.com", domain=self.domain)
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_domaininvitation_changelist"))
|
||||||
|
self.assertNotContains(response, invitation.email)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_view_feb_domain(self):
|
||||||
|
"""Ensure OMB analysts can view federal executive branch domains."""
|
||||||
|
invitation = DomainInvitation.objects.create(email="test@example.com", domain=self.domain)
|
||||||
|
self.portfolio.organization_type = DomainRequest.OrganizationChoices.FEDERAL
|
||||||
|
self.portfolio.federal_agency = self.fed_agency
|
||||||
|
self.portfolio.save()
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_domaininvitation_changelist"))
|
||||||
|
self.assertContains(response, invitation.email)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_superuser_view(self):
|
||||||
|
"""Ensure superusers can view domain invitations."""
|
||||||
|
invitation = DomainInvitation.objects.create(email="test@example.com", domain=self.domain)
|
||||||
|
response = self.client.get(reverse("admin:registrar_domaininvitation_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, invitation.email)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_analyst_change(self):
|
||||||
|
"""Ensure regular analysts can view domain invitations but not update."""
|
||||||
|
invitation = DomainInvitation.objects.create(email="test@example.com", domain=self.domain)
|
||||||
|
self.client.force_login(self.cisa_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_domaininvitation_change", args=[invitation.id]))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, invitation.email)
|
||||||
|
# test whether fields are readonly or editable
|
||||||
|
self.assertNotContains(response, "id_domain")
|
||||||
|
self.assertNotContains(response, "id_email")
|
||||||
|
self.assertContains(response, "closelink")
|
||||||
|
self.assertNotContains(response, "Save")
|
||||||
|
self.assertNotContains(response, "Delete")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_change_non_feb_domain(self):
|
||||||
|
"""Ensure OMB analysts cannot change non-federal domains."""
|
||||||
|
invitation = DomainInvitation.objects.create(email="test@example.com", domain=self.domain)
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_domaininvitation_change", args=[invitation.id]))
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_change_feb_domain(self):
|
||||||
|
"""Ensure OMB analysts can view federal executive branch domains."""
|
||||||
|
invitation = DomainInvitation.objects.create(email="test@example.com", domain=self.domain)
|
||||||
|
# update domain
|
||||||
|
self.portfolio.organization_type = DomainRequest.OrganizationChoices.FEDERAL
|
||||||
|
self.portfolio.federal_agency = self.fed_agency
|
||||||
|
self.portfolio.save()
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_domaininvitation_change", args=[invitation.id]))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, invitation.email)
|
||||||
|
# test whether fields are readonly or editable
|
||||||
|
self.assertNotContains(response, "id_domain")
|
||||||
|
self.assertNotContains(response, "id_email")
|
||||||
|
self.assertContains(response, "closelink")
|
||||||
|
self.assertNotContains(response, "Save")
|
||||||
|
self.assertNotContains(response, "Delete")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_superuser_change(self):
|
||||||
|
"""Ensure superusers can change domain invitations."""
|
||||||
|
invitation = DomainInvitation.objects.create(email="test@example.com", domain=self.domain)
|
||||||
|
response = self.client.get(reverse("admin:registrar_domaininvitation_change", args=[invitation.id]))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, invitation.email)
|
||||||
|
# test whether fields are readonly or editable
|
||||||
|
self.assertContains(response, "id_domain")
|
||||||
|
self.assertContains(response, "id_email")
|
||||||
|
self.assertNotContains(response, "closelink")
|
||||||
|
self.assertContains(response, "Save")
|
||||||
|
self.assertContains(response, "Delete")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_filter_feb_domain(self):
|
||||||
|
"""Ensure OMB analysts can apply filters and only federal executive branch domains show."""
|
||||||
|
# create invitation on domain that is not FEB
|
||||||
|
invitation = DomainInvitation.objects.create(email="test@example.com", domain=self.domain)
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("admin:registrar_domaininvitation_changelist"),
|
||||||
|
{"status": DomainInvitation.DomainInvitationStatus.INVITED},
|
||||||
|
)
|
||||||
|
self.assertNotContains(response, invitation.email)
|
||||||
|
# update domain
|
||||||
|
self.portfolio.organization_type = DomainRequest.OrganizationChoices.FEDERAL
|
||||||
|
self.portfolio.federal_agency = self.fed_agency
|
||||||
|
self.portfolio.save()
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("admin:registrar_domaininvitation_changelist"),
|
||||||
|
{"status": DomainInvitation.DomainInvitationStatus.INVITED},
|
||||||
|
)
|
||||||
|
self.assertContains(response, invitation.email)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_has_model_description(self):
|
def test_has_model_description(self):
|
||||||
"""Tests if this model has a model description on the table view"""
|
"""Tests if this model has a model description on the table view"""
|
||||||
|
@ -1139,6 +1262,7 @@ class TestUserPortfolioPermissionAdmin(TestCase):
|
||||||
self.client = Client(HTTP_HOST="localhost:8080")
|
self.client = Client(HTTP_HOST="localhost:8080")
|
||||||
self.superuser = create_superuser()
|
self.superuser = create_superuser()
|
||||||
self.testuser = create_test_user()
|
self.testuser = create_test_user()
|
||||||
|
self.omb_analyst = create_omb_analyst_user()
|
||||||
self.portfolio = Portfolio.objects.create(organization_name="Test Portfolio", creator=self.superuser)
|
self.portfolio = Portfolio.objects.create(organization_name="Test Portfolio", creator=self.superuser)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
@ -1148,6 +1272,26 @@ class TestUserPortfolioPermissionAdmin(TestCase):
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
UserPortfolioPermission.objects.all().delete()
|
UserPortfolioPermission.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_view(self):
|
||||||
|
"""Ensure OMB analysts cannot view user portfolio permissions list."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_userportfoliopermission_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_change(self):
|
||||||
|
"""Ensure OMB analysts cannot change user portfolio permission."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
user_portfolio_permission, _ = UserPortfolioPermission.objects.get_or_create(
|
||||||
|
user=self.superuser, portfolio=self.portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
|
||||||
|
)
|
||||||
|
response = self.client.get(
|
||||||
|
"/admin/registrar/userportfoliopermission/{}/change/".format(user_portfolio_permission.pk),
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_has_change_form_description(self):
|
def test_has_change_form_description(self):
|
||||||
"""Tests if this model has a model description on the change form view"""
|
"""Tests if this model has a model description on the change form view"""
|
||||||
|
@ -1204,6 +1348,7 @@ class TestPortfolioInvitationAdmin(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Create a client object"""
|
"""Create a client object"""
|
||||||
self.client = Client(HTTP_HOST="localhost:8080")
|
self.client = Client(HTTP_HOST="localhost:8080")
|
||||||
|
self.omb_analyst = create_omb_analyst_user()
|
||||||
self.portfolio = Portfolio.objects.create(organization_name="Test Portfolio", creator=self.superuser)
|
self.portfolio = Portfolio.objects.create(organization_name="Test Portfolio", creator=self.superuser)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
@ -1217,6 +1362,26 @@ class TestPortfolioInvitationAdmin(TestCase):
|
||||||
def tearDownClass(self):
|
def tearDownClass(self):
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_view(self):
|
||||||
|
"""Ensure OMB analysts cannot view portfolio invitations list."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_portfolioinvitation_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_change(self):
|
||||||
|
"""Ensure OMB analysts cannot change portfolio invitation."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
invitation, _ = PortfolioInvitation.objects.get_or_create(
|
||||||
|
email=self.superuser.email, portfolio=self.portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
|
||||||
|
)
|
||||||
|
response = self.client.get(
|
||||||
|
"/admin/registrar/portfolioinvitation/{}/change/".format(invitation.pk),
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_has_model_description(self):
|
def test_has_model_description(self):
|
||||||
"""Tests if this model has a model description on the table view"""
|
"""Tests if this model has a model description on the table view"""
|
||||||
|
@ -1791,6 +1956,8 @@ class TestHostAdmin(TestCase):
|
||||||
cls.factory = RequestFactory()
|
cls.factory = RequestFactory()
|
||||||
cls.admin = MyHostAdmin(model=Host, admin_site=cls.site)
|
cls.admin = MyHostAdmin(model=Host, admin_site=cls.site)
|
||||||
cls.superuser = create_superuser()
|
cls.superuser = create_superuser()
|
||||||
|
cls.staffuser = create_user()
|
||||||
|
cls.omb_analyst = create_omb_analyst_user()
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Setup environment for a mock admin user"""
|
"""Setup environment for a mock admin user"""
|
||||||
|
@ -1806,6 +1973,20 @@ class TestHostAdmin(TestCase):
|
||||||
def tearDownClass(cls):
|
def tearDownClass(cls):
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_analyst_view(self):
|
||||||
|
"""Ensure analysts cannot view hosts list."""
|
||||||
|
self.client.force_login(self.staffuser)
|
||||||
|
response = self.client.get(reverse("admin:registrar_host_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_view(self):
|
||||||
|
"""Ensure OMB analysts cannot view hosts list."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_host_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_has_model_description(self):
|
def test_has_model_description(self):
|
||||||
"""Tests if this model has a model description on the table view"""
|
"""Tests if this model has a model description on the table view"""
|
||||||
|
@ -1870,6 +2051,7 @@ class TestDomainInformationAdmin(TestCase):
|
||||||
cls.admin = DomainInformationAdmin(model=DomainInformation, admin_site=cls.site)
|
cls.admin = DomainInformationAdmin(model=DomainInformation, admin_site=cls.site)
|
||||||
cls.superuser = create_superuser()
|
cls.superuser = create_superuser()
|
||||||
cls.staffuser = create_user()
|
cls.staffuser = create_user()
|
||||||
|
cls.omb_analyst = create_omb_analyst_user()
|
||||||
cls.mock_data_generator = AuditedAdminMockData()
|
cls.mock_data_generator = AuditedAdminMockData()
|
||||||
cls.test_helper = GenericTestHelper(
|
cls.test_helper = GenericTestHelper(
|
||||||
factory=cls.factory,
|
factory=cls.factory,
|
||||||
|
@ -1881,12 +2063,24 @@ class TestDomainInformationAdmin(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = Client(HTTP_HOST="localhost:8080")
|
self.client = Client(HTTP_HOST="localhost:8080")
|
||||||
|
self.nonfeddomain = Domain.objects.create(name="nonfeddomain.com")
|
||||||
|
self.feddomain = Domain.objects.create(name="feddomain.com")
|
||||||
|
self.fed_agency = FederalAgency.objects.create(
|
||||||
|
agency="New FedExec Agency", federal_type=BranchChoices.EXECUTIVE
|
||||||
|
)
|
||||||
|
self.portfolio = Portfolio.objects.create(organization_name="new portfolio", creator=self.superuser)
|
||||||
|
self.domain_info = DomainInformation.objects.create(
|
||||||
|
domain=self.feddomain, portfolio=self.portfolio, creator=self.superuser
|
||||||
|
)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
"""Delete all Users, Domains, and UserDomainRoles"""
|
"""Delete all Users, Domains, and UserDomainRoles"""
|
||||||
DomainInformation.objects.all().delete()
|
DomainInformation.objects.all().delete()
|
||||||
DomainRequest.objects.all().delete()
|
DomainRequest.objects.all().delete()
|
||||||
Domain.objects.all().delete()
|
Domain.objects.all().delete()
|
||||||
|
DomainInformation.objects.all().delete()
|
||||||
|
Portfolio.objects.all().delete()
|
||||||
|
self.fed_agency.delete()
|
||||||
Contact.objects.all().delete()
|
Contact.objects.all().delete()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -1894,6 +2088,56 @@ class TestDomainInformationAdmin(TestCase):
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
SeniorOfficial.objects.all().delete()
|
SeniorOfficial.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_analyst_view(self):
|
||||||
|
"""Ensure regular analysts cannot view domain information list."""
|
||||||
|
self.client.force_login(self.staffuser)
|
||||||
|
response = self.client.get(reverse("admin:registrar_domaininformation_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_view(self):
|
||||||
|
"""Ensure OMB analysts cannot view domain information list."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_domaininformation_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_superuser_view(self):
|
||||||
|
"""Ensure superusers can view domain information list."""
|
||||||
|
self.client.force_login(self.superuser)
|
||||||
|
response = self.client.get(reverse("admin:registrar_domaininformation_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, self.feddomain.name)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_analyst_change(self):
|
||||||
|
"""Ensure regular analysts cannot view/edit domain information directly."""
|
||||||
|
self.client.force_login(self.staffuser)
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("admin:registrar_domaininformation_change", args=[self.feddomain.domain_info.id])
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_change(self):
|
||||||
|
"""Ensure OMB analysts cannot view/edit domain information directly."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("admin:registrar_domaininformation_change", args=[self.feddomain.domain_info.id])
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_superuser_change(self):
|
||||||
|
"""Ensure superusers can view/change domain information directly."""
|
||||||
|
self.client.force_login(self.superuser)
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("admin:registrar_domaininformation_change", args=[self.feddomain.domain_info.id])
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, self.feddomain.name)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_domain_information_senior_official_is_alphabetically_sorted(self):
|
def test_domain_information_senior_official_is_alphabetically_sorted(self):
|
||||||
"""Tests if the senior offical dropdown is alphanetically sorted in the django admin display"""
|
"""Tests if the senior offical dropdown is alphanetically sorted in the django admin display"""
|
||||||
|
@ -2258,6 +2502,8 @@ class TestUserDomainRoleAdmin(WebTest):
|
||||||
cls.factory = RequestFactory()
|
cls.factory = RequestFactory()
|
||||||
cls.admin = UserDomainRoleAdmin(model=UserDomainRole, admin_site=cls.site)
|
cls.admin = UserDomainRoleAdmin(model=UserDomainRole, admin_site=cls.site)
|
||||||
cls.superuser = create_superuser()
|
cls.superuser = create_superuser()
|
||||||
|
cls.staffuser = create_user()
|
||||||
|
cls.omb_analyst = create_omb_analyst_user()
|
||||||
cls.test_helper = GenericTestHelper(
|
cls.test_helper = GenericTestHelper(
|
||||||
factory=cls.factory,
|
factory=cls.factory,
|
||||||
user=cls.superuser,
|
user=cls.superuser,
|
||||||
|
@ -2285,6 +2531,31 @@ class TestUserDomainRoleAdmin(WebTest):
|
||||||
super().tearDownClass()
|
super().tearDownClass()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_analyst_view(self):
|
||||||
|
"""Ensure analysts cannot view user domain roles list."""
|
||||||
|
self.client.force_login(self.staffuser)
|
||||||
|
response = self.client.get(reverse("admin:registrar_userdomainrole_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_view(self):
|
||||||
|
"""Ensure OMB analysts cannot view user domain roles list."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_userdomainrole_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_change(self):
|
||||||
|
"""Ensure OMB analysts cannot view/edit user domain roles list."""
|
||||||
|
domain, _ = Domain.objects.get_or_create(name="anyrandomdomain.com")
|
||||||
|
user_domain_role, _ = UserDomainRole.objects.get_or_create(
|
||||||
|
user=self.superuser, domain=domain, role=[UserDomainRole.Roles.MANAGER]
|
||||||
|
)
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_userdomainrole_change", args=[user_domain_role.id]))
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_has_model_description(self):
|
def test_has_model_description(self):
|
||||||
"""Tests if this model has a model description on the table view"""
|
"""Tests if this model has a model description on the table view"""
|
||||||
|
@ -2580,6 +2851,7 @@ class TestMyUserAdmin(MockDbForSharedTests, WebTest):
|
||||||
cls.admin = MyUserAdmin(model=get_user_model(), admin_site=admin_site)
|
cls.admin = MyUserAdmin(model=get_user_model(), admin_site=admin_site)
|
||||||
cls.superuser = create_superuser()
|
cls.superuser = create_superuser()
|
||||||
cls.staffuser = create_user()
|
cls.staffuser = create_user()
|
||||||
|
cls.omb_analyst = create_omb_analyst_user()
|
||||||
cls.test_helper = GenericTestHelper(admin=cls.admin)
|
cls.test_helper = GenericTestHelper(admin=cls.admin)
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -2596,6 +2868,13 @@ class TestMyUserAdmin(MockDbForSharedTests, WebTest):
|
||||||
super().tearDownClass()
|
super().tearDownClass()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_view(self):
|
||||||
|
"""Ensure OMB analysts cannot view users list."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_user_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_has_model_description(self):
|
def test_has_model_description(self):
|
||||||
"""Tests if this model has a model description on the table view"""
|
"""Tests if this model has a model description on the table view"""
|
||||||
|
@ -3221,6 +3500,7 @@ class TestContactAdmin(TestCase):
|
||||||
cls.admin = ContactAdmin(model=Contact, admin_site=None)
|
cls.admin = ContactAdmin(model=Contact, admin_site=None)
|
||||||
cls.superuser = create_superuser()
|
cls.superuser = create_superuser()
|
||||||
cls.staffuser = create_user()
|
cls.staffuser = create_user()
|
||||||
|
cls.omb_analyst = create_omb_analyst_user()
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
@ -3236,6 +3516,13 @@ class TestContactAdmin(TestCase):
|
||||||
super().tearDownClass()
|
super().tearDownClass()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_view(self):
|
||||||
|
"""Ensure OMB analysts cannot view contact list."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_contact_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_has_model_description(self):
|
def test_has_model_description(self):
|
||||||
"""Tests if this model has a model description on the table view"""
|
"""Tests if this model has a model description on the table view"""
|
||||||
|
@ -3282,6 +3569,7 @@ class TestVerifiedByStaffAdmin(TestCase):
|
||||||
super().setUpClass()
|
super().setUpClass()
|
||||||
cls.site = AdminSite()
|
cls.site = AdminSite()
|
||||||
cls.superuser = create_superuser()
|
cls.superuser = create_superuser()
|
||||||
|
cls.omb_analyst = create_omb_analyst_user()
|
||||||
cls.admin = VerifiedByStaffAdmin(model=VerifiedByStaff, admin_site=cls.site)
|
cls.admin = VerifiedByStaffAdmin(model=VerifiedByStaff, admin_site=cls.site)
|
||||||
cls.factory = RequestFactory()
|
cls.factory = RequestFactory()
|
||||||
cls.test_helper = GenericTestHelper(admin=cls.admin)
|
cls.test_helper = GenericTestHelper(admin=cls.admin)
|
||||||
|
@ -3299,18 +3587,20 @@ class TestVerifiedByStaffAdmin(TestCase):
|
||||||
super().tearDownClass()
|
super().tearDownClass()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_view(self):
|
||||||
|
"""Ensure OMB analysts cannot view verified by staff list."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_verifiedbystaff_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_has_model_description(self):
|
def test_has_model_description(self):
|
||||||
"""Tests if this model has a model description on the table view"""
|
"""Tests if this model has a model description on the table view"""
|
||||||
self.client.force_login(self.superuser)
|
self.client.force_login(self.superuser)
|
||||||
response = self.client.get(
|
response = self.client.get(reverse("admin:registrar_verifiedbystaff_changelist"))
|
||||||
"/admin/registrar/verifiedbystaff/",
|
|
||||||
follow=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Make sure that the page is loaded correctly
|
# Make sure that the page is loaded correctly
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
# Test for a description snippet
|
# Test for a description snippet
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response, "This table contains users who have been allowed to bypass " "identity proofing through Login.gov"
|
response, "This table contains users who have been allowed to bypass " "identity proofing through Login.gov"
|
||||||
|
@ -3365,6 +3655,7 @@ class TestWebsiteAdmin(TestCase):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.site = AdminSite()
|
self.site = AdminSite()
|
||||||
self.superuser = create_superuser()
|
self.superuser = create_superuser()
|
||||||
|
self.omb_analyst = create_omb_analyst_user()
|
||||||
self.admin = WebsiteAdmin(model=Website, admin_site=self.site)
|
self.admin = WebsiteAdmin(model=Website, admin_site=self.site)
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
self.client = Client(HTTP_HOST="localhost:8080")
|
self.client = Client(HTTP_HOST="localhost:8080")
|
||||||
|
@ -3375,15 +3666,18 @@ class TestWebsiteAdmin(TestCase):
|
||||||
Website.objects.all().delete()
|
Website.objects.all().delete()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_view(self):
|
||||||
|
"""Ensure OMB analysts cannot view website list."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_website_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_has_model_description(self):
|
def test_has_model_description(self):
|
||||||
"""Tests if this model has a model description on the table view"""
|
"""Tests if this model has a model description on the table view"""
|
||||||
self.client.force_login(self.superuser)
|
self.client.force_login(self.superuser)
|
||||||
response = self.client.get(
|
response = self.client.get(reverse("admin:registrar_website_changelist"))
|
||||||
"/admin/registrar/website/",
|
|
||||||
follow=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Make sure that the page is loaded correctly
|
# Make sure that the page is loaded correctly
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
@ -3392,13 +3686,14 @@ class TestWebsiteAdmin(TestCase):
|
||||||
self.assertContains(response, "Show more")
|
self.assertContains(response, "Show more")
|
||||||
|
|
||||||
|
|
||||||
class TestDraftDomain(TestCase):
|
class TestDraftDomainAdmin(TestCase):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super().setUpClass()
|
super().setUpClass()
|
||||||
cls.site = AdminSite()
|
cls.site = AdminSite()
|
||||||
cls.superuser = create_superuser()
|
cls.superuser = create_superuser()
|
||||||
|
cls.omb_analyst = create_omb_analyst_user()
|
||||||
cls.admin = DraftDomainAdmin(model=DraftDomain, admin_site=cls.site)
|
cls.admin = DraftDomainAdmin(model=DraftDomain, admin_site=cls.site)
|
||||||
cls.factory = RequestFactory()
|
cls.factory = RequestFactory()
|
||||||
cls.test_helper = GenericTestHelper(admin=cls.admin)
|
cls.test_helper = GenericTestHelper(admin=cls.admin)
|
||||||
|
@ -3416,15 +3711,18 @@ class TestDraftDomain(TestCase):
|
||||||
super().tearDownClass()
|
super().tearDownClass()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_view(self):
|
||||||
|
"""Ensure OMB analysts cannot view draft domain list."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_draftdomain_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_has_model_description(self):
|
def test_has_model_description(self):
|
||||||
"""Tests if this model has a model description on the table view"""
|
"""Tests if this model has a model description on the table view"""
|
||||||
self.client.force_login(self.superuser)
|
self.client.force_login(self.superuser)
|
||||||
response = self.client.get(
|
response = self.client.get(reverse("admin:registrar_draftdomain_changelist"))
|
||||||
"/admin/registrar/draftdomain/",
|
|
||||||
follow=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Make sure that the page is loaded correctly
|
# Make sure that the page is loaded correctly
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
@ -3435,13 +3733,21 @@ class TestDraftDomain(TestCase):
|
||||||
self.assertContains(response, "Show more")
|
self.assertContains(response, "Show more")
|
||||||
|
|
||||||
|
|
||||||
class TestFederalAgency(TestCase):
|
class TestFederalAgencyAdmin(TestCase):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super().setUpClass()
|
super().setUpClass()
|
||||||
cls.site = AdminSite()
|
cls.site = AdminSite()
|
||||||
cls.superuser = create_superuser()
|
cls.superuser = create_superuser()
|
||||||
|
cls.staffuser = create_user()
|
||||||
|
cls.omb_analyst = create_omb_analyst_user()
|
||||||
|
cls.non_feb_agency = FederalAgency.objects.create(
|
||||||
|
agency="Fake judicial agency", federal_type=BranchChoices.JUDICIAL
|
||||||
|
)
|
||||||
|
cls.feb_agency = FederalAgency.objects.create(
|
||||||
|
agency="Fake executive agency", federal_type=BranchChoices.EXECUTIVE
|
||||||
|
)
|
||||||
cls.admin = FederalAgencyAdmin(model=FederalAgency, admin_site=cls.site)
|
cls.admin = FederalAgencyAdmin(model=FederalAgency, admin_site=cls.site)
|
||||||
cls.factory = RequestFactory()
|
cls.factory = RequestFactory()
|
||||||
cls.test_helper = GenericTestHelper(admin=cls.admin)
|
cls.test_helper = GenericTestHelper(admin=cls.admin)
|
||||||
|
@ -3454,6 +3760,100 @@ class TestFederalAgency(TestCase):
|
||||||
super().tearDownClass()
|
super().tearDownClass()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_analyst_view(self):
|
||||||
|
"""Ensure regular analysts can view federal agencies."""
|
||||||
|
self.client.force_login(self.staffuser)
|
||||||
|
response = self.client.get(reverse("admin:registrar_federalagency_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, self.non_feb_agency.agency)
|
||||||
|
self.assertContains(response, self.feb_agency.agency)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_view(self):
|
||||||
|
"""Ensure OMB analysts can view FEB agencies but not other branches."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_federalagency_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertNotContains(response, self.non_feb_agency.agency)
|
||||||
|
self.assertContains(response, self.feb_agency.agency)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_superuser_view(self):
|
||||||
|
"""Ensure superusers can view domain invitations."""
|
||||||
|
self.client.force_login(self.superuser)
|
||||||
|
response = self.client.get(reverse("admin:registrar_federalagency_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, self.non_feb_agency.agency)
|
||||||
|
self.assertContains(response, self.feb_agency.agency)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_analyst_change(self):
|
||||||
|
"""Ensure regular analysts can view/edit federal agencies list."""
|
||||||
|
self.client.force_login(self.staffuser)
|
||||||
|
response = self.client.get(reverse("admin:registrar_federalagency_change", args=[self.non_feb_agency.id]))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
response = self.client.get(reverse("admin:registrar_federalagency_change", args=[self.feb_agency.id]))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, self.feb_agency.agency)
|
||||||
|
# test whether fields are readonly or editable
|
||||||
|
self.assertContains(response, "id_agency")
|
||||||
|
self.assertContains(response, "id_federal_type")
|
||||||
|
self.assertContains(response, "id_acronym")
|
||||||
|
self.assertContains(response, "id_is_fceb")
|
||||||
|
self.assertNotContains(response, "closelink")
|
||||||
|
self.assertContains(response, "Save")
|
||||||
|
self.assertContains(response, "Delete")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_change(self):
|
||||||
|
"""Ensure OMB analysts can change FEB agencies but not others."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_federalagency_change", args=[self.non_feb_agency.id]))
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
response = self.client.get(reverse("admin:registrar_federalagency_change", args=[self.feb_agency.id]))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, self.feb_agency.agency)
|
||||||
|
# test whether fields are readonly or editable
|
||||||
|
self.assertNotContains(response, "id_agency")
|
||||||
|
self.assertNotContains(response, "id_federal_type")
|
||||||
|
self.assertNotContains(response, "id_acronym")
|
||||||
|
self.assertNotContains(response, "id_is_fceb")
|
||||||
|
self.assertContains(response, "closelink")
|
||||||
|
self.assertNotContains(response, "Save")
|
||||||
|
self.assertNotContains(response, "Delete")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_superuser_change(self):
|
||||||
|
"""Ensure superusers can change all federal agencies."""
|
||||||
|
self.client.force_login(self.superuser)
|
||||||
|
response = self.client.get(reverse("admin:registrar_federalagency_change", args=[self.non_feb_agency.id]))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
response = self.client.get(reverse("admin:registrar_federalagency_change", args=[self.feb_agency.id]))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, self.feb_agency.agency)
|
||||||
|
# test whether fields are readonly or editable
|
||||||
|
self.assertContains(response, "id_agency")
|
||||||
|
self.assertContains(response, "id_federal_type")
|
||||||
|
self.assertContains(response, "id_acronym")
|
||||||
|
self.assertContains(response, "id_is_fceb")
|
||||||
|
self.assertNotContains(response, "closelink")
|
||||||
|
self.assertContains(response, "Save")
|
||||||
|
self.assertContains(response, "Delete")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_filter_feb_agencies(self):
|
||||||
|
"""Ensure OMB analysts can apply filters and only federal agencies show."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
# in setup, created two agencies: Fake judicial agency and Fake executive agency
|
||||||
|
# only executive agency should show up with the search for 'fake'
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("admin:registrar_federalagency_changelist"),
|
||||||
|
data={"q": "fake"},
|
||||||
|
)
|
||||||
|
self.assertNotContains(response, self.non_feb_agency.agency)
|
||||||
|
self.assertContains(response, self.feb_agency.agency)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_has_model_description(self):
|
def test_has_model_description(self):
|
||||||
"""Tests if this model has a model description on the table view"""
|
"""Tests if this model has a model description on the table view"""
|
||||||
|
@ -3471,11 +3871,12 @@ class TestFederalAgency(TestCase):
|
||||||
self.assertContains(response, "Show more")
|
self.assertContains(response, "Show more")
|
||||||
|
|
||||||
|
|
||||||
class TestPublicContact(TestCase):
|
class TestPublicContactAdmin(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.site = AdminSite()
|
self.site = AdminSite()
|
||||||
self.superuser = create_superuser()
|
self.superuser = create_superuser()
|
||||||
|
self.omb_analyst = create_omb_analyst_user()
|
||||||
self.admin = PublicContactAdmin(model=PublicContact, admin_site=self.site)
|
self.admin = PublicContactAdmin(model=PublicContact, admin_site=self.site)
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
self.client = Client(HTTP_HOST="localhost:8080")
|
self.client = Client(HTTP_HOST="localhost:8080")
|
||||||
|
@ -3486,16 +3887,19 @@ class TestPublicContact(TestCase):
|
||||||
PublicContact.objects.all().delete()
|
PublicContact.objects.all().delete()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_view(self):
|
||||||
|
"""Ensure OMB analysts cannot view public contact list."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_publiccontact_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_has_model_description(self):
|
def test_has_model_description(self):
|
||||||
"""Tests if this model has a model description on the table view"""
|
"""Tests if this model has a model description on the table view"""
|
||||||
p = "adminpass"
|
p = "adminpass"
|
||||||
self.client.login(username="superuser", password=p)
|
self.client.login(username="superuser", password=p)
|
||||||
response = self.client.get(
|
response = self.client.get(reverse("admin:registrar_publiccontact_changelist"))
|
||||||
"/admin/registrar/publiccontact/",
|
|
||||||
follow=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Make sure that the page is loaded correctly
|
# Make sure that the page is loaded correctly
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
@ -3504,11 +3908,12 @@ class TestPublicContact(TestCase):
|
||||||
self.assertContains(response, "Show more")
|
self.assertContains(response, "Show more")
|
||||||
|
|
||||||
|
|
||||||
class TestTransitionDomain(TestCase):
|
class TestTransitionDomainAdmin(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.site = AdminSite()
|
self.site = AdminSite()
|
||||||
self.superuser = create_superuser()
|
self.superuser = create_superuser()
|
||||||
|
self.omb_analyst = create_omb_analyst_user()
|
||||||
self.admin = TransitionDomainAdmin(model=TransitionDomain, admin_site=self.site)
|
self.admin = TransitionDomainAdmin(model=TransitionDomain, admin_site=self.site)
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
self.client = Client(HTTP_HOST="localhost:8080")
|
self.client = Client(HTTP_HOST="localhost:8080")
|
||||||
|
@ -3519,15 +3924,18 @@ class TestTransitionDomain(TestCase):
|
||||||
PublicContact.objects.all().delete()
|
PublicContact.objects.all().delete()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_view(self):
|
||||||
|
"""Ensure OMB analysts cannot view transition domain list."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_transitiondomain_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_has_model_description(self):
|
def test_has_model_description(self):
|
||||||
"""Tests if this model has a model description on the table view"""
|
"""Tests if this model has a model description on the table view"""
|
||||||
self.client.force_login(self.superuser)
|
self.client.force_login(self.superuser)
|
||||||
response = self.client.get(
|
response = self.client.get(reverse("admin:registrar_transitiondomain_changelist"))
|
||||||
"/admin/registrar/transitiondomain/",
|
|
||||||
follow=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Make sure that the page is loaded correctly
|
# Make sure that the page is loaded correctly
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
@ -3536,11 +3944,12 @@ class TestTransitionDomain(TestCase):
|
||||||
self.assertContains(response, "Show more")
|
self.assertContains(response, "Show more")
|
||||||
|
|
||||||
|
|
||||||
class TestUserGroup(TestCase):
|
class TestUserGroupAdmin(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.site = AdminSite()
|
self.site = AdminSite()
|
||||||
self.superuser = create_superuser()
|
self.superuser = create_superuser()
|
||||||
|
self.omb_analyst = create_omb_analyst_user()
|
||||||
self.admin = UserGroupAdmin(model=UserGroup, admin_site=self.site)
|
self.admin = UserGroupAdmin(model=UserGroup, admin_site=self.site)
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
self.client = Client(HTTP_HOST="localhost:8080")
|
self.client = Client(HTTP_HOST="localhost:8080")
|
||||||
|
@ -3550,15 +3959,18 @@ class TestUserGroup(TestCase):
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_view(self):
|
||||||
|
"""Ensure OMB analysts cannot view user group list."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_usergroup_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_has_model_description(self):
|
def test_has_model_description(self):
|
||||||
"""Tests if this model has a model description on the table view"""
|
"""Tests if this model has a model description on the table view"""
|
||||||
self.client.force_login(self.superuser)
|
self.client.force_login(self.superuser)
|
||||||
response = self.client.get(
|
response = self.client.get(reverse("admin:registrar_usergroup_changelist"))
|
||||||
"/admin/registrar/usergroup/",
|
|
||||||
follow=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Make sure that the page is loaded correctly
|
# Make sure that the page is loaded correctly
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
@ -3575,12 +3987,23 @@ class TestPortfolioAdmin(TestCase):
|
||||||
super().setUpClass()
|
super().setUpClass()
|
||||||
cls.site = AdminSite()
|
cls.site = AdminSite()
|
||||||
cls.superuser = create_superuser()
|
cls.superuser = create_superuser()
|
||||||
|
cls.staffuser = create_user()
|
||||||
|
cls.omb_analyst = create_omb_analyst_user()
|
||||||
cls.admin = PortfolioAdmin(model=Portfolio, admin_site=cls.site)
|
cls.admin = PortfolioAdmin(model=Portfolio, admin_site=cls.site)
|
||||||
cls.factory = RequestFactory()
|
cls.factory = RequestFactory()
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = Client(HTTP_HOST="localhost:8080")
|
self.client = Client(HTTP_HOST="localhost:8080")
|
||||||
self.portfolio = Portfolio.objects.create(organization_name="Test Portfolio", creator=self.superuser)
|
self.portfolio = Portfolio.objects.create(organization_name="Test portfolio", creator=self.superuser)
|
||||||
|
self.feb_agency = FederalAgency.objects.create(
|
||||||
|
agency="Test FedExec Agency", federal_type=BranchChoices.EXECUTIVE
|
||||||
|
)
|
||||||
|
self.feb_portfolio = Portfolio.objects.create(
|
||||||
|
organization_name="Test FEB portfolio",
|
||||||
|
creator=self.superuser,
|
||||||
|
federal_agency=self.feb_agency,
|
||||||
|
organization_type=DomainRequest.OrganizationChoices.FEDERAL,
|
||||||
|
)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
Suborganization.objects.all().delete()
|
Suborganization.objects.all().delete()
|
||||||
|
@ -3588,8 +4011,118 @@ class TestPortfolioAdmin(TestCase):
|
||||||
DomainRequest.objects.all().delete()
|
DomainRequest.objects.all().delete()
|
||||||
Domain.objects.all().delete()
|
Domain.objects.all().delete()
|
||||||
Portfolio.objects.all().delete()
|
Portfolio.objects.all().delete()
|
||||||
|
self.feb_agency.delete()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_analyst_view(self):
|
||||||
|
"""Ensure regular analysts can view portfolios."""
|
||||||
|
self.client.force_login(self.staffuser)
|
||||||
|
response = self.client.get(reverse("admin:registrar_portfolio_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, self.portfolio.organization_name)
|
||||||
|
self.assertContains(response, self.feb_portfolio.organization_name)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_view(self):
|
||||||
|
"""Ensure OMB analysts can view FEB portfolios but not others."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_portfolio_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertNotContains(response, self.portfolio.organization_name)
|
||||||
|
self.assertContains(response, self.feb_portfolio.organization_name)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_superuser_view(self):
|
||||||
|
"""Ensure superusers can view portfolios."""
|
||||||
|
self.client.force_login(self.superuser)
|
||||||
|
response = self.client.get(reverse("admin:registrar_portfolio_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, self.portfolio.organization_name)
|
||||||
|
self.assertContains(response, self.feb_portfolio.organization_name)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_analyst_change(self):
|
||||||
|
"""Ensure regular analysts can view/edit portfolios."""
|
||||||
|
self.client.force_login(self.staffuser)
|
||||||
|
response = self.client.get(reverse("admin:registrar_portfolio_change", args=[self.portfolio.id]))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
response = self.client.get(reverse("admin:registrar_portfolio_change", args=[self.feb_portfolio.id]))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, self.feb_portfolio.organization_name)
|
||||||
|
# test whether fields are readonly or editable
|
||||||
|
self.assertContains(response, "id_organization_name")
|
||||||
|
self.assertContains(response, "id_notes")
|
||||||
|
self.assertContains(response, "id_organization_type")
|
||||||
|
self.assertContains(response, "id_state_territory")
|
||||||
|
self.assertContains(response, "id_address_line1")
|
||||||
|
self.assertContains(response, "id_address_line2")
|
||||||
|
self.assertContains(response, "id_city")
|
||||||
|
self.assertContains(response, "id_zipcode")
|
||||||
|
self.assertContains(response, "id_urbanization")
|
||||||
|
self.assertNotContains(response, "closelink")
|
||||||
|
self.assertContains(response, "Save")
|
||||||
|
self.assertContains(response, "Delete")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_change(self):
|
||||||
|
"""Ensure OMB analysts can change FEB portfolios but not others."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_portfolio_change", args=[self.portfolio.id]))
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
response = self.client.get(reverse("admin:registrar_portfolio_change", args=[self.feb_portfolio.id]))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, self.feb_portfolio.organization_name)
|
||||||
|
# test whether fields are readonly or editable
|
||||||
|
self.assertNotContains(response, "id_organization_name")
|
||||||
|
self.assertNotContains(response, "id_notes")
|
||||||
|
self.assertNotContains(response, "id_organization_type")
|
||||||
|
self.assertNotContains(response, "id_state_territory")
|
||||||
|
self.assertNotContains(response, "id_address_line1")
|
||||||
|
self.assertNotContains(response, "id_address_line2")
|
||||||
|
self.assertNotContains(response, "id_city")
|
||||||
|
self.assertNotContains(response, "id_zipcode")
|
||||||
|
self.assertNotContains(response, "id_urbanization")
|
||||||
|
self.assertContains(response, "closelink")
|
||||||
|
self.assertNotContains(response, "Save")
|
||||||
|
self.assertNotContains(response, "Delete")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_superuser_change(self):
|
||||||
|
"""Ensure superusers can change all portfolios."""
|
||||||
|
self.client.force_login(self.superuser)
|
||||||
|
response = self.client.get(reverse("admin:registrar_portfolio_change", args=[self.portfolio.id]))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
response = self.client.get(reverse("admin:registrar_portfolio_change", args=[self.feb_portfolio.id]))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, self.feb_portfolio.organization_name)
|
||||||
|
# test whether fields are readonly or editable
|
||||||
|
self.assertContains(response, "id_organization_name")
|
||||||
|
self.assertContains(response, "id_notes")
|
||||||
|
self.assertContains(response, "id_organization_type")
|
||||||
|
self.assertContains(response, "id_state_territory")
|
||||||
|
self.assertContains(response, "id_address_line1")
|
||||||
|
self.assertContains(response, "id_address_line2")
|
||||||
|
self.assertContains(response, "id_city")
|
||||||
|
self.assertContains(response, "id_zipcode")
|
||||||
|
self.assertContains(response, "id_urbanization")
|
||||||
|
self.assertNotContains(response, "closelink")
|
||||||
|
self.assertContains(response, "Save")
|
||||||
|
self.assertContains(response, "Delete")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_filter_feb_portfolios(self):
|
||||||
|
"""Ensure OMB analysts can apply filters and only feb portfolios show."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
# in setup, created two portfolios: Test portfolio and Test FEB portfolio
|
||||||
|
# only executive portfolio should show up with the search for 'portfolio'
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("admin:registrar_portfolio_changelist"),
|
||||||
|
data={"q": "test"},
|
||||||
|
)
|
||||||
|
self.assertNotContains(response, self.portfolio.organization_name)
|
||||||
|
self.assertContains(response, self.feb_portfolio.organization_name)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_created_on_display(self):
|
def test_created_on_display(self):
|
||||||
"""Tests the custom created on which is a reskin of the created_at field"""
|
"""Tests the custom created on which is a reskin of the created_at field"""
|
||||||
|
@ -3777,6 +4310,7 @@ class TestTransferUser(WebTest):
|
||||||
super().setUpClass()
|
super().setUpClass()
|
||||||
cls.site = AdminSite()
|
cls.site = AdminSite()
|
||||||
cls.superuser = create_superuser()
|
cls.superuser = create_superuser()
|
||||||
|
cls.omb_analyst = create_omb_analyst_user()
|
||||||
cls.admin = PortfolioAdmin(model=Portfolio, admin_site=cls.site)
|
cls.admin = PortfolioAdmin(model=Portfolio, admin_site=cls.site)
|
||||||
cls.factory = RequestFactory()
|
cls.factory = RequestFactory()
|
||||||
|
|
||||||
|
@ -3797,6 +4331,13 @@ class TestTransferUser(WebTest):
|
||||||
Portfolio.objects.all().delete()
|
Portfolio.objects.all().delete()
|
||||||
UserDomainRole.objects.all().delete()
|
UserDomainRole.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst(self):
|
||||||
|
"""Ensure OMB analysts cannot view transfer_user."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("transfer_user", args=[self.user1.pk]))
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_transfer_user_shows_current_and_selected_user_information(self):
|
def test_transfer_user_shows_current_and_selected_user_information(self):
|
||||||
"""Assert we pull the current user info and display it on the transfer page"""
|
"""Assert we pull the current user info and display it on the transfer page"""
|
||||||
|
|
|
@ -17,14 +17,17 @@ from registrar.models import (
|
||||||
Host,
|
Host,
|
||||||
Portfolio,
|
Portfolio,
|
||||||
)
|
)
|
||||||
|
from registrar.models.federal_agency import FederalAgency
|
||||||
from registrar.models.public_contact import PublicContact
|
from registrar.models.public_contact import PublicContact
|
||||||
from registrar.models.user_domain_role import UserDomainRole
|
from registrar.models.user_domain_role import UserDomainRole
|
||||||
|
from registrar.utility.constants import BranchChoices
|
||||||
from .common import (
|
from .common import (
|
||||||
MockSESClient,
|
MockSESClient,
|
||||||
completed_domain_request,
|
completed_domain_request,
|
||||||
less_console_noise,
|
less_console_noise,
|
||||||
create_superuser,
|
create_superuser,
|
||||||
create_user,
|
create_user,
|
||||||
|
create_omb_analyst_user,
|
||||||
create_ready_domain,
|
create_ready_domain,
|
||||||
MockEppLib,
|
MockEppLib,
|
||||||
GenericTestHelper,
|
GenericTestHelper,
|
||||||
|
@ -48,7 +51,9 @@ class TestDomainAdminAsStaff(MockEppLib):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(self):
|
def setUpClass(self):
|
||||||
super().setUpClass()
|
super().setUpClass()
|
||||||
|
self.superuser = create_superuser()
|
||||||
self.staffuser = create_user()
|
self.staffuser = create_user()
|
||||||
|
self.omb_analyst = create_omb_analyst_user()
|
||||||
self.site = AdminSite()
|
self.site = AdminSite()
|
||||||
self.admin = DomainAdmin(model=Domain, admin_site=self.site)
|
self.admin = DomainAdmin(model=Domain, admin_site=self.site)
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
|
@ -56,6 +61,24 @@ class TestDomainAdminAsStaff(MockEppLib):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = Client(HTTP_HOST="localhost:8080")
|
self.client = Client(HTTP_HOST="localhost:8080")
|
||||||
self.client.force_login(self.staffuser)
|
self.client.force_login(self.staffuser)
|
||||||
|
self.nonfebdomain = Domain.objects.create(name="nonfebexample.com")
|
||||||
|
self.febdomain = Domain.objects.create(name="febexample.com", state=Domain.State.READY)
|
||||||
|
self.fed_agency = FederalAgency.objects.create(
|
||||||
|
agency="New FedExec Agency", federal_type=BranchChoices.EXECUTIVE
|
||||||
|
)
|
||||||
|
self.portfolio = Portfolio.objects.create(
|
||||||
|
organization_name="new portfolio",
|
||||||
|
organization_type=DomainRequest.OrganizationChoices.FEDERAL,
|
||||||
|
federal_agency=self.fed_agency,
|
||||||
|
creator=self.staffuser,
|
||||||
|
)
|
||||||
|
self.domain_info = DomainInformation.objects.create(
|
||||||
|
domain=self.febdomain, portfolio=self.portfolio, creator=self.staffuser
|
||||||
|
)
|
||||||
|
self.nonfebportfolio = Portfolio.objects.create(
|
||||||
|
organization_name="non feb portfolio",
|
||||||
|
creator=self.staffuser,
|
||||||
|
)
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
@ -65,12 +88,134 @@ class TestDomainAdminAsStaff(MockEppLib):
|
||||||
Domain.objects.all().delete()
|
Domain.objects.all().delete()
|
||||||
DomainInformation.objects.all().delete()
|
DomainInformation.objects.all().delete()
|
||||||
DomainRequest.objects.all().delete()
|
DomainRequest.objects.all().delete()
|
||||||
|
Portfolio.objects.all().delete()
|
||||||
|
self.fed_agency.delete()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(self):
|
def tearDownClass(self):
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
super().tearDownClass()
|
super().tearDownClass()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_view(self):
|
||||||
|
"""Ensure OMB analysts can view domain list."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_domain_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, self.febdomain.name)
|
||||||
|
self.assertNotContains(response, self.nonfebdomain.name)
|
||||||
|
self.assertNotContains(response, ">Import<")
|
||||||
|
self.assertNotContains(response, ">Export<")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_change(self):
|
||||||
|
"""Ensure OMB analysts can view/edit federal executive branch domains."""
|
||||||
|
self.client.force_login(self.omb_analyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_domain_change", args=[self.nonfebdomain.id]))
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
response = self.client.get(reverse("admin:registrar_domain_change", args=[self.febdomain.id]))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, self.febdomain.name)
|
||||||
|
# test portfolio dropdown
|
||||||
|
self.assertContains(response, self.portfolio.organization_name)
|
||||||
|
self.assertNotContains(response, self.nonfebportfolio.organization_name)
|
||||||
|
# test buttons
|
||||||
|
self.assertNotContains(response, "Manage domain")
|
||||||
|
self.assertNotContains(response, "Get registry status")
|
||||||
|
self.assertNotContains(response, "Extend expiration date")
|
||||||
|
self.assertNotContains(response, "Remove from registry")
|
||||||
|
self.assertContains(response, "Place hold")
|
||||||
|
self.assertContains(response, "Save")
|
||||||
|
self.assertNotContains(response, ">Delete<")
|
||||||
|
# test whether fields are readonly or editable
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-portfolio")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-sub_organization")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-creator")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-federal_agency")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-about_your_organization")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-anything_else")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-cisa_representative_first_name")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-cisa_representative_last_name")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-cisa_representative_email")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-domain_request")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-notes")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-senior_official")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-organization_type")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-state_territory")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-address_line1")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-address_line2")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-city")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-zipcode")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-urbanization")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-portfolio_organization_type")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-portfolio_federal_type")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-portfolio_organization_name")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-portfolio_federal_agency")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-portfolio_state_territory")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-portfolio_address_line1")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-portfolio_address_line2")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-portfolio_city")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-portfolio_zipcode")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-portfolio_urbanization")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-organization_type")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-federal_type")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-federal_agency")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-tribe_name")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-federally_recognized_tribe")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-state_recognized_tribe")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-about_your_organization")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-portfolio")
|
||||||
|
self.assertNotContains(response, "id_domain_info-0-sub_organization")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_superuser_change(self):
|
||||||
|
"""Ensure super user can view/edit all domains."""
|
||||||
|
self.client.force_login(self.superuser)
|
||||||
|
response = self.client.get(reverse("admin:registrar_domain_change", args=[self.nonfebdomain.id]))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
response = self.client.get(reverse("admin:registrar_domain_change", args=[self.febdomain.id]))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, self.febdomain.name)
|
||||||
|
# test portfolio dropdown
|
||||||
|
self.assertContains(response, self.portfolio.organization_name)
|
||||||
|
# test buttons
|
||||||
|
self.assertContains(response, "Manage domain")
|
||||||
|
self.assertContains(response, "Get registry status")
|
||||||
|
self.assertContains(response, "Extend expiration date")
|
||||||
|
self.assertContains(response, "Remove from registry")
|
||||||
|
self.assertContains(response, "Place hold")
|
||||||
|
self.assertContains(response, "Save")
|
||||||
|
self.assertContains(response, ">Delete<")
|
||||||
|
# test whether fields are readonly or editable
|
||||||
|
self.assertContains(response, "id_domain_info-0-portfolio")
|
||||||
|
self.assertContains(response, "id_domain_info-0-sub_organization")
|
||||||
|
self.assertContains(response, "id_domain_info-0-creator")
|
||||||
|
self.assertContains(response, "id_domain_info-0-federal_agency")
|
||||||
|
self.assertContains(response, "id_domain_info-0-about_your_organization")
|
||||||
|
self.assertContains(response, "id_domain_info-0-anything_else")
|
||||||
|
self.assertContains(response, "id_domain_info-0-cisa_representative_first_name")
|
||||||
|
self.assertContains(response, "id_domain_info-0-cisa_representative_last_name")
|
||||||
|
self.assertContains(response, "id_domain_info-0-cisa_representative_email")
|
||||||
|
self.assertContains(response, "id_domain_info-0-domain_request")
|
||||||
|
self.assertContains(response, "id_domain_info-0-notes")
|
||||||
|
self.assertContains(response, "id_domain_info-0-senior_official")
|
||||||
|
self.assertContains(response, "id_domain_info-0-organization_type")
|
||||||
|
self.assertContains(response, "id_domain_info-0-state_territory")
|
||||||
|
self.assertContains(response, "id_domain_info-0-address_line1")
|
||||||
|
self.assertContains(response, "id_domain_info-0-address_line2")
|
||||||
|
self.assertContains(response, "id_domain_info-0-city")
|
||||||
|
self.assertContains(response, "id_domain_info-0-zipcode")
|
||||||
|
self.assertContains(response, "id_domain_info-0-urbanization")
|
||||||
|
self.assertContains(response, "id_domain_info-0-organization_type")
|
||||||
|
self.assertContains(response, "id_domain_info-0-federal_type")
|
||||||
|
self.assertContains(response, "id_domain_info-0-federal_agency")
|
||||||
|
self.assertContains(response, "id_domain_info-0-tribe_name")
|
||||||
|
self.assertContains(response, "id_domain_info-0-federally_recognized_tribe")
|
||||||
|
self.assertContains(response, "id_domain_info-0-state_recognized_tribe")
|
||||||
|
self.assertContains(response, "id_domain_info-0-about_your_organization")
|
||||||
|
self.assertContains(response, "id_domain_info-0-portfolio")
|
||||||
|
self.assertContains(response, "id_domain_info-0-sub_organization")
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_staff_can_see_cisa_region_federal(self):
|
def test_staff_can_see_cisa_region_federal(self):
|
||||||
"""Tests if staff can see CISA Region: N/A"""
|
"""Tests if staff can see CISA Region: N/A"""
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from django.forms import ValidationError
|
from django.forms import ValidationError
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from registrar.models.federal_agency import FederalAgency
|
||||||
|
from registrar.utility.constants import BranchChoices
|
||||||
from waffle.testutils import override_flag
|
from waffle.testutils import override_flag
|
||||||
import re
|
import re
|
||||||
from django.test import RequestFactory, Client, TestCase, override_settings
|
from django.test import RequestFactory, Client, TestCase, override_settings
|
||||||
|
@ -37,6 +39,7 @@ from .common import (
|
||||||
less_console_noise,
|
less_console_noise,
|
||||||
create_superuser,
|
create_superuser,
|
||||||
create_user,
|
create_user,
|
||||||
|
create_omb_analyst_user,
|
||||||
multiple_unalphabetical_domain_objects,
|
multiple_unalphabetical_domain_objects,
|
||||||
MockEppLib,
|
MockEppLib,
|
||||||
GenericTestHelper,
|
GenericTestHelper,
|
||||||
|
@ -68,6 +71,7 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
self.admin = DomainRequestAdmin(model=DomainRequest, admin_site=self.site)
|
self.admin = DomainRequestAdmin(model=DomainRequest, admin_site=self.site)
|
||||||
self.superuser = create_superuser()
|
self.superuser = create_superuser()
|
||||||
self.staffuser = create_user()
|
self.staffuser = create_user()
|
||||||
|
self.ombanalyst = create_omb_analyst_user()
|
||||||
self.client = Client(HTTP_HOST="localhost:8080")
|
self.client = Client(HTTP_HOST="localhost:8080")
|
||||||
self.test_helper = GenericTestHelper(
|
self.test_helper = GenericTestHelper(
|
||||||
factory=self.factory,
|
factory=self.factory,
|
||||||
|
@ -80,6 +84,12 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
allowed_emails = [AllowedEmail(email="mayor@igorville.gov"), AllowedEmail(email="help@get.gov")]
|
allowed_emails = [AllowedEmail(email="mayor@igorville.gov"), AllowedEmail(email="help@get.gov")]
|
||||||
AllowedEmail.objects.bulk_create(allowed_emails)
|
AllowedEmail.objects.bulk_create(allowed_emails)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.fed_agency = FederalAgency.objects.create(
|
||||||
|
agency="New FedExec Agency", federal_type=BranchChoices.EXECUTIVE
|
||||||
|
)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
Host.objects.all().delete()
|
Host.objects.all().delete()
|
||||||
|
@ -92,6 +102,7 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
SeniorOfficial.objects.all().delete()
|
SeniorOfficial.objects.all().delete()
|
||||||
Suborganization.objects.all().delete()
|
Suborganization.objects.all().delete()
|
||||||
Portfolio.objects.all().delete()
|
Portfolio.objects.all().delete()
|
||||||
|
self.fed_agency.delete()
|
||||||
self.mock_client.EMAILS_SENT.clear()
|
self.mock_client.EMAILS_SENT.clear()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -100,6 +111,71 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
AllowedEmail.objects.all().delete()
|
AllowedEmail.objects.all().delete()
|
||||||
|
|
||||||
|
@override_flag("organization_feature", active=True)
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_view(self):
|
||||||
|
"""Ensure OMB analysts can view domain request list."""
|
||||||
|
febportfolio = Portfolio.objects.create(
|
||||||
|
organization_name="new portfolio",
|
||||||
|
organization_type=DomainRequest.OrganizationChoices.FEDERAL,
|
||||||
|
federal_agency=self.fed_agency,
|
||||||
|
creator=self.ombanalyst,
|
||||||
|
)
|
||||||
|
nonfebportfolio = Portfolio.objects.create(
|
||||||
|
organization_name="non feb portfolio",
|
||||||
|
creator=self.ombanalyst,
|
||||||
|
)
|
||||||
|
nonfebdomainrequest = completed_domain_request(
|
||||||
|
name="test1234nonfeb.gov",
|
||||||
|
portfolio=nonfebportfolio,
|
||||||
|
status=DomainRequest.DomainRequestStatus.SUBMITTED,
|
||||||
|
)
|
||||||
|
febdomainrequest = completed_domain_request(
|
||||||
|
name="test1234feb.gov",
|
||||||
|
portfolio=febportfolio,
|
||||||
|
status=DomainRequest.DomainRequestStatus.SUBMITTED,
|
||||||
|
)
|
||||||
|
self.client.force_login(self.ombanalyst)
|
||||||
|
response = self.client.get(reverse("admin:registrar_domainrequest_changelist"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, febdomainrequest.requested_domain.name)
|
||||||
|
self.assertNotContains(response, nonfebdomainrequest.requested_domain.name)
|
||||||
|
self.assertNotContains(response, ">Import<")
|
||||||
|
self.assertNotContains(response, ">Export<")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_omb_analyst_change(self):
|
||||||
|
"""Ensure OMB analysts can view/edit federal executive branch domain requests."""
|
||||||
|
self.client.force_login(self.ombanalyst)
|
||||||
|
febportfolio = Portfolio.objects.create(
|
||||||
|
organization_name="new portfolio",
|
||||||
|
organization_type=DomainRequest.OrganizationChoices.FEDERAL,
|
||||||
|
federal_agency=self.fed_agency,
|
||||||
|
creator=self.ombanalyst,
|
||||||
|
)
|
||||||
|
nonfebportfolio = Portfolio.objects.create(
|
||||||
|
organization_name="non feb portfolio",
|
||||||
|
creator=self.ombanalyst,
|
||||||
|
)
|
||||||
|
nonfebdomainrequest = completed_domain_request(
|
||||||
|
name="test1234nonfeb.gov",
|
||||||
|
portfolio=nonfebportfolio,
|
||||||
|
status=DomainRequest.DomainRequestStatus.SUBMITTED,
|
||||||
|
)
|
||||||
|
febdomainrequest = completed_domain_request(
|
||||||
|
name="test1234feb.gov",
|
||||||
|
portfolio=febportfolio,
|
||||||
|
status=DomainRequest.DomainRequestStatus.SUBMITTED,
|
||||||
|
)
|
||||||
|
response = self.client.get(reverse("admin:registrar_domainrequest_change", args=[nonfebdomainrequest.id]))
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
response = self.client.get(reverse("admin:registrar_domainrequest_change", args=[febdomainrequest.id]))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, febdomainrequest.requested_domain.name)
|
||||||
|
# test buttons
|
||||||
|
self.assertContains(response, "Save")
|
||||||
|
self.assertNotContains(response, ">Delete<")
|
||||||
|
|
||||||
@override_flag("organization_feature", active=True)
|
@override_flag("organization_feature", active=True)
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_clean_validates_duplicate_suborganization(self):
|
def test_clean_validates_duplicate_suborganization(self):
|
||||||
|
@ -2072,6 +2148,86 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
|
|
||||||
self.assertEqual(readonly_fields, expected_fields)
|
self.assertEqual(readonly_fields, expected_fields)
|
||||||
|
|
||||||
|
def test_readonly_fields_for_omb_analyst(self):
|
||||||
|
with less_console_noise():
|
||||||
|
request = self.factory.get("/") # Use the correct method and path
|
||||||
|
request.user = self.ombanalyst
|
||||||
|
|
||||||
|
readonly_fields = self.admin.get_readonly_fields(request)
|
||||||
|
|
||||||
|
expected_fields = [
|
||||||
|
"portfolio_senior_official",
|
||||||
|
"portfolio_organization_type",
|
||||||
|
"portfolio_federal_type",
|
||||||
|
"portfolio_organization_name",
|
||||||
|
"portfolio_federal_agency",
|
||||||
|
"portfolio_state_territory",
|
||||||
|
"portfolio_address_line1",
|
||||||
|
"portfolio_address_line2",
|
||||||
|
"portfolio_city",
|
||||||
|
"portfolio_zipcode",
|
||||||
|
"portfolio_urbanization",
|
||||||
|
"other_contacts",
|
||||||
|
"current_websites",
|
||||||
|
"alternative_domains",
|
||||||
|
"is_election_board",
|
||||||
|
"status_history",
|
||||||
|
"federal_agency",
|
||||||
|
"creator",
|
||||||
|
"about_your_organization",
|
||||||
|
"requested_domain",
|
||||||
|
"approved_domain",
|
||||||
|
"alternative_domains",
|
||||||
|
"purpose",
|
||||||
|
"no_other_contacts_rationale",
|
||||||
|
"anything_else",
|
||||||
|
"is_policy_acknowledged",
|
||||||
|
"cisa_representative_first_name",
|
||||||
|
"cisa_representative_last_name",
|
||||||
|
"cisa_representative_email",
|
||||||
|
"status",
|
||||||
|
"investigator",
|
||||||
|
"notes",
|
||||||
|
"senior_official",
|
||||||
|
"organization_type",
|
||||||
|
"organization_name",
|
||||||
|
"state_territory",
|
||||||
|
"address_line1",
|
||||||
|
"address_line2",
|
||||||
|
"city",
|
||||||
|
"zipcode",
|
||||||
|
"urbanization",
|
||||||
|
"portfolio_organization_type",
|
||||||
|
"portfolio_federal_type",
|
||||||
|
"portfolio_organization_name",
|
||||||
|
"portfolio_federal_agency",
|
||||||
|
"portfolio_state_territory",
|
||||||
|
"portfolio_address_line1",
|
||||||
|
"portfolio_address_line2",
|
||||||
|
"portfolio_city",
|
||||||
|
"portfolio_zipcode",
|
||||||
|
"portfolio_urbanization",
|
||||||
|
"is_election_board",
|
||||||
|
"organization_type",
|
||||||
|
"federal_type",
|
||||||
|
"federal_agency",
|
||||||
|
"tribe_name",
|
||||||
|
"federally_recognized_tribe",
|
||||||
|
"state_recognized_tribe",
|
||||||
|
"about_your_organization",
|
||||||
|
"rejection_reason",
|
||||||
|
"rejection_reason_email",
|
||||||
|
"action_needed_reason",
|
||||||
|
"action_needed_reason_email",
|
||||||
|
"portfolio",
|
||||||
|
"sub_organization",
|
||||||
|
"requested_suborganization",
|
||||||
|
"suborganization_city",
|
||||||
|
"suborganization_state_territory",
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertEqual(readonly_fields, expected_fields)
|
||||||
|
|
||||||
def test_saving_when_restricted_creator(self):
|
def test_saving_when_restricted_creator(self):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
# Create an instance of the model
|
# Create an instance of the model
|
||||||
|
|
|
@ -72,7 +72,7 @@ class CsvReportsTest(MockDbForSharedTests):
|
||||||
fake_open = mock_open()
|
fake_open = mock_open()
|
||||||
expected_file_content = [
|
expected_file_content = [
|
||||||
call("Domain name,Domain type,Agency,Organization name,City,State,Security contact email\r\n"),
|
call("Domain name,Domain type,Agency,Organization name,City,State,Security contact email\r\n"),
|
||||||
call("cdomain11.gov,Federal - Executive,World War I Centennial Commission,,,,(blank)\r\n"),
|
call("cdomain11.gov,Federal,World War I Centennial Commission,,,,(blank)\r\n"),
|
||||||
call("cdomain1.gov,Federal - Executive,World War I Centennial Commission,,,,(blank)\r\n"),
|
call("cdomain1.gov,Federal - Executive,World War I Centennial Commission,,,,(blank)\r\n"),
|
||||||
call("adomain10.gov,Federal,Armed Forces Retirement Home,,,,(blank)\r\n"),
|
call("adomain10.gov,Federal,Armed Forces Retirement Home,,,,(blank)\r\n"),
|
||||||
call("ddomain3.gov,Federal,Armed Forces Retirement Home,,,,(blank)\r\n"),
|
call("ddomain3.gov,Federal,Armed Forces Retirement Home,,,,(blank)\r\n"),
|
||||||
|
@ -94,7 +94,7 @@ class CsvReportsTest(MockDbForSharedTests):
|
||||||
fake_open = mock_open()
|
fake_open = mock_open()
|
||||||
expected_file_content = [
|
expected_file_content = [
|
||||||
call("Domain name,Domain type,Agency,Organization name,City,State,Security contact email\r\n"),
|
call("Domain name,Domain type,Agency,Organization name,City,State,Security contact email\r\n"),
|
||||||
call("cdomain11.gov,Federal - Executive,World War I Centennial Commission,,,,(blank)\r\n"),
|
call("cdomain11.gov,Federal,World War I Centennial Commission,,,,(blank)\r\n"),
|
||||||
call("cdomain1.gov,Federal - Executive,World War I Centennial Commission,,,,(blank)\r\n"),
|
call("cdomain1.gov,Federal - Executive,World War I Centennial Commission,,,,(blank)\r\n"),
|
||||||
call("adomain10.gov,Federal,Armed Forces Retirement Home,,,,(blank)\r\n"),
|
call("adomain10.gov,Federal,Armed Forces Retirement Home,,,,(blank)\r\n"),
|
||||||
call("ddomain3.gov,Federal,Armed Forces Retirement Home,,,,(blank)\r\n"),
|
call("ddomain3.gov,Federal,Armed Forces Retirement Home,,,,(blank)\r\n"),
|
||||||
|
@ -261,9 +261,6 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
|
||||||
"defaultsecurity.gov,Ready,2023-11-01,(blank),Federal - Executive,"
|
"defaultsecurity.gov,Ready,2023-11-01,(blank),Federal - Executive,"
|
||||||
"Portfolio 1 Federal Agency,Portfolio 1 Federal Agency,,, ,,(blank),"
|
"Portfolio 1 Federal Agency,Portfolio 1 Federal Agency,,, ,,(blank),"
|
||||||
'"big_lebowski@dude.co, info@example.com, meoward@rocks.com",woofwardthethird@rocks.com\n'
|
'"big_lebowski@dude.co, info@example.com, meoward@rocks.com",woofwardthethird@rocks.com\n'
|
||||||
"cdomain11.gov,Ready,2024-04-02,(blank),Federal - Executive,"
|
|
||||||
"World War I Centennial Commission,,,, ,,(blank),"
|
|
||||||
"meoward@rocks.com,\n"
|
|
||||||
"adomain10.gov,Ready,2024-04-03,(blank),Federal,Armed Forces Retirement Home,,,, ,,(blank),,"
|
"adomain10.gov,Ready,2024-04-03,(blank),Federal,Armed Forces Retirement Home,,,, ,,(blank),,"
|
||||||
"squeaker@rocks.com\n"
|
"squeaker@rocks.com\n"
|
||||||
"bdomain4.gov,Unknown,(blank),(blank),Federal,Armed Forces Retirement Home,,,, ,,(blank),,\n"
|
"bdomain4.gov,Unknown,(blank),(blank),Federal,Armed Forces Retirement Home,,,, ,,(blank),,\n"
|
||||||
|
@ -274,6 +271,9 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
|
||||||
"sdomain8.gov,Deleted,(blank),(blank),Federal,Armed Forces Retirement Home,,,, ,,(blank),,\n"
|
"sdomain8.gov,Deleted,(blank),(blank),Federal,Armed Forces Retirement Home,,,, ,,(blank),,\n"
|
||||||
"xdomain7.gov,Deleted,(blank),(blank),Federal,Armed Forces Retirement Home,,,, ,,(blank),,\n"
|
"xdomain7.gov,Deleted,(blank),(blank),Federal,Armed Forces Retirement Home,,,, ,,(blank),,\n"
|
||||||
"zdomain9.gov,Deleted,(blank),(blank),Federal,Armed Forces Retirement Home,,,, ,,(blank),,\n"
|
"zdomain9.gov,Deleted,(blank),(blank),Federal,Armed Forces Retirement Home,,,, ,,(blank),,\n"
|
||||||
|
"cdomain11.gov,Ready,2024-04-02,(blank),Federal,"
|
||||||
|
"World War I Centennial Commission,,,, ,,(blank),"
|
||||||
|
"meoward@rocks.com,\n"
|
||||||
"zdomain12.gov,Ready,2024-04-02,(blank),Interstate,,,,, ,,(blank),meoward@rocks.com,\n"
|
"zdomain12.gov,Ready,2024-04-02,(blank),Interstate,,,,, ,,(blank),meoward@rocks.com,\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -498,7 +498,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
|
||||||
# sorted alphabetially by domain name
|
# sorted alphabetially by domain name
|
||||||
expected_content = (
|
expected_content = (
|
||||||
"Domain name,Domain type,Agency,Organization name,City,State,Security contact email\n"
|
"Domain name,Domain type,Agency,Organization name,City,State,Security contact email\n"
|
||||||
"cdomain11.gov,Federal - Executive,World War I Centennial Commission,,,,(blank)\n"
|
"cdomain11.gov,Federal,World War I Centennial Commission,,,,(blank)\n"
|
||||||
"defaultsecurity.gov,Federal - Executive,World War I Centennial Commission,,,,(blank)\n"
|
"defaultsecurity.gov,Federal - Executive,World War I Centennial Commission,,,,(blank)\n"
|
||||||
"adomain10.gov,Federal,Armed Forces Retirement Home,,,,(blank)\n"
|
"adomain10.gov,Federal,Armed Forces Retirement Home,,,,(blank)\n"
|
||||||
"ddomain3.gov,Federal,Armed Forces Retirement Home,,,,security@mail.gov\n"
|
"ddomain3.gov,Federal,Armed Forces Retirement Home,,,,security@mail.gov\n"
|
||||||
|
@ -538,7 +538,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
|
||||||
# sorted alphabetially by domain name
|
# sorted alphabetially by domain name
|
||||||
expected_content = (
|
expected_content = (
|
||||||
"Domain name,Domain type,Agency,Organization name,City,State,Security contact email\n"
|
"Domain name,Domain type,Agency,Organization name,City,State,Security contact email\n"
|
||||||
"cdomain11.gov,Federal - Executive,World War I Centennial Commission,,,,(blank)\n"
|
"cdomain11.gov,Federal,World War I Centennial Commission,,,,(blank)\n"
|
||||||
"defaultsecurity.gov,Federal - Executive,World War I Centennial Commission,,,,(blank)\n"
|
"defaultsecurity.gov,Federal - Executive,World War I Centennial Commission,,,,(blank)\n"
|
||||||
"adomain10.gov,Federal,Armed Forces Retirement Home,,,,(blank)\n"
|
"adomain10.gov,Federal,Armed Forces Retirement Home,,,,(blank)\n"
|
||||||
"ddomain3.gov,Federal,Armed Forces Retirement Home,,,,security@mail.gov\n"
|
"ddomain3.gov,Federal,Armed Forces Retirement Home,,,,security@mail.gov\n"
|
||||||
|
@ -594,7 +594,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
|
||||||
"State,Status,Expiration date, Deleted\n"
|
"State,Status,Expiration date, Deleted\n"
|
||||||
"cdomain1.gov,Federal-Executive,Portfolio1FederalAgency,Portfolio1FederalAgency,Ready,(blank)\n"
|
"cdomain1.gov,Federal-Executive,Portfolio1FederalAgency,Portfolio1FederalAgency,Ready,(blank)\n"
|
||||||
"adomain10.gov,Federal,ArmedForcesRetirementHome,Ready,(blank)\n"
|
"adomain10.gov,Federal,ArmedForcesRetirementHome,Ready,(blank)\n"
|
||||||
"cdomain11.gov,Federal-Executive,WorldWarICentennialCommission,Ready,(blank)\n"
|
"cdomain11.gov,Federal,WorldWarICentennialCommission,Ready,(blank)\n"
|
||||||
"zdomain12.gov,Interstate,Ready,(blank)\n"
|
"zdomain12.gov,Interstate,Ready,(blank)\n"
|
||||||
"zdomain9.gov,Federal,ArmedForcesRetirementHome,Deleted,(blank),2024-04-01\n"
|
"zdomain9.gov,Federal,ArmedForcesRetirementHome,Deleted,(blank),2024-04-01\n"
|
||||||
"sdomain8.gov,Federal,ArmedForcesRetirementHome,Deleted,(blank),2024-04-02\n"
|
"sdomain8.gov,Federal,ArmedForcesRetirementHome,Deleted,(blank),2024-04-02\n"
|
||||||
|
@ -642,7 +642,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
|
||||||
"3,2,1,0,0,0,0,0,0,0\n"
|
"3,2,1,0,0,0,0,0,0,0\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Domain name,Domain type,Domain managers,Invited domain managers\n"
|
"Domain name,Domain type,Domain managers,Invited domain managers\n"
|
||||||
"cdomain11.gov,Federal - Executive,meoward@rocks.com,\n"
|
"cdomain11.gov,Federal,meoward@rocks.com,\n"
|
||||||
'cdomain1.gov,Federal - Executive,"big_lebowski@dude.co, info@example.com, meoward@rocks.com",'
|
'cdomain1.gov,Federal - Executive,"big_lebowski@dude.co, info@example.com, meoward@rocks.com",'
|
||||||
"woofwardthethird@rocks.com\n"
|
"woofwardthethird@rocks.com\n"
|
||||||
"zdomain12.gov,Interstate,meoward@rocks.com,\n"
|
"zdomain12.gov,Interstate,meoward@rocks.com,\n"
|
||||||
|
@ -716,7 +716,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
|
||||||
expected_content = (
|
expected_content = (
|
||||||
"Domain request,Domain type,Federal type\n"
|
"Domain request,Domain type,Federal type\n"
|
||||||
"city3.gov,Federal,Executive\n"
|
"city3.gov,Federal,Executive\n"
|
||||||
"city4.gov,City,Executive\n"
|
"city4.gov,City,\n"
|
||||||
"city6.gov,Federal,Executive\n"
|
"city6.gov,Federal,Executive\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -783,7 +783,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
|
||||||
"SO last name,SO email,SO title/role,Request purpose,Request additional details,Other contacts,"
|
"SO last name,SO email,SO title/role,Request purpose,Request additional details,Other contacts,"
|
||||||
"CISA regional representative,Current websites,Investigator\n"
|
"CISA regional representative,Current websites,Investigator\n"
|
||||||
# Content
|
# Content
|
||||||
"city5.gov,Approved,Federal,No,Executive,,Testorg,N/A,,NY,2,requested_suborg,SanFran,CA,,,,,1,0,"
|
"city5.gov,Approved,Federal,No,,,Testorg,N/A,,NY,2,requested_suborg,SanFran,CA,,,,,1,0,"
|
||||||
"city1.gov,Testy,Tester,testy@town.com,Chief Tester,Purpose of the site,There is more,"
|
"city1.gov,Testy,Tester,testy@town.com,Chief Tester,Purpose of the site,There is more,"
|
||||||
"Testy Tester testy2@town.com,,city.com,\n"
|
"Testy Tester testy2@town.com,,city.com,\n"
|
||||||
"city2.gov,In review,Federal,Yes,Executive,Portfolio 1 Federal Agency,Portfolio 1 Federal Agency,"
|
"city2.gov,In review,Federal,Yes,Executive,Portfolio 1 Federal Agency,Portfolio 1 Federal Agency,"
|
||||||
|
@ -795,7 +795,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
|
||||||
'There is more,"Meow Tester24 te2@town.com, Testy1232 Tester24 te2@town.com, '
|
'There is more,"Meow Tester24 te2@town.com, Testy1232 Tester24 te2@town.com, '
|
||||||
'Testy Tester testy2@town.com",'
|
'Testy Tester testy2@town.com",'
|
||||||
'test@igorville.com,"city.com, https://www.example2.com, https://www.example.com",\n'
|
'test@igorville.com,"city.com, https://www.example2.com, https://www.example.com",\n'
|
||||||
"city4.gov,Submitted,City,No,Executive,,Testorg,Yes,,NY,2,,,,,,,,0,1,city1.gov,Testy,"
|
"city4.gov,Submitted,City,No,,,Testorg,Yes,,NY,2,,,,,,,,0,1,city1.gov,Testy,"
|
||||||
"Tester,testy@town.com,"
|
"Tester,testy@town.com,"
|
||||||
"Chief Tester,Purpose of the site,CISA-first-name CISA-last-name | There is more,"
|
"Chief Tester,Purpose of the site,CISA-first-name CISA-last-name | There is more,"
|
||||||
"Testy Tester testy2@town.com,"
|
"Testy Tester testy2@town.com,"
|
||||||
|
|
|
@ -579,8 +579,8 @@ class DomainExport(BaseExport):
|
||||||
Q(portfolio__isnull=False) & Q(portfolio__federal_agency__isnull=False),
|
Q(portfolio__isnull=False) & Q(portfolio__federal_agency__isnull=False),
|
||||||
then=F("portfolio__federal_agency__federal_type"),
|
then=F("portfolio__federal_agency__federal_type"),
|
||||||
),
|
),
|
||||||
# Otherwise, return the natively assigned value
|
# Otherwise, return the federal type from federal agency
|
||||||
default=F("federal_type"),
|
default=F("federal_agency__federal_type"),
|
||||||
output_field=CharField(),
|
output_field=CharField(),
|
||||||
),
|
),
|
||||||
"converted_organization_name": Case(
|
"converted_organization_name": Case(
|
||||||
|
@ -1654,8 +1654,8 @@ class DomainRequestExport(BaseExport):
|
||||||
Q(portfolio__isnull=False) & Q(portfolio__federal_agency__isnull=False),
|
Q(portfolio__isnull=False) & Q(portfolio__federal_agency__isnull=False),
|
||||||
then=F("portfolio__federal_agency__federal_type"),
|
then=F("portfolio__federal_agency__federal_type"),
|
||||||
),
|
),
|
||||||
# Otherwise, return the natively assigned value
|
# Otherwise, return the federal type from federal agency
|
||||||
default=F("federal_type"),
|
default=F("federal_agency__federal_type"),
|
||||||
output_field=CharField(),
|
output_field=CharField(),
|
||||||
),
|
),
|
||||||
"converted_organization_name": Case(
|
"converted_organization_name": Case(
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.shortcuts import render
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.db.models import Avg, F
|
from django.db.models import Avg, F
|
||||||
|
|
||||||
from registrar.decorators import ALL, HAS_PORTFOLIO_MEMBERS_VIEW, IS_STAFF, grant_access
|
from registrar.decorators import ALL, HAS_PORTFOLIO_MEMBERS_VIEW, IS_CISA_ANALYST, IS_FULL_ACCESS, grant_access
|
||||||
from .. import models
|
from .. import models
|
||||||
import datetime
|
import datetime
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -16,7 +16,7 @@ import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@grant_access(IS_STAFF)
|
@grant_access(IS_CISA_ANALYST, IS_FULL_ACCESS)
|
||||||
class AnalyticsView(View):
|
class AnalyticsView(View):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
thirty_days_ago = datetime.datetime.today() - datetime.timedelta(days=30)
|
thirty_days_ago = datetime.datetime.today() - datetime.timedelta(days=30)
|
||||||
|
@ -176,7 +176,7 @@ class AnalyticsView(View):
|
||||||
return render(request, "admin/analytics.html", context)
|
return render(request, "admin/analytics.html", context)
|
||||||
|
|
||||||
|
|
||||||
@grant_access(IS_STAFF)
|
@grant_access(IS_CISA_ANALYST, IS_FULL_ACCESS)
|
||||||
class ExportDataType(View):
|
class ExportDataType(View):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
# match the CSV example with all the fields
|
# match the CSV example with all the fields
|
||||||
|
@ -227,7 +227,7 @@ class ExportMembersPortfolio(View):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@grant_access(IS_STAFF)
|
@grant_access(IS_CISA_ANALYST, IS_FULL_ACCESS)
|
||||||
class ExportDataFull(View):
|
class ExportDataFull(View):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
# Smaller export based on 1
|
# Smaller export based on 1
|
||||||
|
@ -237,7 +237,7 @@ class ExportDataFull(View):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@grant_access(IS_STAFF)
|
@grant_access(IS_CISA_ANALYST, IS_FULL_ACCESS)
|
||||||
class ExportDataFederal(View):
|
class ExportDataFederal(View):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
# Federal only
|
# Federal only
|
||||||
|
@ -247,7 +247,7 @@ class ExportDataFederal(View):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@grant_access(IS_STAFF)
|
@grant_access(IS_CISA_ANALYST, IS_FULL_ACCESS)
|
||||||
class ExportDomainRequestDataFull(View):
|
class ExportDomainRequestDataFull(View):
|
||||||
"""Generates a downloaded report containing all Domain Requests (except started)"""
|
"""Generates a downloaded report containing all Domain Requests (except started)"""
|
||||||
|
|
||||||
|
@ -259,7 +259,7 @@ class ExportDomainRequestDataFull(View):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@grant_access(IS_STAFF)
|
@grant_access(IS_CISA_ANALYST, IS_FULL_ACCESS)
|
||||||
class ExportDataDomainsGrowth(View):
|
class ExportDataDomainsGrowth(View):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
start_date = request.GET.get("start_date", "")
|
start_date = request.GET.get("start_date", "")
|
||||||
|
@ -272,7 +272,7 @@ class ExportDataDomainsGrowth(View):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@grant_access(IS_STAFF)
|
@grant_access(IS_CISA_ANALYST, IS_FULL_ACCESS)
|
||||||
class ExportDataRequestsGrowth(View):
|
class ExportDataRequestsGrowth(View):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
start_date = request.GET.get("start_date", "")
|
start_date = request.GET.get("start_date", "")
|
||||||
|
@ -285,7 +285,7 @@ class ExportDataRequestsGrowth(View):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@grant_access(IS_STAFF)
|
@grant_access(IS_CISA_ANALYST, IS_FULL_ACCESS)
|
||||||
class ExportDataManagedDomains(View):
|
class ExportDataManagedDomains(View):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
start_date = request.GET.get("start_date", "")
|
start_date = request.GET.get("start_date", "")
|
||||||
|
@ -297,7 +297,7 @@ class ExportDataManagedDomains(View):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@grant_access(IS_STAFF)
|
@grant_access(IS_CISA_ANALYST, IS_FULL_ACCESS)
|
||||||
class ExportDataUnmanagedDomains(View):
|
class ExportDataUnmanagedDomains(View):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
start_date = request.GET.get("start_date", "")
|
start_date = request.GET.get("start_date", "")
|
||||||
|
|
|
@ -4,7 +4,7 @@ from django.db.models import ForeignKey, OneToOneField, ManyToManyField, ManyToO
|
||||||
|
|
||||||
from django.shortcuts import render, get_object_or_404, redirect
|
from django.shortcuts import render, get_object_or_404, redirect
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from registrar.decorators import IS_STAFF, grant_access
|
from registrar.decorators import IS_CISA_ANALYST, IS_FULL_ACCESS, grant_access
|
||||||
from registrar.models.domain import Domain
|
from registrar.models.domain import Domain
|
||||||
from registrar.models.domain_request import DomainRequest
|
from registrar.models.domain_request import DomainRequest
|
||||||
from registrar.models.user import User
|
from registrar.models.user import User
|
||||||
|
@ -19,7 +19,7 @@ from registrar.utility.db_helpers import ignore_unique_violation
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@grant_access(IS_STAFF)
|
@grant_access(IS_CISA_ANALYST, IS_FULL_ACCESS)
|
||||||
class TransferUserView(View):
|
class TransferUserView(View):
|
||||||
"""Transfer user methods that set up the transfer_user template and handle the forms on it."""
|
"""Transfer user methods that set up the transfer_user template and handle the forms on it."""
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.forms.models import model_to_dict
|
from django.forms.models import model_to_dict
|
||||||
from registrar.decorators import IS_STAFF, grant_access
|
from registrar.decorators import IS_CISA_ANALYST, IS_FULL_ACCESS, IS_OMB_ANALYST, grant_access
|
||||||
from registrar.models import FederalAgency, SeniorOfficial, DomainRequest
|
from registrar.models import FederalAgency, SeniorOfficial, DomainRequest
|
||||||
from registrar.utility.admin_helpers import get_action_needed_reason_default_email, get_rejection_reason_default_email
|
from registrar.utility.admin_helpers import get_action_needed_reason_default_email, get_rejection_reason_default_email
|
||||||
from registrar.models.portfolio import Portfolio
|
from registrar.models.portfolio import Portfolio
|
||||||
|
@ -10,16 +10,10 @@ from registrar.utility.constants import BranchChoices
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@grant_access(IS_STAFF)
|
@grant_access(IS_CISA_ANALYST, IS_OMB_ANALYST, IS_FULL_ACCESS)
|
||||||
def get_senior_official_from_federal_agency_json(request):
|
def get_senior_official_from_federal_agency_json(request):
|
||||||
"""Returns federal_agency information as a JSON"""
|
"""Returns federal_agency information as a JSON"""
|
||||||
|
|
||||||
# This API is only accessible to admins and analysts
|
|
||||||
superuser_perm = request.user.has_perm("registrar.full_access_permission")
|
|
||||||
analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
|
|
||||||
if not request.user.is_authenticated or not any([analyst_perm, superuser_perm]):
|
|
||||||
return JsonResponse({"error": "You do not have access to this resource"}, status=403)
|
|
||||||
|
|
||||||
agency_name = request.GET.get("agency_name")
|
agency_name = request.GET.get("agency_name")
|
||||||
agency = FederalAgency.objects.filter(agency=agency_name).first()
|
agency = FederalAgency.objects.filter(agency=agency_name).first()
|
||||||
senior_official = SeniorOfficial.objects.filter(federal_agency=agency).first()
|
senior_official = SeniorOfficial.objects.filter(federal_agency=agency).first()
|
||||||
|
@ -37,16 +31,10 @@ def get_senior_official_from_federal_agency_json(request):
|
||||||
return JsonResponse({"error": "Senior Official not found"}, status=404)
|
return JsonResponse({"error": "Senior Official not found"}, status=404)
|
||||||
|
|
||||||
|
|
||||||
@grant_access(IS_STAFF)
|
@grant_access(IS_CISA_ANALYST, IS_OMB_ANALYST, IS_FULL_ACCESS)
|
||||||
def get_portfolio_json(request):
|
def get_portfolio_json(request):
|
||||||
"""Returns portfolio information as a JSON"""
|
"""Returns portfolio information as a JSON"""
|
||||||
|
|
||||||
# This API is only accessible to admins and analysts
|
|
||||||
superuser_perm = request.user.has_perm("registrar.full_access_permission")
|
|
||||||
analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
|
|
||||||
if not request.user.is_authenticated or not any([analyst_perm, superuser_perm]):
|
|
||||||
return JsonResponse({"error": "You do not have access to this resource"}, status=403)
|
|
||||||
|
|
||||||
portfolio_id = request.GET.get("id")
|
portfolio_id = request.GET.get("id")
|
||||||
try:
|
try:
|
||||||
portfolio = Portfolio.objects.get(id=portfolio_id)
|
portfolio = Portfolio.objects.get(id=portfolio_id)
|
||||||
|
@ -93,16 +81,10 @@ def get_portfolio_json(request):
|
||||||
return JsonResponse(portfolio_dict)
|
return JsonResponse(portfolio_dict)
|
||||||
|
|
||||||
|
|
||||||
@grant_access(IS_STAFF)
|
@grant_access(IS_CISA_ANALYST, IS_OMB_ANALYST, IS_FULL_ACCESS)
|
||||||
def get_suborganization_list_json(request):
|
def get_suborganization_list_json(request):
|
||||||
"""Returns suborganization list information for a portfolio as a JSON"""
|
"""Returns suborganization list information for a portfolio as a JSON"""
|
||||||
|
|
||||||
# This API is only accessible to admins and analysts
|
|
||||||
superuser_perm = request.user.has_perm("registrar.full_access_permission")
|
|
||||||
analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
|
|
||||||
if not request.user.is_authenticated or not any([analyst_perm, superuser_perm]):
|
|
||||||
return JsonResponse({"error": "You do not have access to this resource"}, status=403)
|
|
||||||
|
|
||||||
portfolio_id = request.GET.get("portfolio_id")
|
portfolio_id = request.GET.get("portfolio_id")
|
||||||
try:
|
try:
|
||||||
portfolio = Portfolio.objects.get(id=portfolio_id)
|
portfolio = Portfolio.objects.get(id=portfolio_id)
|
||||||
|
@ -115,17 +97,11 @@ def get_suborganization_list_json(request):
|
||||||
return JsonResponse({"results": results, "pagination": {"more": False}})
|
return JsonResponse({"results": results, "pagination": {"more": False}})
|
||||||
|
|
||||||
|
|
||||||
@grant_access(IS_STAFF)
|
@grant_access(IS_CISA_ANALYST, IS_OMB_ANALYST, IS_FULL_ACCESS)
|
||||||
def get_federal_and_portfolio_types_from_federal_agency_json(request):
|
def get_federal_and_portfolio_types_from_federal_agency_json(request):
|
||||||
"""Returns specific portfolio information as a JSON. Request must have
|
"""Returns specific portfolio information as a JSON. Request must have
|
||||||
both agency_name and organization_type."""
|
both agency_name and organization_type."""
|
||||||
|
|
||||||
# This API is only accessible to admins and analysts
|
|
||||||
superuser_perm = request.user.has_perm("registrar.full_access_permission")
|
|
||||||
analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
|
|
||||||
if not request.user.is_authenticated or not any([analyst_perm, superuser_perm]):
|
|
||||||
return JsonResponse({"error": "You do not have access to this resource"}, status=403)
|
|
||||||
|
|
||||||
federal_type = None
|
federal_type = None
|
||||||
portfolio_type = None
|
portfolio_type = None
|
||||||
|
|
||||||
|
@ -143,16 +119,10 @@ def get_federal_and_portfolio_types_from_federal_agency_json(request):
|
||||||
return JsonResponse(response_data)
|
return JsonResponse(response_data)
|
||||||
|
|
||||||
|
|
||||||
@grant_access(IS_STAFF)
|
@grant_access(IS_CISA_ANALYST, IS_OMB_ANALYST, IS_FULL_ACCESS)
|
||||||
def get_action_needed_email_for_user_json(request):
|
def get_action_needed_email_for_user_json(request):
|
||||||
"""Returns a default action needed email for a given user"""
|
"""Returns a default action needed email for a given user"""
|
||||||
|
|
||||||
# This API is only accessible to admins and analysts
|
|
||||||
superuser_perm = request.user.has_perm("registrar.full_access_permission")
|
|
||||||
analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
|
|
||||||
if not request.user.is_authenticated or not any([analyst_perm, superuser_perm]):
|
|
||||||
return JsonResponse({"error": "You do not have access to this resource"}, status=403)
|
|
||||||
|
|
||||||
reason = request.GET.get("reason")
|
reason = request.GET.get("reason")
|
||||||
domain_request_id = request.GET.get("domain_request_id")
|
domain_request_id = request.GET.get("domain_request_id")
|
||||||
if not reason:
|
if not reason:
|
||||||
|
@ -167,16 +137,10 @@ def get_action_needed_email_for_user_json(request):
|
||||||
return JsonResponse({"email": email}, status=200)
|
return JsonResponse({"email": email}, status=200)
|
||||||
|
|
||||||
|
|
||||||
@grant_access(IS_STAFF)
|
@grant_access(IS_CISA_ANALYST, IS_OMB_ANALYST, IS_FULL_ACCESS)
|
||||||
def get_rejection_email_for_user_json(request):
|
def get_rejection_email_for_user_json(request):
|
||||||
"""Returns a default rejection email for a given user"""
|
"""Returns a default rejection email for a given user"""
|
||||||
|
|
||||||
# This API is only accessible to admins and analysts
|
|
||||||
superuser_perm = request.user.has_perm("registrar.full_access_permission")
|
|
||||||
analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
|
|
||||||
if not request.user.is_authenticated or not any([analyst_perm, superuser_perm]):
|
|
||||||
return JsonResponse({"error": "You do not have access to this resource"}, status=403)
|
|
||||||
|
|
||||||
reason = request.GET.get("reason")
|
reason = request.GET.get("reason")
|
||||||
domain_request_id = request.GET.get("domain_request_id")
|
domain_request_id = request.GET.get("domain_request_id")
|
||||||
if not reason:
|
if not reason:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue