Skip to content

[fix] Corrected initial field value assignment in AbstractDevice class#1301

Merged
nemesifier merged 2 commits intomasterfrom
fix-device-get-deferred-values
Mar 27, 2026
Merged

[fix] Corrected initial field value assignment in AbstractDevice class#1301
nemesifier merged 2 commits intomasterfrom
fix-device-get-deferred-values

Conversation

@pandafy
Copy link
Copy Markdown
Member

@pandafy pandafy commented Mar 19, 2026

Checklist

  • I have read the OpenWISP Contributing Guidelines.
  • I have manually tested the changes proposed in this pull request.
  • I have written new test cases for new code and/or updated existing tests for changes to existing code.
  • N/A I have updated the documentation.

Description of Changes

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.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The patch modifies _get_initial_values_for_checked_fields() to restore only fields that were actually present after deferred-field detection by iterating saved present_values. For each (field, value) in present_values it sets initial{field} from the refreshed instance’s current attribute (getattr(self, field)) and then restores the instance field to the saved pre-refresh value. A test was added (test_deferred_fields_populated_correctly) that verifies _initial_last_ip contains the persisted value after saving a deferred field.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Bug Fixes ❓ Inconclusive Repository code is not accessible in the current environment, preventing verification of the bug fix implementation and regression test. Ensure repository code is accessible to verify that the test reproduces the bug when the patch is removed and that the implementation fix addresses the root cause.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title follows the required format with [fix] prefix and clearly describes the main change: correcting field value assignment in the AbstractDevice class.
Description check ✅ Passed The description covers all required sections including checklist completion, clear explanation of the bug, root cause analysis, the fix applied, and a changelog entry.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-device-get-deferred-values

Comment @coderabbitai help to get the list of available commands and usage tips.

@pandafy pandafy moved this from To do (general) to To do (Python & Django) in OpenWISP Contributor's Board Mar 19, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@openwisp_controller/config/base/device.py`:
- Around line 341-343: The loop currently iterates over
self._changed_checked_fields but then indexes present_values[field], which
raises KeyError because present_values only contains deferred fields that were
refreshed; change the loop to iterate over present_values.keys() (or
present_values) so only refreshed fields are processed, and keep the existing
setattr calls that set _initial_{field} from getattr(self, field) and then
assign setattr(self, field, present_values[field]).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 70fe1648-0ef1-4a3d-a2d6-bc0498e23d9a

📥 Commits

Reviewing files that changed from the base of the PR and between 45b24b6 and 320571b.

📒 Files selected for processing (1)
  • openwisp_controller/config/base/device.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}

📄 CodeRabbit inference engine (Custom checks)

**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}: Flag potential security vulnerabilities in code
Avoid unnecessary comments or docstrings for code that is already clear
Code formatting is compact and readable. Do not add excessive blank lines, especially inside function or method bodies
Flag unused or redundant code
Ensure variables, functions, classes, and files have descriptive and consistent names
New code must handle errors properly: log errors that cannot be resolved by the user with error level, log unusual conditions with warning level, log important background actions with info level, and provide user-facing messages for errors that the user can solve autonomously

Files:

  • openwisp_controller/config/base/device.py
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp,sql}

📄 CodeRabbit inference engine (Custom checks)

Flag obvious performance regressions, such as heavy loops, repeated I/O, or unoptimized queries

Files:

  • openwisp_controller/config/base/device.py
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp,sh,bash,sql}

📄 CodeRabbit inference engine (Custom checks)

Cryptic or non-obvious code (regex, complex bash commands, or hard-to-read code) must include a concise comment explaining why it is needed and why the complexity is acceptable

Files:

  • openwisp_controller/config/base/device.py
**/*.{py,html}

📄 CodeRabbit inference engine (Custom checks)

For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework

Files:

  • openwisp_controller/config/base/device.py
🧠 Learnings (4)
📚 Learning: 2026-01-15T14:06:53.460Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/geo/base/models.py:63-93
Timestamp: 2026-01-15T14:06:53.460Z
Learning: In openwisp_controller/geo/base/models.py, the BaseLocation.save() method protects the is_estimated field from manual changes when the estimated location feature is disabled by reverting it to its initial database value using the elif branch. This is intentional behavior to ensure the field can only be modified when the feature is properly configured.

Applied to files:

  • openwisp_controller/config/base/device.py
📚 Learning: 2026-01-15T15:05:49.557Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/management/commands/clear_last_ip.py:38-42
Timestamp: 2026-01-15T15:05:49.557Z
Learning: In Django projects, when using select_related() to traverse relations (for example, select_related("organization__config_settings")), the traversed relation must not be deferred. If you also use .only() in the same query, include the relation name or FK field (e.g., "organization" or "organization_id") in the .only() list to avoid the error "Field X cannot be both deferred and traversed using select_related at the same time." Apply this guideline to Django code in openwisp_controller/config/management/commands/clear_last_ip.py and similar modules by ensuring any select_related with an accompanying only() includes the related field names to prevent deferred/traversed conflicts.

Applied to files:

  • openwisp_controller/config/base/device.py
📚 Learning: 2026-02-17T19:13:10.088Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/whois/commands.py:0-0
Timestamp: 2026-02-17T19:13:10.088Z
Learning: In reviews for the openwisp/openwisp-controller repository, do not propose changes based on Ruff warnings. The project does not use Ruff as its linter; ignore Ruff-related suggestions and follow the repository’s established linting and configuration rules. This guidance applies to all Python files under the openwisp_controller directory.

Applied to files:

  • openwisp_controller/config/base/device.py
📚 Learning: 2026-01-15T15:07:17.354Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/geo/estimated_location/tests/tests.py:172-175
Timestamp: 2026-01-15T15:07:17.354Z
Learning: In this repository, flake8 enforces E501 (line too long) via setup.cfg (max-line-length = 88) while ruff ignores E501 via ruff.toml. Therefore, use '# noqa: E501' on lines that intentionally exceed 88 characters to satisfy flake8 without affecting ruff checks. This applies to Python files across the project (any .py) and is relevant for tests as well. Use sparingly and only where breaking lines is not feasible without hurting readability or functionality.

Applied to files:

  • openwisp_controller/config/base/device.py

@github-project-automation github-project-automation bot moved this from To do (Python & Django) to In progress in OpenWISP Contributor's Board Mar 19, 2026
@coveralls
Copy link
Copy Markdown

coveralls commented Mar 19, 2026

Coverage Status

coverage: 98.646%. remained the same
when pulling 1a5fb89 on fix-device-get-deferred-values
into af0d99b on master.

@pandafy pandafy self-assigned this Mar 19, 2026
@pandafy pandafy force-pushed the fix-device-get-deferred-values branch from 320571b to 4145eed Compare March 19, 2026 10:30
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@openwisp_controller/config/tests/test_device.py`:
- Around line 544-557: The test test_deferred_fields_populated_correctly should
also assert that the instance retains the user-updated value after calling
Device._check_changed_fields(); after invoking device._check_changed_fields(),
add an assertion that device.last_ip == "172.16.1.1" (while still asserting
getattr(device, "_initial_last_ip") == "172.16.0.1"), referencing the test
function name and the Device._check_changed_fields and _initial_last_ip
attributes to locate where to add the extra assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9f9cfc99-0109-4550-94a2-1c527064a1bf

📥 Commits

Reviewing files that changed from the base of the PR and between 320571b and 4145eed.

📒 Files selected for processing (2)
  • openwisp_controller/config/base/device.py
  • openwisp_controller/config/tests/test_device.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}

📄 CodeRabbit inference engine (Custom checks)

**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}: Flag potential security vulnerabilities in code
Avoid unnecessary comments or docstrings for code that is already clear
Code formatting is compact and readable. Do not add excessive blank lines, especially inside function or method bodies
Flag unused or redundant code
Ensure variables, functions, classes, and files have descriptive and consistent names
New code must handle errors properly: log errors that cannot be resolved by the user with error level, log unusual conditions with warning level, log important background actions with info level, and provide user-facing messages for errors that the user can solve autonomously

Files:

  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/base/device.py
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp,sql}

📄 CodeRabbit inference engine (Custom checks)

Flag obvious performance regressions, such as heavy loops, repeated I/O, or unoptimized queries

Files:

  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/base/device.py
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp,sh,bash,sql}

📄 CodeRabbit inference engine (Custom checks)

Cryptic or non-obvious code (regex, complex bash commands, or hard-to-read code) must include a concise comment explaining why it is needed and why the complexity is acceptable

Files:

  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/base/device.py
**/*.{py,html}

📄 CodeRabbit inference engine (Custom checks)

For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework

Files:

  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/base/device.py
🧠 Learnings (5)
📚 Learning: 2026-01-12T22:27:48.342Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1175
File: tests/openwisp2/sample_config/migrations/0008_whoisinfo_organizationconfigsettings_whois_enabled.py:18-67
Timestamp: 2026-01-12T22:27:48.342Z
Learning: In tests/openwisp2/sample_config/models.py and corresponding test migrations, the WHOISInfo model intentionally includes an additional "details" field not present in the base AbstractWHOISInfo model. This is a testing pattern to verify that swappable models (CONFIG_WHOISINFO_MODEL) can be extended with custom fields without errors.

Applied to files:

  • openwisp_controller/config/tests/test_device.py
📚 Learning: 2026-01-15T15:05:49.557Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/management/commands/clear_last_ip.py:38-42
Timestamp: 2026-01-15T15:05:49.557Z
Learning: In Django projects, when using select_related() to traverse relations (for example, select_related("organization__config_settings")), the traversed relation must not be deferred. If you also use .only() in the same query, include the relation name or FK field (e.g., "organization" or "organization_id") in the .only() list to avoid the error "Field X cannot be both deferred and traversed using select_related at the same time." Apply this guideline to Django code in openwisp_controller/config/management/commands/clear_last_ip.py and similar modules by ensuring any select_related with an accompanying only() includes the related field names to prevent deferred/traversed conflicts.

Applied to files:

  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/base/device.py
📚 Learning: 2026-02-17T19:13:10.088Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/whois/commands.py:0-0
Timestamp: 2026-02-17T19:13:10.088Z
Learning: In reviews for the openwisp/openwisp-controller repository, do not propose changes based on Ruff warnings. The project does not use Ruff as its linter; ignore Ruff-related suggestions and follow the repository’s established linting and configuration rules. This guidance applies to all Python files under the openwisp_controller directory.

Applied to files:

  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/base/device.py
📚 Learning: 2026-01-15T15:07:17.354Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/geo/estimated_location/tests/tests.py:172-175
Timestamp: 2026-01-15T15:07:17.354Z
Learning: In this repository, flake8 enforces E501 (line too long) via setup.cfg (max-line-length = 88) while ruff ignores E501 via ruff.toml. Therefore, use '# noqa: E501' on lines that intentionally exceed 88 characters to satisfy flake8 without affecting ruff checks. This applies to Python files across the project (any .py) and is relevant for tests as well. Use sparingly and only where breaking lines is not feasible without hurting readability or functionality.

Applied to files:

  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/base/device.py
📚 Learning: 2026-01-15T14:06:53.460Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/geo/base/models.py:63-93
Timestamp: 2026-01-15T14:06:53.460Z
Learning: In openwisp_controller/geo/base/models.py, the BaseLocation.save() method protects the is_estimated field from manual changes when the estimated location feature is disabled by reverting it to its initial database value using the elif branch. This is intentional behavior to ensure the field can only be modified when the feature is properly configured.

Applied to files:

  • openwisp_controller/config/base/device.py
🧬 Code graph analysis (1)
openwisp_controller/config/tests/test_device.py (4)
openwisp_controller/config/tests/utils.py (1)
  • _create_device (31-44)
openwisp_controller/config/models.py (1)
  • Device (16-23)
tests/openwisp2/sample_config/models.py (1)
  • Device (26-32)
openwisp_controller/config/base/device.py (1)
  • _check_changed_fields (309-315)
🔇 Additional comments (1)
openwisp_controller/config/base/device.py (1)

342-344: Guarded update correctly avoids deferred-field lookup errors.

Line 342 now safely restricts updates to keys present in present_values, and Line 343 correctly stores the refreshed DB value in _initial_<field> before restoring the in-memory value.

Comment on lines +544 to +557
def test_deferred_fields_populated_correctly(self):
device = self._create_device(
name="deferred-test",
last_ip="172.16.0.1",
management_ip="10.0.0.1",
)
# Load the instance with deferred fields omitted
device = Device.objects.only("id").get(pk=device.pk)
device.last_ip = "172.16.1.1"
# Populate initial values for checked fields
device._check_changed_fields()
# Ensure `_initial_<field>` contains the actual value, not the field name
self.assertEqual(getattr(device, "_initial_last_ip"), "172.16.0.1")
self.assertNotEqual(getattr(device, "_initial_last_ip"), "last_ip")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Strengthen the regression test with a post-check value assertion.

This test verifies _initial_last_ip, but it should also assert that last_ip still equals the user-updated value after Line 554.

✅ Suggested test addition
         # Ensure `_initial_<field>` contains the actual value, not the field name
         self.assertEqual(getattr(device, "_initial_last_ip"), "172.16.0.1")
         self.assertNotEqual(getattr(device, "_initial_last_ip"), "last_ip")
+        self.assertEqual(device.last_ip, "172.16.1.1")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openwisp_controller/config/tests/test_device.py` around lines 544 - 557, The
test test_deferred_fields_populated_correctly should also assert that the
instance retains the user-updated value after calling
Device._check_changed_fields(); after invoking device._check_changed_fields(),
add an assertion that device.last_ip == "172.16.1.1" (while still asserting
getattr(device, "_initial_last_ip") == "172.16.0.1"), referencing the test
function name and the Device._check_changed_fields and _initial_last_ip
attributes to locate where to add the extra assertion.

@pandafy pandafy force-pushed the fix-device-get-deferred-values branch from 4145eed to 4ea7ace Compare March 19, 2026 10:39
@pandafy
Copy link
Copy Markdown
Member Author

pandafy commented Mar 19, 2026

@nemesifier this will needed to be backported to 1.2 branch.

@pandafy pandafy marked this pull request as ready for review March 19, 2026 10:42
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
openwisp_controller/config/tests/test_device.py (1)

554-557: ⚠️ Potential issue | 🟡 Minor

Add a post-check assertion for the current field value.

After Line 554, assert that device.last_ip is still "172.16.1.1" so the test verifies both sides of the contract (initial DB snapshot + preserved in-memory update).

Suggested test addition
         device._check_changed_fields()
         # Ensure `_initial_<field>` contains the actual value, not the field name
         self.assertEqual(getattr(device, "_initial_last_ip"), "172.16.0.1")
         self.assertNotEqual(getattr(device, "_initial_last_ip"), "last_ip")
+        self.assertEqual(device.last_ip, "172.16.1.1")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openwisp_controller/config/tests/test_device.py` around lines 554 - 557, Add
a post-check assertion to verify the current in-memory field value is preserved:
after calling device._check_changed_fields() and after asserting getattr(device,
"_initial_last_ip") equals "172.16.0.1", add an assertion that device.last_ip ==
"172.16.1.1" so the test validates both the stored initial snapshot
(_initial_last_ip) and the preserved in-memory update (last_ip).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@openwisp_controller/config/tests/test_device.py`:
- Around line 554-557: Add a post-check assertion to verify the current
in-memory field value is preserved: after calling device._check_changed_fields()
and after asserting getattr(device, "_initial_last_ip") equals "172.16.0.1", add
an assertion that device.last_ip == "172.16.1.1" so the test validates both the
stored initial snapshot (_initial_last_ip) and the preserved in-memory update
(last_ip).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2cb91baf-6ac8-4750-b141-52407df11385

📥 Commits

Reviewing files that changed from the base of the PR and between 4145eed and 4ea7ace.

📒 Files selected for processing (2)
  • openwisp_controller/config/base/device.py
  • openwisp_controller/config/tests/test_device.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.1.0
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}

📄 CodeRabbit inference engine (Custom checks)

**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}: Flag potential security vulnerabilities in code
Avoid unnecessary comments or docstrings for code that is already clear
Code formatting is compact and readable. Do not add excessive blank lines, especially inside function or method bodies
Flag unused or redundant code
Ensure variables, functions, classes, and files have descriptive and consistent names
New code must handle errors properly: log errors that cannot be resolved by the user with error level, log unusual conditions with warning level, log important background actions with info level, and provide user-facing messages for errors that the user can solve autonomously

Files:

  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/base/device.py
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp,sql}

📄 CodeRabbit inference engine (Custom checks)

Flag obvious performance regressions, such as heavy loops, repeated I/O, or unoptimized queries

Files:

  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/base/device.py
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp,sh,bash,sql}

📄 CodeRabbit inference engine (Custom checks)

Cryptic or non-obvious code (regex, complex bash commands, or hard-to-read code) must include a concise comment explaining why it is needed and why the complexity is acceptable

Files:

  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/base/device.py
**/*.{py,html}

📄 CodeRabbit inference engine (Custom checks)

For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework

Files:

  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/base/device.py
🧠 Learnings (5)
📚 Learning: 2026-01-12T22:27:48.342Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1175
File: tests/openwisp2/sample_config/migrations/0008_whoisinfo_organizationconfigsettings_whois_enabled.py:18-67
Timestamp: 2026-01-12T22:27:48.342Z
Learning: In tests/openwisp2/sample_config/models.py and corresponding test migrations, the WHOISInfo model intentionally includes an additional "details" field not present in the base AbstractWHOISInfo model. This is a testing pattern to verify that swappable models (CONFIG_WHOISINFO_MODEL) can be extended with custom fields without errors.

Applied to files:

  • openwisp_controller/config/tests/test_device.py
📚 Learning: 2026-01-15T15:05:49.557Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/management/commands/clear_last_ip.py:38-42
Timestamp: 2026-01-15T15:05:49.557Z
Learning: In Django projects, when using select_related() to traverse relations (for example, select_related("organization__config_settings")), the traversed relation must not be deferred. If you also use .only() in the same query, include the relation name or FK field (e.g., "organization" or "organization_id") in the .only() list to avoid the error "Field X cannot be both deferred and traversed using select_related at the same time." Apply this guideline to Django code in openwisp_controller/config/management/commands/clear_last_ip.py and similar modules by ensuring any select_related with an accompanying only() includes the related field names to prevent deferred/traversed conflicts.

Applied to files:

  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/base/device.py
📚 Learning: 2026-02-17T19:13:10.088Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/whois/commands.py:0-0
Timestamp: 2026-02-17T19:13:10.088Z
Learning: In reviews for the openwisp/openwisp-controller repository, do not propose changes based on Ruff warnings. The project does not use Ruff as its linter; ignore Ruff-related suggestions and follow the repository’s established linting and configuration rules. This guidance applies to all Python files under the openwisp_controller directory.

Applied to files:

  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/base/device.py
📚 Learning: 2026-01-15T15:07:17.354Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/geo/estimated_location/tests/tests.py:172-175
Timestamp: 2026-01-15T15:07:17.354Z
Learning: In this repository, flake8 enforces E501 (line too long) via setup.cfg (max-line-length = 88) while ruff ignores E501 via ruff.toml. Therefore, use '# noqa: E501' on lines that intentionally exceed 88 characters to satisfy flake8 without affecting ruff checks. This applies to Python files across the project (any .py) and is relevant for tests as well. Use sparingly and only where breaking lines is not feasible without hurting readability or functionality.

Applied to files:

  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/base/device.py
📚 Learning: 2026-01-15T14:06:53.460Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/geo/base/models.py:63-93
Timestamp: 2026-01-15T14:06:53.460Z
Learning: In openwisp_controller/geo/base/models.py, the BaseLocation.save() method protects the is_estimated field from manual changes when the estimated location feature is disabled by reverting it to its initial database value using the elif branch. This is intentional behavior to ensure the field can only be modified when the feature is properly configured.

Applied to files:

  • openwisp_controller/config/base/device.py
🧬 Code graph analysis (1)
openwisp_controller/config/tests/test_device.py (3)
openwisp_controller/config/tests/utils.py (1)
  • _create_device (31-44)
openwisp_controller/connection/tests/utils.py (1)
  • _create_device (51-56)
openwisp_controller/config/base/device.py (1)
  • _check_changed_fields (309-315)
🔇 Additional comments (1)
openwisp_controller/config/base/device.py (1)

341-343: Deferred-field restoration fix looks correct.

This correctly scopes restoration to present_values, captures refreshed DB values into _initial_<field>, and restores the in-memory value without the previous key-mismatch risk.

@openwisp-companion
Copy link
Copy Markdown

Test Failure in test_deferred_fields_populated_correctly

Hello @pandafy,
(Analysis for commit 4145eed)

The test test_deferred_fields_populated_correctly in openwisp_controller.config.tests.test_device failed with an AttributeError: 'Device' object has no attribute '_initial_last_ip'. The traceback suggests that the attribute _initial_last_ip was expected, but the error message hints at _initial_group_id as a possible alternative.

Fix:

It appears there's a mismatch between the expected attribute name in the test and the actual attribute available on the Device object. Please check the Device model definition and the test case to ensure consistency. It's likely that the attribute name in the test needs to be updated to _initial_group_id or the Device model needs to be adjusted if _initial_last_ip is indeed the intended attribute.

@openwisp-companion
Copy link
Copy Markdown

Test Failure in Device Field

Hello @pandafy,
(Analysis for commit 4ea7ace)

The CI failed due to an AttributeError in the test_deferred_fields_populated_correctly test. The test is trying to access an attribute _initial_last_ip on a Device object, but this attribute does not exist on the Device model.

Fix:

The test is likely attempting to verify the population of deferred fields. The attribute name _initial_last_ip seems to be incorrect. Please check the Device model or related code to find the correct attribute name for the deferred last_ip field and update the test assertion accordingly.

# Populate initial values for checked fields
device._check_changed_fields()
# Ensure `_initial_<field>` contains the actual value, not the field name
self.assertEqual(getattr(device, "_initial_last_ip"), "172.16.0.1")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test fails here, let's make sure the test fails with a clear error message without the patch.

device = Device.objects.only("id").get(pk=device.pk)
device.last_ip = "172.16.1.1"
# Populate initial values for checked fields
device._check_changed_fields()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to accomplish the same here with a public method? This would make the test more readable.

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.
@pandafy pandafy force-pushed the fix-device-get-deferred-values branch from 4ea7ace to fd4f6b3 Compare March 27, 2026 07:14
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
openwisp_controller/config/tests/test_device.py (1)

544-557: ⚠️ Potential issue | 🟠 Major

Exercise save() here instead of the private helper.

This only proves _check_changed_fields() in isolation. The regression happens on the public device.save() path, so this can stay green while deferred-field updates still lose the old DB value in production. Please drive the scenario through save() and assert an observable effect there, such as management_ip_changed signal args or WHOIS input.

Based on learnings: "Regression test must fail if the patch is removed or commented out."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openwisp_controller/config/tests/test_device.py` around lines 544 - 557,
Replace the direct call to the private helper `_check_changed_fields()` in the
test `test_deferred_fields_populated_correctly` with exercising the public
`Device.save()` path: load the instance via
`Device.objects.only("id").get(...)`, modify `device.last_ip`, then call
`device.save()` (or `device.save(update_fields=...)` as appropriate) and
capture/assert the observable effect (e.g., subscribe to the
`management_ip_changed` signal or inspect WHOIS input) to verify
`_initial_last_ip` is populated with the DB value ("172.16.0.1") and not the
field name; keep assertions on `getattr(device, "_initial_last_ip")` but drive
the population through `Device.save()` so the regression would fail if the fix
is removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@openwisp_controller/config/base/device.py`:
- Around line 340-343: The helper currently sets _initial_<field> after the DB
write (via refresh_from_db()), causing deferred updates to appear as
already-applied; move the logic that populates _initial_<field> so it runs
before the write and before _check_last_ip()/_check_changed_fields(): call
self.refresh_from_db(fields=present_values.keys()) and then run the loop that
does setattr(self, f"_initial_{field}", getattr(self, field)) prior to invoking
super().save() (or before the call site that triggers _check_last_ip()/
_check_changed_fields()), ensuring _initial_* holds the pre-save values for
present_values.

---

Duplicate comments:
In `@openwisp_controller/config/tests/test_device.py`:
- Around line 544-557: Replace the direct call to the private helper
`_check_changed_fields()` in the test `test_deferred_fields_populated_correctly`
with exercising the public `Device.save()` path: load the instance via
`Device.objects.only("id").get(...)`, modify `device.last_ip`, then call
`device.save()` (or `device.save(update_fields=...)` as appropriate) and
capture/assert the observable effect (e.g., subscribe to the
`management_ip_changed` signal or inspect WHOIS input) to verify
`_initial_last_ip` is populated with the DB value ("172.16.0.1") and not the
field name; keep assertions on `getattr(device, "_initial_last_ip")` but drive
the population through `Device.save()` so the regression would fail if the fix
is removed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e3129ff1-9de5-43bf-81a9-ed0b8bb32aa3

📥 Commits

Reviewing files that changed from the base of the PR and between 4ea7ace and fd4f6b3.

📒 Files selected for processing (2)
  • openwisp_controller/config/base/device.py
  • openwisp_controller/config/tests/test_device.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}

📄 CodeRabbit inference engine (Custom checks)

**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}: Flag potential security vulnerabilities in code
Avoid unnecessary comments or docstrings for code that is already clear
Code formatting is compact and readable. Do not add excessive blank lines, especially inside function or method bodies
Flag unused or redundant code
Ensure variables, functions, classes, and files have descriptive and consistent names
New code must handle errors properly: log errors that cannot be resolved by the user with error level, log unusual conditions with warning level, log important background actions with info level, and provide user-facing messages for errors that the user can solve autonomously

Files:

  • openwisp_controller/config/base/device.py
  • openwisp_controller/config/tests/test_device.py
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp,sql}

📄 CodeRabbit inference engine (Custom checks)

Flag obvious performance regressions, such as heavy loops, repeated I/O, or unoptimized queries

Files:

  • openwisp_controller/config/base/device.py
  • openwisp_controller/config/tests/test_device.py
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp,sh,bash,sql}

📄 CodeRabbit inference engine (Custom checks)

Cryptic or non-obvious code (regex, complex bash commands, or hard-to-read code) must include a concise comment explaining why it is needed and why the complexity is acceptable

Files:

  • openwisp_controller/config/base/device.py
  • openwisp_controller/config/tests/test_device.py
**/*.{py,html}

📄 CodeRabbit inference engine (Custom checks)

For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework

Files:

  • openwisp_controller/config/base/device.py
  • openwisp_controller/config/tests/test_device.py
🧠 Learnings (6)
📚 Learning: 2026-01-15T14:06:53.460Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/geo/base/models.py:63-93
Timestamp: 2026-01-15T14:06:53.460Z
Learning: In openwisp_controller/geo/base/models.py, the BaseLocation.save() method protects the is_estimated field from manual changes when the estimated location feature is disabled by reverting it to its initial database value using the elif branch. This is intentional behavior to ensure the field can only be modified when the feature is properly configured.

Applied to files:

  • openwisp_controller/config/base/device.py
📚 Learning: 2026-01-15T15:05:49.557Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/management/commands/clear_last_ip.py:38-42
Timestamp: 2026-01-15T15:05:49.557Z
Learning: In Django projects, when using select_related() to traverse relations (for example, select_related("organization__config_settings")), the traversed relation must not be deferred. If you also use .only() in the same query, include the relation name or FK field (e.g., "organization" or "organization_id") in the .only() list to avoid the error "Field X cannot be both deferred and traversed using select_related at the same time." Apply this guideline to Django code in openwisp_controller/config/management/commands/clear_last_ip.py and similar modules by ensuring any select_related with an accompanying only() includes the related field names to prevent deferred/traversed conflicts.

Applied to files:

  • openwisp_controller/config/base/device.py
  • openwisp_controller/config/tests/test_device.py
📚 Learning: 2026-02-17T19:13:10.088Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/whois/commands.py:0-0
Timestamp: 2026-02-17T19:13:10.088Z
Learning: In reviews for the openwisp/openwisp-controller repository, do not propose changes based on Ruff warnings. The project does not use Ruff as its linter; ignore Ruff-related suggestions and follow the repository’s established linting and configuration rules. This guidance applies to all Python files under the openwisp_controller directory.

Applied to files:

  • openwisp_controller/config/base/device.py
  • openwisp_controller/config/tests/test_device.py
📚 Learning: 2026-01-15T15:07:17.354Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/geo/estimated_location/tests/tests.py:172-175
Timestamp: 2026-01-15T15:07:17.354Z
Learning: In this repository, flake8 enforces E501 (line too long) via setup.cfg (max-line-length = 88) while ruff ignores E501 via ruff.toml. Therefore, use '# noqa: E501' on lines that intentionally exceed 88 characters to satisfy flake8 without affecting ruff checks. This applies to Python files across the project (any .py) and is relevant for tests as well. Use sparingly and only where breaking lines is not feasible without hurting readability or functionality.

Applied to files:

  • openwisp_controller/config/base/device.py
  • openwisp_controller/config/tests/test_device.py
📚 Learning: 2026-01-12T22:27:48.342Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1175
File: tests/openwisp2/sample_config/migrations/0008_whoisinfo_organizationconfigsettings_whois_enabled.py:18-67
Timestamp: 2026-01-12T22:27:48.342Z
Learning: In tests/openwisp2/sample_config/models.py and corresponding test migrations, the WHOISInfo model intentionally includes an additional "details" field not present in the base AbstractWHOISInfo model. This is a testing pattern to verify that swappable models (CONFIG_WHOISINFO_MODEL) can be extended with custom fields without errors.

Applied to files:

  • openwisp_controller/config/tests/test_device.py
📚 Learning: 2026-03-14T19:34:33.773Z
Learnt from: CR
Repo: openwisp/openwisp-controller PR: 0
File: coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt:0-0
Timestamp: 2026-03-14T19:34:33.773Z
Learning: Applies to **/*{.test,.spec,_test,test_}.{js,ts,tsx,jsx,py,java,go,cs,rb} : Regression test must fail if the patch is removed or commented out

Applied to files:

  • openwisp_controller/config/tests/test_device.py

Comment on lines 340 to +343
self.refresh_from_db(fields=present_values.keys())
for field in self._changed_checked_fields:
setattr(self, f"_initial_{field}", field)
setattr(self, field, present_values[field])
for field, value in present_values.items():
setattr(self, f"_initial_{field}", getattr(self, field))
setattr(self, field, value)
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

This still captures the post-save value on the public update path.

save() does not reach this helper until after super().save() on Line 290 via _check_changed_fields() on Line 299. For a deferred field changed through device.save(), refresh_from_db() now reads the just-written value, so _initial_<field> becomes the new value and _check_*_changed() / _check_last_ip() no-op.

Please populate _initial_* before the write (and before _check_last_ip() on Line 292), otherwise deferred updates are still missed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openwisp_controller/config/base/device.py` around lines 340 - 343, The helper
currently sets _initial_<field> after the DB write (via refresh_from_db()),
causing deferred updates to appear as already-applied; move the logic that
populates _initial_<field> so it runs before the write and before
_check_last_ip()/_check_changed_fields(): call
self.refresh_from_db(fields=present_values.keys()) and then run the loop that
does setattr(self, f"_initial_{field}", getattr(self, field)) prior to invoking
super().save() (or before the call site that triggers _check_last_ip()/
_check_changed_fields()), ensuring _initial_* holds the pre-save values for
present_values.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a deferred field changed through device.save(), refresh_from_db() now reads the just-written value, so initial becomes the new value and check*_changed() / _check_last_ip() no-op.

If a deferred field is updated in the database, the the intial_<field> shall reflect the latest value from the database. We are not interested in the original value of the field.

@coderabbitai is my assumption correct?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pandafy, yes, your assumption is correct. I need to retract my earlier concern.

Tracing the execution order in save():

  1. super().save() — writes the new value to the DB
  2. _check_last_ip() — for a deferred field, _initial_last_ip == models.DEFERRED, so it returns early (pre-existing guard)
  3. _check_changed_fields()_get_initial_values_for_checked_fields() — this is where the fix applies

After step 3, _initial_<field> is set to the just-saved DB value. This is intentionally consistent with how the non-deferred path works: _check_last_ip() and _check_management_ip_changed() both end with self._initial_<field> = self.<field>, i.e., the post-save current value, which becomes the baseline for the next comparison.

The fix in this PR correctly addresses the bug (the field name string being stored instead of the actual value), and the behavior of _initial_<field> reflecting the latest DB value after a save is the right design.

My original comment was mistaken in treating the "missed signal for deferred field on first save" as a problem introduced here — that's governed by the pre-existing if self._initial_last_ip == models.DEFERRED: return guard and is separate from the bug this PR fixes.


🧠 Learnings used
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/geo/base/models.py:63-93
Timestamp: 2026-01-15T14:06:53.460Z
Learning: In openwisp_controller/geo/base/models.py, the BaseLocation.save() method protects the is_estimated field from manual changes when the estimated location feature is disabled by reverting it to its initial database value using the elif branch. This is intentional behavior to ensure the field can only be modified when the feature is properly configured.

Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/management/commands/clear_last_ip.py:38-42
Timestamp: 2026-01-15T15:05:49.557Z
Learning: In Django projects, when using select_related() to traverse relations (for example, select_related("organization__config_settings")), the traversed relation must not be deferred. If you also use .only() in the same query, include the relation name or FK field (e.g., "organization" or "organization_id") in the .only() list to avoid the error "Field X cannot be both deferred and traversed using select_related at the same time." Apply this guideline to Django code in openwisp_controller/config/management/commands/clear_last_ip.py and similar modules by ensuring any select_related with an accompanying only() includes the related field names to prevent deferred/traversed conflicts.

Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/geo/estimated_location/tests/tests.py:172-175
Timestamp: 2026-01-15T15:07:17.354Z
Learning: In this repository, flake8 enforces E501 (line too long) via setup.cfg (max-line-length = 88) while ruff ignores E501 via ruff.toml. Therefore, use '# noqa: E501' on lines that intentionally exceed 88 characters to satisfy flake8 without affecting ruff checks. This applies to Python files across the project (any .py) and is relevant for tests as well. Use sparingly and only where breaking lines is not feasible without hurting readability or functionality.

Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/whois/commands.py:0-0
Timestamp: 2026-02-17T19:13:10.088Z
Learning: In reviews for the openwisp/openwisp-controller repository, do not propose changes based on Ruff warnings. The project does not use Ruff as its linter; ignore Ruff-related suggestions and follow the repository’s established linting and configuration rules. This guidance applies to all Python files under the openwisp_controller directory.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
openwisp_controller/config/tests/test_device.py (1)

544-557: ⚠️ Potential issue | 🟠 Major

Assert the original DB value in _initial_last_ip, not the updated value.

On Line 556, the expected value should be the pre-save persisted value ("172.16.0.1"). Also assert device.last_ip remains "172.16.1.1" after save() to verify restoration behavior.

Proposed fix
     def test_deferred_fields_populated_correctly(self):
         device = self._create_device(
             name="deferred-test",
             last_ip="172.16.0.1",
             management_ip="10.0.0.1",
         )
         # Load the instance with deferred fields omitted
         device = Device.objects.only("id").get(pk=device.pk)
         device.last_ip = "172.16.1.1"
         # Saving the device object will populate the deferred fields
         device.save()
-        # Ensure `_initial_<field>` contains the actual value, not the field name
-        self.assertEqual(getattr(device, "_initial_last_ip"), "172.16.1.1")
+        # Ensure `_initial_<field>` contains the original DB value, not the field name
+        self.assertEqual(getattr(device, "_initial_last_ip"), "172.16.0.1")
         self.assertNotEqual(getattr(device, "_initial_last_ip"), "last_ip")
+        self.assertEqual(device.last_ip, "172.16.1.1")

Based on learnings: "Regression test must fail if the patch is removed or commented out".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openwisp_controller/config/tests/test_device.py` around lines 544 - 557, The
test test_deferred_fields_populated_correctly currently asserts _initial_last_ip
equals the updated value; change it to assert that getattr(device,
"_initial_last_ip") == "172.16.0.1" (the pre-save DB value) and add an assertion
that device.last_ip == "172.16.1.1" after device.save() to confirm the
instance's last_ip was restored; update assertions around
Device.objects.only(...), device.save(), _initial_last_ip and device.last_ip
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@openwisp_controller/config/tests/test_device.py`:
- Around line 544-557: The test test_deferred_fields_populated_correctly
currently asserts _initial_last_ip equals the updated value; change it to assert
that getattr(device, "_initial_last_ip") == "172.16.0.1" (the pre-save DB value)
and add an assertion that device.last_ip == "172.16.1.1" after device.save() to
confirm the instance's last_ip was restored; update assertions around
Device.objects.only(...), device.save(), _initial_last_ip and device.last_ip
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 776ca9af-7716-4dfe-95ef-49846651cea6

📥 Commits

Reviewing files that changed from the base of the PR and between fd4f6b3 and 3e0db75.

📒 Files selected for processing (1)
  • openwisp_controller/config/tests/test_device.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}

📄 CodeRabbit inference engine (Custom checks)

**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}: Flag potential security vulnerabilities in code
Avoid unnecessary comments or docstrings for code that is already clear
Code formatting is compact and readable. Do not add excessive blank lines, especially inside function or method bodies
Flag unused or redundant code
Ensure variables, functions, classes, and files have descriptive and consistent names
New code must handle errors properly: log errors that cannot be resolved by the user with error level, log unusual conditions with warning level, log important background actions with info level, and provide user-facing messages for errors that the user can solve autonomously

Files:

  • openwisp_controller/config/tests/test_device.py
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp,sql}

📄 CodeRabbit inference engine (Custom checks)

Flag obvious performance regressions, such as heavy loops, repeated I/O, or unoptimized queries

Files:

  • openwisp_controller/config/tests/test_device.py
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp,sh,bash,sql}

📄 CodeRabbit inference engine (Custom checks)

Cryptic or non-obvious code (regex, complex bash commands, or hard-to-read code) must include a concise comment explaining why it is needed and why the complexity is acceptable

Files:

  • openwisp_controller/config/tests/test_device.py
**/*.{py,html}

📄 CodeRabbit inference engine (Custom checks)

For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework

Files:

  • openwisp_controller/config/tests/test_device.py
🧠 Learnings (5)
📚 Learning: 2026-01-12T22:27:48.342Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1175
File: tests/openwisp2/sample_config/migrations/0008_whoisinfo_organizationconfigsettings_whois_enabled.py:18-67
Timestamp: 2026-01-12T22:27:48.342Z
Learning: In tests/openwisp2/sample_config/models.py and corresponding test migrations, the WHOISInfo model intentionally includes an additional "details" field not present in the base AbstractWHOISInfo model. This is a testing pattern to verify that swappable models (CONFIG_WHOISINFO_MODEL) can be extended with custom fields without errors.

Applied to files:

  • openwisp_controller/config/tests/test_device.py
📚 Learning: 2026-03-14T19:34:33.773Z
Learnt from: CR
Repo: openwisp/openwisp-controller PR: 0
File: coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt:0-0
Timestamp: 2026-03-14T19:34:33.773Z
Learning: Applies to **/*{.test,.spec,_test,test_}.{js,ts,tsx,jsx,py,java,go,cs,rb} : Regression test must fail if the patch is removed or commented out

Applied to files:

  • openwisp_controller/config/tests/test_device.py
📚 Learning: 2026-01-15T15:05:49.557Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/management/commands/clear_last_ip.py:38-42
Timestamp: 2026-01-15T15:05:49.557Z
Learning: In Django projects, when using select_related() to traverse relations (for example, select_related("organization__config_settings")), the traversed relation must not be deferred. If you also use .only() in the same query, include the relation name or FK field (e.g., "organization" or "organization_id") in the .only() list to avoid the error "Field X cannot be both deferred and traversed using select_related at the same time." Apply this guideline to Django code in openwisp_controller/config/management/commands/clear_last_ip.py and similar modules by ensuring any select_related with an accompanying only() includes the related field names to prevent deferred/traversed conflicts.

Applied to files:

  • openwisp_controller/config/tests/test_device.py
📚 Learning: 2026-02-17T19:13:10.088Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/whois/commands.py:0-0
Timestamp: 2026-02-17T19:13:10.088Z
Learning: In reviews for the openwisp/openwisp-controller repository, do not propose changes based on Ruff warnings. The project does not use Ruff as its linter; ignore Ruff-related suggestions and follow the repository’s established linting and configuration rules. This guidance applies to all Python files under the openwisp_controller directory.

Applied to files:

  • openwisp_controller/config/tests/test_device.py
📚 Learning: 2026-01-15T15:07:17.354Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/geo/estimated_location/tests/tests.py:172-175
Timestamp: 2026-01-15T15:07:17.354Z
Learning: In this repository, flake8 enforces E501 (line too long) via setup.cfg (max-line-length = 88) while ruff ignores E501 via ruff.toml. Therefore, use '# noqa: E501' on lines that intentionally exceed 88 characters to satisfy flake8 without affecting ruff checks. This applies to Python files across the project (any .py) and is relevant for tests as well. Use sparingly and only where breaking lines is not feasible without hurting readability or functionality.

Applied to files:

  • openwisp_controller/config/tests/test_device.py

@openwisp-companion
Copy link
Copy Markdown

Test Failures in openwisp-controller

Hello @pandafy,
(Analysis for commit fd4f6b3)

There are two test failures in your commit:

  1. AttributeError: 'Device' object has no attribute '_initial_last_ip': This error occurs in openwisp_controller/config/tests/test_device.py at line 556. The test is trying to access _initial_last_ip on a Device object, but this attribute does not exist.
  • Fix: Review the Device model and the test case to ensure _initial_last_ip is correctly defined or accessed. If it's intended to be a private attribute for internal use, the test should not access it directly.
  1. openwisp_controller.config.models.Config.DoesNotExist: Config matching query does not exist.: This error occurs in openwisp_controller/subnet_division/tests/test_admin.py at line 365. The test attempts to retrieve a Config object based on a device name, but no matching Config is found in the database.
  • Fix: Ensure that a Config object is created and associated with the Device before the test attempts to query it. This might involve adjusting the test setup or the data creation logic.

@openwisp-companion
Copy link
Copy Markdown

Test Failures in CI Pipeline

Hello @pandafy,
(Analysis for commit 3e0db75)

There are two test failures in the CI pipeline:

  1. AttributeError: 'Device' object has no attribute '_initial_last_ip':
  • Failure: The test test_deferred_fields_populated_correctly in test_device.py is failing because it tries to access an attribute _initial_last_ip on a Device object, but this attribute does not exist.
  • Fix: This likely indicates a change in the Device model or how its attributes are handled. The test needs to be updated to reflect the correct attribute name or the logic that populates it.
  1. openwisp_controller.config.models.Config.DoesNotExist: Config matching query does not exist.:
  • Failure: The test test_vpn_template_switch_checksum_db in test_admin.py is failing because it attempts to retrieve a Config object that does not exist in the database based on the query criteria.
  • Fix: This suggests that the test setup or the test data is insufficient, or there's a logic error in the test where a Config object is expected but not created or found. The test needs to ensure that the Config object it queries for actually exists before attempting to retrieve it.

Please review the code related to Device attributes and Config object creation/retrieval in the respective test files to address these failures.

@pandafy pandafy force-pushed the fix-device-get-deferred-values branch from 3e0db75 to 1a5fb89 Compare March 27, 2026 09:35
@nemesifier nemesifier merged commit 0d17acd into master Mar 27, 2026
21 checks passed
@github-project-automation github-project-automation bot moved this from In progress to Done in OpenWISP Contributor's Board Mar 27, 2026
@nemesifier nemesifier deleted the fix-device-get-deferred-values branch March 27, 2026 16:28
@openwisp-companion
Copy link
Copy Markdown

❌ Cherry-pick to 1.2 failed due to conflicts. Please backport manually:

git fetch origin 1.2
git checkout -b backport/1301-to-1.2-1774630174 origin/1.2
git cherry-pick -x 0d17acdd75fa8d4bb49c47cf2e52516b72503568
# resolve conflicts
git cherry-pick --continue
git push origin backport/1301-to-1.2-1774630174

@openwisp-companion
Copy link
Copy Markdown

❌ Cherry-pick to 1.2 failed due to conflicts. Please backport manually:

git fetch origin 1.2
git checkout -b backport/1301-to-1.2-1774646484 origin/1.2
git cherry-pick -x 0d17acdd75fa8d4bb49c47cf2e52516b72503568
# resolve conflicts
git cherry-pick --continue
git push origin backport/1301-to-1.2-1774646484

@openwisp-companion
Copy link
Copy Markdown

❌ Cherry-pick to 1.2 failed due to conflicts. Please backport manually:

git fetch origin 1.2
git checkout -b backport/1301-to-1.2-1774655973 origin/1.2
git cherry-pick -x 0d17acdd75fa8d4bb49c47cf2e52516b72503568
# resolve conflicts
git cherry-pick --continue
git push origin backport/1301-to-1.2-1774655973

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Development

Successfully merging this pull request may close these issues.

3 participants