Skip to content

[fix] Skip WHOIS and estimated location for deactivated devices #1325#1340

Merged
nemesifier merged 13 commits into
openwisp:masterfrom
tomsebastian10:fix-whois-deactivated
Jun 2, 2026
Merged

[fix] Skip WHOIS and estimated location for deactivated devices #1325#1340
nemesifier merged 13 commits into
openwisp:masterfrom
tomsebastian10:fix-whois-deactivated

Conversation

@tomsebastian10
Copy link
Copy Markdown
Contributor

@tomsebastian10 tomsebastian10 commented Apr 23, 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.

Reference to Existing Issue

Closes #1325.

Description of Changes

This PR fixes an issue where WHOIS and Estimated Location lookups were still being triggered for deactivated devices.

Previously, the logic responsible for scheduling WHOIS lookup tasks (process_ip_data_and_location and update_whois_info) did not check whether the device was active. As a result, background tasks continued to run even when a device was deactivated, causing WHOIS and location data to be repopulated unintentionally.

This change introduces a guard condition to skip these operations when the device is inactive:
Added an early return in process_ip_data_and_location
Added an early return in update_whois_info

This ensures that:
No WHOIS lookup tasks are scheduled for deactivated devices
No unnecessary background processing occurs
Device state is respected consistently

…openwisp#1325

This commit adds regression tests to ensure that the WHOIS handling
for deactivated devices works as expected.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

Review Change Stack

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

This PR adds deactivation guards to prevent WHOIS and estimated-location features from triggering on deactivated devices. It introduces early-return checks in service methods (WHOISService, EstimatedLocationService) and Celery tasks (fetch_whois_details, manage_estimated_locations) that skip task scheduling or processing when a device is deactivated. Query methods are updated to include the _is_deactivated field for deactivation-aware filtering. Test coverage validates that both service methods and tasks correctly skip operations when devices are deactivated.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Suggested reviewers

  • nemesifier
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title follows the required format [fix] and clearly summarizes the main change: skipping WHOIS and estimated location operations for deactivated devices.
Linked Issues check ✅ Passed The PR addresses all requirements from issue #1325: adds guard conditions in process_ip_data_and_location and update_whois_info to prevent WHOIS lookups for deactivated devices, includes task-level guards in fetch_whois_details and manage_estimated_locations, and provides comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly related to preventing WHOIS and estimated location operations on deactivated devices; no unrelated or out-of-scope modifications are present.
Bug Fixes ✅ Passed Root cause fixed with is_deactivated() guards in service-level methods. Task-level guards provide defense-in-depth. Five regression tests verify fix with proper mocking, no timing/flakiness issues.
Description check ✅ Passed The pull request description provides a clear overview of the issue, the changes made, and their purpose, with all major required sections completed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@tomsebastian10 tomsebastian10 changed the title Fix whois deactivated fix(geo): skip WHOIS and estimated location on deactivated devices Apr 23, 2026
Trigger WHOIS lookup based on the conditions of `_need_whois_lookup`.
Tasks are triggered on commit to ensure redundant data is not created.
"""
if self.device.is_deactived():
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: Method name typo - is_deactived() should be is_deactivated() (missing 'a'). This will cause an AttributeError at runtime since the method doesn't exist.

Suggested change
if self.device.is_deactived():
if self.device.is_deactivated():

when the data is older than
``OPENWISP_CONTROLLER_WHOIS_REFRESH_THRESHOLD_DAYS``.
"""
if self.device.is_deactived():
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: Method name typo - is_deactived() should be is_deactivated() (missing 'a'). This will cause an AttributeError at runtime.

Suggested change
if self.device.is_deactived():
if self.device.is_deactivated():

from datetime import timedelta
from io import StringIO
from unittest import mock
from unittest import mock, mocke
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

TYPO: mocke should be removed - this is an unused import typo.

Suggested change
from unittest import mock, mocke
from unittest import mock

@kilo-code-bot
Copy link
Copy Markdown

kilo-code-bot Bot commented Apr 23, 2026

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Incremental Changes Reviewed

Since commit 6ace4369a147f982e9ace9f7934e998830c1f0f8:

openwisp_controller/config/whois/tasks.py (lines 75-77):

  • Added device.is_deactivated() check in fetch_whois_details() task
  • Prevents WHOIS lookup for deactivated devices at task execution time

openwisp_controller/geo/estimated_location/tasks.py (lines 124-128):

  • Added device.is_deactivated() check in manage_estimated_locations() task
  • Prevents estimated location processing for deactivated devices at task execution time
Verification

These incremental changes provide defense-in-depth for issue #1325:

  • ✅ Service layer checks is_deactivated() before triggering tasks
  • ✅ Task layer now also checks is_deactivated() at execution time
  • ✅ This covers the race condition where a device is deactivated after task is queued
  • ✅ Method name is correct: is_deactivated()
  • ✅ Logging is appropriate (info level for expected skips)

All modified methods now properly guard against processing deactivated devices.

Files Reviewed (2 files)
  • openwisp_controller/config/whois/tasks.py
  • openwisp_controller/geo/estimated_location/tasks.py

Reviewed by kimi-k2.5 · 120,072 tokens

…openwisp#1325

This commit adds regression tests to ensure that the WHOIS handling
for deactivated devices works as expected.
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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
openwisp_controller/config/whois/tests/tests.py (1)

24-32: ⚠️ Potential issue | 🟡 Minor

Duplicate imports — app_settings and WHOISService are each imported twice.

Lines 24/30 both import app_settings, and Lines 26/32 both import WHOISService. Keep one — the existing relative imports on Lines 30 and 32 are consistent with the rest of the file, so the newly-added absolute-path imports on Lines 24 and 26 should be removed.

🧹 Proposed fix
-from openwisp_controller.config import settings as app_settings
 from openwisp_controller.config.signals import whois_fetched, whois_lookup_skipped
-from openwisp_controller.config.whois.service import WHOISService
 from openwisp_utils.tests import SeleniumTestMixin, catch_signal

 from ....tests.utils import TestAdminMixin
 from ... import settings as app_settings
 from ..handlers import connect_whois_handlers
 from ..service import WHOISService
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openwisp_controller/config/whois/tests/tests.py` around lines 24 - 32, Remove
the duplicate absolute imports: delete the top-level imports "from
openwisp_controller.config import settings as app_settings" and "from
openwisp_controller.config.whois.service import WHOISService", keeping the
existing relative imports "from ... import settings as app_settings" and "from
..service import WHOISService" (so only one app_settings and one WHOISService
import remain consistent with the file).
openwisp_controller/config/whois/service.py (1)

228-248: ⚠️ Potential issue | 🟠 Major

Missing test coverage for the update_whois_info guard.

The PR description and issue #1325 call out both WHOIS code paths, and this method has the same early-return. The new test class only exercises process_ip_data_and_location. Please add a symmetric regression test that seeds a stale WHOISInfo record for the device's last_ip, marks the device deactivated, calls update_whois_info(), and asserts fetch_whois_details.delay is not scheduled.

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

In `@openwisp_controller/config/whois/service.py` around lines 228 - 248, Add a
regression test for WHOIS early-return: create a stale WHOISInfo record for the
device.last_ip (modified older than
OPENWISP_CONTROLLER_WHOIS_REFRESH_THRESHOLD_DAYS), set device.is_deactived() to
True (or mark the device deactivated state used by the code), ensure
device.last_ip is a valid public IP and is_whois_enabled is set appropriately,
then call WHOISService.update_whois_info() and assert that
fetch_whois_details.delay was NOT scheduled; locate logic in update_whois_info
and WHOISService._get_whois_info_from_db to seed the DB and mock/assert calls to
fetch_whois_details.delay.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@null`:
- Line 1: The PR contains a Git error message instead of code — replace the
erroneous file content with the actual implementation changes and include guard
checks in the two functions mentioned: in process_ip_data_and_location and in
update_whois_info add an early-return guard that checks the device's
active/deactivated state (e.g., device.is_active, device.status, or by loading
device by device_id and checking its active flag) and skip performing WHOIS
lookups when the device is deactivated; ensure these guards run before any
network/WHOIS calls and update tests/fixtures accordingly so the PR actually
contains the modified functions rather than the git error text.

In `@openwisp_controller/config/whois/service.py`:
- Around line 213-214: The guard is calling a non-existent method is_deactived()
which raises AttributeError and prevents WHOIS saves; replace both calls (in
process_ip_data_and_location and in update_whois_info) to use the correct Device
method is_deactivated() so the deactivated-device check actually runs; update
the occurrences of self.device.is_deactived() to self.device.is_deactivated() in
the methods process_ip_data_and_location and update_whois_info.

In `@openwisp_controller/config/whois/tests/tests.py`:
- Around line 1199-1217: The active-path test test_process_ip_runs_when_active
is nondeterministic: enable the organization's whois flag explicitly (set
org.config_settings.whois_enabled = True and save) before invoking WHOISService
so the check doesn't rely on global WHOIS_CONFIGURED; also ensure the device
creation/IP change that triggers process_ip_data_and_location happens inside the
patched context (or create/change the device after applying the `@mock.patch` for
fetch_whois_details.delay) so the mock observes the call; for
test_process_ip_skips_when_deactivated either call self.device.deactivate() or
add a brief comment explaining why setting self.device._is_deactivated = True
in-memory is acceptable, and keep assertions against
fetch_whois_details.delay.call_count as-is.
- Line 5: The import line incorrectly includes a non-existent symbol `mocke`,
causing ImportError during test collection; update the import to only import the
valid `mock` symbol (e.g., change `from unittest import mock, mocke` to `from
unittest import mock`) so the test module can load; reference the incorrect
symbol `mocke` and the import statement to locate and correct the issue.
- Around line 1193-1198: TestWHOISDeactivated fails because it calls
self._create_device() but does not inherit CreateWHOISMixin; update the class
declaration to include CreateWHOISMixin alongside TransactionTestCase (mirror
TestWHOISTransaction's bases) so the _create_device helper is available, then
run the tests to verify setUp runs without AttributeError.

---

Outside diff comments:
In `@openwisp_controller/config/whois/service.py`:
- Around line 228-248: Add a regression test for WHOIS early-return: create a
stale WHOISInfo record for the device.last_ip (modified older than
OPENWISP_CONTROLLER_WHOIS_REFRESH_THRESHOLD_DAYS), set device.is_deactived() to
True (or mark the device deactivated state used by the code), ensure
device.last_ip is a valid public IP and is_whois_enabled is set appropriately,
then call WHOISService.update_whois_info() and assert that
fetch_whois_details.delay was NOT scheduled; locate logic in update_whois_info
and WHOISService._get_whois_info_from_db to seed the DB and mock/assert calls to
fetch_whois_details.delay.

In `@openwisp_controller/config/whois/tests/tests.py`:
- Around line 24-32: Remove the duplicate absolute imports: delete the top-level
imports "from openwisp_controller.config import settings as app_settings" and
"from openwisp_controller.config.whois.service import WHOISService", keeping the
existing relative imports "from ... import settings as app_settings" and "from
..service import WHOISService" (so only one app_settings and one WHOISService
import remain consistent with the file).
🪄 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: 4efda95c-48aa-4af4-a43f-55d36ba1b331

📥 Commits

Reviewing files that changed from the base of the PR and between ceb63ad and b9666e5.

📒 Files selected for processing (3)
  • null
  • openwisp_controller/config/whois/service.py
  • openwisp_controller/config/whois/tests/tests.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). (12)
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.13 | django~=5.1.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.2.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Kilo Code Review
🧰 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/whois/service.py
  • openwisp_controller/config/whois/tests/tests.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/whois/service.py
  • openwisp_controller/config/whois/tests/tests.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/whois/service.py
  • openwisp_controller/config/whois/tests/tests.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/whois/service.py
  • openwisp_controller/config/whois/tests/tests.py
🧠 Learnings (8)
📓 Common learnings
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1315
File: openwisp_controller/geo/estimated_location/service.py:70-76
Timestamp: 2026-03-27T20:50:30.883Z
Learning: In openwisp/openwisp-controller, the WHOIS service (openwisp_controller/config/whois/) and the EstimatedLocationService (openwisp_controller/geo/estimated_location/service.py) only ever process public IP addresses. Logging these IPs in error/debug messages is acceptable and not a security concern. Do not flag logging of IP addresses in this codebase as a privacy or security issue.
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.
📚 Learning: 2026-01-15T14:06:44.800Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/geo/base/models.py:63-93
Timestamp: 2026-01-15T14:06:44.800Z
Learning: In openwisp_controller/geo/base/models.py, ensure that in BaseLocation.save(), when the estimated location feature is disabled, the is_estimated field is not overwritten by user input; instead, revert to its initial database value. This preserves the invariant that is_estimated can only change when the feature is enabled. Consider documenting this behavior and refactoring into a guard or helper to reduce future regressions.

Applied to files:

  • null
📚 Learning: 2026-03-17T09:20:04.547Z
Learnt from: pandafy
Repo: openwisp/openwisp-controller PR: 1292
File: openwisp_controller/connection/tasks.py:27-31
Timestamp: 2026-03-17T09:20:04.547Z
Learning: In openwisp_controller/connection/tasks.py, the update_config Celery task is defined to take a single argument device_id, which is consistently passed as a string (via str(device.pk)) from openwisp_controller/connection/apps.py. Do not flag str(device_id) in task["args"] as unreliable due to typed arguments, because the task arguments are always strings. When reviewing, treat this as a specific, file-scoped guideline: do not raise issues about string conversion or type of the device_id argument for this task; if needed, add a note in the codebase explaining the argument is intentionally a single string ID and that the calling site ensures string conversion.

Applied to files:

  • null
📚 Learning: 2026-03-27T20:50:26.240Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1315
File: openwisp_controller/geo/estimated_location/service.py:70-76
Timestamp: 2026-03-27T20:50:26.240Z
Learning: In openwisp-controller’s WHOIS and estimated-location services (openwisp_controller/config/whois/ and openwisp_controller/geo/estimated_location/), these components only process public IP addresses. When reviewing logs/error/debug messages in this area, treat logging the IP address as acceptable and do not flag it as a privacy/security concern—unless the logged value can originate from non-public/private IPs in that specific code path.

Applied to files:

  • openwisp_controller/config/whois/service.py
  • openwisp_controller/config/whois/tests/tests.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/whois/service.py
  • openwisp_controller/config/whois/tests/tests.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/whois/service.py
  • openwisp_controller/config/whois/tests/tests.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/whois/service.py
  • openwisp_controller/config/whois/tests/tests.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/whois/service.py
  • openwisp_controller/config/whois/tests/tests.py
🔇 Additional comments (2)
openwisp_controller/config/whois/tests/tests.py (1)

351-352: LGTM on the query-count bump.

Increase from 4 → 7 is consistent with the extra _is_deactivated=False filter now applied across the clear_last_ip command path (see openwisp_controller/config/whois/tasks.py:116-122).

openwisp_controller/config/whois/service.py (1)

208-226: The early return for deactivated devices is intentional design—deactivated devices should not trigger WHOIS processing or signal emission. The pattern is consistent across multiple methods in WHOISService (both process_ip_data_and_location and update_whois_info have identical early returns), and the existing test confirms this behavior is expected. Signal receivers should not expect notifications from deactivated devices as the intent is to suppress all related background tasks entirely.

Comment thread null Outdated
Comment thread openwisp_controller/config/whois/service.py Outdated
Comment thread openwisp_controller/config/whois/tests/tests.py Outdated
Comment thread openwisp_controller/config/whois/tests/tests.py Outdated
Comment thread openwisp_controller/config/whois/tests/tests.py Outdated
@openwisp-companion
Copy link
Copy Markdown

Flake8 Fails on Unused Imports

Hello @tomsebastian10,
(Analysis for commit b9666e5)

The CI build failed due to the following issues:

  1. Code Style/QA: Flake8 reported unused imports and redefinitions of variables.
  • Fix: Run openwisp-qa-format to automatically fix these issues. If the errors persist, manually remove the unused imports and fix the redefinitions.

The test suite also encountered an issue, but it appears to be related to the code style failures. Please address the code style issues and push a new commit.

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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
openwisp_controller/config/whois/tests/tests.py (1)

24-32: 🛠️ Refactor suggestion | 🟠 Major

Duplicate imports — remove the newly added absolute imports.

Lines 24 and 26 duplicate lines 30 and 32 (the relative imports from ... import settings as app_settings and from ..service import WHOISService resolve to the exact same symbols). The later relative imports shadow the new absolute ones, making lines 24 and 26 redundant.

♻️ Proposed fix
-from openwisp_controller.config import settings as app_settings
 from openwisp_controller.config.signals import whois_fetched, whois_lookup_skipped
-from openwisp_controller.config.whois.service import WHOISService
 from openwisp_utils.tests import SeleniumTestMixin, catch_signal

 from ....tests.utils import TestAdminMixin
 from ... import settings as app_settings
 from ..handlers import connect_whois_handlers
 from ..service import WHOISService

As per coding guidelines: "Flag unused or redundant code".

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

In `@openwisp_controller/config/whois/tests/tests.py` around lines 24 - 32, The
test file imports app_settings and WHOISService twice: the absolute imports
"from openwisp_controller.config import settings as app_settings" and "from
openwisp_controller.config.whois.service import WHOISService" are duplicated and
shadowed by the later relative imports "from ... import settings as
app_settings" and "from ..service import WHOISService"; remove the earlier
absolute import statements so only the relative imports for app_settings and
WHOISService remain to eliminate redundancy.
♻️ Duplicate comments (1)
openwisp_controller/config/whois/tests/tests.py (1)

1205-1211: 🧹 Nitpick | 🔵 Trivial

Prefer deactivate() over in-memory flag flip for realism.

Setting self.device._is_deactivated = True without persisting works only because WHOISService(self.device) below holds the same in-memory instance. Using self.device.deactivate() exercises the real deactivation path (save + signal) and would also catch regressions where a caller reloads the device from DB before invoking the service.

♻️ Proposed change
-    def test_process_ip_skips_when_deactivated(self, mock_task):
-        self.device._is_deactivated = True
-
-        service = WHOISService(self.device)
-        service.process_ip_data_and_location()
+    def test_process_ip_skips_when_deactivated(self, mock_task):
+        self.device.deactivate()
+        mock_task.reset_mock()  # deactivate() may trigger save-side effects
+
+        service = WHOISService(self.device)
+        service.process_ip_data_and_location()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openwisp_controller/config/whois/tests/tests.py` around lines 1205 - 1211,
The test currently flips the private in-memory flag self.device._is_deactivated;
instead call the public deactivation path by invoking self.device.deactivate()
(which persists and emits signals) before instantiating
WHOISService(self.device) to more realistically exercise deactivation and catch
reload/regression issues; update the test method
test_process_ip_skips_when_deactivated to use self.device.deactivate() and keep
the assertion that mock_task.call_count == 0 after
service.process_ip_data_and_location().
🤖 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/whois/service.py`:
- Around line 213-214: Add the same deactivation guard used in WHOIS: at the
start of EstimatedLocationService.trigger_estimated_location_task (or the method
that invokes it), call self.device.is_deactivated() and immediately return if
True so no Celery task or signals are enqueued; mirror the WHOIS pattern that
prevents _need_whois_lookup and transaction.on_commit from running for
deactivated devices.

In `@openwisp_controller/config/whois/tests/tests.py`:
- Around line 1213-1222: The test test_process_ip_runs_when_active currently
only asserts mock_task.call_count; update it to assert that
fetch_whois_details.delay was called with the expected device and IP so the
scheduled task is for the correct device/initial IP. After creating
WHOISService(self.device) and calling service.process_ip_data_and_location(),
replace or augment the count assertion with an assertion on
mock_task.assert_called_once_with(...) (or inspect mock_task.call_args/kwargs)
verifying the device identifier (e.g., self.device.pk or device.id as used by
the service) and the IP "8.8.8.8" are passed to fetch_whois_details.delay so the
call target and payload are validated.
- Around line 351-353: The test triggers an N+1 due to deferred access of the
_is_deactivated field: the clear_last_ip command loads devices with
.only("last_ip", "organization", "key") but saving each device calls
_check_last_ip() → process_ip_data_and_location() → is_deactivated(), which
dereferences _is_deactivated and issues one SELECT per device; update the device
queryset used by the clear_last_ip command (the .only(...) call) to include
"_is_deactivated" so the field is preloaded and avoids the extra queries.

---

Outside diff comments:
In `@openwisp_controller/config/whois/tests/tests.py`:
- Around line 24-32: The test file imports app_settings and WHOISService twice:
the absolute imports "from openwisp_controller.config import settings as
app_settings" and "from openwisp_controller.config.whois.service import
WHOISService" are duplicated and shadowed by the later relative imports "from
... import settings as app_settings" and "from ..service import WHOISService";
remove the earlier absolute import statements so only the relative imports for
app_settings and WHOISService remain to eliminate redundancy.

---

Duplicate comments:
In `@openwisp_controller/config/whois/tests/tests.py`:
- Around line 1205-1211: The test currently flips the private in-memory flag
self.device._is_deactivated; instead call the public deactivation path by
invoking self.device.deactivate() (which persists and emits signals) before
instantiating WHOISService(self.device) to more realistically exercise
deactivation and catch reload/regression issues; update the test method
test_process_ip_skips_when_deactivated to use self.device.deactivate() and keep
the assertion that mock_task.call_count == 0 after
service.process_ip_data_and_location().
🪄 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: a686e353-0710-4a86-9b42-c70f282c810f

📥 Commits

Reviewing files that changed from the base of the PR and between b9666e5 and 8d528e7.

📒 Files selected for processing (2)
  • openwisp_controller/config/whois/service.py
  • openwisp_controller/config/whois/tests/tests.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.13 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=4.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~=5.1.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/whois/service.py
  • openwisp_controller/config/whois/tests/tests.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/whois/service.py
  • openwisp_controller/config/whois/tests/tests.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/whois/service.py
  • openwisp_controller/config/whois/tests/tests.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/whois/service.py
  • openwisp_controller/config/whois/tests/tests.py
🧠 Learnings (6)
📓 Common learnings
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1315
File: openwisp_controller/geo/estimated_location/service.py:70-76
Timestamp: 2026-03-27T20:50:30.883Z
Learning: In openwisp/openwisp-controller, the WHOIS service (openwisp_controller/config/whois/) and the EstimatedLocationService (openwisp_controller/geo/estimated_location/service.py) only ever process public IP addresses. Logging these IPs in error/debug messages is acceptable and not a security concern. Do not flag logging of IP addresses in this codebase as a privacy or security issue.
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.
📚 Learning: 2026-03-27T20:50:26.240Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1315
File: openwisp_controller/geo/estimated_location/service.py:70-76
Timestamp: 2026-03-27T20:50:26.240Z
Learning: In openwisp-controller’s WHOIS and estimated-location services (openwisp_controller/config/whois/ and openwisp_controller/geo/estimated_location/), these components only process public IP addresses. When reviewing logs/error/debug messages in this area, treat logging the IP address as acceptable and do not flag it as a privacy/security concern—unless the logged value can originate from non-public/private IPs in that specific code path.

Applied to files:

  • openwisp_controller/config/whois/service.py
  • openwisp_controller/config/whois/tests/tests.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/whois/service.py
  • openwisp_controller/config/whois/tests/tests.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/whois/service.py
  • openwisp_controller/config/whois/tests/tests.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/whois/service.py
  • openwisp_controller/config/whois/tests/tests.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/whois/service.py
  • openwisp_controller/config/whois/tests/tests.py
🔇 Additional comments (1)
openwisp_controller/config/whois/service.py (1)

234-235: LGTM on the second guard.

Placed before the public-IP/enabled/staleness checks so no redundant work runs, and consistent with the guard in process_ip_data_and_location.

Comment thread openwisp_controller/config/whois/service.py
Comment thread openwisp_controller/config/whois/tests/tests.py Outdated
Comment thread openwisp_controller/config/whois/tests/tests.py Outdated
@openwisp-companion
Copy link
Copy Markdown

Flake8 and Commit Message Failures

Hello @tomsebastian10,
(Analysis for commit 8d528e7)

There are two issues with your commit:

  1. Code Style Failures: Your code has some style violations detected by Flake8.
  • Fix: Run openwisp-qa-format in your local environment to automatically fix most of these issues. For specific errors like F811 (redefinition of unused app_settings and WHOISService), you'll need to manually adjust the imports or variable names in ./openwisp_controller/config/whois/tests/tests.py.
  1. Commit Message Format: Your commit message does not follow the required format.
  • Fix: Please reformat your commit message. It should start with a tag in brackets, followed by a capitalized title and the issue number (if applicable), then a blank line, and finally the detailed description.

Here's an example of the correct format:

[tag] Capitalized short title #<issue_number>

Detailed description of the changes.

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
openwisp_controller/config/whois/tests/tests.py (1)

24-32: ⚠️ Potential issue | 🟡 Minor

Duplicate imports for app_settings and WHOISService.

Lines 24 and 26 are equivalent to the existing relative imports on lines 30 and 32 — ... resolves to openwisp_controller.config and ..service resolves to openwisp_controller.config.whois.service from this test module. The second import rebinds the same name, which flake8 will report as F811. Drop the new absolute imports (keep the existing relative ones to stay consistent with the surrounding imports) and keep only the genuinely new whois_fetched, whois_lookup_skipped import.

🧹 Proposed fix
-from openwisp_controller.config import settings as app_settings
 from openwisp_controller.config.signals import whois_fetched, whois_lookup_skipped
-from openwisp_controller.config.whois.service import WHOISService
 from openwisp_utils.tests import SeleniumTestMixin, catch_signal

 from ....tests.utils import TestAdminMixin
 from ... import settings as app_settings
 from ..handlers import connect_whois_handlers
 from ..service import WHOISService

As per coding guidelines: "Flag unused or redundant code".

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

In `@openwisp_controller/config/whois/tests/tests.py` around lines 24 - 32, The
test file has duplicate imports rebinding app_settings and WHOISService (the
absolute imports at the top shadow the existing relative imports from lines that
import app_settings and WHOISService); remove the redundant absolute imports
that rebind app_settings and WHOISService and keep the existing relative imports
(retain the relative imports of app_settings and WHOISService from the current
package) while keeping only the new signal imports (whois_fetched,
whois_lookup_skipped) from openwisp_controller.config.signals so there is no
F811 duplicate-import error.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@openwisp_controller/config/whois/tests/tests.py`:
- Around line 24-32: The test file has duplicate imports rebinding app_settings
and WHOISService (the absolute imports at the top shadow the existing relative
imports from lines that import app_settings and WHOISService); remove the
redundant absolute imports that rebind app_settings and WHOISService and keep
the existing relative imports (retain the relative imports of app_settings and
WHOISService from the current package) while keeping only the new signal imports
(whois_fetched, whois_lookup_skipped) from openwisp_controller.config.signals so
there is no F811 duplicate-import error.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 21a6e4db-6832-429e-946d-a61a2386ed64

📥 Commits

Reviewing files that changed from the base of the PR and between 8d528e7 and 7a02dde.

📒 Files selected for processing (3)
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.py
  • openwisp_controller/geo/estimated_location/service.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). (12)
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.1.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~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Kilo Code Review
🧰 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/geo/estimated_location/service.py
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.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/geo/estimated_location/service.py
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.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/geo/estimated_location/service.py
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.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/geo/estimated_location/service.py
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.py
🧠 Learnings (8)
📓 Common learnings
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1315
File: openwisp_controller/geo/estimated_location/service.py:70-76
Timestamp: 2026-03-27T20:50:30.883Z
Learning: In openwisp/openwisp-controller, the WHOIS service (openwisp_controller/config/whois/) and the EstimatedLocationService (openwisp_controller/geo/estimated_location/service.py) only ever process public IP addresses. Logging these IPs in error/debug messages is acceptable and not a security concern. Do not flag logging of IP addresses in this codebase as a privacy or security issue.
📚 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/geo/estimated_location/service.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/geo/estimated_location/service.py
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.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/geo/estimated_location/service.py
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.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/geo/estimated_location/service.py
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.py
📚 Learning: 2026-03-27T20:50:26.240Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1315
File: openwisp_controller/geo/estimated_location/service.py:70-76
Timestamp: 2026-03-27T20:50:26.240Z
Learning: In openwisp-controller’s WHOIS and estimated-location services (openwisp_controller/config/whois/ and openwisp_controller/geo/estimated_location/), these components only process public IP addresses. When reviewing logs/error/debug messages in this area, treat logging the IP address as acceptable and do not flag it as a privacy/security concern—unless the logged value can originate from non-public/private IPs in that specific code path.

Applied to files:

  • openwisp_controller/geo/estimated_location/service.py
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.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/whois/tests/tests.py
📚 Learning: 2026-03-17T09:20:10.456Z
Learnt from: pandafy
Repo: openwisp/openwisp-controller PR: 1292
File: openwisp_controller/connection/tasks.py:27-31
Timestamp: 2026-03-17T09:20:10.456Z
Learning: In `openwisp_controller/connection/tasks.py`, the `update_config` Celery task only accepts one argument `device_id`, which is always passed as a string (via `str(device.pk)`) from the call site in `openwisp_controller/connection/apps.py`. Do not flag `str(device_id) in task["args"]` as unreliable due to typed args — the args are always strings.

Applied to files:

  • openwisp_controller/config/whois/tests/tests.py
🔇 Additional comments (3)
openwisp_controller/geo/estimated_location/service.py (1)

65-67: LGTM — defense-in-depth guard for deactivated devices.

The early return mirrors the guard added in WHOISService and correctly short-circuits whois_fetched_handler / whois_lookup_skipped_handler paths that eventually call trigger_estimated_location_task. Even though process_ip_data_and_location already guards at the source, having this guard here prevents regressions if the task is ever triggered from a different code path.

openwisp_controller/config/whois/tests/tests.py (1)

1193-1225: LGTM — prior review feedback fully addressed.

  • The class now correctly inherits CreateWHOISMixin and WHOISTransactionMixin and invokes super().setUp(), so _create_device() is available.
  • setUp explicitly enables whois_enabled on the organization's config_settings, making the active-path test deterministic rather than relying on the global WHOIS_ENABLED default.
  • test_process_ip_runs_when_active now asserts kwargs via assert_called_once_with(...), providing a stronger regression signal than a bare call count.

One residual note (not a blocker): test_process_ip_skips_when_deactivated still mutates _is_deactivated in memory — this works because WHOISService holds the same instance, but self.device.deactivate() would be more faithful to the production flow.

openwisp_controller/config/whois/commands.py (1)

42-42: LGTM — correctly prevents the N+1 regression.

Including _is_deactivated in .only() preloads the field so that device.save()_check_last_ip()process_ip_data_and_location()is_deactivated() doesn't trigger a deferred-field SELECT per device during iteration. This keeps test_last_ip_management_command_queries at the expected 4 queries regardless of device count.

Based on learnings: "In Django projects, when using select_related() to traverse relations ... the traversed relation must not be deferred. If you also use .only() in the same query, include the relation name or FK field in the .only() list" — while this specific addition is for a performance reason rather than a traversal conflict, it's consistent with keeping .only() aligned with all fields accessed downstream of the queryset.

@openwisp-companion
Copy link
Copy Markdown

Flake8 F811 Redefinition Errors

Hello @tomsebastian10,
(Analysis for commit 7a02dde)

There are F811 errors reported by Flake8 in your commit. This error indicates that a variable or function is being redefined in the same scope.

Error: F811 redefinition of unused 'app_settings' from line 24
File: openwisp_controller/config/whois/tests/tests.py, line 30

Error: F811 redefinition of unused 'WHOISService' from line 26
File: openwisp_controller/config/whois/tests/tests.py, line 32

Fix:
Please review the test file openwisp_controller/config/whois/tests/tests.py and rename the variables or functions that are causing these redefinition errors. Ensure that each variable and function has a unique name within its scope.

@tomsebastian10 tomsebastian10 force-pushed the fix-whois-deactivated branch from 7a02dde to 75273f8 Compare April 24, 2026 13:39
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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
openwisp_controller/geo/estimated_location/service.py (1)

65-79: ⚠️ Potential issue | 🟡 Minor

Add a test for the deactivation guard in trigger_estimated_location_task.

The guard is exercised only indirectly through WHOIS signal handlers; no test in openwisp_controller/geo/estimated_location/tests/ directly verifies that current_app.send_task is not called when a device is deactivated. A dedicated test should deactivate a device and assert the task is not enqueued, ensuring that future refactors (e.g., the handler being called from another code path) do not silently bypass this safety check.

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

In `@openwisp_controller/geo/estimated_location/service.py` around lines 65 - 79,
Write a new unit test that directly exercises
EstimatedLocationService.trigger_estimated_location_task to assert it does not
enqueue a task when the device is deactivated: instantiate or retrieve a Device,
call device.deactivate() (or set the state so device.is_deactivated() returns
True), call
EstimatedLocationService(device).trigger_estimated_location_task(ip_address),
and assert current_app.send_task was not called (use patch/mock on
current_app.send_task or task sending mechanism); reference the service method
trigger_estimated_location_task, the guard device.is_deactivated, and the call
to current_app.send_task (task name whois_estimated_location_task) so the test
fails if the guard 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/whois/commands.py`:
- Around line 40-42: The comment should be extended to explain why
"_is_deactivated" is included in the .only(...) call: because the per-device
device.save() within the loop flows into process_ip_data_and_location(), which
calls is_deactivated() and would otherwise trigger a SELECT per device for a
deferred field; update the comment near the .only("last_ip", "organization",
"key", "_is_deactivated") line to note that "_is_deactivated" is required to
avoid reintroducing an N+1 query (see is_deactivated(),
process_ip_data_and_location(), and test_last_ip_management_command_queries for
context).

In `@openwisp_controller/config/whois/tests/tests.py`:
- Around line 1203-1204: The test test_process_ip_skips_when_deactivated mutates
the private flag self.device._is_deactivated instead of exercising the real
deactivation flow; replace that in-memory assignment with a call to
self.device.deactivate() so the device's last_ip/management_ip are cleared and
the model is persisted (this mirrors production behavior when WHOISService may
fetch a fresh instance), or if you intentionally must keep the in-memory toggle,
add a short comment explaining why the real deactivate() cannot be used.
- Around line 1191-1223: Test coverage missing for the deactivation guard in
WHOISService.update_whois_info: add a test that (1) creates/updates a WHOISInfo
for the device's public last_ip with a timestamp older than
WHOIS_REFRESH_THRESHOLD_DAYS, (2) sets self.device._is_deactivated = True, (3)
instantiates WHOISService(self.device) and calls service.update_whois_info(),
and (4) asserts that fetch_whois_details.delay was not called (mocking
"openwisp_controller.config.whois.service.fetch_whois_details.delay"); reference
WHOISService.update_whois_info, WHOISInfo, and WHOIS_REFRESH_THRESHOLD_DAYS to
locate the logic to exercise.
- Around line 1201-1209: The test test_process_ip_skips_when_deactivated is not
asserting the deactivation guard because the device created in setUp has
last_ip=None; set a public last_ip on the device and save it before
instantiating WHOISService so _need_whois_lookup sees a valid IP, then mark the
device deactivated (self.device._is_deactivated = True or use is_deactivated)
and call WHOISService(self.device).process_ip_data_and_location(); this mirrors
test_process_ip_runs_when_active and ensures the only reason the task is not
scheduled is the deactivation guard in
WHOISService.process_ip_data_and_location/_need_whois_lookup.

---

Outside diff comments:
In `@openwisp_controller/geo/estimated_location/service.py`:
- Around line 65-79: Write a new unit test that directly exercises
EstimatedLocationService.trigger_estimated_location_task to assert it does not
enqueue a task when the device is deactivated: instantiate or retrieve a Device,
call device.deactivate() (or set the state so device.is_deactivated() returns
True), call
EstimatedLocationService(device).trigger_estimated_location_task(ip_address),
and assert current_app.send_task was not called (use patch/mock on
current_app.send_task or task sending mechanism); reference the service method
trigger_estimated_location_task, the guard device.is_deactivated, and the call
to current_app.send_task (task name whois_estimated_location_task) so the test
fails if the guard 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: 859dd8f1-01f0-4df2-9986-15fb37eec4a3

📥 Commits

Reviewing files that changed from the base of the PR and between 7a02dde and 75273f8.

📒 Files selected for processing (3)
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.py
  • openwisp_controller/geo/estimated_location/service.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). (12)
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.12 | 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.10 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Kilo Code Review
🧰 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/geo/estimated_location/service.py
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.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/geo/estimated_location/service.py
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.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/geo/estimated_location/service.py
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.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/geo/estimated_location/service.py
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.py
🧠 Learnings (8)
📓 Common learnings
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1315
File: openwisp_controller/geo/estimated_location/service.py:70-76
Timestamp: 2026-03-27T20:50:30.883Z
Learning: In openwisp/openwisp-controller, the WHOIS service (openwisp_controller/config/whois/) and the EstimatedLocationService (openwisp_controller/geo/estimated_location/service.py) only ever process public IP addresses. Logging these IPs in error/debug messages is acceptable and not a security concern. Do not flag logging of IP addresses in this codebase as a privacy or security issue.
📚 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/geo/estimated_location/service.py
📚 Learning: 2026-03-17T09:20:10.456Z
Learnt from: pandafy
Repo: openwisp/openwisp-controller PR: 1292
File: openwisp_controller/connection/tasks.py:27-31
Timestamp: 2026-03-17T09:20:10.456Z
Learning: In `openwisp_controller/connection/tasks.py`, the `update_config` Celery task only accepts one argument `device_id`, which is always passed as a string (via `str(device.pk)`) from the call site in `openwisp_controller/connection/apps.py`. Do not flag `str(device_id) in task["args"]` as unreliable due to typed args — the args are always strings.

Applied to files:

  • openwisp_controller/geo/estimated_location/service.py
  • openwisp_controller/config/whois/tests/tests.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/geo/estimated_location/service.py
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.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/geo/estimated_location/service.py
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.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/geo/estimated_location/service.py
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.py
📚 Learning: 2026-03-27T20:50:26.240Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1315
File: openwisp_controller/geo/estimated_location/service.py:70-76
Timestamp: 2026-03-27T20:50:26.240Z
Learning: In openwisp-controller’s WHOIS and estimated-location services (openwisp_controller/config/whois/ and openwisp_controller/geo/estimated_location/), these components only process public IP addresses. When reviewing logs/error/debug messages in this area, treat logging the IP address as acceptable and do not flag it as a privacy/security concern—unless the logged value can originate from non-public/private IPs in that specific code path.

Applied to files:

  • openwisp_controller/geo/estimated_location/service.py
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.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/whois/tests/tests.py

Comment thread openwisp_controller/config/whois/commands.py Outdated
Comment thread openwisp_controller/config/whois/tests/tests.py Outdated
Comment thread openwisp_controller/config/whois/tests/tests.py Outdated
Comment thread openwisp_controller/config/whois/tests/tests.py Outdated
@openwisp-companion
Copy link
Copy Markdown

Test Failure in test_process_ip_runs_when_active

Hello @tomsebastian10,
(Analysis for commit 75273f8)

A AttributeError: 'Device' object has no attribute '_initial_last_ip' occurred in the test_process_ip_runs_when_active test.

This indicates that the Device model is missing the _initial_last_ip attribute, which is likely expected by the process_ip_data_and_location method.

To fix this:

  1. Add _initial_last_ip to the Device model: This might involve adding it as a field or ensuring it's set correctly during object creation or in a __init__ method.
  2. Review the test setup: Ensure that the Device object used in the test has this attribute properly initialized before service.process_ip_data_and_location() is called.

@tomsebastian10 tomsebastian10 force-pushed the fix-whois-deactivated branch from 75273f8 to 2eeff23 Compare April 24, 2026 14:15
Comment thread openwisp_controller/config/whois/tests/tests.py Outdated
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: 2

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

1242-1243: ⚠️ Potential issue | 🔴 Critical

Typo erviceNameError at runtime; this test cannot pass.

Line 1242 defines ervice (missing s) while line 1243 references service. service.update_whois_info() will raise NameError: name 'service' is not defined, so this test never reaches its assert_not_called() check.

🐛 Proposed fix
-        ervice = WHOISService(self.device)
+        service = WHOISService(self.device)
         service.update_whois_info()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openwisp_controller/config/whois/tests/tests.py` around lines 1242 - 1243,
There's a typo: the variable is created as "ervice = WHOISService(self.device)"
but later referenced as "service.update_whois_info()", causing a NameError;
rename the declared variable to "service" (or update the later reference to
"ervice") so the WHOISService instance assigned to service is the one whose
update_whois_info() method is called, ensuring the test proceeds to the
subsequent assert_not_called() check (look for WHOISService and
update_whois_info in this test).
openwisp_controller/config/whois/commands.py (1)

40-42: 🧹 Nitpick | 🔵 Trivial

Extend the comment to also justify _is_deactivated.

The existing comment only motivates the organization entry. Without a note, a future cleanup could silently drop _is_deactivated and reintroduce the N+1 on the device.save()process_ip_data_and_location()is_deactivated() path that test_last_ip_management_command_queries pins at 4 queries.

♻️ Proposed comment tweak
-        # include the FK field 'organization' in .only() so the related
-        # `organization__config_settings` traversal is not deferred
+        # Include the FK 'organization' so the `organization__config_settings`
+        # traversal is not deferred, and '_is_deactivated' so the per-device
+        # save() -> process_ip_data_and_location() -> is_deactivated() path
+        # does not trigger an N+1 on the deferred field.
         .only("last_ip", "organization", "key", "_is_deactivated")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openwisp_controller/config/whois/commands.py` around lines 40 - 42, The
comment explaining the .only("last_ip", "organization", "key",
"_is_deactivated") call is incomplete; extend it to also justify including
"_is_deactivated" so future maintainers don't remove it and reintroduce an N+1
during device.save() → process_ip_data_and_location() → is_deactivated(); update
the comment to state that "_is_deactivated" is eagerly loaded to avoid extra DB
hits observed by test_last_ip_management_command_queries and to preserve the
intended query count when traversing organization__config_settings as well.
🤖 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/whois/tests/tests.py`:
- Around line 1219-1231: Add a one-line clarifying comment in the
test_process_ip_runs_when_active test explaining that because this test class
uses TransactionTestCase, transaction.on_commit callbacks run immediately (there
is no enclosing atomic block) and that saving the device synchronizes
_initial_last_ip with last_ip, which is why the mock_task assertion succeeds;
reference the test name (test_process_ip_runs_when_active),
WHOISService.process_ip_data_and_location, and the _initial_last_ip/on_commit
behavior to make future migration to TestCase (which defers on_commit) obvious.
- Around line 1233-1245: The test test_update_whois_skips_when_deactivated is
invalid because it never exercises the deactivation branch; fix it by creating a
WHOISInfo row for the device IP that is expired (so update_whois_info would
attempt to schedule work), correct the typo "ervice" → "service", then assert
fetch_whois_details.delay is called for the active device and after calling
self.device.deactivate() call WHOISService(self.device).update_whois_info()
again and assert delay is not called; target WHOISService.update_whois_info, the
WHOISInfo test fixture/creation, and the mocked fetch_whois_details.delay in
your changes.

---

Duplicate comments:
In `@openwisp_controller/config/whois/commands.py`:
- Around line 40-42: The comment explaining the .only("last_ip", "organization",
"key", "_is_deactivated") call is incomplete; extend it to also justify
including "_is_deactivated" so future maintainers don't remove it and
reintroduce an N+1 during device.save() → process_ip_data_and_location() →
is_deactivated(); update the comment to state that "_is_deactivated" is eagerly
loaded to avoid extra DB hits observed by
test_last_ip_management_command_queries and to preserve the intended query count
when traversing organization__config_settings as well.

In `@openwisp_controller/config/whois/tests/tests.py`:
- Around line 1242-1243: There's a typo: the variable is created as "ervice =
WHOISService(self.device)" but later referenced as
"service.update_whois_info()", causing a NameError; rename the declared variable
to "service" (or update the later reference to "ervice") so the WHOISService
instance assigned to service is the one whose update_whois_info() method is
called, ensuring the test proceeds to the subsequent assert_not_called() check
(look for WHOISService and update_whois_info in this test).
🪄 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: ae9ec19c-6928-40c3-bee7-ba3c3a28eab1

📥 Commits

Reviewing files that changed from the base of the PR and between 75273f8 and 2eeff23.

📒 Files selected for processing (3)
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.py
  • openwisp_controller/geo/estimated_location/service.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.13 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.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/whois/commands.py
  • openwisp_controller/geo/estimated_location/service.py
  • openwisp_controller/config/whois/tests/tests.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/whois/commands.py
  • openwisp_controller/geo/estimated_location/service.py
  • openwisp_controller/config/whois/tests/tests.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/whois/commands.py
  • openwisp_controller/geo/estimated_location/service.py
  • openwisp_controller/config/whois/tests/tests.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/whois/commands.py
  • openwisp_controller/geo/estimated_location/service.py
  • openwisp_controller/config/whois/tests/tests.py
🧠 Learnings (8)
📓 Common learnings
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1315
File: openwisp_controller/geo/estimated_location/service.py:70-76
Timestamp: 2026-03-27T20:50:30.883Z
Learning: In openwisp/openwisp-controller, the WHOIS service (openwisp_controller/config/whois/) and the EstimatedLocationService (openwisp_controller/geo/estimated_location/service.py) only ever process public IP addresses. Logging these IPs in error/debug messages is acceptable and not a security concern. Do not flag logging of IP addresses in this codebase as a privacy or security issue.
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.
📚 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/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.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/whois/commands.py
  • openwisp_controller/geo/estimated_location/service.py
  • openwisp_controller/config/whois/tests/tests.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/whois/commands.py
  • openwisp_controller/geo/estimated_location/service.py
  • openwisp_controller/config/whois/tests/tests.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/whois/commands.py
  • openwisp_controller/geo/estimated_location/service.py
  • openwisp_controller/config/whois/tests/tests.py
📚 Learning: 2026-03-27T20:50:26.240Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1315
File: openwisp_controller/geo/estimated_location/service.py:70-76
Timestamp: 2026-03-27T20:50:26.240Z
Learning: In openwisp-controller’s WHOIS and estimated-location services (openwisp_controller/config/whois/ and openwisp_controller/geo/estimated_location/), these components only process public IP addresses. When reviewing logs/error/debug messages in this area, treat logging the IP address as acceptable and do not flag it as a privacy/security concern—unless the logged value can originate from non-public/private IPs in that specific code path.

Applied to files:

  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/geo/estimated_location/service.py
  • openwisp_controller/config/whois/tests/tests.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/geo/estimated_location/service.py
📚 Learning: 2026-03-17T09:20:10.456Z
Learnt from: pandafy
Repo: openwisp/openwisp-controller PR: 1292
File: openwisp_controller/connection/tasks.py:27-31
Timestamp: 2026-03-17T09:20:10.456Z
Learning: In `openwisp_controller/connection/tasks.py`, the `update_config` Celery task only accepts one argument `device_id`, which is always passed as a string (via `str(device.pk)`) from the call site in `openwisp_controller/connection/apps.py`. Do not flag `str(device_id) in task["args"]` as unreliable due to typed args — the args are always strings.

Applied to files:

  • openwisp_controller/geo/estimated_location/service.py
  • openwisp_controller/config/whois/tests/tests.py
🔇 Additional comments (1)
openwisp_controller/geo/estimated_location/service.py (1)

66-67: LGTM!

The deactivation guard at the entry of trigger_estimated_location_task correctly short-circuits task enqueueing and mirrors the same pattern introduced in openwisp_controller/config/whois/service.py (process_ip_data_and_location and update_whois_info). Placing the check before the try/except send_task block is the right spot — callers in handlers.py (whois_fetched_handler and whois_lookup_skipped_handler) don't need to duplicate the check, and no Celery task is scheduled for deactivated devices.

Comment thread openwisp_controller/config/whois/tests/tests.py Outdated
Comment thread openwisp_controller/config/whois/tests/tests.py Outdated
@openwisp-companion
Copy link
Copy Markdown

Flake8 Errors in openwisp_controller/config/whois/tests/tests.py

Hello @tomsebastian10,
(Analysis for commit 2eeff23)

  • Code Style/QA: There are two Flake8 errors in openwisp_controller/config/whois/tests/tests.py:
  • F841 local variable 'ervice' is assigned to but never used
  • F821 undefined name 'service'

To fix this, please correct the typo in the variable name ervice to service and ensure it is used or remove it if it's unused.

@tomsebastian10 tomsebastian10 force-pushed the fix-whois-deactivated branch from 2eeff23 to eadb209 Compare April 24, 2026 15:10
@openwisp-companion
Copy link
Copy Markdown

Test Failures in openwisp_controller.config.whois.tests.tests

Hello @tomsebastian10,
(Analysis for commit eadb209)

There are three test failures in the openwisp_controller.config.whois.tests.tests module:

  1. test_process_ip_runs_when_active
  2. test_process_ip_skips_when_deactivated
  3. test_update_whois_skips_when_deactivated

All three tests fail with an AttributeError: 'Device' object has no attribute '_initial_last_ip'. This indicates that the Device model is missing the _initial_last_ip attribute, which is being accessed in the _check_last_ip method within openwisp_controller/config/base/device.py.

To fix this:
You need to add the _initial_last_ip attribute to the Device model. Based on the traceback, it seems like this attribute might be intended for internal use within the Device class, possibly for tracking changes to the last_ip field. You might need to initialize it in the __init__ method of the Device model or ensure it's set correctly during object creation or when the last_ip field is modified.

Please review the openwisp_controller/config/base/device.py file and the Device model definition to correctly add and initialize _initial_last_ip.

@tomsebastian10 tomsebastian10 force-pushed the fix-whois-deactivated branch from eadb209 to 8633a01 Compare April 24, 2026 16:05
@openwisp-companion
Copy link
Copy Markdown

Flake8 F821 Error in openwisp_controller/config/whois/tests/tests.py

Hello @tomsebastian10,

(Analysis for commit 8633a01)

The CI failed due to a flake8 error (F821: undefined name 'ip') in the test file openwisp_controller/config/whois/tests/tests.py on line 1247.

Fix:
The error indicates that the variable ip is used without being defined. Please ensure that ip is properly defined or imported before being used in the test. You might need to add an import statement or initialize the variable within the test scope.

@tomsebastian10 tomsebastian10 force-pushed the fix-whois-deactivated branch from 8633a01 to 6e6f41e Compare April 24, 2026 16:26
@openwisp-companion
Copy link
Copy Markdown

Test Failure in test_vpn_template_switch_checksum_db

Hello @tomsebastian10,
(Analysis for commit 6e6f41e)

The test test_vpn_template_switch_checksum_db failed because it could not find a Config object with the specified device name. This indicates that the test setup might be missing a required Config entry or the params dictionary is not correctly populated.

Fix:
Ensure that a Config object associated with the device name specified in params["name"] exists before the test attempts to retrieve it. You might need to create a Config instance with a corresponding Device in your test setup.

@tomsebastian10 tomsebastian10 force-pushed the fix-whois-deactivated branch from 6e6f41e to 2504f10 Compare April 24, 2026 17:19
@tomsebastian10
Copy link
Copy Markdown
Contributor Author

The remaining CI failure appears unrelated to the changes in this PR.
From the logs, the failure is caused by a dependency resolution conflicts.
Please let me know if you'd still like me to investigate further from the PR side.

@openwisp-companion
Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3).

@pandafy
Copy link
Copy Markdown
Member

pandafy commented Jun 1, 2026

@coderabbitai full review!

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get the PR diff
gh pr view 1340 --repo openwisp/openwisp-controller --json files,body,title

# Get the diff
gh pr diff 1340 --repo openwisp/openwisp-controller

Length of output: 20396


🏁 Script executed:

#!/bin/bash
# Check which test class the new tests are added to
# Look at lines around test_get_whois_info_when_not_configured
rg -n "class Test|def test_get_whois_info_when_not_configured|def test_process_ip_skips|def test_process_ip_runs|def test_update_whois_skips|TransactionTestCase|TestCase" openwisp_controller/config/whois/tests/tests.py | head -80

# Check service.py for _need_whois_lookup implementation
rg -n "_need_whois_lookup|_initial_last_ip|is_deactivated" openwisp_controller/config/whois/service.py -A3

# Check estimated_location/service.py for is_deactivated usage
rg -n "is_deactivated|_is_deactivated" openwisp_controller/geo/estimated_location/service.py

# Check views.py for how save() triggers signals related to is_deactivated
rg -n "is_deactivated|_is_deactivated" openwisp_controller/config/controller/views.py

# Check if there are similar .only() queries that might be missing _is_deactivated
rg -n "\.only(" openwisp_controller/config/whois/ openwisp_controller/geo/estimated_location/ -A2

# Check the whois_lookup_skipped signal handler
rg -n "whois_lookup_skipped|estimated_location" openwisp_controller/config/whois/service.py -A5

# Check what fields _is_deactivated is a property/field
rg -n "_is_deactivated|is_deactivated" openwisp_controller/config/models.py -A3 2>/dev/null | head -40
rg -rn "def is_deactivated" openwisp_controller/ -A5

# Check if estimated_location tasks.py has .only() queries that might need _is_deactivated
rg -n "\.only(" openwisp_controller/geo/estimated_location/tasks.py -A3

Length of output: 5527


🏁 Script executed:

#!/bin/bash
# Check which class test_process_ip_skips_when_deactivated belongs to (lines 1090-1170)
sed -n '1090,1170p' openwisp_controller/config/whois/tests/tests.py

# Check the TestWHOISTransaction class definition and what it inherits
sed -n '427,440p' openwisp_controller/config/whois/tests/tests.py

# Check _need_whois_lookup implementation
sed -n '177,210p' openwisp_controller/config/whois/service.py

# Check is_deactivated in device.py
rg -n "is_deactivated|_is_deactivated" openwisp_controller/config/base/device.py -A3 | head -40

# Check process_ip_data_and_location - the full lambda to see what initial_ip is passed
sed -n '208,235p' openwisp_controller/config/whois/service.py

# Check if any .only() queries in estimated_location tasks are missing _is_deactivated
rg -n "\.only\(" openwisp_controller/geo/estimated_location/tasks.py -A2

# Check if any .only() queries in whois are missing _is_deactivated
rg -n "\.only\(" openwisp_controller/config/whois/ -A2

# Check estimated location task manage_estimated_locations for full context
sed -n '110,145p' openwisp_controller/geo/estimated_location/tasks.py

# Check if test_process_ip_runs_when_active uses a TransactionTestCase
# by checking the full class containing those tests
sed -n '427,435p' openwisp_controller/config/whois/tests/tests.py

Length of output: 12310



@pandafy: I've completed a full review of PR #1340. The fix is well-structured — applying is_deactivated() guards at both the service layer and the Celery task layer (defense-in-depth) is the right approach. Here are my findings:


Review of PR #1340

✅ What's done well

  • Defense-in-depth: Deactivation checks at the service level (preventing task scheduling) and again inside each task (handling tasks queued before deactivation) is exactly the right pattern.
  • Signal suppression: The comment in process_ip_data_and_location correctly explains that the early return also suppresses the whois_lookup_skipped signal, preventing the estimated-location path from firing for deactivated devices.
  • Test coverage: All four new guard paths have dedicated tests (fetch_whois_details, process_ip_data_and_location, update_whois_info, trigger_estimated_location_task, manage_estimated_locations).
  • _is_deactivated in .only() queries: Adding this field where needed (commands.py, views.py) correctly avoids extra per-row DB queries when save() triggers signals that now call is_deactivated().

🐛 Issues

1. test_process_ip_runs_when_active — opaque assertion using a dynamic value

openwisp_controller/config/whois/tests/tests.py, line 1142–1145:

mock_task.assert_called_once_with(
    device_pk=device.pk,
    initial_ip_address=device._initial_last_ip,  # evaluates to None at assertion time
)

device._initial_last_ip was set to None before device.save(). Django's save() does not reset instance-level tracking attributes, so _initial_last_ip is still None at assertion time. Meanwhile, process_ip_data_and_location() captures initial_ip = self.device._initial_last_ip (also None) into the lambda. So both sides evaluate to None and the test passes — but using a dynamic reference instead of an explicit value makes the test's intent invisible.

Suggestion: Use an explicit expected value:

mock_task.assert_called_once_with(
    device_pk=device.pk,
    initial_ip_address=None,  # _initial_last_ip was set to None before save
)

2. views.py — missing comment on _is_deactivated in .only()

openwisp_controller/config/controller/views.py, lines 96 and 111:

for dupe in queryset.only("pk", "key", "management_ip", "_is_deactivated"):

Without a comment, it's non-obvious why _is_deactivated was added alongside fields directly used in the loop body. Future maintainers may remove it thinking it's unnecessary.

Suggestion: Add an inline comment, similar to the existing comment in commands.py:

# include '_is_deactivated' so save() signal handlers can call
# is_deactivated() without triggering an extra per-row DB query
for dupe in queryset.only("pk", "key", "management_ip", "_is_deactivated"):

🔍 Minor observation

In test_process_ip_skips_when_deactivated, device.deactivate() is called after setting last_ip = "8.8.8.8" and saving. The deactivation itself triggers a save() (and therefore signals) — you may want to add a mock_task.reset_mock() after device.deactivate() (not just after device.save()) to be safe against any task calls during the deactivate save. Currently mock_task.reset_mock() is called before device.deactivate(), so if deactivate() internally triggers process_ip_data_and_location via a signal, the reset would not clear it. The test still passes because the deactivation guard would prevent the task from being called, but it's worth making explicit.


Overall the fix is correct and the test suite is solid. Addressing the two issues above will improve clarity and long-term maintainability.

✅ Actions performed

Full review triggered.

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: 2

♻️ Duplicate comments (1)
openwisp_controller/config/whois/commands.py (1)

40-42: 🧹 Nitpick | 🔵 Trivial

Comment should explain why _is_deactivated is included.

The comment explains why organization is in .only(), but not why _is_deactivated is included. Without it, the per-device save() → signal handlers → is_deactivated() path would trigger an N+1 query on the deferred field.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openwisp_controller/config/whois/commands.py` around lines 40 - 42, The
comment on the QuerySet .only("last_ip", "organization", "key",
"_is_deactivated") is missing why "_is_deactivated" is included; update the
comment to state that including the model field "_is_deactivated" prevents
deferred-loading during the per-device save() → signal handlers →
is_deactivated() path and avoids an N+1 query when signal handlers access that
attribute (also keep the existing note about including the FK 'organization' to
prevent deferring organization__config_settings).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openwisp_controller/config/controller/views.py`:
- Line 96: Add a concise inline comment next to the queryset.only("pk", "key",
"management_ip", "_is_deactivated") call explaining that _is_deactivated is
included even though it's not referenced in the loop because dupe.save()
triggers signal handlers that call is_deactivated(), and omitting the field
would cause a SELECT per save (N+1 query); reference the symbols queryset.only,
"_is_deactivated", dupe.save(), and is_deactivated() in the comment for clarity.
- Line 111: Add a brief inline comment next to the queryset.only("pk", "key",
"last_ip", "_is_deactivated") call explaining that "_is_deactivated" is included
to avoid N+1 queries because calling dupe.save() triggers signal handlers that
call is_deactivated(), so the flag is prefetched; reference the loop variable
"dupe", the "is_deactivated()" method, and the save-triggered signal handlers in
the comment.

---

Duplicate comments:
In `@openwisp_controller/config/whois/commands.py`:
- Around line 40-42: The comment on the QuerySet .only("last_ip",
"organization", "key", "_is_deactivated") is missing why "_is_deactivated" is
included; update the comment to state that including the model field
"_is_deactivated" prevents deferred-loading during the per-device save() →
signal handlers → is_deactivated() path and avoids an N+1 query when signal
handlers access that attribute (also keep the existing note about including the
FK 'organization' to prevent deferring organization__config_settings).
🪄 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: 95e882a5-28bb-4575-8351-bd94b4a94c9a

📥 Commits

Reviewing files that changed from the base of the PR and between 897429b and c096ace.

📒 Files selected for processing (8)
  • openwisp_controller/config/controller/views.py
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/service.py
  • openwisp_controller/config/whois/tasks.py
  • openwisp_controller/config/whois/tests/tests.py
  • openwisp_controller/geo/estimated_location/service.py
  • openwisp_controller/geo/estimated_location/tasks.py
  • openwisp_controller/geo/estimated_location/tests/tests.py
📜 Review details
🧰 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/geo/estimated_location/tasks.py
  • openwisp_controller/config/controller/views.py
  • openwisp_controller/config/whois/tasks.py
  • openwisp_controller/geo/estimated_location/service.py
  • openwisp_controller/geo/estimated_location/tests/tests.py
  • openwisp_controller/config/whois/service.py
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.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/geo/estimated_location/tasks.py
  • openwisp_controller/config/controller/views.py
  • openwisp_controller/config/whois/tasks.py
  • openwisp_controller/geo/estimated_location/service.py
  • openwisp_controller/geo/estimated_location/tests/tests.py
  • openwisp_controller/config/whois/service.py
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.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/geo/estimated_location/tasks.py
  • openwisp_controller/config/controller/views.py
  • openwisp_controller/config/whois/tasks.py
  • openwisp_controller/geo/estimated_location/service.py
  • openwisp_controller/geo/estimated_location/tests/tests.py
  • openwisp_controller/config/whois/service.py
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.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/geo/estimated_location/tasks.py
  • openwisp_controller/config/controller/views.py
  • openwisp_controller/config/whois/tasks.py
  • openwisp_controller/geo/estimated_location/service.py
  • openwisp_controller/geo/estimated_location/tests/tests.py
  • openwisp_controller/config/whois/service.py
  • openwisp_controller/config/whois/commands.py
  • openwisp_controller/config/whois/tests/tests.py
🪛 GitHub Actions: OpenWISP Controller CI Build / 1_Python==3.10 django~=5.2.0.txt
openwisp_controller/geo/estimated_location/service.py

[warning] 70-70: AlwaysEagerIgnored: task_always_eager has no effect on send_task.

🪛 GitHub Actions: OpenWISP Controller CI Build / 2_Python==3.12 django~=4.2.0.txt
openwisp_controller/geo/estimated_location/service.py

[warning] 70-70: Celery warning: AlwaysEagerIgnored: task_always_eager has no effect on send_task.

🪛 GitHub Actions: OpenWISP Controller CI Build / Python==3.10 django~=4.2.0
openwisp_controller/geo/estimated_location/service.py

[warning] 70-70: AlwaysEagerIgnored: task_always_eager has no effect on send_task (current_app.send_task...).

🪛 GitHub Actions: OpenWISP Controller CI Build / Python==3.10 django~=5.1.0
openwisp_controller/geo/estimated_location/service.py

[warning] 70-70: AlwaysEagerIgnored: task_always_eager has no effect on send_task (current_app.send_task).

🪛 GitHub Actions: OpenWISP Controller CI Build / Python==3.10 django~=5.2.0
openwisp_controller/geo/estimated_location/service.py

[warning] 70-70: AlwaysEagerIgnored: task_always_eager has no effect on send_task.

🪛 GitHub Actions: OpenWISP Controller CI Build / Python==3.11 django~=4.2.0
openwisp_controller/geo/estimated_location/service.py

[warning] 70-70: AlwaysEagerIgnored: task_always_eager has no effect on send_task (current_app.send_task(...)).

🪛 GitHub Actions: OpenWISP Controller CI Build / Python==3.12 django~=4.2.0
openwisp_controller/geo/estimated_location/service.py

[warning] 70-70: AlwaysEagerIgnored: task_always_eager has no effect on send_task.

🪛 GitHub Actions: OpenWISP Controller CI Build / Python==3.12 django~=5.1.0
openwisp_controller/geo/estimated_location/service.py

[warning] 70-70: AlwaysEagerIgnored: task_always_eager has no effect on send_task.

🪛 GitHub Actions: OpenWISP Controller CI Build / Python==3.12 django~=5.2.0
openwisp_controller/geo/estimated_location/service.py

[warning] 70-70: AlwaysEagerIgnored: task_always_eager has no effect on send_task.

🪛 GitHub Actions: OpenWISP Controller CI Build / Python==3.13 django~=5.1.0
openwisp_controller/geo/estimated_location/service.py

[warning] 70-70: AlwaysEagerIgnored: task_always_eager has no effect on send_task

🪛 GitHub Actions: OpenWISP Controller CI Build / Python==3.13 django~=5.2.0
openwisp_controller/geo/estimated_location/service.py

[warning] 70-70: Celery warning AlwaysEagerIgnored: task_always_eager has no effect on send_task.

🔇 Additional comments (12)
openwisp_controller/config/whois/service.py (2)

211-218: LGTM!


239-241: LGTM!

openwisp_controller/config/whois/tasks.py (1)

75-79: LGTM!

openwisp_controller/config/whois/tests/tests.py (5)

180-184: LGTM!

Also applies to: 487-492, 506-509, 831-843, 881-886, 960-963, 979-984


1012-1024: LGTM!


1108-1124: LGTM!


1126-1144: LGTM!


1146-1165: LGTM!

openwisp_controller/geo/estimated_location/service.py (1)

66-68: LGTM!

openwisp_controller/geo/estimated_location/tasks.py (1)

124-130: LGTM!

openwisp_controller/geo/estimated_location/tests/tests.py (2)

197-211: LGTM!


816-827: LGTM!

Comment thread openwisp_controller/config/controller/views.py
Comment thread openwisp_controller/config/controller/views.py Outdated
@nemesifier
Copy link
Copy Markdown
Member

@coderabbitai any outstanding issue to resolve? full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the current state of the test assertion in test_process_ip_runs_when_active
rg -n "test_process_ip_runs_when_active|initial_ip_address|_initial_last_ip|assert_called_once_with" openwisp_controller/config/whois/tests/tests.py -A3 | head -60

# Check views.py for the _is_deactivated .only() comment
rg -n "_is_deactivated|is_deactivated" openwisp_controller/config/controller/views.py -B1 -A1

# Check reset_mock placement relative to deactivate() in test_process_ip_skips_when_deactivated
rg -n "test_process_ip_skips_when_deactivated|reset_mock|deactivate" openwisp_controller/config/whois/tests/tests.py -A2 | head -40

Length of output: 4750



@nemesifier: Both issues flagged in the previous review have been resolved in the current state of the branch:

  1. test_process_ip_runs_when_active assertion (Issue 1) — The assertion now uses the explicit value "8.8.8.8" (line 1145) instead of the opaque device._initial_last_ip, and a comment was added at lines 1141–1142 explaining why that value is expected (_check_last_ip in device.save() updates _initial_last_ip after the first save).

  2. views.py .only() comment (Issue 2) — Both .only() calls (lines 96–100 and 115–118) now have inline comments explaining that _is_deactivated is included to prevent N+1 queries when save() signal handlers call is_deactivated().

The minor observation about mock_task.reset_mock() placement relative to device.deactivate() in test_process_ip_skips_when_deactivated is a robustness note but not a blocker — the test is functionally correct since the deactivation guard prevents the task from being called regardless.

No outstanding blocking issues. The PR looks good to merge.

✅ Actions performed

Full review triggered.

nemesifier
nemesifier previously approved these changes Jun 1, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 1, 2026
@openwisp-companion
Copy link
Copy Markdown

Proposed change log entry:

[fix] Skip WHOIS and estimated location for deactivated devices #1325

This change prevents WHOIS and estimated location lookups from being triggered for devices that have been deactivated. Previously, these operations would continue to run even for inactive devices, leading to unnecessary background processing and potential data repopulation.

The fix introduces checks within the relevant service methods to ensure that these lookups are skipped if the device is marked as deactivated. This maintains consistency with the device's state and avoids redundant operations.

Closes #1325

@nemesifier nemesifier dismissed stale reviews from coderabbitai[bot] and themself via 6f207f8 June 1, 2026 23:54
nemesifier
nemesifier previously approved these changes Jun 1, 2026
Copy link
Copy Markdown
Member

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

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

LGTM ✔️

Thanks @tomsebastian10 @pandafy

@nemesifier
Copy link
Copy Markdown
Member

@coderabbitai resolve

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 2, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 2, 2026

✅ Actions performed

Comments resolved and changes approved.

@nemesifier nemesifier dismissed stale reviews from coderabbitai[bot] and themself via 6bc1b2c June 2, 2026 00:28
@nemesifier nemesifier merged commit cb1d495 into openwisp:master Jun 2, 2026
26 of 28 checks passed
@github-project-automation github-project-automation Bot moved this from In progress to Done in OpenWISP Contributor's Board Jun 2, 2026
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.

[bug] Skip WHOIS & Estimated Location on deactivated devices

4 participants