Change list_display logic

This commit is contained in:
zandercymatics 2023-12-18 10:59:12 -07:00
parent 43a595081f
commit e5e5fc66e6
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
2 changed files with 46 additions and 26 deletions

View file

@ -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",
]
@ -497,14 +494,12 @@ 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 = [

View file

@ -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