Skip to content

[feature] Added generic solution for cache invalidation in config models #1095#1418

Merged
nemesifier merged 28 commits into
masterfrom
issues/1095-generic-cache-invalidation
Jul 11, 2026
Merged

[feature] Added generic solution for cache invalidation in config models #1095#1418
nemesifier merged 28 commits into
masterfrom
issues/1095-generic-cache-invalidation

Conversation

@pandafy

@pandafy pandafy commented Jul 1, 2026

Copy link
Copy Markdown
Member

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 #1095

Blockers

@pandafy pandafy self-assigned this Jul 1, 2026
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0c3487e4-103b-4246-b4ee-f6f3d04c3b3d

📥 Commits

Reviewing files that changed from the base of the PR and between 8e44395 and f2a566f.

📒 Files selected for processing (3)
  • docs/developer/utils.rst
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/tests/test_config.py
📜 Recent review details
⏰ Context from checks skipped due to timeout. (13)
  • GitHub Check: CodeRabbit
  • 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.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Kilo Code Review
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Mark user-facing strings for translation with Django i18n helpers in Django code
Avoid unnecessary blank lines inside function and method bodies
Be careful with authentication, authorization, queryset filtering, serializers, admin behavior, cache invalidation, signals, Celery tasks, and websocket updates in Django code
Preserve validation around templates, VPN/PKI material, SSH credentials, device commands, uploaded files, URLs, and subnet/IP data
Write comments and docstrings only when they explain why code is shaped a certain way, placing them before the relevant code block instead of scattering them inside it

Files:

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

📄 CodeRabbit inference engine (Custom checks)

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

Files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-07T12:07:08.468Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_admin.py:2335-2335
Timestamp: 2026-06-07T12:07:08.468Z
Learning: In this project’s Python test suite (files under openwisp_controller/**/tests/), don’t require or request prose/inline comments that document the breakdown of query-count changes (e.g., assertions around template/DB query counts in helpers like _verify_template_queries). Treat query-count assertions as volatile implementation details that change frequently; review should focus on whether the test asserts the expected behavior, not on explaining the specific query-count deltas in comments.

Applied to files:

  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-07T12:07:24.608Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/pki/tests/test_api.py:155-155
Timestamp: 2026-06-07T12:07:24.608Z
Learning: When reviewing Python test files in this repository, avoid recommending inline comments that explain or justify `assertNumQueries` (Django query count) expectations. Query counts can change frequently as implementations evolve, and inline explanations add maintenance burden; the expected count should be understandable without added comment blocks.

Applied to files:

  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-25T12:20:18.414Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/base/models.py:571-572
Timestamp: 2026-06-25T12:20:18.414Z
Learning: When writing or reviewing tests that override pagination behavior via OpenWispPagination.paginate_queryset(), patch `view.pagination_page_size` (not `page_size`). The method uses `getattr(view, "pagination_page_size", self.page_size)`, so tests must set the attribute on the view to affect pagination. If the view class does not define `pagination_page_size`, using `unittest.mock.patch(..., create=True)` is intentional and correct because the attribute may not exist until patched.

Applied to files:

  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-07T12:07:25.164Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_config.py:864-865
Timestamp: 2026-06-07T12:07:25.164Z
Learning: When reviewing this repo’s Python test suite, treat changes to the *expected* query count in `assertNumQueries(...)` calls as routine test maintenance. If a PR updates the numeric argument (e.g., in `test_config.py`, `test_api.py`, `test_admin.py`, `test_pki.py`) and the test remains consistent with the feature changes, reviewers should not flag the increased number as a performance regression that requires investigation solely because the count went up; instead, focus on whether the update is intentional and the surrounding test/code changes justify the revised expectation.

Applied to files:

  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-25T12:20:45.387Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/tests/test_api.py:916-932
Timestamp: 2026-06-25T12:20:45.387Z
Learning: When reviewing API pagination behavior in openwisp-controller, assume `OpenWispPagination.paginate_queryset()` allows a per-view page-size override via `getattr(view, "pagination_page_size", self.page_size)` (so `view.pagination_page_size`, if present, should affect pagination). In Python tests, it is valid to patch `pagination_page_size` on a view class even if the attribute isn’t declared on the class by default, by using `unittest.mock.patch.object(..., "pagination_page_size", ..., create=True)` so the override is available for the pagination logic during the test.

Applied to files:

  • openwisp_controller/config/tests/test_config.py
🔇 Additional comments (6)
docs/developer/utils.rst (4)

410-452: LGTM!


453-458: LGTM!


472-485: 📐 Maintainability & Code Quality

Verify the documented text output against the command formatter.

The first dependency combines resolve, track_fields, and flags on one line, while the second splits resolve and the UID across separate lines. Since the tests only check substrings, confirm the examples match the actual print_cache_dependencies text output and format both entries consistently.


494-499: LGTM!

openwisp_controller/config/base/config.py (1)

256-265: LGTM!

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

1217-1246: LGTM!

Also applies to: 1521-1521, 1570-1570, 1579-1579


📝 Walkthrough

Walkthrough

This PR adds declarative cache dependencies with signal tracking, deferred invalidation, field snapshots, registry introspection, and a management command. Config and VPN models declare related dependencies, application wiring handles controller and device-group caches, and legacy save-based invalidation is removed. Tests, subnet checksum invalidation, PKI transaction coverage, provisioning order, and documentation are updated.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ModelSignal
  participant CacheDependency
  participant Config
  participant Cache
  ModelSignal->>CacheDependency: detect related model change
  CacheDependency->>Config: resolve affected objects
  CacheDependency->>Config: defer invalidation until commit
  Config->>Cache: invalidate checksum or view cache
Loading

Possibly related PRs

Suggested labels: enhancement

Suggested reviewers: nemesifier, devkapilbansal, codesankalp


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Out of Scope Changes check ❌ Error The PR includes unrelated changes, such as the subnet division admin help-text typo fix, which are outside the cache-invalidation scope. Split unrelated cleanup into a separate PR and keep this one focused on cache-invalidation and its direct tests/docs.
✅ Passed checks (7 passed)
Check name Status Explanation
Title check ✅ Passed The title is descriptive, matches the cache-invalidation feature work, and includes the linked issue number.
Description check ✅ Passed The checklist and issue reference are present, and the remaining template gaps are non-critical.
Linked Issues check ✅ Passed The changes implement a generic cache-invalidation mechanism for Config, VPN, and Device-related caches as requested in #1095.
Bug Fixes ✅ Passed PASS: The PR fixes the stale-cache root cause via declarative dependencies, and adds deterministic regression tests for the affected config/vpn/device-group delete and update paths.
Features ✅ Passed PASS: linked issue #1095 exists with labels/projects, docs add a cache-invalidation section, and the PR adds broad regression tests; no UI changes.
Changes ✅ Passed Docs and tests cover the new cache-invalidation behavior; no UI changes mean screenshots aren't required.
General Rules ✅ Passed PASS: issue #1095 is label/project-validated, and the commit adds group_id tracking, a regression test, and matching docs; no obvious rule violations found.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issues/1095-generic-cache-invalidation

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

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 6

🤖 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/apps.py`:
- Line 82: The inline comment in apps.py exceeds the repository’s 88-character
flake8 limit and needs to be wrapped. Update the comment near the
DeviceChecksumView cache invalidation logic in the relevant module so it fits
within max-line-length while preserving the same meaning.
- Around line 90-95: The delete-path cache invalidation in CacheDependency is
happening too early because it uses pre_delete with on_commit=False. Update the
dependency in apps.py to use DeviceChecksumView.invalidate_get_device_cache with
post_delete and commit-time invalidation so the cache is cleared only after the
delete transaction is committed.

In `@openwisp_controller/config/base/config.py`:
- Around line 225-240: Defer the checksum invalidation work for the DeviceGroup
and OrganizationConfigSettings CacheDependency entries until after the
transaction commits, because on_commit=False can let the async recomputation run
before the saved context is visible. Update the CacheDependency registrations in
the config.Config-related dependency list to use on_commit=True so
cls._invalidate_configs_in_group and cls._invalidate_configs_in_org run only
after the save is committed.

In `@openwisp_controller/config/base/vpn.py`:
- Around line 351-356: The VPN view cache invalidation in the CacheDependency
registration for config.Vpn is happening too early, before the delete is
committed. Update the signal from pre_delete to post_delete and keep on_commit
handling so cls._invalidate_vpn_view_cache runs only after the transaction
commits, preventing GetVpnView.get_vpn from caching a row that is already
deleted.

In `@openwisp_controller/config/handlers.py`:
- Around line 72-74: The cache invalidation task in the post-save handler is
enqueued too early and may run before the surrounding transaction is committed.
Update the handler that calls tasks.invalidate_devicegroup_cache_change.delay to
wrap the enqueue in transaction.on_commit so the task is scheduled only after a
successful commit. Use the existing post-save callback logic and keep the
instance.id and instance._meta.model_name arguments unchanged.

In `@openwisp_controller/config/tests/test_config.py`:
- Around line 1025-1099: Add test coverage for the default on_commit=True
behavior in TestCacheDependency, since the current tests only exercise
on_commit=False and miss the transaction.on_commit deferral path used by real
CacheDependency wiring. Add a test around a save-triggered dependency using
self.captureOnCommitCallbacks(execute=True) so the callback is actually executed
under TestCase, and verify the target is invoked only after commit; reference
CacheDependency, _connect, and the existing post_save save flow when adding the
new 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: 5ff567e8-9b77-4f24-bac9-2d85ca9c32ee

📥 Commits

Reviewing files that changed from the base of the PR and between 8b875e7 and 6a8f4ff.

📒 Files selected for processing (9)
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/base/base.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/device_group.py
  • openwisp_controller/config/base/multitenancy.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/tests/test_controller.py
💤 Files with no reviewable changes (2)
  • openwisp_controller/config/base/device_group.py
  • openwisp_controller/config/base/multitenancy.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}

📄 CodeRabbit inference engine (Custom checks)

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

Files:

  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/base/base.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/apps.py
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp,sql}

📄 CodeRabbit inference engine (Custom checks)

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

Files:

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

📄 CodeRabbit inference engine (Custom checks)

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

Files:

  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/base/base.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/apps.py
**/*.{py,html}

📄 CodeRabbit inference engine (Custom checks)

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

Files:

  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/base/base.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/apps.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Mark user-facing strings for translation with Django i18n helpers in Django code
Avoid unnecessary blank lines inside function and method bodies
Be careful with authentication, authorization, queryset filtering, serializers, admin behavior, cache invalidation, signals, Celery tasks, and websocket updates in Django code
Preserve validation around templates, VPN/PKI material, SSH credentials, device commands, uploaded files, URLs, and subnet/IP data
Write comments and docstrings only when they explain why code is shaped a certain way, placing them before the relevant code block instead of scattering them inside it

Files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/base/base.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/apps.py
📚 Learning: 2026-06-07T12:07:08.468Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_admin.py:2335-2335
Timestamp: 2026-06-07T12:07:08.468Z
Learning: In this project’s Python test suite (files under openwisp_controller/**/tests/), don’t require or request prose/inline comments that document the breakdown of query-count changes (e.g., assertions around template/DB query counts in helpers like _verify_template_queries). Treat query-count assertions as volatile implementation details that change frequently; review should focus on whether the test asserts the expected behavior, not on explaining the specific query-count deltas in comments.

Applied to files:

  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-07T12:07:24.608Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/pki/tests/test_api.py:155-155
Timestamp: 2026-06-07T12:07:24.608Z
Learning: When reviewing Python test files in this repository, avoid recommending inline comments that explain or justify `assertNumQueries` (Django query count) expectations. Query counts can change frequently as implementations evolve, and inline explanations add maintenance burden; the expected count should be understandable without added comment blocks.

Applied to files:

  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-25T12:20:18.414Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/base/models.py:571-572
Timestamp: 2026-06-25T12:20:18.414Z
Learning: When writing or reviewing tests that override pagination behavior via OpenWispPagination.paginate_queryset(), patch `view.pagination_page_size` (not `page_size`). The method uses `getattr(view, "pagination_page_size", self.page_size)`, so tests must set the attribute on the view to affect pagination. If the view class does not define `pagination_page_size`, using `unittest.mock.patch(..., create=True)` is intentional and correct because the attribute may not exist until patched.

Applied to files:

  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-07T12:07:25.164Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_config.py:864-865
Timestamp: 2026-06-07T12:07:25.164Z
Learning: When reviewing this repo’s Python test suite, treat changes to the *expected* query count in `assertNumQueries(...)` calls as routine test maintenance. If a PR updates the numeric argument (e.g., in `test_config.py`, `test_api.py`, `test_admin.py`, `test_pki.py`) and the test remains consistent with the feature changes, reviewers should not flag the increased number as a performance regression that requires investigation solely because the count went up; instead, focus on whether the update is intentional and the surrounding test/code changes justify the revised expectation.

Applied to files:

  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-25T12:20:45.387Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/tests/test_api.py:916-932
Timestamp: 2026-06-25T12:20:45.387Z
Learning: When reviewing API pagination behavior in openwisp-controller, assume `OpenWispPagination.paginate_queryset()` allows a per-view page-size override via `getattr(view, "pagination_page_size", self.page_size)` (so `view.pagination_page_size`, if present, should affect pagination). In Python tests, it is valid to patch `pagination_page_size` on a view class even if the attribute isn’t declared on the class by default, by using `unittest.mock.patch.object(..., "pagination_page_size", ..., create=True)` so the override is available for the pagination logic during the test.

Applied to files:

  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/tests/test_config.py
🪛 ast-grep (0.44.0)
openwisp_controller/config/tests/test_config.py

[warning] 26-26: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("config", "Template")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)

openwisp_controller/config/base/base.py

[warning] 197-197: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model(app_label, model_name)
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)

🔇 Additional comments (7)
openwisp_controller/config/tests/test_config.py (1)

2-2: LGTM!

Also applies to: 14-14, 26-26

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

478-489: 🩺 Stability & Availability

Check the cache invalidation path in this test
If vpn.delete() still defers GetVpnView invalidation with transaction.on_commit(), TestCase won’t run that callback automatically; wrap the delete in self.captureOnCommitCallbacks(execute=True) or use a test case that commits transactions.

openwisp_controller/config/base/base.py (1)

9-35: LGTM!

Also applies to: 112-329

openwisp_controller/config/apps.py (1)

5-5: LGTM!

Also applies to: 45-56, 57-89, 96-151, 352-383

openwisp_controller/config/base/config.py (1)

29-35: LGTM!

Also applies to: 49-49, 177-222

openwisp_controller/config/base/vpn.py (1)

36-41: LGTM!

Also applies to: 51-56, 329-350, 357-365

openwisp_controller/config/handlers.py (1)

44-71: LGTM!

Comment thread openwisp_controller/config/apps.py Outdated
Comment thread openwisp_controller/config/apps.py
Comment thread openwisp_controller/config/base/config.py
Comment thread openwisp_controller/config/base/vpn.py
Comment thread openwisp_controller/config/handlers.py
Comment thread openwisp_controller/config/tests/test_config.py

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 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/tests/test_config.py`:
- Around line 578-584: The test is updating cert.revoked via cert.save(), but
the transaction.on_commit cache invalidation callback never runs under TestCase,
so the assertion may not exercise _resolve_cert_dependency() as intended. In
test_config.py, wrap the cert.save() call in
captureOnCommitCallbacks(execute=True) so the deferred callback is executed
before config.refresh_from_db(), and keep the existing assertions around
_resolve_cert_dependency() and the VpnClient deletion path.
🪄 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: e656bc4e-934a-4c2e-b5ae-599ad2e1e2ab

📥 Commits

Reviewing files that changed from the base of the PR and between 6a8f4ff and 2c520fd.

📒 Files selected for processing (2)
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/tests/test_config.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}

📄 CodeRabbit inference engine (Custom checks)

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

Files:

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

📄 CodeRabbit inference engine (Custom checks)

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

Files:

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

📄 CodeRabbit inference engine (Custom checks)

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

Files:

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

📄 CodeRabbit inference engine (Custom checks)

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

Files:

  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/apps.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Mark user-facing strings for translation with Django i18n helpers in Django code
Avoid unnecessary blank lines inside function and method bodies
Be careful with authentication, authorization, queryset filtering, serializers, admin behavior, cache invalidation, signals, Celery tasks, and websocket updates in Django code
Preserve validation around templates, VPN/PKI material, SSH credentials, device commands, uploaded files, URLs, and subnet/IP data
Write comments and docstrings only when they explain why code is shaped a certain way, placing them before the relevant code block instead of scattering them inside it

Files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/apps.py
📚 Learning: 2026-06-07T12:07:08.468Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_admin.py:2335-2335
Timestamp: 2026-06-07T12:07:08.468Z
Learning: In this project’s Python test suite (files under openwisp_controller/**/tests/), don’t require or request prose/inline comments that document the breakdown of query-count changes (e.g., assertions around template/DB query counts in helpers like _verify_template_queries). Treat query-count assertions as volatile implementation details that change frequently; review should focus on whether the test asserts the expected behavior, not on explaining the specific query-count deltas in comments.

Applied to files:

  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-07T12:07:24.608Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/pki/tests/test_api.py:155-155
Timestamp: 2026-06-07T12:07:24.608Z
Learning: When reviewing Python test files in this repository, avoid recommending inline comments that explain or justify `assertNumQueries` (Django query count) expectations. Query counts can change frequently as implementations evolve, and inline explanations add maintenance burden; the expected count should be understandable without added comment blocks.

Applied to files:

  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-25T12:20:18.414Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/base/models.py:571-572
Timestamp: 2026-06-25T12:20:18.414Z
Learning: When writing or reviewing tests that override pagination behavior via OpenWispPagination.paginate_queryset(), patch `view.pagination_page_size` (not `page_size`). The method uses `getattr(view, "pagination_page_size", self.page_size)`, so tests must set the attribute on the view to affect pagination. If the view class does not define `pagination_page_size`, using `unittest.mock.patch(..., create=True)` is intentional and correct because the attribute may not exist until patched.

Applied to files:

  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-07T12:07:25.164Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_config.py:864-865
Timestamp: 2026-06-07T12:07:25.164Z
Learning: When reviewing this repo’s Python test suite, treat changes to the *expected* query count in `assertNumQueries(...)` calls as routine test maintenance. If a PR updates the numeric argument (e.g., in `test_config.py`, `test_api.py`, `test_admin.py`, `test_pki.py`) and the test remains consistent with the feature changes, reviewers should not flag the increased number as a performance regression that requires investigation solely because the count went up; instead, focus on whether the update is intentional and the surrounding test/code changes justify the revised expectation.

Applied to files:

  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-25T12:20:45.387Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/tests/test_api.py:916-932
Timestamp: 2026-06-25T12:20:45.387Z
Learning: When reviewing API pagination behavior in openwisp-controller, assume `OpenWispPagination.paginate_queryset()` allows a per-view page-size override via `getattr(view, "pagination_page_size", self.page_size)` (so `view.pagination_page_size`, if present, should affect pagination). In Python tests, it is valid to patch `pagination_page_size` on a view class even if the attribute isn’t declared on the class by default, by using `unittest.mock.patch.object(..., "pagination_page_size", ..., create=True)` so the override is available for the pagination logic during the test.

Applied to files:

  • openwisp_controller/config/tests/test_config.py
🔇 Additional comments (4)
openwisp_controller/config/apps.py (3)

91-96: Previously flagged: delete invalidation still runs before commit.

This matches the existing review comment: pre_delete with on_commit=False can clear the device cache before the delete transaction commits, allowing stale cache repopulation before commit.


45-56: LGTM!


57-90: LGTM!

Also applies to: 97-150, 351-382

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

1032-1036: LGTM!

Comment thread openwisp_controller/config/tests/test_config.py
@coveralls

coveralls commented Jul 1, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 98.477% (-0.07%) from 98.547% — issues/1095-generic-cache-invalidation into master

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 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/base/vpn.py`:
- Around line 1111-1115: The loop in vpn.vpnclient_set.iterator() is causing
per-client N+1 queries because client.config and its render path fetch related
objects repeatedly. Update the queryset used in this block to preload config
plus config__device and the device’s organization/group relations before
iterating, so update_status_if_checksum_changed() can run without extra database
hits while preserving the _config_modified_action assignment.
🪄 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: 89cff386-fcf6-47ad-aa46-3e736cf376f7

📥 Commits

Reviewing files that changed from the base of the PR and between 2c520fd and 6c414c5.

📒 Files selected for processing (6)
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/subnet_division/admin.py
📜 Review details
⏰ Context from checks skipped due to timeout. (11)
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.13 | django~=5.2.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 (5)
**/*.{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/subnet_division/admin.py
  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/base/config.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/subnet_division/admin.py
  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/base/config.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/subnet_division/admin.py
  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/base/config.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/subnet_division/admin.py
  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/base/config.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Mark user-facing strings for translation with Django i18n helpers in Django code
Avoid unnecessary blank lines inside function and method bodies
Be careful with authentication, authorization, queryset filtering, serializers, admin behavior, cache invalidation, signals, Celery tasks, and websocket updates in Django code
Preserve validation around templates, VPN/PKI material, SSH credentials, device commands, uploaded files, URLs, and subnet/IP data
Write comments and docstrings only when they explain why code is shaped a certain way, placing them before the relevant code block instead of scattering them inside it

Files:

  • openwisp_controller/subnet_division/admin.py
  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/base/config.py
🧠 Learnings (8)
📚 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/subnet_division/admin.py
  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/base/config.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/subnet_division/admin.py
  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/base/config.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/subnet_division/admin.py
  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/base/config.py
📚 Learning: 2026-06-07T12:07:08.468Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_admin.py:2335-2335
Timestamp: 2026-06-07T12:07:08.468Z
Learning: In this project’s Python test suite (files under openwisp_controller/**/tests/), don’t require or request prose/inline comments that document the breakdown of query-count changes (e.g., assertions around template/DB query counts in helpers like _verify_template_queries). Treat query-count assertions as volatile implementation details that change frequently; review should focus on whether the test asserts the expected behavior, not on explaining the specific query-count deltas in comments.

Applied to files:

  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-07T12:07:24.608Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/pki/tests/test_api.py:155-155
Timestamp: 2026-06-07T12:07:24.608Z
Learning: When reviewing Python test files in this repository, avoid recommending inline comments that explain or justify `assertNumQueries` (Django query count) expectations. Query counts can change frequently as implementations evolve, and inline explanations add maintenance burden; the expected count should be understandable without added comment blocks.

Applied to files:

  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-25T12:20:18.414Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/base/models.py:571-572
Timestamp: 2026-06-25T12:20:18.414Z
Learning: When writing or reviewing tests that override pagination behavior via OpenWispPagination.paginate_queryset(), patch `view.pagination_page_size` (not `page_size`). The method uses `getattr(view, "pagination_page_size", self.page_size)`, so tests must set the attribute on the view to affect pagination. If the view class does not define `pagination_page_size`, using `unittest.mock.patch(..., create=True)` is intentional and correct because the attribute may not exist until patched.

Applied to files:

  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-25T12:20:45.387Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/tests/test_api.py:916-932
Timestamp: 2026-06-25T12:20:45.387Z
Learning: When reviewing API pagination behavior in openwisp-controller, assume `OpenWispPagination.paginate_queryset()` allows a per-view page-size override via `getattr(view, "pagination_page_size", self.page_size)` (so `view.pagination_page_size`, if present, should affect pagination). In Python tests, it is valid to patch `pagination_page_size` on a view class even if the attribute isn’t declared on the class by default, by using `unittest.mock.patch.object(..., "pagination_page_size", ..., create=True)` so the override is available for the pagination logic during the test.

Applied to files:

  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-07T12:07:25.164Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_config.py:864-865
Timestamp: 2026-06-07T12:07:25.164Z
Learning: When reviewing this repo’s Python test suite, treat changes to the *expected* query count in `assertNumQueries(...)` calls as routine test maintenance. If a PR updates the numeric argument (e.g., in `test_config.py`, `test_api.py`, `test_admin.py`, `test_pki.py`) and the test remains consistent with the feature changes, reviewers should not flag the increased number as a performance regression that requires investigation solely because the count went up; instead, focus on whether the update is intentional and the surrounding test/code changes justify the revised expectation.

Applied to files:

  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/tests/test_config.py
🪛 ast-grep (0.44.0)
openwisp_controller/config/tests/test_config.py

[warning] 27-27: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("config", "Template")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)

🔇 Additional comments (7)
openwisp_controller/subnet_division/admin.py (1)

28-38: LGTM!

openwisp_controller/config/tests/test_config.py (3)

579-584: 🩺 Stability & Availability

Unaddressed: deferred on_commit callback never executed in this test.

cert.save() schedules the cache-invalidation callback via transaction.on_commit (per CacheDependency), but under plain TestCase this callback never runs unless wrapped in captureOnCommitCallbacks(execute=True). This was already flagged in a previous review round and remains unresolved in this version.

Proposed fix
-        cert.revoked = False
-        cert.save()
+        with self.captureOnCommitCallbacks(execute=True):
+            cert.revoked = False
+            cert.save()

1084-1160: 🩺 Stability & Availability

Still missing coverage for the default on_commit=True deferral path.

All four TestCacheDependency tests pass on_commit=False, so the default deferred-to-transaction.on_commit() behavior used by all real production wiring (Config/Vpn) is still never exercised. This was already raised in a prior review and remains open.

Add at least one test using self.captureOnCommitCallbacks(execute=True) around a save that triggers a dependency with the default on_commit=True.


1014-1073: LGTM!

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

541-559: LGTM!

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

180-180: 🎯 Functional Correctness

No follow-up needed. The WireGuard tests that use this helper only assert VPN/client behavior; none rely on the template carrying interface entries.

			> Likely an incorrect or invalid review comment.
openwisp_controller/config/base/config.py (1)

212-240: LGTM!

Comment thread openwisp_controller/config/base/vpn.py Outdated
@openwisp-companion

Copy link
Copy Markdown

Test Failures in Query Counts

Hello @pandafy,
(Analysis for commit 6c414c5)

There are multiple test failures related to unexpected query counts:

  1. test_organization_shares_management_ip_address_space: Expected 6 queries, but 7 were executed.
  • Fix: Review the test openwisp_controller/config/tests/test_controller.py around line 1608. The test expects 6 queries, but 7 are being made. Identify the extra query and optimize the code or adjust the test's query count.
  1. test_remove_duplicated_last_ip_no_nplus1_queries: The assertion self.assertEqual(three - one, 2) failed with AssertionError: 6 != 2. This indicates a significant deviation in the number of queries.
  • Fix: Examine the test openwisp_controller/config/tests/test_controller.py around line 1600. The test logic three - one seems to be comparing incorrect values or the underlying code is performing more operations than anticipated.
  1. test_last_ip_management_command_queries: Expected 4 queries, but 10 were executed.
  • Fix: Investigate the test openwisp_controller/config/whois/tests/tests.py around line 351. The test expects 4 queries, but the execution shows 10. Analyze the captured queries to pinpoint the excess database interactions.

These failures suggest that the code being tested is performing more database operations than the tests are configured to expect. You should review the test files mentioned and the corresponding code to identify and resolve the N+1 query issues or optimize the database interactions.

@nemesifier nemesifier left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Good progress, see my comments below.

Comment thread openwisp_controller/config/apps.py Outdated
),
CacheDependency(
source=self.device_model,
signal="pre_delete",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would move this to post_delete and let it run on commit. With pre_delete plus on_commit=False, the cache is cleared while the row is still visible. A controller request during the delete transaction can repopulate DeviceChecksumView.get_device and leave a deleted device cached after commit. The smallest fix is to use post_delete with the default on_commit=True.

source="config.DeviceGroup",
signal="post_save",
track_fields=["context"],
on_commit=False,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would not enqueue this Celery recomputation before commit. If a group or organization context is saved inside a transaction, the worker can run against the old context and decide the checksums did not change. Please use the default on_commit=True for both this dependency and the OrganizationConfigSettings dependency below.

Comment thread openwisp_controller/config/base/vpn.py Outdated
),
CacheDependency(
source="config.Vpn",
signal="pre_delete",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same issue as the device view cache: this should be post_delete and deferred to commit. Otherwise a request can repopulate GetVpnView.get_vpn between the early invalidation and the transaction commit, leaving the deleted VPN cached.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

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/base/vpn.py (1)

366-372: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Defer VPN view-cache invalidation to commit

post_save and the vpn_server_modified path emitted from Vpn.save() both run before the outer transaction commits, so a concurrent get_vpn() can repopulate the cache from the old row. Switch these cache dependencies to on_commit=True; the related-object cascade already runs after commit.

🤖 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/base/vpn.py` around lines 366 - 372, The VPN
view-cache invalidation for the Vpn model is still happening before the
surrounding transaction commits, which allows stale cache repopulation. Update
the CacheDependency registration in Vpn-related cache setup to use
on_commit=True for the post_save path, and ensure the vpn_server_modified flow
from Vpn.save() also defers invalidation until commit; keep the related-object
cascade behavior aligned with the commit-time invalidation.
🤖 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 @.github/workflows/ci.yml:
- Around line 75-76: The CI workaround in the workflow step that installs
django-x509 from the issues/236-renew-signals tarball is using a mutable branch
ref, which can break reproducibility. Update the pip install in the CI job to
pin django-x509 to a specific commit SHA instead of the branch tarball, and keep
the existing TODO/removal tracking tied to the upstream django-x509 release. Use
the workflow step containing the temporary workaround install comment to locate
it.

---

Outside diff comments:
In `@openwisp_controller/config/base/vpn.py`:
- Around line 366-372: The VPN view-cache invalidation for the Vpn model is
still happening before the surrounding transaction commits, which allows stale
cache repopulation. Update the CacheDependency registration in Vpn-related cache
setup to use on_commit=True for the post_save path, and ensure the
vpn_server_modified flow from Vpn.save() also defers invalidation until commit;
keep the related-object cascade behavior aligned with the commit-time
invalidation.
🪄 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: 04131af7-bc5d-49d4-936a-f06ffe009c06

📥 Commits

Reviewing files that changed from the base of the PR and between 457a4b7 and 5dc927e.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/pki/tests/test_api.py
📜 Review details
⏰ Context from checks skipped due to timeout. (9)
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.2.0
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{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/pki/tests/test_api.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.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/pki/tests/test_api.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.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/pki/tests/test_api.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.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/pki/tests/test_api.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Mark user-facing strings for translation with Django i18n helpers in Django code
Avoid unnecessary blank lines inside function and method bodies
Be careful with authentication, authorization, queryset filtering, serializers, admin behavior, cache invalidation, signals, Celery tasks, and websocket updates in Django code
Preserve validation around templates, VPN/PKI material, SSH credentials, device commands, uploaded files, URLs, and subnet/IP data
Write comments and docstrings only when they explain why code is shaped a certain way, placing them before the relevant code block instead of scattering them inside it

Files:

  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
🧠 Learnings (10)
📚 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/pki/tests/test_api.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.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/pki/tests/test_api.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.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/pki/tests/test_api.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-07T12:07:08.468Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_admin.py:2335-2335
Timestamp: 2026-06-07T12:07:08.468Z
Learning: In this project’s Python test suite (files under openwisp_controller/**/tests/), don’t require or request prose/inline comments that document the breakdown of query-count changes (e.g., assertions around template/DB query counts in helpers like _verify_template_queries). Treat query-count assertions as volatile implementation details that change frequently; review should focus on whether the test asserts the expected behavior, not on explaining the specific query-count deltas in comments.

Applied to files:

  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-07T12:07:24.608Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/pki/tests/test_api.py:155-155
Timestamp: 2026-06-07T12:07:24.608Z
Learning: When reviewing Python test files in this repository, avoid recommending inline comments that explain or justify `assertNumQueries` (Django query count) expectations. Query counts can change frequently as implementations evolve, and inline explanations add maintenance burden; the expected count should be understandable without added comment blocks.

Applied to files:

  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-25T12:20:18.414Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/base/models.py:571-572
Timestamp: 2026-06-25T12:20:18.414Z
Learning: When writing or reviewing tests that override pagination behavior via OpenWispPagination.paginate_queryset(), patch `view.pagination_page_size` (not `page_size`). The method uses `getattr(view, "pagination_page_size", self.page_size)`, so tests must set the attribute on the view to affect pagination. If the view class does not define `pagination_page_size`, using `unittest.mock.patch(..., create=True)` is intentional and correct because the attribute may not exist until patched.

Applied to files:

  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-07T12:07:25.164Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_config.py:864-865
Timestamp: 2026-06-07T12:07:25.164Z
Learning: When reviewing this repo’s Python test suite, treat changes to the *expected* query count in `assertNumQueries(...)` calls as routine test maintenance. If a PR updates the numeric argument (e.g., in `test_config.py`, `test_api.py`, `test_admin.py`, `test_pki.py`) and the test remains consistent with the feature changes, reviewers should not flag the increased number as a performance regression that requires investigation solely because the count went up; instead, focus on whether the update is intentional and the surrounding test/code changes justify the revised expectation.

Applied to files:

  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-25T12:20:45.387Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/tests/test_api.py:916-932
Timestamp: 2026-06-25T12:20:45.387Z
Learning: When reviewing API pagination behavior in openwisp-controller, assume `OpenWispPagination.paginate_queryset()` allows a per-view page-size override via `getattr(view, "pagination_page_size", self.page_size)` (so `view.pagination_page_size`, if present, should affect pagination). In Python tests, it is valid to patch `pagination_page_size` on a view class even if the attribute isn’t declared on the class by default, by using `unittest.mock.patch.object(..., "pagination_page_size", ..., create=True)` so the override is available for the pagination logic during the test.

Applied to files:

  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-02-24T16:24:55.443Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1233
File: .github/workflows/backport.yml:22-22
Timestamp: 2026-02-24T16:24:55.443Z
Learning: In repositories within the OpenWISP organization, it is acceptable to reference reusable workflows from other OpenWISP-controlled repos using mutable refs (e.g., master) in .github/workflows. This is permissible due to the shared trust boundary within the organization. If applying this pattern, ensure the target repos are under the same organization and maintain awareness of potential breakages from upstream mutable refs; consider pinning to a tagged version for longer-term stability when appropriate.

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2026-02-24T16:25:20.080Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1233
File: .github/workflows/backport.yml:35-35
Timestamp: 2026-02-24T16:25:20.080Z
Learning: In .github/workflows/backport.yml, enforce that backport-on-comment triggers only for users with author_association MEMBE R or OWNER (COLLABORATOR excluded), reflecting maintainer feedback. Update the trigger condition to check author_association and restrict to MEMBERS/OWNERS; document rationale and PR `#1233` reference in code comments.

Applied to files:

  • .github/workflows/ci.yml
🔇 Additional comments (9)
openwisp_controller/config/tests/test_config.py (4)

1117-1166: Missing coverage for the default on_commit=True deferral path.

All three tests here pass on_commit=False, so the transaction.on_commit() deferral — the default and the mode used by all production wiring (Config/Vpn) — is never exercised. This was already flagged in a previous review of this file.


1086-1101: LGTM!


1168-1295: LGTM!


1104-1116: 🩺 Stability & Availability

Shared test uid is safe here disconnect() unwinds both the main receiver and the .snapshot pre_save receiver, so this helper does not leak handlers across tests.

			> Likely an incorrect or invalid review comment.
openwisp_controller/config/base/vpn.py (4)

373-378: pre_delete + on_commit=False invalidates the view cache before the row is deleted, allowing a concurrent request to repopulate GetVpnView.get_vpn with the soon-to-be-deleted VPN. Use post_delete with on_commit=True. Already raised in prior review.


1141-1154: vpn.vpnclient_set.iterator() triggers per-client N+1 queries: client.config and the checksum recompute path load related objects inside the loop. Preload config, config__device, and org/group relations. Already raised in prior review.


387-403: 🎯 Functional Correctness

x509_renewed isn’t a django-x509 signal, so this sender check doesn’t apply. Wire the cache invalidation to the actual renewal hook instead.

			> Likely an incorrect or invalid review comment.

52-57: 📐 Maintainability & Code Quality

No MRO issue. Only clean() is shared, and the current order keeps ShareableOrgMixinUniqueName.clean() before BaseConfig.clean(). save, get_context, get_config, and checksum aren’t impacted.

			> Likely an incorrect or invalid review comment.
openwisp_controller/pki/tests/test_api.py (1)

164-164: LGTM!

Also applies to: 292-292

Comment thread .github/workflows/ci.yml Outdated
@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).

@openwisp-companion

Copy link
Copy Markdown

Test Failures in OpenWISP Controller CI

Hello @pandafy,
(Analysis for commit 5dc927e)

There are test failures in the CI pipeline.

1. Django Test Failures:

The CI logs indicate a failure during the Django test execution. The traceback points to an ImproperlyConfigured error originating from the swapper.load_model function within openwisp_controller/config/base/base.py, specifically when trying to load a model in the build_dispatch_uid method. This suggests an issue with how models are being loaded or referenced, possibly due to a missing or incorrectly configured model in the INSTALLED_APPS or a settings.py issue.

Fix:
Review the INSTALLED_APPS in tests/openwisp2/settings.py and ensure that all necessary apps, especially those related to swapper and the models being loaded, are correctly included and ordered. Check for any circular dependencies or missing configurations that might lead to ImproperlyConfigured errors during application startup.

2. Coverage Warning:

A CoverageWarning: No data was collected. message is present. This typically means that the coverage tool did not instrument or execute any code during the test run.

Fix:
This warning often accompanies test failures. Resolving the primary test failures might also resolve this coverage warning. If it persists after fixing other issues, ensure that the coverage configuration is correctly set up to include all relevant source files.

old_serial_num = ca1.serial_number
path = reverse("pki_api:ca_renew", args=[ca1.pk])
with self.assertNumQueries(4):
with self.assertNumQueries(5):

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Number of queries has increased because renewing a CA also invalidates the VPN checksum. In order to find related Vpn, we need to perform another query.

old_serial_num = cert1.serial_number
path = reverse("pki_api:cert_renew", args=[cert1.pk])
with self.assertNumQueries(5):
with self.assertNumQueries(6):

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Number of queries has increased because renewing a Cert also invalidates the VPN and Config checksum. In order to find related Vpn, we need to perform another query.

@openwisp-companion

Copy link
Copy Markdown
[
  {"line": 2026, "message": "Could not find pki.Ca!"}
]

ImproperlyConfigured: Could not find pki.Ca!

Hello @pandafy,
(Analysis for commit e1d4b50)

The CI failed because the pki.Ca model could not be found during the Django setup. This typically happens when an app is listed in INSTALLED_APPS but its migrations haven't been applied or the app itself is not correctly configured.

Fix:

Ensure that the openwisp_controller.pki app is correctly installed and that its migrations are applied. If you are using the SAMPLE_APP environment, verify that the sample_pki app is correctly configured in tests/openwisp2/settings.py.

The traceback points to openwisp_controller/config/apps.py, specifically in the connect_cache_dependencies method, which tries to load the pki.Ca model. This suggests an issue with how the pki app is being loaded or accessed, possibly related to the SAMPLE_APP configuration.

The failure occurs during the django.setup() call in tests/manage.py, which is triggered by the runtests.py script. The error message Could not find pki.Ca! indicates that the swapper library, used for swapping models, is unable to locate the Ca model from the pki app.

@openwisp-companion

Copy link
Copy Markdown

CI Failure: Test Failures and Configuration Issues

Hello @pandafy,
(Analysis for commit 5a07dca)

There are two main issues identified in the CI logs:

  1. django.core.exceptions.ImproperlyConfigured: Could not find pki.Cert!: This indicates that the Django application registry is unable to locate the pki.Cert model. This is likely due to an incorrect INSTALLED_APPS configuration or a missing dependency.
  • Fix: Review your settings.py file, specifically the INSTALLED_APPS setting, to ensure that openwisp_controller.pki (or its sample equivalent if SAMPLE_APP is set) is correctly included and that all necessary dependencies for the pki app are installed.
  1. WHOIS/GeoIP Lookup Failures: Multiple WHOIS lookup failed errors suggest issues with the GeoIP service configuration or availability. These errors are related to the OPENWISP_CONTROLLER_WHOIS_GEOIP_ACCOUNT and OPENWISP_CONTROLLER_WHOIS_GEOIP_KEY settings.
  • Fix: Ensure that the OPENWISP_CONTROLLER_WHOIS_GEOIP_ACCOUNT and OPENWISP_CONTROLLER_WHOIS_GEOIP_KEY environment variables are correctly set in your CI environment, or that the corresponding settings in settings.py are properly configured if not using environment variables. If these services are temporarily unavailable, the tests might be failing due to this infrastructure issue.

@coderabbitai coderabbitai Bot left a comment

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.

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/base/vpn.py (1)

366-371: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Defer VPN cache invalidation until commit.

post_save, pre_delete, and vpn_server_modified all clear GetVpnView before commit; another request can repopulate the cache with pre-commit VPN data and leave stale responses after the transaction finishes.

🤖 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/base/vpn.py` around lines 366 - 371, The VPN cache
invalidation hooks are running before the database transaction commits, which
can let stale pre-commit data be cached again by another request. Update the
cache invalidation setup in the Vpn class so the CacheDependency entries for
post_save, pre_delete, and vpn_server_modified defer clearing GetVpnView until
commit by using the existing cls._invalidate_vpn_view_cache target with
on_commit enabled.
🤖 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.

Outside diff comments:
In `@openwisp_controller/config/base/vpn.py`:
- Around line 366-371: The VPN cache invalidation hooks are running before the
database transaction commits, which can let stale pre-commit data be cached
again by another request. Update the cache invalidation setup in the Vpn class
so the CacheDependency entries for post_save, pre_delete, and
vpn_server_modified defer clearing GetVpnView until commit by using the existing
cls._invalidate_vpn_view_cache target with on_commit enabled.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c7e5a077-bdc0-4cc0-a2c3-1c3d7bc7cba6

📥 Commits

Reviewing files that changed from the base of the PR and between 457a4b7 and 5a07dca.

📒 Files selected for processing (5)
  • .github/workflows/ci.yml
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/pki/tests/test_api.py

@coderabbitai coderabbitai Bot left a comment

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.

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/apps.py (1)

139-149: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Defer these post_delete invalidations to commit. Running them inside the transaction leaves a window where a concurrent request can repopulate DeviceGroupCommonName cache with stale data before the delete commits. The handler only needs organization_id/common_name, so this can safely be deferred.

🤖 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/apps.py` around lines 139 - 149, The post_delete
cache invalidations for the CacheDependency entries using
devicegroup_delete_handler are running too early. Update the CacheDependency
configuration for self.devicegroup_model and self.cert_model to defer the
invalidation until commit by enabling on_commit, so the handler runs after the
transaction is finalized. Keep the change focused on the CacheDependency
definitions in apps.py and preserve the existing handler target.
♻️ Duplicate comments (1)
openwisp_controller/config/base/vpn.py (1)

1147-1160: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

N+1 queries per client still present.

Same structural issue flagged in a previous review (then targeting _send_config_modified_signal, now update_status_if_checksum_changed()): vpn.vpnclient_set.iterator() has no select_related, so client.config and the checksum recompute path will hit the DB per client.

♻️ Proposed fix
-        for client in vpn.vpnclient_set.iterator():
+        for client in vpn.vpnclient_set.select_related(
+            "config", "config__device", "config__device__group"
+        ).iterator():
             config = client.config
             # keep the historical signal action for this related change
             config._config_modified_action = "related_template_changed"
             config.update_status_if_checksum_changed()
🤖 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/base/vpn.py` around lines 1147 - 1160, Repeated
per-client database hits remain in invalidate_clients_cache because
vpn.vpnclient_set.iterator() does not prefetch related config data, so
client.config and update_status_if_checksum_changed() trigger N+1 queries.
Update the queryset used in invalidate_clients_cache to fetch the related config
up front (for example via select_related on the client relation), then keep the
existing loop and _config_modified_action assignment so the checksum/status
update still happens per client without extra queries.
🤖 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.

Outside diff comments:
In `@openwisp_controller/config/apps.py`:
- Around line 139-149: The post_delete cache invalidations for the
CacheDependency entries using devicegroup_delete_handler are running too early.
Update the CacheDependency configuration for self.devicegroup_model and
self.cert_model to defer the invalidation until commit by enabling on_commit, so
the handler runs after the transaction is finalized. Keep the change focused on
the CacheDependency definitions in apps.py and preserve the existing handler
target.

---

Duplicate comments:
In `@openwisp_controller/config/base/vpn.py`:
- Around line 1147-1160: Repeated per-client database hits remain in
invalidate_clients_cache because vpn.vpnclient_set.iterator() does not prefetch
related config data, so client.config and update_status_if_checksum_changed()
trigger N+1 queries. Update the queryset used in invalidate_clients_cache to
fetch the related config up front (for example via select_related on the client
relation), then keep the existing loop and _config_modified_action assignment so
the checksum/status update still happens per client without extra queries.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 67c8d211-2a4d-4ef0-aecd-340045ec772f

📥 Commits

Reviewing files that changed from the base of the PR and between 5a07dca and 3931e97.

📒 Files selected for processing (7)
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/base/base.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/tests/test_controller.py
💤 Files with no reviewable changes (1)
  • openwisp_controller/config/base/config.py
📜 Review details
⏰ Context from checks skipped due to timeout. (9)
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.1.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~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.1.0
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Mark user-facing strings for translation with Django i18n helpers in Django code
Avoid unnecessary blank lines inside function and method bodies
Be careful with authentication, authorization, queryset filtering, serializers, admin behavior, cache invalidation, signals, Celery tasks, and websocket updates in Django code
Preserve validation around templates, VPN/PKI material, SSH credentials, device commands, uploaded files, URLs, and subnet/IP data
Write comments and docstrings only when they explain why code is shaped a certain way, placing them before the relevant code block instead of scattering them inside it

Files:

  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/base.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/tests/test_config.py
**/*.{py,html}

📄 CodeRabbit inference engine (Custom checks)

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

Files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/base.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-07T12:07:08.468Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_admin.py:2335-2335
Timestamp: 2026-06-07T12:07:08.468Z
Learning: In this project’s Python test suite (files under openwisp_controller/**/tests/), don’t require or request prose/inline comments that document the breakdown of query-count changes (e.g., assertions around template/DB query counts in helpers like _verify_template_queries). Treat query-count assertions as volatile implementation details that change frequently; review should focus on whether the test asserts the expected behavior, not on explaining the specific query-count deltas in comments.

Applied to files:

  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-07T12:07:24.608Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/pki/tests/test_api.py:155-155
Timestamp: 2026-06-07T12:07:24.608Z
Learning: When reviewing Python test files in this repository, avoid recommending inline comments that explain or justify `assertNumQueries` (Django query count) expectations. Query counts can change frequently as implementations evolve, and inline explanations add maintenance burden; the expected count should be understandable without added comment blocks.

Applied to files:

  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-25T12:20:18.414Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/base/models.py:571-572
Timestamp: 2026-06-25T12:20:18.414Z
Learning: When writing or reviewing tests that override pagination behavior via OpenWispPagination.paginate_queryset(), patch `view.pagination_page_size` (not `page_size`). The method uses `getattr(view, "pagination_page_size", self.page_size)`, so tests must set the attribute on the view to affect pagination. If the view class does not define `pagination_page_size`, using `unittest.mock.patch(..., create=True)` is intentional and correct because the attribute may not exist until patched.

Applied to files:

  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-07T12:07:25.164Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_config.py:864-865
Timestamp: 2026-06-07T12:07:25.164Z
Learning: When reviewing this repo’s Python test suite, treat changes to the *expected* query count in `assertNumQueries(...)` calls as routine test maintenance. If a PR updates the numeric argument (e.g., in `test_config.py`, `test_api.py`, `test_admin.py`, `test_pki.py`) and the test remains consistent with the feature changes, reviewers should not flag the increased number as a performance regression that requires investigation solely because the count went up; instead, focus on whether the update is intentional and the surrounding test/code changes justify the revised expectation.

Applied to files:

  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-25T12:20:45.387Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/tests/test_api.py:916-932
Timestamp: 2026-06-25T12:20:45.387Z
Learning: When reviewing API pagination behavior in openwisp-controller, assume `OpenWispPagination.paginate_queryset()` allows a per-view page-size override via `getattr(view, "pagination_page_size", self.page_size)` (so `view.pagination_page_size`, if present, should affect pagination). In Python tests, it is valid to patch `pagination_page_size` on a view class even if the attribute isn’t declared on the class by default, by using `unittest.mock.patch.object(..., "pagination_page_size", ..., create=True)` so the override is available for the pagination logic during the test.

Applied to files:

  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_config.py
🔇 Additional comments (10)
openwisp_controller/config/tests/test_config.py (5)

571-587: Still missing captureOnCommitCallbacks — assertion doesn't exercise _resolve_cert_dependency().

Only the comments changed here; cert.save() still isn't wrapped in self.captureOnCommitCallbacks(execute=True). Since TestConfig extends TestCase, the transaction.on_commit callback that would invoke _resolve_cert_dependency() never fires, so this test passes trivially regardless of whether the "returns early because VpnClient was deleted" logic is correct. This was already raised in a previous review of this test.


932-975: LGTM!


1059-1197: LGTM!


1365-1391: LGTM!


1207-1211: 🎯 Functional Correctness

No issue here Each test method calls _connect() only once, so the fixed dispatch_uid does not create a collision in this class.

			> Likely an incorrect or invalid review comment.
openwisp_controller/config/base/base.py (1)

5-50: LGTM!

Also applies to: 263-283, 341-355, 377-387, 407-415

openwisp_controller/config/apps.py (1)

45-155: LGTM!

Also applies to: 168-253, 356-387

openwisp_controller/config/base/vpn.py (1)

340-410: LGTM!

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

6-6: LGTM!

Also applies to: 20-20, 1492-1517

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

187-188: LGTM!

Also applies to: 479-491

@pandafy

pandafy commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai summary

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Summary regeneration triggered.

@coderabbitai coderabbitai Bot added bug and removed enhancement labels Jul 3, 2026
@pandafy
pandafy force-pushed the issues/1095-generic-cache-invalidation branch from 86f5e0c to d32576a Compare July 3, 2026 18:12
@coderabbitai coderabbitai Bot added enhancement and removed bug labels Jul 3, 2026
@pandafy
pandafy marked this pull request as ready for review July 3, 2026 18:16

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 5

🤖 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/apps.py`:
- Around line 139-149: The post_delete cache invalidations for the device group
and cert dependencies are running before the transaction commits, which can race
with concurrent reads. Update the CacheDependency entries in apps.py to use
on_commit for the delete handlers, and adjust devicegroup_delete_handler so it
receives the needed snapshot data (organization_id, common_name, and model name)
instead of relying on the soon-to-be-deleted object. Make sure the delete path
still targets devicegroup_delete_handler and defers the invalidation until
commit.
- Around line 84-108: The cache invalidations wired in `CacheDependency` for
`DeviceChecksumView.invalidate_get_device_cache` and
`DeviceChecksumView.invalidate_get_device_cache_on_config_deactivated` should
run after transaction commit instead of immediately. Update the
`CacheDependency` entries for `post_save` and `config_deactivated` in the app
config setup so they defer to commit consistently with the existing
`post_delete` handling, preventing stale `DeviceChecksumView` data from being
repopulated before the transaction is finalized.

In `@openwisp_controller/config/base/base.py`:
- Around line 263-365: The tracked-field snapshot is being reused across later
saves, so `_snapshot_handler` should consume and clear the stored snapshot after
each post-save comparison. Update the snapshot lifecycle around
`_snapshot_handler`, `_tracked_fields_changed`, and `_should_skip` so a fresh
snapshot is taken per save and any old entry in
`instance.__dict__[self._SNAPSHOT_ATTR]` for `self._uid` is removed once it has
been evaluated. This prevents stale snapshot objects from affecting unrelated
future `save(update_fields=...)` calls.

In `@openwisp_controller/config/base/vpn.py`:
- Around line 367-391: Update the CacheDependency entries in Vpn view-cache
invalidation so that both the post_save path and the vpn_server_modified signal
defer invalidation until transaction commit. In the Vpn cache dependency setup,
change the on_commit behavior for cls._invalidate_vpn_view_cache so
GetVpnView.get_vpn is not invalidated/repopulated before the transaction is
committed.

In `@openwisp_controller/config/tests/test_vpn.py`:
- Around line 573-600: The two renew-cascade tests duplicate the same
VPN/template/device setup and initial config checksum assertions, so factor that
shared logic into a small helper in test_vpn.py and keep each test focused on
its specific action (`vpn.ca.renew()` vs `vpn.cert.renew()`). Use the existing
test methods `test_ca_renew_cascades_to_client_config` and
`test_cert_renew_cascades_to_client_config`, plus the helper builders like
`_create_vpn`, `_create_template`, and `_create_device_config`, to reduce
repetition while preserving the distinct post-renew assertions and signal check.
🪄 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: bfba3d59-70c2-4c62-8109-c22c977554d8

📥 Commits

Reviewing files that changed from the base of the PR and between 86f5e0c and d32576a.

📒 Files selected for processing (18)
  • .github/workflows/ci.yml
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/base/base.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/device_group.py
  • openwisp_controller/config/base/multitenancy.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/subnet_division/admin.py
  • openwisp_controller/subnet_division/rule_types/vpn.py
  • openwisp_controller/subnet_division/tests/test_models.py
💤 Files with no reviewable changes (2)
  • openwisp_controller/config/base/multitenancy.py
  • openwisp_controller/config/base/device_group.py
📜 Review details
⏰ Context from checks skipped due to timeout. (11)
  • GitHub Check: Python==3.13 | 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.10 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.2.0
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Mark user-facing strings for translation with Django i18n helpers in Django code
Avoid unnecessary blank lines inside function and method bodies
Be careful with authentication, authorization, queryset filtering, serializers, admin behavior, cache invalidation, signals, Celery tasks, and websocket updates in Django code
Preserve validation around templates, VPN/PKI material, SSH credentials, device commands, uploaded files, URLs, and subnet/IP data
Write comments and docstrings only when they explain why code is shaped a certain way, placing them before the relevant code block instead of scattering them inside it

Files:

  • openwisp_controller/subnet_division/admin.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/subnet_division/rule_types/vpn.py
  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/base.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/apps.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/subnet_division/admin.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/subnet_division/rule_types/vpn.py
  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/base.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/apps.py
🧠 Learnings (10)
📚 Learning: 2026-02-24T16:24:55.443Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1233
File: .github/workflows/backport.yml:22-22
Timestamp: 2026-02-24T16:24:55.443Z
Learning: In repositories within the OpenWISP organization, it is acceptable to reference reusable workflows from other OpenWISP-controlled repos using mutable refs (e.g., master) in .github/workflows. This is permissible due to the shared trust boundary within the organization. If applying this pattern, ensure the target repos are under the same organization and maintain awareness of potential breakages from upstream mutable refs; consider pinning to a tagged version for longer-term stability when appropriate.

Applied to files:

  • .github/workflows/ci.yml
📚 Learning: 2026-02-24T16:25:20.080Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1233
File: .github/workflows/backport.yml:35-35
Timestamp: 2026-02-24T16:25:20.080Z
Learning: In .github/workflows/backport.yml, enforce that backport-on-comment triggers only for users with author_association MEMBE R or OWNER (COLLABORATOR excluded), reflecting maintainer feedback. Update the trigger condition to check author_association and restrict to MEMBERS/OWNERS; document rationale and PR `#1233` reference in code comments.

Applied to files:

  • .github/workflows/ci.yml
📚 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/subnet_division/admin.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/subnet_division/rule_types/vpn.py
  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/base.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/apps.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/subnet_division/admin.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/subnet_division/rule_types/vpn.py
  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/base.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/apps.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/subnet_division/admin.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/subnet_division/rule_types/vpn.py
  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/base.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/apps.py
📚 Learning: 2026-06-07T12:07:08.468Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_admin.py:2335-2335
Timestamp: 2026-06-07T12:07:08.468Z
Learning: In this project’s Python test suite (files under openwisp_controller/**/tests/), don’t require or request prose/inline comments that document the breakdown of query-count changes (e.g., assertions around template/DB query counts in helpers like _verify_template_queries). Treat query-count assertions as volatile implementation details that change frequently; review should focus on whether the test asserts the expected behavior, not on explaining the specific query-count deltas in comments.

Applied to files:

  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-07T12:07:24.608Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/pki/tests/test_api.py:155-155
Timestamp: 2026-06-07T12:07:24.608Z
Learning: When reviewing Python test files in this repository, avoid recommending inline comments that explain or justify `assertNumQueries` (Django query count) expectations. Query counts can change frequently as implementations evolve, and inline explanations add maintenance burden; the expected count should be understandable without added comment blocks.

Applied to files:

  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-25T12:20:18.414Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/base/models.py:571-572
Timestamp: 2026-06-25T12:20:18.414Z
Learning: When writing or reviewing tests that override pagination behavior via OpenWispPagination.paginate_queryset(), patch `view.pagination_page_size` (not `page_size`). The method uses `getattr(view, "pagination_page_size", self.page_size)`, so tests must set the attribute on the view to affect pagination. If the view class does not define `pagination_page_size`, using `unittest.mock.patch(..., create=True)` is intentional and correct because the attribute may not exist until patched.

Applied to files:

  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-07T12:07:25.164Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_config.py:864-865
Timestamp: 2026-06-07T12:07:25.164Z
Learning: When reviewing this repo’s Python test suite, treat changes to the *expected* query count in `assertNumQueries(...)` calls as routine test maintenance. If a PR updates the numeric argument (e.g., in `test_config.py`, `test_api.py`, `test_admin.py`, `test_pki.py`) and the test remains consistent with the feature changes, reviewers should not flag the increased number as a performance regression that requires investigation solely because the count went up; instead, focus on whether the update is intentional and the surrounding test/code changes justify the revised expectation.

Applied to files:

  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-25T12:20:45.387Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/tests/test_api.py:916-932
Timestamp: 2026-06-25T12:20:45.387Z
Learning: When reviewing API pagination behavior in openwisp-controller, assume `OpenWispPagination.paginate_queryset()` allows a per-view page-size override via `getattr(view, "pagination_page_size", self.page_size)` (so `view.pagination_page_size`, if present, should affect pagination). In Python tests, it is valid to patch `pagination_page_size` on a view class even if the attribute isn’t declared on the class by default, by using `unittest.mock.patch.object(..., "pagination_page_size", ..., create=True)` so the override is available for the pagination logic during the test.

Applied to files:

  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_controller.py
  • openwisp_controller/config/tests/utils.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/subnet_division/tests/test_models.py
  • openwisp_controller/config/tests/test_vpn.py
  • openwisp_controller/config/tests/test_config.py
🪛 ast-grep (0.44.0)
openwisp_controller/subnet_division/tests/test_models.py

[warning] 26-26: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("config", "VpnClient")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)

openwisp_controller/config/base/config.py

[warning] 241-241: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("openwisp_users", "Organization")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)

openwisp_controller/config/base/base.py

[warning] 212-212: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model(app_label, model_name)
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)

openwisp_controller/config/tests/test_config.py

[warning] 27-27: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("config", "OrganizationConfigSettings")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)


[warning] 28-28: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("config", "Template")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)

🔇 Additional comments (17)
.github/workflows/ci.yml (1)

75-76: Same concern already raised.

This TODO workaround using a mutable branch tarball was already flagged in a previous review requesting a pinned commit SHA for reproducibility.

openwisp_controller/subnet_division/admin.py (1)

28-38: LGTM!

openwisp_controller/subnet_division/rule_types/vpn.py (1)

44-58: LGTM! Reordering correctly ensures instance.ip is persisted before super().post_provision_handler() caches the checksum, resolving the ip_address_<vpn_pk> template variable at the right time — validated by the new regression test in test_models.py.

openwisp_controller/subnet_division/tests/test_models.py (1)

26-26: LGTM! Regression test correctly exercises the post_provision_handler ordering fix by asserting the ip_address_<vpn_pk> context resolves and that checksum_db matches the freshly computed checksum after a DB refresh.

Also applies to: 66-94

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

606-663: LGTM! Moving these cache-invalidation tests into TestTransactionDevice (a TransactionTestCase) is necessary since CacheDependency defers invalidation via transaction.on_commit, which never fires under the default TestCase transaction wrapping.

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

571-975: LGTM!

Also applies to: 1059-1391

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

1492-1517: LGTM!

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

187-188: LGTM!

Also applies to: 479-491

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

519-557: LGTM!

Also applies to: 601-2008

openwisp_controller/pki/tests/test_api.py (1)

164-164: LGTM!

Based on learnings, "treat changes to the expected query count in assertNumQueries(...) calls as routine test maintenance" and avoid requiring justification comments for such deltas.

Also applies to: 292-292

Source: Learnings

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

174-181: 🎯 Functional Correctness

No issue with the shared WireGuard test fixture config override

openwisp_controller/config/handlers.py (2)

72-74: Duplicate: schedule this cache invalidation after commit.

The direct .delay() call can run before the surrounding transaction commits; this was already flagged in a previous review comment for this handler.


44-60: LGTM!

Also applies to: 62-71

openwisp_controller/config/base/base.py (1)

5-51: LGTM!

Also applies to: 127-262, 368-416

openwisp_controller/config/apps.py (1)

5-5: LGTM!

Also applies to: 50-83, 110-138, 152-155, 356-387

openwisp_controller/config/base/config.py (1)

29-35: LGTM!

Also applies to: 49-49, 177-299

openwisp_controller/config/base/vpn.py (1)

18-18: LGTM!

Also applies to: 37-58, 331-360, 393-410

Comment thread openwisp_controller/config/apps.py
Comment thread openwisp_controller/config/apps.py
Comment thread openwisp_controller/config/base/base.py Outdated
Comment thread openwisp_controller/config/base/vpn.py
Comment thread openwisp_controller/config/tests/test_vpn.py Outdated
@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).

@openwisp-companion

Copy link
Copy Markdown

Flake8 E501 Line Too Long

Hello @pandafy,
(Analysis for commit a494223)

The CI build failed because of a flake8 error (E501: line too long). This indicates that a line of code exceeds the maximum allowed length (88 characters).

To fix this:

  1. Locate the line: The error message points to openwisp_controller/config/apps.py:139:89.
  2. Break the line: Manually refactor the line to be more readable and adhere to the line length limit.

After fixing the line, please commit the changes and push again.

@openwisp-companion

Copy link
Copy Markdown

Migrations Check Failed

Hello @pandafy,
(Analysis for commit 8786166)

There are multiple CI jobs failing due to a "Migrations check failed" error. This indicates that the database migrations have not been applied correctly.

Fix:
Please run the following command to generate and apply the missing migrations:

./manage.py makemigrations
./manage.py migrate

After applying the migrations locally, commit the changes and push again.

@pandafy
pandafy force-pushed the issues/1095-generic-cache-invalidation branch from fef2d66 to 75c59a7 Compare July 11, 2026 10:17
@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).

@github-project-automation github-project-automation Bot moved this from Backlog to In progress in 26.06 Release Jul 11, 2026
@nemesifier nemesifier moved this from In progress to Ready for review/testing in 26.06 Release Jul 11, 2026
@nemesifier

Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

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.

Caution

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

⚠️ Outside diff range comments (1)
docs/developer/utils.rst (1)

477-485: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the documented dispatch UIDs match build_dispatch_uid().

The output lists the resolver and tracked fields at Lines 479 and 484, but the corresponding UIDs omit them. CacheDependency.build_dispatch_uid() appends both components, so these examples cannot match the command’s actual output.

Proposed correction
-        uid: cache_invalidation.config.config.config.device.post_save.update_status_if_checksum_changed
+        uid: cache_invalidation.config.config.config.device.post_save.update_status_if_checksum_changed._resolve_device_dependency.os+organization_id
...
-        uid: cache_invalidation.config.config.config.template.pre_delete.update_status_if_checksum_changed
+        uid: cache_invalidation.config.config.config.template.pre_delete.update_status_if_checksum_changed._resolve_template_dependency
🤖 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 `@docs/developer/utils.rst` around lines 477 - 485, Update the documented UIDs
for the config.device and config.template examples to include the resolver and
tracked-field components produced by CacheDependency.build_dispatch_uid().
Ensure both examples exactly match the dispatch UID format generated by
build_dispatch_uid(), including the applicable fields shown in each resolve
configuration.
🤖 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.

Outside diff comments:
In `@docs/developer/utils.rst`:
- Around line 477-485: Update the documented UIDs for the config.device and
config.template examples to include the resolver and tracked-field components
produced by CacheDependency.build_dispatch_uid(). Ensure both examples exactly
match the dispatch UID format generated by build_dispatch_uid(), including the
applicable fields shown in each resolve configuration.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 16af539d-ace5-47f6-9078-f4e59965dff1

📥 Commits

Reviewing files that changed from the base of the PR and between a503e01 and d30946d.

📒 Files selected for processing (7)
  • docs/developer/utils.rst
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/base/cache.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/management/commands/print_cache_dependencies.py
  • openwisp_controller/config/tests/test_config.py
📜 Review details
⏰ Context from checks skipped due to timeout. (10)
  • GitHub Check: CodeRabbit
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Kilo Code Review
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Mark user-facing strings for translation with Django i18n helpers in Django code
Avoid unnecessary blank lines inside function and method bodies
Be careful with authentication, authorization, queryset filtering, serializers, admin behavior, cache invalidation, signals, Celery tasks, and websocket updates in Django code
Preserve validation around templates, VPN/PKI material, SSH credentials, device commands, uploaded files, URLs, and subnet/IP data
Write comments and docstrings only when they explain why code is shaped a certain way, placing them before the relevant code block instead of scattering them inside it

Files:

  • openwisp_controller/config/management/commands/print_cache_dependencies.py
  • openwisp_controller/config/base/cache.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/apps.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/management/commands/print_cache_dependencies.py
  • openwisp_controller/config/base/cache.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/apps.py
🧠 Learnings (8)
📚 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/management/commands/print_cache_dependencies.py
  • openwisp_controller/config/base/cache.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/apps.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/management/commands/print_cache_dependencies.py
  • openwisp_controller/config/base/cache.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/apps.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/management/commands/print_cache_dependencies.py
  • openwisp_controller/config/base/cache.py
  • openwisp_controller/config/base/vpn.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/apps.py
📚 Learning: 2026-06-07T12:07:08.468Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_admin.py:2335-2335
Timestamp: 2026-06-07T12:07:08.468Z
Learning: In this project’s Python test suite (files under openwisp_controller/**/tests/), don’t require or request prose/inline comments that document the breakdown of query-count changes (e.g., assertions around template/DB query counts in helpers like _verify_template_queries). Treat query-count assertions as volatile implementation details that change frequently; review should focus on whether the test asserts the expected behavior, not on explaining the specific query-count deltas in comments.

Applied to files:

  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-07T12:07:24.608Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/pki/tests/test_api.py:155-155
Timestamp: 2026-06-07T12:07:24.608Z
Learning: When reviewing Python test files in this repository, avoid recommending inline comments that explain or justify `assertNumQueries` (Django query count) expectations. Query counts can change frequently as implementations evolve, and inline explanations add maintenance burden; the expected count should be understandable without added comment blocks.

Applied to files:

  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-25T12:20:18.414Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/base/models.py:571-572
Timestamp: 2026-06-25T12:20:18.414Z
Learning: When writing or reviewing tests that override pagination behavior via OpenWispPagination.paginate_queryset(), patch `view.pagination_page_size` (not `page_size`). The method uses `getattr(view, "pagination_page_size", self.page_size)`, so tests must set the attribute on the view to affect pagination. If the view class does not define `pagination_page_size`, using `unittest.mock.patch(..., create=True)` is intentional and correct because the attribute may not exist until patched.

Applied to files:

  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-07T12:07:25.164Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/config/tests/test_config.py:864-865
Timestamp: 2026-06-07T12:07:25.164Z
Learning: When reviewing this repo’s Python test suite, treat changes to the *expected* query count in `assertNumQueries(...)` calls as routine test maintenance. If a PR updates the numeric argument (e.g., in `test_config.py`, `test_api.py`, `test_admin.py`, `test_pki.py`) and the test remains consistent with the feature changes, reviewers should not flag the increased number as a performance regression that requires investigation solely because the count went up; instead, focus on whether the update is intentional and the surrounding test/code changes justify the revised expectation.

Applied to files:

  • openwisp_controller/config/tests/test_config.py
📚 Learning: 2026-06-25T12:20:45.387Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/tests/test_api.py:916-932
Timestamp: 2026-06-25T12:20:45.387Z
Learning: When reviewing API pagination behavior in openwisp-controller, assume `OpenWispPagination.paginate_queryset()` allows a per-view page-size override via `getattr(view, "pagination_page_size", self.page_size)` (so `view.pagination_page_size`, if present, should affect pagination). In Python tests, it is valid to patch `pagination_page_size` on a view class even if the attribute isn’t declared on the class by default, by using `unittest.mock.patch.object(..., "pagination_page_size", ..., create=True)` so the override is available for the pagination logic during the test.

Applied to files:

  • openwisp_controller/config/tests/test_config.py
🪛 ast-grep (0.44.1)
openwisp_controller/config/base/cache.py

[warning] 129-129: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model(app_label, model_name)
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)


[info] 234-234: use jsonify instead of json.dumps for JSON output
Context: json.dumps([dep.describe() for dep in dependencies], indent=2)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🔇 Additional comments (19)
openwisp_controller/config/management/commands/print_cache_dependencies.py (1)

2-4: LGTM!

Also applies to: 7-18, 21-22

docs/developer/utils.rst (1)

433-457: LGTM!

Also applies to: 461-476, 487-499

openwisp_controller/config/base/cache.py (6)

19-35: LGTM!


96-164: LGTM!


166-198: LGTM!


200-259: LGTM!


261-342: LGTM!


344-402: LGTM!

openwisp_controller/config/apps.py (4)

45-55: LGTM!


110-115: 🗄️ Data Integrity & Integration | ⚡ Quick win

Confirm the immediate (on_commit=False) org-disable invalidation is intentional.

Unlike the device/config handlers in this same list (which were deliberately moved to commit-time to avoid a concurrent request repopulating a stale entry), this one invalidates on pre_save immediately. If a controller request reads a device of this org during the disable transaction, it can repopulate the controller-view cache with still-enabled data that persists after commit. This preserves the previously-wired Organization.pre_save behavior, so please confirm the timing asymmetry is deliberate (e.g. the handler must observe the old DB state at pre_save).


357-388: LGTM!


84-151: 🩺 Stability & Availability

No change needed. The handlers already take a single instance argument, and both custom signals send instance= in their payloads.

			> Likely an incorrect or invalid review comment.
openwisp_controller/config/base/vpn.py (3)

1157-1159: 🚀 Performance & Scalability

Confirm the select_related covers every relation touched during checksum recompute.

update_status_if_checksum_changed() regenerates the config via get_context(), which for device configs reads the device's organization/config_settings (not just group). The current select_related preloads config__device__group but not the organization relation, so a VPN with many clients may still incur a per-client query for org context — leaving the previously flagged N+1 only partially resolved.

#!/bin/bash
set -euo pipefail
# Inspect whether get_context / config generation traverses device.organization(.config_settings)
rg -nP -C4 'def get_context' openwisp_controller/config/base/device.py openwisp_controller/config/base/config.py
rg -nP -C3 'organization(__|\.)config_settings|self\.device\.organization|_get_organization' openwisp_controller/config/base -g '*.py'
Suggested direction if org context is traversed
         for client in vpn.vpnclient_set.select_related(
-            "config", "config__device", "config__device__group"
+            "config",
+            "config__device",
+            "config__device__group",
+            "config__device__organization__config_settings",
         ).iterator():

37-38: LGTM!


329-406: LGTM!

openwisp_controller/config/base/config.py (3)

29-30: LGTM!


269-293: LGTM!


246-266: 🚀 Performance & Scalability

Keep the django_x509.Cert dependency broad. _resolve_cert_dependency() already filters revoked or unlinked certs, and the current post_save hook is what catches cert renewals and other state changes that can alter the rendered config.

			> Likely an incorrect or invalid review comment.
openwisp_controller/config/tests/test_config.py (1)

21-21: LGTM!

@nemesifier

Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nemesifier

Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

@nemesifier

Copy link
Copy Markdown
Member

@coderabbitai any remaining issue?

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@nemesifier Based on the last full review (focused on critical issues), all review comment threads were resolved and the PR was approved — no outstanding critical issues were found. The subsequent review requests didn't surface anything new since there were no new commits to review (the incremental review system only re-reviews new changes). So, as things stand, there are no remaining issues from my side.

ᕦ(ò_óˇ)ᕤ

@openwisp-companion

Copy link
Copy Markdown

Proposed change log entry:

[feature] Added generic cache invalidation for config models #1095

Introduced a declarative engine for cache invalidation based on
`CacheDependency`. This system centralizes cache invalidation logic,
replacing scattered signal handlers. Models can now declare dependencies
on related object changes to invalidate their cached values. A new
management command, `print_cache_dependencies`, allows inspection of the
invalidation graph.

Closes #1095

@nemesifier

Copy link
Copy Markdown
Member

@coderabbitai resolve

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Comments resolved and changes approved.

@nemesifier
nemesifier merged commit 3cac71b into master Jul 11, 2026
25 checks passed
@nemesifier
nemesifier deleted the issues/1095-generic-cache-invalidation branch July 11, 2026 18:23
@github-project-automation github-project-automation Bot moved this from Ready for review/testing to Done in 26.06 Release Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[feature] Generic solution for invalidating cache on related field changes

3 participants