django-forms-workflows 0.77.5__tar.gz → 0.78.0__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 (234) hide show
  1. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/PKG-INFO +12 -7
  2. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/README.md +5 -3
  3. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/admin.py +23 -0
  4. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/api_views.py +1 -1
  5. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/apps.py +1 -1
  6. django_forms_workflows-0.78.0/django_forms_workflows/checks.py +24 -0
  7. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/diff_views.py +2 -0
  8. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/form_builder_views.py +14 -0
  9. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/forms.py +6 -25
  10. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/models.py +3 -0
  11. django_forms_workflows-0.78.0/django_forms_workflows/pdf.py +53 -0
  12. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/reporting_views.py +44 -19
  13. django_forms_workflows-0.78.0/django_forms_workflows/security.py +108 -0
  14. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/sso_backends.py +30 -67
  15. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/sso_views.py +8 -75
  16. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/admin/django_forms_workflows/workflow_builder.html +2 -2
  17. django_forms_workflows-0.78.0/django_forms_workflows/uploads.py +104 -0
  18. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/utils.py +43 -31
  19. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/views.py +56 -108
  20. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/workflow_builder_views.py +17 -6
  21. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/pyproject.toml +11 -5
  22. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/CHANGELOG.md +0 -0
  23. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/LICENSE +0 -0
  24. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/__init__.py +0 -0
  25. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/api_urls.py +0 -0
  26. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/callback_registry.py +0 -0
  27. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/conditions.py +0 -0
  28. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/context_processors.py +0 -0
  29. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/data_sources/__init__.py +0 -0
  30. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/data_sources/base.py +0 -0
  31. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/data_sources/database_source.py +0 -0
  32. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/data_sources/ldap_source.py +0 -0
  33. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/data_sources/user_source.py +0 -0
  34. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/email_backends/__init__.py +0 -0
  35. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/email_backends/gmail_api.py +0 -0
  36. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/form_builder_urls.py +0 -0
  37. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/handlers/__init__.py +0 -0
  38. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/handlers/api_handler.py +0 -0
  39. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/handlers/base.py +0 -0
  40. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/handlers/database_handler.py +0 -0
  41. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/handlers/email_handler.py +0 -0
  42. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/handlers/executor.py +0 -0
  43. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/handlers/file_handler.py +0 -0
  44. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/handlers/ldap_handler.py +0 -0
  45. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/history.py +0 -0
  46. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/ldap_backend.py +0 -0
  47. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/management/commands/create_default_templates.py +0 -0
  48. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/management/commands/pull_forms.py +0 -0
  49. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/management/commands/push_forms.py +0 -0
  50. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/management/commands/seed_farm_demo.py +0 -0
  51. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/management/commands/seed_pcn_form.py +0 -0
  52. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/management/commands/seed_prefill_sources.py +0 -0
  53. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/management/commands/sync_ldap_profiles.py +0 -0
  54. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/management/commands/sync_ldap_users.py +0 -0
  55. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/management/commands/test_db_connection.py +0 -0
  56. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0001_initial.py +0 -0
  57. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0002_prefillsource_alter_formfield_prefill_source_and_more.py +0 -0
  58. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0003_postsubmissionaction.py +0 -0
  59. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0004_formtemplate.py +0 -0
  60. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0005_advanced_conditional_logic.py +0 -0
  61. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0006_workflowdefinition_visual_workflow_data_and_more.py +0 -0
  62. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0007_add_userprofile_ldap_enhancements.py +0 -0
  63. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0008_add_file_workflow_models.py +0 -0
  64. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0009_add_formfield_readonly.py +0 -0
  65. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0010_add_prefillsource_template_fields.py +0 -0
  66. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0011_postsubmissionaction_email_body_template_and_more.py +0 -0
  67. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0012_add_approval_step_fields.py +0 -0
  68. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0013_add_database_query_key.py +0 -0
  69. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0014_add_form_category.py +0 -0
  70. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0015_add_workflowstage_staged_approvals.py +0 -0
  71. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0016_add_pdf_generation.py +0 -0
  72. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0017_add_notification_batching.py +0 -0
  73. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0018_add_form_category_parent.py +0 -0
  74. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0019_add_ldap_group_profile.py +0 -0
  75. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0020_add_bulk_export_field.py +0 -0
  76. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0021_alter_pendingnotification_scheduled_for_and_more.py +0 -0
  77. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0022_add_bulk_pdf_export.py +0 -0
  78. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0023_add_performance_indexes.py +0 -0
  79. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0024_add_allow_resubmit.py +0 -0
  80. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0025_workflowstage_approve_label.py +0 -0
  81. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0026_alter_formfield_width.py +0 -0
  82. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0027_formsubmission_form_data_gin.py +0 -0
  83. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0028_workflowstagegroupconfig.py +0 -0
  84. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0029_formfield_workflow_stage_fk_drop_groupconfig.py +0 -0
  85. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0030_formfield_phone_type.py +0 -0
  86. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0031_sub_workflows.py +0 -0
  87. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0032_approved_pending_detached_sub_workflow.py +0 -0
  88. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0033_add_reject_parent_to_subworkflowdefinition.py +0 -0
  89. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0034_add_notificationlog.py +0 -0
  90. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0035_migrate_prefill_source_strings.py +0 -0
  91. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0036_add_workflowstage_auto_created.py +0 -0
  92. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0037_migrate_flat_workflows_to_staged.py +0 -0
  93. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0038_remove_legacy_prefill_source_and_approval_step.py +0 -0
  94. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0039_update_help_text.py +0 -0
  95. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0040_migrate_deprecated_features.py +0 -0
  96. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0041_remove_deprecated_fields.py +0 -0
  97. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0042_add_sub_workflow_section_label.py +0 -0
  98. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0043_fix_form_data_decimal_encoding.py +0 -0
  99. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0044_remove_subworkflowinstance_label.py +0 -0
  100. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0045_stageapprovalgroup_workflow_name_label.py +0 -0
  101. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0046_workflow_definition_one_to_many.py +0 -0
  102. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0047_add_hide_approval_history.py +0 -0
  103. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0048_workflowdefinition_trigger_conditions_and_more.py +0 -0
  104. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0049_formfield_multifile_type.py +0 -0
  105. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0050_workflowdefinition_collapse_parallel_stages.py +0 -0
  106. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0051_formdefinition_is_listed.py +0 -0
  107. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0052_dynamic_assignee_and_form_field_notifications.py +0 -0
  108. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0053_stageformfieldnotification_static_emails.py +0 -0
  109. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0054_rename_approved_pending_to_pending_approval.py +0 -0
  110. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0055_add_send_back.py +0 -0
  111. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0056_calculated_formula_spreadsheet.py +0 -0
  112. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0057_add_currency_field_type.py +0 -0
  113. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0058_rename_assignee_email_field_add_lookup_type.py +0 -0
  114. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0059_add_multiselect_list_field_type.py +0 -0
  115. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0060_alter_workflowstage_assignee_lookup_type_helptext.py +0 -0
  116. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0061_add_country_us_state_field_types.py +0 -0
  117. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0062_add_batch_import_to_formdefinition.py +0 -0
  118. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0063_formdefinition_api_enabled_apitoken.py +0 -0
  119. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0064_add_reviewer_groups.py +0 -0
  120. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0065_workflownotification.py +0 -0
  121. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0066_add_notify_submitter_to_workflownotification.py +0 -0
  122. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0067_migrate_legacy_notify_flags.py +0 -0
  123. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0068_drop_legacy_notify_fields.py +0 -0
  124. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0069_add_signature_field_type.py +0 -0
  125. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0070_workflowstage_allow_edit_form_data.py +0 -0
  126. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0071_alter_pendingnotification_notification_type.py +0 -0
  127. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0072_add_change_history.py +0 -0
  128. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0073_add_stage_notify_assignee_on_final_decision.py +0 -0
  129. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0074_add_notification_rule.py +0 -0
  130. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0075_migrate_to_notification_rules.py +0 -0
  131. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0076_update_notification_event_choices.py +0 -0
  132. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0077_drop_legacy_notification_models.py +0 -0
  133. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0078_allow_anonymous_submissions.py +0 -0
  134. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0079_webhookendpoint_webhookdeliverylog.py +0 -0
  135. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0080_add_rating_matrix_address_slider_fields_and_form_controls.py +0 -0
  136. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0081_add_success_page_fields.py +0 -0
  137. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0082_add_document_template_model.py +0 -0
  138. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0083_workflowdefinition_start_trigger.py +0 -0
  139. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0084_add_shared_option_list.py +0 -0
  140. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0085_add_payment_system.py +0 -0
  141. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0086_add_embed_enabled.py +0 -0
  142. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0087_notificationrule_use_triggering_stage.py +0 -0
  143. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0088_notificationrule_body_template_and_events.py +0 -0
  144. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0089_workflowstage_hide_comment_field.py +0 -0
  145. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0090_alter_stageapprovalgroup_options_and_more.py +0 -0
  146. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0091_add_uuid_fields.py +0 -0
  147. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0092_add_user_notification_preference.py +0 -0
  148. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0093_normalize_conditional_operators.py +0 -0
  149. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0094_formfield_show_help_text_in_detail.py +0 -0
  150. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0095_notificationrule_cc_fields.py +0 -0
  151. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0096_notificationlog_cc_bcc_emails.py +0 -0
  152. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0097_notificationlog_delivery_reconciliation.py +0 -0
  153. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/__init__.py +0 -0
  154. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/payments/__init__.py +0 -0
  155. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/payments/base.py +0 -0
  156. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/payments/checkout.py +0 -0
  157. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/payments/registry.py +0 -0
  158. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/payments/stripe_provider.py +0 -0
  159. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/payments/views.py +0 -0
  160. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/signals.py +0 -0
  161. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/sso_urls.py +0 -0
  162. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/api/swagger-ui-bundle.js +0 -0
  163. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/api/swagger-ui.css +0 -0
  164. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/css/forms.css +0 -0
  165. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/admin_notification_rule_stage_filter.js +0 -0
  166. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/dfw-embed.js +0 -0
  167. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/form-builder-api.js +0 -0
  168. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/form-builder-canvas.js +0 -0
  169. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/form-builder-history.js +0 -0
  170. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/form-builder-property-editor.js +0 -0
  171. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/form-builder-store.js +0 -0
  172. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/form-builder.js +0 -0
  173. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/form-enhancements.js +0 -0
  174. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/payment-stripe.js +0 -0
  175. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/signature-pad.js +0 -0
  176. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/workflow-builder-api.js +0 -0
  177. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/workflow-builder-store.js +0 -0
  178. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/workflow-builder.js +0 -0
  179. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/sync_api.py +0 -0
  180. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/sync_views.py +0 -0
  181. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/tasks.py +0 -0
  182. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/admin/django_forms_workflows/_sync_diff.html +0 -0
  183. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/admin/django_forms_workflows/diff_forms.html +0 -0
  184. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/admin/django_forms_workflows/form_builder.html +0 -0
  185. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/admin/django_forms_workflows/formdef_change_form.html +0 -0
  186. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/admin/django_forms_workflows/formdefinition/change_list.html +0 -0
  187. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/admin/django_forms_workflows/sync_import.html +0 -0
  188. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/admin/django_forms_workflows/sync_pull.html +0 -0
  189. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/admin/django_forms_workflows/sync_push.html +0 -0
  190. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/_category_node.html +0 -0
  191. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/_field_value.html +0 -0
  192. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/_form_data_rows.html +0 -0
  193. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/analytics_dashboard.html +0 -0
  194. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/api/docs.html +0 -0
  195. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/approval_inbox.html +0 -0
  196. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/approve.html +0 -0
  197. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/base.html +0 -0
  198. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/batch_import_result.html +0 -0
  199. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/completed_approvals.html +0 -0
  200. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/discard_draft_confirm.html +0 -0
  201. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/embed_base.html +0 -0
  202. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/embed_success.html +0 -0
  203. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/form_categories.html +0 -0
  204. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/form_embed.html +0 -0
  205. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/form_list.html +0 -0
  206. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/form_submit.html +0 -0
  207. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/my_submissions.html +0 -0
  208. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/notification_preferences.html +0 -0
  209. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/payment_collect.html +0 -0
  210. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/payment_error.html +0 -0
  211. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/public_submission_confirmation.html +0 -0
  212. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/rate_limited.html +0 -0
  213. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/reassign_task.html +0 -0
  214. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/resubmit_confirm.html +0 -0
  215. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/sso/login.html +0 -0
  216. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/sub_workflow_detail.html +0 -0
  217. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/submission_bulk_pdf.html +0 -0
  218. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/submission_detail.html +0 -0
  219. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/submission_pdf.html +0 -0
  220. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/submission_success.html +0 -0
  221. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/withdraw_confirm.html +0 -0
  222. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/emails/approval_notification.html +0 -0
  223. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/emails/approval_reminder.html +0 -0
  224. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/emails/approval_request.html +0 -0
  225. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/emails/email_styles.html +0 -0
  226. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/emails/escalation_notification.html +0 -0
  227. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/emails/notification_digest.html +0 -0
  228. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/emails/rejection_notification.html +0 -0
  229. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/emails/submission_notification.html +0 -0
  230. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/registration/login.html +0 -0
  231. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templatetags/__init__.py +0 -0
  232. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/templatetags/forms_workflows_tags.py +0 -0
  233. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/urls.py +0 -0
  234. {django_forms_workflows-0.77.5 → django_forms_workflows-0.78.0}/django_forms_workflows/workflow_engine.py +0 -0
@@ -1,22 +1,22 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-forms-workflows
3
- Version: 0.77.5
3
+ Version: 0.78.0
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
7
7
  Keywords: django,forms,workflows,approval,form-builder,dynamic-forms,ldap,enterprise,audit,database-driven,sso,saml,oauth2
8
8
  Author: Matt Davis
9
9
  Author-email: matt@opensensor.io
10
- Requires-Python: >=3.10,<4.0
10
+ Requires-Python: >=3.11,<4.0
11
11
  Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
12
12
  Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.10
14
13
  Classifier: Programming Language :: Python :: 3.11
15
14
  Classifier: Programming Language :: Python :: 3.12
16
15
  Classifier: Programming Language :: Python :: 3.13
17
16
  Classifier: Programming Language :: Python :: 3.14
18
17
  Classifier: Programming Language :: Python :: 3.15
19
18
  Provides-Extra: all
19
+ Provides-Extra: cache
20
20
  Provides-Extra: dev
21
21
  Provides-Extra: docs
22
22
  Provides-Extra: excel
@@ -31,7 +31,7 @@ Provides-Extra: postgresql
31
31
  Provides-Extra: qr
32
32
  Provides-Extra: saml
33
33
  Provides-Extra: sso
34
- Requires-Dist: Django (>=5.1,<7.0)
34
+ Requires-Dist: Django (>=5.2.18,<7.0,!=6.0.0,!=6.0.1,!=6.0.2,!=6.0.3,!=6.0.4,!=6.0.5,!=6.0.6,!=6.0.7,!=6.0.8,!=6.1.0,!=6.1.1)
35
35
  Requires-Dist: celery (>=5.3)
36
36
  Requires-Dist: crispy-bootstrap5 (>=2.0)
37
37
  Requires-Dist: django-auth-ldap (>=4.0) ; extra == "ldap" or extra == "all"
@@ -44,7 +44,9 @@ Requires-Dist: google-auth (>=2.20) ; extra == "gmail" or extra == "all"
44
44
  Requires-Dist: markdown (>=3.4) ; extra == "markdown" or extra == "all"
45
45
  Requires-Dist: mssql-django (>=1.3) ; extra == "mssql" or extra == "all"
46
46
  Requires-Dist: mysqlclient (>=2.2) ; extra == "mysql" or extra == "all"
47
+ Requires-Dist: nh3 (>=0.3.2)
47
48
  Requires-Dist: openpyxl (>=3.1) ; extra == "excel" or extra == "all"
49
+ Requires-Dist: pillow (>=13.0.0) ; extra == "pdf" or extra == "all"
48
50
  Requires-Dist: psycopg2-binary (>=2.9) ; extra == "postgresql" or extra == "all"
49
51
  Requires-Dist: pyodbc (>=5.0) ; extra == "mssql" or extra == "all"
50
52
  Requires-Dist: pytest (>=7.4) ; extra == "dev"
@@ -53,6 +55,7 @@ Requires-Dist: pytest-django (>=4.5) ; extra == "dev"
53
55
  Requires-Dist: python-decouple (>=3.8)
54
56
  Requires-Dist: python-ldap (>=3.4) ; extra == "ldap" or extra == "all"
55
57
  Requires-Dist: python3-saml (>=1.16) ; extra == "sso" or extra == "saml" or extra == "all"
58
+ Requires-Dist: redis (>=5.0) ; extra == "cache" or extra == "all"
56
59
  Requires-Dist: requests (>=2.31)
57
60
  Requires-Dist: ruff (>=0.4) ; extra == "dev"
58
61
  Requires-Dist: segno (>=1.6) ; extra == "qr" or extra == "all"
@@ -72,7 +75,7 @@ Description-Content-Type: text/markdown
72
75
  **Enterprise-grade, database-driven form builder with multi-stage approval workflows, external data integration, and cross-instance sync**
73
76
 
74
77
  [![License: LGPL v3](https://img.shields.io/badge/License-LGPL_v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
75
- [![Python Version](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/downloads/)
78
+ [![Python Version](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/downloads/)
76
79
  [![Django Version](https://img.shields.io/badge/django-5.2%2B-green)](https://www.djangoproject.com/)
77
80
  [![Version](https://img.shields.io/badge/version-0.49.0-orange)](https://github.com/opensensor/django-forms-workflows)
78
81
 
@@ -258,6 +261,8 @@ Move form definitions between environments from the Django Admin:
258
261
 
259
262
  ## Quick Start
260
263
 
264
+ See [security upgrade notes](docs/SECURITY_UPGRADE.md) before upgrading an existing deployment.
265
+
261
266
  ### Installation
262
267
 
263
268
  ```bash
@@ -413,8 +418,8 @@ graph TB
413
418
 
414
419
  ## Requirements
415
420
 
416
- - Python 3.10+
417
- - Django 5.2+
421
+ - Python 3.11+
422
+ - Django 5.2.18+, 6.0.9+, or 6.1.2+ (security patch minimums)
418
423
  - PostgreSQL, MySQL, or SQLite (PostgreSQL recommended for production)
419
424
  - Optional: Celery 5.0+ with Redis/Valkey for background task processing
420
425
  - Optional: `openpyxl` for Excel spreadsheet field support (`pip install django-forms-workflows[excel]`)
@@ -3,7 +3,7 @@
3
3
  **Enterprise-grade, database-driven form builder with multi-stage approval workflows, external data integration, and cross-instance sync**
4
4
 
5
5
  [![License: LGPL v3](https://img.shields.io/badge/License-LGPL_v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
6
- [![Python Version](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/downloads/)
6
+ [![Python Version](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/downloads/)
7
7
  [![Django Version](https://img.shields.io/badge/django-5.2%2B-green)](https://www.djangoproject.com/)
8
8
  [![Version](https://img.shields.io/badge/version-0.49.0-orange)](https://github.com/opensensor/django-forms-workflows)
9
9
 
@@ -189,6 +189,8 @@ Move form definitions between environments from the Django Admin:
189
189
 
190
190
  ## Quick Start
191
191
 
192
+ See [security upgrade notes](docs/SECURITY_UPGRADE.md) before upgrading an existing deployment.
193
+
192
194
  ### Installation
193
195
 
194
196
  ```bash
@@ -344,8 +346,8 @@ graph TB
344
346
 
345
347
  ## Requirements
346
348
 
347
- - Python 3.10+
348
- - Django 5.2+
349
+ - Python 3.11+
350
+ - Django 5.2.18+, 6.0.9+, or 6.1.2+ (security patch minimums)
349
351
  - PostgreSQL, MySQL, or SQLite (PostgreSQL recommended for production)
350
352
  - Optional: Celery 5.0+ with Redis/Valkey for background task processing
351
353
  - Optional: `openpyxl` for Excel spreadsheet field support (`pip install django-forms-workflows[excel]`)
@@ -1154,6 +1154,15 @@ class FormDefinitionAdmin(nested_admin.NestedModelAdmin):
1154
1154
 
1155
1155
  def sync_import_admin_view(self, request):
1156
1156
  """Admin page for importing form definitions from a JSON file or raw JSON."""
1157
+ from django.core.exceptions import PermissionDenied
1158
+
1159
+ if not self.has_view_permission(request):
1160
+ raise PermissionDenied
1161
+ if not (
1162
+ self.has_add_permission(request) and self.has_change_permission(request)
1163
+ ):
1164
+ raise PermissionDenied
1165
+
1157
1166
  from .sync_api import import_payload
1158
1167
 
1159
1168
  context = dict(self.admin_site.each_context(request))
@@ -1219,6 +1228,15 @@ class FormDefinitionAdmin(nested_admin.NestedModelAdmin):
1219
1228
  Step 1 (POST) – fetch available forms from the remote, show checkbox list
1220
1229
  Step 2 (POST) – import selected forms, show results
1221
1230
  """
1231
+ from django.core.exceptions import PermissionDenied
1232
+
1233
+ if not self.has_view_permission(request):
1234
+ raise PermissionDenied
1235
+ if not (
1236
+ self.has_add_permission(request) and self.has_change_permission(request)
1237
+ ):
1238
+ raise PermissionDenied
1239
+
1222
1240
  from .sync_api import (
1223
1241
  build_export_payload,
1224
1242
  fetch_remote_payload,
@@ -1372,6 +1390,11 @@ class FormDefinitionAdmin(nested_admin.NestedModelAdmin):
1372
1390
  Step 1 (POST) – show all local forms with checkboxes + diff status
1373
1391
  Step 2 (POST) – execute push for selected forms, show results
1374
1392
  """
1393
+ from django.core.exceptions import PermissionDenied
1394
+
1395
+ if not self.has_view_permission(request):
1396
+ raise PermissionDenied
1397
+
1375
1398
  from .sync_api import (
1376
1399
  build_export_payload,
1377
1400
  fetch_remote_payload,
@@ -106,7 +106,7 @@ def require_api_token(view_func):
106
106
  raw = auth.removeprefix("Bearer ").strip()
107
107
  try:
108
108
  token_obj = APIToken.objects.select_related("user").get(
109
- token=raw, is_active=True
109
+ token=raw, is_active=True, user__is_active=True
110
110
  )
111
111
  except (APIToken.DoesNotExist, ValueError, Exception): # noqa: BLE001
112
112
  return JsonResponse({"error": "Invalid or inactive token."}, status=401)
@@ -17,7 +17,7 @@ class DjangoFormsWorkflowsConfig(AppConfig):
17
17
  Import signal handlers and perform app initialization.
18
18
  """
19
19
  # Import signals to register handlers
20
- from . import signals # noqa: F401
20
+ from . import checks, signals # noqa: F401
21
21
 
22
22
  # Load custom callback handlers from FORMS_WORKFLOWS_CALLBACKS setting
23
23
  from .callback_registry import load_from_settings
@@ -0,0 +1,24 @@
1
+ """Deployment checks for public submission throttling."""
2
+
3
+ from django.conf import settings
4
+ from django.core.checks import Tags, Warning, register
5
+
6
+
7
+ @register(Tags.security, deploy=True)
8
+ def check_rate_limit_cache(app_configs, **kwargs):
9
+ alias = getattr(settings, "FORMS_WORKFLOWS_RATE_LIMIT_CACHE", "default")
10
+ backend = settings.CACHES.get(alias, {}).get("BACKEND", "")
11
+ if backend not in {
12
+ "django.core.cache.backends.redis.RedisCache",
13
+ "django.core.cache.backends.memcached.PyMemcacheCache",
14
+ "django.core.cache.backends.memcached.PyLibMCCache",
15
+ "django_redis.cache.RedisCache",
16
+ }:
17
+ return [
18
+ Warning(
19
+ "Public form rate limiting requires a shared cache with atomic add/incr.",
20
+ hint="Configure Redis or Memcached and FORMS_WORKFLOWS_RATE_LIMIT_CACHE; local-memory caches only limit one process.",
21
+ id="django_forms_workflows.W001",
22
+ )
23
+ ]
24
+ return []
@@ -8,6 +8,7 @@ from django.http import HttpResponseBadRequest
8
8
  from django.shortcuts import render
9
9
 
10
10
  from .models import FormDefinition
11
+ from .security import builder_permission_required
11
12
  from .sync_api import build_export_payload
12
13
 
13
14
 
@@ -429,6 +430,7 @@ def _build_summary(forms_data):
429
430
  return summaries
430
431
 
431
432
 
433
+ @builder_permission_required("view_formdefinition")
432
434
  def diff_forms_view(request):
433
435
  """Render a side-by-side JSON diff of selected FormDefinitions."""
434
436
  pks = request.GET.get("pks", "")
@@ -26,11 +26,13 @@ from .models import (
26
26
  WorkflowDefinition,
27
27
  WorkflowStage,
28
28
  )
29
+ from .security import builder_permission_required
29
30
 
30
31
  logger = logging.getLogger(__name__)
31
32
 
32
33
 
33
34
  @require_GET
35
+ @builder_permission_required("view_formtemplate")
34
36
  def form_builder_templates(request):
35
37
  """
36
38
  API endpoint to list available form templates.
@@ -63,6 +65,7 @@ def form_builder_templates(request):
63
65
 
64
66
 
65
67
  @require_GET
68
+ @builder_permission_required("view_formtemplate")
66
69
  def form_builder_load_template(request, template_id):
67
70
  """
68
71
  API endpoint to load a form template.
@@ -83,6 +86,7 @@ def form_builder_load_template(request, template_id):
83
86
 
84
87
 
85
88
  @require_POST
89
+ @builder_permission_required("view_formdefinition", "add_formdefinition")
86
90
  def form_builder_clone(request, form_id):
87
91
  """
88
92
  API endpoint to clone an existing form.
@@ -225,6 +229,7 @@ def form_builder_clone(request, form_id):
225
229
 
226
230
 
227
231
  @require_GET
232
+ @builder_permission_required("view_formdefinition")
228
233
  def form_builder_view(request, form_id=None):
229
234
  """
230
235
  Main form builder page.
@@ -269,6 +274,7 @@ def form_builder_view(request, form_id=None):
269
274
 
270
275
 
271
276
  @require_GET
277
+ @builder_permission_required("view_formdefinition")
272
278
  def form_builder_load(request, form_id):
273
279
  """
274
280
  API endpoint to load form data as JSON.
@@ -353,6 +359,7 @@ def form_builder_load(request, form_id):
353
359
 
354
360
 
355
361
  @require_POST
362
+ @builder_permission_required("save_formdefinition")
356
363
  def form_builder_save(request):
357
364
  """
358
365
  API endpoint to save form data.
@@ -604,6 +611,7 @@ def save_form_definition_from_builder_data(data, user, form_definition=None):
604
611
 
605
612
 
606
613
  @require_POST
614
+ @builder_permission_required("view_formdefinition")
607
615
  def form_builder_preview(request):
608
616
  """
609
617
  API endpoint to generate a preview of the form.
@@ -764,6 +772,7 @@ def form_builder_preview(request):
764
772
 
765
773
 
766
774
  @require_GET
775
+ @builder_permission_required("view_formdefinition", "view_documenttemplate")
767
776
  def document_template_list(request, form_id):
768
777
  """List document templates for a form."""
769
778
  templates = DocumentTemplate.objects.filter(form_definition_id=form_id).order_by(
@@ -789,6 +798,7 @@ def document_template_list(request, form_id):
789
798
 
790
799
 
791
800
  @require_POST
801
+ @builder_permission_required("change_formdefinition", "save_documenttemplate")
792
802
  def document_template_save(request, form_id):
793
803
  """Create or update a document template."""
794
804
  try:
@@ -847,6 +857,7 @@ def document_template_save(request, form_id):
847
857
 
848
858
 
849
859
  @require_POST
860
+ @builder_permission_required("change_formdefinition", "delete_documenttemplate")
850
861
  def document_template_delete(request, form_id, template_id):
851
862
  """Delete a document template."""
852
863
  tpl = get_object_or_404(
@@ -862,6 +873,7 @@ def document_template_delete(request, form_id, template_id):
862
873
 
863
874
 
864
875
  @require_GET
876
+ @builder_permission_required("view_sharedoptionlist")
865
877
  def shared_option_list_api(request):
866
878
  """List all shared option lists (for form builder dropdowns)."""
867
879
  lists = SharedOptionList.objects.filter(is_active=True).order_by("name")
@@ -882,6 +894,7 @@ def shared_option_list_api(request):
882
894
 
883
895
 
884
896
  @require_POST
897
+ @builder_permission_required("save_sharedoptionlist")
885
898
  def shared_option_list_save(request):
886
899
  """Create or update a shared option list."""
887
900
  try:
@@ -924,6 +937,7 @@ def shared_option_list_save(request):
924
937
 
925
938
 
926
939
  @require_POST
940
+ @builder_permission_required("delete_sharedoptionlist")
927
941
  def shared_option_list_delete(request, list_id):
928
942
  """Delete a shared option list."""
929
943
  ol = get_object_or_404(SharedOptionList, id=list_id)
@@ -333,18 +333,6 @@ class DynamicForm(forms.Form):
333
333
  # files). Also used to carry forward draft / resubmit file data.
334
334
  self.stashed_files = stashed_files or {}
335
335
 
336
- # Recover stashed-file metadata carried forward via hidden input from
337
- # a prior validation-failure render (browsers cannot re-send files).
338
- if not self.stashed_files and self.data:
339
- prev = self.data.get("_stashed_files")
340
- if prev:
341
- try:
342
- self.stashed_files = json.loads(prev)
343
- except (json.JSONDecodeError, TypeError):
344
- # Ignore malformed client metadata; uploaded files are rebuilt
345
- # from validated server-side data below.
346
- pass
347
-
348
336
  # Build form fields from definition
349
337
  # Exclude fields scoped to a workflow stage - those are for approvers only
350
338
  for field in (
@@ -354,15 +342,6 @@ class DynamicForm(forms.Form):
354
342
  ):
355
343
  self.add_field(field, initial_data)
356
344
 
357
- # Embed stashed-file metadata as a hidden field so it survives
358
- # validation-failure round-trips (browsers cannot re-send files).
359
- if self.stashed_files:
360
- self.fields["_stashed_files"] = forms.CharField(
361
- widget=forms.HiddenInput(),
362
- required=False,
363
- initial=json.dumps(self.stashed_files),
364
- )
365
-
366
345
  # Setup form layout with Crispy Forms
367
346
  self.helper = FormHelper()
368
347
  self.helper.form_method = "post"
@@ -763,7 +742,7 @@ class DynamicForm(forms.Form):
763
742
  # failure, saved with a draft, or carried from a resubmission).
764
743
  existing_file = self._get_existing_file_info(field_def.field_name)
765
744
  if existing_file:
766
- fname = existing_file.get("filename", "file")
745
+ fname = html_escape(existing_file.get("filename", "file"))
767
746
  extra_help = f"Previously uploaded: <strong>{fname}</strong>. Leave blank to keep it."
768
747
  base_help = field_args.get("help_text", "") or ""
769
748
  field_args["help_text"] = (
@@ -789,10 +768,11 @@ class DynamicForm(forms.Form):
789
768
  if isinstance(existing_file, list)
790
769
  else existing_file.get("filename", "file")
791
770
  )
792
- extra = f"Previously uploaded: <strong>{names}</strong>. Leave blank to keep."
771
+ extra = f"Previously uploaded: <strong>{html_escape(names)}</strong>. Leave blank to keep."
793
772
  help_text = f"{help_text} {extra}" if help_text else extra
794
773
  required = False
795
774
  self.fields[field_def.field_name] = MultipleFileField(
775
+ validators=_build_file_validators(field_def),
796
776
  required=required,
797
777
  label=field_def.field_label,
798
778
  help_text=help_text,
@@ -931,12 +911,13 @@ class DynamicForm(forms.Form):
931
911
  )
932
912
 
933
913
  elif field_def.field_type == "display_text":
934
- display_html = _render_display_text(initial or field_def.default_value)
914
+ display_html = _render_display_text(field_def.default_value)
935
915
  self.fields[field_def.field_name] = forms.CharField(
936
916
  required=False,
937
917
  label=field_def.field_label,
938
918
  help_text=field_def.help_text,
939
- initial=initial or field_def.default_value,
919
+ initial=field_def.default_value,
920
+ disabled=True,
940
921
  widget=DisplayTextWidget(
941
922
  display_html=display_html,
942
923
  attrs={"data-display-text": "true"},
@@ -881,6 +881,8 @@ class DocumentTemplate(models.Model):
881
881
  """Render this template with submission data, returning HTML string."""
882
882
  import re
883
883
 
884
+ from django.utils.html import escape
885
+
884
886
  form_data = submission.form_data or {}
885
887
 
886
888
  # Build merge context with system variables + form data
@@ -906,6 +908,7 @@ class DocumentTemplate(models.Model):
906
908
  else:
907
909
  context[key] = str(val) if val is not None else ""
908
910
 
911
+ context = {key: str(escape(value)) for key, value in context.items()}
909
912
  html = self.html_content
910
913
 
911
914
  # Process conditional blocks: {% if field_name %}...{% endif %}
@@ -0,0 +1,53 @@
1
+ """PDF resources are offline by default, including CSS and SVG subresources."""
2
+
3
+ import base64
4
+ import binascii
5
+
6
+ from django.conf import settings
7
+
8
+
9
+ def safe_pdf_url_fetcher(url, *args, **kwargs):
10
+ """Allow bounded embedded raster images; never open files or network URLs.
11
+
12
+ Logos/signatures should be embedded as data:image/png;base64,... (or JPEG).
13
+ A deployment needing additional resources can supply a reviewed fetcher via
14
+ FORMS_WORKFLOWS_PDF_URL_FETCHER. It must enforce its own allowlist and limits.
15
+ """
16
+ limit = getattr(settings, "FORMS_WORKFLOWS_PDF_MAX_RESOURCE_BYTES", 5 * 1024 * 1024)
17
+ if len(url) > limit * 4 // 3 + 128:
18
+ raise ValueError("PDF resource exceeds size limit")
19
+ header, sep, encoded = url.partition(",")
20
+ if not sep or header not in ("data:image/png;base64", "data:image/jpeg;base64"):
21
+ raise ValueError("PDF resource URL is not permitted")
22
+ try:
23
+ data = base64.b64decode(encoded, validate=True)
24
+ except (ValueError, binascii.Error) as exc:
25
+ raise ValueError("Invalid PDF image") from exc
26
+ mime = header[5:].split(";")[0]
27
+ signature = b"\x89PNG\r\n\x1a\n" if mime == "image/png" else b"\xff\xd8\xff"
28
+ if len(data) > limit or not data.startswith(signature):
29
+ raise ValueError("Invalid PDF image")
30
+ return {"string": data, "mime_type": mime}
31
+
32
+
33
+ def pdf_url_fetcher():
34
+ from django.utils.module_loading import import_string
35
+
36
+ custom = getattr(settings, "FORMS_WORKFLOWS_PDF_URL_FETCHER", None)
37
+ if custom:
38
+ factory = import_string(custom)
39
+ return factory()
40
+ # WeasyPrint 70 replaced dictionary-returning callables with URLFetcher.
41
+ try:
42
+ from weasyprint.urls import URLFetcher, URLFetcherResponse
43
+ except ImportError:
44
+ return safe_pdf_url_fetcher
45
+
46
+ class OfflineFetcher(URLFetcher):
47
+ def fetch(self, url, headers=None):
48
+ resource = safe_pdf_url_fetcher(url)
49
+ return URLFetcherResponse(
50
+ url, resource["string"], {"Content-Type": resource["mime_type"]}
51
+ )
52
+
53
+ return OfflineFetcher(allowed_protocols={"data"}, allow_redirects=False)
@@ -18,17 +18,31 @@ from django.shortcuts import render
18
18
  from django.utils import timezone
19
19
  from django.utils.safestring import mark_safe
20
20
 
21
- from .models import ApprovalTask, FormDefinition, FormSubmission
21
+ from .models import ApprovalTask, FormDefinition
22
+ from .security import visible_submissions
23
+
24
+
25
+ def _range_days(request):
26
+ try:
27
+ return max(1, min(int(request.GET.get("days", 90)), 3660))
28
+ except (TypeError, ValueError):
29
+ return 90
30
+
31
+
32
+ def _csv_cell(value):
33
+ if isinstance(value, str) and value.lstrip().startswith(("=", "+", "-", "@")):
34
+ return "'" + value
35
+ return value
22
36
 
23
37
 
24
38
  @login_required
25
39
  def analytics_export_csv(request):
26
40
  """Export analytics summary data as CSV."""
27
- range_days = int(request.GET.get("days", 90))
41
+ range_days = _range_days(request)
28
42
  cutoff = timezone.now() - timedelta(days=range_days)
29
43
  form_slug = request.GET.get("form", "")
30
44
 
31
- submissions_qs = FormSubmission.objects.exclude(status="draft")
45
+ submissions_qs = visible_submissions(request.user).exclude(status="draft")
32
46
  if form_slug:
33
47
  submissions_qs = submissions_qs.filter(form_definition__slug=form_slug)
34
48
 
@@ -46,15 +60,18 @@ def analytics_export_csv(request):
46
60
  ).order_by("-created_at"):
47
61
  writer.writerow(
48
62
  [
49
- sub.created_at.strftime("%Y-%m-%d %H:%M"),
50
- sub.form_definition.name if sub.form_definition else "",
51
- sub.status,
52
- (
53
- sub.submitter.get_full_name() or sub.submitter.username
54
- if sub.submitter
55
- else "Anonymous"
56
- ),
57
- sub.id,
63
+ _csv_cell(value)
64
+ for value in [
65
+ sub.created_at.strftime("%Y-%m-%d %H:%M"),
66
+ sub.form_definition.name if sub.form_definition else "",
67
+ sub.status,
68
+ (
69
+ sub.submitter.get_full_name() or sub.submitter.username
70
+ if sub.submitter
71
+ else "Anonymous"
72
+ ),
73
+ sub.id,
74
+ ]
58
75
  ]
59
76
  )
60
77
 
@@ -66,14 +83,14 @@ def analytics_dashboard(request):
66
83
  """Render the analytics dashboard with submission & approval metrics."""
67
84
 
68
85
  # ── Time range filter ───────────────────────────────────────────────
69
- range_days = int(request.GET.get("days", 90))
86
+ range_days = _range_days(request)
70
87
  cutoff = timezone.now() - timedelta(days=range_days)
71
88
  prev_cutoff = cutoff - timedelta(days=range_days) # Previous period
72
89
  form_slug = request.GET.get("form", "")
73
90
 
74
91
  # Base querysets
75
- submissions_qs = FormSubmission.objects.exclude(status="draft")
76
- tasks_qs = ApprovalTask.objects.all()
92
+ submissions_qs = visible_submissions(request.user).exclude(status="draft")
93
+ tasks_qs = ApprovalTask.objects.filter(submission__in=submissions_qs)
77
94
 
78
95
  if form_slug:
79
96
  submissions_qs = submissions_qs.filter(form_definition__slug=form_slug)
@@ -195,7 +212,7 @@ def analytics_dashboard(request):
195
212
  # ── 7. Bottleneck stages ────────────────────────────────────────────
196
213
  # Stages with the most pending tasks right now
197
214
  bottleneck_stages = list(
198
- ApprovalTask.objects.filter(status="pending", workflow_stage__isnull=False)
215
+ tasks_qs.filter(status="pending", workflow_stage__isnull=False)
199
216
  .values(
200
217
  "workflow_stage__name", "workflow_stage__workflow__form_definition__name"
201
218
  )
@@ -232,13 +249,16 @@ def analytics_dashboard(request):
232
249
 
233
250
  # ── Available forms for filter dropdown ─────────────────────────────
234
251
  available_forms = list(
235
- FormDefinition.objects.filter(is_active=True)
252
+ FormDefinition.objects.filter(
253
+ is_active=True, submissions__in=visible_submissions(request.user)
254
+ )
255
+ .distinct()
236
256
  .values_list("slug", "name")
237
257
  .order_by("name")
238
258
  )
239
259
 
240
260
  # ── 10. Overdue tasks ───────────────────────────────────────────────
241
- overdue_count = ApprovalTask.objects.filter(
261
+ overdue_count = tasks_qs.filter(
242
262
  status="pending",
243
263
  due_date__isnull=False,
244
264
  due_date__lt=timezone.now(),
@@ -256,7 +276,12 @@ def analytics_dashboard(request):
256
276
  else:
257
277
  clean[k] = v
258
278
  serializable.append(clean)
259
- return mark_safe(json.dumps(serializable))
279
+ return mark_safe(
280
+ json.dumps(serializable)
281
+ .replace("<", "\\u003C")
282
+ .replace(">", "\\u003E")
283
+ .replace("&", "\\u0026")
284
+ )
260
285
 
261
286
  context = {
262
287
  "range_days": range_days,