Skip to content

Commit f7cf5b3

Browse files
matteiusclaude
andcommitted
fix: render section headers in approval stage fields
ApprovalStepForm excluded section-type fields from the queryset so they never reached _build_layout_fields. _build_approval_step_sections also excluded them, so completed approval step displays had no headers. Now sections flow through to both the crispy form layout and the read-only completed-step tables (approve, submission detail, PDF). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c8c5470 commit f7cf5b3

File tree

6 files changed

+39
-101
lines changed

6 files changed

+39
-101
lines changed

django_forms_workflows/forms.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1413,15 +1413,15 @@ def _build_fields(self):
14131413
FormFields belong to it.
14141414
"""
14151415
if self.approval_task.workflow_stage_id:
1416-
qs = (
1417-
self.form_definition.fields.exclude(field_type="section")
1418-
.filter(workflow_stage_id=self.approval_task.workflow_stage_id)
1419-
.order_by("order")
1420-
)
1416+
qs = self.form_definition.fields.filter(
1417+
workflow_stage_id=self.approval_task.workflow_stage_id
1418+
).order_by("order")
14211419
else:
14221420
# Workflow with no stages — no stage-specific fields to show.
14231421
qs = self.form_definition.fields.none()
14241422
for field_def in qs:
1423+
if field_def.field_type == "section":
1424+
continue # sections are visual-only; handled in _build_layout_fields
14251425
self._add_field(field_def)
14261426

14271427
def _add_field(self, field_def):

django_forms_workflows/templates/django_forms_workflows/approve.html

Lines changed: 10 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -232,51 +232,18 @@ <h5 class="mb-0">
232232
{% if section.fields %}
233233
<table class="table table-bordered table-sm mb-0">
234234
{% for field in section.fields %}
235-
<tr>
236-
<th style="width: 30%;">{{ field.label }}</th>
237-
<td>
238-
{% if field.value.url %}
239-
{% if field.value.content_type and 'image' in field.value.content_type %}
240-
<div class="mb-2">
241-
<img src="{{ field.value.url }}" alt="{{ field.value.filename }}" class="img-fluid" style="max-width: 400px; max-height: 400px;">
242-
</div>
243-
{% endif %}
244-
<a href="{{ field.value.url }}" target="_blank" class="btn btn-sm btn-outline-primary">
245-
<i class="bi bi-download"></i> {{ field.value.filename }}
246-
</a>
247-
<small class="text-muted ms-2">
248-
{% if field.value.size %}({{ field.value.size|filesizeformat }}){% endif %}
249-
</small>
250-
{% elif field.value.path %}
251-
<span class="text-muted">{{ field.value.filename }}</span>
252-
<small class="text-muted ms-2">(File stored but URL unavailable)</small>
253-
{% elif field.value|is_signature %}
254-
<img src="{{ field.value }}" alt="Signature" class="img-fluid" style="max-width: 400px; max-height: 160px; border: 1px solid #dee2e6; border-radius: .375rem;">
255-
{% elif field.value.0.filename %}
256-
<ul class="list-unstyled mb-0">
257-
{% for f in field.value %}
258-
<li class="mb-1">
259-
{% if f.url %}
260-
{% if f.content_type and 'image' in f.content_type %}
261-
<div class="mb-1">
262-
<img src="{{ f.url }}" alt="{{ f.filename }}" class="img-fluid" style="max-width: 200px; max-height: 200px;">
263-
</div>
264-
{% endif %}
265-
<a href="{{ f.url }}" target="_blank" class="btn btn-sm btn-outline-primary">
266-
<i class="bi bi-download"></i> {{ f.filename }}
267-
</a>
268-
<small class="text-muted ms-2">{% if f.size %}({{ f.size|filesizeformat }}){% endif %}</small>
269-
{% else %}
270-
<span class="text-muted">{{ f.filename }}</span>
271-
{% endif %}
272-
</li>
273-
{% endfor %}
274-
</ul>
275-
{% else %}
276-
{{ field.value }}
277-
{% endif %}
235+
{% if field.type == 'section' %}
236+
<tr class="table-light">
237+
<td colspan="2" class="pt-3 pb-2 px-3">
238+
<h5 class="mb-0">{{ field.label }}</h5>
278239
</td>
279240
</tr>
241+
{% else %}
242+
<tr>
243+
<th style="width: 30%; vertical-align: top;">{{ field.label }}</th>
244+
<td>{% include "django_forms_workflows/_field_value.html" with entry=field %}</td>
245+
</tr>
246+
{% endif %}
280247
{% endfor %}
281248
</table>
282249
{% else %}

django_forms_workflows/templates/django_forms_workflows/submission_detail.html

Lines changed: 10 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -149,55 +149,18 @@ <h6 class="mb-0 fw-bold">{{ section.step_name }}</h6>
149149
<div class="px-3 pb-3">
150150
<table class="table table-bordered table-sm mb-0">
151151
{% for field in section.fields %}
152-
<tr>
153-
<th style="width: 30%;">{{ field.label }}</th>
154-
<td>
155-
{% if field.value.url %}
156-
{# Single file upload with URL #}
157-
{% if field.value.content_type and 'image' in field.value.content_type %}
158-
<div class="mb-2">
159-
<img src="{{ field.value.url }}" alt="{{ field.value.filename }}" class="img-fluid" style="max-width: 400px; max-height: 400px;">
160-
</div>
161-
{% endif %}
162-
<a href="{{ field.value.url }}" target="_blank" class="btn btn-sm btn-outline-primary">
163-
<i class="bi bi-download"></i> {{ field.value.filename }}
164-
</a>
165-
<small class="text-muted ms-2">
166-
{% if field.value.size %}({{ field.value.size|filesizeformat }}){% endif %}
167-
</small>
168-
{% elif field.value.path %}
169-
{# Single file with path but no URL #}
170-
<span class="text-muted">{{ field.value.filename }}</span>
171-
<small class="text-muted ms-2">(File stored but URL unavailable)</small>
172-
{% elif field.value|is_signature %}
173-
{# Signature — inline base64 image #}
174-
<img src="{{ field.value }}" alt="Signature" class="img-fluid" style="max-width: 400px; max-height: 160px; border: 1px solid #dee2e6; border-radius: .375rem;">
175-
{% elif field.value.0.filename %}
176-
{# Multi-file upload: list of file dicts #}
177-
<ul class="list-unstyled mb-0">
178-
{% for f in field.value %}
179-
<li class="mb-1">
180-
{% if f.url %}
181-
{% if f.content_type and 'image' in f.content_type %}
182-
<div class="mb-1">
183-
<img src="{{ f.url }}" alt="{{ f.filename }}" class="img-fluid" style="max-width: 200px; max-height: 200px;">
184-
</div>
185-
{% endif %}
186-
<a href="{{ f.url }}" target="_blank" class="btn btn-sm btn-outline-primary">
187-
<i class="bi bi-download"></i> {{ f.filename }}
188-
</a>
189-
<small class="text-muted ms-2">{% if f.size %}({{ f.size|filesizeformat }}){% endif %}</small>
190-
{% else %}
191-
<span class="text-muted">{{ f.filename }}</span>
192-
{% endif %}
193-
</li>
194-
{% endfor %}
195-
</ul>
196-
{% else %}
197-
{{ field.value }}
198-
{% endif %}
152+
{% if field.type == 'section' %}
153+
<tr class="table-light">
154+
<td colspan="2" class="pt-3 pb-2 px-3">
155+
<h5 class="mb-0">{{ field.label }}</h5>
199156
</td>
200157
</tr>
158+
{% else %}
159+
<tr>
160+
<th style="width: 30%; vertical-align: top;">{{ field.label }}</th>
161+
<td>{% include "django_forms_workflows/_field_value.html" with entry=field %}</td>
162+
</tr>
163+
{% endif %}
201164
{% endfor %}
202165
</table>
203166
</div>

django_forms_workflows/templates/django_forms_workflows/submission_pdf.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,10 +284,16 @@ <h3 style="margin-top: 20px;">Attachments</h3>
284284
</colgroup>
285285
<tbody>
286286
{% for field in section.fields %}
287+
{% if field.type == 'section' %}
288+
<tr>
289+
<td colspan="4" class="step-section-header">{{ field.label }}</td>
290+
</tr>
291+
{% else %}
287292
<tr>
288293
<th>{{ field.label }}</th>
289294
<td colspan="3">{% with v=field.value %}{% if v|is_signature %}<img src="{{ v }}" style="max-width:360px;max-height:120px;">{% elif v.filename %}{{ v.filename }}{% else %}{{ v }}{% endif %}{% endwith %}</td>
290295
</tr>
296+
{% endif %}
291297
{% endfor %}
292298
</tbody>
293299
</table>

django_forms_workflows/views.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3208,12 +3208,11 @@ def _build_approval_step_sections(submission):
32083208

32093209
# Fields keyed by workflow_stage_id. Store enough metadata to resolve
32103210
# choice labels (select/radio/multiselect/checkboxes) at display time.
3211+
# Section headers are included so they render in completed-step displays.
32113212
fields_by_stage: dict = defaultdict(list)
3212-
for field in (
3213-
submission.form_definition.fields.filter(workflow_stage__isnull=False)
3214-
.exclude(field_type="section")
3215-
.order_by("order")
3216-
):
3213+
for field in submission.form_definition.fields.filter(
3214+
workflow_stage__isnull=False
3215+
).order_by("order"):
32173216
fields_by_stage[field.workflow_stage_id].append(
32183217
{
32193218
"label": field.field_label,
@@ -3248,6 +3247,9 @@ def _build_approval_step_sections(submission):
32483247

32493248
visible_fields = []
32503249
for f in field_defs:
3250+
if f.get("field_type") == "section":
3251+
visible_fields.append({"label": f["label"], "type": "section"})
3252+
continue
32513253
lookup_key = f"{f['key']}_{swi_index}" if swi_index else f["key"]
32523254
if lookup_key in form_data:
32533255
raw_value = form_data[lookup_key]

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "django-forms-workflows"
3-
version = "0.64.0"
3+
version = "0.64.1"
44
description = "Enterprise-grade, database-driven form builder with approval workflows and external data integration"
55
license = "LGPL-3.0-only"
66
readme = "README.md"

0 commit comments

Comments
 (0)