diff --git a/awx/api/serializers.py b/awx/api/serializers.py index f4aab86136..22ca4b6490 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -2274,6 +2274,7 @@ class BulkHostCreateSerializer(serializers.Serializer): return attrs def create(self, validated_data): + # DAB: dedupe Validation rejected (child BulkHostSerializer is_valid) vs ORM bypass bulk_create. with serializer_mediated_persistence_context(): return self._create_bulk_hosts(validated_data) @@ -5282,6 +5283,7 @@ class BulkJobLaunchSerializer(PromptFieldCleanTextMixin, serializers.Serializer) ) def create(self, validated_data): + # DAB: dedupe Validation rejected (is_valid) vs audit_workflow_job_nodes / bulk_create bypass logs. with serializer_mediated_persistence_context(): return self._create_bulk_job_launch(validated_data) diff --git a/awx/main/utils/validation_bypass_observability.py b/awx/main/utils/validation_bypass_observability.py index 7725eb3277..7c4c83a1f2 100644 --- a/awx/main/utils/validation_bypass_observability.py +++ b/awx/main/utils/validation_bypass_observability.py @@ -61,6 +61,11 @@ def audit_workflow_job_nodes_for_bulk_create(nodes: Iterable[Model]) -> None: columns. Node prompt values (limit, job_tags, etc.) live in ``char_prompts`` via ``NullablePromptPseudoField`` and must be read with ``getattr`` after deferred attrs are applied on the in-memory instances. + + When called from a DRF ``create()`` after ``is_valid()``, the caller must run + inside DAB ``serializer_mediated_persistence_context`` so duplicate + ``Validation rejected`` / ``ORM bypass`` lines are not emitted (see DAB + ``docs/lib/validation_bypass_observability.md``). """ materialized = list(nodes) if not materialized: