Skip to content

Commit fd4f6b3

Browse files
committed
[fix] Correct handling of deferred fields when tracking changed fields
When a Device instance was loaded with deferred fields (e.g. using QuerySet.only()), `_check_changed_fields()` incorrectly populated the `_initial_<field>` attributes. Instead of storing the original value retrieved from the database, it stored the field name itself. This happened because the code iterated over `_changed_checked_fields` and assigned the field name rather than the actual value coming from `present_values`. The logic has been updated to iterate over `present_values.items()`, store the current database value in `_initial_<field>`, and then restore the updated value on the instance. This ensures change detection works correctly even when fields were initially deferred. Changelog (Bugfix): Fixed incorrect initialization of `_initial_<field>` values when a Device instance is loaded with deferred fields, which could break change detection logic.
1 parent af0d99b commit fd4f6b3

File tree

2 files changed

+18
-3
lines changed

2 files changed

+18
-3
lines changed

openwisp_controller/config/base/device.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -338,9 +338,9 @@ def _get_initial_values_for_checked_fields(self):
338338
if not present_values:
339339
return
340340
self.refresh_from_db(fields=present_values.keys())
341-
for field in self._changed_checked_fields:
342-
setattr(self, f"_initial_{field}", field)
343-
setattr(self, field, present_values[field])
341+
for field, value in present_values.items():
342+
setattr(self, f"_initial_{field}", getattr(self, field))
343+
setattr(self, field, value)
344344

345345
def _check_name_changed(self):
346346
if self._initial_name == models.DEFERRED:

openwisp_controller/config/tests/test_device.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,21 @@ def test_changed_checked_fields_no_duplicates(self):
541541
device.__init__()
542542
self.assertEqual(device._changed_checked_fields.count("last_ip"), 1)
543543

544+
def test_deferred_fields_populated_correctly(self):
545+
device = self._create_device(
546+
name="deferred-test",
547+
last_ip="172.16.0.1",
548+
management_ip="10.0.0.1",
549+
)
550+
# Load the instance with deferred fields omitted
551+
device = Device.objects.only("id").get(pk=device.pk)
552+
device.last_ip = "172.16.1.1"
553+
# Populate initial values for checked fields
554+
device._check_changed_fields()
555+
# Ensure `_initial_<field>` contains the actual value, not the field name
556+
self.assertEqual(getattr(device, "_initial_last_ip"), "172.16.0.1")
557+
self.assertNotEqual(getattr(device, "_initial_last_ip"), "last_ip")
558+
544559
def test_exceed_organization_device_limit(self):
545560
org = self._get_org()
546561
org.config_limits.device_limit = 1

0 commit comments

Comments
 (0)