django-forms-workflows 0.30.2__tar.gz → 0.30.4__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 (144) hide show
  1. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/PKG-INFO +1 -1
  2. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/admin.py +9 -3
  3. django_forms_workflows-0.30.4/django_forms_workflows/migrations/0053_stageformfieldnotification_static_emails.py +42 -0
  4. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/models.py +36 -13
  5. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/tasks.py +39 -15
  6. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/pyproject.toml +1 -1
  7. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/CHANGELOG.md +0 -0
  8. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/LICENSE +0 -0
  9. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/README.md +0 -0
  10. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/__init__.py +0 -0
  11. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/apps.py +0 -0
  12. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/conditions.py +0 -0
  13. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/context_processors.py +0 -0
  14. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/data_sources/__init__.py +0 -0
  15. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/data_sources/base.py +0 -0
  16. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/data_sources/database_source.py +0 -0
  17. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/data_sources/ldap_source.py +0 -0
  18. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/data_sources/user_source.py +0 -0
  19. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/diff_views.py +0 -0
  20. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/email_backends/__init__.py +0 -0
  21. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/email_backends/gmail_api.py +0 -0
  22. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/form_builder_urls.py +0 -0
  23. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/form_builder_views.py +0 -0
  24. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/forms.py +0 -0
  25. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/handlers/__init__.py +0 -0
  26. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/handlers/api_handler.py +0 -0
  27. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/handlers/base.py +0 -0
  28. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/handlers/database_handler.py +0 -0
  29. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/handlers/email_handler.py +0 -0
  30. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/handlers/executor.py +0 -0
  31. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/handlers/file_handler.py +0 -0
  32. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/handlers/ldap_handler.py +0 -0
  33. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/ldap_backend.py +0 -0
  34. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/management/commands/create_default_templates.py +0 -0
  35. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/management/commands/pull_forms.py +0 -0
  36. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/management/commands/push_forms.py +0 -0
  37. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/management/commands/seed_farm_demo.py +0 -0
  38. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/management/commands/seed_pcn_form.py +0 -0
  39. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/management/commands/seed_prefill_sources.py +0 -0
  40. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/management/commands/sync_ldap_profiles.py +0 -0
  41. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/management/commands/test_db_connection.py +0 -0
  42. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0001_initial.py +0 -0
  43. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0002_prefillsource_alter_formfield_prefill_source_and_more.py +0 -0
  44. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0003_postsubmissionaction.py +0 -0
  45. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0004_formtemplate.py +0 -0
  46. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0005_advanced_conditional_logic.py +0 -0
  47. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0006_workflowdefinition_visual_workflow_data_and_more.py +0 -0
  48. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0007_add_userprofile_ldap_enhancements.py +0 -0
  49. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0008_add_file_workflow_models.py +0 -0
  50. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0009_add_formfield_readonly.py +0 -0
  51. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0010_add_prefillsource_template_fields.py +0 -0
  52. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0011_postsubmissionaction_email_body_template_and_more.py +0 -0
  53. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0012_add_approval_step_fields.py +0 -0
  54. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0013_add_database_query_key.py +0 -0
  55. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0014_add_form_category.py +0 -0
  56. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0015_add_workflowstage_staged_approvals.py +0 -0
  57. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0016_add_pdf_generation.py +0 -0
  58. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0017_add_notification_batching.py +0 -0
  59. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0018_add_form_category_parent.py +0 -0
  60. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0019_add_ldap_group_profile.py +0 -0
  61. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0020_add_bulk_export_field.py +0 -0
  62. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0021_alter_pendingnotification_scheduled_for_and_more.py +0 -0
  63. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0022_add_bulk_pdf_export.py +0 -0
  64. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0023_add_performance_indexes.py +0 -0
  65. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0024_add_allow_resubmit.py +0 -0
  66. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0025_workflowstage_approve_label.py +0 -0
  67. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0026_alter_formfield_width.py +0 -0
  68. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0027_formsubmission_form_data_gin.py +0 -0
  69. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0028_workflowstagegroupconfig.py +0 -0
  70. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0029_formfield_workflow_stage_fk_drop_groupconfig.py +0 -0
  71. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0030_formfield_phone_type.py +0 -0
  72. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0031_sub_workflows.py +0 -0
  73. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0032_approved_pending_detached_sub_workflow.py +0 -0
  74. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0033_add_reject_parent_to_subworkflowdefinition.py +0 -0
  75. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0034_add_notificationlog.py +0 -0
  76. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0035_migrate_prefill_source_strings.py +0 -0
  77. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0036_add_workflowstage_auto_created.py +0 -0
  78. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0037_migrate_flat_workflows_to_staged.py +0 -0
  79. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0038_remove_legacy_prefill_source_and_approval_step.py +0 -0
  80. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0039_update_help_text.py +0 -0
  81. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0040_migrate_deprecated_features.py +0 -0
  82. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0041_remove_deprecated_fields.py +0 -0
  83. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0042_add_sub_workflow_section_label.py +0 -0
  84. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0043_fix_form_data_decimal_encoding.py +0 -0
  85. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0044_remove_subworkflowinstance_label.py +0 -0
  86. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0045_stageapprovalgroup_workflow_name_label.py +0 -0
  87. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0046_workflow_definition_one_to_many.py +0 -0
  88. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0047_add_hide_approval_history.py +0 -0
  89. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0048_workflowdefinition_trigger_conditions_and_more.py +0 -0
  90. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0049_formfield_multifile_type.py +0 -0
  91. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0050_workflowdefinition_collapse_parallel_stages.py +0 -0
  92. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0051_formdefinition_is_listed.py +0 -0
  93. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/0052_dynamic_assignee_and_form_field_notifications.py +0 -0
  94. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/migrations/__init__.py +0 -0
  95. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/signals.py +0 -0
  96. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/sso_backends.py +0 -0
  97. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/sso_urls.py +0 -0
  98. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/sso_views.py +0 -0
  99. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/static/django_forms_workflows/js/form-builder.js +0 -0
  100. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/static/django_forms_workflows/js/form-enhancements.js +0 -0
  101. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/static/django_forms_workflows/js/workflow-builder.js +0 -0
  102. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/sync_api.py +0 -0
  103. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/sync_views.py +0 -0
  104. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/admin/django_forms_workflows/_sync_diff.html +0 -0
  105. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/admin/django_forms_workflows/diff_forms.html +0 -0
  106. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/admin/django_forms_workflows/form_builder.html +0 -0
  107. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/admin/django_forms_workflows/formdef_change_form.html +0 -0
  108. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/admin/django_forms_workflows/formdefinition/change_list.html +0 -0
  109. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/admin/django_forms_workflows/sync_import.html +0 -0
  110. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/admin/django_forms_workflows/sync_pull.html +0 -0
  111. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/admin/django_forms_workflows/sync_push.html +0 -0
  112. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/admin/django_forms_workflows/workflow_builder.html +0 -0
  113. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/django_forms_workflows/_category_node.html +0 -0
  114. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/django_forms_workflows/approval_inbox.html +0 -0
  115. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/django_forms_workflows/approve.html +0 -0
  116. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/django_forms_workflows/base.html +0 -0
  117. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/django_forms_workflows/completed_approvals.html +0 -0
  118. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/django_forms_workflows/form_categories.html +0 -0
  119. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/django_forms_workflows/form_list.html +0 -0
  120. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/django_forms_workflows/form_submit.html +0 -0
  121. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/django_forms_workflows/my_submissions.html +0 -0
  122. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/django_forms_workflows/resubmit_confirm.html +0 -0
  123. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/django_forms_workflows/sso/login.html +0 -0
  124. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/django_forms_workflows/sub_workflow_detail.html +0 -0
  125. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/django_forms_workflows/submission_bulk_pdf.html +0 -0
  126. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/django_forms_workflows/submission_detail.html +0 -0
  127. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/django_forms_workflows/submission_pdf.html +0 -0
  128. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/django_forms_workflows/withdraw_confirm.html +0 -0
  129. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/emails/approval_notification.html +0 -0
  130. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/emails/approval_reminder.html +0 -0
  131. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/emails/approval_request.html +0 -0
  132. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/emails/email_styles.html +0 -0
  133. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/emails/escalation_notification.html +0 -0
  134. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/emails/notification_digest.html +0 -0
  135. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/emails/rejection_notification.html +0 -0
  136. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/emails/submission_notification.html +0 -0
  137. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templates/registration/login.html +0 -0
  138. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templatetags/__init__.py +0 -0
  139. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/templatetags/forms_workflows_tags.py +0 -0
  140. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/urls.py +0 -0
  141. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/utils.py +0 -0
  142. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/views.py +0 -0
  143. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/workflow_builder_views.py +0 -0
  144. {django_forms_workflows-0.30.2 → django_forms_workflows-0.30.4}/django_forms_workflows/workflow_engine.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-forms-workflows
3
- Version: 0.30.2
3
+ Version: 0.30.4
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
@@ -44,7 +44,7 @@ from .models import (
44
44
 
45
45
 
46
46
  # Inline for form fields when editing a form definition
47
- class FormFieldInline(admin.StackedInline):
47
+ class FormFieldInline(nested_admin.NestedStackedInline):
48
48
  model = FormField
49
49
  extra = 0
50
50
  ordering = ("order",)
@@ -281,11 +281,17 @@ class StageApprovalGroupInline(nested_admin.NestedTabularInline):
281
281
 
282
282
 
283
283
  class StageFormFieldNotificationInline(nested_admin.NestedTabularInline):
284
- """Inline for configuring form-field conditional notifications on a stage."""
284
+ """Inline for configuring stage-level notifications (static and/or form-field driven)."""
285
285
 
286
286
  model = StageFormFieldNotification
287
287
  extra = 0
288
- fields = ("notification_type", "email_field", "subject_template", "conditions")
288
+ fields = (
289
+ "notification_type",
290
+ "email_field",
291
+ "static_emails",
292
+ "subject_template",
293
+ "conditions",
294
+ )
289
295
 
290
296
 
291
297
  class WorkflowStageInline(nested_admin.NestedStackedInline):
@@ -0,0 +1,42 @@
1
+ from django.db import migrations, models
2
+
3
+
4
+ class Migration(migrations.Migration):
5
+
6
+ dependencies = [
7
+ ("django_forms_workflows", "0052_dynamic_assignee_and_form_field_notifications"),
8
+ ]
9
+
10
+ operations = [
11
+ # Allow email_field to be blank — rules may now use only static_emails
12
+ migrations.AlterField(
13
+ model_name="stageformfieldnotification",
14
+ name="email_field",
15
+ field=models.CharField(
16
+ blank=True,
17
+ default="",
18
+ help_text=(
19
+ "Slug of the form field whose value is the recipient email address. "
20
+ "The value is read from the submission's form_data at send time. "
21
+ "Leave blank if you only need static recipients."
22
+ ),
23
+ max_length=200,
24
+ ),
25
+ ),
26
+ # Add static_emails for fixed addresses notified on every matching submission
27
+ migrations.AddField(
28
+ model_name="stageformfieldnotification",
29
+ name="static_emails",
30
+ field=models.CharField(
31
+ blank=True,
32
+ default="",
33
+ help_text=(
34
+ "Comma-separated list of fixed email addresses to notify. "
35
+ "These recipients are always included regardless of form data. "
36
+ "Can be combined with Email Field to notify both static and dynamic addresses."
37
+ ),
38
+ max_length=1000,
39
+ ),
40
+ ),
41
+ ]
42
+
@@ -838,18 +838,28 @@ class StageApprovalGroup(models.Model):
838
838
 
839
839
  class StageFormFieldNotification(models.Model):
840
840
  """
841
- A notification rule that sends an email to an address captured in the
842
- form submission data when a stage activates (or when the submission
843
- reaches a final state).
844
-
845
- This enables "conditional notifications" where — for example — the
846
- Instructor whose email was entered in the form receives a notification
847
- when the workflow reaches the Instructor Approval stage, even if that
848
- person is not a registered system user.
849
-
850
- Conditions (same format as WorkflowStage.trigger_conditions) are
851
- evaluated against the submission's form_data before sending; leave
852
- blank to always send.
841
+ A notification rule attached to a workflow stage that can send emails to:
842
+
843
+ * A **dynamic** recipient — an email address captured in a form field
844
+ (``email_field``). The value is resolved from the submission's
845
+ ``form_data`` at send time, so the recipient can vary per submission.
846
+ * One or more **static** recipients — a comma-separated list of fixed
847
+ email addresses (``static_emails``) that always receive the notification
848
+ regardless of what was submitted.
849
+
850
+ Both fields may be set simultaneously; the union of recipients is notified.
851
+ At least one of ``email_field`` or ``static_emails`` must be provided.
852
+
853
+ ``notification_type`` controls which email template is used and when the
854
+ notification fires:
855
+
856
+ * ``approval_request`` — fires when the stage activates.
857
+ * ``submission_received`` — fires when the form is first submitted.
858
+ * ``approval_notification`` — fires when the whole workflow is approved.
859
+ * ``rejection_notification``— fires when the submission is rejected.
860
+
861
+ Optional ``conditions`` (same JSON format as ``WorkflowStage.trigger_conditions``)
862
+ are evaluated against ``form_data`` before sending; leave blank to always send.
853
863
  """
854
864
 
855
865
  NOTIFICATION_TYPES = [
@@ -872,9 +882,22 @@ class StageFormFieldNotification(models.Model):
872
882
  )
873
883
  email_field = models.CharField(
874
884
  max_length=200,
885
+ blank=True,
886
+ default="",
875
887
  help_text=(
876
888
  "Slug of the form field whose value is the recipient email address. "
877
- "The value is read from the submission's form_data at send time."
889
+ "The value is read from the submission's form_data at send time. "
890
+ "Leave blank if you only need static recipients."
891
+ ),
892
+ )
893
+ static_emails = models.CharField(
894
+ max_length=1000,
895
+ blank=True,
896
+ default="",
897
+ help_text=(
898
+ "Comma-separated list of fixed email addresses to notify. "
899
+ "These recipients are always included regardless of form data. "
900
+ "Can be combined with Email Field to notify both static and dynamic addresses."
878
901
  ),
879
902
  )
880
903
  subject_template = models.CharField(
@@ -781,6 +781,25 @@ def _get_form_field_email(form_data: dict, email_field: str) -> str | None:
781
781
  return value if value and "@" in value else None
782
782
 
783
783
 
784
+ def _collect_notification_recipients(notif, form_data: dict) -> list[str]:
785
+ """Return the deduplicated list of recipient emails for a StageFormFieldNotification.
786
+
787
+ Combines:
788
+ - The dynamic email read from form_data via notif.email_field (if set and valid).
789
+ - All static emails from notif.static_emails (comma-separated, if set).
790
+ """
791
+ recipients: list[str] = []
792
+ if notif.email_field:
793
+ dynamic = _get_form_field_email(form_data, notif.email_field)
794
+ if dynamic:
795
+ recipients.append(dynamic)
796
+ for addr in (notif.static_emails or "").split(","):
797
+ addr = addr.strip()
798
+ if addr and "@" in addr and addr not in recipients:
799
+ recipients.append(addr)
800
+ return recipients
801
+
802
+
784
803
  def _build_form_field_notification_context(
785
804
  submission: FormSubmission,
786
805
  task: ApprovalTask | None = None,
@@ -834,24 +853,25 @@ def send_stage_form_field_notifications(task_id: int) -> None:
834
853
 
835
854
  if not evaluate_conditions(notif.conditions, form_data):
836
855
  logger.info(
837
- "Form-field notification %s skipped: conditions not met (submission %s)",
856
+ "Stage notification %s skipped: conditions not met (submission %s)",
838
857
  notif.id,
839
858
  submission.id,
840
859
  )
841
860
  continue
842
861
  except Exception:
843
862
  logger.warning(
844
- "Form-field notification %s: error evaluating conditions; skipping.",
863
+ "Stage notification %s: error evaluating conditions; skipping.",
845
864
  notif.id,
846
865
  )
847
866
  continue
848
867
 
849
- email = _get_form_field_email(form_data, notif.email_field)
850
- if not email:
868
+ recipients = _collect_notification_recipients(notif, form_data)
869
+ if not recipients:
851
870
  logger.info(
852
- "Form-field notification %s: field '%s' is empty or invalid; skipping.",
871
+ "Stage notification %s: no recipients resolved (email_field='%s', static_emails='%s'); skipping.",
853
872
  notif.id,
854
873
  notif.email_field,
874
+ notif.static_emails,
855
875
  )
856
876
  continue
857
877
 
@@ -870,7 +890,7 @@ def send_stage_form_field_notifications(task_id: int) -> None:
870
890
  }
871
891
  _send_html_email(
872
892
  subject,
873
- [email],
893
+ recipients,
874
894
  "emails/approval_request.html",
875
895
  context,
876
896
  notification_type="approval_request",
@@ -910,6 +930,12 @@ def send_submission_form_field_notifications(
910
930
  notification_type=notification_type,
911
931
  ).select_related("stage")
912
932
 
933
+ default_subjects = {
934
+ "submission_received": f"Submission Received: {form_name} (ID {submission.id})",
935
+ "approval_notification": f"Submission Approved: {form_name} (ID {submission.id})",
936
+ "rejection_notification": f"Submission Rejected: {form_name} (ID {submission.id})",
937
+ }
938
+
913
939
  for notif in notifications:
914
940
  if notif.conditions:
915
941
  try:
@@ -919,13 +945,13 @@ def send_submission_form_field_notifications(
919
945
  continue
920
946
  except Exception:
921
947
  logger.warning(
922
- "Form-field notification %s: error evaluating conditions; skipping.",
948
+ "Stage notification %s: error evaluating conditions; skipping.",
923
949
  notif.id,
924
950
  )
925
951
  continue
926
952
 
927
- email = _get_form_field_email(form_data, notif.email_field)
928
- if not email:
953
+ recipients = _collect_notification_recipients(notif, form_data)
954
+ if not recipients:
929
955
  continue
930
956
 
931
957
  subject = (
@@ -933,16 +959,14 @@ def send_submission_form_field_notifications(
933
959
  form_name=form_name, submission_id=submission.id
934
960
  )
935
961
  if notif.subject_template
936
- else {
937
- "submission_received": f"Submission Received: {form_name} (ID {submission.id})",
938
- "approval_notification": f"Submission Approved: {form_name} (ID {submission.id})",
939
- "rejection_notification": f"Submission Rejected: {form_name} (ID {submission.id})",
940
- }.get(notification_type, f"{form_name} (ID {submission.id})")
962
+ else default_subjects.get(
963
+ notification_type, f"{form_name} (ID {submission.id})"
964
+ )
941
965
  )
942
966
  context = {"submission": submission, "submission_url": submission_url}
943
967
  _send_html_email(
944
968
  subject,
945
- [email],
969
+ recipients,
946
970
  template,
947
971
  context,
948
972
  notification_type=notification_type,
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "django-forms-workflows"
3
- version = "0.30.2"
3
+ version = "0.30.4"
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"