This commit is contained in:
zandercymatics 2024-08-13 12:29:22 -06:00
parent df4a02f8ce
commit 4243d19293
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
5 changed files with 174 additions and 4 deletions

View file

@ -6,10 +6,12 @@ from django.template.loader import get_template
from django import forms from django import forms
from django.db.models import Value, CharField, Q from django.db.models import Value, CharField, Q
from django.db.models.functions import Concat, Coalesce from django.db.models.functions import Concat, Coalesce
from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import redirect from django.shortcuts import redirect
from django_fsm import get_available_FIELD_transitions, FSMField from django_fsm import get_available_FIELD_transitions, FSMField
from registrar.models.domain_group import DomainGroup from registrar.models.domain_group import DomainGroup
from registrar.models.portfolio import Portfolio
from registrar.models.suborganization import Suborganization from registrar.models.suborganization import Suborganization
from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices
from waffle.decorators import flag_is_active from waffle.decorators import flag_is_active
@ -19,7 +21,7 @@ from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.urls import reverse from django.urls import reverse
from epplibwrapper.errors import ErrorCode, RegistryError from epplibwrapper.errors import ErrorCode, RegistryError
from registrar.models.user_domain_role import UserDomainRole from registrar.models import UserDomainRole, DomainInformation
from waffle.admin import FlagAdmin from waffle.admin import FlagAdmin
from waffle.models import Sample, Switch from waffle.models import Sample, Switch
from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website, SeniorOfficial from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website, SeniorOfficial
@ -39,7 +41,7 @@ from import_export import resources
from import_export.admin import ImportExportModelAdmin from import_export.admin import ImportExportModelAdmin
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.contrib.admin.widgets import FilteredSelectMultiple from django.contrib.admin.widgets import FilteredSelectMultiple
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -2833,23 +2835,94 @@ class VerifiedByStaffAdmin(ListHeaderAdmin):
super().save_model(request, obj, form, change) super().save_model(request, obj, form, change)
class PortfolioAdmin(ListHeaderAdmin): class PortfolioAdmin(ListHeaderAdmin):
class SuborganizationInline(admin.StackedInline):
""""""
model = models.Suborganization
change_form_template = "django/admin/portfolio_change_form.html" change_form_template = "django/admin/portfolio_change_form.html"
#inlines = [SuborganizationInline, UserInline]
fieldsets = [
# TODO - this will need to be reworked
#(None, {"fields": ["organization_name", "federal_agency", "creator", "created_at", "notes"]}),
(None, {"fields": ["organization_name", "creator", "created_at", "notes"]}),
("Portfolio members", {"fields": ["administrators", "members"]}),
("Portfolio domains", {"fields": ["domains", "domain_requests"]}),
("Type of organization", {"fields": ["organization_type", "federal_type"]}),
("Organization name and mailing address", {"fields": ["federal_agency", "state_territory", "address_line1", "address_line2", "city", "zipcode", "urbanization"]}),
("Suborganizations", {"fields": ["suborganizations"]}),
("Senior official", {"fields": ["senior_official"]}),
("Other", {"fields": ["security_contact_email"]}),
]
# NOTE: use add_fieldsets to modify that page
list_display = ("organization_name", "federal_agency", "creator") list_display = ("organization_name", "federal_agency", "creator")
search_fields = ["organization_name"] search_fields = ["organization_name"]
search_help_text = "Search by organization name." search_help_text = "Search by organization name."
readonly_fields = [ readonly_fields = [
"creator", "created_at",
# Custom fields such as these must be defined as readonly, even if they are not.
"administrators",
"members",
"domains",
"domain_requests",
"suborganizations"
] ]
def suborganizations(self, obj: models.Portfolio):
queryset = obj.get_suborganizations()
return self.get_links_csv(queryset, "suborganization")
suborganizations.short_description = "Suborganizations"
def domains(self, obj: models.Portfolio):
queryset = obj.get_domains()
return self.get_links_csv(queryset, "domaininformation")
domains.short_description = "Domains"
def domain_requests(self, obj: models.Portfolio):
queryset = obj.get_domain_requests()
return self.get_links_csv(queryset, "domainrequest")
domain_requests.short_description = "Domain requests"
def administrators(self, obj: models.Portfolio):
queryset = obj.get_administrators()
return self.get_links_csv(queryset, "user", "get_full_name")
administrators.short_description = "Administrators"
def members(self, obj: models.Portfolio):
queryset = obj.get_members()
return self.get_links_csv(queryset, "user", "get_full_name")
members.short_description = "Members"
# Creates select2 fields (with search bars) # Creates select2 fields (with search bars)
autocomplete_fields = [ autocomplete_fields = [
"creator", "creator",
"federal_agency", "federal_agency",
] ]
# TODO change these names
def get_links_csv(self, queryset, model_name, link_text_attribute=None):
links = []
for item in queryset:
if link_text_attribute:
item_display_value = getattr(item, link_text_attribute)
if callable(item_display_value):
item_display_value = item_display_value()
else:
item_display_value = item
if item_display_value:
link = self.get_html_change_link(model_name=model_name, object_id=item.pk, text_content=item_display_value)
links.append(link)
return format_html(", ".join(links))
def get_html_change_link(self, model_name, object_id, text_content):
change_url = reverse(f"admin:registrar_{model_name}_change", args=[object_id])
return f'<a href="{change_url}">{escape(text_content)}</a>'
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"""
obj = self.get_object(request, object_id) obj = self.get_object(request, object_id)

View file

@ -0,0 +1,44 @@
# Generated by Django 4.2.10 on 2024-08-13 16:31
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("registrar", "0117_alter_portfolioinvitation_portfolio_additional_permissions_and_more"),
]
operations = [
migrations.AddField(
model_name="portfolio",
name="federal_type",
field=models.CharField(
blank=True,
choices=[("executive", "Executive"), ("judicial", "Judicial"), ("legislative", "Legislative")],
max_length=50,
null=True,
),
),
migrations.AlterField(
model_name="suborganization",
name="portfolio",
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="portfolio_suborganizations",
to="registrar.portfolio",
),
),
migrations.AlterField(
model_name="user",
name="portfolio",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="portfolio_users",
to="registrar.portfolio",
),
),
]

View file

@ -2,6 +2,8 @@ from django.db import models
from registrar.models.domain_request import DomainRequest from registrar.models.domain_request import DomainRequest
from registrar.models.federal_agency import FederalAgency from registrar.models.federal_agency import FederalAgency
from registrar.models.utility.portfolio_helper import UserPortfolioRoleChoices
from registrar.utility.constants import BranchChoices
from .utility.time_stamped_model import TimeStampedModel from .utility.time_stamped_model import TimeStampedModel
@ -39,6 +41,13 @@ class Portfolio(TimeStampedModel):
default=FederalAgency.get_non_federal_agency, default=FederalAgency.get_non_federal_agency,
) )
federal_type = models.CharField(
max_length=50,
choices=BranchChoices.choices,
null=True,
blank=True,
)
senior_official = models.ForeignKey( senior_official = models.ForeignKey(
"registrar.SeniorOfficial", "registrar.SeniorOfficial",
on_delete=models.PROTECT, on_delete=models.PROTECT,
@ -110,3 +119,46 @@ class Portfolio(TimeStampedModel):
def __str__(self) -> str: def __str__(self) -> str:
return f"{self.organization_name}" return f"{self.organization_name}"
# == Getters for domains == #
def get_domains(self):
"""Returns all DomainInformations associated with this portfolio"""
return self.information_portfolio.all()
def get_domain_requests(self):
"""Returns all DomainRequests associated with this portfolio"""
return self.DomainRequest_portfolio.all()
# == Getters for suborganization == #
def get_suborganizations(self):
"""Returns all suborganizations associated with this portfolio"""
return self.portfolio_suborganizations.all()
# == Getters for users == #
def get_users(self):
"""Returns all users associated with this portfolio"""
return self.portfolio_users.all()
def get_administrators(self):
"""Returns all administrators associated with this portfolio"""
return self.portfolio_users.filter(
portfolio_roles__overlap=[
UserPortfolioRoleChoices.ORGANIZATION_ADMIN,
]
)
def get_readonly_administrators(self):
"""Returns all readonly_administrators associated with this portfolio"""
return self.portfolio_users.filter(
portfolio_roles__overlap=[
UserPortfolioRoleChoices.ORGANIZATION_ADMIN_READ_ONLY,
]
)
def get_members(self):
"""Returns all members associated with this portfolio"""
return self.portfolio_users.filter(
portfolio_roles__overlap=[
UserPortfolioRoleChoices.ORGANIZATION_MEMBER,
]
)

View file

@ -16,6 +16,7 @@ class Suborganization(TimeStampedModel):
portfolio = models.ForeignKey( portfolio = models.ForeignKey(
"registrar.Portfolio", "registrar.Portfolio",
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name="portfolio_suborganizations",
) )
def __str__(self) -> str: def __str__(self) -> str:

View file

@ -115,7 +115,7 @@ class User(AbstractUser):
"registrar.Portfolio", "registrar.Portfolio",
null=True, null=True,
blank=True, blank=True,
related_name="user", related_name="portfolio_users",
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
) )