{% from "admin/components/field.html" import render_field_value with context %} {% from "admin/components/icons.html" import icon %} {# Table, ported from shadcn/ui: a bordered card wrapping a `w-full caption-bottom text-sm` table, muted uppercase headers, and rows that highlight on hover. Class strings come from ui('table', ...) so the dashboard's Table widget and tabular inlines match this one. Included, not a macro, so it shares list_content.html's ambient context (rows, list_display, model_admin, ordering, actions, permissions, base_path, reorderable). Row actions are one dropdown trigger rather than a row of icon buttons. Built inline instead of through a shared dropdown-menu partial: Delete needs hx-delete/hx-confirm on an item, which a data-only item list cannot express. Reorderable drag-and-drop is visual only: SortableJS reorders the elements with no onEnd handler and nothing hydrated from a data attribute, so there is no state to keep in sync and the next sort, search, page, or reload simply re-renders the server's real order. That is also why x-init sits on with no x-data: htmx's outerHTML swap replaces this whole component, and Alpine re-runs x-init on what it discovers, so the drag survives a swap for free. #} {# selectAll state lives here, not on the bulk-actions form: the header checkbox is inside this table and the form is a sibling in the toolbar (linked by the HTML form= attribute), so Alpine state cannot span them. The form's hidden flag is written imperatively for the same reason. #}
{% if actions %} {# Only worth offering when there is more to select than is on screen. #}
{{ ngettext("The %(num)d on this page is selected.", "All %(num)d on this page are selected.", rows|length) }} {{ ngettext("The %(num)d matching this filter is selected.", "All %(num)d matching this filter are selected.", page.total_count) }}
{% endif %}
{% if reorderable %}{% endif %} {% if actions %} {% endif %} {# The header is a dropdown offering Asc/Desc explicitly rather than a link that cycles: with a cycling link you cannot tell what a click will do until you try it. Both choices are real hrefs, so sorting works with JS off. #} {% for name in list_display %} {% set sorted_by = sort_controls[name] %} {% endfor %} {% for row in rows %} {% if reorderable %} {% endif %} {% if actions %} {% endif %} {% for name in list_display %} {% endfor %} {% endfor %}
{% if not sorted_by.sortable %} {# Outside sortable_by: a plain label, with no menu and no sort URLs to hand-type either -- the server drops them. #} {{ _(model_admin.get_field(name).label) }} {% else %}
{% endif %}
{% set rendered = render_field_value(model_admin.get_field(name), row.cells[name], model_admin.get_empty_value()) %} {#- A cell named by list_display_links links to the record, unless it already contains an anchor -- a relation, chiefly. Nesting in is invalid HTML that browsers silently restructure. -#} {% if permissions.can_view and name in linked_columns and "{{ rendered }} {%- else %}{{ rendered }}{% endif %} {% if permissions.can_view or permissions.can_update or permissions.can_delete %}
{% endif %}
{% if not rows %}

{{ _("No records.") }}

{% endif %}