mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-17 10:07:04 +02:00
Finishing touches
This commit is contained in:
parent
ffb7e146c7
commit
fdc5236968
3 changed files with 104 additions and 17 deletions
|
@ -2836,7 +2836,8 @@ class VerifiedByStaffAdmin(ListHeaderAdmin):
|
||||||
class PortfolioAdmin(ListHeaderAdmin):
|
class PortfolioAdmin(ListHeaderAdmin):
|
||||||
change_form_template = "django/admin/portfolio_change_form.html"
|
change_form_template = "django/admin/portfolio_change_form.html"
|
||||||
fieldsets = [
|
fieldsets = [
|
||||||
(None, {"fields": ["portfolio_type", "organization_name", "creator", "created_at", "notes"]}),
|
# created_on is the created_at field, and portfolio_type is f"{organization_type} - {federal_type}"
|
||||||
|
(None, {"fields": ["portfolio_type", "organization_name", "creator", "created_on", "notes"]}),
|
||||||
# TODO - uncomment in #2521
|
# TODO - uncomment in #2521
|
||||||
# ("Portfolio members", {
|
# ("Portfolio members", {
|
||||||
# "classes": ("collapse", "closed"),
|
# "classes": ("collapse", "closed"),
|
||||||
|
@ -2862,12 +2863,39 @@ class PortfolioAdmin(ListHeaderAdmin):
|
||||||
("Senior official", {"fields": ["senior_official"]}),
|
("Senior official", {"fields": ["senior_official"]}),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# This is the fieldset display when adding a new model
|
||||||
|
add_fieldsets = [
|
||||||
|
(None, {"fields": ["organization_name", "creator", "notes"]}),
|
||||||
|
("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",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
),
|
||||||
|
("Senior official", {"fields": ["senior_official"]}),
|
||||||
|
]
|
||||||
|
|
||||||
|
# NOT all fields are readonly for admin, otherwise we would have
|
||||||
|
# set this at the permissions level. The exception is 'status'
|
||||||
|
analyst_readonly_fields = [
|
||||||
|
"federal_type",
|
||||||
|
]
|
||||||
|
|
||||||
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 = [
|
||||||
"created_at",
|
# This is the created_at field
|
||||||
"federal_type",
|
"created_on",
|
||||||
# Custom fields such as these must be defined as readonly.
|
# Custom fields such as these must be defined as readonly.
|
||||||
"domains",
|
"domains",
|
||||||
"domain_requests",
|
"domain_requests",
|
||||||
|
@ -2875,6 +2903,13 @@ class PortfolioAdmin(ListHeaderAdmin):
|
||||||
"portfolio_type",
|
"portfolio_type",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def created_on(self, obj: models.Portfolio):
|
||||||
|
"""Returns the created_at field, with a different short description"""
|
||||||
|
# Format: Dec 12, 2024
|
||||||
|
return obj.created_at.strftime("%b %d, %Y") if obj.created_at else "-"
|
||||||
|
|
||||||
|
created_on.short_description = "Created on"
|
||||||
|
|
||||||
def portfolio_type(self, obj: models.Portfolio):
|
def portfolio_type(self, obj: models.Portfolio):
|
||||||
"""Returns the portfolio type, or "-" if the result is empty"""
|
"""Returns the portfolio type, or "-" if the result is empty"""
|
||||||
return obj.portfolio_type if obj.portfolio_type else "-"
|
return obj.portfolio_type if obj.portfolio_type else "-"
|
||||||
|
@ -2893,7 +2928,9 @@ class PortfolioAdmin(ListHeaderAdmin):
|
||||||
"""Returns a comma seperated list of links for each related domain"""
|
"""Returns a comma seperated list of links for each related domain"""
|
||||||
queryset = obj.get_domains()
|
queryset = obj.get_domains()
|
||||||
sep = '<div class="display-block margin-top-1"></div>'
|
sep = '<div class="display-block margin-top-1"></div>'
|
||||||
return self.get_field_links_as_csv(queryset, "domaininformation", seperator=sep)
|
return self.get_field_links_as_csv(
|
||||||
|
queryset, "domaininformation", link_info_attribute="get_state_display_of_domain", seperator=sep
|
||||||
|
)
|
||||||
|
|
||||||
domains.short_description = "Domains"
|
domains.short_description = "Domains"
|
||||||
|
|
||||||
|
@ -2901,7 +2938,7 @@ class PortfolioAdmin(ListHeaderAdmin):
|
||||||
"""Returns a comma seperated list of links for each related domain request"""
|
"""Returns a comma seperated list of links for each related domain request"""
|
||||||
queryset = obj.get_domain_requests()
|
queryset = obj.get_domain_requests()
|
||||||
sep = '<div class="display-block margin-top-1"></div>'
|
sep = '<div class="display-block margin-top-1"></div>'
|
||||||
return self.get_field_links_as_csv(queryset, "domainrequest", seperator=sep)
|
return self.get_field_links_as_csv(queryset, "domainrequest", link_info_attribute="get_status_display", seperator=sep)
|
||||||
|
|
||||||
domain_requests.short_description = "Domain requests"
|
domain_requests.short_description = "Domain requests"
|
||||||
|
|
||||||
|
@ -2912,14 +2949,17 @@ class PortfolioAdmin(ListHeaderAdmin):
|
||||||
]
|
]
|
||||||
|
|
||||||
# Q for reviewers: What should this be called?
|
# Q for reviewers: What should this be called?
|
||||||
def get_field_links_as_csv(self, queryset, model_name, link_text_attribute=None, seperator=", "):
|
def get_field_links_as_csv(
|
||||||
|
self, queryset, model_name, attribute_name=None, link_info_attribute=None, seperator=", "
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Generate HTML links for items in a queryset, using a specified attribute for link text.
|
Generate HTML links for items in a queryset, using a specified attribute for link text.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
queryset: The queryset of items to generate links for.
|
queryset: The queryset of items to generate links for.
|
||||||
model_name: The model name used to construct the admin change URL.
|
model_name: The model name used to construct the admin change URL.
|
||||||
link_text_attribute: The attribute or method name to use for link text. If None, the item itself is used.
|
attribute_name: The attribute or method name to use for link text. If None, the item itself is used.
|
||||||
|
link_info_attribute: Appends f"({value_of_attribute})" to the end of the link.
|
||||||
separator: The separator to use between links in the resulting HTML.
|
separator: The separator to use between links in the resulting HTML.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
@ -2928,19 +2968,59 @@ class PortfolioAdmin(ListHeaderAdmin):
|
||||||
links = []
|
links = []
|
||||||
for item in queryset:
|
for item in queryset:
|
||||||
|
|
||||||
# This allows you to pass in link_text_attribute="get_full_name" for instance.
|
# This allows you to pass in attribute_name="get_full_name" for instance.
|
||||||
if link_text_attribute:
|
if attribute_name:
|
||||||
item_display_value = getattr(item, link_text_attribute)
|
item_display_value = self.value_of_attribute(item, attribute_name)
|
||||||
if callable(item_display_value):
|
|
||||||
item_display_value = item_display_value()
|
|
||||||
else:
|
else:
|
||||||
item_display_value = item
|
item_display_value = item
|
||||||
|
|
||||||
if item_display_value:
|
if item_display_value:
|
||||||
change_url = reverse(f"admin:registrar_{model_name}_change", args=[item.pk])
|
change_url = reverse(f"admin:registrar_{model_name}_change", args=[item.pk])
|
||||||
links.append(f'<a href="{change_url}">{escape(item_display_value)}</a>')
|
|
||||||
|
link = f'<a href="{change_url}">{escape(item_display_value)}</a>'
|
||||||
|
if link_info_attribute:
|
||||||
|
link += f" ({self.value_of_attribute(item, link_info_attribute)})"
|
||||||
|
|
||||||
|
links.append(link)
|
||||||
return format_html(seperator.join(links)) if links else "-"
|
return format_html(seperator.join(links)) if links else "-"
|
||||||
|
|
||||||
|
def value_of_attribute(self, obj, attribute_name: str):
|
||||||
|
"""Returns the value of getattr if the attribute isn't callable.
|
||||||
|
If it is, execute the underlying function and return that result instead."""
|
||||||
|
value = getattr(obj, attribute_name)
|
||||||
|
if callable(value):
|
||||||
|
value = value()
|
||||||
|
return value
|
||||||
|
|
||||||
|
def get_fieldsets(self, request, obj=None):
|
||||||
|
"""Override of the default get_fieldsets definition to add an add_fieldsets view"""
|
||||||
|
# This is the add view if no obj exists
|
||||||
|
if not obj:
|
||||||
|
return self.add_fieldsets
|
||||||
|
return super().get_fieldsets(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 creator's status, so
|
||||||
|
we'll use the baseline readonly_fields and extend it as needed.
|
||||||
|
"""
|
||||||
|
readonly_fields = list(self.readonly_fields)
|
||||||
|
|
||||||
|
# Check if the creator is restricted
|
||||||
|
if obj and obj.creator.status == models.User.RESTRICTED:
|
||||||
|
# For fields like CharField, IntegerField, etc., the widget used is
|
||||||
|
# straightforward and the readonly_fields list can control their behavior
|
||||||
|
readonly_fields.extend([field.name for field in self.model._meta.fields])
|
||||||
|
|
||||||
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
|
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 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)
|
||||||
|
|
|
@ -424,3 +424,10 @@ class DomainInformation(TimeStampedModel):
|
||||||
def _get_many_to_many_fields():
|
def _get_many_to_many_fields():
|
||||||
"""Returns a set of each field.name that has the many to many relation"""
|
"""Returns a set of each field.name that has the many to many relation"""
|
||||||
return {field.name for field in DomainInformation._meta.many_to_many} # type: ignore
|
return {field.name for field in DomainInformation._meta.many_to_many} # type: ignore
|
||||||
|
|
||||||
|
def get_state_display_of_domain(self):
|
||||||
|
"""Returns the state display of the underlying domain record"""
|
||||||
|
if self.domain:
|
||||||
|
return self.domain.get_state_display()
|
||||||
|
else:
|
||||||
|
return None
|
|
@ -122,11 +122,11 @@ class Portfolio(TimeStampedModel):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def portfolio_type(self):
|
def portfolio_type(self):
|
||||||
"""Returns a combination of organization_type and federal_agency,
|
"""Returns a combination of organization_type and federal_type,
|
||||||
seperated by ' - '. If no federal_agency is found, we just return the org type."""
|
seperated by ' - '. If no federal_type is found, we just return the org type."""
|
||||||
org_type = self.OrganizationChoices.get_org_label(self.organization_type)
|
org_type = self.OrganizationChoices.get_org_label(self.organization_type)
|
||||||
if self.organization_type == self.OrganizationChoices.FEDERAL and self.federal_agency:
|
if self.organization_type == self.OrganizationChoices.FEDERAL and self.federal_type:
|
||||||
return " - ".join([org_type, self.federal_agency.agency])
|
return " - ".join([org_type, self.federal_type])
|
||||||
else:
|
else:
|
||||||
return org_type
|
return org_type
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue