Merge branch 'main' into za/1456-sorting-not-working-correctly

This commit is contained in:
zandercymatics 2023-12-27 09:11:35 -07:00
commit c01b241ea3
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
2 changed files with 261 additions and 10 deletions

View file

@ -12,6 +12,7 @@ from django.http.response import HttpResponseRedirect
from django.urls import reverse from django.urls import reverse
from epplibwrapper.errors import ErrorCode, RegistryError from epplibwrapper.errors import ErrorCode, RegistryError
from registrar.models.domain import Domain from registrar.models.domain import Domain
from registrar.models.user import User
from registrar.utility import csv_export from registrar.utility import csv_export
from registrar.views.utility.mixins import OrderableFieldsMixin from registrar.views.utility.mixins import OrderableFieldsMixin
from django.contrib.admin.views.main import ORDER_VAR from django.contrib.admin.views.main import ORDER_VAR
@ -628,6 +629,9 @@ class DomainInformationAdmin(ListHeaderAdmin):
# to activate the edit/delete/view buttons # to activate the edit/delete/view buttons
filter_horizontal = ("other_contacts",) filter_horizontal = ("other_contacts",)
# Table ordering
ordering = ["domain__name"]
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
"""Set the read-only state on form elements. """Set the read-only state on form elements.
We have 1 conditions that determine which fields are read-only: We have 1 conditions that determine which fields are read-only:
@ -679,6 +683,27 @@ class DomainApplicationAdmin(ListHeaderAdmin):
"""Custom domain applications admin class.""" """Custom domain applications admin class."""
class InvestigatorFilter(admin.SimpleListFilter):
"""Custom investigator filter that only displays users with the manager role"""
title = "investigator"
# Match the old param name to avoid unnecessary refactoring
parameter_name = "investigator__id__exact"
def lookups(self, request, model_admin):
"""Lookup reimplementation, gets users of is_staff.
Returns a list of tuples consisting of (user.id, user)
"""
privileged_users = User.objects.filter(is_staff=True).order_by("first_name", "last_name", "email")
return [(user.id, user) for user in privileged_users]
def queryset(self, request, queryset):
"""Custom queryset implementation, filters by investigator"""
if self.value() is None:
return queryset
else:
return queryset.filter(investigator__id__exact=self.value())
# Columns # Columns
list_display = [ list_display = [
"requested_domain", "requested_domain",
@ -696,7 +721,7 @@ class DomainApplicationAdmin(ListHeaderAdmin):
] ]
# Filters # Filters
list_filter = ("status", "organization_type", "investigator") list_filter = ("status", "organization_type", InvestigatorFilter)
# Search # Search
search_fields = [ search_fields = [
@ -772,6 +797,23 @@ class DomainApplicationAdmin(ListHeaderAdmin):
filter_horizontal = ("current_websites", "alternative_domains", "other_contacts") filter_horizontal = ("current_websites", "alternative_domains", "other_contacts")
# Table ordering
ordering = ["requested_domain__name"]
# lists in filter_horizontal are not sorted properly, sort them
# by website
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name in ("current_websites", "alternative_domains"):
kwargs["queryset"] = models.Website.objects.all().order_by("website") # Sort websites
return super().formfield_for_manytomany(db_field, request, **kwargs)
def formfield_for_foreignkey(self, db_field, request, **kwargs):
# Removes invalid investigator options from the investigator dropdown
if db_field.name == "investigator":
kwargs["queryset"] = User.objects.filter(is_staff=True)
return db_field.formfield(**kwargs)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
# Trigger action when a fieldset is changed # Trigger action when a fieldset is changed
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
if obj and obj.creator.status != models.User.RESTRICTED: if obj and obj.creator.status != models.User.RESTRICTED:
@ -961,6 +1003,9 @@ class DomainAdmin(ListHeaderAdmin):
change_list_template = "django/admin/domain_change_list.html" change_list_template = "django/admin/domain_change_list.html"
readonly_fields = ["state", "expiration_date"] readonly_fields = ["state", "expiration_date"]
# Table ordering
ordering = ["name"]
def export_data_type(self, request): def export_data_type(self, request):
# match the CSV example with all the fields # match the CSV example with all the fields
response = HttpResponse(content_type="text/csv") response = HttpResponse(content_type="text/csv")

View file

@ -15,14 +15,7 @@ from registrar.admin import (
DomainInformationAdmin, DomainInformationAdmin,
UserDomainRoleAdmin, UserDomainRoleAdmin,
) )
from registrar.models import ( from registrar.models import Domain, DomainApplication, DomainInformation, User, DomainInvitation, Contact, Website
Domain,
DomainApplication,
DomainInformation,
User,
DomainInvitation,
Contact,
)
from registrar.models.user_domain_role import UserDomainRole from registrar.models.user_domain_role import UserDomainRole
from .common import ( from .common import (
AuditedAdminMockData, AuditedAdminMockData,
@ -325,6 +318,7 @@ class TestDomainApplicationAdmin(MockEppLib):
self.admin = DomainApplicationAdmin(model=DomainApplication, admin_site=self.site) self.admin = DomainApplicationAdmin(model=DomainApplication, admin_site=self.site)
self.superuser = create_superuser() self.superuser = create_superuser()
self.staffuser = create_user() self.staffuser = create_user()
self.client = Client(HTTP_HOST="localhost:8080")
self.test_helper = GenericTestHelper( self.test_helper = GenericTestHelper(
factory=self.factory, factory=self.factory,
user=self.superuser, user=self.superuser,
@ -924,12 +918,224 @@ class TestDomainApplicationAdmin(MockEppLib):
with self.assertRaises(DomainInformation.DoesNotExist): with self.assertRaises(DomainInformation.DoesNotExist):
domain_information.refresh_from_db() domain_information.refresh_from_db()
def test_has_correct_filters(self):
"""
This test verifies that DomainApplicationAdmin has the correct filters set up.
It retrieves the current list of filters from DomainApplicationAdmin
and checks that it matches the expected list of filters.
"""
request = self.factory.get("/")
request.user = self.superuser
# Grab the current list of table filters
readonly_fields = self.admin.get_list_filter(request)
expected_fields = ("status", "organization_type", DomainApplicationAdmin.InvestigatorFilter)
self.assertEqual(readonly_fields, expected_fields)
def test_table_sorted_alphabetically(self):
"""
This test verifies that the DomainApplicationAdmin table is sorted alphabetically
by the 'requested_domain__name' field.
It creates a list of DomainApplication instances in a non-alphabetical order,
then retrieves the queryset from the DomainApplicationAdmin and checks
that it matches the expected queryset,
which is sorted alphabetically by the 'requested_domain__name' field.
"""
# Creates a list of DomainApplications in scrambled order
multiple_unalphabetical_domain_objects("application")
request = self.factory.get("/")
request.user = self.superuser
# Get the expected list of alphabetically sorted DomainApplications
expected_order = DomainApplication.objects.order_by("requested_domain__name")
# Get the returned queryset
queryset = self.admin.get_queryset(request)
# Check the order
self.assertEqual(
list(queryset),
list(expected_order),
)
def test_displays_investigator_filter(self):
"""
This test verifies that the investigator filter in the admin interface for
the DomainApplication model displays correctly.
It creates two DomainApplication instances, each with a different investigator.
It then simulates a staff user logging in and applying the investigator filter
on the DomainApplication admin page.
We then test if the page displays the filter we expect, but we do not test
if we get back the correct response in the table. This is to isolate if
the filter displays correctly, when the filter isn't filtering correctly.
"""
# Create a mock DomainApplication object, with a fake investigator
application: DomainApplication = generic_domain_object("application", "SomeGuy")
investigator_user = User.objects.filter(username=application.investigator.username).get()
investigator_user.is_staff = True
investigator_user.save()
p = "userpass"
self.client.login(username="staffuser", password=p)
response = self.client.get(
"/admin/registrar/domainapplication/",
{
"investigator__id__exact": investigator_user.id,
},
follow=True,
)
# Then, test if the filter actually exists
self.assertIn("filters", response.context)
# Assert the content of filters and search_query
filters = response.context["filters"]
self.assertEqual(
filters,
[
{
"parameter_name": "investigator",
"parameter_value": "SomeGuy first_name:investigator SomeGuy last_name:investigator",
},
],
)
def test_investigator_filter_filters_correctly(self):
"""
This test verifies that the investigator filter in the admin interface for
the DomainApplication model works correctly.
It creates two DomainApplication instances, each with a different investigator.
It then simulates a staff user logging in and applying the investigator filter
on the DomainApplication admin page.
It then verifies that it was applied correctly.
The test checks that the response contains the expected DomainApplication pbjects
in the table.
"""
# Create a mock DomainApplication object, with a fake investigator
application: DomainApplication = generic_domain_object("application", "SomeGuy")
investigator_user = User.objects.filter(username=application.investigator.username).get()
investigator_user.is_staff = True
investigator_user.save()
# Create a second mock DomainApplication object, to test filtering
application: DomainApplication = generic_domain_object("application", "BadGuy")
another_user = User.objects.filter(username=application.investigator.username).get()
another_user.is_staff = True
another_user.save()
p = "userpass"
self.client.login(username="staffuser", password=p)
response = self.client.get(
"/admin/registrar/domainapplication/",
{
"investigator__id__exact": investigator_user.id,
},
follow=True,
)
expected_name = "SomeGuy first_name:investigator SomeGuy last_name:investigator"
# We expect to see this four times, two of them are from the html for the filter,
# and the other two are the html from the list entry in the table.
self.assertContains(response, expected_name, count=4)
# Check that we don't also get the thing we aren't filtering for.
# We expect to see this two times in the filter
unexpected_name = "BadGuy first_name:investigator BadGuy last_name:investigator"
self.assertContains(response, unexpected_name, count=2)
def test_investigator_dropdown_displays_only_staff(self):
"""
This test verifies that the dropdown for the 'investigator' field in the DomainApplicationAdmin
interface only displays users who are marked as staff.
It creates two DomainApplication instances, one with an investigator
who is a staff user and another with an investigator who is not a staff user.
It then retrieves the queryset for the 'investigator' dropdown from DomainApplicationAdmin
and checks that it matches the expected queryset, which only includes staff users.
"""
# Create a mock DomainApplication object, with a fake investigator
application: DomainApplication = generic_domain_object("application", "SomeGuy")
investigator_user = User.objects.filter(username=application.investigator.username).get()
investigator_user.is_staff = True
investigator_user.save()
# Create a mock DomainApplication object, with a user that is not staff
application_2: DomainApplication = generic_domain_object("application", "SomeOtherGuy")
investigator_user_2 = User.objects.filter(username=application_2.investigator.username).get()
investigator_user_2.is_staff = False
investigator_user_2.save()
p = "userpass"
self.client.login(username="staffuser", password=p)
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
# Get the actual field from the model's meta information
investigator_field = DomainApplication._meta.get_field("investigator")
# We should only be displaying staff users, in alphabetical order
expected_dropdown = list(User.objects.filter(is_staff=True))
current_dropdown = list(self.admin.formfield_for_foreignkey(investigator_field, request).queryset)
self.assertEqual(expected_dropdown, current_dropdown)
# Non staff users should not be in the list
self.assertNotIn(application_2, current_dropdown)
def test_investigator_list_is_alphabetically_sorted(self):
"""
This test verifies that filter list for the 'investigator'
is displayed alphabetically
"""
# Create a mock DomainApplication object, with a fake investigator
application: DomainApplication = generic_domain_object("application", "SomeGuy")
investigator_user = User.objects.filter(username=application.investigator.username).get()
investigator_user.is_staff = True
investigator_user.save()
application_2: DomainApplication = generic_domain_object("application", "AGuy")
investigator_user_2 = User.objects.filter(username=application_2.investigator.username).get()
investigator_user_2.first_name = "AGuy"
investigator_user_2.is_staff = True
investigator_user_2.save()
application_3: DomainApplication = generic_domain_object("application", "FinalGuy")
investigator_user_3 = User.objects.filter(username=application_3.investigator.username).get()
investigator_user_3.first_name = "FinalGuy"
investigator_user_3.is_staff = True
investigator_user_3.save()
p = "userpass"
self.client.login(username="staffuser", password=p)
request = RequestFactory().get("/")
expected_list = list(User.objects.filter(is_staff=True).order_by("first_name", "last_name", "email"))
# Get the actual sorted list of investigators from the lookups method
actual_list = [item for _, item in self.admin.InvestigatorFilter.lookups(self, request, self.admin)]
self.assertEqual(expected_list, actual_list)
def tearDown(self): def tearDown(self):
super().tearDown() super().tearDown()
Domain.objects.all().delete() Domain.objects.all().delete()
DomainInformation.objects.all().delete() DomainInformation.objects.all().delete()
DomainApplication.objects.all().delete() DomainApplication.objects.all().delete()
User.objects.all().delete() User.objects.all().delete()
Contact.objects.all().delete()
Website.objects.all().delete()
class DomainInvitationAdminTest(TestCase): class DomainInvitationAdminTest(TestCase):