Repository: cisagov/manage.get.gov

Issue:
On Domain requests table, by default, show only domains that are in an active state
### Issue description

When picking up a new domain request, analysts don't initially need to see requests in a `Started` state because they are not yet `Submitted` and may not have been completed by the requester. Similarly, they don't need to see `Approved`, `Rejected` or `Ineligible`, all of which are in a non-active request status.

### Solution:
The default view in the `Domain applications` (soon `Domain requests`) table in DjA should **exclude** domains in a non-active request state. However, it should still be possible for analysts to see them of they so choose. This will require a multi-select filter.

### Acceptance criteria

- [ ] On the domain requests list page, the domain status filter allows for multi-selections.
- [ ] The default view (first render) in the `Domain requests` displays with the domain status filter set to include only `submitted`, `in review`, and `action needed` statuses.
- [ ] A selection is available to clear or modify all filter settings, so that all request appear for the analyst.

### Additional context
[Slack discussion ](https://cisa-corp.slack.com/archives/C05TS76RVS6/p1709053285399059)

<img width="628" alt="image" src="https://github.com/cisagov/manage.get.gov/assets/603901/cdf4b83a-26dd-4714-b03a-5329b2f01914">


### Links to other issues

🔄 #1837 


Discussion:
@rachidatecs - is it possible/easy to create a compound filter, allowing for include as well as exclude? (Not sure how difficult this is with the DjA toolbox)
Edited the original description to include from a multi select on the filter, as well as clarifying the default settings for first rendering of the page.
Action item from 3/6 product huddle met. Removed "refinement" tag.

Resolving patch:
diff --git a/src/registrar/admin.py b/src/registrar/admin.py
--- a/src/registrar/admin.py
+++ b/src/registrar/admin.py
@@ -27,6 +27,7 @@
 from django.utils.safestring import mark_safe
 from django.utils.html import escape
 from django.contrib.auth.forms import UserChangeForm, UsernameField
+from django_admin_multiple_choice_list_filter.list_filters import MultipleChoiceListFilter
 
 from django.utils.translation import gettext_lazy as _
 
@@ -981,6 +982,17 @@ class DomainRequestAdmin(ListHeaderAdmin):
     form = DomainRequestAdminForm
     change_form_template = "django/admin/domain_request_change_form.html"
 
+    class StatusListFilter(MultipleChoiceListFilter):
+        """Custom status filter which is a multiple choice filter"""
+
+        title = "Status"
+        parameter_name = "status__in"
+
+        template = "django/admin/multiple_choice_list_filter.html"
+
+        def lookups(self, request, model_admin):
+            return DomainRequest.DomainRequestStatus.choices
+
     class InvestigatorFilter(admin.SimpleListFilter):
         """Custom investigator filter that only displays users with the manager role"""
 
@@ -1071,7 +1083,7 @@ def custom_election_board(self, obj):
 
     # Filters
     list_filter = (
-        "status",
+        StatusListFilter,
         "generic_org_type",
         "federal_type",
         ElectionOfficeFilter,
@@ -1354,6 +1366,23 @@ def display_restricted_warning(self, request, obj):
                 "Cannot edit a domain request with a restricted creator.",
             )
 
+    def changelist_view(self, request, extra_context=None):
+        """
+        Override changelist_view to set the selected value of status filter.
+        """
+        # use http_referer in order to distinguish between request as a link from another page
+        # and request as a removal of all filters
+        http_referer = request.META.get("HTTP_REFERER", "")
+        # if there are no query parameters in the request
+        # and the request is the initial request for this view
+        if not bool(request.GET) and request.path not in http_referer:
+            # modify the GET of the request to set the selected filter
+            modified_get = copy.deepcopy(request.GET)
+            modified_get["status__in"] = "submitted,in review,action needed"
+            request.GET = modified_get
+        response = super().changelist_view(request, extra_context=extra_context)
+        return response
+
     def change_view(self, request, object_id, form_url="", extra_context=None):
         obj = self.get_object(request, object_id)
         self.display_restricted_warning(request, obj)
diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py
--- a/src/registrar/config/settings.py
+++ b/src/registrar/config/settings.py
@@ -146,6 +146,8 @@
     # "puml_generator",
     # supports necessary headers for Django cross origin
     "corsheaders",
+    # library for multiple choice filters in django admin
+    "django_admin_multiple_choice_list_filter",
 ]
 
 # Middleware are routines for processing web requests.


Test patch:
diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py
--- a/src/registrar/tests/test_admin.py
+++ b/src/registrar/tests/test_admin.py
@@ -706,15 +706,26 @@ def test_short_org_name_in_domain_requests_list(self):
         with less_console_noise():
             self.client.force_login(self.superuser)
             completed_domain_request()
-            response = self.client.get("/admin/registrar/domainrequest/")
-            # There are 4 template references to Federal (4) plus two references in the table
-            # for our actual domain request
+            response = self.client.get("/admin/registrar/domainrequest/?generic_org_type__exact=federal")
+            # There are 2 template references to Federal (4) and two in the results data
+            # of the request
             self.assertContains(response, "Federal", count=34)
             # This may be a bit more robust
             self.assertContains(response, '<td class="field-generic_org_type">Federal</td>', count=1)
             # Now let's make sure the long description does not exist
             self.assertNotContains(response, "Federal: an agency of the U.S. government")
 
+    def test_default_status_in_domain_requests_list(self):
+        """
+        Make sure the default status in admin is selected on the domain requests list page
+        """
+        with less_console_noise():
+            self.client.force_login(self.superuser)
+            completed_domain_request()
+            response = self.client.get("/admin/registrar/domainrequest/")
+            # The results are filtered by "status in [submitted,in review,action needed]"
+            self.assertContains(response, "status in [submitted,in review,action needed]", count=1)
+
     def transition_state_and_send_email(self, domain_request, status, rejection_reason=None):
         """Helper method for the email test cases."""
 
@@ -1719,7 +1730,7 @@ def test_has_correct_filters(self):
             # Grab the current list of table filters
             readonly_fields = self.admin.get_list_filter(request)
             expected_fields = (
-                "status",
+                DomainRequestAdmin.StatusListFilter,
                 "generic_org_type",
                 "federal_type",
                 DomainRequestAdmin.ElectionOfficeFilter,