diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 6e306e9fe..b58ab4cd7 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -2274,11 +2274,12 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): @admin.display(description=_("Requested Domain")) def custom_requested_domain(self, obj): # Example: Show different icons based on `status` - url = reverse("admin:registrar_domainrequest_changelist") + f"{obj.id}" text = obj.requested_domain if obj.portfolio: - return format_html(' {}', url, text) - return format_html('{}', url, text) + return format_html( + f'{escape(text)}' + ) + return text custom_requested_domain.admin_order_field = "requested_domain__name" # type: ignore diff --git a/src/registrar/assets/src/js/getgov-admin/button-utils.js b/src/registrar/assets/src/js/getgov-admin/button-utils.js new file mode 100644 index 000000000..e3746d289 --- /dev/null +++ b/src/registrar/assets/src/js/getgov-admin/button-utils.js @@ -0,0 +1,15 @@ +/** + * Initializes buttons to behave like links by navigating to their data-url attribute + * Example usage: + */ +export function initButtonLinks() { + document.querySelectorAll('button.use-button-as-link').forEach(button => { + button.addEventListener('click', function() { + // Equivalent to button.getAttribute("data-href") + const href = this.dataset.href; + if (href) { + window.location.href = href; + } + }); + }); +} diff --git a/src/registrar/assets/src/js/getgov-admin/main.js b/src/registrar/assets/src/js/getgov-admin/main.js index 5c6de20ab..7eb1fc8cd 100644 --- a/src/registrar/assets/src/js/getgov-admin/main.js +++ b/src/registrar/assets/src/js/getgov-admin/main.js @@ -16,6 +16,7 @@ import { initDynamicPortfolioFields } from './portfolio-form.js'; import { initDynamicDomainInformationFields } from './domain-information-form.js'; import { initDynamicDomainFields } from './domain-form.js'; import { initAnalyticsDashboard } from './analytics.js'; +import { initButtonLinks } from './button-utils.js'; // General initModals(); @@ -23,6 +24,7 @@ initCopyToClipboard(); initFilterHorizontalWidget(); initDescriptions(); initSubmitBar(); +initButtonLinks(); // Domain request initIneligibleModal(); diff --git a/src/registrar/assets/src/js/getgov/table-members.js b/src/registrar/assets/src/js/getgov/table-members.js index a1385b294..a13894e95 100644 --- a/src/registrar/assets/src/js/getgov/table-members.js +++ b/src/registrar/assets/src/js/getgov/table-members.js @@ -69,13 +69,14 @@ export class MembersTable extends BaseTable { const kebabHTML = customTableOptions.hasAdditionalActions ? generateKebabHTML('remove-member', unique_id, cancelInvitationButton, `Expand for more options for ${member.name}`): ''; const row = document.createElement('tr'); + row.classList.add('hide-td-borders'); let admin_tagHTML = ``; if (member.is_admin) admin_tagHTML = `Admin` // generate html blocks for domains and permissions for the member - let domainsHTML = this.generateDomainsHTML(num_domains, member.domain_names, member.domain_urls, member.action_url); - let permissionsHTML = this.generatePermissionsHTML(member.is_admin, member.permissions, customTableOptions.UserPortfolioPermissionChoices); + let domainsHTML = this.generateDomainsHTML(num_domains, member.domain_names, member.domain_urls, member.action_url, unique_id); + let permissionsHTML = this.generatePermissionsHTML(member.is_admin, member.permissions, customTableOptions.UserPortfolioPermissionChoices, unique_id); // domainsHTML block and permissionsHTML block need to be wrapped with hide/show toggle, Expand let showMoreButton = ''; @@ -96,20 +97,26 @@ export class MembersTable extends BaseTable { `; - showMoreRow.innerHTML = `
${domainsHTML} ${permissionsHTML}
`; - showMoreRow.classList.add('show-more-content'); - showMoreRow.classList.add('display-none'); + showMoreRow.innerHTML = ` + + ${showMoreButton} + + + `; showMoreRow.id = unique_id; } row.innerHTML = ` - - ${member.member_display} ${admin_tagHTML} ${showMoreButton} + + ${member.member_display} ${admin_tagHTML} - + ${last_active.display_value} - +
"; - domainsHTML += "

Domains assigned

"; + domainsHTML += `

Domains assigned

`; + domainsHTML += `
` if (num_domains > 0) { domainsHTML += `

This member is assigned to ${num_domains} domain${num_domains > 1 ? 's' : ''}:

`; - domainsHTML += "
"; } else { domainsHTML += `

This member is assigned to 0 domains.

`; } // If there are more than 6 domains, display a "View assigned domains" link domainsHTML += `

View domain assignments

`; - + domainsHTML += "
" domainsHTML += "
"; return domainsHTML; @@ -365,7 +375,7 @@ export class MembersTable extends BaseTable { * - VIEW_ALL_REQUESTS * - EDIT_MEMBERS * - VIEW_MEMBERS - * + * @param {String} unique_id * @returns {string} - A string of HTML representing the user's additional permissions. * If the user has no specific permissions, it returns a default message * indicating no additional permissions. @@ -380,51 +390,51 @@ export class MembersTable extends BaseTable { * - If no relevant permissions are found, the function returns a message stating that the user has no additional permissions. * - The resulting HTML always includes a header "Additional permissions for this member" and appends the relevant permission descriptions. */ - generatePermissionsHTML(is_admin, member_permissions, UserPortfolioPermissionChoices) { - let permissionsHTML = ''; - - // Define shared classes across elements for easier refactoring - let sharedParagraphClasses = "font-body-xs text-base-darker margin-top-1 p--blockquote"; - - // Member access - if (is_admin) { - permissionsHTML += `

Member access: Admin

`; - } else { - permissionsHTML += `

Member access: Basic

`; - } - - // Check domain-related permissions + generatePermissionsHTML(is_admin, member_permissions, UserPortfolioPermissionChoices, unique_id) { + // 1. Role + const memberAccessValue = is_admin ? "Admin" : "Basic"; + + // 2. Domain access + let domainValue = "No access"; if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS)) { - permissionsHTML += `

Domains: Viewer

`; + domainValue = "Viewer"; } else if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS)) { - permissionsHTML += `

Domains: Viewer, limited

`; + domainValue = "Viewer, limited"; } - - // Check request-related permissions + + // 3. Request access + let requestValue = "No access"; if (member_permissions.includes(UserPortfolioPermissionChoices.EDIT_REQUESTS)) { - permissionsHTML += `

Domain requests: Creator

`; + requestValue = "Creator"; } else if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS)) { - permissionsHTML += `

Domain requests: Viewer

`; - } else { - permissionsHTML += `

Domain requests: No access

`; + requestValue = "Viewer"; } - - // Check member-related permissions + + // 4. Member access + let memberValue = "No access"; if (member_permissions.includes(UserPortfolioPermissionChoices.EDIT_MEMBERS)) { - permissionsHTML += `

Members: Manager

`; + memberValue = "Manager"; } else if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_MEMBERS)) { - permissionsHTML += `

Members: Viewer

`; - } else { - permissionsHTML += `

Members: No access

`; + memberValue = "Viewer"; } - - // If no specific permissions are assigned, display a message indicating no additional permissions - if (!permissionsHTML) { - permissionsHTML += `

No additional permissions: There are no additional permissions for this member.

`; - } - - // Add a permissions header and wrap the entire output in a container - permissionsHTML = `

Member access and permissions

${permissionsHTML}
`; + + // Helper function for faster element refactoring + const createPermissionItem = (label, value) => { + return `

${label}: ${value}

`; + }; + const permissionsHTML = ` +
+

+ Member access and permissions +

+
+ ${createPermissionItem("Member access", memberAccessValue)} + ${createPermissionItem("Domains", domainValue)} + ${createPermissionItem("Domain requests", requestValue)} + ${createPermissionItem("Members", memberValue)} +
+
+ `; return permissionsHTML; } diff --git a/src/registrar/assets/src/sass/_theme/_admin.scss b/src/registrar/assets/src/sass/_theme/_admin.scss index 9a00cf022..bd55bbfcb 100644 --- a/src/registrar/assets/src/sass/_theme/_admin.scss +++ b/src/registrar/assets/src/sass/_theme/_admin.scss @@ -498,7 +498,7 @@ input[type=submit].button--dja-toolbar:focus, input[type=submit].button--dja-too font-size: 13px; } -.object-tools li button { +.object-tools li button, button.addlink { font-family: Source Sans Pro Web, Helvetica Neue, Helvetica, Roboto, Arial, sans-serif; text-transform: none !important; font-size: 14px !important; @@ -520,6 +520,14 @@ input[type=submit].button--dja-toolbar:focus, input[type=submit].button--dja-too } } +// Mimic the style for +.object-tools > p > button.addlink { + background-image: url(../admin/img/tooltag-add.svg) !important; + background-repeat: no-repeat !important; + background-position: right 7px center !important; + padding-right: 25px; +} + .usa-modal--django-admin .usa-prose ul > li { list-style-type: inherit; // Styling based off of the

styling in django admin @@ -984,3 +992,7 @@ ul.add-list-reset { } } + +#result_list > tbody tr > th > a { + text-decoration: underline; +} diff --git a/src/registrar/templates/admin/change_form_object_tools.html b/src/registrar/templates/admin/change_form_object_tools.html index 2f3d282ea..d2ec555e2 100644 --- a/src/registrar/templates/admin/change_form_object_tools.html +++ b/src/registrar/templates/admin/change_form_object_tools.html @@ -7,10 +7,10 @@ {% if has_absolute_url %}

{% else %} @@ -30,18 +30,18 @@ {% endif %}
  • - {% translate "History" %} +
  • {% if opts.model_name == 'domainrequest' %}
  • - +
  • {% endif %} diff --git a/src/registrar/templates/admin/change_list_object_tools.html b/src/registrar/templates/admin/change_list_object_tools.html index 9a046b4bb..5ba88aa3a 100644 --- a/src/registrar/templates/admin/change_list_object_tools.html +++ b/src/registrar/templates/admin/change_list_object_tools.html @@ -5,9 +5,9 @@ {% if has_add_permission %}

    {% url cl.opts|admin_urlname:'add' as add_url %} - +

    {% endif %} {% endblock %} \ No newline at end of file diff --git a/src/registrar/templates/admin/change_list_results.html b/src/registrar/templates/admin/change_list_results.html index 5e4f37711..c5be04133 100644 --- a/src/registrar/templates/admin/change_list_results.html +++ b/src/registrar/templates/admin/change_list_results.html @@ -19,11 +19,11 @@ Load our custom filters to extract info from the django generated markup. {% if results.0|contains_checkbox %} {# .gov - hardcode the select all checkbox #} - +
    - +
    @@ -34,9 +34,9 @@ Load our custom filters to extract info from the django generated markup. {% if header.sortable %} {% if header.sort_priority > 0 %}
    - + {% if num_sorted_fields > 1 %}{{ header.sort_priority }}{% endif %} - +
    {% endif %} {% endif %} @@ -61,10 +61,10 @@ Load our custom filters to extract info from the django generated markup. {% endif %} {% with result_value=result.0|extract_value %} - {% with result_label=result.1|extract_a_text %} + {% with result_label=result.1|extract_a_text checkbox_id="select-"|add:result_value %} - - + + {% endwith %} {% endwith %} diff --git a/src/registrar/templates/admin/import_export/change_list_export_item.html b/src/registrar/templates/admin/import_export/change_list_export_item.html new file mode 100644 index 000000000..9678d224a --- /dev/null +++ b/src/registrar/templates/admin/import_export/change_list_export_item.html @@ -0,0 +1,7 @@ +{% load i18n %} +{% load admin_urls %} + +{% if has_export_permission %} +{% comment %} Uses the initButtonLinks {% endcomment %} +
  • +{% endif %} diff --git a/src/registrar/templates/admin/import_export/change_list_import_item.html b/src/registrar/templates/admin/import_export/change_list_import_item.html index 8255a8ba7..0f2d59421 100644 --- a/src/registrar/templates/admin/import_export/change_list_import_item.html +++ b/src/registrar/templates/admin/import_export/change_list_import_item.html @@ -3,6 +3,6 @@ {% if has_import_permission %} {% if not IS_PRODUCTION %} -
  • {% trans "Import" %}
  • +
  • {% endif %} {% endif %} diff --git a/src/registrar/templates/admin/search_form.html b/src/registrar/templates/admin/search_form.html new file mode 100644 index 000000000..c5fcf31f8 --- /dev/null +++ b/src/registrar/templates/admin/search_form.html @@ -0,0 +1,26 @@ +{% comment %} This is an override of the django search bar to add better accessibility compliance. +There are no blocks defined here, so we had to copy the code. +https://github.com/django/django/blob/main/django/contrib/admin/templates/admin/search_form.html +{% endcomment %} +{% load i18n static %} +{% if cl.search_fields %} +
    +{% endif %} \ No newline at end of file diff --git a/src/registrar/templates/emails/status_change_rejected.txt b/src/registrar/templates/emails/status_change_rejected.txt index e56d46a1f..e865031fa 100644 --- a/src/registrar/templates/emails/status_change_rejected.txt +++ b/src/registrar/templates/emails/status_change_rejected.txt @@ -68,10 +68,12 @@ Learn more about: NEED ASSISTANCE? If you have questions about this domain request or need help choosing a new domain name, reply to this email. {% endif %} +{% if reason != domain_request.RejectionReasons.REQUESTOR_NOT_ELIGIBLE and reason != domain_request.RejectionReasons.ORG_NOT_ELIGIBLE %} THANK YOU .Gov helps the public identify official, trusted information. Thank you for requesting a .gov domain. +{% endif %} ---------------------------------------------------------------- The .gov team diff --git a/src/registrar/templatetags/custom_filters.py b/src/registrar/templatetags/custom_filters.py index ff73e6dc1..e02a29e73 100644 --- a/src/registrar/templatetags/custom_filters.py +++ b/src/registrar/templatetags/custom_filters.py @@ -25,11 +25,15 @@ def extract_a_text(value): pattern = r"]*>(.*?)" match = re.search(pattern, value) if match: - extracted_text = match.group(1) - else: - extracted_text = "" + # Get the content and strip any nested HTML tags + content = match.group(1) + # Remove any nested HTML tags (like ) + text_pattern = r"<[^>]+>" + text_only = re.sub(text_pattern, "", content) + # Clean up any extra whitespace + return text_only.strip() - return extracted_text + return "" @register.filter