django-forms-workflows 0.76.0__tar.gz → 0.76.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/CHANGELOG.md +47 -0
  2. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/PKG-INFO +13 -1
  3. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/README.md +12 -0
  4. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/admin.py +12 -2
  5. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/diff_views.py +118 -21
  6. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/form_builder_views.py +202 -183
  7. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/forms.py +6 -1
  8. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/models.py +6 -0
  9. django_forms_workflows-0.76.3/django_forms_workflows/static/django_forms_workflows/js/form-builder-history.js +55 -0
  10. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/static/django_forms_workflows/js/form-builder.js +25 -68
  11. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/tasks.py +21 -7
  12. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/admin/django_forms_workflows/form_builder.html +3 -3
  13. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/pyproject.toml +1 -1
  14. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/LICENSE +0 -0
  15. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/__init__.py +0 -0
  16. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/api_urls.py +0 -0
  17. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/api_views.py +0 -0
  18. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/apps.py +0 -0
  19. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/callback_registry.py +0 -0
  20. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/conditions.py +0 -0
  21. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/context_processors.py +0 -0
  22. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/data_sources/__init__.py +0 -0
  23. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/data_sources/base.py +0 -0
  24. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/data_sources/database_source.py +0 -0
  25. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/data_sources/ldap_source.py +0 -0
  26. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/data_sources/user_source.py +0 -0
  27. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/email_backends/__init__.py +0 -0
  28. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/email_backends/gmail_api.py +0 -0
  29. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/form_builder_urls.py +0 -0
  30. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/handlers/__init__.py +0 -0
  31. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/handlers/api_handler.py +0 -0
  32. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/handlers/base.py +0 -0
  33. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/handlers/database_handler.py +0 -0
  34. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/handlers/email_handler.py +0 -0
  35. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/handlers/executor.py +0 -0
  36. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/handlers/file_handler.py +0 -0
  37. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/handlers/ldap_handler.py +0 -0
  38. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/history.py +0 -0
  39. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/ldap_backend.py +0 -0
  40. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/management/commands/create_default_templates.py +0 -0
  41. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/management/commands/pull_forms.py +0 -0
  42. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/management/commands/push_forms.py +0 -0
  43. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/management/commands/seed_farm_demo.py +0 -0
  44. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/management/commands/seed_pcn_form.py +0 -0
  45. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/management/commands/seed_prefill_sources.py +0 -0
  46. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/management/commands/sync_ldap_profiles.py +0 -0
  47. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/management/commands/sync_ldap_users.py +0 -0
  48. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/management/commands/test_db_connection.py +0 -0
  49. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0001_initial.py +0 -0
  50. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0002_prefillsource_alter_formfield_prefill_source_and_more.py +0 -0
  51. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0003_postsubmissionaction.py +0 -0
  52. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0004_formtemplate.py +0 -0
  53. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0005_advanced_conditional_logic.py +0 -0
  54. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0006_workflowdefinition_visual_workflow_data_and_more.py +0 -0
  55. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0007_add_userprofile_ldap_enhancements.py +0 -0
  56. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0008_add_file_workflow_models.py +0 -0
  57. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0009_add_formfield_readonly.py +0 -0
  58. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0010_add_prefillsource_template_fields.py +0 -0
  59. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0011_postsubmissionaction_email_body_template_and_more.py +0 -0
  60. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0012_add_approval_step_fields.py +0 -0
  61. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0013_add_database_query_key.py +0 -0
  62. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0014_add_form_category.py +0 -0
  63. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0015_add_workflowstage_staged_approvals.py +0 -0
  64. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0016_add_pdf_generation.py +0 -0
  65. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0017_add_notification_batching.py +0 -0
  66. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0018_add_form_category_parent.py +0 -0
  67. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0019_add_ldap_group_profile.py +0 -0
  68. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0020_add_bulk_export_field.py +0 -0
  69. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0021_alter_pendingnotification_scheduled_for_and_more.py +0 -0
  70. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0022_add_bulk_pdf_export.py +0 -0
  71. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0023_add_performance_indexes.py +0 -0
  72. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0024_add_allow_resubmit.py +0 -0
  73. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0025_workflowstage_approve_label.py +0 -0
  74. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0026_alter_formfield_width.py +0 -0
  75. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0027_formsubmission_form_data_gin.py +0 -0
  76. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0028_workflowstagegroupconfig.py +0 -0
  77. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0029_formfield_workflow_stage_fk_drop_groupconfig.py +0 -0
  78. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0030_formfield_phone_type.py +0 -0
  79. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0031_sub_workflows.py +0 -0
  80. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0032_approved_pending_detached_sub_workflow.py +0 -0
  81. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0033_add_reject_parent_to_subworkflowdefinition.py +0 -0
  82. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0034_add_notificationlog.py +0 -0
  83. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0035_migrate_prefill_source_strings.py +0 -0
  84. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0036_add_workflowstage_auto_created.py +0 -0
  85. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0037_migrate_flat_workflows_to_staged.py +0 -0
  86. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0038_remove_legacy_prefill_source_and_approval_step.py +0 -0
  87. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0039_update_help_text.py +0 -0
  88. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0040_migrate_deprecated_features.py +0 -0
  89. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0041_remove_deprecated_fields.py +0 -0
  90. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0042_add_sub_workflow_section_label.py +0 -0
  91. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0043_fix_form_data_decimal_encoding.py +0 -0
  92. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0044_remove_subworkflowinstance_label.py +0 -0
  93. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0045_stageapprovalgroup_workflow_name_label.py +0 -0
  94. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0046_workflow_definition_one_to_many.py +0 -0
  95. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0047_add_hide_approval_history.py +0 -0
  96. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0048_workflowdefinition_trigger_conditions_and_more.py +0 -0
  97. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0049_formfield_multifile_type.py +0 -0
  98. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0050_workflowdefinition_collapse_parallel_stages.py +0 -0
  99. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0051_formdefinition_is_listed.py +0 -0
  100. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0052_dynamic_assignee_and_form_field_notifications.py +0 -0
  101. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0053_stageformfieldnotification_static_emails.py +0 -0
  102. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0054_rename_approved_pending_to_pending_approval.py +0 -0
  103. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0055_add_send_back.py +0 -0
  104. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0056_calculated_formula_spreadsheet.py +0 -0
  105. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0057_add_currency_field_type.py +0 -0
  106. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0058_rename_assignee_email_field_add_lookup_type.py +0 -0
  107. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0059_add_multiselect_list_field_type.py +0 -0
  108. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0060_alter_workflowstage_assignee_lookup_type_helptext.py +0 -0
  109. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0061_add_country_us_state_field_types.py +0 -0
  110. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0062_add_batch_import_to_formdefinition.py +0 -0
  111. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0063_formdefinition_api_enabled_apitoken.py +0 -0
  112. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0064_add_reviewer_groups.py +0 -0
  113. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0065_workflownotification.py +0 -0
  114. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0066_add_notify_submitter_to_workflownotification.py +0 -0
  115. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0067_migrate_legacy_notify_flags.py +0 -0
  116. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0068_drop_legacy_notify_fields.py +0 -0
  117. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0069_add_signature_field_type.py +0 -0
  118. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0070_workflowstage_allow_edit_form_data.py +0 -0
  119. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0071_alter_pendingnotification_notification_type.py +0 -0
  120. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0072_add_change_history.py +0 -0
  121. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0073_add_stage_notify_assignee_on_final_decision.py +0 -0
  122. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0074_add_notification_rule.py +0 -0
  123. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0075_migrate_to_notification_rules.py +0 -0
  124. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0076_update_notification_event_choices.py +0 -0
  125. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0077_drop_legacy_notification_models.py +0 -0
  126. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0078_allow_anonymous_submissions.py +0 -0
  127. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0079_webhookendpoint_webhookdeliverylog.py +0 -0
  128. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0080_add_rating_matrix_address_slider_fields_and_form_controls.py +0 -0
  129. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0081_add_success_page_fields.py +0 -0
  130. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0082_add_document_template_model.py +0 -0
  131. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0083_workflowdefinition_start_trigger.py +0 -0
  132. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0084_add_shared_option_list.py +0 -0
  133. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0085_add_payment_system.py +0 -0
  134. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0086_add_embed_enabled.py +0 -0
  135. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0087_notificationrule_use_triggering_stage.py +0 -0
  136. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0088_notificationrule_body_template_and_events.py +0 -0
  137. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0089_workflowstage_hide_comment_field.py +0 -0
  138. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0090_alter_stageapprovalgroup_options_and_more.py +0 -0
  139. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0091_add_uuid_fields.py +0 -0
  140. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0092_add_user_notification_preference.py +0 -0
  141. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0093_normalize_conditional_operators.py +0 -0
  142. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0094_formfield_show_help_text_in_detail.py +0 -0
  143. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0095_notificationrule_cc_fields.py +0 -0
  144. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0096_notificationlog_cc_bcc_emails.py +0 -0
  145. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/0097_notificationlog_delivery_reconciliation.py +0 -0
  146. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/migrations/__init__.py +0 -0
  147. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/payments/__init__.py +0 -0
  148. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/payments/base.py +0 -0
  149. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/payments/registry.py +0 -0
  150. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/payments/stripe_provider.py +0 -0
  151. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/payments/views.py +0 -0
  152. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/reporting_views.py +0 -0
  153. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/signals.py +0 -0
  154. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/sso_backends.py +0 -0
  155. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/sso_urls.py +0 -0
  156. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/sso_views.py +0 -0
  157. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/static/django_forms_workflows/api/swagger-ui-bundle.js +0 -0
  158. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/static/django_forms_workflows/api/swagger-ui.css +0 -0
  159. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/static/django_forms_workflows/css/forms.css +0 -0
  160. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/static/django_forms_workflows/js/admin_notification_rule_stage_filter.js +0 -0
  161. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/static/django_forms_workflows/js/dfw-embed.js +0 -0
  162. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/static/django_forms_workflows/js/form-enhancements.js +0 -0
  163. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/static/django_forms_workflows/js/payment-stripe.js +0 -0
  164. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/static/django_forms_workflows/js/signature-pad.js +0 -0
  165. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/static/django_forms_workflows/js/workflow-builder.js +0 -0
  166. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/sync_api.py +0 -0
  167. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/sync_views.py +0 -0
  168. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/admin/django_forms_workflows/_sync_diff.html +0 -0
  169. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/admin/django_forms_workflows/diff_forms.html +0 -0
  170. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/admin/django_forms_workflows/formdef_change_form.html +0 -0
  171. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/admin/django_forms_workflows/formdefinition/change_list.html +0 -0
  172. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/admin/django_forms_workflows/sync_import.html +0 -0
  173. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/admin/django_forms_workflows/sync_pull.html +0 -0
  174. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/admin/django_forms_workflows/sync_push.html +0 -0
  175. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/admin/django_forms_workflows/workflow_builder.html +0 -0
  176. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/_category_node.html +0 -0
  177. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/_field_value.html +0 -0
  178. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/_form_data_rows.html +0 -0
  179. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/analytics_dashboard.html +0 -0
  180. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/api/docs.html +0 -0
  181. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/approval_inbox.html +0 -0
  182. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/approve.html +0 -0
  183. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/base.html +0 -0
  184. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/batch_import_result.html +0 -0
  185. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/completed_approvals.html +0 -0
  186. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/discard_draft_confirm.html +0 -0
  187. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/embed_base.html +0 -0
  188. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/embed_success.html +0 -0
  189. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/form_categories.html +0 -0
  190. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/form_embed.html +0 -0
  191. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/form_list.html +0 -0
  192. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/form_submit.html +0 -0
  193. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/my_submissions.html +0 -0
  194. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/notification_preferences.html +0 -0
  195. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/payment_collect.html +0 -0
  196. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/payment_error.html +0 -0
  197. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/public_submission_confirmation.html +0 -0
  198. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/rate_limited.html +0 -0
  199. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/reassign_task.html +0 -0
  200. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/resubmit_confirm.html +0 -0
  201. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/sso/login.html +0 -0
  202. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/sub_workflow_detail.html +0 -0
  203. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/submission_bulk_pdf.html +0 -0
  204. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/submission_detail.html +0 -0
  205. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/submission_pdf.html +0 -0
  206. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/submission_success.html +0 -0
  207. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/django_forms_workflows/withdraw_confirm.html +0 -0
  208. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/emails/approval_notification.html +0 -0
  209. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/emails/approval_reminder.html +0 -0
  210. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/emails/approval_request.html +0 -0
  211. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/emails/email_styles.html +0 -0
  212. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/emails/escalation_notification.html +0 -0
  213. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/emails/notification_digest.html +0 -0
  214. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/emails/rejection_notification.html +0 -0
  215. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/emails/submission_notification.html +0 -0
  216. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templates/registration/login.html +0 -0
  217. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templatetags/__init__.py +0 -0
  218. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/templatetags/forms_workflows_tags.py +0 -0
  219. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/urls.py +0 -0
  220. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/utils.py +0 -0
  221. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/views.py +0 -0
  222. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/workflow_builder_views.py +0 -0
  223. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.3}/django_forms_workflows/workflow_engine.py +0 -0
@@ -7,6 +7,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Changed
11
+ - **Form builder save logic factored into a standalone helper.**
12
+ `form_builder_save`'s create/update logic for `FormDefinition` and its
13
+ `FormField`s now lives in `save_form_definition_from_builder_data()`,
14
+ mirroring the existing `convert_visual_to_workflow` split already used by
15
+ the workflow builder. Makes the logic callable (and unit-testable)
16
+ independently of the `@staff_member_required`-decorated HTTP view — no
17
+ behavior or API change.
18
+ - **Notification rules identify their workflow in the admin.** The standalone
19
+ `NotificationRule` list rendered only `workflow.form_definition.name`, so a
20
+ rule on a parent workflow and a rule on its sub-workflow both displayed the
21
+ same form name — and for workflow-level rules the Stage column reads
22
+ "— (all stages)" on either one, giving nothing to tell them apart. The
23
+ workflow column now appends `WorkflowDefinition.name_label` when set
24
+ (`Course Development Request — Payment`), `workflow__name_label` was added to
25
+ `search_fields`, and `workflow` was added to `list_filter`. `name_label` also
26
+ joins `WorkflowDefinitionAdmin.search_fields` so the rule form's `workflow`
27
+ autocomplete can match on it, and `WorkflowDefinition.__str__` now carries the
28
+ label so every FK picker, autocomplete result, and log entry disambiguates.
29
+
30
+ ### Fixed
31
+ - **Browser value suggestions restored on generic text fields.** The
32
+ password-manager opt-out added in 0.74.15 set `autocomplete="off"` alongside
33
+ the `data-1p-ignore` / `data-lpignore` / `data-bwignore` / `data-form-type`
34
+ attributes on every field type in `_PM_OPT_OUT_FIELD_TYPES`. Those `data-*`
35
+ attributes are what actually silence 1Password, LastPass, Bitwarden, and
36
+ Dashlane; `autocomplete="off"` additionally disabled the *browser's* own
37
+ saved-value dropdown, which users relied on for fields like Position/Job
38
+ Title, Department Name, and Supervisor. `autocomplete` is no longer set, so
39
+ native suggestions work again while password-manager popovers stay
40
+ suppressed. Fields with their own suggestion UI (LDAP lookup) are unaffected
41
+ — `ldap-lookup.js` sets `autocomplete="off"` on them directly.
42
+ - **Diff and sync summaries no longer ignore sub-workflows.** `_build_summary`
43
+ read only the `"workflow"` key, but `serialize_form` stores the first
44
+ workflow there and every other one under `"additional_workflows"`. A
45
+ sub-workflow's stages, settings, and notification rules were therefore
46
+ serialized into the payload but never compared — invisible in the standalone
47
+ Diff view and in the Sync **Push** and **Pull** previews, all three of which
48
+ share this function. Every workflow is now compared. Pairs are matched on
49
+ `uuid` (creation order, and so list position, need not agree across
50
+ environments) and fall back to position for payloads predating the field and
51
+ for the diff view's cross-form comparisons; unpaired workflows are reported
52
+ as added/removed. Bullets are prefixed with the workflow's `name_label`
53
+ (`Workflow 'Payment' stages added: Bursar`) so parent and sub-workflow
54
+ changes are distinguishable. `start_trigger` and `sub_workflow_config` — the
55
+ two settings that define sub-workflow sequencing — are now compared as well.
56
+
10
57
  ## [0.76.0] - 2026-06-09
11
58
 
12
59
  ### Removed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-forms-workflows
3
- Version: 0.76.0
3
+ Version: 0.76.3
4
4
  Summary: Enterprise-grade, database-driven form builder with approval workflows and external data integration
5
5
  License: LGPL-3.0-only
6
6
  License-File: LICENSE
@@ -418,6 +418,7 @@ graph TB
418
418
  - Optional: `openpyxl` for Excel spreadsheet field support (`pip install django-forms-workflows[excel]`)
419
419
  - Optional: `django-auth-ldap` for LDAP/AD integration (`pip install django-forms-workflows[ldap]`)
420
420
  - Optional: WeasyPrint for PDF export (`pip install django-forms-workflows[pdf]`)
421
+ - Optional: Node.js ^20.19.0 or >=22.12.0 / npm, to run the JS test suite (`npm test`) covering the visual Form/Workflow Builder's client-side code (an `.nvmrc` is committed at the repo root)
421
422
 
422
423
  ## Testing
423
424
 
@@ -429,6 +430,17 @@ python -m pytest tests/ -v
429
430
 
430
431
  The test suite covers models, forms, workflow engine (including dynamic assignees, conditional stages, multi-workflow parallel tracks, sub-workflows), sync API, post-submission action executor, views, signals, conditions, and utilities — **298 tests**.
431
432
 
433
+ ### JavaScript tests
434
+
435
+ The visual Form Builder's client-side JS (`django_forms_workflows/static/django_forms_workflows/js/form-builder.js`) has its own suite, run with [Vitest](https://vitest.dev/):
436
+
437
+ ```bash
438
+ npm install
439
+ npm test
440
+ ```
441
+
442
+ `npm run test:watch` re-runs on file changes. Coverage is still minimal (tooling was just added) — see [CONTRIBUTING.md](CONTRIBUTING.md) if you're adding to it.
443
+
432
444
  ## Contributing
433
445
 
434
446
  We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
@@ -351,6 +351,7 @@ graph TB
351
351
  - Optional: `openpyxl` for Excel spreadsheet field support (`pip install django-forms-workflows[excel]`)
352
352
  - Optional: `django-auth-ldap` for LDAP/AD integration (`pip install django-forms-workflows[ldap]`)
353
353
  - Optional: WeasyPrint for PDF export (`pip install django-forms-workflows[pdf]`)
354
+ - Optional: Node.js ^20.19.0 or >=22.12.0 / npm, to run the JS test suite (`npm test`) covering the visual Form/Workflow Builder's client-side code (an `.nvmrc` is committed at the repo root)
354
355
 
355
356
  ## Testing
356
357
 
@@ -362,6 +363,17 @@ python -m pytest tests/ -v
362
363
 
363
364
  The test suite covers models, forms, workflow engine (including dynamic assignees, conditional stages, multi-workflow parallel tracks, sub-workflows), sync API, post-submission action executor, views, signals, conditions, and utilities — **298 tests**.
364
365
 
366
+ ### JavaScript tests
367
+
368
+ The visual Form Builder's client-side JS (`django_forms_workflows/static/django_forms_workflows/js/form-builder.js`) has its own suite, run with [Vitest](https://vitest.dev/):
369
+
370
+ ```bash
371
+ npm install
372
+ npm test
373
+ ```
374
+
375
+ `npm run test:watch` re-runs on file changes. Coverage is still minimal (tooling was just added) — see [CONTRIBUTING.md](CONTRIBUTING.md) if you're adding to it.
376
+
365
377
  ## Contributing
366
378
 
367
379
  We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
@@ -1770,7 +1770,7 @@ class WorkflowDefinitionAdmin(nested_admin.NestedModelAdmin):
1770
1770
  "allow_bulk_export",
1771
1771
  "allow_bulk_pdf_export",
1772
1772
  )
1773
- search_fields = ("form_definition__name",)
1773
+ search_fields = ("form_definition__name", "name_label")
1774
1774
  readonly_fields = ("uuid",)
1775
1775
  fieldsets = (
1776
1776
  (
@@ -1891,10 +1891,12 @@ class NotificationRuleAdmin(admin.ModelAdmin):
1891
1891
  "notify_stage_assignees",
1892
1892
  "notify_stage_groups",
1893
1893
  "workflow__form_definition",
1894
+ "workflow",
1894
1895
  )
1895
1896
  list_select_related = ("workflow__form_definition", "stage")
1896
1897
  search_fields = (
1897
1898
  "workflow__form_definition__name",
1899
+ "workflow__name_label",
1898
1900
  "stage__name",
1899
1901
  "email_field",
1900
1902
  "static_emails",
@@ -1950,7 +1952,15 @@ class NotificationRuleAdmin(admin.ModelAdmin):
1950
1952
  description="Form / Workflow", ordering="workflow__form_definition__name"
1951
1953
  )
1952
1954
  def workflow_form(self, obj):
1953
- return obj.workflow.form_definition.name if obj.workflow_id else "—"
1955
+ if not obj.workflow_id:
1956
+ return "—"
1957
+ # Append ``name_label`` so a rule on a parent workflow and one on its
1958
+ # sub-workflow are distinguishable here — both share a form name, and
1959
+ # for workflow-level rules the Stage column reads "— (all stages)" on
1960
+ # either one.
1961
+ name = obj.workflow.form_definition.name
1962
+ label = obj.workflow.name_label
1963
+ return f"{name} — {label}" if label else name
1954
1964
 
1955
1965
  @admin.display(description="Stage", ordering="stage__name")
1956
1966
  def stage_name(self, obj):
@@ -11,6 +11,74 @@ from .models import FormDefinition
11
11
  from .sync_api import build_export_payload
12
12
 
13
13
 
14
+ def _payload_workflows(payload):
15
+ """Return every workflow in a serialized payload, primary first.
16
+
17
+ ``serialize_form`` splits a form's workflows across two keys — ``workflow``
18
+ holds the first one by id, ``additional_workflows`` holds the rest — so
19
+ anything reading only ``workflow`` is blind to sub-workflows.
20
+ """
21
+ workflows = []
22
+ primary = payload.get("workflow")
23
+ if primary:
24
+ workflows.append(primary)
25
+ workflows.extend(wf for wf in (payload.get("additional_workflows") or []) if wf)
26
+ return workflows
27
+
28
+
29
+ def _pair_workflows(b_wfs, o_wfs):
30
+ """Pair base/other workflows, yielding ``(index, base_wf, other_wf)`` tuples.
31
+
32
+ Matched on ``uuid`` first, because creation order — and therefore position
33
+ in the list — is not guaranteed to agree between environments. Whatever
34
+ uuid matching leaves over is paired by position, which covers payloads
35
+ predating the uuid field and the standalone diff view, where two *different*
36
+ forms are compared and no uuid will ever match. Anything still unpaired is
37
+ reported as added or removed.
38
+ """
39
+ o_by_uuid = {}
40
+ for idx, wf in enumerate(o_wfs):
41
+ uid = wf.get("uuid")
42
+ if uid:
43
+ o_by_uuid.setdefault(uid, []).append(idx)
44
+
45
+ pairs = []
46
+ matched_o = set()
47
+ unmatched_b = []
48
+ for idx, wf in enumerate(b_wfs):
49
+ candidates = o_by_uuid.get(wf.get("uuid")) or []
50
+ match = next((c for c in candidates if c not in matched_o), None)
51
+ if match is None:
52
+ unmatched_b.append(idx)
53
+ else:
54
+ matched_o.add(match)
55
+ pairs.append((idx, wf, o_wfs[match]))
56
+
57
+ leftover_o = [i for i in range(len(o_wfs)) if i not in matched_o]
58
+ for b_idx, o_idx in zip(unmatched_b, leftover_o, strict=False):
59
+ pairs.append((b_idx, b_wfs[b_idx], o_wfs[o_idx]))
60
+ overlap = min(len(unmatched_b), len(leftover_o))
61
+ for b_idx in unmatched_b[overlap:]:
62
+ pairs.append((b_idx, b_wfs[b_idx], None))
63
+ for o_idx in leftover_o[overlap:]:
64
+ pairs.append((o_idx, None, o_wfs[o_idx]))
65
+
66
+ pairs.sort(key=lambda p: p[0])
67
+ return pairs
68
+
69
+
70
+ def _workflow_label(wf, index):
71
+ """Label a workflow in the summary bullets.
72
+
73
+ ``name_label`` is what distinguishes a parent workflow from its
74
+ sub-workflows on the same form, so it is preferred when set.
75
+ """
76
+ name_label = (wf or {}).get("name_label")
77
+ if name_label:
78
+ return f"Workflow '{name_label}'"
79
+ return "Workflow" if index == 0 else f"Workflow #{index + 1}"
80
+
81
+
14
82
  def _build_summary(forms_data):
15
83
  """Build a supplemental summary of key differences between serialized forms."""
16
84
  if len(forms_data) < 2:
@@ -49,19 +117,21 @@ def _build_summary(forms_data):
49
117
  if changed_fields:
50
118
  diffs.append(f"Fields modified: {', '.join(changed_fields)}")
51
119
 
52
- # Workflow differences
53
- b_wf = base.get("workflow")
54
- o_wf = other.get("workflow")
55
- if (b_wf is None) != (o_wf is None):
56
- diffs.append(
57
- f"Workflow: {'present' if b_wf else 'absent'}"
58
- f" → {'present' if o_wf else 'absent'}"
59
- )
60
- elif b_wf and o_wf:
120
+ # Workflow differences — every workflow, not only the primary one.
121
+ for wf_index, b_wf, o_wf in _pair_workflows(
122
+ _payload_workflows(base), _payload_workflows(other)
123
+ ):
124
+ wf_label = _workflow_label(b_wf or o_wf, wf_index)
125
+ if b_wf is None or o_wf is None:
126
+ diffs.append(
127
+ f"{wf_label}: {'present' if b_wf else 'absent'}"
128
+ f" → {'present' if o_wf else 'absent'}"
129
+ )
130
+ continue
61
131
  b_stages = b_wf.get("stages", [])
62
132
  o_stages = o_wf.get("stages", [])
63
133
  if len(b_stages) != len(o_stages):
64
- diffs.append(f"Workflow stages: {len(b_stages)} → {len(o_stages)}")
134
+ diffs.append(f"{wf_label} stages: {len(b_stages)} → {len(o_stages)}")
65
135
 
66
136
  # Per-stage comparison keyed on (order, name) to handle parallel stages
67
137
  b_stage_map = {(s.get("order"), s.get("name")): s for s in b_stages}
@@ -69,10 +139,13 @@ def _build_summary(forms_data):
69
139
  added_stages = sorted(o_stage_map.keys() - b_stage_map.keys())
70
140
  removed_stages = sorted(b_stage_map.keys() - o_stage_map.keys())
71
141
  if added_stages:
72
- diffs.append(f"Stages added: {', '.join(n for _, n in added_stages)}")
142
+ diffs.append(
143
+ f"{wf_label} stages added: {', '.join(n for _, n in added_stages)}"
144
+ )
73
145
  if removed_stages:
74
146
  diffs.append(
75
- f"Stages removed: {', '.join(n for _, n in removed_stages)}"
147
+ f"{wf_label} stages removed: "
148
+ f"{', '.join(n for _, n in removed_stages)}"
76
149
  )
77
150
  stage_field_keys = [
78
151
  "assignee_form_field",
@@ -91,7 +164,8 @@ def _build_summary(forms_data):
91
164
  for sk in stage_field_keys:
92
165
  if bs.get(sk) != os_.get(sk):
93
166
  diffs.append(
94
- f"Stage '{key[1]}' {sk}: {bs.get(sk)!r} → {os_.get(sk)!r}"
167
+ f"{wf_label} stage '{key[1]}' {sk}: "
168
+ f"{bs.get(sk)!r} → {os_.get(sk)!r}"
95
169
  )
96
170
  b_notifs = bs.get(
97
171
  "notification_rules", bs.get("form_field_notifications", [])
@@ -101,7 +175,8 @@ def _build_summary(forms_data):
101
175
  )
102
176
  if len(b_notifs) != len(o_notifs):
103
177
  diffs.append(
104
- f"Stage '{key[1]}' notifications: {len(b_notifs)} → {len(o_notifs)}"
178
+ f"{wf_label} stage '{key[1]}' notifications: "
179
+ f"{len(b_notifs)} → {len(o_notifs)}"
105
180
  )
106
181
  elif b_notifs != o_notifs:
107
182
  for idx, (bn, on) in enumerate(
@@ -114,8 +189,8 @@ def _build_summary(forms_data):
114
189
  )
115
190
  label = bn.get("event") or on.get("event") or f"#{idx}"
116
191
  diffs.append(
117
- f"Stage '{key[1]}' notification '{label}' changed: "
118
- f"{', '.join(changed_keys)}"
192
+ f"{wf_label} stage '{key[1]}' notification "
193
+ f"'{label}' changed: {', '.join(changed_keys)}"
119
194
  )
120
195
  # Approval groups: support both old string lists and new dict lists
121
196
  b_groups = {
@@ -135,12 +210,14 @@ def _build_summary(forms_data):
135
210
  if removed_g:
136
211
  parts.append(f"-{', '.join(sorted(removed_g))}")
137
212
  diffs.append(
138
- f"Stage '{key[1]}' approval_groups: {'; '.join(parts)}"
213
+ f"{wf_label} stage '{key[1]}' approval_groups: "
214
+ f"{'; '.join(parts)}"
139
215
  )
140
216
 
141
217
  wf_setting_keys = [
142
218
  "requires_approval",
143
219
  "name_label",
220
+ "start_trigger",
144
221
  "hide_approval_history",
145
222
  "collapse_parallel_stages",
146
223
  "allow_bulk_export",
@@ -156,14 +233,34 @@ def _build_summary(forms_data):
156
233
  ]
157
234
  for key in wf_setting_keys:
158
235
  if b_wf.get(key) != o_wf.get(key):
159
- diffs.append(f"Workflow {key}: {b_wf.get(key)} → {o_wf.get(key)}")
236
+ diffs.append(f"{wf_label} {key}: {b_wf.get(key)} → {o_wf.get(key)}")
237
+
238
+ # Sub-workflow wiring. Compared by changed key rather than dumped
239
+ # whole — the config is a nested dict and printing both sides makes
240
+ # the summary unreadable.
241
+ b_swc = b_wf.get("sub_workflow_config")
242
+ o_swc = o_wf.get("sub_workflow_config")
243
+ if (b_swc is None) != (o_swc is None):
244
+ diffs.append(
245
+ f"{wf_label} sub_workflow_config: "
246
+ f"{'present' if b_swc else 'absent'}"
247
+ f" → {'present' if o_swc else 'absent'}"
248
+ )
249
+ elif b_swc and o_swc and b_swc != o_swc:
250
+ swc_keys = sorted(
251
+ k for k in set(b_swc) | set(o_swc) if b_swc.get(k) != o_swc.get(k)
252
+ )
253
+ diffs.append(
254
+ f"{wf_label} sub_workflow_config changed: {', '.join(swc_keys)}"
255
+ )
160
256
 
161
257
  # Workflow-level notification rules (stage=null)
162
258
  b_wf_notifs = b_wf.get("notification_rules", [])
163
259
  o_wf_notifs = o_wf.get("notification_rules", [])
164
260
  if len(b_wf_notifs) != len(o_wf_notifs):
165
261
  diffs.append(
166
- f"Workflow notification rules: {len(b_wf_notifs)} → {len(o_wf_notifs)}"
262
+ f"{wf_label} notification rules: "
263
+ f"{len(b_wf_notifs)} → {len(o_wf_notifs)}"
167
264
  )
168
265
  elif b_wf_notifs != o_wf_notifs:
169
266
  # Same count but content differs — surface what changed.
@@ -178,12 +275,12 @@ def _build_summary(forms_data):
178
275
  )
179
276
  label = br.get("event") or or_.get("event") or f"#{idx}"
180
277
  diffs.append(
181
- f"Workflow notification rule '{label}' changed: "
278
+ f"{wf_label} notification rule '{label}' changed: "
182
279
  f"{', '.join(changed_keys)}"
183
280
  )
184
281
  changed.append(label)
185
282
  if not changed:
186
- diffs.append("Workflow notification rules: order changed")
283
+ diffs.append(f"{wf_label} notification rules: order changed")
187
284
 
188
285
  # Form metadata
189
286
  meta_keys = [