From a5ecb6a3c2fa271ab470aa87a9fa634ea55a4d4c Mon Sep 17 00:00:00 2001 From: Johan van den Dorpe Date: Wed, 2 Jul 2025 10:34:54 +0100 Subject: [PATCH 1/3] Truncate attribute_value to 255 characters, the maximum size allowed --- nautobot_data_validation_engine/custom_validators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nautobot_data_validation_engine/custom_validators.py b/nautobot_data_validation_engine/custom_validators.py index c653bc52..6a5d77ef 100644 --- a/nautobot_data_validation_engine/custom_validators.py +++ b/nautobot_data_validation_engine/custom_validators.py @@ -295,7 +295,7 @@ def compliance_result(self, message, attribute=None, valid=True): defaults={ "last_validation_date": self.result_date, "validated_object_str": str(instance), - "validated_attribute_value": str(attribute_value) if attribute_value else "", + "validated_attribute_value": str(attribute_value)[:254] if attribute_value else "", "message": message, "valid": valid, }, From 92ab9797a90662aab6850511cb926fd8f86497b4 Mon Sep 17 00:00:00 2001 From: Johan van den Dorpe Date: Wed, 2 Jul 2025 10:38:38 +0100 Subject: [PATCH 2/3] Add datacompliance support for custom field attributes --- nautobot_data_validation_engine/custom_validators.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nautobot_data_validation_engine/custom_validators.py b/nautobot_data_validation_engine/custom_validators.py index 6a5d77ef..9e5157d3 100644 --- a/nautobot_data_validation_engine/custom_validators.py +++ b/nautobot_data_validation_engine/custom_validators.py @@ -283,7 +283,10 @@ def compliance_result(self, message, attribute=None, valid=True): """Generate a DataCompliance object based on the given parameters.""" instance = self.context["object"] attribute_value = None - if attribute: + if attribute and attribute.startswith("cf_"): + # Custom field attributes are prefixed with 'cf_' + attribute_value = instance.cf.get(attribute[3:], None) + elif attribute: attribute_value = getattr(instance, attribute) else: attribute = "__all__" From 57580fcd02ae91728b74b069c66545616398f7a6 Mon Sep 17 00:00:00 2001 From: Johan van den Dorpe Date: Fri, 18 Jul 2025 12:40:32 +0100 Subject: [PATCH 3/3] Update docs and tests for custom field validation --- changes/205.fixed | 1 + docs/user/app_data_compliance.md | 3 ++ .../tests/test_data_compliance_rules.py | 31 +++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 changes/205.fixed diff --git a/changes/205.fixed b/changes/205.fixed new file mode 100644 index 00000000..7d94982d --- /dev/null +++ b/changes/205.fixed @@ -0,0 +1 @@ +Added support for validating custom fields by referencing them as cf_ in ComplianceError exceptions \ No newline at end of file diff --git a/docs/user/app_data_compliance.md b/docs/user/app_data_compliance.md index 5d7aeaae..06e51701 100644 --- a/docs/user/app_data_compliance.md +++ b/docs/user/app_data_compliance.md @@ -18,6 +18,9 @@ Any `DataComplianceRule` class can have a `name` defined to provide a friendly n > > For example, if a user fixes an object attribute that was incompliant with a built-in rule and then navigates to its `Data Compliance` tab, the object will still show as invalid for that built-in rule. This will remain the case until the job is ran again with the `Run built-in validation rules?` option checked. +!!! note + When raising a ComplianceError, the attribute must exist on the object. To raise errors for custom fields, use cf_custom_field_name as the attribute name. + ## How to Use ### Step 1. Create Data Compliance Rules diff --git a/nautobot_data_validation_engine/tests/test_data_compliance_rules.py b/nautobot_data_validation_engine/tests/test_data_compliance_rules.py index 016a2e5d..554902d7 100644 --- a/nautobot_data_validation_engine/tests/test_data_compliance_rules.py +++ b/nautobot_data_validation_engine/tests/test_data_compliance_rules.py @@ -78,3 +78,34 @@ def test_validate_replaces_results(self): len(DataCompliance.objects.filter(compliance_class_name=TestFailedDataComplianceRule.__name__)), 5, ) + + def test_custom_field_attribute_value(self): + # Simulate a Location with a custom field value + self.s.cf = {"foo": "bar"} + # Patch DataComplianceRule.context to include our instance + rule = TestPassedDataComplianceRule(self.s) + rule.context = {"object": self.s} + + # Call _create_data_compliance_object with a custom field attribute + obj = rule._create_data_compliance_object(attribute="cf_foo", valid=True, message="msg") + self.assertEqual(obj.validated_attribute, "cf_foo") + self.assertEqual(obj.validated_attribute_value, "bar") + + def test_custom_field_attribute_value_missing(self): + # Simulate a Location with no custom field value + self.s.cf = {} + rule = TestPassedDataComplianceRule(self.s) + rule.context = {"object": self.s} + + obj = rule._create_data_compliance_object(attribute="cf_missing", valid=True, message="msg") + self.assertEqual(obj.validated_attribute, "cf_missing") + self.assertIsNone(obj.validated_attribute_value) + + def test_regular_attribute_value(self): + # Test with a regular attribute + rule = TestPassedDataComplianceRule(self.s) + rule.context = {"object": self.s} + + obj = rule._create_data_compliance_object(attribute="name", valid=True, message="msg") + self.assertEqual(obj.validated_attribute, "name") + self.assertEqual(obj.validated_attribute_value, self.s.name)