mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-23 19:20:47 +02:00
Merge pull request #1003 from cisagov/rjm/937-admin-ux-review
1000 admin ux audit implementation of quick fixes
This commit is contained in:
commit
c6670bd9f6
6 changed files with 185 additions and 15 deletions
|
@ -6,10 +6,36 @@ from django.http.response import HttpResponseRedirect
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from registrar.models.utility.admin_sort_fields import AdminSortFields
|
from registrar.models.utility.admin_sort_fields import AdminSortFields
|
||||||
from . import models
|
from . import models
|
||||||
|
from auditlog.models import LogEntry # type: ignore
|
||||||
|
from auditlog.admin import LogEntryAdmin # type: ignore
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomLogEntryAdmin(LogEntryAdmin):
|
||||||
|
"""Overwrite the generated LogEntry admin class"""
|
||||||
|
|
||||||
|
list_display = [
|
||||||
|
"created",
|
||||||
|
"resource",
|
||||||
|
"action",
|
||||||
|
"msg_short",
|
||||||
|
"user_url",
|
||||||
|
]
|
||||||
|
|
||||||
|
# We name the custom prop 'resource' because linter
|
||||||
|
# is not allowing a short_description attr on it
|
||||||
|
# This gets around the linter limitation, for now.
|
||||||
|
def resource(self, obj):
|
||||||
|
# Return the field value without a link
|
||||||
|
return f"{obj.content_type} - {obj.object_repr}"
|
||||||
|
|
||||||
|
search_help_text = "Search by resource, changes, or user."
|
||||||
|
|
||||||
|
change_form_template = "admin/change_form_no_submit.html"
|
||||||
|
add_form_template = "admin/change_form_no_submit.html"
|
||||||
|
|
||||||
|
|
||||||
class AuditedAdmin(admin.ModelAdmin, AdminSortFields):
|
class AuditedAdmin(admin.ModelAdmin, AdminSortFields):
|
||||||
"""Custom admin to make auditing easier."""
|
"""Custom admin to make auditing easier."""
|
||||||
|
|
||||||
|
@ -91,14 +117,12 @@ class ListHeaderAdmin(AuditedAdmin):
|
||||||
|
|
||||||
|
|
||||||
class UserContactInline(admin.StackedInline):
|
class UserContactInline(admin.StackedInline):
|
||||||
|
|
||||||
"""Edit a user's profile on the user page."""
|
"""Edit a user's profile on the user page."""
|
||||||
|
|
||||||
model = models.Contact
|
model = models.Contact
|
||||||
|
|
||||||
|
|
||||||
class MyUserAdmin(BaseUserAdmin):
|
class MyUserAdmin(BaseUserAdmin):
|
||||||
|
|
||||||
"""Custom user admin class to use our inlines."""
|
"""Custom user admin class to use our inlines."""
|
||||||
|
|
||||||
inlines = [UserContactInline]
|
inlines = [UserContactInline]
|
||||||
|
@ -152,23 +176,37 @@ class MyUserAdmin(BaseUserAdmin):
|
||||||
|
|
||||||
|
|
||||||
class HostIPInline(admin.StackedInline):
|
class HostIPInline(admin.StackedInline):
|
||||||
|
|
||||||
"""Edit an ip address on the host page."""
|
"""Edit an ip address on the host page."""
|
||||||
|
|
||||||
model = models.HostIP
|
model = models.HostIP
|
||||||
|
|
||||||
|
|
||||||
class MyHostAdmin(AuditedAdmin):
|
class MyHostAdmin(AuditedAdmin):
|
||||||
|
|
||||||
"""Custom host admin class to use our inlines."""
|
"""Custom host admin class to use our inlines."""
|
||||||
|
|
||||||
inlines = [HostIPInline]
|
inlines = [HostIPInline]
|
||||||
|
|
||||||
|
|
||||||
class DomainAdmin(ListHeaderAdmin):
|
class DomainAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
"""Custom domain admin class to add extra buttons."""
|
"""Custom domain admin class to add extra buttons."""
|
||||||
|
|
||||||
|
# Columns
|
||||||
|
list_display = [
|
||||||
|
"name",
|
||||||
|
"organization_type",
|
||||||
|
"state",
|
||||||
|
]
|
||||||
|
|
||||||
|
def organization_type(self, obj):
|
||||||
|
return obj.domain_info.organization_type
|
||||||
|
|
||||||
|
organization_type.admin_order_field = ( # type: ignore
|
||||||
|
"domain_info__organization_type"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Filters
|
||||||
|
list_filter = ["domain_info__organization_type"]
|
||||||
|
|
||||||
search_fields = ["name"]
|
search_fields = ["name"]
|
||||||
search_help_text = "Search by domain name."
|
search_help_text = "Search by domain name."
|
||||||
change_form_template = "django/admin/domain_change_form.html"
|
change_form_template = "django/admin/domain_change_form.html"
|
||||||
|
@ -250,11 +288,100 @@ class ContactAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
search_fields = ["email", "first_name", "last_name"]
|
search_fields = ["email", "first_name", "last_name"]
|
||||||
search_help_text = "Search by firstname, lastname or email."
|
search_help_text = "Search by firstname, lastname or email."
|
||||||
|
list_display = [
|
||||||
|
"contact",
|
||||||
|
"email",
|
||||||
|
]
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
def contact(self, obj: models.Contact):
|
||||||
|
"""Duplicate the contact _str_"""
|
||||||
|
if obj.first_name or obj.last_name:
|
||||||
|
return obj.get_formatted_name()
|
||||||
|
elif obj.email:
|
||||||
|
return obj.email
|
||||||
|
elif obj.pk:
|
||||||
|
return str(obj.pk)
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
contact.admin_order_field = "first_name" # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
class WebsiteAdmin(ListHeaderAdmin):
|
||||||
|
"""Custom website admin class."""
|
||||||
|
|
||||||
|
# Search
|
||||||
|
search_fields = [
|
||||||
|
"website",
|
||||||
|
]
|
||||||
|
search_help_text = "Search by website."
|
||||||
|
|
||||||
|
|
||||||
|
class UserDomainRoleAdmin(ListHeaderAdmin):
|
||||||
|
"""Custom domain role admin class."""
|
||||||
|
|
||||||
|
# Columns
|
||||||
|
list_display = [
|
||||||
|
"user",
|
||||||
|
"domain",
|
||||||
|
"role",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Search
|
||||||
|
search_fields = [
|
||||||
|
"user__first_name",
|
||||||
|
"user__last_name",
|
||||||
|
"domain__name",
|
||||||
|
"role",
|
||||||
|
]
|
||||||
|
search_help_text = "Search by user, domain, or role."
|
||||||
|
|
||||||
|
|
||||||
|
class DomainInvitationAdmin(ListHeaderAdmin):
|
||||||
|
"""Custom domain invitation admin class."""
|
||||||
|
|
||||||
|
# Columns
|
||||||
|
list_display = [
|
||||||
|
"email",
|
||||||
|
"domain",
|
||||||
|
"status",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Search
|
||||||
|
search_fields = [
|
||||||
|
"email",
|
||||||
|
"domain__name",
|
||||||
|
]
|
||||||
|
search_help_text = "Search by email or domain."
|
||||||
|
|
||||||
|
|
||||||
|
class DomainInformationAdmin(ListHeaderAdmin):
|
||||||
|
"""Customize domain information admin class."""
|
||||||
|
|
||||||
|
# Columns
|
||||||
|
list_display = [
|
||||||
|
"domain",
|
||||||
|
"organization_type",
|
||||||
|
"created_at",
|
||||||
|
"submitter",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Filters
|
||||||
|
list_filter = ["organization_type"]
|
||||||
|
|
||||||
|
# Search
|
||||||
|
search_fields = [
|
||||||
|
"domain__name",
|
||||||
|
]
|
||||||
|
search_help_text = "Search by domain."
|
||||||
|
|
||||||
|
|
||||||
class DomainApplicationAdmin(ListHeaderAdmin):
|
class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
"""Customize the applications listing view."""
|
"""Custom domain applications admin class."""
|
||||||
|
|
||||||
# Set multi-selects 'read-only' (hide selects and show data)
|
# Set multi-selects 'read-only' (hide selects and show data)
|
||||||
# based on user perms and application creator's status
|
# based on user perms and application creator's status
|
||||||
|
@ -428,14 +555,16 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
return super().change_view(request, object_id, form_url, extra_context)
|
return super().change_view(request, object_id, form_url, extra_context)
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.unregister(LogEntry) # Unregister the default registration
|
||||||
|
admin.site.register(LogEntry, CustomLogEntryAdmin)
|
||||||
admin.site.register(models.User, MyUserAdmin)
|
admin.site.register(models.User, MyUserAdmin)
|
||||||
admin.site.register(models.UserDomainRole, AuditedAdmin)
|
admin.site.register(models.UserDomainRole, UserDomainRoleAdmin)
|
||||||
admin.site.register(models.Contact, ContactAdmin)
|
admin.site.register(models.Contact, ContactAdmin)
|
||||||
admin.site.register(models.DomainInvitation, AuditedAdmin)
|
admin.site.register(models.DomainInvitation, DomainInvitationAdmin)
|
||||||
admin.site.register(models.DomainInformation, AuditedAdmin)
|
admin.site.register(models.DomainInformation, DomainInformationAdmin)
|
||||||
admin.site.register(models.Domain, DomainAdmin)
|
admin.site.register(models.Domain, DomainAdmin)
|
||||||
admin.site.register(models.Host, MyHostAdmin)
|
admin.site.register(models.Host, MyHostAdmin)
|
||||||
admin.site.register(models.Nameserver, MyHostAdmin)
|
admin.site.register(models.Nameserver, MyHostAdmin)
|
||||||
admin.site.register(models.Website, AuditedAdmin)
|
admin.site.register(models.Website, WebsiteAdmin)
|
||||||
admin.site.register(models.DomainApplication, DomainApplicationAdmin)
|
admin.site.register(models.DomainApplication, DomainApplicationAdmin)
|
||||||
admin.site.register(models.TransitionDomain, AuditedAdmin)
|
admin.site.register(models.TransitionDomain, AuditedAdmin)
|
||||||
|
|
|
@ -31,7 +31,7 @@ html[data-theme="light"] {
|
||||||
|
|
||||||
// #{$theme-link-color} would interpolate to 'primary', so we use the source value instead
|
// #{$theme-link-color} would interpolate to 'primary', so we use the source value instead
|
||||||
--link-fg: #{$theme-color-primary};
|
--link-fg: #{$theme-color-primary};
|
||||||
--link-hover-color: #{$theme-color-primary-darker};
|
--link-hover-color: #{$theme-color-primary};
|
||||||
// $theme-link-visited-color - violet-70v
|
// $theme-link-visited-color - violet-70v
|
||||||
--link-selected-fg: #54278f;
|
--link-selected-fg: #54278f;
|
||||||
|
|
||||||
|
@ -153,9 +153,12 @@ h1, h2, h3 {
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 'Delete button' layout bug
|
// Fix django admin button height bugs
|
||||||
.submit-row a.deletelink {
|
.submit-row a.deletelink,
|
||||||
|
.delete-confirmation form .cancel-link,
|
||||||
|
.submit-row a.closelink {
|
||||||
height: auto!important;
|
height: auto!important;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep th from collapsing
|
// Keep th from collapsing
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<div class="app-{{ app.app_label }} module{% if app.app_url in request.path|urlencode %} current-app{% endif %}">
|
<div class="app-{{ app.app_label }} module{% if app.app_url in request.path|urlencode %} current-app{% endif %}">
|
||||||
<table>
|
<table>
|
||||||
|
|
||||||
{# .gov override #}
|
{# .gov override: add headers #}
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{% if show_changelinks %}
|
{% if show_changelinks %}
|
||||||
|
|
|
@ -2,6 +2,24 @@
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block extrahead %}
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32"
|
||||||
|
href="{% static 'img/registrar/favicons/favicon-32.png' %}"
|
||||||
|
>
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192"
|
||||||
|
href="{% static 'img/registrar/favicons/favicon-192.png' %}"
|
||||||
|
>
|
||||||
|
<link rel="icon" type="image/svg+xml"
|
||||||
|
href="{% static 'img/registrar/favicons/favicon.svg' %}"
|
||||||
|
>
|
||||||
|
<link rel="shortcut icon" type="image/x-icon"
|
||||||
|
href="{% static 'img/registrar/favicons/favicon.ico' %}"
|
||||||
|
>
|
||||||
|
<link rel="apple-touch-icon" size="180x180"
|
||||||
|
href="{% static 'img/registrar/favicons/favicon-180.png' %}"
|
||||||
|
>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
|
{% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
|
||||||
|
|
||||||
{% block extrastyle %}{{ block.super }}
|
{% block extrastyle %}{{ block.super }}
|
||||||
|
|
|
@ -9,4 +9,4 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
20
src/registrar/templates/admin/change_form_no_submit.html
Normal file
20
src/registrar/templates/admin/change_form_no_submit.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{% extends "admin/change_form.html" %}
|
||||||
|
|
||||||
|
{% comment %} Replace the Django ul markup with a div. We'll edit the child markup accordingly in change_form_object_tools {% endcomment %}
|
||||||
|
{% block object-tools %}
|
||||||
|
{% if change and not is_popup %}
|
||||||
|
<div class="object-tools">
|
||||||
|
{% block object-tools-items %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block submit_buttons_top %}
|
||||||
|
{# Do not render the submit buttons #}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block submit_buttons_bottom %}
|
||||||
|
{# Do not render the submit buttons #}
|
||||||
|
{% endblock %}
|
Loading…
Add table
Add a link
Reference in a new issue