mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-31 23:16:36 +02:00
Merge branch 'za/3579-update-public-contact-defaults' into hotgov/3585-update-existing-public-contacts
This commit is contained in:
commit
69d2b4d3b2
37 changed files with 2472 additions and 636 deletions
|
@ -75,6 +75,19 @@ from django.utils.translation import gettext_lazy as _
|
|||
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):
|
||||
"""ModelResource is extended to support importing of tables which
|
||||
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
|
||||
# from editing the domain request; otherwise, the form will be
|
||||
# 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
|
||||
|
||||
def get_custom_field_transitions(self, instance, field):
|
||||
|
@ -919,7 +932,7 @@ class ListHeaderAdmin(AuditedAdmin, OrderableFieldsMixin):
|
|||
return filters
|
||||
|
||||
|
||||
class MyUserAdmin(BaseUserAdmin, ImportExportModelAdmin):
|
||||
class MyUserAdmin(BaseUserAdmin, ImportExportRegistrarModelAdmin):
|
||||
"""Custom user admin class to use our inlines."""
|
||||
|
||||
resource_classes = [UserResource]
|
||||
|
@ -1224,7 +1237,7 @@ class HostResource(resources.ModelResource):
|
|||
model = models.Host
|
||||
|
||||
|
||||
class MyHostAdmin(AuditedAdmin, ImportExportModelAdmin):
|
||||
class MyHostAdmin(AuditedAdmin, ImportExportRegistrarModelAdmin):
|
||||
"""Custom host admin class to use our inlines."""
|
||||
|
||||
resource_classes = [HostResource]
|
||||
|
@ -1242,7 +1255,7 @@ class HostIpResource(resources.ModelResource):
|
|||
model = models.HostIP
|
||||
|
||||
|
||||
class HostIpAdmin(AuditedAdmin, ImportExportModelAdmin):
|
||||
class HostIpAdmin(AuditedAdmin, ImportExportRegistrarModelAdmin):
|
||||
"""Custom host ip admin class"""
|
||||
|
||||
resource_classes = [HostIpResource]
|
||||
|
@ -1264,7 +1277,7 @@ class ContactResource(resources.ModelResource):
|
|||
model = models.Contact
|
||||
|
||||
|
||||
class ContactAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||
class ContactAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||
"""Custom contact admin class to add search."""
|
||||
|
||||
resource_classes = [ContactResource]
|
||||
|
@ -1398,6 +1411,59 @@ class SeniorOfficialAdmin(ListHeaderAdmin):
|
|||
# in autocomplete_fields for Senior Official
|
||||
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):
|
||||
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
||||
|
@ -1407,7 +1473,7 @@ class WebsiteResource(resources.ModelResource):
|
|||
model = models.Website
|
||||
|
||||
|
||||
class WebsiteAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||
class WebsiteAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||
"""Custom website admin class."""
|
||||
|
||||
resource_classes = [WebsiteResource]
|
||||
|
@ -1508,7 +1574,7 @@ class UserPortfolioPermissionAdmin(ListHeaderAdmin):
|
|||
obj.delete() # Calls the overridden delete method on each instance
|
||||
|
||||
|
||||
class UserDomainRoleAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||
class UserDomainRoleAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||
"""Custom user domain role admin class."""
|
||||
|
||||
resource_classes = [UserDomainRoleResource]
|
||||
|
@ -1691,6 +1757,63 @@ class DomainInvitationAdmin(BaseInvitationAdmin):
|
|||
# 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"
|
||||
|
||||
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):
|
||||
"""Override the change_view to add the invitation obj for the change_form_object_tools template"""
|
||||
|
||||
|
@ -1912,7 +2035,7 @@ class DomainInformationResource(resources.ModelResource):
|
|||
model = models.DomainInformation
|
||||
|
||||
|
||||
class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||
class DomainInformationAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||
"""Customize domain information admin class."""
|
||||
|
||||
class GenericOrgFilter(admin.SimpleListFilter):
|
||||
|
@ -2189,6 +2312,47 @@ class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
"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
|
||||
# to activate the edit/delete/view buttons
|
||||
filter_horizontal = ("other_contacts",)
|
||||
|
@ -2217,6 +2381,10 @@ class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
|
||||
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])
|
||||
|
@ -2233,6 +2401,38 @@ class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
use_sort = db_field.name != "senior_official"
|
||||
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):
|
||||
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
||||
|
@ -2242,7 +2442,7 @@ class DomainRequestResource(FsmModelResource):
|
|||
model = models.DomainRequest
|
||||
|
||||
|
||||
class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||
class DomainRequestAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||
"""Custom domain requests admin class."""
|
||||
|
||||
resource_classes = [DomainRequestResource]
|
||||
|
@ -2300,7 +2500,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
class FederalTypeFilter(admin.SimpleListFilter):
|
||||
"""Custom Federal Type filter that accomodates portfolio feature.
|
||||
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"
|
||||
parameter_name = "converted_federal_types"
|
||||
|
@ -2341,7 +2541,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
if self.value():
|
||||
return queryset.filter(
|
||||
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
|
||||
|
||||
|
@ -2756,6 +2956,62 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
"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 = [
|
||||
"approved_domain",
|
||||
"requested_domain",
|
||||
|
@ -2996,6 +3252,10 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
|
||||
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])
|
||||
|
@ -3179,6 +3439,25 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
use_sort = db_field.name != "senior_official"
|
||||
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):
|
||||
"""Custom get_queryset to filter by portfolio if portfolio is in the
|
||||
request params."""
|
||||
|
@ -3188,8 +3467,39 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
if portfolio_id:
|
||||
# Further filter the queryset by the portfolio
|
||||
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
|
||||
|
||||
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):
|
||||
# Call the parent's method to apply default search logic
|
||||
base_queryset, use_distinct = super().get_search_results(request, queryset, search_term)
|
||||
|
@ -3209,6 +3519,15 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
|
||||
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):
|
||||
"""Custom transition domain admin class."""
|
||||
|
@ -3240,6 +3559,16 @@ class DomainInformationInline(admin.StackedInline):
|
|||
template = "django/admin/includes/domain_info_inline_stacked.html"
|
||||
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
|
||||
def portfolio_senior_official(self, obj) -> Optional[SeniorOfficial]:
|
||||
return obj.portfolio.senior_official if obj.portfolio and obj.portfolio.senior_official else None
|
||||
|
@ -3307,6 +3636,7 @@ class DomainInformationInline(admin.StackedInline):
|
|||
fieldsets = copy.deepcopy(list(DomainInformationAdmin.fieldsets))
|
||||
readonly_fields = copy.deepcopy(DomainInformationAdmin.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)
|
||||
|
||||
def get_domain_managers(self, obj):
|
||||
|
@ -3327,12 +3657,16 @@ class DomainInformationInline(admin.StackedInline):
|
|||
if not domain_managers:
|
||||
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:
|
||||
full_name = domain_manager.get_formatted_name()
|
||||
change_url = reverse("admin:registrar_user_change", args=[domain_manager.pk])
|
||||
domain_manager_details += "<tr>"
|
||||
domain_manager_details += f'<td><a href="{change_url}">{escape(domain_manager.username)}</a>'
|
||||
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>{escape(full_name)}</td>"
|
||||
domain_manager_details += f"<td>{escape(domain_manager.email)}</td>"
|
||||
domain_manager_details += "</tr>"
|
||||
|
@ -3364,7 +3698,8 @@ class DomainInformationInline(admin.StackedInline):
|
|||
|
||||
superuser_perm = request.user.has_perm("registrar.full_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 super().has_change_permission(request, obj)
|
||||
|
||||
|
@ -3438,6 +3773,23 @@ class DomainInformationInline(admin.StackedInline):
|
|||
|
||||
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):
|
||||
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
||||
|
@ -3447,7 +3799,7 @@ class DomainResource(FsmModelResource):
|
|||
model = models.Domain
|
||||
|
||||
|
||||
class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||
class DomainAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||
"""Custom domain admin class to add extra buttons."""
|
||||
|
||||
resource_classes = [DomainResource]
|
||||
|
@ -3559,7 +3911,7 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
if self.value():
|
||||
return queryset.filter(
|
||||
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
|
||||
|
||||
|
@ -3586,7 +3938,7 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
Q(domain_info__portfolio__isnull=False) & Q(domain_info__portfolio__federal_agency__isnull=False),
|
||||
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"),
|
||||
),
|
||||
converted_organization_name=Case(
|
||||
|
@ -4013,8 +4365,10 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
# Fixes a bug wherein users which are only is_staff
|
||||
# can access 'change' when GET,
|
||||
# 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(
|
||||
"registrar.analyst_access_permission"
|
||||
if (
|
||||
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 super().has_change_permission(request, obj)
|
||||
|
@ -4029,8 +4383,37 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
if portfolio_id:
|
||||
# Further filter the queryset by the portfolio
|
||||
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
|
||||
|
||||
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):
|
||||
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
||||
|
@ -4040,7 +4423,7 @@ class DraftDomainResource(resources.ModelResource):
|
|||
model = models.DraftDomain
|
||||
|
||||
|
||||
class DraftDomainAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||
class DraftDomainAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||
"""Custom draft domain admin class."""
|
||||
|
||||
resource_classes = [DraftDomainResource]
|
||||
|
@ -4152,7 +4535,7 @@ class PublicContactResource(resources.ModelResource):
|
|||
self.after_save_instance(instance, using_transactions, dry_run)
|
||||
|
||||
|
||||
class PublicContactAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||
class PublicContactAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||
"""Custom PublicContact admin class."""
|
||||
|
||||
resource_classes = [PublicContactResource]
|
||||
|
@ -4211,6 +4594,11 @@ class PortfolioAdmin(ListHeaderAdmin):
|
|||
|
||||
_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"
|
||||
fieldsets = [
|
||||
# created_on is the created_at field
|
||||
|
@ -4292,6 +4680,19 @@ class PortfolioAdmin(ListHeaderAdmin):
|
|||
# rather than strip it out of our logic.
|
||||
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):
|
||||
# Filter UserPortfolioPermission objects related to the portfolio
|
||||
admin_permissions = self.get_user_portfolio_permission_admins(obj)
|
||||
|
@ -4377,6 +4778,8 @@ class PortfolioAdmin(ListHeaderAdmin):
|
|||
"""Returns the number of administrators for this portfolio"""
|
||||
admin_count = len(self.get_user_portfolio_permission_admins(obj))
|
||||
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}"
|
||||
# Create a clickable link with the count
|
||||
return format_html(f'<a href="{url}">{admin_count} admins</a>')
|
||||
|
@ -4388,6 +4791,8 @@ class PortfolioAdmin(ListHeaderAdmin):
|
|||
"""Returns the number of basic members for this portfolio"""
|
||||
member_count = len(self.get_user_portfolio_permission_non_admins(obj))
|
||||
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}"
|
||||
# Create a clickable link with the count
|
||||
return format_html(f'<a href="{url}">{member_count} basic members</a>')
|
||||
|
@ -4433,12 +4838,35 @@ class PortfolioAdmin(ListHeaderAdmin):
|
|||
|
||||
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():
|
||||
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):
|
||||
"""Add related suborganizations and domain groups.
|
||||
Add the summary for the portfolio members field (list of members that link to change_forms)."""
|
||||
|
@ -4483,6 +4911,17 @@ class PortfolioAdmin(ListHeaderAdmin):
|
|||
|
||||
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):
|
||||
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
||||
|
@ -4492,13 +4931,66 @@ class FederalAgencyResource(resources.ModelResource):
|
|||
model = models.FederalAgency
|
||||
|
||||
|
||||
class FederalAgencyAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||
class FederalAgencyAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||
list_display = ["agency"]
|
||||
search_fields = ["agency"]
|
||||
search_help_text = "Search by federal agency."
|
||||
ordering = ["agency"]
|
||||
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):
|
||||
"""Overwrite the generated UserGroup admin class"""
|
||||
|
@ -4548,11 +5040,11 @@ class WaffleFlagAdmin(FlagAdmin):
|
|||
return super().changelist_view(request, extra_context=extra_context)
|
||||
|
||||
|
||||
class DomainGroupAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||
class DomainGroupAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||
list_display = ["name", "portfolio"]
|
||||
|
||||
|
||||
class SuborganizationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||
class SuborganizationAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
|
||||
|
||||
list_display = ["name", "portfolio"]
|
||||
autocomplete_fields = [
|
||||
|
@ -4563,6 +5055,38 @@ class SuborganizationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
|
||||
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):
|
||||
"""Add suborg's related domains and requests to context"""
|
||||
obj = self.get_object(request, object_id)
|
||||
|
@ -4580,6 +5104,30 @@ class SuborganizationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
extra_context = {"domain_requests": domain_requests, "domains": domains}
|
||||
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 Meta:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue