diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 7587407e1..7a3647582 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -41,20 +41,48 @@ class ListHeaderAdmin(AuditedAdmin): return super().changelist_view(request, extra_context=extra_context) def get_filters(self, request): + """Retrieve the current set of parameters being used to filter the table + Returns: + dictionary objects in the format {parameter_name: string, + parameter_value: string} + TODO: convert investigator id to investigator username + """ + filters = [] # Retrieve the filter parameters for param in request.GET.keys(): # Exclude the default search parameter 'q' if param != "q" and param != "o": - # Append the filter parameter and its value to the list - filters.append( - { - "parameter_name": param.replace("__exact", "") - .replace("_type", "") - .replace("__id", " id"), - "parameter_value": request.GET.get(param), - } + parameter_name = ( + param.replace("__exact", "") + .replace("_type", "") + .replace("__id", " id") ) + + if parameter_name == "investigator id": + # Retrieves the corresponding contact from Users + id_value = request.GET.get(param) + try: + contact = models.User.objects.get(id=id_value) + investigator_name = contact.first_name + " " + contact.last_name + + filters.append( + { + "parameter_name": "investigator", + "parameter_value": investigator_name, + } + ) + except models.User.DoesNotExist: + pass + else: + # For other parameter names, append a dictionary with the original + # parameter_name and the corresponding parameter_value + filters.append( + { + "parameter_name": parameter_name, + "parameter_value": request.GET.get(param), + } + ) return filters @@ -128,6 +156,7 @@ class DomainApplicationAdmin(ListHeaderAdmin): """Customize the applications listing view.""" + # Columns list_display = [ "requested_domain", "status", @@ -136,7 +165,11 @@ class DomainApplicationAdmin(ListHeaderAdmin): "submitter", "investigator", ] + + # Filters list_filter = ("status", "organization_type", "investigator") + + # Search search_fields = [ "requested_domain__name", "submitter__email", @@ -144,6 +177,8 @@ class DomainApplicationAdmin(ListHeaderAdmin): "submitter__last_name", ] search_help_text = "Search by domain or submitter." + + # Detail view fieldsets = [ (None, {"fields": ["status", "investigator", "creator"]}), ( @@ -192,6 +227,8 @@ class DomainApplicationAdmin(ListHeaderAdmin): {"fields": ["is_policy_acknowledged"]}, ), ] + + # Read only that we'll leverage for CISA Analysts readonly_fields = [ "creator", "type_of_work", @@ -240,7 +277,7 @@ class DomainApplicationAdmin(ListHeaderAdmin): def get_readonly_fields(self, request, obj=None): if request.user.is_superuser: # Superusers have full access, no fields are read-only - return () + return [] else: # Regular users can only view the specified fields return self.readonly_fields diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 552c7b621..90918c929 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -91,11 +91,11 @@ INSTALLED_APPS = [ # vv Required by django.contrib.admin vv # the "user" model! *\o/* "django.contrib.auth", - # generic interface for Django models - "auditlog", - # library to simplify form templating + # audit logging of changes to models # it needs to be listed before django.contrib.contenttypes # for a ContentType query in fixtures.py + "auditlog", + # generic interface for Django models "django.contrib.contenttypes", # required for CSRF protection and many other things "django.contrib.sessions", @@ -108,7 +108,7 @@ INSTALLED_APPS = [ "django.contrib.staticfiles", # application used for integrating with Login.gov "djangooidc", - # audit logging of changes to models + # library to simplify form templating "widget_tweaks", # library for Finite State Machine statuses "django_fsm", diff --git a/src/registrar/fixtures.py b/src/registrar/fixtures.py index 8e84c26e2..b47ed4aef 100644 --- a/src/registrar/fixtures.py +++ b/src/registrar/fixtures.py @@ -65,6 +65,11 @@ class UserFixture: "first_name": "Rachid-Analyst", "last_name": "Mrad-Analyst", }, + { + "username": "b6a15987-5c88-4e26-8de2-ca71a0bdb2cd", + "first_name": "Alysia-Analyst", + "last_name": "Alysia-Analyst", + }, ] STAFF_PERMISSIONS = [ @@ -99,7 +104,7 @@ class UserFixture: logger.debug("User object created for %s" % admin["first_name"]) except Exception as e: logger.warning(e) - logger.debug("All superusers loaded.") + logger.info("All superusers loaded.") logger.info("Going to load %s CISA analysts (staff)" % str(len(cls.STAFF))) for staff in cls.STAFF: @@ -150,7 +155,7 @@ class UserFixture: logger.debug("User object created for %s" % staff["first_name"]) except Exception as e: logger.warning(e) - logger.debug("All CISA analysts (staff) loaded.") + logger.info("All CISA analysts (staff) loaded.") class DomainApplicationFixture: diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index dfc0787af..b0daa98b3 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -8,7 +8,7 @@ from typing import List, Dict from django.conf import settings from django.contrib.auth import get_user_model, login -from registrar.models import Contact, DraftDomain, Website, DomainApplication +from registrar.models import Contact, DraftDomain, Website, DomainApplication, User def get_handlers(): @@ -157,3 +157,17 @@ def completed_application( application.alternative_domains.add(alt) return application + +def mock_user(): + """A simple user.""" + user_kwargs = dict( + id = 4, + first_name = "Rachid", + last_name = "Mrad", + ) + + user, _ = User.objects.get_or_create( + **user_kwargs + ) + + return user diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index f109e1b46..295268b48 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -2,7 +2,7 @@ from django.test import TestCase, RequestFactory, Client from django.contrib.admin.sites import AdminSite from registrar.admin import DomainApplicationAdmin, ListHeaderAdmin from registrar.models import DomainApplication, DomainInformation, User -from .common import completed_application +from .common import completed_application, mock_user from django.contrib.auth import get_user_model from django.conf import settings @@ -183,13 +183,16 @@ class TestDomainApplicationAdmin(TestCase): # Have to get creative to get past linter p = "adminpassword" self.client.login(username="admin", password=p) + + # Mock a user + user = mock_user() # Make the request using the Client class # which handles CSRF # Follow=True handles the redirect response = self.client.get( "/admin/registrar/domainapplication/", - {"status__exact": "started", "investigator__id__exact": "4", "q": "Hello"}, + {"status__exact": "started", "investigator__id__exact": user.id, "q": "Hello"}, follow=True, ) @@ -204,7 +207,7 @@ class TestDomainApplicationAdmin(TestCase): filters, [ {"parameter_name": "status", "parameter_value": "started"}, - {"parameter_name": "investigator id", "parameter_value": "4"}, + {"parameter_name": "investigator", "parameter_value": user.first_name + " " + user.last_name}, ], ) @@ -212,7 +215,7 @@ class TestDomainApplicationAdmin(TestCase): # Create a mock request object request = self.factory.get("/admin/yourmodel/") # Set the GET parameters for testing - request.GET = {"status": "started", "investigator id": "4", "q": "search_value"} + request.GET = {"status": "started", "investigator": "Rachid Mrad", "q": "search_value"} # Call the get_filters method filters = self.admin.get_filters(request) @@ -221,11 +224,12 @@ class TestDomainApplicationAdmin(TestCase): filters, [ {"parameter_name": "status", "parameter_value": "started"}, - {"parameter_name": "investigator id", "parameter_value": "4"}, + {"parameter_name": "investigator", "parameter_value": "Rachid Mrad"}, ], ) def tearDown(self): # delete any applications too DomainApplication.objects.all().delete() + User.objects.all().delete() self.superuser.delete()