Skip to content

Commit 5f27f7b

Browse files
authored
🚚 release (#152)
2 parents 26476e8 + 5a3a14d commit 5f27f7b

5 files changed

Lines changed: 97 additions & 4 deletions

File tree

‎docker/common/requirements-diode-netbox-plugin.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ Brotli==1.2.0
22
certifi==2024.7.4
33
coverage==7.6.0
44
grpcio==1.62.1
5-
protobuf==5.29.5
5+
protobuf==5.29.6
66
pytest==8.0.2
77
netboxlabs-netbox-branching==0.7.1

‎docker/v4.4.x/requirements-diode-netbox-plugin.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ Brotli==1.2.0
22
certifi==2024.7.4
33
coverage==7.6.0
44
grpcio==1.62.1
5-
protobuf==5.29.5
5+
protobuf==5.29.6
66
pytest==8.0.2
77
netboxlabs-netbox-branching==0.7.1

‎docker/v4.5.x/requirements-diode-netbox-plugin.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ Brotli==1.2.0
22
certifi==2024.7.4
33
coverage==7.6.0
44
grpcio==1.62.1
5-
protobuf==5.29.5
5+
protobuf==5.29.6
66
pytest==8.0.2
77
netboxlabs-netbox-branching==0.8.0

‎netbox_diode_plugin/api/differ.py‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from collections import defaultdict
99

1010
from django.contrib.contenttypes.models import ContentType
11+
from extras.choices import CustomFieldTypeChoices
1112
from rest_framework import serializers
1213
from utilities.data import shallow_compare_dict
1314

@@ -80,7 +81,13 @@ def prechange_data_from_instance(instance) -> dict: # noqa: C901
8081
if isinstance(value, datetime.datetime | datetime.date):
8182
cfmap[cf.name] = value
8283
else:
83-
cfmap[cf.name] = cf.serialize(value)
84+
serialized = cf.serialize(value)
85+
if isinstance(serialized, list) and cf.type in (
86+
CustomFieldTypeChoices.TYPE_MULTIOBJECT,
87+
CustomFieldTypeChoices.TYPE_MULTISELECT,
88+
):
89+
serialized = sort_ints_first(serialized)
90+
cfmap[cf.name] = serialized
8491
prechange_data["custom_fields"] = cfmap
8592
prechange_data = harmonize_formats(prechange_data)
8693

‎netbox_diode_plugin/tests/v4.5.x/tests/test_api_diff_and_apply.py‎

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1814,6 +1814,92 @@ def test_generate_diff_and_apply_create_vlan_translation_policy(self):
18141814
self.assertEqual(new_policy.description, "Policy for VLAN translation")
18151815
self.assertEqual(new_policy.owner.name, f"Owner {owner_uuid}")
18161816

1817+
def test_multiobject_cf_rediff_noop(self):
1818+
"""
1819+
Test that re-diffing a device with multiobject custom field produces no changes.
1820+
1821+
INT-219: multiobject custom field values are order-insensitive (sets),
1822+
but the differ was comparing them as ordered lists. This caused phantom
1823+
changesets on every re-diff because the "before" IDs (from queryset,
1824+
ordered by name) didn't match the "desired" IDs (sorted numerically).
1825+
"""
1826+
device_cf = CustomField.objects.create(
1827+
name='int219_multi_sites',
1828+
type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
1829+
required=False,
1830+
related_object_type=ObjectType.objects.get_for_model(Site),
1831+
)
1832+
device_object_type = ObjectType.objects.get_for_model(Device)
1833+
device_cf.object_types.set([device_object_type])
1834+
device_cf.save()
1835+
1836+
# Pre-create "Gamma" so it gets a LOWER ID than Alpha and Beta.
1837+
# When diff+apply later creates Alpha and Beta, they get higher IDs.
1838+
# This ensures name-alphabetical order (Alpha, Beta, Gamma) differs
1839+
# from numeric ID order (Gamma, Alpha, Beta) — which triggers the bug.
1840+
Site.objects.create(name="INT219-Site-Gamma", slug="int219-site-gamma")
1841+
1842+
payload = {
1843+
"timestamp": 1,
1844+
"object_type": "dcim.device",
1845+
"entity": {
1846+
"device": {
1847+
"name": "INT219-Test-Device",
1848+
"role": {"name": "INT219-Role"},
1849+
"site": {"name": "INT219-Site-Primary"},
1850+
"device_type": {
1851+
"model": "INT219-Model",
1852+
"manufacturer": {"name": "INT219-Manufacturer"},
1853+
},
1854+
"serial": "INT219-SERIAL-001",
1855+
"custom_fields": {
1856+
"int219_multi_sites": {
1857+
"multiple_objects": [
1858+
{"site": {"name": "INT219-Site-Alpha"}},
1859+
{"site": {"name": "INT219-Site-Beta"}},
1860+
{"site": {"name": "INT219-Site-Gamma"}},
1861+
],
1862+
},
1863+
},
1864+
},
1865+
},
1866+
}
1867+
1868+
# First diff+apply: creates the device, Alpha, Beta (Gamma already exists)
1869+
self.diff_and_apply(payload)
1870+
device = Device.objects.get(name="INT219-Test-Device")
1871+
self.assertIsNotNone(device)
1872+
self.assertEqual(len(device.custom_field_data['int219_multi_sites']), 3)
1873+
1874+
# Verify IDs are NOT in alphabetical-name order (precondition for the bug)
1875+
alpha = Site.objects.get(name="INT219-Site-Alpha")
1876+
beta = Site.objects.get(name="INT219-Site-Beta")
1877+
gamma = Site.objects.get(name="INT219-Site-Gamma")
1878+
name_order_ids = [alpha.pk, beta.pk, gamma.pk]
1879+
numeric_order_ids = sorted(name_order_ids)
1880+
self.assertNotEqual(
1881+
name_order_ids, numeric_order_ids,
1882+
"Test precondition failed: IDs happen to match name order. "
1883+
"Pre-creating Gamma should have given it a lower ID than Alpha/Beta."
1884+
)
1885+
1886+
# Step 2: Re-diff with the exact same payload.
1887+
# Before the fix, this produces a false "update" changeset because
1888+
# cf.serialize() returns IDs in queryset name-order [alpha, beta, gamma]
1889+
# but the transformer sorts resolved IDs numerically [gamma, alpha, beta].
1890+
response = self.client.post(
1891+
self.diff_url, data=payload, format="json", **self.authorization_header
1892+
)
1893+
self.assertEqual(response.status_code, status.HTTP_200_OK)
1894+
cs = response.json().get("change_set", {})
1895+
changes = cs.get("changes", [])
1896+
1897+
# The re-diff should produce NO changes — the data hasn't changed.
1898+
self.assertEqual(
1899+
changes, [],
1900+
f"Expected no changes on re-diff, but got: {changes}"
1901+
)
1902+
18171903
def diff_and_apply(self, payload):
18181904
"""Diff and apply the payload."""
18191905
response1 = self.client.post(

0 commit comments

Comments
 (0)