diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 5fe6f3416..4874998eb 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -27,7 +27,7 @@ logger = logging.getLogger(__name__) class MultiFieldSortableChangeList(admin.views.main.ChangeList): """ This class overrides the behavior of column sorting in django admin tables in order - to allow multi field sorting + to allow for multi field sorting on admin_order_field Usage: @@ -46,11 +46,9 @@ class MultiFieldSortableChangeList(admin.views.main.ChangeList): def get_ordering(self, request, queryset): """ Returns the list of ordering fields for the change list. - First we check the get_ordering() method in model admin, then we check - the object's default ordering. Then, any manually-specified ordering - from the query string overrides anything. Finally, a deterministic - order is guaranteed by ensuring the primary key is used as the last - ordering field. + + Mostly identical to the base implementation, except that now it can return + a list of order_field objects rather than just one. """ params = self.params ordering = list(self.model_admin.get_ordering(request) or self._get_default_ordering()) @@ -74,7 +72,7 @@ class MultiFieldSortableChangeList(admin.views.main.ChangeList): else: ordering.append(pfx + order_fields) - except (IndexError, ValueError) as err: + except (IndexError, ValueError): continue # Invalid ordering specified, skip it. # Add the given query's ordering fields, if any. @@ -423,11 +421,10 @@ class UserDomainRoleAdmin(ListHeaderAdmin): _meta = Meta() - # TODO - maybe instead of get we just call it "sort"? # Columns list_display = [ - "get_user", - "get_domain", + "user", + "domain", "role", ] @@ -496,15 +493,13 @@ class DomainInvitationAdmin(ListHeaderAdmin): class DomainInformationAdmin(ListHeaderAdmin): """Customize domain information admin class.""" - - # TODO - include the orderable fk fields inside list display # Columns list_display = [ - "get_domain", + "domain", "organization_type", "created_at", - "get_submitter", + "submitter", ] orderable_fk_fields = [ @@ -643,12 +638,12 @@ class DomainApplicationAdmin(ListHeaderAdmin): # Columns list_display = [ - "get_requested_domain", + "requested_domain", "status", "organization_type", "created_at", - "get_submitter", - "get_investigator", + "submitter", + "investigator", ] orderable_fk_fields = [ diff --git a/src/registrar/views/utility/mixins.py b/src/registrar/views/utility/mixins.py index be7c59f95..3fb1ce4b1 100644 --- a/src/registrar/views/utility/mixins.py +++ b/src/registrar/views/utility/mixins.py @@ -27,17 +27,37 @@ class OrderableFieldsMixin: It dynamically adds a new method to the class for each field in `orderable_fk_fields`. """ new_class = super().__new__(cls) + + # Check if the list_display attribute exists, and if it does, create a local copy of that list. + list_display_exists = hasattr(cls, "list_display") + new_list_display = [] + if list_display_exists and isinstance(cls.list_display, list): + new_list_display = cls.list_display.copy() + for field, sort_field in cls.orderable_fk_fields: - setattr(new_class, f"get_{field}", cls._create_orderable_field_method(field, sort_field)) + updated_name = f"get_{field}" + + # For each item in orderable_fk_fields, create a function and associate it with admin_order_field. + setattr(new_class, updated_name, cls._create_orderable_field_method(field, sort_field)) + + # Update the list_display variable to use our newly created functions + if list_display_exists and field in cls.list_display: + index = new_list_display.index(field) + new_list_display[index] = updated_name + elif list_display_exists: + new_list_display.append(updated_name) + + # Replace the old list with the updated one + if list_display_exists: + cls.list_display = new_list_display + return new_class @classmethod def _create_orderable_field_method(cls, field, sort_field): """ This class method is a factory for creating dynamic methods that will be attached to the ModelAdmin subclass. - It is used to customize how fk fields are ordered. By default, fks are ordered by id, so if you wish to - order by "name" instead, you need to manually override that. - + It is used to customize how fk fields are ordered. In essence, this function will more or less generate code that looks like this, for a given tuple defined in orderable_fk_fields: @@ -47,7 +67,7 @@ class OrderableFieldsMixin: return obj.requested_domain # Allows column order sorting get_requested_domain.admin_order_field = "requested_domain__name" - # Sets column's header + # Sets column's header name get_requested_domain.short_description = "requested domain" ``` @@ -65,8 +85,7 @@ class OrderableFieldsMixin: Parameters: cls: The class that this method is being called on. In the context of this mixin, it would be the ModelAdmin subclass. field: A string representing the name of the attribute that the dynamic method will fetch from the model instance. - sort_field: A string or list of strings representing the field(s) - that Django should sort by when the column is clicked in the admin interface. + sort_field: A string or list of strings representing the field(s) to sort by (ex: "name" or "creator") Returns: method: The dynamically created method. @@ -78,23 +97,29 @@ class OrderableFieldsMixin: """ def method(obj): """ - The dynamically created method. - When called on a model instance, it returns the value of the specified attribute. + Method factory. """ attr = getattr(obj, field) return attr + # Set the function name. For instance, if the field is "domain", + # then this will generate a function called "get_domain" method.__name__ = f"get_{field}" + # Check if a list is passed in, or just a string. 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) + # If its a list, return an array of fields to sort on. + # For instance, ["creator__first_name", "creator__last_name"] method.admin_order_field = sort_list else: + # If its not a list, just return a string method.admin_order_field = f"{field}__{sort_field}" + # Infer the column name in a similar manner to how Django does method.short_description = field.replace("_", " ") return method