Skip to content

fix(geo): skip WHOIS and estimated location on deactivated devices#1340

Open
tomsebastian10 wants to merge 6 commits intoopenwisp:masterfrom
tomsebastian10:fix-whois-deactivated
Open

fix(geo): skip WHOIS and estimated location on deactivated devices#1340
tomsebastian10 wants to merge 6 commits intoopenwisp:masterfrom
tomsebastian10:fix-whois-deactivated

Conversation

@tomsebastian10
Copy link
Copy Markdown

@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.
  • 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

📝 Walkthrough

Walkthrough

Adds deactivation guards to WHOIS and Estimated Location flows so processing stops immediately when a device is deactivated. WHOISService.process_ip_data_and_location and WHOISService.update_whois_info now return early for deactivated devices, avoiding DB updates being committed, skipping scheduling of the Celery fetch_whois_details task and emission of the whois_lookup_skipped signal. trigger_estimated_location_task similarly aborts without sending the estimated-location task for deactivated devices. Tests and a management command projection were adjusted accordingly.

Sequence Diagram(s)

sequenceDiagram
    participant Device
    participant WHOISService
    participant Database
    participant CeleryTask as "Celery (fetch_whois_details)"
    participant SignalBus as "Signal (whois_lookup_skipped)"

    Device->>WHOISService: process_ip_data_and_location(ip, location)
    WHOISService->>WHOISService: check device._is_deactivated
    alt device deactivated
        WHOISService-->>Device: return (skip DB, task, signal)
    else device active
        WHOISService->>Database: update last_ip / location (in transaction)
        Database-->>WHOISService: commit hooks scheduled
        WHOISService->>SignalBus: maybe emit whois_lookup_skipped
        WHOISService->>CeleryTask: schedule fetch_whois_details.delay on commit
    end
Loading
sequenceDiagram
    participant Device
    participant WHOISService
    participant Database
    participant CeleryTask as "Celery (fetch_whois_details)"

    Device->>WHOISService: update_whois_info(device, data)
    WHOISService->>WHOISService: check device._is_deactivated
    alt device deactivated
        WHOISService-->>Device: return (skip validation, staleness, scheduling)
    else device active
        WHOISService->>WHOISService: validate / check staleness
        WHOISService->>Database: persist whois info (transaction)
        WHOISService->>CeleryTask: schedule fetch_whois_details.delay if needed
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

enhancement

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title uses the correct format [fix] with a descriptive summary that accurately reflects the main changes addressing the linked issue.
Description check ✅ Passed The description covers most required sections including checklist, reference to issue #1325, and detailed explanation of changes made.
Linked Issues check ✅ Passed All requirements from issue #1325 are met: guard conditions added to skip WHOIS lookups for deactivated devices in process_ip_data_and_location and update_whois_info, plus estimated location handling in trigger_estimated_location_task.
Out of Scope Changes check ✅ Passed All changes are in scope and related to fixing WHOIS and Estimated Location lookups for deactivated devices, including necessary test updates and a query optimization fix.
Bug Fixes ✅ Passed The PR correctly implements guard conditions checking device._is_deactivated at the entry of process_ip_data_and_location(), update_whois_info(), and trigger_estimated_location_task() functions, preventing task scheduling for deactivated devices. Regression tests comprehensively verify the fix: one test confirms the task is never called when device is deactivated (would fail without the fix), another confirms normal operation for active devices. Tests are deterministic using mocks with no external dependencies, timing issues, or randomness. This is a proper root-cause fix, not a workaround.

✏️ 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: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
Issue Details (click to expand)

CRITICAL

File Line Issue
openwisp_controller/config/whois/service.py N/A Fix removed - The PR reverted all changes; no longer fixes #1325
Other Observations (not in diff)

The PR description states it fixes issue #1325 (preventing WHOIS lookups for deactivated devices), but all changes have been reverted again. The current code does NOT check if a device is deactivated before triggering WHOIS lookups in:

  • process_ip_data_and_location() (line 213)
  • update_whois_info() (line 234)

The fix needs to add early returns in both methods:

if self.device.is_deactivated():
    return

Regression tests for deactivated devices were also removed from the PR.

Files Reviewed (2 files)
  • openwisp_controller/config/whois/service.py - Fix removed
  • openwisp_controller/config/whois/tests/tests.py - Tests removed

Fix these issues in Kilo Cloud


Reviewed by kimi-k2.5-0127 · 114,601 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
@@ -0,0 +1 @@
fatal: no rebase in progress
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Invalid file content - Git error message instead of code.

This file contains only a Git error message (fatal: no rebase in progress) rather than actual code changes. The PR objectives indicate that guard conditions should be added to process_ip_data_and_location and update_whois_info functions to skip WHOIS lookups for deactivated devices, but no such code is present in the files provided for review.

Please verify the PR contents and ensure the actual code changes are included.

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

In `@null` at 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.

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 on lines +1199 to +1217
@mock.patch.object(app_settings, "WHOIS_CONFIGURED", True)
@mock.patch("openwisp_controller.config.whois.service.fetch_whois_details.delay")
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()

self.assertEqual(mock_task.call_count, 0)

@mock.patch.object(app_settings, "WHOIS_CONFIGURED", True)
@mock.patch("openwisp_controller.config.whois.service.fetch_whois_details.delay")
def test_process_ip_runs_when_active(self, mock_task):
self.device._is_deactivated = False

service = WHOISService(self.device)
service.process_ip_data_and_location()

self.assertEqual(mock_task.call_count, 1)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Active-path test is not a reliable regression guard.

Two problems with test_process_ip_runs_when_active:

  1. It only patches WHOIS_CONFIGURED=True but does not enable whois_enabled on the organization's config settings. is_whois_enabled falls back to the global WHOIS_ENABLED, so whether fetch_whois_details.delay is scheduled depends on a global default unrelated to the deactivation fix. Explicitly enable it (org.config_settings.whois_enabled = True; org.config_settings.save()) to make the test deterministic — see the pattern in test_whois_task_called at Lines 483-485.
  2. Calling self.device.save() in setUp with last_ip="8.8.8.8" already triggers process_ip_data_and_location via the post-save flow; by the time the test body runs, the mock patch isn't active yet. Create the device (or change its IP) inside the patched block so mock_task.call_count == 1 actually reflects the call under test.

Additionally, test_process_ip_skips_when_deactivated sets _is_deactivated = True in-memory without saving; this happens to work because WHOISService holds the same instance, but a brief comment would prevent future confusion (or use self.device.deactivate() for realism).

🤖 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 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.

@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 on lines +213 to +214
if self.device.is_deactivated():
return
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether the estimated_location service also has a deactivation guard,
# since issue `#1325` requires both WHOIS and Estimated Location to be skipped.
fd -t f 'service\.py$' openwisp_controller/geo/estimated_location --exec cat {} \;
echo "---"
# Look for any is_deactivated / _is_deactivated usage inside estimated_location
rg -nP -C3 '(is_deactivated|_is_deactivated)' openwisp_controller/geo/estimated_location

Repository: openwisp/openwisp-controller

Length of output: 5427


Estimated Location service is missing the deactivation guard—fix is incomplete.

The WHOIS service guard is correct: is_deactivated() is called before _need_whois_lookup and transaction.on_commit, properly suppressing the Celery task and signal for deactivated devices.

However, issue #1325 explicitly requires the same protection for both WHOIS and Estimated Location. The EstimatedLocationService lacks an equivalent guard in trigger_estimated_location_task(). Deactivated devices will still enqueue estimated location tasks, leaving the fix incomplete.

Add an is_deactivated() guard at the entry point of EstimatedLocationService.trigger_estimated_location_task() (or the method that calls it) to match the WHOIS service implementation.

🤖 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 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.

Comment on lines +351 to 353

with self.assertNumQueries(7):
call_command("clear_last_ip", *args, stdout=out, stderr=StringIO())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect clear_last_ip to see if .only() is used and whether _is_deactivated is included.
fd -t f 'clear_last_ip.py$' openwisp_controller --exec cat {} \;
echo "---"
# Also check any other callers of process_ip_data_and_location / update_whois_info that may load deferred devices
rg -nP -C4 '\b(process_ip_data_and_location|update_whois_info)\s*\(' --type=py

Repository: openwisp/openwisp-controller

Length of output: 5544


🏁 Script executed:

fd -t f 'commands.py' openwisp_controller/config/whois --exec cat {} \;

Repository: openwisp/openwisp-controller

Length of output: 2598


🏁 Script executed:

# Find is_deactivated method
rg -nP 'def is_deactivated' --type=py openwisp_controller

Repository: openwisp/openwisp-controller

Length of output: 224


🏁 Script executed:

sed -n '185,200p' openwisp_controller/config/base/device.py

Repository: openwisp/openwisp-controller

Length of output: 628


🏁 Script executed:

# Find process_ip_data_and_location and check for the guard
rg -nP -A15 'def process_ip_data_and_location' openwisp_controller/config/whois/service.py

Repository: openwisp/openwisp-controller

Length of output: 803


🏁 Script executed:

# Find the signal handler that connects process_ip_data_and_location to post_save
rg -nP 'post_save.*device|process_ip_data_and_location' --type=py openwisp_controller/config/whois

Repository: openwisp/openwisp-controller

Length of output: 500


🏁 Script executed:

# Check the test at line 351 to see the baseline
sed -n '340,360p' openwisp_controller/config/whois/tests/tests.py

Repository: openwisp/openwisp-controller

Length of output: 887


🏁 Script executed:

# Find the method that calls process_ip_data_and_location on post_save
sed -n '530,550p' openwisp_controller/config/base/device.py

Repository: openwisp/openwisp-controller

Length of output: 642


🏁 Script executed:

# Find where _check_last_ip is called
rg -nP '_check_last_ip' --type=py openwisp_controller/config/base/device.py

Repository: openwisp/openwisp-controller

Length of output: 181


🏁 Script executed:

# Check context around line 292
sed -n '280,310p' openwisp_controller/config/base/device.py

Repository: openwisp/openwisp-controller

Length of output: 1535


Include _is_deactivated in the .only() call to avoid N+1 deferred-field access.

The clear_last_ip_command loads devices with .only("last_ip", "organization", "key") without _is_deactivated. When each device is saved, it triggers _check_last_ip()process_ip_data_and_location()is_deactivated(), which dereferences the deferred field and causes one SELECT per device. With 3 test devices, this produces exactly 3 extra queries (4→7), confirming the N+1 regression. Add _is_deactivated to the .only() call so the guard becomes a no-op lookup on an already-loaded field, and the test will remain at 4 queries.

🤖 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 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.

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.

…isp#1325

- add guard in WHOISService to skip processing for deactivated devices
- add guard in EstimatedLocationService to prevent task scheduling
- fix N+1 query by including _is_deactivated in clear_last_ip_command
  queryset
- add regression tests ensuring tasks are skipped when device is
  deactivated
- ensure deterministic tests by enabling whois on organization
  settings

Fixes openwisp#1325
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.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] Skip WHOIS & Estimated Location on deactivated devices

1 participant