diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 63fb33095..cae9ae469 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -22,6 +22,7 @@ from django_fsm import TransitionNotAllowed # type: ignore logger = logging.getLogger(__name__) + # Based off of this excellent example: https://djangosnippets.org/snippets/10471/ class MultiFieldSortableChangeList(admin.views.main.ChangeList): """ @@ -41,6 +42,7 @@ class MultiFieldSortableChangeList(admin.views.main.ChangeList): ... """ + def get_ordering(self, request, queryset): """ Returns the list of ordering fields for the change list. @@ -51,17 +53,16 @@ class MultiFieldSortableChangeList(admin.views.main.ChangeList): ordering field. """ params = self.params - ordering = list(self.model_admin.get_ordering(request) - or self._get_default_ordering()) - + ordering = list(self.model_admin.get_ordering(request) or self._get_default_ordering()) + if ORDER_VAR in params: # Clear ordering and used params ordering = [] - order_params = params[ORDER_VAR].split('.') + order_params = params[ORDER_VAR].split(".") for p in order_params: try: - none, pfx, idx = p.rpartition('-') + none, pfx, idx = p.rpartition("-") field_name = self.list_display[int(idx)] order_fields = self.get_ordering_field(field_name) @@ -83,10 +84,10 @@ class MultiFieldSortableChangeList(admin.views.main.ChangeList): # ordering fields so we can guarantee a deterministic order across all # database backends. pk_name = self.lookup_opts.pk.name - if not (set(ordering) & set(['pk', '-pk', pk_name, '-' + pk_name])): + if not (set(ordering) & set(["pk", "-pk", pk_name, "-" + pk_name])): # The two sets do not intersect, meaning the pk isn't present. So # we add it. - ordering.append('-pk') + ordering.append("-pk") return ordering @@ -137,6 +138,16 @@ class AuditedAdmin(admin.ModelAdmin, AdminSortFields): class ListHeaderAdmin(AuditedAdmin): """Custom admin to add a descriptive subheader to list views.""" + def get_changelist(self, request, **kwargs): + """Returns a custom ChangeList class, as opposed to the default. + This is so we can override the behaviour of the `admin_order_field` field. + By default, django does not support ordering by multiple fields for this + particular field (i.e. self.admin_order_field=["first_name", "last_name"] is invalid). + + Reference: https://code.djangoproject.com/ticket/31975 + """ + return MultiFieldSortableChangeList + def changelist_view(self, request, extra_context=None): if extra_context is None: extra_context = {} @@ -628,9 +639,9 @@ class DomainApplicationAdmin(ListHeaderAdmin, OrderableFieldsMixin): ] orderable_fk_fields = [ - ('requested_domain', 'name'), - ("submitter", ["first_name"]), - ("investigator", "first_name"), + ("requested_domain", "name"), + ("submitter", ["first_name", "last_name"]), + ("investigator", ["first_name", "last_name"]), ] # Filters @@ -709,9 +720,6 @@ class DomainApplicationAdmin(ListHeaderAdmin, OrderableFieldsMixin): ] filter_horizontal = ("current_websites", "alternative_domains", "other_contacts") - - def get_changelist(self, request, **kwargs): - return MultiFieldSortableChangeList # lists in filter_horizontal are not sorted properly, sort them # by website diff --git a/src/registrar/views/utility/mixins.py b/src/registrar/views/utility/mixins.py index 31df1de46..13298f6a5 100644 --- a/src/registrar/views/utility/mixins.py +++ b/src/registrar/views/utility/mixins.py @@ -14,13 +14,14 @@ import logging logger = logging.getLogger(__name__) + class OrderableFieldsMixin: orderable_fk_fields = [] def __new__(cls, *args, **kwargs): new_class = super().__new__(cls) for field, sort_field in cls.orderable_fk_fields: - setattr(new_class, f'get_{field}', cls._create_orderable_field_method(field, sort_field)) + setattr(new_class, f"get_{field}", cls._create_orderable_field_method(field, sort_field)) return new_class @classmethod @@ -28,9 +29,19 @@ class OrderableFieldsMixin: def method(obj): attr = getattr(obj, field) return attr - method.__name__ = f'get_{field}' - method.admin_order_field = f'{field}__{sort_field}' - method.short_description = field.replace('_', ' ').title() + + method.__name__ = f"get_{field}" + + if isinstance(sort_field, list): + sort_list = [] + for sort_field_item in sort_field: + order_field_string = f"{field}__{sort_field_item}" + sort_list.append(order_field_string) + method.admin_order_field = sort_list + else: + method.admin_order_field = f"{field}__{sort_field}" + + method.short_description = field.replace("_", " ").title() return method