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