django-forms-workflows 0.72.4__tar.gz → 0.74.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.
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/CHANGELOG.md +67 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/PKG-INFO +1 -1
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/admin.py +14 -1
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/diff_views.py +32 -1
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/form_builder_views.py +8 -0
- django_forms_workflows-0.74.0/django_forms_workflows/migrations/0094_formfield_show_help_text_in_detail.py +24 -0
- django_forms_workflows-0.74.0/django_forms_workflows/migrations/0095_notificationrule_cc_fields.py +35 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/models.py +23 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/static/django_forms_workflows/js/form-builder.js +10 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/sync_api.py +5 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/tasks.py +104 -7
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/_form_data_rows.html +2 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/submission_detail.html +7 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/emails/approval_notification.html +1 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/views.py +5 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/pyproject.toml +1 -1
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/LICENSE +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/README.md +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/__init__.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/api_urls.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/api_views.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/apps.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/callback_registry.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/conditions.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/context_processors.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/data_sources/__init__.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/data_sources/base.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/data_sources/database_source.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/data_sources/ldap_source.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/data_sources/user_source.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/email_backends/__init__.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/email_backends/gmail_api.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/form_builder_urls.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/forms.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/handlers/__init__.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/handlers/api_handler.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/handlers/base.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/handlers/database_handler.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/handlers/email_handler.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/handlers/executor.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/handlers/file_handler.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/handlers/ldap_handler.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/history.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/ldap_backend.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/management/commands/create_default_templates.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/management/commands/pull_forms.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/management/commands/push_forms.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/management/commands/seed_farm_demo.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/management/commands/seed_pcn_form.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/management/commands/seed_prefill_sources.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/management/commands/sync_ldap_profiles.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/management/commands/sync_ldap_users.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/management/commands/test_db_connection.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0001_initial.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0002_prefillsource_alter_formfield_prefill_source_and_more.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0003_postsubmissionaction.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0004_formtemplate.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0005_advanced_conditional_logic.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0006_workflowdefinition_visual_workflow_data_and_more.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0007_add_userprofile_ldap_enhancements.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0008_add_file_workflow_models.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0009_add_formfield_readonly.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0010_add_prefillsource_template_fields.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0011_postsubmissionaction_email_body_template_and_more.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0012_add_approval_step_fields.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0013_add_database_query_key.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0014_add_form_category.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0015_add_workflowstage_staged_approvals.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0016_add_pdf_generation.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0017_add_notification_batching.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0018_add_form_category_parent.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0019_add_ldap_group_profile.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0020_add_bulk_export_field.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0021_alter_pendingnotification_scheduled_for_and_more.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0022_add_bulk_pdf_export.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0023_add_performance_indexes.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0024_add_allow_resubmit.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0025_workflowstage_approve_label.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0026_alter_formfield_width.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0027_formsubmission_form_data_gin.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0028_workflowstagegroupconfig.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0029_formfield_workflow_stage_fk_drop_groupconfig.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0030_formfield_phone_type.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0031_sub_workflows.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0032_approved_pending_detached_sub_workflow.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0033_add_reject_parent_to_subworkflowdefinition.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0034_add_notificationlog.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0035_migrate_prefill_source_strings.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0036_add_workflowstage_auto_created.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0037_migrate_flat_workflows_to_staged.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0038_remove_legacy_prefill_source_and_approval_step.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0039_update_help_text.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0040_migrate_deprecated_features.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0041_remove_deprecated_fields.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0042_add_sub_workflow_section_label.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0043_fix_form_data_decimal_encoding.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0044_remove_subworkflowinstance_label.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0045_stageapprovalgroup_workflow_name_label.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0046_workflow_definition_one_to_many.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0047_add_hide_approval_history.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0048_workflowdefinition_trigger_conditions_and_more.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0049_formfield_multifile_type.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0050_workflowdefinition_collapse_parallel_stages.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0051_formdefinition_is_listed.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0052_dynamic_assignee_and_form_field_notifications.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0053_stageformfieldnotification_static_emails.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0054_rename_approved_pending_to_pending_approval.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0055_add_send_back.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0056_calculated_formula_spreadsheet.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0057_add_currency_field_type.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0058_rename_assignee_email_field_add_lookup_type.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0059_add_multiselect_list_field_type.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0060_alter_workflowstage_assignee_lookup_type_helptext.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0061_add_country_us_state_field_types.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0062_add_batch_import_to_formdefinition.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0063_formdefinition_api_enabled_apitoken.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0064_add_reviewer_groups.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0065_workflownotification.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0066_add_notify_submitter_to_workflownotification.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0067_migrate_legacy_notify_flags.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0068_drop_legacy_notify_fields.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0069_add_signature_field_type.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0070_workflowstage_allow_edit_form_data.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0071_alter_pendingnotification_notification_type.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0072_add_change_history.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0073_add_stage_notify_assignee_on_final_decision.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0074_add_notification_rule.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0075_migrate_to_notification_rules.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0076_update_notification_event_choices.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0077_drop_legacy_notification_models.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0078_allow_anonymous_submissions.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0079_webhookendpoint_webhookdeliverylog.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0080_add_rating_matrix_address_slider_fields_and_form_controls.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0081_add_success_page_fields.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0082_add_document_template_model.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0083_workflowdefinition_start_trigger.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0084_add_shared_option_list.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0085_add_payment_system.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0086_add_embed_enabled.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0087_notificationrule_use_triggering_stage.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0088_notificationrule_body_template_and_events.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0089_workflowstage_hide_comment_field.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0090_alter_stageapprovalgroup_options_and_more.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0091_add_uuid_fields.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0092_add_user_notification_preference.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/0093_normalize_conditional_operators.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/migrations/__init__.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/payments/__init__.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/payments/base.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/payments/registry.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/payments/stripe_provider.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/payments/views.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/reporting_views.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/signals.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/sso_backends.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/sso_urls.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/sso_views.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/static/django_forms_workflows/api/swagger-ui-bundle.js +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/static/django_forms_workflows/api/swagger-ui.css +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/static/django_forms_workflows/css/forms.css +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/static/django_forms_workflows/js/dfw-embed.js +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/static/django_forms_workflows/js/form-enhancements.js +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/static/django_forms_workflows/js/payment-stripe.js +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/static/django_forms_workflows/js/signature-pad.js +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/static/django_forms_workflows/js/workflow-builder.js +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/sync_views.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/admin/django_forms_workflows/_sync_diff.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/admin/django_forms_workflows/diff_forms.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/admin/django_forms_workflows/form_builder.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/admin/django_forms_workflows/formdef_change_form.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/admin/django_forms_workflows/formdefinition/change_list.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/admin/django_forms_workflows/sync_import.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/admin/django_forms_workflows/sync_pull.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/admin/django_forms_workflows/sync_push.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/admin/django_forms_workflows/workflow_builder.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/_category_node.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/_field_value.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/analytics_dashboard.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/api/docs.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/approval_inbox.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/approve.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/base.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/batch_import_result.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/completed_approvals.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/discard_draft_confirm.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/embed_base.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/embed_success.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/form_categories.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/form_embed.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/form_list.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/form_submit.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/my_submissions.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/notification_preferences.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/payment_collect.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/payment_error.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/public_submission_confirmation.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/rate_limited.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/reassign_task.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/resubmit_confirm.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/sso/login.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/sub_workflow_detail.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/submission_bulk_pdf.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/submission_pdf.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/submission_success.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/django_forms_workflows/withdraw_confirm.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/emails/approval_reminder.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/emails/approval_request.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/emails/email_styles.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/emails/escalation_notification.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/emails/notification_digest.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/emails/rejection_notification.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/emails/submission_notification.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templates/registration/login.html +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templatetags/__init__.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/templatetags/forms_workflows_tags.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/urls.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/utils.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/workflow_builder_views.py +0 -0
- {django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/workflow_engine.py +0 -0
|
@@ -7,6 +7,73 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.74.0] - 2026-04-24
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **Public decision comments now reach the submitter on approval too.**
|
|
14
|
+
The rejection email (`rejection_notification.html`) has always
|
|
15
|
+
rendered the approver's public decision comment, but the mirror
|
|
16
|
+
approval email (`approval_notification.html`) silently dropped it —
|
|
17
|
+
and `submission_detail.html`'s hidden-history mode (used when a
|
|
18
|
+
workflow sets `hide_approval_history=True`) also only surfaced the
|
|
19
|
+
comment on rejections. So an approved submission whose approver left
|
|
20
|
+
a "Welcome aboard / here's your next step" public note had no way to
|
|
21
|
+
reach the submitter. Both the approval email template and the
|
|
22
|
+
approved branch of the privacy-mode submission detail now render
|
|
23
|
+
`task.comments` exactly like the rejection side does. This makes
|
|
24
|
+
public-comment behaviour symmetrical on both outcomes; the
|
|
25
|
+
`hide_approval_history` flag remains purely a "hide the other
|
|
26
|
+
approval stages' activity" toggle.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- **`reviewer_groups` now round-trips through sync push/pull.** The
|
|
30
|
+
`FormDefinition.reviewer_groups` M2M was silently dropped by the
|
|
31
|
+
export/import path, so pulling a form into another environment lost
|
|
32
|
+
every non-admin reviewer assignment. The sync serializer and importer
|
|
33
|
+
now treat `reviewer_groups` exactly like `submit_groups` /
|
|
34
|
+
`view_groups` / `admin_groups` (resolved by group name, auto-created
|
|
35
|
+
on import). The admin diff viewer also reports reviewer_group
|
|
36
|
+
additions and removals.
|
|
37
|
+
- **Full `PostSubmissionAction` coverage in the diff viewer.** The
|
|
38
|
+
admin side-by-side diff only compared a small subset of action fields
|
|
39
|
+
(primarily email_to / api_endpoint / conditions). It now mirrors the
|
|
40
|
+
sync serializer and flags changes to every round-tripped field:
|
|
41
|
+
database action config (`db_alias`, `db_schema`, `db_table`,
|
|
42
|
+
`db_lookup_field`, `db_user_field`, `db_field_mappings`), LDAP config
|
|
43
|
+
(`ldap_dn_template`, `ldap_field_mappings`), API config (`api_headers`,
|
|
44
|
+
`api_body_template`), custom handler config (`custom_handler_path`,
|
|
45
|
+
`custom_handler_config`), email config (`email_to_field`, `email_cc`,
|
|
46
|
+
`email_cc_field`, `email_body_template`, `email_template_name`),
|
|
47
|
+
`is_locked`, `description`, and retry/failure settings
|
|
48
|
+
(`fail_silently`, `retry_on_failure`, `max_retries`).
|
|
49
|
+
|
|
50
|
+
- **CC recipients on notification rules (static and dynamic).** Two new
|
|
51
|
+
fields on `NotificationRule`:
|
|
52
|
+
- `cc_static_emails` — comma-separated fixed CC addresses.
|
|
53
|
+
- `cc_email_field` — form field slug whose submitted value is used as
|
|
54
|
+
a dynamic CC address, resolved per-submission.
|
|
55
|
+
The TO list still drives one personalised email per recipient (so
|
|
56
|
+
`{{ approver }}` context and per-user muting keep working); CC
|
|
57
|
+
addresses are attached to the first outgoing email of each rule
|
|
58
|
+
execution, so each CC recipient receives exactly one copy. CC
|
|
59
|
+
addresses that duplicate a TO are dropped, and per-user mute
|
|
60
|
+
preferences apply to CCs as well. For batched/digest workflows CCs
|
|
61
|
+
are queued alongside TOs. Admin UI (inline + standalone) and
|
|
62
|
+
`NotificationLog` (prefixed `cc:` on CC rows) updated accordingly.
|
|
63
|
+
New migration `0095_notificationrule_cc_fields`.
|
|
64
|
+
|
|
65
|
+
## [0.73.0] - 2026-04-23
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
- **Per-field `show_help_text_in_detail` flag.** Fields can now opt into
|
|
69
|
+
rendering their `help_text` alongside the stored value in the
|
|
70
|
+
submission and approval detail views. Intended for attestation /
|
|
71
|
+
consent statements attached to initials or signature fields, where
|
|
72
|
+
the statement the user agreed to should be visible with their
|
|
73
|
+
initials rather than only at fill-in time. Exposed in the form
|
|
74
|
+
builder (checkbox under Help Text) and in the Django admin. New
|
|
75
|
+
migration `0094_formfield_show_help_text_in_detail`.
|
|
76
|
+
|
|
10
77
|
## [0.72.4] - 2026-04-23
|
|
11
78
|
|
|
12
79
|
### Fixed
|
{django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/admin.py
RENAMED
|
@@ -148,6 +148,7 @@ class FormFieldInline(nested_admin.NestedStackedInline):
|
|
|
148
148
|
("order", "field_label", "field_name", "field_type"),
|
|
149
149
|
("required", "readonly", "workflow_stage"),
|
|
150
150
|
("help_text", "placeholder", "width", "css_class"),
|
|
151
|
+
("show_help_text_in_detail",),
|
|
151
152
|
)
|
|
152
153
|
},
|
|
153
154
|
),
|
|
@@ -342,7 +343,14 @@ class FormFieldAdmin(admin.ModelAdmin):
|
|
|
342
343
|
),
|
|
343
344
|
(
|
|
344
345
|
"Display",
|
|
345
|
-
{
|
|
346
|
+
{
|
|
347
|
+
"fields": (
|
|
348
|
+
"help_text",
|
|
349
|
+
"show_help_text_in_detail",
|
|
350
|
+
"placeholder",
|
|
351
|
+
),
|
|
352
|
+
"classes": ("collapse",),
|
|
353
|
+
},
|
|
346
354
|
),
|
|
347
355
|
(
|
|
348
356
|
"Conditional Display",
|
|
@@ -560,6 +568,7 @@ class NotificationRuleInline(nested_admin.NestedStackedInline):
|
|
|
560
568
|
"notify_stage_groups",
|
|
561
569
|
),
|
|
562
570
|
("email_field", "static_emails"),
|
|
571
|
+
("cc_email_field", "cc_static_emails"),
|
|
563
572
|
"notify_groups",
|
|
564
573
|
"subject_template",
|
|
565
574
|
)
|
|
@@ -918,6 +927,7 @@ class FormDefinitionAdmin(nested_admin.NestedModelAdmin):
|
|
|
918
927
|
field_type=field.field_type,
|
|
919
928
|
required=field.required,
|
|
920
929
|
help_text=field.help_text,
|
|
930
|
+
show_help_text_in_detail=field.show_help_text_in_detail,
|
|
921
931
|
placeholder=field.placeholder,
|
|
922
932
|
width=field.width,
|
|
923
933
|
css_class=field.css_class,
|
|
@@ -1781,6 +1791,8 @@ class NotificationRuleAdmin(admin.ModelAdmin):
|
|
|
1781
1791
|
"stage__name",
|
|
1782
1792
|
"email_field",
|
|
1783
1793
|
"static_emails",
|
|
1794
|
+
"cc_email_field",
|
|
1795
|
+
"cc_static_emails",
|
|
1784
1796
|
)
|
|
1785
1797
|
autocomplete_fields = ("workflow",)
|
|
1786
1798
|
fieldsets = (
|
|
@@ -1796,6 +1808,7 @@ class NotificationRuleAdmin(admin.ModelAdmin):
|
|
|
1796
1808
|
"notify_stage_groups",
|
|
1797
1809
|
),
|
|
1798
1810
|
("email_field", "static_emails"),
|
|
1811
|
+
("cc_email_field", "cc_static_emails"),
|
|
1799
1812
|
"notify_groups",
|
|
1800
1813
|
"subject_template",
|
|
1801
1814
|
)
|
{django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/diff_views.py
RENAMED
|
@@ -183,7 +183,7 @@ def _build_summary(forms_data):
|
|
|
183
183
|
diffs.append(f"category: {b_cat_slug!r} → {o_cat_slug!r}")
|
|
184
184
|
|
|
185
185
|
# Permission groups
|
|
186
|
-
for g in ("submit_groups", "view_groups", "admin_groups"):
|
|
186
|
+
for g in ("submit_groups", "view_groups", "admin_groups", "reviewer_groups"):
|
|
187
187
|
bg = set(b_form.get(g, []))
|
|
188
188
|
og = set(o_form.get(g, []))
|
|
189
189
|
if bg != og:
|
|
@@ -216,18 +216,49 @@ def _build_summary(forms_data):
|
|
|
216
216
|
)
|
|
217
217
|
b_action_map = {a.get("name"): a for a in b_actions}
|
|
218
218
|
o_action_map = {a.get("name"): a for a in o_actions}
|
|
219
|
+
# Keep in sync with sync_api._serialize_post_action so every
|
|
220
|
+
# field that round-trips through push/pull also shows up here.
|
|
219
221
|
action_check_keys = [
|
|
220
222
|
"action_type",
|
|
221
223
|
"trigger",
|
|
222
224
|
"is_active",
|
|
223
225
|
"order",
|
|
226
|
+
"is_locked",
|
|
227
|
+
"description",
|
|
228
|
+
# Database action
|
|
229
|
+
"db_alias",
|
|
230
|
+
"db_schema",
|
|
231
|
+
"db_table",
|
|
232
|
+
"db_lookup_field",
|
|
233
|
+
"db_user_field",
|
|
234
|
+
"db_field_mappings",
|
|
235
|
+
# LDAP action
|
|
236
|
+
"ldap_dn_template",
|
|
237
|
+
"ldap_field_mappings",
|
|
238
|
+
# API action
|
|
224
239
|
"api_endpoint",
|
|
225
240
|
"api_method",
|
|
241
|
+
"api_headers",
|
|
242
|
+
"api_body_template",
|
|
243
|
+
# Custom handler
|
|
244
|
+
"custom_handler_path",
|
|
245
|
+
"custom_handler_config",
|
|
246
|
+
# Email action
|
|
226
247
|
"email_to",
|
|
248
|
+
"email_to_field",
|
|
249
|
+
"email_cc",
|
|
250
|
+
"email_cc_field",
|
|
227
251
|
"email_subject_template",
|
|
252
|
+
"email_body_template",
|
|
253
|
+
"email_template_name",
|
|
254
|
+
# Conditional execution
|
|
228
255
|
"condition_field",
|
|
229
256
|
"condition_operator",
|
|
230
257
|
"condition_value",
|
|
258
|
+
# Retry / failure handling
|
|
259
|
+
"fail_silently",
|
|
260
|
+
"retry_on_failure",
|
|
261
|
+
"max_retries",
|
|
231
262
|
]
|
|
232
263
|
for name in sorted(b_action_names & o_action_names):
|
|
233
264
|
ba, oa = b_action_map[name], o_action_map[name]
|
|
@@ -132,6 +132,7 @@ def form_builder_clone(request, form_id):
|
|
|
132
132
|
field_type=field.field_type,
|
|
133
133
|
required=field.required,
|
|
134
134
|
help_text=field.help_text,
|
|
135
|
+
show_help_text_in_detail=field.show_help_text_in_detail,
|
|
135
136
|
placeholder=field.placeholder,
|
|
136
137
|
width=field.width,
|
|
137
138
|
css_class=field.css_class,
|
|
@@ -294,6 +295,7 @@ def form_builder_load(request, form_id):
|
|
|
294
295
|
"field_type": field.field_type,
|
|
295
296
|
"required": field.required,
|
|
296
297
|
"help_text": field.help_text or "",
|
|
298
|
+
"show_help_text_in_detail": field.show_help_text_in_detail,
|
|
297
299
|
"placeholder": field.placeholder or "",
|
|
298
300
|
"width": field.width,
|
|
299
301
|
"css_class": field.css_class or "",
|
|
@@ -498,6 +500,9 @@ def form_builder_save(request):
|
|
|
498
500
|
"field_type": field_data.get("field_type", "text"),
|
|
499
501
|
"required": field_data.get("required", False),
|
|
500
502
|
"help_text": field_data.get("help_text", ""),
|
|
503
|
+
"show_help_text_in_detail": field_data.get(
|
|
504
|
+
"show_help_text_in_detail", False
|
|
505
|
+
),
|
|
501
506
|
"placeholder": field_data.get("placeholder", ""),
|
|
502
507
|
"width": field_data.get("width", "full"),
|
|
503
508
|
"css_class": field_data.get("css_class", ""),
|
|
@@ -652,6 +657,9 @@ def form_builder_preview(request):
|
|
|
652
657
|
"field_type": field_data.get("field_type", "text"),
|
|
653
658
|
"required": field_data.get("required", False),
|
|
654
659
|
"help_text": field_data.get("help_text", ""),
|
|
660
|
+
"show_help_text_in_detail": field_data.get(
|
|
661
|
+
"show_help_text_in_detail", False
|
|
662
|
+
),
|
|
655
663
|
"placeholder": field_data.get("placeholder", ""),
|
|
656
664
|
"width": field_data.get("width", "full"),
|
|
657
665
|
"css_class": field_data.get("css_class", ""),
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from django.db import migrations, models
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Migration(migrations.Migration):
|
|
5
|
+
|
|
6
|
+
dependencies = [
|
|
7
|
+
("django_forms_workflows", "0093_normalize_conditional_operators"),
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
operations = [
|
|
11
|
+
migrations.AddField(
|
|
12
|
+
model_name="formfield",
|
|
13
|
+
name="show_help_text_in_detail",
|
|
14
|
+
field=models.BooleanField(
|
|
15
|
+
default=False,
|
|
16
|
+
help_text=(
|
|
17
|
+
"If checked, the field's help text is shown next to the value "
|
|
18
|
+
"in the submission and approval detail view. Use for "
|
|
19
|
+
"attestation or consent statements attached to "
|
|
20
|
+
"initials/signature fields."
|
|
21
|
+
),
|
|
22
|
+
),
|
|
23
|
+
),
|
|
24
|
+
]
|
django_forms_workflows-0.74.0/django_forms_workflows/migrations/0095_notificationrule_cc_fields.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from django.db import migrations, models
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Migration(migrations.Migration):
|
|
5
|
+
|
|
6
|
+
dependencies = [
|
|
7
|
+
("django_forms_workflows", "0094_formfield_show_help_text_in_detail"),
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
operations = [
|
|
11
|
+
migrations.AddField(
|
|
12
|
+
model_name="notificationrule",
|
|
13
|
+
name="cc_email_field",
|
|
14
|
+
field=models.CharField(
|
|
15
|
+
blank=True,
|
|
16
|
+
default="",
|
|
17
|
+
help_text=(
|
|
18
|
+
"Form field slug whose submitted value is a CC email "
|
|
19
|
+
"address. Resolved from form_data at send time "
|
|
20
|
+
"(varies per submission)."
|
|
21
|
+
),
|
|
22
|
+
max_length=200,
|
|
23
|
+
),
|
|
24
|
+
),
|
|
25
|
+
migrations.AddField(
|
|
26
|
+
model_name="notificationrule",
|
|
27
|
+
name="cc_static_emails",
|
|
28
|
+
field=models.CharField(
|
|
29
|
+
blank=True,
|
|
30
|
+
default="",
|
|
31
|
+
help_text="Comma-separated fixed CC email addresses.",
|
|
32
|
+
max_length=1000,
|
|
33
|
+
),
|
|
34
|
+
),
|
|
35
|
+
]
|
{django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/models.py
RENAMED
|
@@ -597,6 +597,14 @@ class FormField(models.Model):
|
|
|
597
597
|
# Display
|
|
598
598
|
order = models.IntegerField(default=0, help_text="Fields are sorted by this number")
|
|
599
599
|
help_text = models.TextField(blank=True, help_text="Instructions shown below field")
|
|
600
|
+
show_help_text_in_detail = models.BooleanField(
|
|
601
|
+
default=False,
|
|
602
|
+
help_text=(
|
|
603
|
+
"If checked, the field's help text is shown next to the value in the "
|
|
604
|
+
"submission and approval detail view. Use for attestation or consent "
|
|
605
|
+
"statements attached to initials/signature fields."
|
|
606
|
+
),
|
|
607
|
+
)
|
|
600
608
|
placeholder = models.CharField(max_length=200, blank=True)
|
|
601
609
|
css_class = models.CharField(
|
|
602
610
|
max_length=100, blank=True, help_text="CSS classes for styling"
|
|
@@ -1490,6 +1498,21 @@ class NotificationRule(models.Model):
|
|
|
1490
1498
|
default="",
|
|
1491
1499
|
help_text="Comma-separated fixed email addresses.",
|
|
1492
1500
|
)
|
|
1501
|
+
cc_email_field = models.CharField(
|
|
1502
|
+
max_length=200,
|
|
1503
|
+
blank=True,
|
|
1504
|
+
default="",
|
|
1505
|
+
help_text=(
|
|
1506
|
+
"Form field slug whose submitted value is a CC email address. "
|
|
1507
|
+
"Resolved from form_data at send time (varies per submission)."
|
|
1508
|
+
),
|
|
1509
|
+
)
|
|
1510
|
+
cc_static_emails = models.CharField(
|
|
1511
|
+
max_length=1000,
|
|
1512
|
+
blank=True,
|
|
1513
|
+
default="",
|
|
1514
|
+
help_text="Comma-separated fixed CC email addresses.",
|
|
1515
|
+
)
|
|
1493
1516
|
notify_stage_assignees = models.BooleanField(
|
|
1494
1517
|
default=False,
|
|
1495
1518
|
help_text=(
|
|
@@ -366,6 +366,7 @@ class FormBuilder {
|
|
|
366
366
|
field_type: fieldType,
|
|
367
367
|
required: false,
|
|
368
368
|
help_text: '',
|
|
369
|
+
show_help_text_in_detail: false,
|
|
369
370
|
placeholder: '',
|
|
370
371
|
width: 'full',
|
|
371
372
|
css_class: '',
|
|
@@ -794,6 +795,12 @@ class FormBuilder {
|
|
|
794
795
|
<div class="col-12">
|
|
795
796
|
<label class="form-label">Help Text</label>
|
|
796
797
|
<input type="text" class="form-control" id="propHelpText" value="${this.escapeHtml(field.help_text)}">
|
|
798
|
+
<div class="form-check mt-1">
|
|
799
|
+
<input class="form-check-input" type="checkbox" id="propShowHelpTextInDetail" ${field.show_help_text_in_detail ? 'checked' : ''}>
|
|
800
|
+
<label class="form-check-label small text-muted" for="propShowHelpTextInDetail">
|
|
801
|
+
Also show this help text next to the value in the submission/approval detail view (use for attestation / consent statements on initials fields).
|
|
802
|
+
</label>
|
|
803
|
+
</div>
|
|
797
804
|
</div>
|
|
798
805
|
<div class="col-12">
|
|
799
806
|
<label class="form-label">Placeholder</label>
|
|
@@ -1393,6 +1400,7 @@ class FormBuilder {
|
|
|
1393
1400
|
field_label: this.getDefaultLabel(fieldType),
|
|
1394
1401
|
required: false,
|
|
1395
1402
|
help_text: '',
|
|
1403
|
+
show_help_text_in_detail: false,
|
|
1396
1404
|
placeholder: '',
|
|
1397
1405
|
choices: '',
|
|
1398
1406
|
width: 'full',
|
|
@@ -1670,6 +1678,8 @@ class FormBuilder {
|
|
|
1670
1678
|
field.field_name = document.getElementById('propFieldName').value;
|
|
1671
1679
|
field.required = document.getElementById('propRequired').checked;
|
|
1672
1680
|
field.help_text = document.getElementById('propHelpText').value;
|
|
1681
|
+
const showHelpCheckbox = document.getElementById('propShowHelpTextInDetail');
|
|
1682
|
+
field.show_help_text_in_detail = showHelpCheckbox ? showHelpCheckbox.checked : false;
|
|
1673
1683
|
field.placeholder = document.getElementById('propPlaceholder').value;
|
|
1674
1684
|
field.width = document.getElementById('propWidth').value;
|
|
1675
1685
|
field.css_class = document.getElementById('propCssClass').value;
|
{django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/sync_api.py
RENAMED
|
@@ -445,6 +445,7 @@ def serialize_form(form_definition):
|
|
|
445
445
|
"submit_groups": _group_names(form_definition.submit_groups),
|
|
446
446
|
"view_groups": _group_names(form_definition.view_groups),
|
|
447
447
|
"admin_groups": _group_names(form_definition.admin_groups),
|
|
448
|
+
"reviewer_groups": _group_names(form_definition.reviewer_groups),
|
|
448
449
|
"allow_save_draft": form_definition.allow_save_draft,
|
|
449
450
|
"allow_withdrawal": form_definition.allow_withdrawal,
|
|
450
451
|
"allow_resubmit": form_definition.allow_resubmit,
|
|
@@ -543,6 +544,7 @@ def build_export_payload(queryset):
|
|
|
543
544
|
"submit_groups",
|
|
544
545
|
"view_groups",
|
|
545
546
|
"admin_groups",
|
|
547
|
+
"reviewer_groups",
|
|
546
548
|
"category",
|
|
547
549
|
"category__allowed_groups",
|
|
548
550
|
)
|
|
@@ -980,6 +982,9 @@ def import_form(form_data, conflict="update", category_cache=None):
|
|
|
980
982
|
form_obj.admin_groups.set(
|
|
981
983
|
[_get_or_create_group(n) for n in fd.get("admin_groups", [])]
|
|
982
984
|
)
|
|
985
|
+
form_obj.reviewer_groups.set(
|
|
986
|
+
[_get_or_create_group(n) for n in fd.get("reviewer_groups", [])]
|
|
987
|
+
)
|
|
983
988
|
|
|
984
989
|
# ── Workflows (created BEFORE fields so stage FKs can be resolved) ─────────
|
|
985
990
|
wf_data = form_data.get("workflow")
|
{django_forms_workflows-0.72.4 → django_forms_workflows-0.74.0}/django_forms_workflows/tasks.py
RENAMED
|
@@ -99,8 +99,10 @@ def _send_html_email(
|
|
|
99
99
|
*,
|
|
100
100
|
notification_type: str = "other",
|
|
101
101
|
submission_id: int | None = None,
|
|
102
|
+
cc: Iterable[str] | None = None,
|
|
102
103
|
) -> None:
|
|
103
104
|
to_list = [e for e in to if e]
|
|
105
|
+
cc_list = [e for e in (cc or []) if e and e not in to_list]
|
|
104
106
|
if not to_list:
|
|
105
107
|
logger.info("Skipping email '%s' (no recipients)", subject)
|
|
106
108
|
_write_notification_log(
|
|
@@ -122,12 +124,21 @@ def _send_html_email(
|
|
|
122
124
|
)
|
|
123
125
|
|
|
124
126
|
msg = EmailMultiAlternatives(
|
|
125
|
-
subject=subject,
|
|
127
|
+
subject=subject,
|
|
128
|
+
body=text_body,
|
|
129
|
+
from_email=from_addr,
|
|
130
|
+
to=to_list,
|
|
131
|
+
cc=cc_list or None,
|
|
126
132
|
)
|
|
127
133
|
msg.attach_alternative(html_body, "text/html")
|
|
128
134
|
try:
|
|
129
135
|
msg.send(fail_silently=False)
|
|
130
|
-
logger.info(
|
|
136
|
+
logger.info(
|
|
137
|
+
"Sent email '%s' to %s%s",
|
|
138
|
+
subject,
|
|
139
|
+
to_list,
|
|
140
|
+
f" cc={cc_list}" if cc_list else "",
|
|
141
|
+
)
|
|
131
142
|
for recipient in to_list:
|
|
132
143
|
_write_notification_log(
|
|
133
144
|
notification_type=notification_type,
|
|
@@ -136,6 +147,14 @@ def _send_html_email(
|
|
|
136
147
|
subject=subject,
|
|
137
148
|
status="sent",
|
|
138
149
|
)
|
|
150
|
+
for recipient in cc_list:
|
|
151
|
+
_write_notification_log(
|
|
152
|
+
notification_type=notification_type,
|
|
153
|
+
submission_id=submission_id,
|
|
154
|
+
recipient_email=f"cc:{recipient}",
|
|
155
|
+
subject=subject,
|
|
156
|
+
status="sent",
|
|
157
|
+
)
|
|
139
158
|
except Exception as e: # pragma: no cover
|
|
140
159
|
logger.exception("Failed sending email '%s' to %s: %s", subject, to_list, e)
|
|
141
160
|
for recipient in to_list:
|
|
@@ -147,6 +166,15 @@ def _send_html_email(
|
|
|
147
166
|
status="failed",
|
|
148
167
|
error_message=str(e),
|
|
149
168
|
)
|
|
169
|
+
for recipient in cc_list:
|
|
170
|
+
_write_notification_log(
|
|
171
|
+
notification_type=notification_type,
|
|
172
|
+
submission_id=submission_id,
|
|
173
|
+
recipient_email=f"cc:{recipient}",
|
|
174
|
+
subject=subject,
|
|
175
|
+
status="failed",
|
|
176
|
+
error_message=str(e),
|
|
177
|
+
)
|
|
150
178
|
|
|
151
179
|
|
|
152
180
|
def _send_html_email_from_string(
|
|
@@ -158,6 +186,7 @@ def _send_html_email_from_string(
|
|
|
158
186
|
*,
|
|
159
187
|
notification_type: str = "other",
|
|
160
188
|
submission_id: int | None = None,
|
|
189
|
+
cc: Iterable[str] | None = None,
|
|
161
190
|
) -> None:
|
|
162
191
|
"""Like _send_html_email but renders an inline Django template string
|
|
163
192
|
instead of loading a template file. Used when a NotificationRule has a
|
|
@@ -166,6 +195,7 @@ def _send_html_email_from_string(
|
|
|
166
195
|
from django.template import Context, Template
|
|
167
196
|
|
|
168
197
|
to_list = [e for e in to if e]
|
|
198
|
+
cc_list = [e for e in (cc or []) if e and e not in to_list]
|
|
169
199
|
if not to_list:
|
|
170
200
|
logger.info("Skipping email '%s' (no recipients)", subject)
|
|
171
201
|
_write_notification_log(
|
|
@@ -187,12 +217,21 @@ def _send_html_email_from_string(
|
|
|
187
217
|
)
|
|
188
218
|
|
|
189
219
|
msg = EmailMultiAlternatives(
|
|
190
|
-
subject=subject,
|
|
220
|
+
subject=subject,
|
|
221
|
+
body=text_body,
|
|
222
|
+
from_email=from_addr,
|
|
223
|
+
to=to_list,
|
|
224
|
+
cc=cc_list or None,
|
|
191
225
|
)
|
|
192
226
|
msg.attach_alternative(html_body, "text/html")
|
|
193
227
|
try:
|
|
194
228
|
msg.send(fail_silently=False)
|
|
195
|
-
logger.info(
|
|
229
|
+
logger.info(
|
|
230
|
+
"Sent email '%s' to %s%s",
|
|
231
|
+
subject,
|
|
232
|
+
to_list,
|
|
233
|
+
f" cc={cc_list}" if cc_list else "",
|
|
234
|
+
)
|
|
196
235
|
for recipient in to_list:
|
|
197
236
|
_write_notification_log(
|
|
198
237
|
notification_type=notification_type,
|
|
@@ -201,6 +240,14 @@ def _send_html_email_from_string(
|
|
|
201
240
|
subject=subject,
|
|
202
241
|
status="sent",
|
|
203
242
|
)
|
|
243
|
+
for recipient in cc_list:
|
|
244
|
+
_write_notification_log(
|
|
245
|
+
notification_type=notification_type,
|
|
246
|
+
submission_id=submission_id,
|
|
247
|
+
recipient_email=f"cc:{recipient}",
|
|
248
|
+
subject=subject,
|
|
249
|
+
status="sent",
|
|
250
|
+
)
|
|
204
251
|
except Exception as e: # pragma: no cover
|
|
205
252
|
logger.exception("Failed sending email '%s' to %s: %s", subject, to_list, e)
|
|
206
253
|
for recipient in to_list:
|
|
@@ -212,6 +259,15 @@ def _send_html_email_from_string(
|
|
|
212
259
|
status="failed",
|
|
213
260
|
error_message=str(e),
|
|
214
261
|
)
|
|
262
|
+
for recipient in cc_list:
|
|
263
|
+
_write_notification_log(
|
|
264
|
+
notification_type=notification_type,
|
|
265
|
+
submission_id=submission_id,
|
|
266
|
+
recipient_email=f"cc:{recipient}",
|
|
267
|
+
subject=subject,
|
|
268
|
+
status="failed",
|
|
269
|
+
error_message=str(e),
|
|
270
|
+
)
|
|
215
271
|
|
|
216
272
|
|
|
217
273
|
def _write_notification_log(
|
|
@@ -1167,6 +1223,31 @@ def _collect_notification_recipients(
|
|
|
1167
1223
|
return recipients
|
|
1168
1224
|
|
|
1169
1225
|
|
|
1226
|
+
def _collect_notification_cc(notif, form_data: dict) -> list[str]:
|
|
1227
|
+
"""Return the deduplicated list of CC emails for a notification rule.
|
|
1228
|
+
|
|
1229
|
+
Combines static and dynamic CC sources:
|
|
1230
|
+
|
|
1231
|
+
1. ``cc_email_field`` → form_data[slug] (dynamic per submission)
|
|
1232
|
+
2. ``cc_static_emails`` → comma-separated fixed addresses
|
|
1233
|
+
"""
|
|
1234
|
+
cc: list[str] = []
|
|
1235
|
+
|
|
1236
|
+
def _add(email: str | None) -> None:
|
|
1237
|
+
if email and email not in cc:
|
|
1238
|
+
cc.append(email)
|
|
1239
|
+
|
|
1240
|
+
if getattr(notif, "cc_email_field", ""):
|
|
1241
|
+
_add(_get_form_field_email(form_data, notif.cc_email_field))
|
|
1242
|
+
|
|
1243
|
+
for addr in (getattr(notif, "cc_static_emails", "") or "").split(","):
|
|
1244
|
+
addr = addr.strip()
|
|
1245
|
+
if addr and "@" in addr:
|
|
1246
|
+
_add(addr)
|
|
1247
|
+
|
|
1248
|
+
return cc
|
|
1249
|
+
|
|
1250
|
+
|
|
1170
1251
|
def _build_form_field_notification_context(
|
|
1171
1252
|
submission: FormSubmission,
|
|
1172
1253
|
task: ApprovalTask | None = None,
|
|
@@ -1304,6 +1385,9 @@ def send_notification_rules(
|
|
|
1304
1385
|
recipients = _collect_notification_recipients(
|
|
1305
1386
|
rule, form_data, submission=submission, task=task
|
|
1306
1387
|
)
|
|
1388
|
+
cc_recipients = _collect_notification_cc(rule, form_data)
|
|
1389
|
+
# Prevent duplicate delivery: don't CC anyone already in the TO list.
|
|
1390
|
+
cc_recipients = [c for c in cc_recipients if c not in recipients]
|
|
1307
1391
|
if not recipients:
|
|
1308
1392
|
logger.info(
|
|
1309
1393
|
"NotificationRule %s: no recipients resolved for submission %s; skipping.",
|
|
@@ -1321,7 +1405,7 @@ def send_notification_rules(
|
|
|
1321
1405
|
.objects.filter(
|
|
1322
1406
|
notification_preferences__rule_id=rule.id,
|
|
1323
1407
|
notification_preferences__muted=True,
|
|
1324
|
-
email__in=recipients,
|
|
1408
|
+
email__in=list(recipients) + list(cc_recipients),
|
|
1325
1409
|
)
|
|
1326
1410
|
.exclude(email="")
|
|
1327
1411
|
.values_list("email", flat=True)
|
|
@@ -1329,6 +1413,7 @@ def send_notification_rules(
|
|
|
1329
1413
|
if _muted_emails:
|
|
1330
1414
|
before = len(recipients)
|
|
1331
1415
|
recipients = [r for r in recipients if r not in _muted_emails]
|
|
1416
|
+
cc_recipients = [c for c in cc_recipients if c not in _muted_emails]
|
|
1332
1417
|
logger.info(
|
|
1333
1418
|
"NotificationRule %s: dropped %d muted recipient(s) for submission %s",
|
|
1334
1419
|
rule.id,
|
|
@@ -1389,7 +1474,11 @@ def send_notification_rules(
|
|
|
1389
1474
|
)
|
|
1390
1475
|
if cadence != "immediate" and workflow is not None:
|
|
1391
1476
|
scheduled_for = _compute_scheduled_for(workflow, submission)
|
|
1392
|
-
|
|
1477
|
+
# In batched/digest mode we don't carry the TO/CC distinction —
|
|
1478
|
+
# each recipient just gets the summary email. Queue CCs alongside
|
|
1479
|
+
# TOs so they still receive the digest.
|
|
1480
|
+
queued_emails = list(recipients) + list(cc_recipients)
|
|
1481
|
+
for recipient in queued_emails:
|
|
1393
1482
|
PendingNotification.objects.create(
|
|
1394
1483
|
workflow=workflow,
|
|
1395
1484
|
notification_type=event,
|
|
@@ -1400,7 +1489,7 @@ def send_notification_rules(
|
|
|
1400
1489
|
)
|
|
1401
1490
|
logger.info(
|
|
1402
1491
|
"Queued %d %s notification(s) for submission %s (due %s)",
|
|
1403
|
-
len(
|
|
1492
|
+
len(queued_emails),
|
|
1404
1493
|
event,
|
|
1405
1494
|
submission.id,
|
|
1406
1495
|
scheduled_for,
|
|
@@ -1408,11 +1497,17 @@ def send_notification_rules(
|
|
|
1408
1497
|
else:
|
|
1409
1498
|
# Determine template: per-rule body_template overrides file.
|
|
1410
1499
|
rule_body = getattr(rule, "body_template", "") or ""
|
|
1500
|
+
# Each TO recipient gets a personalized email (approver context).
|
|
1501
|
+
# CCs are attached to the first email only so CC recipients receive
|
|
1502
|
+
# exactly one copy per rule execution rather than one per TO.
|
|
1503
|
+
cc_pending = list(cc_recipients)
|
|
1411
1504
|
for recipient in recipients:
|
|
1412
1505
|
ctx = {**base_context}
|
|
1413
1506
|
recipient_user = _users_by_email.get(recipient)
|
|
1414
1507
|
if recipient_user:
|
|
1415
1508
|
ctx["approver"] = recipient_user
|
|
1509
|
+
email_cc = cc_pending
|
|
1510
|
+
cc_pending = []
|
|
1416
1511
|
if rule_body.strip():
|
|
1417
1512
|
_send_html_email_from_string(
|
|
1418
1513
|
subject,
|
|
@@ -1421,6 +1516,7 @@ def send_notification_rules(
|
|
|
1421
1516
|
ctx,
|
|
1422
1517
|
notification_type=event,
|
|
1423
1518
|
submission_id=submission_id,
|
|
1519
|
+
cc=email_cc,
|
|
1424
1520
|
)
|
|
1425
1521
|
else:
|
|
1426
1522
|
_send_html_email(
|
|
@@ -1430,4 +1526,5 @@ def send_notification_rules(
|
|
|
1430
1526
|
ctx,
|
|
1431
1527
|
notification_type=event,
|
|
1432
1528
|
submission_id=submission_id,
|
|
1529
|
+
cc=email_cc,
|
|
1433
1530
|
)
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
{% for field in row.fields %}
|
|
22
22
|
<div class="{% if row.type == 'pair' %}col-md-6{% elif row.type == 'triple' %}col-md-4{% else %}col-md-3{% endif %} px-3 py-2{% if not forloop.last %} border-end{% endif %}">
|
|
23
23
|
<small class="text-muted fw-semibold">{{ field.label }}</small>
|
|
24
|
+
{% if field.help_text %}<div class="small text-secondary fst-italic mb-1">{{ field.help_text }}</div>{% endif %}
|
|
24
25
|
<div>{% include "django_forms_workflows/_field_value.html" with entry=field %}</div>
|
|
25
26
|
</div>
|
|
26
27
|
{% endfor %}
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
<tr>
|
|
33
34
|
<td colspan="2" class="px-3 py-2">
|
|
34
35
|
<small class="text-muted fw-semibold">{{ field.label }}</small>
|
|
36
|
+
{% if field.help_text %}<div class="small text-secondary fst-italic mb-1">{{ field.help_text }}</div>{% endif %}
|
|
35
37
|
<div>{% include "django_forms_workflows/_field_value.html" with entry=field %}</div>
|
|
36
38
|
</td>
|
|
37
39
|
</tr>
|