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