django-forms-workflows 0.76.0__tar.gz → 0.76.1__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 (222) hide show
  1. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/CHANGELOG.md +9 -0
  2. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/PKG-INFO +1 -1
  3. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/form_builder_views.py +202 -183
  4. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/tasks.py +21 -7
  5. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/pyproject.toml +1 -1
  6. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/LICENSE +0 -0
  7. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/README.md +0 -0
  8. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/__init__.py +0 -0
  9. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/admin.py +0 -0
  10. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/api_urls.py +0 -0
  11. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/api_views.py +0 -0
  12. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/apps.py +0 -0
  13. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/callback_registry.py +0 -0
  14. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/conditions.py +0 -0
  15. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/context_processors.py +0 -0
  16. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/data_sources/__init__.py +0 -0
  17. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/data_sources/base.py +0 -0
  18. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/data_sources/database_source.py +0 -0
  19. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/data_sources/ldap_source.py +0 -0
  20. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/data_sources/user_source.py +0 -0
  21. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/diff_views.py +0 -0
  22. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/email_backends/__init__.py +0 -0
  23. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/email_backends/gmail_api.py +0 -0
  24. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/form_builder_urls.py +0 -0
  25. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/forms.py +0 -0
  26. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/handlers/__init__.py +0 -0
  27. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/handlers/api_handler.py +0 -0
  28. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/handlers/base.py +0 -0
  29. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/handlers/database_handler.py +0 -0
  30. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/handlers/email_handler.py +0 -0
  31. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/handlers/executor.py +0 -0
  32. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/handlers/file_handler.py +0 -0
  33. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/handlers/ldap_handler.py +0 -0
  34. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/history.py +0 -0
  35. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/ldap_backend.py +0 -0
  36. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/management/commands/create_default_templates.py +0 -0
  37. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/management/commands/pull_forms.py +0 -0
  38. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/management/commands/push_forms.py +0 -0
  39. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/management/commands/seed_farm_demo.py +0 -0
  40. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/management/commands/seed_pcn_form.py +0 -0
  41. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/management/commands/seed_prefill_sources.py +0 -0
  42. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/management/commands/sync_ldap_profiles.py +0 -0
  43. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/management/commands/sync_ldap_users.py +0 -0
  44. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/management/commands/test_db_connection.py +0 -0
  45. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0001_initial.py +0 -0
  46. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0002_prefillsource_alter_formfield_prefill_source_and_more.py +0 -0
  47. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0003_postsubmissionaction.py +0 -0
  48. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0004_formtemplate.py +0 -0
  49. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0005_advanced_conditional_logic.py +0 -0
  50. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0006_workflowdefinition_visual_workflow_data_and_more.py +0 -0
  51. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0007_add_userprofile_ldap_enhancements.py +0 -0
  52. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0008_add_file_workflow_models.py +0 -0
  53. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0009_add_formfield_readonly.py +0 -0
  54. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0010_add_prefillsource_template_fields.py +0 -0
  55. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0011_postsubmissionaction_email_body_template_and_more.py +0 -0
  56. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0012_add_approval_step_fields.py +0 -0
  57. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0013_add_database_query_key.py +0 -0
  58. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0014_add_form_category.py +0 -0
  59. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0015_add_workflowstage_staged_approvals.py +0 -0
  60. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0016_add_pdf_generation.py +0 -0
  61. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0017_add_notification_batching.py +0 -0
  62. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0018_add_form_category_parent.py +0 -0
  63. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0019_add_ldap_group_profile.py +0 -0
  64. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0020_add_bulk_export_field.py +0 -0
  65. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0021_alter_pendingnotification_scheduled_for_and_more.py +0 -0
  66. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0022_add_bulk_pdf_export.py +0 -0
  67. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0023_add_performance_indexes.py +0 -0
  68. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0024_add_allow_resubmit.py +0 -0
  69. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0025_workflowstage_approve_label.py +0 -0
  70. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0026_alter_formfield_width.py +0 -0
  71. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0027_formsubmission_form_data_gin.py +0 -0
  72. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0028_workflowstagegroupconfig.py +0 -0
  73. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0029_formfield_workflow_stage_fk_drop_groupconfig.py +0 -0
  74. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0030_formfield_phone_type.py +0 -0
  75. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0031_sub_workflows.py +0 -0
  76. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0032_approved_pending_detached_sub_workflow.py +0 -0
  77. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0033_add_reject_parent_to_subworkflowdefinition.py +0 -0
  78. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0034_add_notificationlog.py +0 -0
  79. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0035_migrate_prefill_source_strings.py +0 -0
  80. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0036_add_workflowstage_auto_created.py +0 -0
  81. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0037_migrate_flat_workflows_to_staged.py +0 -0
  82. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0038_remove_legacy_prefill_source_and_approval_step.py +0 -0
  83. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0039_update_help_text.py +0 -0
  84. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0040_migrate_deprecated_features.py +0 -0
  85. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0041_remove_deprecated_fields.py +0 -0
  86. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0042_add_sub_workflow_section_label.py +0 -0
  87. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0043_fix_form_data_decimal_encoding.py +0 -0
  88. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0044_remove_subworkflowinstance_label.py +0 -0
  89. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0045_stageapprovalgroup_workflow_name_label.py +0 -0
  90. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0046_workflow_definition_one_to_many.py +0 -0
  91. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0047_add_hide_approval_history.py +0 -0
  92. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0048_workflowdefinition_trigger_conditions_and_more.py +0 -0
  93. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0049_formfield_multifile_type.py +0 -0
  94. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0050_workflowdefinition_collapse_parallel_stages.py +0 -0
  95. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0051_formdefinition_is_listed.py +0 -0
  96. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0052_dynamic_assignee_and_form_field_notifications.py +0 -0
  97. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0053_stageformfieldnotification_static_emails.py +0 -0
  98. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0054_rename_approved_pending_to_pending_approval.py +0 -0
  99. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0055_add_send_back.py +0 -0
  100. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0056_calculated_formula_spreadsheet.py +0 -0
  101. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0057_add_currency_field_type.py +0 -0
  102. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0058_rename_assignee_email_field_add_lookup_type.py +0 -0
  103. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0059_add_multiselect_list_field_type.py +0 -0
  104. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0060_alter_workflowstage_assignee_lookup_type_helptext.py +0 -0
  105. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0061_add_country_us_state_field_types.py +0 -0
  106. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0062_add_batch_import_to_formdefinition.py +0 -0
  107. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0063_formdefinition_api_enabled_apitoken.py +0 -0
  108. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0064_add_reviewer_groups.py +0 -0
  109. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0065_workflownotification.py +0 -0
  110. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0066_add_notify_submitter_to_workflownotification.py +0 -0
  111. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0067_migrate_legacy_notify_flags.py +0 -0
  112. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0068_drop_legacy_notify_fields.py +0 -0
  113. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0069_add_signature_field_type.py +0 -0
  114. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0070_workflowstage_allow_edit_form_data.py +0 -0
  115. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0071_alter_pendingnotification_notification_type.py +0 -0
  116. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0072_add_change_history.py +0 -0
  117. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0073_add_stage_notify_assignee_on_final_decision.py +0 -0
  118. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0074_add_notification_rule.py +0 -0
  119. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0075_migrate_to_notification_rules.py +0 -0
  120. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0076_update_notification_event_choices.py +0 -0
  121. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0077_drop_legacy_notification_models.py +0 -0
  122. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0078_allow_anonymous_submissions.py +0 -0
  123. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0079_webhookendpoint_webhookdeliverylog.py +0 -0
  124. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0080_add_rating_matrix_address_slider_fields_and_form_controls.py +0 -0
  125. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0081_add_success_page_fields.py +0 -0
  126. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0082_add_document_template_model.py +0 -0
  127. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0083_workflowdefinition_start_trigger.py +0 -0
  128. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0084_add_shared_option_list.py +0 -0
  129. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0085_add_payment_system.py +0 -0
  130. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0086_add_embed_enabled.py +0 -0
  131. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0087_notificationrule_use_triggering_stage.py +0 -0
  132. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0088_notificationrule_body_template_and_events.py +0 -0
  133. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0089_workflowstage_hide_comment_field.py +0 -0
  134. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0090_alter_stageapprovalgroup_options_and_more.py +0 -0
  135. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0091_add_uuid_fields.py +0 -0
  136. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0092_add_user_notification_preference.py +0 -0
  137. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0093_normalize_conditional_operators.py +0 -0
  138. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0094_formfield_show_help_text_in_detail.py +0 -0
  139. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0095_notificationrule_cc_fields.py +0 -0
  140. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0096_notificationlog_cc_bcc_emails.py +0 -0
  141. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/0097_notificationlog_delivery_reconciliation.py +0 -0
  142. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/migrations/__init__.py +0 -0
  143. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/models.py +0 -0
  144. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/payments/__init__.py +0 -0
  145. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/payments/base.py +0 -0
  146. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/payments/registry.py +0 -0
  147. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/payments/stripe_provider.py +0 -0
  148. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/payments/views.py +0 -0
  149. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/reporting_views.py +0 -0
  150. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/signals.py +0 -0
  151. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/sso_backends.py +0 -0
  152. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/sso_urls.py +0 -0
  153. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/sso_views.py +0 -0
  154. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/static/django_forms_workflows/api/swagger-ui-bundle.js +0 -0
  155. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/static/django_forms_workflows/api/swagger-ui.css +0 -0
  156. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/static/django_forms_workflows/css/forms.css +0 -0
  157. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/static/django_forms_workflows/js/admin_notification_rule_stage_filter.js +0 -0
  158. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/static/django_forms_workflows/js/dfw-embed.js +0 -0
  159. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/static/django_forms_workflows/js/form-builder.js +0 -0
  160. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/static/django_forms_workflows/js/form-enhancements.js +0 -0
  161. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/static/django_forms_workflows/js/payment-stripe.js +0 -0
  162. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/static/django_forms_workflows/js/signature-pad.js +0 -0
  163. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/static/django_forms_workflows/js/workflow-builder.js +0 -0
  164. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/sync_api.py +0 -0
  165. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/sync_views.py +0 -0
  166. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/admin/django_forms_workflows/_sync_diff.html +0 -0
  167. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/admin/django_forms_workflows/diff_forms.html +0 -0
  168. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/admin/django_forms_workflows/form_builder.html +0 -0
  169. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/admin/django_forms_workflows/formdef_change_form.html +0 -0
  170. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/admin/django_forms_workflows/formdefinition/change_list.html +0 -0
  171. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/admin/django_forms_workflows/sync_import.html +0 -0
  172. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/admin/django_forms_workflows/sync_pull.html +0 -0
  173. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/admin/django_forms_workflows/sync_push.html +0 -0
  174. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/admin/django_forms_workflows/workflow_builder.html +0 -0
  175. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/_category_node.html +0 -0
  176. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/_field_value.html +0 -0
  177. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/_form_data_rows.html +0 -0
  178. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/analytics_dashboard.html +0 -0
  179. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/api/docs.html +0 -0
  180. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/approval_inbox.html +0 -0
  181. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/approve.html +0 -0
  182. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/base.html +0 -0
  183. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/batch_import_result.html +0 -0
  184. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/completed_approvals.html +0 -0
  185. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/discard_draft_confirm.html +0 -0
  186. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/embed_base.html +0 -0
  187. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/embed_success.html +0 -0
  188. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/form_categories.html +0 -0
  189. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/form_embed.html +0 -0
  190. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/form_list.html +0 -0
  191. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/form_submit.html +0 -0
  192. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/my_submissions.html +0 -0
  193. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/notification_preferences.html +0 -0
  194. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/payment_collect.html +0 -0
  195. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/payment_error.html +0 -0
  196. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/public_submission_confirmation.html +0 -0
  197. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/rate_limited.html +0 -0
  198. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/reassign_task.html +0 -0
  199. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/resubmit_confirm.html +0 -0
  200. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/sso/login.html +0 -0
  201. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/sub_workflow_detail.html +0 -0
  202. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/submission_bulk_pdf.html +0 -0
  203. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/submission_detail.html +0 -0
  204. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/submission_pdf.html +0 -0
  205. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/submission_success.html +0 -0
  206. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/django_forms_workflows/withdraw_confirm.html +0 -0
  207. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/emails/approval_notification.html +0 -0
  208. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/emails/approval_reminder.html +0 -0
  209. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/emails/approval_request.html +0 -0
  210. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/emails/email_styles.html +0 -0
  211. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/emails/escalation_notification.html +0 -0
  212. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/emails/notification_digest.html +0 -0
  213. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/emails/rejection_notification.html +0 -0
  214. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/emails/submission_notification.html +0 -0
  215. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templates/registration/login.html +0 -0
  216. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templatetags/__init__.py +0 -0
  217. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/templatetags/forms_workflows_tags.py +0 -0
  218. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/urls.py +0 -0
  219. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/utils.py +0 -0
  220. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/views.py +0 -0
  221. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/workflow_builder_views.py +0 -0
  222. {django_forms_workflows-0.76.0 → django_forms_workflows-0.76.1}/django_forms_workflows/workflow_engine.py +0 -0
@@ -7,6 +7,15 @@ 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
+
10
19
  ## [0.76.0] - 2026-06-09
11
20
 
12
21
  ### 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.1
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
@@ -370,36 +370,9 @@ def form_builder_save(request):
370
370
  try:
371
371
  data = json.loads(request.body)
372
372
 
373
- # Extract form definition data
374
373
  form_id = data.get("id")
375
374
  form_name = data.get("name", "").strip()
376
375
  form_slug = data.get("slug", "").strip()
377
- form_description = data.get("description", "").strip()
378
- form_instructions = data.get("instructions", "").strip()
379
- is_active = data.get("is_active", True)
380
- requires_login = data.get("requires_login", True)
381
- allow_save_draft = data.get("allow_save_draft", True)
382
- allow_withdrawal = data.get("allow_withdrawal", True)
383
- success_message = data.get("success_message", "").strip()
384
- success_redirect_url = data.get("success_redirect_url", "").strip()
385
- success_redirect_rules = data.get("success_redirect_rules") or None
386
- close_date = data.get("close_date") or None
387
- max_submissions = data.get("max_submissions") or None
388
- one_per_user = data.get("one_per_user", False)
389
- payment_enabled = data.get("payment_enabled", False)
390
- payment_provider = data.get("payment_provider", "")
391
- payment_amount_type = data.get("payment_amount_type", "fixed")
392
- payment_fixed_amount = data.get("payment_fixed_amount") or None
393
- payment_amount_field = data.get("payment_amount_field", "")
394
- payment_currency = data.get("payment_currency", "usd")
395
- payment_description_template = data.get("payment_description_template", "")
396
- enable_captcha = data.get("enable_captcha", False)
397
- embed_enabled = data.get("embed_enabled", False)
398
- enable_multi_step = data.get("enable_multi_step", False)
399
- form_steps = data.get("form_steps", [])
400
- enable_auto_save = data.get("enable_auto_save", True)
401
- auto_save_interval = data.get("auto_save_interval", 30)
402
- fields_data = data.get("fields", [])
403
376
 
404
377
  # Validate required fields
405
378
  if not form_name:
@@ -411,165 +384,15 @@ def form_builder_save(request):
411
384
  {"success": False, "error": "Form slug is required"}, status=400
412
385
  )
413
386
 
387
+ form_definition = (
388
+ get_object_or_404(FormDefinition, id=form_id) if form_id else None
389
+ )
390
+
414
391
  # Use transaction to ensure atomicity
415
392
  with transaction.atomic():
416
- # Create or update form definition
417
- if form_id:
418
- form_definition = get_object_or_404(FormDefinition, id=form_id)
419
- form_definition.name = form_name
420
- form_definition.slug = form_slug
421
- form_definition.description = form_description
422
- form_definition.instructions = form_instructions
423
- form_definition.is_active = is_active
424
- form_definition.requires_login = requires_login
425
- form_definition.allow_save_draft = allow_save_draft
426
- form_definition.allow_withdrawal = allow_withdrawal
427
- form_definition.success_message = success_message
428
- form_definition.success_redirect_url = success_redirect_url
429
- form_definition.success_redirect_rules = success_redirect_rules
430
- form_definition.close_date = close_date
431
- form_definition.max_submissions = max_submissions
432
- form_definition.one_per_user = one_per_user
433
- form_definition.payment_enabled = payment_enabled
434
- form_definition.payment_provider = payment_provider
435
- form_definition.payment_amount_type = payment_amount_type
436
- form_definition.payment_fixed_amount = payment_fixed_amount
437
- form_definition.payment_amount_field = payment_amount_field
438
- form_definition.payment_currency = payment_currency
439
- form_definition.payment_description_template = (
440
- payment_description_template
441
- )
442
- form_definition.enable_captcha = enable_captcha
443
- form_definition.embed_enabled = embed_enabled
444
- form_definition.enable_multi_step = enable_multi_step
445
- form_definition.form_steps = form_steps
446
- form_definition.enable_auto_save = enable_auto_save
447
- form_definition.auto_save_interval = auto_save_interval
448
- form_definition.version += 1 # Increment version on edit
449
- form_definition.save()
450
- else:
451
- form_definition = FormDefinition.objects.create(
452
- name=form_name,
453
- slug=form_slug,
454
- description=form_description,
455
- instructions=form_instructions,
456
- is_active=is_active,
457
- requires_login=requires_login,
458
- allow_save_draft=allow_save_draft,
459
- allow_withdrawal=allow_withdrawal,
460
- success_message=success_message,
461
- success_redirect_url=success_redirect_url,
462
- success_redirect_rules=success_redirect_rules,
463
- close_date=close_date,
464
- max_submissions=max_submissions,
465
- one_per_user=one_per_user,
466
- payment_enabled=payment_enabled,
467
- payment_provider=payment_provider,
468
- payment_amount_type=payment_amount_type,
469
- payment_fixed_amount=payment_fixed_amount,
470
- payment_amount_field=payment_amount_field,
471
- payment_currency=payment_currency,
472
- payment_description_template=payment_description_template,
473
- enable_captcha=enable_captcha,
474
- embed_enabled=embed_enabled,
475
- enable_multi_step=enable_multi_step,
476
- form_steps=form_steps,
477
- enable_auto_save=enable_auto_save,
478
- auto_save_interval=auto_save_interval,
479
- created_by=request.user,
480
- )
481
-
482
- # Track existing field IDs to determine which to delete
483
- existing_field_ids = set(
484
- form_definition.fields.values_list("id", flat=True)
393
+ form_definition, field_id_mapping = save_form_definition_from_builder_data(
394
+ data, request.user, form_definition=form_definition
485
395
  )
486
- updated_field_ids = set()
487
- field_id_mapping = {} # Map old IDs to new IDs for frontend update
488
-
489
- # Create or update fields
490
- for field_data in fields_data:
491
- field_id = field_data.get("id")
492
- old_id = field_id # Store original ID for mapping
493
-
494
- # Extract field properties
495
- field_props = {
496
- "form_definition": form_definition,
497
- "order": field_data.get("order", 0),
498
- "field_label": field_data.get("field_label", ""),
499
- "field_name": field_data.get("field_name", ""),
500
- "field_type": field_data.get("field_type", "text"),
501
- "required": field_data.get("required", False),
502
- "help_text": field_data.get("help_text", ""),
503
- "show_help_text_in_detail": field_data.get(
504
- "show_help_text_in_detail", False
505
- ),
506
- "placeholder": field_data.get("placeholder", ""),
507
- "width": field_data.get("width", "full"),
508
- "css_class": field_data.get("css_class", ""),
509
- "choices": field_data.get("choices", ""),
510
- "default_value": field_data.get("default_value", ""),
511
- "prefill_source_config_id": field_data.get("prefill_source_id"),
512
- "shared_option_list_id": field_data.get("shared_option_list_id")
513
- or None,
514
- }
515
-
516
- # Add validation properties
517
- validation = field_data.get("validation", {})
518
- field_props.update(
519
- {
520
- "min_value": validation.get("min_value"),
521
- "max_value": validation.get("max_value"),
522
- "min_length": validation.get("min_length"),
523
- "max_length": validation.get("max_length"),
524
- "regex_validation": validation.get("regex_validation", ""),
525
- "regex_error_message": validation.get(
526
- "regex_error_message", ""
527
- ),
528
- }
529
- )
530
-
531
- # Add conditional properties
532
- conditional = field_data.get("conditional", {})
533
- conditional_rules = conditional.get(
534
- "conditional_rules",
535
- field_data.get("conditional_rules"),
536
- )
537
-
538
- # Add client-side enhancement properties
539
- field_props.update(
540
- {
541
- "conditional_rules": conditional_rules,
542
- "validation_rules": field_data.get("validation_rules"),
543
- "field_dependencies": field_data.get("field_dependencies"),
544
- "step_number": field_data.get("step_number"),
545
- }
546
- )
547
-
548
- # Add workflow stage FK (for staged approval workflows)
549
- wf_stage_id = field_data.get("workflow_stage_id")
550
- if wf_stage_id is not None:
551
- field_props["workflow_stage_id"] = (
552
- int(wf_stage_id) if wf_stage_id else None
553
- )
554
- else:
555
- field_props["workflow_stage_id"] = None
556
-
557
- # Create or update field
558
- if field_id and isinstance(field_id, int):
559
- # Update existing field
560
- FormField.objects.filter(id=field_id).update(**field_props)
561
- updated_field_ids.add(field_id)
562
- field_id_mapping[str(old_id)] = field_id
563
- else:
564
- # Create new field
565
- new_field = FormField.objects.create(**field_props)
566
- updated_field_ids.add(new_field.id)
567
- field_id_mapping[str(old_id)] = new_field.id
568
-
569
- # Delete fields that were removed
570
- fields_to_delete = existing_field_ids - updated_field_ids
571
- if fields_to_delete:
572
- FormField.objects.filter(id__in=fields_to_delete).delete()
573
396
 
574
397
  return JsonResponse(
575
398
  {
@@ -591,6 +414,202 @@ def form_builder_save(request):
591
414
  )
592
415
 
593
416
 
417
+ def save_form_definition_from_builder_data(data, user, form_definition=None):
418
+ """
419
+ Create or update a FormDefinition and its FormFields from form builder
420
+ JSON data (see form_builder_save).
421
+
422
+ If `form_definition` is given, updates it in place (its version counter
423
+ is incremented); otherwise creates a new one with created_by=user.
424
+ Assumes data["name"]/data["slug"] have already been validated non-empty
425
+ by the caller.
426
+
427
+ Returns (form_definition, field_id_mapping), where field_id_mapping maps
428
+ each submitted field's original "id" (as sent by the client — an int
429
+ for an existing field, otherwise its client-side placeholder id) to the
430
+ resulting integer pk, as a str-keyed dict, so the frontend can
431
+ reconcile IDs for newly-created fields.
432
+ """
433
+ form_name = data.get("name", "").strip()
434
+ form_slug = data.get("slug", "").strip()
435
+ form_description = data.get("description", "").strip()
436
+ form_instructions = data.get("instructions", "").strip()
437
+ is_active = data.get("is_active", True)
438
+ requires_login = data.get("requires_login", True)
439
+ allow_save_draft = data.get("allow_save_draft", True)
440
+ allow_withdrawal = data.get("allow_withdrawal", True)
441
+ success_message = data.get("success_message", "").strip()
442
+ success_redirect_url = data.get("success_redirect_url", "").strip()
443
+ success_redirect_rules = data.get("success_redirect_rules") or None
444
+ close_date = data.get("close_date") or None
445
+ max_submissions = data.get("max_submissions") or None
446
+ one_per_user = data.get("one_per_user", False)
447
+ payment_enabled = data.get("payment_enabled", False)
448
+ payment_provider = data.get("payment_provider", "")
449
+ payment_amount_type = data.get("payment_amount_type", "fixed")
450
+ payment_fixed_amount = data.get("payment_fixed_amount") or None
451
+ payment_amount_field = data.get("payment_amount_field", "")
452
+ payment_currency = data.get("payment_currency", "usd")
453
+ payment_description_template = data.get("payment_description_template", "")
454
+ enable_captcha = data.get("enable_captcha", False)
455
+ embed_enabled = data.get("embed_enabled", False)
456
+ enable_multi_step = data.get("enable_multi_step", False)
457
+ form_steps = data.get("form_steps", [])
458
+ enable_auto_save = data.get("enable_auto_save", True)
459
+ auto_save_interval = data.get("auto_save_interval", 30)
460
+ fields_data = data.get("fields", [])
461
+
462
+ # Create or update form definition
463
+ if form_definition is not None:
464
+ form_definition.name = form_name
465
+ form_definition.slug = form_slug
466
+ form_definition.description = form_description
467
+ form_definition.instructions = form_instructions
468
+ form_definition.is_active = is_active
469
+ form_definition.requires_login = requires_login
470
+ form_definition.allow_save_draft = allow_save_draft
471
+ form_definition.allow_withdrawal = allow_withdrawal
472
+ form_definition.success_message = success_message
473
+ form_definition.success_redirect_url = success_redirect_url
474
+ form_definition.success_redirect_rules = success_redirect_rules
475
+ form_definition.close_date = close_date
476
+ form_definition.max_submissions = max_submissions
477
+ form_definition.one_per_user = one_per_user
478
+ form_definition.payment_enabled = payment_enabled
479
+ form_definition.payment_provider = payment_provider
480
+ form_definition.payment_amount_type = payment_amount_type
481
+ form_definition.payment_fixed_amount = payment_fixed_amount
482
+ form_definition.payment_amount_field = payment_amount_field
483
+ form_definition.payment_currency = payment_currency
484
+ form_definition.payment_description_template = payment_description_template
485
+ form_definition.enable_captcha = enable_captcha
486
+ form_definition.embed_enabled = embed_enabled
487
+ form_definition.enable_multi_step = enable_multi_step
488
+ form_definition.form_steps = form_steps
489
+ form_definition.enable_auto_save = enable_auto_save
490
+ form_definition.auto_save_interval = auto_save_interval
491
+ form_definition.version += 1 # Increment version on edit
492
+ form_definition.save()
493
+ else:
494
+ form_definition = FormDefinition.objects.create(
495
+ name=form_name,
496
+ slug=form_slug,
497
+ description=form_description,
498
+ instructions=form_instructions,
499
+ is_active=is_active,
500
+ requires_login=requires_login,
501
+ allow_save_draft=allow_save_draft,
502
+ allow_withdrawal=allow_withdrawal,
503
+ success_message=success_message,
504
+ success_redirect_url=success_redirect_url,
505
+ success_redirect_rules=success_redirect_rules,
506
+ close_date=close_date,
507
+ max_submissions=max_submissions,
508
+ one_per_user=one_per_user,
509
+ payment_enabled=payment_enabled,
510
+ payment_provider=payment_provider,
511
+ payment_amount_type=payment_amount_type,
512
+ payment_fixed_amount=payment_fixed_amount,
513
+ payment_amount_field=payment_amount_field,
514
+ payment_currency=payment_currency,
515
+ payment_description_template=payment_description_template,
516
+ enable_captcha=enable_captcha,
517
+ embed_enabled=embed_enabled,
518
+ enable_multi_step=enable_multi_step,
519
+ form_steps=form_steps,
520
+ enable_auto_save=enable_auto_save,
521
+ auto_save_interval=auto_save_interval,
522
+ created_by=user,
523
+ )
524
+
525
+ # Track existing field IDs to determine which to delete
526
+ existing_field_ids = set(form_definition.fields.values_list("id", flat=True))
527
+ updated_field_ids = set()
528
+ field_id_mapping = {} # Map old IDs to new IDs for frontend update
529
+
530
+ # Create or update fields
531
+ for field_data in fields_data:
532
+ field_id = field_data.get("id")
533
+ old_id = field_id # Store original ID for mapping
534
+
535
+ # Extract field properties
536
+ field_props = {
537
+ "form_definition": form_definition,
538
+ "order": field_data.get("order", 0),
539
+ "field_label": field_data.get("field_label", ""),
540
+ "field_name": field_data.get("field_name", ""),
541
+ "field_type": field_data.get("field_type", "text"),
542
+ "required": field_data.get("required", False),
543
+ "help_text": field_data.get("help_text", ""),
544
+ "show_help_text_in_detail": field_data.get(
545
+ "show_help_text_in_detail", False
546
+ ),
547
+ "placeholder": field_data.get("placeholder", ""),
548
+ "width": field_data.get("width", "full"),
549
+ "css_class": field_data.get("css_class", ""),
550
+ "choices": field_data.get("choices", ""),
551
+ "default_value": field_data.get("default_value", ""),
552
+ "prefill_source_config_id": field_data.get("prefill_source_id"),
553
+ "shared_option_list_id": field_data.get("shared_option_list_id") or None,
554
+ }
555
+
556
+ # Add validation properties
557
+ validation = field_data.get("validation", {})
558
+ field_props.update(
559
+ {
560
+ "min_value": validation.get("min_value"),
561
+ "max_value": validation.get("max_value"),
562
+ "min_length": validation.get("min_length"),
563
+ "max_length": validation.get("max_length"),
564
+ "regex_validation": validation.get("regex_validation", ""),
565
+ "regex_error_message": validation.get("regex_error_message", ""),
566
+ }
567
+ )
568
+
569
+ # Add conditional properties
570
+ conditional = field_data.get("conditional", {})
571
+ conditional_rules = conditional.get(
572
+ "conditional_rules",
573
+ field_data.get("conditional_rules"),
574
+ )
575
+
576
+ # Add client-side enhancement properties
577
+ field_props.update(
578
+ {
579
+ "conditional_rules": conditional_rules,
580
+ "validation_rules": field_data.get("validation_rules"),
581
+ "field_dependencies": field_data.get("field_dependencies"),
582
+ "step_number": field_data.get("step_number"),
583
+ }
584
+ )
585
+
586
+ # Add workflow stage FK (for staged approval workflows)
587
+ wf_stage_id = field_data.get("workflow_stage_id")
588
+ if wf_stage_id is not None:
589
+ field_props["workflow_stage_id"] = int(wf_stage_id) if wf_stage_id else None
590
+ else:
591
+ field_props["workflow_stage_id"] = None
592
+
593
+ # Create or update field
594
+ if field_id and isinstance(field_id, int):
595
+ # Update existing field
596
+ FormField.objects.filter(id=field_id).update(**field_props)
597
+ updated_field_ids.add(field_id)
598
+ field_id_mapping[str(old_id)] = field_id
599
+ else:
600
+ # Create new field
601
+ new_field = FormField.objects.create(**field_props)
602
+ updated_field_ids.add(new_field.id)
603
+ field_id_mapping[str(old_id)] = new_field.id
604
+
605
+ # Delete fields that were removed
606
+ fields_to_delete = existing_field_ids - updated_field_ids
607
+ if fields_to_delete:
608
+ FormField.objects.filter(id__in=fields_to_delete).delete()
609
+
610
+ return form_definition, field_id_mapping
611
+
612
+
594
613
  @staff_member_required
595
614
  @require_POST
596
615
  def form_builder_preview(request):
@@ -1108,10 +1108,22 @@ def _dispatch_conclusion_digest(
1108
1108
  # ---------------------------------------------------------------------------
1109
1109
 
1110
1110
 
1111
- def _get_form_field_email(form_data: dict, email_field: str) -> str | None:
1112
- """Extract and validate an email address from form_data by field slug."""
1113
- value = str(form_data.get(email_field, "")).strip()
1114
- return value if value and "@" in value else None
1111
+ def _get_form_field_emails(form_data: dict, email_field: str) -> list[str]:
1112
+ """Extract and validate email addresses from form_data by field slug.
1113
+
1114
+ A multi-select field (e.g. a picklist of faculty) stores several
1115
+ addresses in one value — as a comma-separated string, or as a list —
1116
+ so the value is always split before validation. Returning them
1117
+ separately keeps each address a single recipient; handing the raw
1118
+ ``"a@x.edu,b@x.edu"`` string to the email backend is rejected with
1119
+ "must be a single address".
1120
+ """
1121
+ raw = form_data.get(email_field, "")
1122
+ if isinstance(raw, list | tuple):
1123
+ raw = ",".join(str(part) for part in raw)
1124
+ return [
1125
+ addr for addr in (part.strip() for part in str(raw).split(",")) if "@" in addr
1126
+ ]
1115
1127
 
1116
1128
 
1117
1129
  def _collect_notification_recipients(
@@ -1142,9 +1154,10 @@ def _collect_notification_recipients(
1142
1154
  if getattr(notif, "notify_submitter", False) and submission is not None:
1143
1155
  _add(getattr(getattr(submission, "submitter", None), "email", None))
1144
1156
 
1145
- # 2. Email field (dynamic from form_data)
1157
+ # 2. Email field (dynamic from form_data; may hold several addresses)
1146
1158
  if notif.email_field:
1147
- _add(_get_form_field_email(form_data, notif.email_field))
1159
+ for addr in _get_form_field_emails(form_data, notif.email_field):
1160
+ _add(addr)
1148
1161
 
1149
1162
  # 3. Static emails
1150
1163
  for addr in (notif.static_emails or "").split(","):
@@ -1294,7 +1307,8 @@ def _collect_notification_cc(notif, form_data: dict) -> list[str]:
1294
1307
  cc.append(email)
1295
1308
 
1296
1309
  if getattr(notif, "cc_email_field", ""):
1297
- _add(_get_form_field_email(form_data, notif.cc_email_field))
1310
+ for addr in _get_form_field_emails(form_data, notif.cc_email_field):
1311
+ _add(addr)
1298
1312
 
1299
1313
  for addr in (getattr(notif, "cc_static_emails", "") or "").split(","):
1300
1314
  addr = addr.strip()
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "django-forms-workflows"
3
- version = "0.76.0"
3
+ version = "0.76.1"
4
4
  description = "Enterprise-grade, database-driven form builder with approval workflows and external data integration"
5
5
  license = "LGPL-3.0-only"
6
6
  readme = "README.md"