django-forms-workflows 0.77.4__tar.gz → 0.78.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/CHANGELOG.md +24 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/PKG-INFO +12 -7
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/README.md +5 -3
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/admin.py +23 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/api_views.py +1 -1
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/apps.py +1 -1
- django_forms_workflows-0.78.0/django_forms_workflows/checks.py +24 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/diff_views.py +2 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/form_builder_views.py +14 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/forms.py +6 -25
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/models.py +3 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/payments/__init__.py +8 -1
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/payments/base.py +24 -0
- django_forms_workflows-0.78.0/django_forms_workflows/payments/checkout.py +84 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/payments/stripe_provider.py +38 -13
- django_forms_workflows-0.78.0/django_forms_workflows/payments/views.py +484 -0
- django_forms_workflows-0.78.0/django_forms_workflows/pdf.py +53 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/reporting_views.py +44 -19
- django_forms_workflows-0.78.0/django_forms_workflows/security.py +108 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/sso_backends.py +30 -67
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/sso_views.py +8 -75
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/admin/django_forms_workflows/workflow_builder.html +2 -2
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/payment_collect.html +8 -2
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/payment_error.html +6 -3
- django_forms_workflows-0.78.0/django_forms_workflows/uploads.py +104 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/utils.py +43 -31
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/views.py +66 -108
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/workflow_builder_views.py +17 -6
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/pyproject.toml +11 -5
- django_forms_workflows-0.77.4/django_forms_workflows/payments/views.py +0 -358
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/LICENSE +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/__init__.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/api_urls.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/callback_registry.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/conditions.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/context_processors.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/data_sources/__init__.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/data_sources/base.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/data_sources/database_source.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/data_sources/ldap_source.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/data_sources/user_source.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/email_backends/__init__.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/email_backends/gmail_api.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/form_builder_urls.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/handlers/__init__.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/handlers/api_handler.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/handlers/base.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/handlers/database_handler.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/handlers/email_handler.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/handlers/executor.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/handlers/file_handler.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/handlers/ldap_handler.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/history.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/ldap_backend.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/management/commands/create_default_templates.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/management/commands/pull_forms.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/management/commands/push_forms.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/management/commands/seed_farm_demo.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/management/commands/seed_pcn_form.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/management/commands/seed_prefill_sources.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/management/commands/sync_ldap_profiles.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/management/commands/sync_ldap_users.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/management/commands/test_db_connection.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0001_initial.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0002_prefillsource_alter_formfield_prefill_source_and_more.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0003_postsubmissionaction.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0004_formtemplate.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0005_advanced_conditional_logic.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0006_workflowdefinition_visual_workflow_data_and_more.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0007_add_userprofile_ldap_enhancements.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0008_add_file_workflow_models.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0009_add_formfield_readonly.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0010_add_prefillsource_template_fields.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0011_postsubmissionaction_email_body_template_and_more.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0012_add_approval_step_fields.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0013_add_database_query_key.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0014_add_form_category.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0015_add_workflowstage_staged_approvals.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0016_add_pdf_generation.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0017_add_notification_batching.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0018_add_form_category_parent.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0019_add_ldap_group_profile.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0020_add_bulk_export_field.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0021_alter_pendingnotification_scheduled_for_and_more.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0022_add_bulk_pdf_export.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0023_add_performance_indexes.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0024_add_allow_resubmit.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0025_workflowstage_approve_label.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0026_alter_formfield_width.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0027_formsubmission_form_data_gin.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0028_workflowstagegroupconfig.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0029_formfield_workflow_stage_fk_drop_groupconfig.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0030_formfield_phone_type.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0031_sub_workflows.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0032_approved_pending_detached_sub_workflow.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0033_add_reject_parent_to_subworkflowdefinition.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0034_add_notificationlog.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0035_migrate_prefill_source_strings.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0036_add_workflowstage_auto_created.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0037_migrate_flat_workflows_to_staged.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0038_remove_legacy_prefill_source_and_approval_step.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0039_update_help_text.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0040_migrate_deprecated_features.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0041_remove_deprecated_fields.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0042_add_sub_workflow_section_label.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0043_fix_form_data_decimal_encoding.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0044_remove_subworkflowinstance_label.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0045_stageapprovalgroup_workflow_name_label.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0046_workflow_definition_one_to_many.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0047_add_hide_approval_history.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0048_workflowdefinition_trigger_conditions_and_more.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0049_formfield_multifile_type.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0050_workflowdefinition_collapse_parallel_stages.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0051_formdefinition_is_listed.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0052_dynamic_assignee_and_form_field_notifications.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0053_stageformfieldnotification_static_emails.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0054_rename_approved_pending_to_pending_approval.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0055_add_send_back.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0056_calculated_formula_spreadsheet.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0057_add_currency_field_type.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0058_rename_assignee_email_field_add_lookup_type.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0059_add_multiselect_list_field_type.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0060_alter_workflowstage_assignee_lookup_type_helptext.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0061_add_country_us_state_field_types.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0062_add_batch_import_to_formdefinition.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0063_formdefinition_api_enabled_apitoken.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0064_add_reviewer_groups.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0065_workflownotification.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0066_add_notify_submitter_to_workflownotification.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0067_migrate_legacy_notify_flags.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0068_drop_legacy_notify_fields.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0069_add_signature_field_type.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0070_workflowstage_allow_edit_form_data.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0071_alter_pendingnotification_notification_type.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0072_add_change_history.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0073_add_stage_notify_assignee_on_final_decision.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0074_add_notification_rule.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0075_migrate_to_notification_rules.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0076_update_notification_event_choices.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0077_drop_legacy_notification_models.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0078_allow_anonymous_submissions.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0079_webhookendpoint_webhookdeliverylog.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0080_add_rating_matrix_address_slider_fields_and_form_controls.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0081_add_success_page_fields.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0082_add_document_template_model.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0083_workflowdefinition_start_trigger.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0084_add_shared_option_list.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0085_add_payment_system.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0086_add_embed_enabled.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0087_notificationrule_use_triggering_stage.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0088_notificationrule_body_template_and_events.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0089_workflowstage_hide_comment_field.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0090_alter_stageapprovalgroup_options_and_more.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0091_add_uuid_fields.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0092_add_user_notification_preference.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0093_normalize_conditional_operators.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0094_formfield_show_help_text_in_detail.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0095_notificationrule_cc_fields.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0096_notificationlog_cc_bcc_emails.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/0097_notificationlog_delivery_reconciliation.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/migrations/__init__.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/payments/registry.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/signals.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/sso_urls.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/api/swagger-ui-bundle.js +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/api/swagger-ui.css +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/css/forms.css +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/admin_notification_rule_stage_filter.js +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/dfw-embed.js +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/form-builder-api.js +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/form-builder-canvas.js +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/form-builder-history.js +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/form-builder-property-editor.js +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/form-builder-store.js +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/form-builder.js +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/form-enhancements.js +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/payment-stripe.js +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/signature-pad.js +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/workflow-builder-api.js +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/workflow-builder-store.js +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/static/django_forms_workflows/js/workflow-builder.js +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/sync_api.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/sync_views.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/tasks.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/admin/django_forms_workflows/_sync_diff.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/admin/django_forms_workflows/diff_forms.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/admin/django_forms_workflows/form_builder.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/admin/django_forms_workflows/formdef_change_form.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/admin/django_forms_workflows/formdefinition/change_list.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/admin/django_forms_workflows/sync_import.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/admin/django_forms_workflows/sync_pull.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/admin/django_forms_workflows/sync_push.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/_category_node.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/_field_value.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/_form_data_rows.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/analytics_dashboard.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/api/docs.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/approval_inbox.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/approve.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/base.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/batch_import_result.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/completed_approvals.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/discard_draft_confirm.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/embed_base.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/embed_success.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/form_categories.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/form_embed.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/form_list.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/form_submit.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/my_submissions.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/notification_preferences.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/public_submission_confirmation.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/rate_limited.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/reassign_task.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/resubmit_confirm.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/sso/login.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/sub_workflow_detail.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/submission_bulk_pdf.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/submission_detail.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/submission_pdf.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/submission_success.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/django_forms_workflows/withdraw_confirm.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/emails/approval_notification.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/emails/approval_reminder.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/emails/approval_request.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/emails/email_styles.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/emails/escalation_notification.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/emails/notification_digest.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/emails/rejection_notification.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/emails/submission_notification.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templates/registration/login.html +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templatetags/__init__.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/templatetags/forms_workflows_tags.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/urls.py +0 -0
- {django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/workflow_engine.py +0 -0
|
@@ -8,6 +8,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
10
|
### Security
|
|
11
|
+
- **Payment endpoints hardened against unauthorized access and unsafe state
|
|
12
|
+
transitions (issue #27, supersedes #4).** All checkout endpoints
|
|
13
|
+
(`initiate`, `confirm`, `return`, `cancel`) now enforce one access policy
|
|
14
|
+
before touching records or providers: an authenticated submitter may act
|
|
15
|
+
only on their own submission, and an anonymous public-form submitter needs
|
|
16
|
+
a short-lived (`FORMS_WORKFLOWS_PAYMENT_CHECKOUT_TTL`, default 1 hour),
|
|
17
|
+
unguessable session-bound checkout credential issued when the form post
|
|
18
|
+
enters `pending_payment`. Guessing a numeric submission or payment-record
|
|
19
|
+
ID now yields a non-disclosing 404. Payment initiation is idempotent per
|
|
20
|
+
attempt — repeated GETs reuse the single open `PaymentRecord` and its
|
|
21
|
+
stable idempotency key (now forwarded to Stripe's `PaymentIntent.create`)
|
|
22
|
+
instead of creating uncontrolled attempts; a legitimate retry after
|
|
23
|
+
cancel/failure starts a fresh key. Cancellation is a CSRF-protected
|
|
24
|
+
`POST`-only action restricted to `pending_payment` submissions and can no
|
|
25
|
+
longer revert a completed/submitted/approved submission to `draft`.
|
|
26
|
+
Stripe webhooks now **fail closed**: events are rejected (HTTP 400) when
|
|
27
|
+
`STRIPE_WEBHOOK_SECRET` is unset or the signature is invalid, and the
|
|
28
|
+
provider interface requires the same authenticated-event guarantee from
|
|
29
|
+
all providers via `WebhookVerificationError`. Replayed or out-of-order
|
|
30
|
+
events no longer roll payment state backward (`completed`/`refunded` are
|
|
31
|
+
terminal; only `completed → refunded` is allowed) and duplicate deliveries
|
|
32
|
+
create no duplicate workflow tasks. `payment_collect.html` and
|
|
33
|
+
`payment_error.html` cancel actions are now POST forms.
|
|
34
|
+
|
|
11
35
|
- **`social-auth-core` 4.8.3 → 5.1.1 in `poetry.lock`.** Closes five Dependabot
|
|
12
36
|
advisories against the locked version: GHSA-vqg6-3fw6-j9jg (session fixation),
|
|
13
37
|
GHSA-3c93-f73f-qc9h (VK App backend accepted unsigned callback data),
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-forms-workflows
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.78.0
|
|
4
4
|
Summary: Enterprise-grade, database-driven form builder with approval workflows and external data integration
|
|
5
5
|
License: LGPL-3.0-only
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Keywords: django,forms,workflows,approval,form-builder,dynamic-forms,ldap,enterprise,audit,database-driven,sso,saml,oauth2
|
|
8
8
|
Author: Matt Davis
|
|
9
9
|
Author-email: matt@opensensor.io
|
|
10
|
-
Requires-Python: >=3.
|
|
10
|
+
Requires-Python: >=3.11,<4.0
|
|
11
11
|
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.14
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.15
|
|
19
18
|
Provides-Extra: all
|
|
19
|
+
Provides-Extra: cache
|
|
20
20
|
Provides-Extra: dev
|
|
21
21
|
Provides-Extra: docs
|
|
22
22
|
Provides-Extra: excel
|
|
@@ -31,7 +31,7 @@ Provides-Extra: postgresql
|
|
|
31
31
|
Provides-Extra: qr
|
|
32
32
|
Provides-Extra: saml
|
|
33
33
|
Provides-Extra: sso
|
|
34
|
-
Requires-Dist: Django (>=5.
|
|
34
|
+
Requires-Dist: Django (>=5.2.18,<7.0,!=6.0.0,!=6.0.1,!=6.0.2,!=6.0.3,!=6.0.4,!=6.0.5,!=6.0.6,!=6.0.7,!=6.0.8,!=6.1.0,!=6.1.1)
|
|
35
35
|
Requires-Dist: celery (>=5.3)
|
|
36
36
|
Requires-Dist: crispy-bootstrap5 (>=2.0)
|
|
37
37
|
Requires-Dist: django-auth-ldap (>=4.0) ; extra == "ldap" or extra == "all"
|
|
@@ -44,7 +44,9 @@ Requires-Dist: google-auth (>=2.20) ; extra == "gmail" or extra == "all"
|
|
|
44
44
|
Requires-Dist: markdown (>=3.4) ; extra == "markdown" or extra == "all"
|
|
45
45
|
Requires-Dist: mssql-django (>=1.3) ; extra == "mssql" or extra == "all"
|
|
46
46
|
Requires-Dist: mysqlclient (>=2.2) ; extra == "mysql" or extra == "all"
|
|
47
|
+
Requires-Dist: nh3 (>=0.3.2)
|
|
47
48
|
Requires-Dist: openpyxl (>=3.1) ; extra == "excel" or extra == "all"
|
|
49
|
+
Requires-Dist: pillow (>=13.0.0) ; extra == "pdf" or extra == "all"
|
|
48
50
|
Requires-Dist: psycopg2-binary (>=2.9) ; extra == "postgresql" or extra == "all"
|
|
49
51
|
Requires-Dist: pyodbc (>=5.0) ; extra == "mssql" or extra == "all"
|
|
50
52
|
Requires-Dist: pytest (>=7.4) ; extra == "dev"
|
|
@@ -53,6 +55,7 @@ Requires-Dist: pytest-django (>=4.5) ; extra == "dev"
|
|
|
53
55
|
Requires-Dist: python-decouple (>=3.8)
|
|
54
56
|
Requires-Dist: python-ldap (>=3.4) ; extra == "ldap" or extra == "all"
|
|
55
57
|
Requires-Dist: python3-saml (>=1.16) ; extra == "sso" or extra == "saml" or extra == "all"
|
|
58
|
+
Requires-Dist: redis (>=5.0) ; extra == "cache" or extra == "all"
|
|
56
59
|
Requires-Dist: requests (>=2.31)
|
|
57
60
|
Requires-Dist: ruff (>=0.4) ; extra == "dev"
|
|
58
61
|
Requires-Dist: segno (>=1.6) ; extra == "qr" or extra == "all"
|
|
@@ -72,7 +75,7 @@ Description-Content-Type: text/markdown
|
|
|
72
75
|
**Enterprise-grade, database-driven form builder with multi-stage approval workflows, external data integration, and cross-instance sync**
|
|
73
76
|
|
|
74
77
|
[](https://www.gnu.org/licenses/lgpl-3.0)
|
|
75
|
-
[](https://www.python.org/downloads/)
|
|
76
79
|
[](https://www.djangoproject.com/)
|
|
77
80
|
[](https://github.com/opensensor/django-forms-workflows)
|
|
78
81
|
|
|
@@ -258,6 +261,8 @@ Move form definitions between environments from the Django Admin:
|
|
|
258
261
|
|
|
259
262
|
## Quick Start
|
|
260
263
|
|
|
264
|
+
See [security upgrade notes](docs/SECURITY_UPGRADE.md) before upgrading an existing deployment.
|
|
265
|
+
|
|
261
266
|
### Installation
|
|
262
267
|
|
|
263
268
|
```bash
|
|
@@ -413,8 +418,8 @@ graph TB
|
|
|
413
418
|
|
|
414
419
|
## Requirements
|
|
415
420
|
|
|
416
|
-
- Python 3.
|
|
417
|
-
- Django 5.2+
|
|
421
|
+
- Python 3.11+
|
|
422
|
+
- Django 5.2.18+, 6.0.9+, or 6.1.2+ (security patch minimums)
|
|
418
423
|
- PostgreSQL, MySQL, or SQLite (PostgreSQL recommended for production)
|
|
419
424
|
- Optional: Celery 5.0+ with Redis/Valkey for background task processing
|
|
420
425
|
- Optional: `openpyxl` for Excel spreadsheet field support (`pip install django-forms-workflows[excel]`)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
**Enterprise-grade, database-driven form builder with multi-stage approval workflows, external data integration, and cross-instance sync**
|
|
4
4
|
|
|
5
5
|
[](https://www.gnu.org/licenses/lgpl-3.0)
|
|
6
|
-
[](https://www.python.org/downloads/)
|
|
7
7
|
[](https://www.djangoproject.com/)
|
|
8
8
|
[](https://github.com/opensensor/django-forms-workflows)
|
|
9
9
|
|
|
@@ -189,6 +189,8 @@ Move form definitions between environments from the Django Admin:
|
|
|
189
189
|
|
|
190
190
|
## Quick Start
|
|
191
191
|
|
|
192
|
+
See [security upgrade notes](docs/SECURITY_UPGRADE.md) before upgrading an existing deployment.
|
|
193
|
+
|
|
192
194
|
### Installation
|
|
193
195
|
|
|
194
196
|
```bash
|
|
@@ -344,8 +346,8 @@ graph TB
|
|
|
344
346
|
|
|
345
347
|
## Requirements
|
|
346
348
|
|
|
347
|
-
- Python 3.
|
|
348
|
-
- Django 5.2+
|
|
349
|
+
- Python 3.11+
|
|
350
|
+
- Django 5.2.18+, 6.0.9+, or 6.1.2+ (security patch minimums)
|
|
349
351
|
- PostgreSQL, MySQL, or SQLite (PostgreSQL recommended for production)
|
|
350
352
|
- Optional: Celery 5.0+ with Redis/Valkey for background task processing
|
|
351
353
|
- Optional: `openpyxl` for Excel spreadsheet field support (`pip install django-forms-workflows[excel]`)
|
{django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/admin.py
RENAMED
|
@@ -1154,6 +1154,15 @@ class FormDefinitionAdmin(nested_admin.NestedModelAdmin):
|
|
|
1154
1154
|
|
|
1155
1155
|
def sync_import_admin_view(self, request):
|
|
1156
1156
|
"""Admin page for importing form definitions from a JSON file or raw JSON."""
|
|
1157
|
+
from django.core.exceptions import PermissionDenied
|
|
1158
|
+
|
|
1159
|
+
if not self.has_view_permission(request):
|
|
1160
|
+
raise PermissionDenied
|
|
1161
|
+
if not (
|
|
1162
|
+
self.has_add_permission(request) and self.has_change_permission(request)
|
|
1163
|
+
):
|
|
1164
|
+
raise PermissionDenied
|
|
1165
|
+
|
|
1157
1166
|
from .sync_api import import_payload
|
|
1158
1167
|
|
|
1159
1168
|
context = dict(self.admin_site.each_context(request))
|
|
@@ -1219,6 +1228,15 @@ class FormDefinitionAdmin(nested_admin.NestedModelAdmin):
|
|
|
1219
1228
|
Step 1 (POST) – fetch available forms from the remote, show checkbox list
|
|
1220
1229
|
Step 2 (POST) – import selected forms, show results
|
|
1221
1230
|
"""
|
|
1231
|
+
from django.core.exceptions import PermissionDenied
|
|
1232
|
+
|
|
1233
|
+
if not self.has_view_permission(request):
|
|
1234
|
+
raise PermissionDenied
|
|
1235
|
+
if not (
|
|
1236
|
+
self.has_add_permission(request) and self.has_change_permission(request)
|
|
1237
|
+
):
|
|
1238
|
+
raise PermissionDenied
|
|
1239
|
+
|
|
1222
1240
|
from .sync_api import (
|
|
1223
1241
|
build_export_payload,
|
|
1224
1242
|
fetch_remote_payload,
|
|
@@ -1372,6 +1390,11 @@ class FormDefinitionAdmin(nested_admin.NestedModelAdmin):
|
|
|
1372
1390
|
Step 1 (POST) – show all local forms with checkboxes + diff status
|
|
1373
1391
|
Step 2 (POST) – execute push for selected forms, show results
|
|
1374
1392
|
"""
|
|
1393
|
+
from django.core.exceptions import PermissionDenied
|
|
1394
|
+
|
|
1395
|
+
if not self.has_view_permission(request):
|
|
1396
|
+
raise PermissionDenied
|
|
1397
|
+
|
|
1375
1398
|
from .sync_api import (
|
|
1376
1399
|
build_export_payload,
|
|
1377
1400
|
fetch_remote_payload,
|
{django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/api_views.py
RENAMED
|
@@ -106,7 +106,7 @@ def require_api_token(view_func):
|
|
|
106
106
|
raw = auth.removeprefix("Bearer ").strip()
|
|
107
107
|
try:
|
|
108
108
|
token_obj = APIToken.objects.select_related("user").get(
|
|
109
|
-
token=raw, is_active=True
|
|
109
|
+
token=raw, is_active=True, user__is_active=True
|
|
110
110
|
)
|
|
111
111
|
except (APIToken.DoesNotExist, ValueError, Exception): # noqa: BLE001
|
|
112
112
|
return JsonResponse({"error": "Invalid or inactive token."}, status=401)
|
{django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/apps.py
RENAMED
|
@@ -17,7 +17,7 @@ class DjangoFormsWorkflowsConfig(AppConfig):
|
|
|
17
17
|
Import signal handlers and perform app initialization.
|
|
18
18
|
"""
|
|
19
19
|
# Import signals to register handlers
|
|
20
|
-
from . import signals # noqa: F401
|
|
20
|
+
from . import checks, signals # noqa: F401
|
|
21
21
|
|
|
22
22
|
# Load custom callback handlers from FORMS_WORKFLOWS_CALLBACKS setting
|
|
23
23
|
from .callback_registry import load_from_settings
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Deployment checks for public submission throttling."""
|
|
2
|
+
|
|
3
|
+
from django.conf import settings
|
|
4
|
+
from django.core.checks import Tags, Warning, register
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@register(Tags.security, deploy=True)
|
|
8
|
+
def check_rate_limit_cache(app_configs, **kwargs):
|
|
9
|
+
alias = getattr(settings, "FORMS_WORKFLOWS_RATE_LIMIT_CACHE", "default")
|
|
10
|
+
backend = settings.CACHES.get(alias, {}).get("BACKEND", "")
|
|
11
|
+
if backend not in {
|
|
12
|
+
"django.core.cache.backends.redis.RedisCache",
|
|
13
|
+
"django.core.cache.backends.memcached.PyMemcacheCache",
|
|
14
|
+
"django.core.cache.backends.memcached.PyLibMCCache",
|
|
15
|
+
"django_redis.cache.RedisCache",
|
|
16
|
+
}:
|
|
17
|
+
return [
|
|
18
|
+
Warning(
|
|
19
|
+
"Public form rate limiting requires a shared cache with atomic add/incr.",
|
|
20
|
+
hint="Configure Redis or Memcached and FORMS_WORKFLOWS_RATE_LIMIT_CACHE; local-memory caches only limit one process.",
|
|
21
|
+
id="django_forms_workflows.W001",
|
|
22
|
+
)
|
|
23
|
+
]
|
|
24
|
+
return []
|
{django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/diff_views.py
RENAMED
|
@@ -8,6 +8,7 @@ from django.http import HttpResponseBadRequest
|
|
|
8
8
|
from django.shortcuts import render
|
|
9
9
|
|
|
10
10
|
from .models import FormDefinition
|
|
11
|
+
from .security import builder_permission_required
|
|
11
12
|
from .sync_api import build_export_payload
|
|
12
13
|
|
|
13
14
|
|
|
@@ -429,6 +430,7 @@ def _build_summary(forms_data):
|
|
|
429
430
|
return summaries
|
|
430
431
|
|
|
431
432
|
|
|
433
|
+
@builder_permission_required("view_formdefinition")
|
|
432
434
|
def diff_forms_view(request):
|
|
433
435
|
"""Render a side-by-side JSON diff of selected FormDefinitions."""
|
|
434
436
|
pks = request.GET.get("pks", "")
|
|
@@ -26,11 +26,13 @@ from .models import (
|
|
|
26
26
|
WorkflowDefinition,
|
|
27
27
|
WorkflowStage,
|
|
28
28
|
)
|
|
29
|
+
from .security import builder_permission_required
|
|
29
30
|
|
|
30
31
|
logger = logging.getLogger(__name__)
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
@require_GET
|
|
35
|
+
@builder_permission_required("view_formtemplate")
|
|
34
36
|
def form_builder_templates(request):
|
|
35
37
|
"""
|
|
36
38
|
API endpoint to list available form templates.
|
|
@@ -63,6 +65,7 @@ def form_builder_templates(request):
|
|
|
63
65
|
|
|
64
66
|
|
|
65
67
|
@require_GET
|
|
68
|
+
@builder_permission_required("view_formtemplate")
|
|
66
69
|
def form_builder_load_template(request, template_id):
|
|
67
70
|
"""
|
|
68
71
|
API endpoint to load a form template.
|
|
@@ -83,6 +86,7 @@ def form_builder_load_template(request, template_id):
|
|
|
83
86
|
|
|
84
87
|
|
|
85
88
|
@require_POST
|
|
89
|
+
@builder_permission_required("view_formdefinition", "add_formdefinition")
|
|
86
90
|
def form_builder_clone(request, form_id):
|
|
87
91
|
"""
|
|
88
92
|
API endpoint to clone an existing form.
|
|
@@ -225,6 +229,7 @@ def form_builder_clone(request, form_id):
|
|
|
225
229
|
|
|
226
230
|
|
|
227
231
|
@require_GET
|
|
232
|
+
@builder_permission_required("view_formdefinition")
|
|
228
233
|
def form_builder_view(request, form_id=None):
|
|
229
234
|
"""
|
|
230
235
|
Main form builder page.
|
|
@@ -269,6 +274,7 @@ def form_builder_view(request, form_id=None):
|
|
|
269
274
|
|
|
270
275
|
|
|
271
276
|
@require_GET
|
|
277
|
+
@builder_permission_required("view_formdefinition")
|
|
272
278
|
def form_builder_load(request, form_id):
|
|
273
279
|
"""
|
|
274
280
|
API endpoint to load form data as JSON.
|
|
@@ -353,6 +359,7 @@ def form_builder_load(request, form_id):
|
|
|
353
359
|
|
|
354
360
|
|
|
355
361
|
@require_POST
|
|
362
|
+
@builder_permission_required("save_formdefinition")
|
|
356
363
|
def form_builder_save(request):
|
|
357
364
|
"""
|
|
358
365
|
API endpoint to save form data.
|
|
@@ -604,6 +611,7 @@ def save_form_definition_from_builder_data(data, user, form_definition=None):
|
|
|
604
611
|
|
|
605
612
|
|
|
606
613
|
@require_POST
|
|
614
|
+
@builder_permission_required("view_formdefinition")
|
|
607
615
|
def form_builder_preview(request):
|
|
608
616
|
"""
|
|
609
617
|
API endpoint to generate a preview of the form.
|
|
@@ -764,6 +772,7 @@ def form_builder_preview(request):
|
|
|
764
772
|
|
|
765
773
|
|
|
766
774
|
@require_GET
|
|
775
|
+
@builder_permission_required("view_formdefinition", "view_documenttemplate")
|
|
767
776
|
def document_template_list(request, form_id):
|
|
768
777
|
"""List document templates for a form."""
|
|
769
778
|
templates = DocumentTemplate.objects.filter(form_definition_id=form_id).order_by(
|
|
@@ -789,6 +798,7 @@ def document_template_list(request, form_id):
|
|
|
789
798
|
|
|
790
799
|
|
|
791
800
|
@require_POST
|
|
801
|
+
@builder_permission_required("change_formdefinition", "save_documenttemplate")
|
|
792
802
|
def document_template_save(request, form_id):
|
|
793
803
|
"""Create or update a document template."""
|
|
794
804
|
try:
|
|
@@ -847,6 +857,7 @@ def document_template_save(request, form_id):
|
|
|
847
857
|
|
|
848
858
|
|
|
849
859
|
@require_POST
|
|
860
|
+
@builder_permission_required("change_formdefinition", "delete_documenttemplate")
|
|
850
861
|
def document_template_delete(request, form_id, template_id):
|
|
851
862
|
"""Delete a document template."""
|
|
852
863
|
tpl = get_object_or_404(
|
|
@@ -862,6 +873,7 @@ def document_template_delete(request, form_id, template_id):
|
|
|
862
873
|
|
|
863
874
|
|
|
864
875
|
@require_GET
|
|
876
|
+
@builder_permission_required("view_sharedoptionlist")
|
|
865
877
|
def shared_option_list_api(request):
|
|
866
878
|
"""List all shared option lists (for form builder dropdowns)."""
|
|
867
879
|
lists = SharedOptionList.objects.filter(is_active=True).order_by("name")
|
|
@@ -882,6 +894,7 @@ def shared_option_list_api(request):
|
|
|
882
894
|
|
|
883
895
|
|
|
884
896
|
@require_POST
|
|
897
|
+
@builder_permission_required("save_sharedoptionlist")
|
|
885
898
|
def shared_option_list_save(request):
|
|
886
899
|
"""Create or update a shared option list."""
|
|
887
900
|
try:
|
|
@@ -924,6 +937,7 @@ def shared_option_list_save(request):
|
|
|
924
937
|
|
|
925
938
|
|
|
926
939
|
@require_POST
|
|
940
|
+
@builder_permission_required("delete_sharedoptionlist")
|
|
927
941
|
def shared_option_list_delete(request, list_id):
|
|
928
942
|
"""Delete a shared option list."""
|
|
929
943
|
ol = get_object_or_404(SharedOptionList, id=list_id)
|
{django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/forms.py
RENAMED
|
@@ -333,18 +333,6 @@ class DynamicForm(forms.Form):
|
|
|
333
333
|
# files). Also used to carry forward draft / resubmit file data.
|
|
334
334
|
self.stashed_files = stashed_files or {}
|
|
335
335
|
|
|
336
|
-
# Recover stashed-file metadata carried forward via hidden input from
|
|
337
|
-
# a prior validation-failure render (browsers cannot re-send files).
|
|
338
|
-
if not self.stashed_files and self.data:
|
|
339
|
-
prev = self.data.get("_stashed_files")
|
|
340
|
-
if prev:
|
|
341
|
-
try:
|
|
342
|
-
self.stashed_files = json.loads(prev)
|
|
343
|
-
except (json.JSONDecodeError, TypeError):
|
|
344
|
-
# Ignore malformed client metadata; uploaded files are rebuilt
|
|
345
|
-
# from validated server-side data below.
|
|
346
|
-
pass
|
|
347
|
-
|
|
348
336
|
# Build form fields from definition
|
|
349
337
|
# Exclude fields scoped to a workflow stage - those are for approvers only
|
|
350
338
|
for field in (
|
|
@@ -354,15 +342,6 @@ class DynamicForm(forms.Form):
|
|
|
354
342
|
):
|
|
355
343
|
self.add_field(field, initial_data)
|
|
356
344
|
|
|
357
|
-
# Embed stashed-file metadata as a hidden field so it survives
|
|
358
|
-
# validation-failure round-trips (browsers cannot re-send files).
|
|
359
|
-
if self.stashed_files:
|
|
360
|
-
self.fields["_stashed_files"] = forms.CharField(
|
|
361
|
-
widget=forms.HiddenInput(),
|
|
362
|
-
required=False,
|
|
363
|
-
initial=json.dumps(self.stashed_files),
|
|
364
|
-
)
|
|
365
|
-
|
|
366
345
|
# Setup form layout with Crispy Forms
|
|
367
346
|
self.helper = FormHelper()
|
|
368
347
|
self.helper.form_method = "post"
|
|
@@ -763,7 +742,7 @@ class DynamicForm(forms.Form):
|
|
|
763
742
|
# failure, saved with a draft, or carried from a resubmission).
|
|
764
743
|
existing_file = self._get_existing_file_info(field_def.field_name)
|
|
765
744
|
if existing_file:
|
|
766
|
-
fname = existing_file.get("filename", "file")
|
|
745
|
+
fname = html_escape(existing_file.get("filename", "file"))
|
|
767
746
|
extra_help = f"Previously uploaded: <strong>{fname}</strong>. Leave blank to keep it."
|
|
768
747
|
base_help = field_args.get("help_text", "") or ""
|
|
769
748
|
field_args["help_text"] = (
|
|
@@ -789,10 +768,11 @@ class DynamicForm(forms.Form):
|
|
|
789
768
|
if isinstance(existing_file, list)
|
|
790
769
|
else existing_file.get("filename", "file")
|
|
791
770
|
)
|
|
792
|
-
extra = f"Previously uploaded: <strong>{names}</strong>. Leave blank to keep."
|
|
771
|
+
extra = f"Previously uploaded: <strong>{html_escape(names)}</strong>. Leave blank to keep."
|
|
793
772
|
help_text = f"{help_text} {extra}" if help_text else extra
|
|
794
773
|
required = False
|
|
795
774
|
self.fields[field_def.field_name] = MultipleFileField(
|
|
775
|
+
validators=_build_file_validators(field_def),
|
|
796
776
|
required=required,
|
|
797
777
|
label=field_def.field_label,
|
|
798
778
|
help_text=help_text,
|
|
@@ -931,12 +911,13 @@ class DynamicForm(forms.Form):
|
|
|
931
911
|
)
|
|
932
912
|
|
|
933
913
|
elif field_def.field_type == "display_text":
|
|
934
|
-
display_html = _render_display_text(
|
|
914
|
+
display_html = _render_display_text(field_def.default_value)
|
|
935
915
|
self.fields[field_def.field_name] = forms.CharField(
|
|
936
916
|
required=False,
|
|
937
917
|
label=field_def.field_label,
|
|
938
918
|
help_text=field_def.help_text,
|
|
939
|
-
initial=
|
|
919
|
+
initial=field_def.default_value,
|
|
920
|
+
disabled=True,
|
|
940
921
|
widget=DisplayTextWidget(
|
|
941
922
|
display_html=display_html,
|
|
942
923
|
attrs={"data-display-text": "true"},
|
{django_forms_workflows-0.77.4 → django_forms_workflows-0.78.0}/django_forms_workflows/models.py
RENAMED
|
@@ -881,6 +881,8 @@ class DocumentTemplate(models.Model):
|
|
|
881
881
|
"""Render this template with submission data, returning HTML string."""
|
|
882
882
|
import re
|
|
883
883
|
|
|
884
|
+
from django.utils.html import escape
|
|
885
|
+
|
|
884
886
|
form_data = submission.form_data or {}
|
|
885
887
|
|
|
886
888
|
# Build merge context with system variables + form data
|
|
@@ -906,6 +908,7 @@ class DocumentTemplate(models.Model):
|
|
|
906
908
|
else:
|
|
907
909
|
context[key] = str(val) if val is not None else ""
|
|
908
910
|
|
|
911
|
+
context = {key: str(escape(value)) for key, value in context.items()}
|
|
909
912
|
html = self.html_content
|
|
910
913
|
|
|
911
914
|
# Process conditional blocks: {% if field_name %}...{% endif %}
|
|
@@ -12,7 +12,13 @@ apps can add ECSI, PayPal, Authorize.Net, or any other provider
|
|
|
12
12
|
without modifying the library itself.
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
from .base import
|
|
15
|
+
from .base import (
|
|
16
|
+
PaymentFlow,
|
|
17
|
+
PaymentProvider,
|
|
18
|
+
PaymentResult,
|
|
19
|
+
PaymentStatus,
|
|
20
|
+
WebhookVerificationError,
|
|
21
|
+
)
|
|
16
22
|
from .registry import (
|
|
17
23
|
clear,
|
|
18
24
|
get_available_providers,
|
|
@@ -27,6 +33,7 @@ __all__ = [
|
|
|
27
33
|
"PaymentProvider",
|
|
28
34
|
"PaymentResult",
|
|
29
35
|
"PaymentStatus",
|
|
36
|
+
"WebhookVerificationError",
|
|
30
37
|
"clear",
|
|
31
38
|
"get_available_providers",
|
|
32
39
|
"get_provider",
|
|
@@ -47,6 +47,17 @@ class PaymentStatus(Enum):
|
|
|
47
47
|
REFUNDED = "refunded"
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
class WebhookVerificationError(Exception):
|
|
51
|
+
"""
|
|
52
|
+
Raised by a provider's ``handle_webhook()`` when the event payload
|
|
53
|
+
cannot be authenticated (e.g. a missing/invalid signature, or a
|
|
54
|
+
required verification secret that is not configured).
|
|
55
|
+
|
|
56
|
+
Providers must raise this instead of processing unverified payloads:
|
|
57
|
+
the webhook endpoint fails closed and discards the event.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
|
|
50
61
|
@dataclass
|
|
51
62
|
class PaymentResult:
|
|
52
63
|
"""Returned by create_payment() and confirm_payment()."""
|
|
@@ -93,12 +104,19 @@ class PaymentProvider(ABC):
|
|
|
93
104
|
submission_id: int,
|
|
94
105
|
description: str = "",
|
|
95
106
|
metadata: dict[str, Any] | None = None,
|
|
107
|
+
idempotency_key: str | None = None,
|
|
96
108
|
) -> PaymentResult:
|
|
97
109
|
"""
|
|
98
110
|
Initiate a payment.
|
|
99
111
|
|
|
100
112
|
For INLINE providers: creates a payment intent, returns client_secret.
|
|
101
113
|
For REDIRECT providers: creates a session, returns redirect_url.
|
|
114
|
+
|
|
115
|
+
``idempotency_key`` is stable for a single payment attempt (one
|
|
116
|
+
``PaymentRecord``). Providers that support request idempotency
|
|
117
|
+
should forward it so a retried initiation cannot create duplicate
|
|
118
|
+
provider-side charges. A new attempt (after a cancellation or
|
|
119
|
+
failure) is issued a fresh key.
|
|
102
120
|
"""
|
|
103
121
|
|
|
104
122
|
@abstractmethod
|
|
@@ -121,6 +139,12 @@ class PaymentProvider(ABC):
|
|
|
121
139
|
|
|
122
140
|
Override if the provider sends server-to-server callbacks.
|
|
123
141
|
Returns None if the event is not relevant.
|
|
142
|
+
|
|
143
|
+
Implementations MUST authenticate the event before returning a
|
|
144
|
+
result (e.g. verify a signature or shared secret) and raise
|
|
145
|
+
:class:`WebhookVerificationError` when verification fails or the
|
|
146
|
+
required verification secret is not configured. Unverified
|
|
147
|
+
payloads must never produce a state-changing result.
|
|
124
148
|
"""
|
|
125
149
|
return None
|
|
126
150
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Checkout authorization for the payment flow.
|
|
3
|
+
|
|
4
|
+
Payment endpoints act on a :class:`~django_forms_workflows.models.FormSubmission`
|
|
5
|
+
that is in ``pending_payment``. Access is governed by exactly one policy:
|
|
6
|
+
|
|
7
|
+
* **Owned submissions** — only the authenticated submitter may act on them.
|
|
8
|
+
A numeric submission or payment-record ID alone grants nothing.
|
|
9
|
+
* **Anonymous (public-form) submissions** — the browser that just posted the
|
|
10
|
+
public form is issued a *checkout credential*: an unguessable token stored
|
|
11
|
+
in its session and bound to the submission ID, valid for a short time
|
|
12
|
+
(``FORMS_WORKFLOWS_PAYMENT_CHECKOUT_TTL`` seconds, default one hour).
|
|
13
|
+
|
|
14
|
+
The credential is granted by the submit views when a submission enters
|
|
15
|
+
``pending_payment`` and is revoked when the checkout is cancelled. It is
|
|
16
|
+
never exposed in a URL. Callers that fail the policy receive a
|
|
17
|
+
non-disclosing 404, identical to a nonexistent submission.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import secrets
|
|
23
|
+
import time
|
|
24
|
+
|
|
25
|
+
from django.conf import settings
|
|
26
|
+
|
|
27
|
+
SESSION_KEY = "payment_checkouts"
|
|
28
|
+
DEFAULT_CHECKOUT_TTL_SECONDS = 3600 # 1 hour
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _checkout_ttl() -> int:
|
|
32
|
+
return int(
|
|
33
|
+
getattr(
|
|
34
|
+
settings,
|
|
35
|
+
"FORMS_WORKFLOWS_PAYMENT_CHECKOUT_TTL",
|
|
36
|
+
DEFAULT_CHECKOUT_TTL_SECONDS,
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def grant_checkout_access(request, submission) -> None:
|
|
42
|
+
"""Bind a short-lived checkout credential for *submission* to the session.
|
|
43
|
+
|
|
44
|
+
Called by the submit views right after a submission enters
|
|
45
|
+
``pending_payment`` so the same browser session can complete (confirm,
|
|
46
|
+
return, or cancel) the checkout that belongs to it.
|
|
47
|
+
"""
|
|
48
|
+
checkouts = dict(request.session.get(SESSION_KEY, {}))
|
|
49
|
+
checkouts[str(submission.id)] = {
|
|
50
|
+
"token": secrets.token_urlsafe(32),
|
|
51
|
+
"exp": time.time() + _checkout_ttl(),
|
|
52
|
+
}
|
|
53
|
+
request.session[SESSION_KEY] = checkouts
|
|
54
|
+
request.session.modified = True
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def revoke_checkout_access(request, submission) -> None:
|
|
58
|
+
"""Drop any checkout credential for *submission* from the session."""
|
|
59
|
+
checkouts = dict(request.session.get(SESSION_KEY, {}))
|
|
60
|
+
if checkouts.pop(str(submission.id), None) is not None:
|
|
61
|
+
request.session[SESSION_KEY] = checkouts
|
|
62
|
+
request.session.modified = True
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def has_checkout_access(request, submission) -> bool:
|
|
66
|
+
"""The single payment access policy used by every payment endpoint.
|
|
67
|
+
|
|
68
|
+
* Authenticated submitter: may act only on their own submission.
|
|
69
|
+
* Anonymous submission (``submitter`` is null): the caller's session
|
|
70
|
+
must hold an unexpired checkout credential issued by
|
|
71
|
+
:func:`grant_checkout_access`.
|
|
72
|
+
"""
|
|
73
|
+
if submission.submitter_id is not None:
|
|
74
|
+
return (
|
|
75
|
+
request.user.is_authenticated and request.user.pk == submission.submitter_id
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
entry = request.session.get(SESSION_KEY, {}).get(str(submission.id))
|
|
79
|
+
if not entry:
|
|
80
|
+
return False
|
|
81
|
+
try:
|
|
82
|
+
return float(entry.get("exp", 0)) > time.time()
|
|
83
|
+
except (TypeError, ValueError):
|
|
84
|
+
return False
|