Merge pull request #1791 from cisagov/dk/1786-domain-columns

Issue #1786: django admin updates for domain and application lists
This commit is contained in:
dave-kennedy-ecs 2024-02-26 19:41:58 -05:00 committed by GitHub
commit 1cfb4958b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 237 additions and 125 deletions

View file

@ -3,7 +3,7 @@ import logging
from django import forms
from django.db.models.functions import Concat, Coalesce
from django.db.models import Value, CharField
from django.db.models import Value, CharField, Q
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import redirect
from django_fsm import get_available_FIELD_transitions
@ -24,7 +24,7 @@ from auditlog.admin import LogEntryAdmin # type: ignore
from django_fsm import TransitionNotAllowed # type: ignore
from django.utils.safestring import mark_safe
from django.utils.html import escape
from django.utils.translation import gettext_lazy as _
logger = logging.getLogger(__name__)
@ -855,11 +855,35 @@ class DomainApplicationAdmin(ListHeaderAdmin):
else:
return queryset.filter(investigator__id__exact=self.value())
class ElectionOfficeFilter(admin.SimpleListFilter):
"""Define a custom filter for is_election_board"""
title = _("election office")
parameter_name = "is_election_board"
def lookups(self, request, model_admin):
return (
("1", _("Yes")),
("0", _("No")),
)
def queryset(self, request, queryset):
if self.value() == "1":
return queryset.filter(is_election_board=True)
if self.value() == "0":
return queryset.filter(Q(is_election_board=False) | Q(is_election_board=None))
# Columns
list_display = [
"requested_domain",
"status",
"organization_type",
"federal_type",
"federal_agency",
"organization_name",
"custom_election_board",
"city",
"state_territory",
"created_at",
"submitter",
"investigator",
@ -871,8 +895,21 @@ class DomainApplicationAdmin(ListHeaderAdmin):
("investigator", ["first_name", "last_name"]),
]
def custom_election_board(self, obj):
return "Yes" if obj.is_election_board else "No"
custom_election_board.admin_order_field = "is_election_board" # type: ignore
custom_election_board.short_description = "Election office" # type: ignore
# Filters
list_filter = ("status", "organization_type", "rejection_reason", InvestigatorFilter)
list_filter = (
"status",
"organization_type",
"federal_type",
ElectionOfficeFilter,
"rejection_reason",
InvestigatorFilter,
)
# Search
search_fields = [
@ -1142,12 +1179,37 @@ class DomainInformationInline(admin.StackedInline):
class DomainAdmin(ListHeaderAdmin):
"""Custom domain admin class to add extra buttons."""
class ElectionOfficeFilter(admin.SimpleListFilter):
"""Define a custom filter for is_election_board"""
title = _("election office")
parameter_name = "is_election_board"
def lookups(self, request, model_admin):
return (
("1", _("Yes")),
("0", _("No")),
)
def queryset(self, request, queryset):
logger.debug(self.value())
if self.value() == "1":
return queryset.filter(domain_info__is_election_board=True)
if self.value() == "0":
return queryset.filter(Q(domain_info__is_election_board=False) | Q(domain_info__is_election_board=None))
inlines = [DomainInformationInline]
# Columns
list_display = [
"name",
"organization_type",
"federal_type",
"federal_agency",
"organization_name",
"custom_election_board",
"city",
"state_territory",
"state",
"expiration_date",
"created_at",
@ -1171,8 +1233,42 @@ class DomainAdmin(ListHeaderAdmin):
organization_type.admin_order_field = "domain_info__organization_type" # type: ignore
def federal_agency(self, obj):
return obj.domain_info.federal_agency if obj.domain_info else None
federal_agency.admin_order_field = "domain_info__federal_agency" # type: ignore
def federal_type(self, obj):
return obj.domain_info.federal_type if obj.domain_info else None
federal_type.admin_order_field = "domain_info__federal_type" # type: ignore
def organization_name(self, obj):
return obj.domain_info.organization_name if obj.domain_info else None
organization_name.admin_order_field = "domain_info__organization_name" # type: ignore
def custom_election_board(self, obj):
domain_info = getattr(obj, "domain_info", None)
if domain_info:
return "Yes" if domain_info.is_election_board else "No"
return "No"
custom_election_board.admin_order_field = "domain_info__is_election_board" # type: ignore
custom_election_board.short_description = "Election office" # type: ignore
def city(self, obj):
return obj.domain_info.city if obj.domain_info else None
city.admin_order_field = "domain_info__city" # type: ignore
def state_territory(self, obj):
return obj.domain_info.state_territory if obj.domain_info else None
state_territory.admin_order_field = "domain_info__state_territory" # type: ignore
# Filters
list_filter = ["domain_info__organization_type", "state"]
list_filter = ["domain_info__organization_type", "domain_info__federal_type", ElectionOfficeFilter, "state"]
search_fields = ["name"]
search_help_text = "Search by domain name."

View file

@ -15,7 +15,15 @@
{% if filters %}
filtered by
{% for filter_param in filters %}
{% if filter_param.parameter_name == 'is_election_board' %}
{%if filter_param.parameter_value == '0' %}
election office = No
{% else %}
election office = Yes
{% endif %}
{% else %}
{{ filter_param.parameter_name }} = {{ filter_param.parameter_value }}
{% endif %}
{% if not forloop.last %}, {% endif %}
{% endfor %}
{% endif %}

View file

@ -243,9 +243,9 @@ class TestDomainAdmin(MockEppLib, WebTest):
response = self.client.get("/admin/registrar/domain/")
# There are 3 template references to Federal (3) plus one reference in the table
# There are 4 template references to Federal (4) plus four references in the table
# for our actual application
self.assertContains(response, "Federal", count=4)
self.assertContains(response, "Federal", count=8)
# This may be a bit more robust
self.assertContains(response, '<td class="field-organization_type">Federal</td>', count=1)
# Now let's make sure the long description does not exist
@ -532,7 +532,7 @@ class TestDomainApplicationAdmin(MockEppLib):
# Assert that our sort works correctly
self.test_helper.assert_table_sorted(
"5",
"11",
(
"submitter__first_name",
"submitter__last_name",
@ -541,7 +541,7 @@ class TestDomainApplicationAdmin(MockEppLib):
# Assert that sorting in reverse works correctly
self.test_helper.assert_table_sorted(
"-5",
"-11",
(
"-submitter__first_name",
"-submitter__last_name",
@ -586,9 +586,9 @@ class TestDomainApplicationAdmin(MockEppLib):
self.client.force_login(self.superuser)
completed_application()
response = self.client.get("/admin/registrar/domainapplication/")
# There are 3 template references to Federal (3) plus one reference in the table
# There are 4 template references to Federal (4) plus two references in the table
# for our actual application
self.assertContains(response, "Federal", count=4)
self.assertContains(response, "Federal", count=6)
# This may be a bit more robust
self.assertContains(response, '<td class="field-organization_type">Federal</td>', count=1)
# Now let's make sure the long description does not exist
@ -1070,8 +1070,8 @@ class TestDomainApplicationAdmin(MockEppLib):
self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
def test_save_model_sets_approved_domain(self):
# make sure there is no user with this email
with less_console_noise():
# make sure there is no user with this email
EMAIL = "mayor@igorville.gov"
User.objects.filter(email=EMAIL).delete()
@ -1082,7 +1082,6 @@ class TestDomainApplicationAdmin(MockEppLib):
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
with less_console_noise():
# Modify the application's property
application.status = DomainApplication.ApplicationStatus.APPROVED
@ -1343,6 +1342,8 @@ class TestDomainApplicationAdmin(MockEppLib):
expected_fields = (
"status",
"organization_type",
"federal_type",
DomainApplicationAdmin.ElectionOfficeFilter,
"rejection_reason",
DomainApplicationAdmin.InvestigatorFilter,
)
@ -1622,8 +1623,8 @@ class TestDomainInformationAdmin(TestCase):
User.objects.all().delete()
def test_readonly_fields_for_analyst(self):
with less_console_noise():
"""Ensures that analysts have their permissions setup correctly"""
with less_console_noise():
request = self.factory.get("/")
request.user = self.staffuser
@ -1931,6 +1932,7 @@ class AuditedAdminTest(TestCase):
self.client = Client(HTTP_HOST="localhost:8080")
def order_by_desired_field_helper(self, obj_to_sort: AuditedAdmin, request, field_name, *obj_names):
with less_console_noise():
formatted_sort_fields = []
for obj in obj_names:
formatted_sort_fields.append("{}__{}".format(field_name, obj))
@ -2319,7 +2321,6 @@ class ContactAdminTest(TestCase):
def test_change_view_for_joined_contact_five_or_less(self):
"""Create a contact, join it to 4 domain requests. The 5th join will be a user.
Assert that the warning on the contact form lists 5 joins."""
with less_console_noise():
self.client.force_login(self.superuser)

View file

@ -321,13 +321,13 @@ class TestDomainCreation(MockEppLib):
Then a Domain exists in the database with the same `name`
But a domain object does not exist in the registry
"""
with less_console_noise():
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
user, _ = User.objects.get_or_create()
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
mock_client = MockSESClient()
with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise():
# skip using the submit method
application.status = DomainApplication.ApplicationStatus.SUBMITTED
# transition to approve state
@ -346,6 +346,7 @@ class TestDomainCreation(MockEppLib):
And `domain.state` is set to `UNKNOWN`
And `domain.is_active()` returns False
"""
with less_console_noise():
domain = Domain.objects.create(name="beef-tongue.gov")
# trigger getter
_ = domain.statuses
@ -382,6 +383,7 @@ class TestDomainCreation(MockEppLib):
def test_minimal_creation(self):
"""Can create with just a name."""
with less_console_noise():
Domain.objects.create(name="igorville.gov")
@skip("assertion broken with mock addition")
@ -504,6 +506,7 @@ class TestDomainAvailable(MockEppLib):
res_data=[responses.check.CheckDomainResultData(name="available.gov", avail=True, reason=None)],
)
with less_console_noise():
patcher = patch("registrar.models.domain.registry.send")
mocked_send = patcher.start()
mocked_send.side_effect = side_effect
@ -537,6 +540,7 @@ class TestDomainAvailable(MockEppLib):
res_data=[responses.check.CheckDomainResultData(name="unavailable.gov", avail=False, reason="In Use")],
)
with less_console_noise():
patcher = patch("registrar.models.domain.registry.send")
mocked_send = patcher.start()
mocked_send.side_effect = side_effect
@ -562,6 +566,7 @@ class TestDomainAvailable(MockEppLib):
Validate InvalidDomainError is raised
"""
with less_console_noise():
with self.assertRaises(errors.InvalidDomainError):
Domain.available("invalid-string")
@ -572,6 +577,7 @@ class TestDomainAvailable(MockEppLib):
Validate InvalidDomainError is raised
"""
with less_console_noise():
with self.assertRaises(errors.InvalidDomainError):
Domain.available("")
@ -585,6 +591,7 @@ class TestDomainAvailable(MockEppLib):
def side_effect(_request, cleaned):
raise RegistryError(code=ErrorCode.COMMAND_SYNTAX_ERROR)
with less_console_noise():
patcher = patch("registrar.models.domain.registry.send")
mocked_send = patcher.start()
mocked_send.side_effect = side_effect