Display & Header Sheets
How the print/PDF layout for an entry is defined.
Header Sheet (optional)
An optional third worksheet — any sheet with "header" in its name — for a print/export header that's separate from the main display layout: letterhead, a logo row, a title block, or anything you want to appear above the entry's data on printed/exported output without cluttering the display sheet itself.
It's read using the exact same mechanism as the display sheet — same cell-value/formatting/merged-cell/image capture, same <pa_X>-style field placeholders, same PRINT_PAGE_BREAK comment support (see below). Think of it as "another display sheet, rendered as the header" rather than a separate feature to learn.
Display Sheet
The display sheet defines how one entry looks when viewed, printed, or exported as PDF — a free-form print layout, not a form. PersivApps reads the complete spreadsheet layout via openpyxl (services.py: _get_display_metadata_from_file) into one JSON object per cell: values/formulas, fonts, fills, borders, merged cells, column widths & row heights, embedded images, and rich text runs. There is no required structure — build it exactly like you'd lay out a printed form in Excel, then drop field placeholders into whichever cells should show data.
How the Display Sheet Links to the Entry Sheet
The two sheets are connected by exactly one thing: the numeric id column on the entry sheet (see Entry Sheet — Column Reference). A display-sheet cell references a field purely by that id, written as a placeholder token in the cell's raw text value — never by field name, and never by cell position. This means:
- Renaming a field's
nameon the entry sheet never breaks the display sheet — the placeholder still points at the sameid. - A placeholder can go in any cell, in any layout you want — a title block, a signature strip at the bottom, a value repeated in two places — since it's just text the extractor substitutes, not a fixed template slot.
- Reordering, hiding, or moving a field on the entry sheet doesn't move its placeholder on the display sheet — you'd need to edit the display sheet's own cell if the field's id itself changes (which it normally doesn't across a refresh).
Field Placeholder Reference
All placeholders below are written as literal text inside a cell — angle brackets included — and resolved server-side (process_smarttable_data_detail, apps/smarttables/views.py) into that cell's display_value before the frontend renders it. ID is always the entry sheet's id column value for that field.
| Placeholder | Resolves To |
|---|---|
<pa_ID> | The field's value, from observation 1. For a checkbox field with properties.display_as set, shows that label only when checked (blank when unchecked); otherwise shows the literal value. Number/date fields are formatted per their properties (see Field Types & Properties). |
<pa_ID_oN> | The field's value from a specific observation number N instead of observation 1 — the way to show more than one observation's data on the same display layout (e.g. a 3-shift checksheet with one column per shift, each pulling from a different observation). See SmartTable Settings for how the number of observations is configured. |
<pa_ID_r> | That field's remarks across every observation that has one, formatted as Obs 1 - text, Obs 2 - text, ... and comma-joined. Only meaningful for a field with the remarks entry-sheet column enabled — see Entry Sheet — Column Reference. |
<pa_ID_table> | A table field's whole per-row array (observation 1). Placed alone in an otherwise-empty cell, the app repeats that entire row as its own display row for each entry in the array — the standard way to print a table field as an actual multi-row table. Mixed with other text or other _table placeholders in the same cell, it instead zips row-by-row: each row of the printed output substitutes that row's value into the cell text, with shorter arrays padded blank past their own length. |
<pa_ID_table_rN> | One specific row (1-indexed, matching row order on the entry form) of a table field, as a plain scalar — pin one row's value into a fixed cell instead of letting the table repeat. |
<pa_ID_table_summary> | Same whole-array behavior as <pa_ID_table> used alone in a cell (no row-zip support) — an older/alternate form of the same "print this table field as repeating rows" mechanism. |
<pa_ID_attachment_N> | The N-th attachment (1-indexed, observation 1) on that field, rendered inline as an image sized to fill the cell — for printing a photo/scan directly into the layout instead of leaving it as a download link. |
<pa_attest_step_N> | The attestation signature image for interleaved-approval step N, plus the signatory's name and the approval date/time (DD-MM-YYYY HH:MM, IST) printed underneath — blank if that step hasn't been attested yet. See Attestation. |
<pa_appr_sig_sN_aM> | The signature image (plus name and approval date/time, DD-MM-YYYY HH:MM IST) of the M-th configured approver of approval step N — blank until that specific approver has actually approved. See Approval Workflows. |
<pa_count_acc:NAME> | A headcount for one accordion — how many filled entries that accordion currently holds. Addressed by the accordion's own name (exactly as written in the entry sheet's accordion column), not by a field id, so adding one needs no entry-sheet change. See Group Counts below. |
<pa_count_sec:NAME> | The same headcount for a whole section (the entry sheet's smarttable_section column) — the combined total across every accordion in that section. |
<pa_ID>/<pa_ID_oN>/<pa_ID_r> — e.g. a cell containing Reading: <pa_12> <pa_13> substitutes both and keeps the surrounding text. Only the table placeholders (<pa_ID_table>) change behavior (zip mode) when combined with other content — see above.
<pa_attest_step_N> and <pa_appr_sig_sN_aM> both resolve through the identical image-rendering path (<pa_attest_sig:...> internally) — an attestation signature and an approval signature look and scale identically on the printed output, including the name and approval date/time printed beneath the image, since both ultimately come from the same base64 signature storage used for a user's My Signature profile image.
Static Images
A picture pasted directly into a display-sheet cell (a company logo, a letterhead graphic) is captured independently of the placeholder system — it's stored as the cell's image data (path/width/height) and rendered as a plain <img>, unrelated to any field. Use this for anything that should appear on every print-out unchanged, as opposed to the attachment/signature placeholders above, which pull in image data that differs per entry.
Remarks
A remark is per-field, per-observation free text captured alongside a field's value (enabled via the entry sheet's remarks column — see Entry Sheet — Column Reference). On the display sheet, <pa_ID_r> is the only placeholder that surfaces it — there's no per-observation remarks placeholder; all observations' remarks for that field are combined into the single comma-joined string described above.
Group Counts (Accordion & Section Totals)
Print a live count of how many entries a group currently holds, next to that group's heading — e.g. FOLDING 01 (2) and DYEING SECTION 1 (51). Typical use is a headcount on an attendance or manning sheet.
Write the placeholder alongside the heading text you already have:
| You type in the cell | It prints as |
|---|---|
FOLDING 01 (<pa_count_acc:FOLDING 01>) | FOLDING 01 (2) |
DYEING SECTION 1 (<pa_count_sec:DYEING SECTION 1>) | DYEING SECTION 1 (51) |
The name after the colon must match the entry sheet's accordion / smarttable_section value exactly (spaces included). A name that matches nothing prints as blank and leaves the rest of the cell's text intact, so one typo never breaks the rest of the page.
What counts as one entry
Fields inside a group are paired by their row value, and a pair is counted only when both halves are filled — the field whose name contains "Code" and the one whose name contains "Name". A row with a code but no name (or the reverse) is treated as an incomplete entry and is not counted, so a half-typed line never inflates the total. A section's number is worked out from its own fields directly, not by adding up the accordion numbers, so it stays correct whether or not each accordion prints its own count.
id, so adding or moving a count is a display-sheet edit and a display re-sync only. This also means the number is not stored with the entry — it is not available as an entries-list column or in an Excel export, and it does not appear while filling the form, only on the display/PDF. Counts are also not computed in offline mode.
Formula Cells
A display-sheet cell can also hold a live formula instead of a static placeholder: mark the cell with an Excel cell comment reading exactly pa_FORMULA, and write a HyperFormula expression as its value (referencing fields the same way, pa_ID). It's evaluated client-side, after placeholder substitution, against the raw, unformatted values (not the comma/decimal-formatted display values — a formatted "1,000" isn't a valid numeric literal). See Formulas & Computed Fields for the field-level formula mechanism this is distinct from.
Special Comments
Excel cell comments drive two different special behaviors on the display sheet — same mechanism, different comment text:
| Comment Text | Effect |
|---|---|
PRINT_PAGE_BREAK | Forces a page break before this row when printing/generating PDF |
pa_FORMULA | Evaluates this cell's value as a HyperFormula expression after placeholder substitution — see Formula Cells above |