05/26 Fix exported PDF form layout broken

This commit is contained in:
2026-05-26 14:29:40 -04:00
parent eceee86944
commit 7520059d44
+47 -27
View File
@@ -351,6 +351,7 @@ def _form_fields_section(form_fields, form_data, static_folder):
if ft in _ALWAYS_SHOW_PRE: if ft in _ALWAYS_SHOW_PRE:
return True return True
if ft == 'rating' and str(v).isdigit() and int(v) > 0: return True if ft == 'rating' and str(v).isdigit() and int(v) > 0: return True
if ft == 'pass_fail' and v: return True
if ft == 'checkbox' and v in ('yes','true'): return True if ft == 'checkbox' and v in ('yes','true'): return True
if ft == 'checkbox_group'and isinstance(v, list) and v: return True if ft == 'checkbox_group'and isinstance(v, list) and v: return True
if ft == 'image' and v and os.path.exists(os.path.join(static_folder, v)): return True if ft == 'image' and v and os.path.exists(os.path.join(static_folder, v)): return True
@@ -398,14 +399,48 @@ def _form_fields_section(form_fields, form_data, static_folder):
# Build one Table row with cells sized by colSpan. # Build one Table row with cells sized by colSpan.
# Fields with no value are skipped unless they are text/textarea/label — # Fields with no value are skipped unless they are text/textarea/label —
# those always appear so free-text notes are never suppressed. # those always appear so free-text notes are never suppressed.
cells = [] # current_col tracks horizontal position (1-based) so gaps between fields
col_widths = [] # are filled with invisible placeholder cells that preserve column alignment.
cells = []
col_widths = []
has_content = False
current_col = 1
# Returns True if the field should be hidden from the PDF.
def _skip(ftype, val):
if ftype == 'rating':
return not str(val).isdigit() or int(val) == 0
if ftype == 'image':
img_path = os.path.join(static_folder, val) if val else ''
return not val or not os.path.exists(img_path)
if ftype == 'signature':
return not val or not str(val).startswith('data:')
if ftype == 'checkbox':
# stored as 'true'/'false' by _collect_form_responses
return val not in ('yes', 'true')
if ftype == 'pass_fail':
return not val
if ftype == 'checkbox_group':
return not val or not isinstance(val, list) or len(val) == 0
if ftype == 'table':
return not val or not isinstance(val, list) or len(val) == 0
# All remaining input types (text, textarea, number, date,
# email, radio, select, and any future types)
return not val
for field in fields_in_row: for field in fields_in_row:
ftype = field.get('type', '') ftype = field.get('type', '')
col = field.get('col', current_col)
col_span = field.get('colSpan', 1) col_span = field.get('colSpan', 1)
cell_w = UNIT * col_span cell_w = UNIT * col_span
# Fill horizontal gap before this field with an invisible spacer
if col > current_col:
gap_w = UNIT * (col - current_col)
cells.append(Paragraph('', STYLES['FieldValue']))
col_widths.append(gap_w)
current_col = col + col_span
# ── Inline label (free-standing text element) ───────────────────── # ── Inline label (free-standing text element) ─────────────────────
if ftype == 'label': if ftype == 'label':
fs_map = {'small': 8, 'normal': 9, 'large': 11, 'x-large': 13} fs_map = {'small': 8, 'normal': 9, 'large': 11, 'x-large': 13}
@@ -418,6 +453,7 @@ def _form_fields_section(form_fields, form_data, static_folder):
) )
cells.append(p) cells.append(p)
col_widths.append(cell_w) col_widths.append(cell_w)
has_content = True
continue continue
fid = str(field.get('id', '')) fid = str(field.get('id', ''))
@@ -425,29 +461,10 @@ def _form_fields_section(form_fields, form_data, static_folder):
lbl = field.get('label', '') lbl = field.get('label', '')
# ── Skip fields with no meaningful value ────────────────────────── # ── Skip fields with no meaningful value ──────────────────────────
# Returns True if the field should be hidden from the PDF.
def _skip(ftype, val):
if ftype == 'rating':
return not str(val).isdigit() or int(val) == 0
if ftype == 'image':
img_path = os.path.join(static_folder, val) if val else ''
return not val or not os.path.exists(img_path)
if ftype == 'signature':
return not val or not str(val).startswith('data:')
if ftype == 'checkbox':
# stored as 'true'/'false' by _collect_form_responses
return val not in ('yes', 'true')
if ftype == 'pass_fail':
return not val
if ftype == 'checkbox_group':
return not val or not isinstance(val, list) or len(val) == 0
if ftype == 'table':
return not val or not isinstance(val, list) or len(val) == 0
# All remaining input types (text, textarea, number, date,
# email, radio, select, and any future types)
return not val
if _skip(ftype, val): if _skip(ftype, val):
# Keep a placeholder so subsequent fields stay in column position
cells.append(Paragraph('', STYLES['FieldValue']))
col_widths.append(cell_w)
continue continue
lbl_p = Paragraph(lbl, STYLES['FieldLabel']) lbl_p = Paragraph(lbl, STYLES['FieldLabel'])
@@ -479,7 +496,9 @@ def _form_fields_section(form_fields, form_data, static_folder):
height=1.0 * inch, height=1.0 * inch,
kind='proportional') kind='proportional')
except Exception: except Exception:
# Could not load image — skip this cell entirely # Could not load image — insert placeholder to preserve column position
cells.append(Paragraph('', STYLES['FieldValue']))
col_widths.append(cell_w)
continue continue
elif ftype == 'signature': elif ftype == 'signature':
@@ -536,9 +555,10 @@ def _form_fields_section(form_fields, form_data, static_folder):
cells.append(box) cells.append(box)
col_widths.append(cell_w) col_widths.append(cell_w)
has_content = True
# Skip the entire row if nothing made it through the filter # Skip the entire row if no field had meaningful content
if not cells: if not has_content:
continue continue
row_tbl = Table([cells], colWidths=col_widths, hAlign='LEFT') row_tbl = Table([cells], colWidths=col_widths, hAlign='LEFT')