|
4 | 4 | from django.contrib.admin.templatetags.admin_list import ( |
5 | 5 | ResultList, |
6 | 6 | _coerce_field_name, |
7 | | - result_headers, |
8 | 7 | result_hidden_fields, |
9 | 8 | ) |
10 | 9 | from django.contrib.admin.templatetags.admin_urls import add_preserved_filters |
11 | 10 | from django.contrib.admin.templatetags.base import InclusionAdminNode |
12 | | -from django.contrib.admin.utils import lookup_field |
13 | | -from django.contrib.admin.views.main import PAGE_VAR, ChangeList |
| 11 | +from django.contrib.admin.utils import label_for_field, lookup_field |
| 12 | +from django.contrib.admin.views.main import ( |
| 13 | + ORDER_VAR, |
| 14 | + PAGE_VAR, |
| 15 | + ChangeList, |
| 16 | +) |
14 | 17 | from django.core.exceptions import ObjectDoesNotExist |
15 | 18 | from django.db import models |
16 | 19 | from django.forms import Form |
|
21 | 24 | from django.urls import NoReverseMatch |
22 | 25 | from django.utils.html import format_html |
23 | 26 | from django.utils.safestring import SafeText, mark_safe |
| 27 | +from django.utils.translation import gettext_lazy as _ |
24 | 28 |
|
25 | 29 | from ..utils import ( |
26 | 30 | display_for_field, |
27 | 31 | display_for_header, |
28 | 32 | display_for_label, |
29 | 33 | display_for_value, |
30 | 34 | ) |
| 35 | +from ..widgets import UnfoldBooleanWidget |
31 | 36 |
|
32 | 37 | register = Library() |
33 | 38 |
|
34 | 39 | LINK_CLASSES = ["text-gray-700 dark:text-gray-200"] |
35 | 40 |
|
36 | 41 |
|
| 42 | +def result_headers(cl): |
| 43 | + """ |
| 44 | + Generate the list column headers. |
| 45 | + """ |
| 46 | + ordering_field_columns = cl.get_ordering_field_columns() |
| 47 | + for i, field_name in enumerate(cl.list_display): |
| 48 | + text, attr = label_for_field( |
| 49 | + field_name, cl.model, model_admin=cl.model_admin, return_attr=True |
| 50 | + ) |
| 51 | + is_field_sortable = cl.sortable_by is None or field_name in cl.sortable_by |
| 52 | + if attr: |
| 53 | + field_name = _coerce_field_name(field_name, i) |
| 54 | + # Potentially not sortable |
| 55 | + |
| 56 | + # if the field is the action checkbox: no sorting and special class |
| 57 | + if field_name == "action_checkbox": |
| 58 | + yield { |
| 59 | + "text": UnfoldBooleanWidget( |
| 60 | + { |
| 61 | + "id": "action-toggle", |
| 62 | + "aria-label": _( |
| 63 | + "Select all objects on this page for an action" |
| 64 | + ), |
| 65 | + } |
| 66 | + ).render("action-toggle", False), |
| 67 | + "class_attrib": mark_safe("action-checkbox-column"), |
| 68 | + "sortable": False, |
| 69 | + } |
| 70 | + continue |
| 71 | + |
| 72 | + admin_order_field = getattr(attr, "admin_order_field", None) |
| 73 | + # Set ordering for attr that is a property, if defined. |
| 74 | + if isinstance(attr, property) and hasattr(attr, "fget"): |
| 75 | + admin_order_field = getattr(attr.fget, "admin_order_field", None) |
| 76 | + if not admin_order_field: |
| 77 | + is_field_sortable = False |
| 78 | + |
| 79 | + if not is_field_sortable: |
| 80 | + # Not sortable |
| 81 | + yield { |
| 82 | + "text": text, |
| 83 | + "class_attrib": format_html("column-{}", field_name), |
| 84 | + "sortable": False, |
| 85 | + } |
| 86 | + continue |
| 87 | + |
| 88 | + # OK, it is sortable if we got this far |
| 89 | + th_classes = ["sortable", f"column-{field_name}"] |
| 90 | + order_type = "" |
| 91 | + new_order_type = "asc" |
| 92 | + sort_priority = 0 |
| 93 | + # Is it currently being sorted on? |
| 94 | + is_sorted = i in ordering_field_columns |
| 95 | + if is_sorted: |
| 96 | + order_type = ordering_field_columns.get(i).lower() |
| 97 | + sort_priority = list(ordering_field_columns).index(i) + 1 |
| 98 | + th_classes.append("sorted %sending" % order_type) |
| 99 | + new_order_type = {"asc": "desc", "desc": "asc"}[order_type] |
| 100 | + |
| 101 | + # build new ordering param |
| 102 | + o_list_primary = [] # URL for making this field the primary sort |
| 103 | + o_list_remove = [] # URL for removing this field from sort |
| 104 | + o_list_toggle = [] # URL for toggling order type for this field |
| 105 | + |
| 106 | + def make_qs_param(t, n): |
| 107 | + return ("-" if t == "desc" else "") + str(n) |
| 108 | + |
| 109 | + for j, ot in ordering_field_columns.items(): |
| 110 | + if j == i: # Same column |
| 111 | + param = make_qs_param(new_order_type, j) |
| 112 | + # We want clicking on this header to bring the ordering to the |
| 113 | + # front |
| 114 | + o_list_primary.insert(0, param) |
| 115 | + o_list_toggle.append(param) |
| 116 | + # o_list_remove - omit |
| 117 | + else: |
| 118 | + param = make_qs_param(ot, j) |
| 119 | + o_list_primary.append(param) |
| 120 | + o_list_toggle.append(param) |
| 121 | + o_list_remove.append(param) |
| 122 | + |
| 123 | + if i not in ordering_field_columns: |
| 124 | + o_list_primary.insert(0, make_qs_param(new_order_type, i)) |
| 125 | + |
| 126 | + yield { |
| 127 | + "text": text, |
| 128 | + "sortable": True, |
| 129 | + "sorted": is_sorted, |
| 130 | + "ascending": order_type == "asc", |
| 131 | + "sort_priority": sort_priority, |
| 132 | + "url_primary": cl.get_query_string({ORDER_VAR: ".".join(o_list_primary)}), |
| 133 | + "url_remove": cl.get_query_string({ORDER_VAR: ".".join(o_list_remove)}), |
| 134 | + "url_toggle": cl.get_query_string({ORDER_VAR: ".".join(o_list_toggle)}), |
| 135 | + "class_attrib": format_html(' class="{}"', " ".join(th_classes)) |
| 136 | + if th_classes |
| 137 | + else "", |
| 138 | + } |
| 139 | + |
| 140 | + |
37 | 141 | def items_for_result(cl: ChangeList, result: HttpRequest, form) -> SafeText: |
38 | 142 | """ |
39 | 143 | Generate the actual list of data. |
|
0 commit comments