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.db.models import Value, CharField, Q
from django.db.models.functions import Concat, Coalesce
from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
from django.http import HttpResponseRedirect
from django.shortcuts import redirect
from django_fsm import get_available_FIELD_transitions, FSMField
from registrar.models.domain_group import DomainGroup
from registrar.models.portfolio import Portfolio
from registrar.models.suborganization import Suborganization
from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices
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.urls import reverse
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.models import Sample, Switch
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 django.core.exceptions import ObjectDoesNotExist
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
logger = logging.getLogger(__name__)
@ -2833,23 +2835,94 @@ class VerifiedByStaffAdmin(ListHeaderAdmin):
super().save_model(request, obj, form, change)
class PortfolioAdmin(ListHeaderAdmin):
class SuborganizationInline(admin.StackedInline):
""""""
model = models.Suborganization
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")
search_fields = ["organization_name"]
search_help_text = "Search by organization name."
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)
autocomplete_fields = [
"creator",
"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):
"""Add related suborganizations and domain groups"""
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.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
@ -39,6 +41,13 @@ class Portfolio(TimeStampedModel):
default=FederalAgency.get_non_federal_agency,
)
federal_type = models.CharField(
max_length=50,
choices=BranchChoices.choices,
null=True,
blank=True,
)
senior_official = models.ForeignKey(
"registrar.SeniorOfficial",
on_delete=models.PROTECT,
@ -110,3 +119,46 @@ class Portfolio(TimeStampedModel):
def __str__(self) -> str:
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(
"registrar.Portfolio",
on_delete=models.PROTECT,
related_name="portfolio_suborganizations",
)
def __str__(self) -> str:

View file

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