From fa4326802fea80053523d627983e436c0312c92f Mon Sep 17 00:00:00 2001 From: Michael Uray <25169478+MichaelUray@users.noreply.github.com> Date: Sat, 18 Apr 2026 04:27:21 +0000 Subject: [PATCH] [fix] Guard missing skip_push_update_on_save in register view #1331 DeviceRegisterView._update_device_name calls ``device.skip_push_update_on_save()`` whenever an agent re-registers with a hostname different from the MAC address. While the method is defined on AbstractDevice, subclasses or older installations that lack it would crash with AttributeError on every such re-registration (HTTP 500). This breaks the consistent_registration / factory-reset workflow: 1. A router does a factory reset and loses its local UUID/key. 2. The agent computes a consistent key and POSTs to /register/. 3. OpenWISP finds the existing device by key. 4. The view enters _update_device_name (hostname differs from MAC). 5. If skip_push_update_on_save is missing, AttributeError -> HTTP 500. 6. The agent sees no X-Openwisp-Controller header and aborts. 7. After 6 retries procd kills the agent. Wrap the call in ``getattr`` so the registration path succeeds whether the helper method exists or not. The guard is forward-compatible and emits a warning log when the method is missing so operators running non-standard device models notice the missing push-skip optimization. Closes #1331 --- openwisp_controller/config/controller/views.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/openwisp_controller/config/controller/views.py b/openwisp_controller/config/controller/views.py index 3dc714c0c..66840ff00 100644 --- a/openwisp_controller/config/controller/views.py +++ b/openwisp_controller/config/controller/views.py @@ -475,7 +475,19 @@ def _update_device_name(self, request, device): normalized_name = name.replace(":", "").replace("-", "").lower() if normalized_name != normalized_mac: device.name = name - device.skip_push_update_on_save() + # NOTE: ``device.skip_push_update_on_save()`` is defined on + # AbstractDevice, but we guard the call defensively so that + # subclasses or older installations that may lack the method + # do not crash during factory-reset re-registration. + skip = getattr(device, "skip_push_update_on_save", None) + if callable(skip): + skip() + else: + logger.warning( + "Device %s does not implement skip_push_update_on_save(); " + "continuing registration without push-skip optimization.", + device.pk, + ) class GetVpnView(SingleObjectMixin, View):