Skip to content

Commit

Permalink
Merge branch 'main' into za/1948-remove-draft-domain-and-websites
Browse files Browse the repository at this point in the history
  • Loading branch information
zandercymatics committed Apr 10, 2024
2 parents 7303f80 + 25dfe81 commit e1d09a0
Show file tree
Hide file tree
Showing 34 changed files with 1,552 additions and 245 deletions.
12 changes: 12 additions & 0 deletions docs/developer/user-permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ role or set of permissions that they have. We use a `UserDomainRole`
`User.domains` many-to-many relationship that works through the
`UserDomainRole` link table.

## Migrating changes to Analyst Permissions model
Analysts are allowed a certain set of read/write registrar permissions.
Setting user permissions requires a migration to change the UserGroup
and Permission models, which requires us to manually make a migration
file for user permission changes.
To update analyst permissions do the following:
1. Make desired changes to analyst group permissions in user_group.py.
2. Follow the steps in the migration file0037_create_groups_v01.py to
create a duplicate migration for the updated user group permissions.
3. To migrate locally, run docker-compose up. To migrate on a sandbox,
push the new migration onto your sandbox before migrating.

## Permission decorator

The Django objects that need to be permission controlled are various views.
Expand Down
2 changes: 1 addition & 1 deletion ops/manifests/manifest-stable.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ applications:
- python_buildpack
path: ../../src
instances: 2
memory: 512M
memory: 1G
stack: cflinuxfs4
timeout: 180
command: ./run.sh
Expand Down
46 changes: 41 additions & 5 deletions src/registrar/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,8 @@ class Meta:
# in autocomplete_fields for user
ordering = ["first_name", "last_name", "email"]

change_form_template = "django/admin/email_clipboard_change_form.html"

def get_search_results(self, request, queryset, search_term):
"""
Override for get_search_results. This affects any upstream model using autocomplete_fields,
Expand Down Expand Up @@ -666,6 +668,17 @@ class ContactAdmin(ListHeaderAdmin):
# in autocomplete_fields for user
ordering = ["first_name", "last_name", "email"]

fieldsets = [
(
None,
{"fields": ["user", "first_name", "middle_name", "last_name", "title", "email", "phone"]},
)
]

autocomplete_fields = ["user"]

change_form_template = "django/admin/email_clipboard_change_form.html"

# We name the custom prop 'contact' because linter
# is not allowing a short_description attr on it
# This gets around the linter limitation, for now.
Expand Down Expand Up @@ -884,6 +897,8 @@ class Meta:
# error.
readonly_fields = ["status"]

change_form_template = "django/admin/email_clipboard_change_form.html"


class DomainInformationAdmin(ListHeaderAdmin):
"""Customize domain information admin class."""
Expand Down Expand Up @@ -923,6 +938,7 @@ class DomainInformationAdmin(ListHeaderAdmin):
"fields": [
"generic_org_type",
"is_election_board",
"organization_type",
]
},
),
Expand Down Expand Up @@ -965,7 +981,7 @@ class DomainInformationAdmin(ListHeaderAdmin):
]

# Readonly fields for analysts and superusers
readonly_fields = ("other_contacts",)
readonly_fields = ("other_contacts", "generic_org_type", "is_election_board")

# Read only that we'll leverage for CISA Analysts
analyst_readonly_fields = [
Expand Down Expand Up @@ -1093,6 +1109,7 @@ def queryset(self, request, queryset):
# Columns
list_display = [
"requested_domain",
"submission_date",
"status",
"generic_org_type",
"federal_type",
Expand All @@ -1101,7 +1118,6 @@ def queryset(self, request, queryset):
"custom_election_board",
"city",
"state_territory",
"submission_date",
"submitter",
"investigator",
]
Expand Down Expand Up @@ -1161,6 +1177,7 @@ def custom_election_board(self, obj):
"fields": [
"generic_org_type",
"is_election_board",
"organization_type",
]
},
),
Expand Down Expand Up @@ -1203,7 +1220,13 @@ def custom_election_board(self, obj):
]

# Readonly fields for analysts and superusers
readonly_fields = ("other_contacts", "current_websites", "alternative_domains")
readonly_fields = (
"other_contacts",
"current_websites",
"alternative_domains",
"generic_org_type",
"is_election_board",
)

# Read only that we'll leverage for CISA Analysts
analyst_readonly_fields = [
Expand All @@ -1229,7 +1252,9 @@ def custom_election_board(self, obj):
filter_horizontal = ("current_websites", "alternative_domains", "other_contacts")

# Table ordering
ordering = ["requested_domain__name"]
# NOTE: This impacts the select2 dropdowns (combobox)
# Currentl, there's only one for requests on DomainInfo
ordering = ["-submission_date", "requested_domain__name"]

change_form_template = "django/admin/domain_request_change_form.html"

Expand Down Expand Up @@ -1441,6 +1466,8 @@ class TransitionDomainAdmin(ListHeaderAdmin):
search_fields = ["username", "domain_name"]
search_help_text = "Search by user or domain name."

change_form_template = "django/admin/email_clipboard_change_form.html"


class DomainInformationInline(admin.StackedInline):
"""Edit a domain information on the domain page.
Expand Down Expand Up @@ -1947,6 +1974,13 @@ def response_change(self, request, obj):
return super().response_change(request, obj)


class PublicContactAdmin(ListHeaderAdmin):
"""Custom PublicContact admin class."""

change_form_template = "django/admin/email_clipboard_change_form.html"
autocomplete_fields = ["domain"]


class VerifiedByStaffAdmin(ListHeaderAdmin):
list_display = ("email", "requestor", "truncated_notes", "created_at")
search_fields = ["email"]
Expand All @@ -1955,6 +1989,8 @@ class VerifiedByStaffAdmin(ListHeaderAdmin):
"requestor",
]

change_form_template = "django/admin/email_clipboard_change_form.html"

def truncated_notes(self, obj):
# Truncate the 'notes' field to 50 characters
return str(obj.notes)[:50]
Expand Down Expand Up @@ -1992,7 +2028,7 @@ class FederalAgencyAdmin(ListHeaderAdmin):
# do not propagate to registry and logic not applied
admin.site.register(models.Host, MyHostAdmin)
admin.site.register(models.Website, WebsiteAdmin)
admin.site.register(models.PublicContact, AuditedAdmin)
admin.site.register(models.PublicContact, PublicContactAdmin)
admin.site.register(models.DomainRequest, DomainRequestAdmin)
admin.site.register(models.TransitionDomain, TransitionDomainAdmin)
admin.site.register(models.VerifiedByStaff, VerifiedByStaffAdmin)
88 changes: 88 additions & 0 deletions src/registrar/assets/js/get-gov-admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,94 @@ function openInNewTab(el, removeAttribute = false){
prepareDjangoAdmin();
})();

/** An IIFE for pages in DjangoAdmin that use a clipboard button
*/
(function (){

function copyInnerTextToClipboard(elem) {
let text = elem.innerText
navigator.clipboard.writeText(text)
}

function copyToClipboardAndChangeIcon(button) {
// Assuming the input is the previous sibling of the button
let input = button.previousElementSibling;
let userId = input.getAttribute("user-id")
// Copy input value to clipboard
if (input) {
navigator.clipboard.writeText(input.value).then(function() {
// Change the icon to a checkmark on successful copy
let buttonIcon = button.querySelector('.usa-button__clipboard use');
if (buttonIcon) {
let currentHref = buttonIcon.getAttribute('xlink:href');
let baseHref = currentHref.split('#')[0];

// Append the new icon reference
buttonIcon.setAttribute('xlink:href', baseHref + '#check');

// Change the button text
nearestSpan = button.querySelector("span")
nearestSpan.innerText = "Copied to clipboard"

setTimeout(function() {
// Change back to the copy icon
buttonIcon.setAttribute('xlink:href', currentHref);
if (button.classList.contains('usa-button__small-text')) {
nearestSpan.innerText = "Copy email";
} else {
nearestSpan.innerText = "Copy";
}
}, 2000);

}

}).catch(function(error) {
console.error('Clipboard copy failed', error);
});
}
}

function handleClipboardButtons() {
clipboardButtons = document.querySelectorAll(".usa-button__clipboard")
clipboardButtons.forEach((button) => {

// Handle copying the text to your clipboard,
// and changing the icon.
button.addEventListener("click", ()=>{
copyToClipboardAndChangeIcon(button);
});

// Add a class that adds the outline style on click
button.addEventListener("mousedown", function() {
this.classList.add("no-outline-on-click");
});

// But add it back in after the user clicked,
// for accessibility reasons (so we can still tab, etc)
button.addEventListener("blur", function() {
this.classList.remove("no-outline-on-click");
});

});
}

function handleClipboardLinks() {
let emailButtons = document.querySelectorAll(".usa-button__clipboard-link");
if (emailButtons){
emailButtons.forEach((button) => {
button.addEventListener("click", ()=>{
copyInnerTextToClipboard(button);
})
});
}
}

handleClipboardButtons();
handleClipboardLinks();

})();


/**
* An IIFE to listen to changes on filter_horizontal and enable or disable the change/delete/view buttons as applicable
*
Expand Down
88 changes: 78 additions & 10 deletions src/registrar/assets/sass/_theme/_admin.scss
Original file line number Diff line number Diff line change
Expand Up @@ -392,32 +392,34 @@ address.margin-top-neg-1__detail-list {
margin-top: 5px !important;
}
// Mimic the normal label size
dt {
address, dt {
font-size: 0.8125rem;
color: var(--body-quiet-color);
}
}

address {
font-size: 0.8125rem;
color: var(--body-quiet-color);
}
td button.usa-button__clipboard-link, address.dja-address-contact-list {
font-size: unset;
}

address.dja-address-contact-list {
font-size: 0.8125rem;
color: var(--body-quiet-color);
button.usa-button__clipboard-link {
font-size: unset;
}
}

// Mimic the normal label size
@media (max-width: 1024px){
.dja-detail-list dt {
.dja-detail-list dt, .dja-detail-list address {
font-size: 0.875rem;
color: var(--body-quiet-color);
}
.dja-detail-list address {
font-size: 0.875rem;
color: var(--body-quiet-color);

address button.usa-button__clipboard-link, td button.usa-button__clipboard-link {
font-size: 0.875rem !important;
}

}

.errors span.select2-selection {
Expand Down Expand Up @@ -533,3 +535,69 @@ address.dja-address-contact-list {
color: var(--link-fg);
}
}


// Make the clipboard button "float" inside of the input box
.admin-icon-group {
position: relative;
display: inline;
align-items: center;

input {
// Allow for padding around the copy button
padding-right: 35px !important;
// Match the height of other inputs
min-height: 2.25rem !important;
}

button {
line-height: 14px;
width: max-content;
font-size: unset;
text-decoration: none !important;
}

@media (max-width: 1000px) {
button {
display: block;
padding-top: 8px;
}
}

span {
padding-left: 0.1rem;
}

}

.admin-icon-group.admin-icon-group__clipboard-link {
position: relative;
display: inline;
align-items: center;


.usa-button__icon {
position: absolute;
right: auto;
left: 4px;
height: 100%;
}
button {
font-size: unset !important;
display: inline-flex;
padding-top: 4px;
line-height: 14px;
color: var(--link-fg);
width: max-content;
font-size: unset;
text-decoration: none !important;
}
}

.no-outline-on-click:focus {
outline: none !important;
}

.usa-button__small-text {
font-size: small;
}
Loading

0 comments on commit e1d09a0

Please sign in to comment.