{% from "admin/components/icons.html" import icon %}
{# Context-aware read-only rendering of a field value.
Default renderer, overridable by replacing this template.
`with context` so the macro can see `admin`, `base_path`, and
`relation_permissions` from whichever page imports it, without every
call site having to pass them through explicitly. #}
{% macro render_related_link(relation, related_obj) %}
{%- set target_admin = admin.get_model_admin(relation.target) if admin else none -%}
{%- set label = target_admin.get_field(relation.display_field).get_value(related_obj) if target_admin else related_obj -%}
{%- if target_admin and relation_permissions.get(relation.target) -%}
{{ label }}
{%- else -%}
{{ label }}
{%- endif -%}
{% endmacro %}
{# A boolean renders as a check or a cross rather than "Yes"/"No": a
glyph reads as a shape at a glance where two similar-length words
have to be read. The word stays as an sr-only label, so nothing
depends on the icon alone. Exports stringify through
core/exporter.py and are untouched.
The emerald pair is picked for contrast against bg-card in both
themes, since there is no shadcn "success" token to defer to. #}
{% macro render_field_value(field, value, empty=DEFAULT_EMPTY_VALUE) -%}
{#- Blank counts as empty, not just None: a column of empty cells and a
column of dashes say different things to a reader, and "" is by far
the more common way a value goes missing in practice. A zero or a
False is a real value and must not be dashed. -#}
{%- if value is none or (value is string and not value.strip()) -%}
{{ empty }}
{%- elif field.field_type == "boolean" -%}
{%- if value -%}
{{ icon("check", "size-4") }}{{ _("Yes") }}
{%- else -%}
{{ icon("close", "size-4") }}{{ _("No") }}
{%- endif -%}
{%- elif field.field_type == "password" -%}
••••••••
{%- elif field.field_type in ("foreignkey", "onetoone") -%}
{{ render_related_link(field.relation, value) }}
{%- elif field.field_type == "manytomany" -%}
{%- if not value -%}
{{ empty }}
{%- else -%}
{%- for related_obj in value -%}
{{ render_related_link(field.relation, related_obj) }}{% if not loop.last %}, {% endif %}
{%- endfor -%}
{%- endif -%}
{%- elif field.field_type == "date" and value|iso_date -%}
{%- elif field.field_type == "datetime" and value|iso_datetime -%}
{%- elif field.field_type == "decimal" -%}
{{ value|decimal_display }}
{%- elif field.field_type == "image" -%}
{# A failed load flips the img and its fallback sibling's inline
display rather than replacing DOM: no JS dependency beyond the
one-line onerror.
Hidden via an inline style, not the `hidden` attribute: the browser's
`[hidden] { display: none }` is a lowest-priority UA rule, so the
fallback's own display:inline-flex (an ordinary author-stylesheet
rule, which always outranks a UA one regardless of selector order)
would otherwise win and show it beside every successfully-loaded
thumbnail. An inline style outranks both. #}
{{ icon("photo", "size-4") }}
{%- else -%}
{{ value }}
{%- endif -%}
{%- endmacro %}