Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#2934: Django Admin - Dynamic Domain Request edit page [DK] #3040

Merged
merged 29 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
81d3ec0
reordering of fields, js for conditional Approved Domain display
dave-kennedy-ecs Nov 4, 2024
b01bb92
javascript hide and show based on portfolio selection
dave-kennedy-ecs Nov 5, 2024
fa7bc95
wip
dave-kennedy-ecs Nov 5, 2024
75ef643
wip
dave-kennedy-ecs Nov 5, 2024
cc116c5
wip
dave-kennedy-ecs Nov 5, 2024
05a387d
wip
dave-kennedy-ecs Nov 5, 2024
3c9e93e
wip
dave-kennedy-ecs Nov 5, 2024
bd22700
updatePortfolioFieldsDataDynamicDisplay
rachidatecs Nov 5, 2024
df6649a
revise updatePortfolioFieldsDataDynamicDisplay to work with dynamic d…
rachidatecs Nov 6, 2024
e948dc4
wip
dave-kennedy-ecs Nov 6, 2024
661f9ff
revise IDs in contact_detail_list to classes
rachidatecs Nov 6, 2024
e013dcc
temp
dave-kennedy-ecs Nov 6, 2024
4c7aaea
Merge branch 'tempdk' into dk/2934-dja-domain-request
dave-kennedy-ecs Nov 6, 2024
5f65f61
wip
dave-kennedy-ecs Nov 6, 2024
4887a47
Dynamic portfolkio senior official handling
rachidatecs Nov 6, 2024
4e12b12
updatePortfolioFieldsDataDynamicDisplay cleanup
rachidatecs Nov 6, 2024
b540178
blank value displays on portfolio and suborg, return ids in json
rachidatecs Nov 6, 2024
ca523a3
added comments and refactored code for simplicity and readability
dave-kennedy-ecs Nov 7, 2024
96c478e
modify text to links for portfolio fields
dave-kennedy-ecs Nov 7, 2024
2d5da90
git ignore VS code specific files
dave-kennedy-ecs Nov 7, 2024
e23a2ee
updated tests, linted some
dave-kennedy-ecs Nov 7, 2024
bc86c44
linter
dave-kennedy-ecs Nov 7, 2024
0b28435
api tests written
dave-kennedy-ecs Nov 7, 2024
4a07c9a
comment fix
dave-kennedy-ecs Nov 7, 2024
f6062da
lint
dave-kennedy-ecs Nov 8, 2024
15b5252
senior official text found
dave-kennedy-ecs Nov 8, 2024
654df17
fixing a bug in suborganization handling
dave-kennedy-ecs Nov 15, 2024
af10118
fixing a bug in suborganization handling
dave-kennedy-ecs Nov 15, 2024
0a11dff
Merge branch 'main' into dk/2934-dja-domain-request
dave-kennedy-ecs Nov 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ node_modules
# Vim
*.swp

# VS Code
.vscode

# Compliance/trestle related
docs/compliance/.trestle/cache

Expand Down
147 changes: 138 additions & 9 deletions src/registrar/admin.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from datetime import date
import logging
import copy
from typing import Optional
from django import forms
from django.db.models import Value, CharField, Q
from django.db.models.functions import Concat, Coalesce
from django.http import HttpResponseRedirect
from registrar.models.federal_agency import FederalAgency
from registrar.utility.admin_helpers import (
AutocompleteSelectWithPlaceholder,
get_action_needed_reason_default_email,
get_rejection_reason_default_email,
get_field_links_as_list,
Expand Down Expand Up @@ -236,6 +238,14 @@ class Meta:
"current_websites": NoAutocompleteFilteredSelectMultiple("current_websites", False),
"alternative_domains": NoAutocompleteFilteredSelectMultiple("alternative_domains", False),
"other_contacts": NoAutocompleteFilteredSelectMultiple("other_contacts", False),
"portfolio": AutocompleteSelectWithPlaceholder(
DomainRequest._meta.get_field("portfolio"), admin.site, attrs={"data-placeholder": "---------"}
),
"sub_organization": AutocompleteSelectWithPlaceholder(
DomainRequest._meta.get_field("sub_organization"),
admin.site,
attrs={"data-placeholder": "---------", "ajax-url": "get-suborganization-list-json"},
),
}
labels = {
"action_needed_reason_email": "Email",
Expand Down Expand Up @@ -1816,6 +1826,70 @@ def custom_election_board(self, obj):
custom_election_board.admin_order_field = "is_election_board" # type: ignore
custom_election_board.short_description = "Election office" # type: ignore

# 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
Comment on lines +1830 to +1831
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def portfolio_senior_official(self, obj) -> Optional[SeniorOfficial]:
return obj.portfolio.senior_official if obj.portfolio and obj.portfolio.senior_official else None
def portfolio_senior_official(self, obj) -> Optional[SeniorOfficial]:
# Queryset is the list that gets iterated on.
# model_name is what model the link will, well, link to: `<a href={{reverse_of_model_name}}>`
# attribute_name is what is displayed between the `<a>` tag: `<a>{{obj.attribute_name}}</a>`
# separator does a join on each `<a>` element that gets generated and returns the resu;t
# msg_for_none is what is returned if the given queryset is empty
queryset = Portfolio.objects.filter(id=obj.portfolio.id, senior_official__isnull=False)
return get_field_links_as_list(queryset, model_name="seniorofficial", attribute_name="senior_official", separator=",", msg_for_none="No senior official found.")

This is the shortcut as mentioned earlier. The separator and queryset is a minor hack: a current limitation of get_field_links is that it expects a list of items (as it was designed for that), hence the separator / queryset dependency. However with a list size of either one or zero, it will look as you'd expect. What is needed is basically the same exact function but it doesn't operate on a queryset list.

No need to use this, but it may be helpful to know in any event

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Zander. I think I will keep this as is in this case. I think it makes most sense for this logic to be in the template, if it is simple enough to define there.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough!


portfolio_senior_official.short_description = "Senior official" # type: ignore

def portfolio_organization_type(self, obj):
return (
DomainRequest.OrganizationChoices.get_org_label(obj.portfolio.organization_type)
if obj.portfolio and obj.portfolio.organization_type
else "-"
)

portfolio_organization_type.short_description = "Organization type" # type: ignore

def portfolio_federal_type(self, obj):
return (
BranchChoices.get_branch_label(obj.portfolio.federal_type)
if obj.portfolio and obj.portfolio.federal_type
else "-"
)

portfolio_federal_type.short_description = "Federal type" # type: ignore

def portfolio_organization_name(self, obj):
return obj.portfolio.organization_name if obj.portfolio else ""

portfolio_organization_name.short_description = "Organization name" # type: ignore

def portfolio_federal_agency(self, obj):
return obj.portfolio.federal_agency if obj.portfolio else ""

portfolio_federal_agency.short_description = "Federal agency" # type: ignore

def portfolio_state_territory(self, obj):
return obj.portfolio.state_territory if obj.portfolio else ""

portfolio_state_territory.short_description = "State, territory, or military post" # type: ignore

def portfolio_address_line1(self, obj):
return obj.portfolio.address_line1 if obj.portfolio else ""

portfolio_address_line1.short_description = "Address line 1" # type: ignore

def portfolio_address_line2(self, obj):
return obj.portfolio.address_line2 if obj.portfolio else ""

portfolio_address_line2.short_description = "Address line 2" # type: ignore

def portfolio_city(self, obj):
return obj.portfolio.city if obj.portfolio else ""

portfolio_city.short_description = "City" # type: ignore

def portfolio_zipcode(self, obj):
return obj.portfolio.zipcode if obj.portfolio else ""

portfolio_zipcode.short_description = "Zip code" # type: ignore

def portfolio_urbanization(self, obj):
return obj.portfolio.urbanization if obj.portfolio else ""

portfolio_urbanization.short_description = "Urbanization" # type: ignore

# This is just a placeholder. This field will be populated in the detail_table_fieldset view.
# This is not a field that exists on the model.
def status_history(self, obj):
Expand Down Expand Up @@ -1847,30 +1921,38 @@ def status_history(self, obj):
None,
{
"fields": [
"portfolio",
"sub_organization",
"requested_suborganization",
"suborganization_city",
"suborganization_state_territory",
"status_history",
"status",
"rejection_reason",
"rejection_reason_email",
"action_needed_reason",
"action_needed_reason_email",
"investigator",
"creator",
"approved_domain",
"investigator",
"notes",
]
},
),
(
"Requested by",
{
"fields": [
"portfolio",
"sub_organization",
"requested_suborganization",
"suborganization_city",
"suborganization_state_territory",
"creator",
]
},
),
(".gov domain", {"fields": ["requested_domain", "alternative_domains"]}),
(
"Contacts",
{
"fields": [
"senior_official",
"portfolio_senior_official",
"other_contacts",
"no_other_contacts_rationale",
"cisa_representative_first_name",
Expand Down Expand Up @@ -1927,10 +2009,55 @@ def status_history(self, obj):
],
},
),
# the below three sections are for portfolio fields
(
"Type of organization",
{
"fields": [
"portfolio_organization_type",
"portfolio_federal_type",
]
},
),
(
"Organization name and mailing address",
{
"fields": [
"portfolio_organization_name",
"portfolio_federal_agency",
]
},
),
(
"Show details",
{
"classes": ["collapse--dgfieldset"],
"description": "Extends organization name and mailing address",
"fields": [
"portfolio_state_territory",
"portfolio_address_line1",
"portfolio_address_line2",
"portfolio_city",
"portfolio_zipcode",
"portfolio_urbanization",
],
},
),
]

# Readonly fields for analysts and superusers
readonly_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",
Expand Down Expand Up @@ -1979,10 +2106,12 @@ def status_history(self, obj):
def get_fieldsets(self, request, obj=None):
fieldsets = super().get_fieldsets(request, obj)

# Hide certain suborg fields behind the organization feature flag
# Hide certain portfolio and suborg fields behind the organization requests flag
# if it is not enabled
if not flag_is_active_for_user(request.user, "organization_feature"):
if not flag_is_active_for_user(request.user, "organization_requests"):
excluded_fields = [
"portfolio",
"sub_organization",
"requested_suborganization",
"suborganization_city",
"suborganization_state_territory",
Expand Down
Loading
Loading