Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/unfold/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class ModelAdmin(BaseModelAdminMixin, ActionModelAdminMixin, BaseModelAdmin):
action_form = ActionForm
custom_urls = ()
add_fieldsets = ()
ordering_field = None
hide_ordering_field = False
list_horizontal_scrollbar_top = False
list_filter_submit = False
list_filter_sheet = True
Expand Down Expand Up @@ -87,8 +89,21 @@ def changelist_view(
}
)

if self.ordering_field and self.ordering_field not in self.list_editable:
list_editable = list(getattr(self, "list_editable", []))
list_editable.append(self.ordering_field)
self.list_editable = list_editable

return super().changelist_view(request, extra_context)

def get_list_display(self, request: HttpRequest) -> list[str]:
list_display = super().get_list_display(request)

if self.ordering_field and self.ordering_field not in list_display:
list_display.append(self.ordering_field)

return list_display

def get_fieldsets(self, request: HttpRequest, obj=None) -> FieldsetsType:
if not obj and self.add_fieldsets:
return self.add_fieldsets
Expand Down
2 changes: 1 addition & 1 deletion src/unfold/static/unfold/css/styles.css

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion src/unfold/static/unfold/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ const sortRecords = (e) => {
const orderingField = e.from.dataset.orderingField;

const weightInputs = Array.from(
e.from.querySelectorAll(`.has_original input[name$=-${orderingField}]`)
e.from.querySelectorAll(
`.has_original input[name$=-${orderingField}], td.field-${orderingField} input[name$=-${orderingField}]`
)
);

weightInputs.forEach((input, index) => {
Expand Down
72 changes: 10 additions & 62 deletions src/unfold/templates/admin/change_list_results.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,69 +8,11 @@

{% if results %}
<div class="{% if cl.search_fields or cl.has_filters %}lg:rounded-b-default{% else %}lg:rounded-default{% endif %} -mx-1 px-1 overflow-x-auto lg:border lg:border-base-200 lg:mx-0 lg:px-0 lg:shadow-xs lg:dark:border-base-800 lg:bg-white lg:dark:bg-base-900 {% if cl.model_admin.list_horizontal_scrollbar_top %}simplebar-horizontal-scrollbar-top{% endif %}" data-simplebar data-simplebar-auto-hide="false">
<table id="result_list" class="block border-base-200 border-spacing-none border-separate w-full lg:table">
<thead>
<tr>
{% if cl.model_admin.list_sections|length > 0 %}
<th></th>
{% endif %}

{% for header in result_headers %}
<th class="align-middle font-semibold py-2 text-left text-font-important-light dark:text-font-important-dark whitespace-nowrap {{ header.class_attrib }} {% if "action-toggle" in header.text and forloop.counter == 1 %}lg:px-3 lg:w-10{% else %}hidden px-3 lg:table-cell{% endif %}" scope="col">
<div class="flex items-center">
<div class="text">
{% if header.sortable %}
<a href="{{ header.url_primary }}">
{{ header.text|capfirst }}
</a>
{% else %}
{% if "action-toggle" in header.text and forloop.counter == 1 %}
<label class="flex flex-row items-center gap-2">
{{ header.text|capfirst }}

<span class="block font-normal lg:hidden">
{% trans "Select all rows"%}
</span>
</label>
{% else %}
<span>
{{ header.text|capfirst }}
</span>
{% endif %}
{% endif %}
</div>

{% if header.sortable %}
{% if header.sort_priority > 0 %}
<div class="sortoptions flex items-center ml-2">
<a href="{{ header.url_toggle }}" class="flex items-center leading-none text-base-400 hover:text-base-500 dark:text-base-500 dark:hover:text-base-400 toggle {% if header.ascending %}ascending{% else %}descending{% endif %}" title="{% translate "Toggle sorting" %}">
{% if header.ascending %}
<span class="block material-symbols-outlined">arrow_circle_down</span>
{% else %}
<span class="block material-symbols-outlined">arrow_circle_up</span>
{% endif %}
</a>

<a class="sortremove flex items-center leading-none ml-1 text-base-400 dark:text-base-500 transition-all hover:text-red-700 dark:hover:text-red-500" href="{{ header.url_remove }}" title="{% translate "Remove from sorting" %}">
<span class="block material-symbols-outlined">cancel</span>
</a>
</div>

{% if num_sorted_fields > 1 %}
<span class="sortpriority font-medium ml-2 text-xs" title="{% blocktranslate with priority_number=header.sort_priority %}Sorting priority: {{ priority_number }}{% endblocktranslate %}">
{{ header.sort_priority }}
</span>
{% endif %}
{% endif %}
{% endif %}
</div>
</th>
{% endfor %}
</tr>
</thead>
<table id="result_list" class="block border-base-200 border-spacing-none border-separate w-full lg:table" {% if cl.model_admin.ordering_field %}x-sort.ghost x-on:end="sortRecords"{% endif %} data-ordering-field="{{ cl.model_admin.ordering_field }}">
{% include 'unfold/helpers/change_list_headers.html' %}

{% for result in results %}
<tbody class="block relative lg:table-row-group lg:hover:shadow-raised lg:dark:hover:shadow-raised-dark lg:hover:z-20 {% cycle '' 'bg-base-50 dark:bg-white/[.02]' %}" x-data="{rowOpen: false}">
<tbody class="block relative lg:table-row-group lg:hover:shadow-raised lg:dark:hover:shadow-raised-dark lg:hover:z-20 {% cycle '' 'bg-base-50 dark:bg-white/[.02]' %}" x-data="{rowOpen: false}" {% if cl.model_admin.ordering_field %}x-sort:item{% endif %}>
{% if result.form and result.form.non_field_errors %}
<tr>
<td class="text-left" colspan="{{ result|length }}">
Expand All @@ -80,6 +22,12 @@
{% endif %}

<tr class="block border border-base-200 mb-3 relative rounded-default shadow-xs lg:table-row lg:border-none lg:mb-0 lg:rounded-none lg:shadow-none dark:border-base-800">
{% if cl.model_admin.ordering_field %}
<td class="align-middle cursor-move flex border-b border-base-200 font-normal px-2.5 py-2 relative text-left before:font-semibold before:text-font-important-light before:block before:capitalize before:content-[attr(data-label)] before:mr-auto lg:before:hidden lg:border-b-0 lg:border-t lg:pl-3 lg:pr-0 lg:py-3 lg:table-cell dark:border-base-800 dark:lg:border-base-800 dark:before:text-font-important-dark lg:w-px" x-sort:handle>
<span class="material-symbols-outlined align-middle cursor-move">drag_indicator</span>
</td>
{% endif %}

{% if cl.model_admin.list_sections|length > 0 %}
<td class="align-middle cursor-pointer flex border-b border-base-200 font-normal px-2.5 py-2 relative text-left before:font-semibold before:text-font-important-light before:block before:capitalize before:content-[attr(data-label)] before:mr-auto lg:before:hidden lg:border-b-0 lg:border-t lg:pl-3 lg:pr-0 lg:py-3 lg:table-cell dark:border-base-800 dark:lg:border-base-800 dark:before:text-font-important-dark lg:w-px"
data-label="{% trans "Expand row" %}"
Expand All @@ -100,7 +48,7 @@

{% if cl.model_admin.list_sections|length > 0 %}
<tr class="block mb-3 relative z-30 lg:table-row" x-show="rowOpen">
<td colspan="{{ result|length|add:2 }}" class="border bg-base-200/10 block border-base-200 relative rounded-default p-3 lg:shadow-inner lg:border-0 lg:border-t lg:rounded-none lg:table-cell dark:border-base-800">
<td colspan="100%" class="border bg-base-200/10 block border-base-200 relative rounded-default p-3 lg:shadow-inner lg:border-0 lg:border-t lg:rounded-none lg:table-cell dark:border-base-800">
<div class="absolute bg-primary-600 h-full hidden left-0 top-0 w-0.5 lg:block"></div>

<div class="grid gap-3 {{ cl.model_admin.list_sections_classes }}">
Expand Down
2 changes: 1 addition & 1 deletion src/unfold/templates/admin/edit_inline/stacked.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ <h3 class="border-b border-base-200 flex font-medium items-center gap-2 px-3 py-

{% if inline_admin_formset.opts.ordering_field %}
{% if inline_admin_form.original %}
<span class="material-symbols-outlined cursor-pointer" x-sort:handle>drag_indicator</span>
<span class="material-symbols-outlined cursor-move" x-sort:handle>drag_indicator</span>
{% else %}
<span class="-mr-2" x-sort:handle></span>
{% endif %}
Expand Down
65 changes: 65 additions & 0 deletions src/unfold/templates/unfold/helpers/change_list_headers.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{% load i18n %}

<thead>
<tr>
{% if cl.model_admin.ordering_field %}
<th></th>
{% endif %}

{% if cl.model_admin.list_sections|length > 0 %}
<th></th>
{% endif %}

{% for header in result_headers %}
<th class="align-middle font-semibold py-2 text-left text-font-important-light dark:text-font-important-dark whitespace-nowrap {{ header.class_attrib }} {% if "action-toggle" in header.text and forloop.counter == 1 %}lg:px-3 lg:w-10{% else %}hidden px-3 lg:table-cell{% endif %}" scope="col">
<div class="flex items-center">
<div class="text">
{% if header.sortable %}
<a href="{{ header.url_primary }}">
{{ header.text|capfirst }}
</a>
{% else %}
{% if "action-toggle" in header.text and forloop.counter == 1 %}
<label class="flex flex-row items-center gap-2">
{{ header.text|capfirst }}

<span class="block font-normal lg:hidden">
{% trans "Select all rows"%}
</span>
</label>
{% else %}
<span>
{{ header.text|capfirst }}
</span>
{% endif %}
{% endif %}
</div>

{% if header.sortable %}
{% if header.sort_priority > 0 %}
<div class="sortoptions flex items-center ml-2">
<a href="{{ header.url_toggle }}" class="flex items-center leading-none text-base-400 hover:text-base-500 dark:text-base-500 dark:hover:text-base-400 toggle {% if header.ascending %}ascending{% else %}descending{% endif %}" title="{% translate "Toggle sorting" %}">
{% if header.ascending %}
<span class="block material-symbols-outlined">arrow_circle_down</span>
{% else %}
<span class="block material-symbols-outlined">arrow_circle_up</span>
{% endif %}
</a>

<a class="sortremove flex items-center leading-none ml-1 text-base-400 dark:text-base-500 transition-all hover:text-red-700 dark:hover:text-red-500" href="{{ header.url_remove }}" title="{% translate "Remove from sorting" %}">
<span class="block material-symbols-outlined">cancel</span>
</a>
</div>

{% if num_sorted_fields > 1 %}
<span class="sortpriority font-medium ml-2 text-xs" title="{% blocktranslate with priority_number=header.sort_priority %}Sorting priority: {{ priority_number }}{% endblocktranslate %}">
{{ header.sort_priority }}
</span>
{% endif %}
{% endif %}
{% endif %}
</div>
</th>
{% endfor %}
</tr>
</thead>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{% if forloop.parentloop.counter == 1 and forloop.counter == 1 %}
{% if inline_admin_formset.opts.ordering_field %}
{% if inline_admin_form.original %}
<span class="material-symbols-outlined cursor-pointer" x-sort:handle>drag_indicator</span>
<span class="material-symbols-outlined cursor-move" x-sort:handle>drag_indicator</span>
{% else %}
<span class="-mr-3" x-sort:handle></span>
{% endif %}
Expand Down
13 changes: 13 additions & 0 deletions src/unfold/templatetags/unfold_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ def result_headers(cl):
Generate the list column headers.
"""
ordering_field_columns = cl.get_ordering_field_columns()
ordering_field = getattr(cl.model_admin, "ordering_field", None)
hide_ordering_field = getattr(cl.model_admin, "hide_ordering_field", False)

for i, field_name in enumerate(cl.list_display):
text, attr = label_for_field(
field_name, cl.model, model_admin=cl.model_admin, return_attr=True
Expand Down Expand Up @@ -145,6 +148,10 @@ def result_headers(cl):
order_type = ""
new_order_type = "asc"
sort_priority = 0

if ordering_field and field_name == ordering_field and hide_ordering_field:
th_classes.append("!hidden")

# Is it currently being sorted on?
is_sorted = i in ordering_field_columns
if is_sorted:
Expand Down Expand Up @@ -209,6 +216,9 @@ def link_in_col(is_first: bool, field_name: str, cl: ChangeList) -> bool:

for field_index, field_name in enumerate(cl.list_display):
empty_value_display = cl.model_admin.get_empty_value_display()
ordering_field = getattr(cl.model_admin, "ordering_field", None)
hide_ordering_field = getattr(cl.model_admin, "hide_ordering_field", False)

row_classes = [
f"field-{_coerce_field_name(field_name, field_index)}",
*ROW_CLASSES,
Expand Down Expand Up @@ -321,6 +331,9 @@ def link_in_col(is_first: bool, field_name: str, cl: ChangeList) -> bool:
if bf.errors:
row_classes += ["group", "errors"]

if ordering_field and field_name == ordering_field and hide_ordering_field:
row_classes.append("!hidden")

row_class = mark_safe(f' class="{" ".join(row_classes)}"')

if field_index != 0:
Expand Down