Skip to content

Commit 2020360

Browse files
committed
v0.20.0: Customizable sub-workflow section labels
- Add section_label field to SubWorkflowDefinition for user-facing heading (e.g. 'Payment Approvals' instead of 'Sub-Workflow Approvals') - Falls back to sub-workflow form name, then 'Additional Approvals' - Add section_label to workflow builder properties panel, load/save, and sync API - Use list-check icon instead of diagram-2 for cleaner end-user UX
1 parent b728462 commit 2020360

8 files changed

Lines changed: 59 additions & 3 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Generated by Django 5.2.7 on 2026-03-12 20:16
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("django_forms_workflows", "0041_remove_deprecated_fields"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="subworkflowdefinition",
15+
name="section_label",
16+
field=models.CharField(
17+
blank=True,
18+
help_text="Heading shown to end users in the approval history (e.g. 'Payment Approvals'). If blank, defaults to the sub-workflow form name.",
19+
max_length=100,
20+
),
21+
),
22+
]

django_forms_workflows/models.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2126,6 +2126,12 @@ class SubWorkflowDefinition(models.Model):
21262126
max_length=100,
21272127
help_text="Form field name whose integer value determines how many sub-workflows to spawn (e.g. 'number_of_payments')",
21282128
)
2129+
section_label = models.CharField(
2130+
max_length=100,
2131+
blank=True,
2132+
help_text="Heading shown to end users in the approval history (e.g. 'Payment Approvals'). "
2133+
"If blank, defaults to the sub-workflow form name.",
2134+
)
21292135
label_template = models.CharField(
21302136
max_length=100,
21312137
default="Sub-workflow {index}",

django_forms_workflows/static/django_forms_workflows/js/workflow-builder.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ class WorkflowBuilder {
378378
sub_workflow_def_id: null,
379379
sub_workflow_id: null,
380380
sub_workflow_name: '',
381+
section_label: '',
381382
count_field: '',
382383
label_template: 'Sub-workflow {index}',
383384
trigger: 'on_approval',
@@ -957,6 +958,15 @@ class WorkflowBuilder {
957958
<small class="text-muted">The workflow definition used for each sub-workflow instance</small>
958959
</div>
959960
961+
<div class="mb-3">
962+
<label class="form-label"><strong>Section Label</strong></label>
963+
<input type="text" class="form-control" name="section_label"
964+
value="${this.escapeHtml(data.section_label || '')}"
965+
placeholder="e.g. Payment Approvals"
966+
onchange="workflowBuilder.updateSubWorkflowConfig('${node.id}')" />
967+
<small class="text-muted">Heading shown to end users in approval history. If blank, uses the workflow name.</small>
968+
</div>
969+
960970
<div class="mb-3">
961971
<label class="form-label"><strong>Count Field</strong></label>
962972
<select class="form-select" name="count_field"
@@ -1027,6 +1037,7 @@ class WorkflowBuilder {
10271037
const selectedOption = subWfSelect.selectedOptions[0];
10281038
node.data.sub_workflow_name = selectedOption && selectedOption.value ? selectedOption.text : '';
10291039

1040+
node.data.section_label = container.querySelector('input[name="section_label"]').value;
10301041
node.data.count_field = container.querySelector('select[name="count_field"]').value;
10311042
node.data.label_template = container.querySelector('input[name="label_template"]').value;
10321043
node.data.trigger = container.querySelector('select[name="trigger"]').value;

django_forms_workflows/sync_api.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ def _serialize_sub_workflow_config(swc):
241241
return None
242242
return {
243243
"sub_workflow_form_slug": swc.sub_workflow.form_definition.slug,
244+
"section_label": swc.section_label,
244245
"count_field": swc.count_field,
245246
"label_template": swc.label_template,
246247
"trigger": swc.trigger,
@@ -586,6 +587,7 @@ def import_form(form_data, conflict="update", category_cache=None):
586587
parent_workflow=wf,
587588
defaults={
588589
"sub_workflow": sub_wf_form.workflow,
590+
"section_label": sub_wf_data.get("section_label", ""),
589591
"count_field": sub_wf_data.get("count_field", ""),
590592
"label_template": sub_wf_data.get(
591593
"label_template", "Sub-workflow {index}"

django_forms_workflows/templates/django_forms_workflows/submission_detail.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,11 @@ <h5 class="mb-3"><i class="bi bi-diagram-3"></i> Approval History</h5>
242242
{% endfor %}
243243

244244
{% if sub_workflow_groups %}
245-
{# ---- Sub-workflow tasks (detached child workflow stages) ---- #}
246-
<h5 class="mb-3 mt-4"><i class="bi bi-diagram-2"></i> Sub-Workflow Approvals</h5>
245+
{# ---- Sub-workflow tasks ---- #}
247246
{% for stage in sub_workflow_groups %}
247+
{% if forloop.first %}
248+
<h5 class="mb-3 mt-4"><i class="bi bi-list-check"></i> {{ stage.section_label|default:"Additional Approvals" }}</h5>
249+
{% endif %}
248250
<div class="card mb-3
249251
{% if stage.rejected_count > 0 %}border-danger
250252
{% elif stage.approved_count == stage.total_count and stage.total_count > 0 %}border-success

django_forms_workflows/views.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,17 @@ def submission_detail(request, submission_id):
593593
stage_groups = sorted(parent_groups.values(), key=lambda x: x["number"])
594594
if sub_groups:
595595
sub_workflow_groups = sorted(sub_groups.values(), key=lambda x: x["number"])
596+
# Resolve the user-facing section label from SubWorkflowDefinition
597+
if workflow:
598+
sub_wf_config = getattr(workflow, "sub_workflow_config", None)
599+
if sub_wf_config and sub_wf_config.section_label:
600+
for sg in sub_workflow_groups:
601+
sg["section_label"] = sub_wf_config.section_label
602+
elif sub_wf_config:
603+
# Fall back to the sub-workflow's form definition name
604+
sg_name = sub_wf_config.sub_workflow.form_definition.name
605+
for sg in sub_workflow_groups:
606+
sg["section_label"] = sg_name
596607

597608
# Resolve fresh presigned URLs for any file-upload fields
598609
form_data = _resolve_form_data_urls(submission.form_data)

django_forms_workflows/workflow_builder_views.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,7 @@ def convert_workflow_to_visual(workflow, form_definition):
450450
"sub_workflow_def_id": sub_wf_config.id,
451451
"sub_workflow_id": sub_wf.form_definition_id,
452452
"sub_workflow_name": sub_wf.form_definition.name,
453+
"section_label": sub_wf_config.section_label,
453454
"count_field": sub_wf_config.count_field,
454455
"label_template": sub_wf_config.label_template,
455456
"trigger": sub_wf_config.trigger,
@@ -670,6 +671,7 @@ def convert_visual_to_workflow(workflow_data, form_definition):
670671
if target_workflow:
671672
sw_fields = {
672673
"sub_workflow": target_workflow,
674+
"section_label": sw_data.get("section_label", ""),
673675
"count_field": sw_data.get("count_field", ""),
674676
"label_template": sw_data.get(
675677
"label_template", "Sub-workflow {index}"

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.19.2"
3+
version = "0.20.0"
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)