django-forms-workflows 0.35.0__tar.gz → 0.35.2__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 (152) hide show
  1. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/PKG-INFO +1 -1
  2. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/admin.py +9 -5
  3. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/data_sources/database_source.py +3 -6
  4. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/data_sources/ldap_source.py +7 -9
  5. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/email_backends/gmail_api.py +1 -2
  6. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/form_builder_views.py +3 -1
  7. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/forms.py +27 -3
  8. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/handlers/api_handler.py +8 -9
  9. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/handlers/email_handler.py +4 -5
  10. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/handlers/file_handler.py +14 -19
  11. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/handlers/ldap_handler.py +4 -3
  12. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/ldap_backend.py +41 -26
  13. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/management/commands/sync_ldap_users.py +9 -3
  14. django_forms_workflows-0.35.2/django_forms_workflows/migrations/0059_add_multiselect_list_field_type.py +57 -0
  15. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/models.py +18 -14
  16. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/sso_backends.py +69 -11
  17. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/tasks.py +13 -7
  18. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/utils.py +5 -2
  19. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/views.py +30 -19
  20. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/workflow_builder_views.py +4 -5
  21. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/workflow_engine.py +77 -43
  22. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/pyproject.toml +1 -1
  23. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/CHANGELOG.md +0 -0
  24. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/LICENSE +0 -0
  25. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/README.md +0 -0
  26. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/__init__.py +0 -0
  27. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/apps.py +0 -0
  28. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/conditions.py +0 -0
  29. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/context_processors.py +0 -0
  30. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/data_sources/__init__.py +0 -0
  31. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/data_sources/base.py +0 -0
  32. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/data_sources/user_source.py +0 -0
  33. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/diff_views.py +0 -0
  34. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/email_backends/__init__.py +0 -0
  35. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/form_builder_urls.py +0 -0
  36. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/handlers/__init__.py +0 -0
  37. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/handlers/base.py +0 -0
  38. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/handlers/database_handler.py +0 -0
  39. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/handlers/executor.py +0 -0
  40. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/management/commands/create_default_templates.py +0 -0
  41. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/management/commands/pull_forms.py +0 -0
  42. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/management/commands/push_forms.py +0 -0
  43. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/management/commands/seed_farm_demo.py +0 -0
  44. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/management/commands/seed_pcn_form.py +0 -0
  45. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/management/commands/seed_prefill_sources.py +0 -0
  46. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/management/commands/sync_ldap_profiles.py +0 -0
  47. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/management/commands/test_db_connection.py +0 -0
  48. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0001_initial.py +0 -0
  49. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0002_prefillsource_alter_formfield_prefill_source_and_more.py +0 -0
  50. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0003_postsubmissionaction.py +0 -0
  51. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0004_formtemplate.py +0 -0
  52. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0005_advanced_conditional_logic.py +0 -0
  53. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0006_workflowdefinition_visual_workflow_data_and_more.py +0 -0
  54. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0007_add_userprofile_ldap_enhancements.py +0 -0
  55. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0008_add_file_workflow_models.py +0 -0
  56. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0009_add_formfield_readonly.py +0 -0
  57. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0010_add_prefillsource_template_fields.py +0 -0
  58. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0011_postsubmissionaction_email_body_template_and_more.py +0 -0
  59. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0012_add_approval_step_fields.py +0 -0
  60. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0013_add_database_query_key.py +0 -0
  61. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0014_add_form_category.py +0 -0
  62. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0015_add_workflowstage_staged_approvals.py +0 -0
  63. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0016_add_pdf_generation.py +0 -0
  64. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0017_add_notification_batching.py +0 -0
  65. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0018_add_form_category_parent.py +0 -0
  66. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0019_add_ldap_group_profile.py +0 -0
  67. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0020_add_bulk_export_field.py +0 -0
  68. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0021_alter_pendingnotification_scheduled_for_and_more.py +0 -0
  69. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0022_add_bulk_pdf_export.py +0 -0
  70. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0023_add_performance_indexes.py +0 -0
  71. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0024_add_allow_resubmit.py +0 -0
  72. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0025_workflowstage_approve_label.py +0 -0
  73. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0026_alter_formfield_width.py +0 -0
  74. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0027_formsubmission_form_data_gin.py +0 -0
  75. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0028_workflowstagegroupconfig.py +0 -0
  76. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0029_formfield_workflow_stage_fk_drop_groupconfig.py +0 -0
  77. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0030_formfield_phone_type.py +0 -0
  78. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0031_sub_workflows.py +0 -0
  79. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0032_approved_pending_detached_sub_workflow.py +0 -0
  80. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0033_add_reject_parent_to_subworkflowdefinition.py +0 -0
  81. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0034_add_notificationlog.py +0 -0
  82. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0035_migrate_prefill_source_strings.py +0 -0
  83. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0036_add_workflowstage_auto_created.py +0 -0
  84. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0037_migrate_flat_workflows_to_staged.py +0 -0
  85. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0038_remove_legacy_prefill_source_and_approval_step.py +0 -0
  86. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0039_update_help_text.py +0 -0
  87. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0040_migrate_deprecated_features.py +0 -0
  88. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0041_remove_deprecated_fields.py +0 -0
  89. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0042_add_sub_workflow_section_label.py +0 -0
  90. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0043_fix_form_data_decimal_encoding.py +0 -0
  91. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0044_remove_subworkflowinstance_label.py +0 -0
  92. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0045_stageapprovalgroup_workflow_name_label.py +0 -0
  93. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0046_workflow_definition_one_to_many.py +0 -0
  94. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0047_add_hide_approval_history.py +0 -0
  95. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0048_workflowdefinition_trigger_conditions_and_more.py +0 -0
  96. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0049_formfield_multifile_type.py +0 -0
  97. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0050_workflowdefinition_collapse_parallel_stages.py +0 -0
  98. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0051_formdefinition_is_listed.py +0 -0
  99. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0052_dynamic_assignee_and_form_field_notifications.py +0 -0
  100. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0053_stageformfieldnotification_static_emails.py +0 -0
  101. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0054_rename_approved_pending_to_pending_approval.py +0 -0
  102. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0055_add_send_back.py +0 -0
  103. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0056_calculated_formula_spreadsheet.py +0 -0
  104. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0057_add_currency_field_type.py +0 -0
  105. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/0058_rename_assignee_email_field_add_lookup_type.py +0 -0
  106. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/migrations/__init__.py +0 -0
  107. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/signals.py +0 -0
  108. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/sso_urls.py +0 -0
  109. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/sso_views.py +0 -0
  110. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/static/django_forms_workflows/js/form-builder.js +0 -0
  111. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/static/django_forms_workflows/js/form-enhancements.js +0 -0
  112. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/static/django_forms_workflows/js/workflow-builder.js +0 -0
  113. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/sync_api.py +0 -0
  114. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/sync_views.py +0 -0
  115. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/admin/django_forms_workflows/_sync_diff.html +0 -0
  116. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/admin/django_forms_workflows/diff_forms.html +0 -0
  117. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/admin/django_forms_workflows/form_builder.html +0 -0
  118. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/admin/django_forms_workflows/formdef_change_form.html +0 -0
  119. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/admin/django_forms_workflows/formdefinition/change_list.html +0 -0
  120. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/admin/django_forms_workflows/sync_import.html +0 -0
  121. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/admin/django_forms_workflows/sync_pull.html +0 -0
  122. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/admin/django_forms_workflows/sync_push.html +0 -0
  123. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/admin/django_forms_workflows/workflow_builder.html +0 -0
  124. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/django_forms_workflows/_category_node.html +0 -0
  125. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/django_forms_workflows/approval_inbox.html +0 -0
  126. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/django_forms_workflows/approve.html +0 -0
  127. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/django_forms_workflows/base.html +0 -0
  128. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/django_forms_workflows/completed_approvals.html +0 -0
  129. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/django_forms_workflows/form_categories.html +0 -0
  130. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/django_forms_workflows/form_list.html +0 -0
  131. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/django_forms_workflows/form_submit.html +0 -0
  132. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/django_forms_workflows/my_submissions.html +0 -0
  133. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/django_forms_workflows/reassign_task.html +0 -0
  134. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/django_forms_workflows/resubmit_confirm.html +0 -0
  135. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/django_forms_workflows/sso/login.html +0 -0
  136. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/django_forms_workflows/sub_workflow_detail.html +0 -0
  137. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/django_forms_workflows/submission_bulk_pdf.html +0 -0
  138. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/django_forms_workflows/submission_detail.html +0 -0
  139. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/django_forms_workflows/submission_pdf.html +0 -0
  140. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/django_forms_workflows/withdraw_confirm.html +0 -0
  141. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/emails/approval_notification.html +0 -0
  142. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/emails/approval_reminder.html +0 -0
  143. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/emails/approval_request.html +0 -0
  144. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/emails/email_styles.html +0 -0
  145. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/emails/escalation_notification.html +0 -0
  146. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/emails/notification_digest.html +0 -0
  147. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/emails/rejection_notification.html +0 -0
  148. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/emails/submission_notification.html +0 -0
  149. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templates/registration/login.html +0 -0
  150. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templatetags/__init__.py +0 -0
  151. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/templatetags/forms_workflows_tags.py +0 -0
  152. {django_forms_workflows-0.35.0 → django_forms_workflows-0.35.2}/django_forms_workflows/urls.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-forms-workflows
3
- Version: 0.35.0
3
+ Version: 0.35.2
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
@@ -6,6 +6,7 @@ configure approval workflows, and review submissions and audit logs.
6
6
  """
7
7
 
8
8
  import json
9
+ import logging
9
10
 
10
11
  import nested_admin
11
12
  from django.contrib import admin
@@ -42,6 +43,8 @@ from .models import (
42
43
  WorkflowStage,
43
44
  )
44
45
 
46
+ logger = logging.getLogger(__name__)
47
+
45
48
 
46
49
  # Inline for form fields when editing a form definition
47
50
  class FormFieldInline(nested_admin.NestedStackedInline):
@@ -655,7 +658,9 @@ class FormDefinitionAdmin(nested_admin.NestedModelAdmin):
655
658
  data_prefix=swc.data_prefix,
656
659
  )
657
660
  except SubWorkflowDefinition.DoesNotExist:
658
- pass
661
+ logger.debug(
662
+ "SubWorkflowDefinition not found during clone; skipping component"
663
+ )
659
664
 
660
665
  cloned_count += 1
661
666
  except Exception as e:
@@ -679,7 +684,7 @@ class FormDefinitionAdmin(nested_admin.NestedModelAdmin):
679
684
  self.message_user(
680
685
  request, "Select at least 2 forms to diff.", level="error"
681
686
  )
682
- return
687
+ return None
683
688
  pks = ",".join(str(pk) for pk in queryset.values_list("pk", flat=True))
684
689
  from django.urls import reverse
685
690
 
@@ -1944,10 +1949,9 @@ class ManagedFileAdmin(admin.ModelAdmin):
1944
1949
  """Display file size in human-readable format."""
1945
1950
  if obj.file_size < 1024:
1946
1951
  return f"{obj.file_size} B"
1947
- elif obj.file_size < 1024 * 1024:
1952
+ if obj.file_size < 1024 * 1024:
1948
1953
  return f"{obj.file_size / 1024:.1f} KB"
1949
- else:
1950
- return f"{obj.file_size / (1024 * 1024):.1f} MB"
1954
+ return f"{obj.file_size / (1024 * 1024):.1f} MB"
1951
1955
 
1952
1956
  file_size_display.short_description = "Size"
1953
1957
 
@@ -105,7 +105,7 @@ class DatabaseDataSource(DataSource):
105
105
  return None
106
106
 
107
107
  # Query the database
108
- value = self._query_database(
108
+ return self._query_database(
109
109
  database_alias=database_alias,
110
110
  schema=schema,
111
111
  table=table,
@@ -114,8 +114,6 @@ class DatabaseDataSource(DataSource):
114
114
  lookup_field=lookup_field,
115
115
  )
116
116
 
117
- return value
118
-
119
117
  except Exception as e:
120
118
  logger.error(f"Error getting database value for {field_name}: {e}")
121
119
  return None
@@ -176,11 +174,10 @@ class DatabaseDataSource(DataSource):
176
174
 
177
175
  if len(parts) == 3:
178
176
  return parts[0], parts[1], parts[2]
179
- elif len(parts) == 2:
177
+ if len(parts) == 2:
180
178
  # Assume default schema
181
179
  return default_schema, parts[0], parts[1]
182
- else:
183
- return None, None, None
180
+ return None, None, None
184
181
 
185
182
  def _is_safe_identifier(self, identifier: str) -> bool:
186
183
  """
@@ -105,9 +105,9 @@ class LDAPDataSource(DataSource):
105
105
  Query LDAP directly for a user attribute.
106
106
 
107
107
  This is used for SSO users who don't have a cached ldap_user object.
108
+ TLS configuration is applied via configure_ldap_connection(), which
109
+ respects FORMS_WORKFLOWS_LDAP_ALLOW_SELF_SIGNED and LDAP_TLS_REQUIRE_CERT.
108
110
  """
109
- import os
110
-
111
111
  try:
112
112
  import ldap
113
113
  from ldap.filter import escape_filter_chars
@@ -140,14 +140,12 @@ class LDAPDataSource(DataSource):
140
140
  return None
141
141
 
142
142
  try:
143
+ from django_forms_workflows.ldap_backend import configure_ldap_connection
144
+
143
145
  conn = ldap.initialize(ldap_server)
144
- conn.set_option(ldap.OPT_REFERRALS, 0)
145
146
  conn.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3)
146
-
147
- # Configure TLS
148
- tls_require_cert = os.getenv("LDAP_TLS_REQUIRE_CERT", "demand").lower()
149
- if tls_require_cert == "never":
150
- conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
147
+ # Apply TLS + referral settings centrally (honours FORMS_WORKFLOWS_LDAP_ALLOW_SELF_SIGNED)
148
+ configure_ldap_connection(conn)
151
149
 
152
150
  if bind_dn and bind_password:
153
151
  conn.simple_bind_s(bind_dn, bind_password)
@@ -178,7 +176,7 @@ class LDAPDataSource(DataSource):
178
176
  try:
179
177
  conn.unbind_s()
180
178
  except Exception:
181
- pass
179
+ logger.debug("Could not unbind LDAP connection", exc_info=True)
182
180
 
183
181
  def _get_manager_email(self, ldap_user) -> str | None:
184
182
  """
@@ -78,8 +78,7 @@ def get_gmail_service(config):
78
78
  credentials = credentials.with_subject(delegated_user)
79
79
 
80
80
  # Build the Gmail service
81
- service = build("gmail", "v1", credentials=credentials, cache_discovery=False)
82
- return service
81
+ return build("gmail", "v1", credentials=credentials, cache_discovery=False)
83
82
 
84
83
 
85
84
  class GmailAPIBackend(BaseEmailBackend):
@@ -210,7 +210,9 @@ def form_builder_clone(request, form_id):
210
210
  data_prefix=swc.data_prefix,
211
211
  )
212
212
  except SubWorkflowDefinition.DoesNotExist:
213
- pass
213
+ logger.debug(
214
+ "SubWorkflowDefinition not found during clone; skipping component"
215
+ )
214
216
 
215
217
  return JsonResponse(
216
218
  {
@@ -395,6 +395,14 @@ class DynamicForm(forms.Form):
395
395
  choices=choices, widget=forms.CheckboxSelectMultiple, **field_args
396
396
  )
397
397
 
398
+ elif field_def.field_type == "multiselect_list":
399
+ choices = self._parse_choices(field_def.choices)
400
+ self.fields[field_def.field_name] = forms.MultipleChoiceField(
401
+ choices=choices,
402
+ widget=forms.SelectMultiple(attrs={"class": "form-select"}),
403
+ **field_args,
404
+ )
405
+
398
406
  elif field_def.field_type == "radio":
399
407
  choices = self._parse_choices(field_def.choices)
400
408
  self.fields[field_def.field_name] = forms.ChoiceField(
@@ -541,19 +549,19 @@ class DynamicForm(forms.Form):
541
549
  return source.get_value(self.user, field_name) or ""
542
550
 
543
551
  # Handle ldap.* sources
544
- elif prefill_source.startswith("ldap."):
552
+ if prefill_source.startswith("ldap."):
545
553
  source = LDAPDataSource()
546
554
  field_name = prefill_source.replace("ldap.", "")
547
555
  return source.get_value(self.user, field_name) or ""
548
556
 
549
557
  # Handle dbquery.* sources (code-defined complex queries)
550
- elif prefill_source.startswith("dbquery."):
558
+ if prefill_source.startswith("dbquery."):
551
559
  source = DatabaseDataSource()
552
560
  query_key = prefill_source.replace("dbquery.", "")
553
561
  return source.execute_custom_query(self.user, query_key) or ""
554
562
 
555
563
  # Handle db.* or {{ db.* }} sources
556
- elif prefill_source.startswith("db.") or prefill_source.startswith("{{"):
564
+ if prefill_source.startswith("db.") or prefill_source.startswith("{{"):
557
565
  source = DatabaseDataSource()
558
566
 
559
567
  # Build kwargs from prefill_config
@@ -1012,6 +1020,22 @@ class ApprovalStepForm(forms.Form):
1012
1020
  choices=choices, **field_args
1013
1021
  )
1014
1022
 
1023
+ elif field_def.field_type == "multiselect":
1024
+ choices = self._parse_choices(field_def.choices)
1025
+ self.fields[field_def.field_name] = forms.MultipleChoiceField(
1026
+ choices=choices,
1027
+ widget=forms.CheckboxSelectMultiple,
1028
+ **field_args,
1029
+ )
1030
+
1031
+ elif field_def.field_type == "multiselect_list":
1032
+ choices = self._parse_choices(field_def.choices)
1033
+ self.fields[field_def.field_name] = forms.MultipleChoiceField(
1034
+ choices=choices,
1035
+ widget=forms.SelectMultiple(attrs={"class": "form-select"}),
1036
+ **field_args,
1037
+ )
1038
+
1015
1039
  elif field_def.field_type == "radio":
1016
1040
  choices = self._parse_choices(field_def.choices)
1017
1041
  self.fields[field_def.field_name] = forms.ChoiceField(
@@ -178,15 +178,14 @@ class APICallHandler(BaseActionHandler):
178
178
  "response": response.text[:500], # First 500 chars
179
179
  },
180
180
  }
181
- else:
182
- return {
183
- "success": False,
184
- "message": f"API call failed: {method} {url} -> {response.status_code}",
185
- "data": {
186
- "status_code": response.status_code,
187
- "response": response.text[:500],
188
- },
189
- }
181
+ return {
182
+ "success": False,
183
+ "message": f"API call failed: {method} {url} -> {response.status_code}",
184
+ "data": {
185
+ "status_code": response.status_code,
186
+ "response": response.text[:500],
187
+ },
188
+ }
190
189
 
191
190
  except requests.exceptions.Timeout:
192
191
  return {"success": False, "message": f"API call timed out: {method} {url}"}
@@ -338,11 +338,10 @@ class EmailHandler(BaseActionHandler):
338
338
  "subject": subject,
339
339
  },
340
340
  }
341
- else:
342
- return {
343
- "success": False,
344
- "message": "Email send returned 0 (no emails sent)",
345
- }
341
+ return {
342
+ "success": False,
343
+ "message": "Email send returned 0 (no emails sent)",
344
+ }
346
345
 
347
346
  except Exception as e:
348
347
  return {
@@ -80,9 +80,7 @@ class FilePatternResolver:
80
80
  result = re.sub(r"\{[^}]+\}", "", result)
81
81
  # Clean up double slashes/underscores
82
82
  result = re.sub(r"//+", "/", result)
83
- result = re.sub(r"__+", "_", result)
84
-
85
- return result
83
+ return re.sub(r"__+", "_", result)
86
84
 
87
85
  def _get_employee_id(self):
88
86
  """Get employee ID from user profile."""
@@ -92,7 +90,9 @@ class FilePatternResolver:
92
90
  if hasattr(self.user, "profile"):
93
91
  return getattr(self.user.profile, "employee_id", "") or ""
94
92
  except Exception:
95
- pass
93
+ logger.debug(
94
+ "Could not retrieve employee_id from user profile", exc_info=True
95
+ )
96
96
  return ""
97
97
 
98
98
  def _get_field_name(self):
@@ -140,8 +140,7 @@ class FileOperationHandler:
140
140
 
141
141
  logger.info(f"Renamed file from {old_path} to {new_path}")
142
142
  return {"success": True, "message": f"Renamed to {new_filename}"}
143
- else:
144
- return {"success": False, "message": f"File not found: {old_path}"}
143
+ return {"success": False, "message": f"File not found: {old_path}"}
145
144
 
146
145
  except Exception as e:
147
146
  logger.error(f"Error renaming file: {e}", exc_info=True)
@@ -177,8 +176,7 @@ class FileOperationHandler:
177
176
 
178
177
  logger.info(f"Moved file from {old_path} to {new_path}")
179
178
  return {"success": True, "message": f"Moved to {new_path}"}
180
- else:
181
- return {"success": False, "message": f"File not found: {old_path}"}
179
+ return {"success": False, "message": f"File not found: {old_path}"}
182
180
 
183
181
  except Exception as e:
184
182
  logger.error(f"Error moving file: {e}", exc_info=True)
@@ -208,8 +206,7 @@ class FileOperationHandler:
208
206
 
209
207
  logger.info(f"Copied file from {old_path} to {new_path}")
210
208
  return {"success": True, "message": f"Copied to {new_path}"}
211
- else:
212
- return {"success": False, "message": f"File not found: {old_path}"}
209
+ return {"success": False, "message": f"File not found: {old_path}"}
213
210
 
214
211
  except Exception as e:
215
212
  logger.error(f"Error copying file: {e}", exc_info=True)
@@ -230,8 +227,7 @@ class FileOperationHandler:
230
227
 
231
228
  logger.info(f"Deleted file: {file_path}")
232
229
  return {"success": True, "message": f"Deleted {file_path}"}
233
- else:
234
- return {"success": False, "message": f"File not found: {file_path}"}
230
+ return {"success": False, "message": f"File not found: {file_path}"}
235
231
 
236
232
  except Exception as e:
237
233
  logger.error(f"Error deleting file: {e}", exc_info=True)
@@ -277,13 +273,12 @@ class WebhookHandler:
277
273
  "message": f"Webhook returned {response.status_code}",
278
274
  "data": {"status_code": response.status_code},
279
275
  }
280
- else:
281
- logger.warning(f"Webhook call failed: {url} ({response.status_code})")
282
- return {
283
- "success": False,
284
- "message": f"Webhook returned {response.status_code}",
285
- "data": {"status_code": response.status_code},
286
- }
276
+ logger.warning(f"Webhook call failed: {url} ({response.status_code})")
277
+ return {
278
+ "success": False,
279
+ "message": f"Webhook returned {response.status_code}",
280
+ "data": {"status_code": response.status_code},
281
+ }
287
282
 
288
283
  except requests.Timeout:
289
284
  logger.error(f"Webhook timeout: {self.hook.webhook_url}")
@@ -153,12 +153,13 @@ class LDAPUpdateHandler(BaseActionHandler):
153
153
  if value:
154
154
  placeholders[field] = value
155
155
  except Exception:
156
- pass
156
+ logger.debug(
157
+ "Could not load user profile fields for LDAP DN template", exc_info=True
158
+ )
157
159
 
158
160
  # Replace placeholders
159
161
  try:
160
- dn = template.format(**placeholders)
161
- return dn
162
+ return template.format(**placeholders)
162
163
  except KeyError as e:
163
164
  self.log_error(f"Missing placeholder in DN template: {e}")
164
165
  return None
@@ -21,37 +21,53 @@ logger = logging.getLogger(__name__)
21
21
 
22
22
  def configure_ldap_connection(conn):
23
23
  """
24
- Configure LDAP connection with TLS settings from environment variables.
24
+ Configure LDAP connection with TLS settings.
25
25
 
26
- This function applies TLS certificate verification settings based on the
27
- LDAP_TLS_REQUIRE_CERT environment variable.
26
+ Checks the ``FORMS_WORKFLOWS_LDAP_ALLOW_SELF_SIGNED`` Django setting first.
27
+ When ``True``, self-signed certificates are accepted (``OPT_X_TLS_ALLOW``).
28
+
29
+ Falls back to the ``LDAP_TLS_REQUIRE_CERT`` environment variable for
30
+ fine-grained control:
31
+ - ``'never'`` – never require/verify certificates
32
+ - ``'allow'`` – allow self-signed / unverifiable certificates
33
+ - ``'try'`` – try to verify; proceed even if verification fails
34
+ - ``'demand'`` – require a valid certificate (default)
35
+
36
+ Django Settings:
37
+ FORMS_WORKFLOWS_LDAP_ALLOW_SELF_SIGNED (bool):
38
+ Set to ``True`` to accept self-signed LDAP/AD certificates.
39
+ Takes precedence over the ``LDAP_TLS_REQUIRE_CERT`` env var.
40
+ Default: ``False``.
28
41
 
29
42
  Args:
30
43
  conn: LDAP connection object
31
-
32
- Environment Variables:
33
- LDAP_TLS_REQUIRE_CERT: TLS certificate verification level
34
- - 'never': Don't require or verify certificates (ldap.OPT_X_TLS_NEVER)
35
- - 'allow': Allow connection without cert verification (ldap.OPT_X_TLS_ALLOW)
36
- - 'try': Try to verify but proceed if verification fails (ldap.OPT_X_TLS_TRY)
37
- - 'demand' or 'hard': Require valid certificate (ldap.OPT_X_TLS_DEMAND)
38
- - Default: 'demand'
39
44
  """
40
- # Configure TLS settings based on environment variable
41
- tls_require_cert = os.getenv("LDAP_TLS_REQUIRE_CERT", "demand").lower()
45
+ # Check Django setting first — simple on/off for self-signed cert acceptance
46
+ allow_self_signed = getattr(
47
+ settings, "FORMS_WORKFLOWS_LDAP_ALLOW_SELF_SIGNED", False
48
+ )
42
49
 
43
- if tls_require_cert == "never":
44
- conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
45
- logger.debug("LDAP TLS certificate verification: NEVER")
46
- elif tls_require_cert == "allow":
50
+ if allow_self_signed:
47
51
  conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW)
48
- logger.debug("LDAP TLS certificate verification: ALLOW")
49
- elif tls_require_cert == "try":
50
- conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_TRY)
51
- logger.debug("LDAP TLS certificate verification: TRY")
52
- else: # 'demand' or 'hard' or any other value
53
- conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)
54
- logger.debug("LDAP TLS certificate verification: DEMAND")
52
+ logger.debug(
53
+ "LDAP TLS certificate verification: ALLOW (FORMS_WORKFLOWS_LDAP_ALLOW_SELF_SIGNED=True)"
54
+ )
55
+ else:
56
+ # Fall back to env-var-based configuration for fine-grained control
57
+ tls_require_cert = os.getenv("LDAP_TLS_REQUIRE_CERT", "demand").lower()
58
+
59
+ if tls_require_cert == "never":
60
+ conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
61
+ logger.debug("LDAP TLS certificate verification: NEVER")
62
+ elif tls_require_cert == "allow":
63
+ conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW)
64
+ logger.debug("LDAP TLS certificate verification: ALLOW")
65
+ elif tls_require_cert == "try":
66
+ conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_TRY)
67
+ logger.debug("LDAP TLS certificate verification: TRY")
68
+ else: # 'demand' or 'hard' or any other value
69
+ conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)
70
+ logger.debug("LDAP TLS certificate verification: DEMAND")
55
71
 
56
72
  # Set other standard options
57
73
  conn.set_option(ldap.OPT_REFERRALS, 0)
@@ -339,8 +355,7 @@ def get_user_manager(user):
339
355
  User = get_user_model()
340
356
 
341
357
  try:
342
- manager_user = User.objects.get(username=manager_username)
343
- return manager_user
358
+ return User.objects.get(username=manager_username)
344
359
  except User.DoesNotExist:
345
360
  logger.warning(
346
361
  f"Manager {manager_username} not found in Django database"
@@ -98,6 +98,7 @@ class Command(BaseCommand):
98
98
  self.stderr.write(self.style.ERROR(f"LDAP connection failed: {e}"))
99
99
  return
100
100
 
101
+ results = []
101
102
  try:
102
103
  conn.set_option(ldap.OPT_SIZELIMIT, max_results)
103
104
  results = conn.search_s(
@@ -108,9 +109,10 @@ class Command(BaseCommand):
108
109
  )
109
110
  except ldap.SIZELIMIT_EXCEEDED:
110
111
  self.stdout.write(
111
- self.style.WARNING(f"Hit LDAP size limit ({max_results})")
112
+ self.style.WARNING(
113
+ f"Hit LDAP size limit ({max_results}); results may be partial"
114
+ )
112
115
  )
113
- results = []
114
116
  except Exception as e:
115
117
  self.stderr.write(self.style.ERROR(f"LDAP search failed: {e}"))
116
118
  conn.unbind_s()
@@ -158,7 +160,11 @@ class Command(BaseCommand):
158
160
  defaults={"department": dept, "title": title},
159
161
  )
160
162
  except Exception:
161
- pass
163
+ logger.debug(
164
+ "UserProfile creation failed for '%s'; continuing",
165
+ username,
166
+ exc_info=True,
167
+ )
162
168
  created += 1
163
169
  except Exception as e:
164
170
  errors += 1
@@ -0,0 +1,57 @@
1
+ """
2
+ Add multiselect_list field type.
3
+
4
+ Adds "Multiple Select (List)" as an alternative to the existing
5
+ "Multiple Select (Checkboxes)" (multiselect) field type. The new type
6
+ renders using Django's SelectMultiple widget (<select multiple>) rather
7
+ than CheckboxSelectMultiple.
8
+
9
+ The label of the existing 'multiselect' choice is also updated from
10
+ "Multiple Select" to "Multiple Select (Checkboxes)" to make the
11
+ distinction clear in the admin UI.
12
+ """
13
+
14
+ from django.db import migrations, models
15
+
16
+
17
+ class Migration(migrations.Migration):
18
+
19
+ dependencies = [
20
+ ("django_forms_workflows", "0058_rename_assignee_email_field_add_lookup_type"),
21
+ ]
22
+
23
+ operations = [
24
+ migrations.AlterField(
25
+ model_name="formfield",
26
+ name="field_type",
27
+ field=models.CharField(
28
+ max_length=20,
29
+ choices=[
30
+ ("text", "Single Line Text"),
31
+ ("phone", "Phone Number"),
32
+ ("textarea", "Multi-line Text"),
33
+ ("number", "Whole Number"),
34
+ ("decimal", "Decimal Number"),
35
+ ("currency", "Currency ($)"),
36
+ ("date", "Date"),
37
+ ("datetime", "Date and Time"),
38
+ ("time", "Time"),
39
+ ("email", "Email Address"),
40
+ ("url", "Website URL"),
41
+ ("select", "Dropdown Select"),
42
+ ("multiselect", "Multiple Select (Checkboxes)"),
43
+ ("multiselect_list", "Multiple Select (List)"),
44
+ ("radio", "Radio Buttons"),
45
+ ("checkbox", "Single Checkbox"),
46
+ ("checkboxes", "Multiple Checkboxes"),
47
+ ("file", "File Upload"),
48
+ ("multifile", "Multi-File Upload"),
49
+ ("hidden", "Hidden Field"),
50
+ ("section", "Section Header (not a field)"),
51
+ ("calculated", "Calculated / Formula"),
52
+ ("spreadsheet", "Spreadsheet Upload (CSV / Excel)"),
53
+ ],
54
+ ),
55
+ ),
56
+ ]
57
+
@@ -373,7 +373,7 @@ class PrefillSource(models.Model):
373
373
  if self.db_template and self.db_columns:
374
374
  return f"{{{{ {self.db_schema}.{self.db_table}.* }}}}"
375
375
  # Single column lookup
376
- elif self.db_column:
376
+ if self.db_column:
377
377
  return f"{{{{ {self.db_schema}.{self.db_table}.{self.db_column} }}}}"
378
378
  elif self.source_type == "ldap" and self.ldap_attribute:
379
379
  return f"ldap.{self.ldap_attribute}"
@@ -411,7 +411,8 @@ class FormField(models.Model):
411
411
  ("email", "Email Address"),
412
412
  ("url", "Website URL"),
413
413
  ("select", "Dropdown Select"),
414
- ("multiselect", "Multiple Select"),
414
+ ("multiselect", "Multiple Select (Checkboxes)"),
415
+ ("multiselect_list", "Multiple Select (List)"),
415
416
  ("radio", "Radio Buttons"),
416
417
  ("checkbox", "Single Checkbox"),
417
418
  ("checkboxes", "Multiple Checkboxes"),
@@ -825,9 +826,13 @@ class WorkflowStage(models.Model):
825
826
  "How to resolve the form field value to a system user. "
826
827
  "'Email' looks up by email address. "
827
828
  "'Username' looks up by sAMAccountName/username. "
828
- "'Full name' matches against first + last name. "
829
- "'LDAP lookup' searches Active Directory by display name and "
830
- "auto-provisions the Django user if not yet in the system."
829
+ "'Full name' matches against Django User first_name + last_name (iexact), "
830
+ "then falls back to an LDAP search — NOTE: for Google SSO-only sites, "
831
+ "first_name/last_name are only populated if Google SAML sends those "
832
+ "attributes or users were pre-provisioned via sync_ldap_users. "
833
+ "'LDAP lookup' searches Active Directory directly by display name and "
834
+ "auto-provisions the Django user if not yet in the system — recommended "
835
+ "for sites where SSO does not populate first/last name."
831
836
  ),
832
837
  )
833
838
  allow_send_back = models.BooleanField(
@@ -1264,8 +1269,7 @@ class PostSubmissionAction(models.Model):
1264
1269
  # Check if action is locked and has already executed
1265
1270
  if self.is_locked:
1266
1271
  # Check if this action has already executed successfully for this submission
1267
- from django_forms_workflows.models import ActionExecutionLog
1268
-
1272
+ # ActionExecutionLog is defined later in this module; safe to reference at runtime
1269
1273
  if ActionExecutionLog.objects.filter(
1270
1274
  action=self, submission=submission, success=True
1271
1275
  ).exists():
@@ -1277,13 +1281,13 @@ class PostSubmissionAction(models.Model):
1277
1281
 
1278
1282
  if self.condition_operator == "equals":
1279
1283
  return str(field_value) == self.condition_value
1280
- elif self.condition_operator == "not_equals":
1284
+ if self.condition_operator == "not_equals":
1281
1285
  return str(field_value) != self.condition_value
1282
- elif self.condition_operator == "contains":
1286
+ if self.condition_operator == "contains":
1283
1287
  return self.condition_value in str(field_value)
1284
- elif self.condition_operator == "not_contains":
1288
+ if self.condition_operator == "not_contains":
1285
1289
  return self.condition_value not in str(field_value)
1286
- elif self.condition_operator == "greater_than":
1290
+ if self.condition_operator == "greater_than":
1287
1291
  try:
1288
1292
  return float(field_value) > float(self.condition_value)
1289
1293
  except (ValueError, TypeError):
@@ -2268,11 +2272,11 @@ class FileWorkflowHook(models.Model):
2268
2272
 
2269
2273
  if self.condition_operator == "equals":
2270
2274
  return str(field_value) == self.condition_value
2271
- elif self.condition_operator == "not_equals":
2275
+ if self.condition_operator == "not_equals":
2272
2276
  return str(field_value) != self.condition_value
2273
- elif self.condition_operator == "contains":
2277
+ if self.condition_operator == "contains":
2274
2278
  return self.condition_value in str(field_value)
2275
- elif self.condition_operator == "greater_than":
2279
+ if self.condition_operator == "greater_than":
2276
2280
  try:
2277
2281
  return float(field_value) > float(self.condition_value)
2278
2282
  except (ValueError, TypeError):