Skip to content

[fix] Disallow changing configuration backend from UI #789#1293

Open
hypnoastic wants to merge 1 commit intoopenwisp:masterfrom
hypnoastic:issues/789-disallow-changing-configuration-backend-ui
Open

[fix] Disallow changing configuration backend from UI #789#1293
hypnoastic wants to merge 1 commit intoopenwisp:masterfrom
hypnoastic:issues/789-disallow-changing-configuration-backend-ui

Conversation

@hypnoastic
Copy link

@hypnoastic hypnoastic commented Mar 17, 2026

What does this PR do?

This PR makes backend read-only on Django admin change forms for device configuration, template, and VPN server, while keeping
it selectable on create forms.

Why the scope is bigger than the issue title

The admin change made backend non-editable, which removed selectors used by existing JS and Selenium flows on change pages. The extra updates are regression/compatibility fixes to keep current admin behavior working with the new readonly contract, not additional feature work.

What changed

  • Enforced UI-only readonly-on-change for backend in:
    • device configuration
    • template
    • VPN server
  • Kept backend selectable on create forms.
  • No model/API/shell restrictions added.
  • Updated JS/view logic and tests so change-page behavior remains stable when backend is readonly.
  • Added/updated regression tests for immutability, recover behavior, and backend inference paths.

Files updated (and why)

  • openwisp_controller/config/admin.py core readonly-on-change behavior.
  • openwisp_controller/config/tests/test_admin.py create/change immutability + recover-view coverage.
  • openwisp_controller/config/static/config/js/relevant_templates.js template loading on readonly change forms.
  • openwisp_controller/config/views.py backend inference fallback for readonly flows with tenant safety.
  • openwisp_controller/config/tests/test_views.py coverage for fallback and cross-org safety.
  • openwisp_controller/config/static/config/js/vpn.js VPN related-field behavior with readonly backend.
  • openwisp_controller/config/static/config/js/widget.js schema selection fallback when backend is readonly.
  • openwisp_controller/config/tests/test_selenium.py aligned Selenium expectations with readonly-on-change behavior.

UI Behaviour

Manually verified in admin UI for create vs edit flows for VPN, devices, and templates.

VPN

Screenshot 2026-03-17 at 3 19 54 PM

Devices

Screenshot 2026-03-17 at 3 27 11 PM

Templates

Screenshot 2026-03-17 at 3 17 03 PM

Self-review checklist

  • Change is scoped only to issue [change] Disallow changing configuration backend from UI #789
  • Backend remains selectable on create forms
  • Backend is not editable on change forms
  • No model/API/shell restriction was introduced
  • Relevant tests pass
  • UI behavior was manually verified for VPN, devices, and templates
  • No unrelated changes were included

Closes #789

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 17, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Makes the configuration "backend" field readonly on admin change forms except for recover views. Adds server-side detection (is_recover_view) and disables the backend field in ConfigForm; updates ConfigInline, TemplateAdmin, and VpnAdmin to include backend in readonly fields when appropriate. Frontend JS reads the backend from a readonly label when the input is absent, uses config_id when fetching relevant templates, and loads templates for readonly-backend scenarios. get_relevant_templates can derive backend from config_id when backend is not provided. Tests cover readonly behavior, POST immutability of backend, and template loading with inferred backend.

Sequence Diagram(s)

sequenceDiagram
    participant Browser as Admin UI (browser + JS)
    participant DjangoAdmin as Django Admin View
    participant ConfigForm as ConfigForm (server)
    participant Views as get_relevant_templates
    participant DB as Database

    Browser->>DjangoAdmin: GET change-form for object
    DjangoAdmin->>ConfigForm: instantiate (__init__) / request check
    ConfigForm-->>DjangoAdmin: mark backend field disabled if not recover view
    DjangoAdmin-->>Browser: Render form (backend input or readonly label)
    Browser->>Browser: JS reads backend from input or readonly label
    Browser->>Views: GET relevant templates (org, backend or config_id)
    Views->>DB: if backend missing and config_id present -> fetch Config.backend
    DB-->>Views: return backend (or none)
    Views-->>Browser: Return filtered templates
    Browser->>DjangoAdmin: POST change-form attempting backend change
    DjangoAdmin->>ConfigForm: validate/save (backend ignored when readonly)
    ConfigForm->>DB: Save object (backend preserved)
    DB-->>DjangoAdmin: Saved object
    DjangoAdmin-->>Browser: Redirect / success (backend unchanged)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes


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
Bug Fixes ❌ Error PR implements core fix for readonly backend on change forms but leaves three critical issues unaddressed: vpn.js label parsing incompatibility with custom backends, widget.js fuzzy-matching failure for custom backend identifiers, and missing ValidationError handling for malformed UUIDs. Fix label parsing by exposing backend identifier in DOM data attributes; update test to verify change handler execution; add ValidationError to exception handlers for graceful UUID error handling.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title '[fix] Disallow changing configuration backend from UI #789' follows the required format with the [fix] prefix, clearly describes the main change (disallowing backend changes), and references issue #789.
Description check ✅ Passed The PR description comprehensively covers all required sections: clearly explains what the PR does, addresses why scope exceeds title, details all changes, lists affected files with justifications, includes manual verification screenshots, provides a self-review checklist, and closes #789.
Linked Issues check ✅ Passed The PR fully implements issue #789's requirements: backend is made read-only on admin change forms for device configuration, template, and VPN server while remaining selectable on create forms, and no model/API/shell restrictions were introduced.
Out of Scope Changes check ✅ Passed All changes are scoped to issue #789: admin readonly enforcement on change forms, JS/view compatibility fixes for readonly backend, and comprehensive test coverage including regression tests for immutability and recover behavior.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@openwisp_controller/config/tests/test_admin.py`:
- Around line 1545-1568: In
test_template_backend_cannot_be_changed_from_change_form replace the hardcoded
backend assertion with a comparison to the template's original backend value:
capture the original value (e.g., original_backend = template.backend) before
posting, then after response and refresh_from_db assert template.backend ==
original_backend instead of "netjsonconfig.OpenWrt"; update the similar
assertions in the other test referenced (lines 1688-1712) to use the template
variable's original backend value as well to avoid fixture-specific constants.
🪄 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: eaec9162-5a0d-49a9-bf38-d7e62a735b8d

📥 Commits

Reviewing files that changed from the base of the PR and between 8cf6733 and 6298af4.

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

📄 CodeRabbit inference engine (Custom checks)

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

Files:

  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_admin.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/admin.py
  • openwisp_controller/config/tests/test_admin.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/admin.py
  • openwisp_controller/config/tests/test_admin.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/admin.py
  • openwisp_controller/config/tests/test_admin.py
🧠 Learnings (3)
📚 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/admin.py
  • openwisp_controller/config/tests/test_admin.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/admin.py
  • openwisp_controller/config/tests/test_admin.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/admin.py
  • openwisp_controller/config/tests/test_admin.py
🔇 Additional comments (7)
openwisp_controller/config/admin.py (3)

470-474: Good conditional lock for device config backend on change.

This correctly keeps backend immutable only when an existing config is present, while not affecting add-time behavior.


1087-1092: Template backend readonly behavior is implemented cleanly.

The guard on obj preserves backend selection on add and enforces immutability on change.


1272-1277: VPN backend readonly behavior is consistent with TemplateAdmin.

The implementation is minimal and correctly scoped to change forms.

openwisp_controller/config/tests/test_admin.py (4)

1438-1445: Good assertion strategy for readonly rendering on device change form.

Checking presence of the field wrapper and absence of the editable input captures the intended UI lock.


1446-1470: Great regression test for backend tampering via POST on device change.

This correctly verifies that submitted backend edits are ignored on change forms.


1531-1544: Template readonly UI coverage looks good.

The test cleanly validates non-editability in the admin change form.


1681-1687: VPN readonly rendering test is correct and focused.

This verifies backend is displayed but not editable in change context.

@openwisp-companion
Copy link

Commit Message and Test Failures

Hello @hypnoastic,
(Analysis for commit 6298af4)

  1. Commit Message Failure: The commit message does not follow the required format.
  • Fix: Ensure the commit message adheres to the following structure:
[tag] Capitalized short title #<issue>

<detailed description>

Fixes #<issue>

For example:

[feature] Add new user authentication method #123

Implemented a new authentication method using OAuth2,
which enhances security and user experience.

Fixes #123
  1. Test Failure: The test test_restoring_deleted_device in openwisp_controller.tests.test_selenium.TestDevice failed due to a TimeoutException and AssertionError.
  • Diagnosis: The test timed out while waiting for a condition, indicating that the deleted device was not restored as expected within the 5-second timeout. This is likely due to an issue in the device restoration logic or the test setup.
  • Fix: Review the device restoration process in the test_restoring_deleted_device method and the corresponding backend logic to identify why the restoration is failing or taking too long. Ensure that all related objects and states are correctly handled during the restoration.

@hypnoastic hypnoastic force-pushed the issues/789-disallow-changing-configuration-backend-ui branch from 6298af4 to 242c45b Compare March 17, 2026 10:47
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@openwisp_controller/config/tests/test_admin.py`:
- Around line 1466-1469: The test posts the change form but never asserts the
submission succeeded, so it can miss validation failures; after calling
self.client.post(path, params) assert the response indicates success (e.g., a
302 redirect or expected status_code) before calling config.refresh_from_db()
and asserting immutability of config.backend against template.backend; update
the block around self.client.post(path, params), response,
config.refresh_from_db(), and self.assertEqual(config.backend, template.backend)
to first assert the redirect/status and then reload and compare backend to
ensure the form was accepted and the backend remained unchanged.
🪄 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: a162074c-a008-49d3-bf31-2dc9b194eaea

📥 Commits

Reviewing files that changed from the base of the PR and between 6298af4 and 242c45b.

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

📄 CodeRabbit inference engine (Custom checks)

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

Files:

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

Applied to files:

  • openwisp_controller/config/tests/test_admin.py
📚 Learning: 2026-03-14T19:34:33.773Z
Learnt from: CR
Repo: openwisp/openwisp-controller PR: 0
File: coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt:0-0
Timestamp: 2026-03-14T19:34:33.773Z
Learning: Applies to **/*{.test,.spec,_test,test_}.{js,ts,tsx,jsx,py,java,go,cs,rb} : Tests must be updated to cover non-trivial changes and ensure proper validation of modified behavior

Applied to files:

  • openwisp_controller/config/tests/test_admin.py
🔇 Additional comments (8)
openwisp_controller/config/admin.py (3)

470-474: Good conditional readonly enforcement for device config backend.

This correctly keeps backend editable when creating config and locks it once config already exists on change forms.


1087-1092: Template backend immutability is implemented correctly.

The obj guard ensures backend remains selectable on add and readonly on change.


1272-1277: VPN backend readonly behavior aligns with the intended UX.

This change is consistent with the Template and Device config behavior.

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

1438-1445: Strong readonly rendering assertion for device backend.

This test correctly verifies that change forms display backend as readonly instead of editable input.


1531-1544: Template readonly UI test is well-scoped and clear.

It validates the intended admin rendering contract for backend immutability.


1545-1569: Great immutability test pattern for template backend.

Checking both unchanged backend and updated name after a 302 makes this test resilient and behavior-accurate.


1682-1688: VPN change-form readonly assertion looks correct.

This properly confirms backend is displayed but not editable in the change UI.


1689-1714: VPN backend immutability test is solid.

This verifies successful form processing while ensuring backend cannot be altered via crafted POST data.

@openwisp-companion
Copy link

Commit Message and Test Failures

Hello @hypnoastic,
(Analysis for commit 242c45b)

  1. Commit Message: The commit message is invalid. It needs to follow the OpenWISP commit message conventions.

Fix: Please reformat your commit message. A correct format looks like this:

[tag] Capitalized short title #<issue>

<description>

Fixes #<issue>

For example:

[feature] Add new functionality #123

This commit introduces a new feature that allows users to do X,
improving Y and Z.

Fixes #123
  1. Test Failure: The test test_restoring_deleted_device in openwisp_controller.tests.test_selenium.TestDevice failed with an AssertionError: Deleted device was not restored. This indicates that the device restoration process in the test is not working as expected.

Fix: Please investigate the test test_restoring_deleted_device in openwisp_controller/tests/test_selenium.py and the related code in openwisp_controller/connection/base/models.py to identify why the device is not being restored correctly. The TimeoutException during WebDriverWait suggests a potential issue with the UI element not appearing or the restoration process taking too long.

@hypnoastic hypnoastic force-pushed the issues/789-disallow-changing-configuration-backend-ui branch from 242c45b to d723890 Compare March 17, 2026 11:40
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@openwisp_controller/config/tests/test_admin.py`:
- Around line 1438-1471: Add a new test that mirrors the existing change-form
tests but targets the admin "recover" view to lock the branch where readonly
enforcement is skipped (i.e., where is_recover_view(request) is True). Use the
same helpers (_create_device, _create_config, Template.objects.first,
_get_device_params) and build a path with
reverse("admin:{app_label}_device_recover", args=[device.pk]) or the actual
recover-view name used in the app; POST params should attempt to change
config-0-backend to an invalid value and assert the outcome differs from the
normal change-view (e.g., readonly enforcement is skipped or validation behaves
as expected), refresh config with config.refresh_from_db() and assert
config.backend remained or changed per the intended recover-view behavior; add
at least one test function named test_device_backend_recover_view_behavior to
cover this branch and replicate the assertions pattern used in
test_device_backend_cannot_be_changed_from_change_form.
🪄 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: 8bf82ffa-6360-4286-b5d9-81ef8970ef16

📥 Commits

Reviewing files that changed from the base of the PR and between 242c45b and d723890.

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

📄 CodeRabbit inference engine (Custom checks)

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

Files:

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

Applied to files:

  • openwisp_controller/config/tests/test_admin.py
📚 Learning: 2026-03-14T19:34:33.773Z
Learnt from: CR
Repo: openwisp/openwisp-controller PR: 0
File: coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt:0-0
Timestamp: 2026-03-14T19:34:33.773Z
Learning: Applies to **/*{.test,.spec,_test,test_}.{js,ts,tsx,jsx,py,java,go,cs,rb} : Tests must be updated to cover non-trivial changes and ensure proper validation of modified behavior

Applied to files:

  • openwisp_controller/config/tests/test_admin.py
🔇 Additional comments (10)
openwisp_controller/config/admin.py (4)

69-75: is_recover_view helper is clean and safe.

The fallback on request._recover_view plus URL-name suffix check is a solid way to keep recover flows exempt from readonly enforcement.


480-487: Config inline backend immutability logic looks correct.

The condition correctly targets existing device configs only and preserves recover behavior.


1101-1105: Template admin readonly handling is correctly scoped to change forms.

obj gating keeps backend selectable on add forms while blocking edits on change forms.


1286-1290: VPN admin readonly handling matches intended behavior.

This is consistent with template/device behavior and keeps recover view exceptions intact.

openwisp_controller/config/tests/test_admin.py (6)

1438-1445: Device change-form readonly assertion is solid.

The test verifies backend is displayed but not editable in inline form markup.


1446-1471: Device backend immutability POST test is well hardened.

Good use of successful submission assertion (302) plus persisted backend and mutable-field change checks.


1533-1546: Template readonly rendering test is correct.

This correctly checks that backend appears on change form without an editable input.


1547-1571: Template immutability test implementation is robust.

Using original_backend avoids fixture-specific constants and improves test stability.


1684-1690: VPN readonly rendering test is correct.

The assertion pattern is consistent with template/device and validates expected UI behavior.


1691-1715: VPN backend immutability POST test is correctly structured.

It validates successful save and confirms backend remains unchanged while other fields update.

@openwisp-companion
Copy link

Test Failures in CI Run

Hello @hypnoastic,
(Analysis for commit d723890)

  • Test Failure: The test test_device_backend_cannot_be_changed_from_change_form in openwisp_controller.config.tests.test_admin failed because the response code was 200 instead of the expected 302. This indicates an issue with how the device backend change is handled in the admin interface, likely related to redirection or form validation.

Fix: Review the test case and the corresponding admin code to ensure the expected redirection (status code 302) occurs after attempting to change the device backend. This might involve checking form processing logic and redirect URLs.

@hypnoastic hypnoastic force-pushed the issues/789-disallow-changing-configuration-backend-ui branch from d723890 to 991e13f Compare March 17, 2026 12:51
@hypnoastic hypnoastic changed the title [admin] Disallow changing configuration backend from UI #789 [fix] Disallow changing configuration backend from UI #789 Mar 17, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In `@openwisp_controller/config/static/config/js/widget.js`:
- Around line 416-440: Add a short explanatory comment above the
getReadonlySchemaKey function describing the fuzzy matching strategy: explain
that backendLabel is normalized via the inner normalize function (to lowercase
and stripping non-alphanumerics) and that keys from schemas are compared using
the same normalization on the last dot-segment (normalizedBackendKey), then
matched via exact equality or substring containment both ways to tolerate
variations like "OpenWRT" vs "netjsonconfig.OpenWrt"; reference the normalize
helper, normalizedBackendLabel and normalizedBackendKey in the comment so future
maintainers understand why the flexible matching is required.

In `@openwisp_controller/config/tests/test_admin.py`:
- Around line 1492-1494: The test currently grabs the inline via
django_admin.site._registry[Device].inlines[0], which is brittle; instead
iterate the inlines on django_admin.site._registry[Device] and pick the inline
class whose model is Config (or whose class name is ConfigInline) and
instantiate that for config_inline; update the test to locate the inline by
checking inline.model is Config (referencing Device, Config, and
django_admin.site._registry) so it no longer depends on index position.
🪄 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: 0e29369d-6d2a-43b3-b639-1b8ee6ce5f43

📥 Commits

Reviewing files that changed from the base of the PR and between d723890 and 991e13f.

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

📄 CodeRabbit inference engine (Custom checks)

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

Files:

  • openwisp_controller/config/static/config/js/vpn.js
  • openwisp_controller/config/static/config/js/widget.js
  • openwisp_controller/config/views.py
  • openwisp_controller/config/static/config/js/relevant_templates.js
  • openwisp_controller/config/tests/test_views.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/config/tests/test_selenium.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/static/config/js/vpn.js
  • openwisp_controller/config/static/config/js/widget.js
  • openwisp_controller/config/views.py
  • openwisp_controller/config/static/config/js/relevant_templates.js
  • openwisp_controller/config/tests/test_views.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/config/tests/test_selenium.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/static/config/js/vpn.js
  • openwisp_controller/config/static/config/js/widget.js
  • openwisp_controller/config/views.py
  • openwisp_controller/config/static/config/js/relevant_templates.js
  • openwisp_controller/config/tests/test_views.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/config/tests/test_selenium.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/views.py
  • openwisp_controller/config/tests/test_views.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/config/tests/test_selenium.py
🧠 Learnings (5)
📚 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/views.py
  • openwisp_controller/config/tests/test_views.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/config/tests/test_selenium.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/views.py
  • openwisp_controller/config/tests/test_views.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/config/tests/test_selenium.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/views.py
  • openwisp_controller/config/tests/test_views.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/config/tests/test_selenium.py
📚 Learning: 2026-03-14T19:34:33.773Z
Learnt from: CR
Repo: openwisp/openwisp-controller PR: 0
File: coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt:0-0
Timestamp: 2026-03-14T19:34:33.773Z
Learning: Applies to **/*{.test,.spec,_test,test_}.{js,ts,tsx,jsx,py,java,go,cs,rb} : Tests must be updated to cover non-trivial changes and ensure proper validation of modified behavior

Applied to files:

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

Applied to files:

  • openwisp_controller/config/tests/test_selenium.py
🔇 Additional comments (23)
openwisp_controller/config/static/config/js/vpn.js (2)

36-43: LGTM!

The getBackendValue helper correctly handles both scenarios: extracting the value from the editable #id_backend input when present, and falling back to the readonly field text when the backend field is rendered as readonly. The .field-backend .readonly selector matches the Django admin pattern used in VpnAdmin.get_readonly_fields.


71-76: LGTM!

The guard check ensures the change handler is only attached when the #id_backend element exists, preventing JavaScript errors on change forms where the backend field is readonly.

openwisp_controller/config/static/config/js/widget.js (1)

468-473: LGTM!

The fallback to getReadonlySchemaKey when no selector element exists ensures the JSON schema editor loads correctly on change forms where the backend field is readonly.

openwisp_controller/config/admin.py (6)

69-76: LGTM!

The is_recover_view function properly detects recover views via both the _recover_view attribute (set in DeviceAdmin.recover_view) and the URL name pattern. The fallback to checking resolver_match.url_name handles edge cases where the attribute might not be set.


359-366: LGTM!

The readonly_backend class attribute with __init__ logic to disable the backend field provides a clean way to make the field read-only at the form level, complementing the get_readonly_fields approach in the admin.


475-482: Dynamic form class creation is correct but worth noting.

Creating a dynamic form class with type() is a valid pattern for injecting the readonly_backend flag per-request. This ensures each formset instance gets the appropriate readonly state based on whether the device has an existing config.


494-503: LGTM!

The get_readonly_fields override correctly appends "backend" when:

  1. An object exists (obj)
  2. The object has a config (obj._has_config())
  3. It's not a recover view
  4. "backend" isn't already in readonly fields

This ensures backend is editable on add forms and recover views, but readonly on change forms.


1116-1120: LGTM!

The TemplateAdmin.get_readonly_fields follows the same pattern as the other admin classes, making backend readonly on change forms while allowing edits on add forms and recover views.


1301-1305: LGTM!

The VpnAdmin.get_readonly_fields is consistent with the pattern used in TemplateAdmin and ConfigInline.

openwisp_controller/config/views.py (1)

72-78: LGTM!

The fallback logic correctly derives the backend from the Config when the backend parameter is missing but config_id is present. This supports the readonly backend scenario where the frontend can't send the backend value. Using .only("backend") minimizes the query overhead.

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

174-198: LGTM!

The test properly validates the backend inference fallback by:

  1. Creating a config with a specific backend (OpenWrt)
  2. Creating templates with different backends
  3. Requesting templates with only config_id (no backend param)
  4. Asserting that only matching-backend templates are returned

This provides good coverage for the new logic in get_relevant_templates.

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

273-279: LGTM!

The updated test properly validates that:

  1. The backend field is not editable on the device change form (no config-0-backend input)
  2. Templates still load correctly using the stored backend value

The use of subTest blocks improves test organization and failure diagnosis.


668-674: LGTM!

These subTests effectively verify that:

  1. The backend field renders as readonly (.field-backend .readonly visible, #id_backend absent)
  2. WireGuard-related fields remain visible despite the readonly backend

This ensures the VPN change form displays correctly.


676-681: LGTM!

The new test validates that the backend field remains editable on the VPN add form and that selecting OpenVPN correctly hides the WireGuard-specific fields (webhook_endpoint, auth_token). This confirms the add form behavior is preserved.

openwisp_controller/config/static/config/js/relevant_templates.js (3)

13-17: LGTM!

The updated isAddingNewObject function correctly handles both contexts:

  • Device groups: checks for .add-form class
  • Device configs: checks if config-0-id field exists and has a value

This distinguishes between add and change views properly.


128-133: LGTM!

The logic correctly handles the readonly backend scenario:

  • For device groups, backend is empty string (no backend filtering)
  • For device configs, uses the backend value if available, otherwise empty string
  • The early return now includes configID check, allowing template loading when backend is readonly but config exists

199-215: LGTM!

The bindDefaultTemplateLoading logic correctly handles all scenarios:

  1. Editable backend field present → attach change handler
  2. Non-device-group with existing config (readonly backend) → call showRelevantTemplates directly, server infers backend from config_id
  3. Device group → initialize and show templates
  4. Add view → wait for config section to be added

This ensures templates load correctly regardless of backend editability.

openwisp_controller/config/tests/test_admin.py (6)

4-4: Good test-only imports for recover-view coverage.

These imports are focused and used by the new admin behavior tests.

Also applies to: 9-9, 17-17


1440-1479: Device backend readonly + immutability tests are solid.

Nice combination of UI-readonly assertion and POST immutability check with successful submission validation.


1560-1599: Template backend readonly and immutable-on-change tests look good.

Coverage clearly verifies both rendered readonly behavior and persistence safety on change POST.


1600-1614: Template recover-view branch coverage is well targeted.

This test locks the change-vs-recover readonly branching in admin logic.


1727-1759: VPN backend readonly + immutability checks are comprehensive.

Good parity with device/template tests and successful form submission assertion.


1760-1773: VPN recover-view behavior test is clear and valuable.

This prevents regressions in the recover exception path for backend editability.

@hypnoastic hypnoastic force-pushed the issues/789-disallow-changing-configuration-backend-ui branch from 991e13f to e802b19 Compare March 17, 2026 13:08
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

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

Inline comments:
In `@openwisp_controller/config/static/config/js/relevant_templates.js`:
- Around line 13-17: The isAddingNewObject() logic is inverted for device
groups: currently it returns true when $(".add-form") is absent; update the
branch for isDeviceGroup() so it returns true when an add form is present (e.g.,
$(".add-form").length > 0) so add forms are detected as adding and change forms
as not adding; leave the fallback that checks $('input[name="config-0-id"]')
(config-0-id) unchanged.

In `@openwisp_controller/config/tests/test_admin.py`:
- Around line 1486-1493: Extract the repeated RequestFactory setup into a helper
(e.g., a test utility function like build_request or make_recover_request) that
accepts parameters for user (use self._get_admin()), path/method (default "/"),
and resolver_match url_name (compose using self.app_label + "_device_recover" or
other recover names); replace the inline RequestFactory + resolver_match
assignments in the recover-view tests for device/template/vpn with calls to this
helper to eliminate duplication and avoid unused/redundant code.

In `@openwisp_controller/config/tests/test_views.py`:
- Around line 192-197: The test calls response.json() without asserting the HTTP
status first, which can obscure failures; update the test in
openwisp_controller/config/tests/test_views.py (the test that does
reverse("admin:get_relevant_templates", args=[org1.pk]) and assigns response) to
assert the expected status (e.g., self.assertEqual(response.status_code, 200))
immediately after obtaining response and before calling response.json(), then
proceed to parse JSON and assert that str(t1.pk) is in templates.

In `@openwisp_controller/config/views.py`:
- Around line 74-77: The lookup that sets backend from
Config.objects.only("backend").get(pk=config_id) is not tenant-scoped; change
the query to include the same tenant/organization filter used elsewhere in this
view (e.g. add the organization/tenant field or use the view's request-scoped
queryset such as self.request.organization or request.user.organization or an
existing Config queryset helper) so the lookup becomes
Config.objects.only("backend").get(pk=config_id, <tenant_field>=current_tenant)
(or use the view's get_queryset()/for_tenant helper) and keep the existing
exception handling for Config.DoesNotExist/ValueError to avoid leaking other
tenants' backends.
🪄 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: 1fd05826-da54-4c24-990d-209c1a7b64a5

📥 Commits

Reviewing files that changed from the base of the PR and between 991e13f and e802b19.

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

📄 CodeRabbit inference engine (Custom checks)

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

Files:

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

Applied to files:

  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/tests/test_admin.py
📚 Learning: 2026-03-14T19:34:33.773Z
Learnt from: CR
Repo: openwisp/openwisp-controller PR: 0
File: coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt:0-0
Timestamp: 2026-03-14T19:34:33.773Z
Learning: Applies to **/*{.test,.spec,_test,test_}.{js,ts,tsx,jsx,py,java,go,cs,rb} : Tests must be updated to cover non-trivial changes and ensure proper validation of modified behavior

Applied to files:

  • openwisp_controller/config/tests/test_admin.py
🔇 Additional comments (8)
openwisp_controller/config/static/config/js/widget.js (1)

416-443: Readonly schema fallback is well-handled.

This correctly covers the no-selector path and keeps schema loading functional on readonly backend change forms.

Also applies to: 471-476

openwisp_controller/config/static/config/js/vpn.js (1)

36-43: Backend fallback and guarded binding are solid.

Good hardening for readonly backend pages without regressing editable add-form behavior.

Also applies to: 71-76

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

273-280: Coverage updates align well with readonly-backend behavior.

The new assertions validate both non-editability on change forms and backend-driven field toggling on add forms.

Also applies to: 668-682

openwisp_controller/config/admin.py (1)

69-75: Readonly backend enforcement is correctly implemented across admin surfaces.

This cleanly preserves add/recover behavior while preventing backend edits on standard change forms.

Also applies to: 361-367, 475-505, 1118-1122, 1303-1307

openwisp_controller/config/tests/test_admin.py (4)

4-17: Import additions are clean and purposeful.

The new imports are all exercised by the added admin readonly/recover tests; no dead import introduced.


1440-1506: Device backend test coverage is strong.

This block correctly validates readonly UI state, POST immutability, and recover-view behavior split for admin logic.


1568-1624: Template backend tests are well-targeted.

Good validation of readonly behavior on change forms and backend immutability while preserving recover-view editability.


1737-1783: VPN backend protection tests look complete.

These assertions correctly verify change-form immutability and preserve the recover-view exception path.

@coveralls
Copy link

coveralls commented Mar 17, 2026

Coverage Status

coverage: 98.664% (+0.006%) from 98.658%
when pulling 4651ba3 on hypnoastic:issues/789-disallow-changing-configuration-backend-ui
into 45b24b6 on openwisp:master.

@hypnoastic hypnoastic force-pushed the issues/789-disallow-changing-configuration-backend-ui branch from e802b19 to 076d8cd Compare March 17, 2026 13:29
@openwisp-companion
Copy link

CI Failure: Test Failures and WHOIS Lookup Issues

Hello @hypnoastic,
(Analysis for commit 076d8cd)

There are multiple failures in the CI run:

  1. Test Failure: test_multiple_organization_templates
  • Issue: The test test_multiple_organization_templates in openwisp_controller.config.tests.test_selenium failed due to a TimeoutException and NoSuchElementError. This indicates that an element expected to be visible on the page was not found within the timeout period. The traceback points to an AssertionError related to visibility_of_element_located.
  • Fix: This is likely due to a timing issue or a change in the UI that the test is not accounting for. You may need to adjust the wait conditions or selectors in the test to match the current UI behavior.
  1. WHOIS Lookup Failures
  • Issue: Multiple WHOIS lookup failures were reported, indicating problems with the GeoIP2 service. The errors suggest issues with account credentials, query limits, or permissions.
  • Fix: This appears to be an infrastructure or configuration issue with the WHOIS/GeoIP service. Please check the OPENWISP_CONTROLLER_WHOIS_GEOIP_ACCOUNT and OPENWISP_CONTROLLER_WHOIS_GEOIP_KEY settings in your environment. If these are correctly configured, it might be a temporary issue with the GeoIP service itself.
  1. Runtime Warnings: Naive Datetime Usage
  • Issue: Several RuntimeWarning: DateTimeField ... received a naive datetime ... while time zone support is active warnings were observed. This indicates that timezone-aware datetime objects are expected, but naive datetime objects are being used.
  • Fix: Ensure that all datetime objects being used with DateTimeField are timezone-aware. You might need to use timezone.now() or make_aware() from django.utils.timezone where appropriate.
  1. Test Failure: sqlite3.IntegrityError: FOREIGN KEY constraint failed
  • Issue: A FOREIGN KEY constraint failed occurred during the execution of a command (likely related to device connection or task execution) within the test_multiple_organization_templates failure context. This indicates a data integrity issue, where a foreign key constraint was violated during a database operation.
  • Fix: This is often caused by trying to save an object with a foreign key pointing to a non-existent or invalid related object. Review the test logic that leads to this error, specifically around the creation and saving of DeviceConnection or related objects, to ensure referential integrity is maintained.
  1. Test Failure: floorplan image not found
  • Issue: Several tests reported "floorplan image not found" errors when trying to show or delete floorplans. This indicates that the image files referenced by the floorplan objects are missing or inaccessible in the test environment.
  • Fix: Ensure that floorplan image files are correctly set up or mocked in the test environment. If these images are expected to be present, verify their paths and availability.

Please address these issues to resolve the CI failures.

@hypnoastic hypnoastic force-pushed the issues/789-disallow-changing-configuration-backend-ui branch from 076d8cd to 6bebb4e Compare March 17, 2026 14:19
@atif09
Copy link
Contributor

atif09 commented Mar 18, 2026

Left a comment, please take a look. Other than that address the comments left by kilo bot and code rabbit and the PR should be good for the most part

@hypnoastic hypnoastic force-pushed the issues/789-disallow-changing-configuration-backend-ui branch from 156fd4f to 6fece20 Compare March 19, 2026 04:13
@hypnoastic
Copy link
Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 19, 2026

✅ Actions performed

Review triggered.

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.

@hypnoastic
Copy link
Author

Hi, I addressed the bot comments and all review conversations are now resolved, but I still can’t see the manual review comment you mentioned on my side. GitHub is only showing resolved conversations for me. Could you please mention it again here or point me to the file/line?

Also, the PR is still showing a stale changes requested status even though all visible comments have been addressed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

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

Inline comments:
In `@openwisp_controller/config/static/config/js/vpn.js`:
- Around line 36-43: getBackendValue currently falls back to the visible label
text (".field-backend .readonly") which is not a stable identifier for
toggleRelatedFields; change the readonly branch to read a stable backend
identifier from a data attribute on the readonly element (e.g. use
$(".field-backend .readonly").first().data("backend") or a similar data-*
attribute) and normalize that value (toLocaleLowerCase) so toggleRelatedFields
receives actual backend keys like "wireguard", "openvpn", "vxlan" instead of the
display label; ensure the code still checks backendInput.val() first and only
uses the data attribute when backendInput is absent.

In `@openwisp_controller/config/static/config/js/widget.js`:
- Around line 416-443: The readonly schema inference in getReadonlySchemaKey
relies on fuzzy-matching the human label from .field-backend .readonly which
fails for custom labels; change the DOM to expose the actual backend value
(e.g., add a data-backend or value attribute to the .readonly element where
BACKENDS/VPN_BACKENDS are rendered) and update getReadonlySchemaKey to read that
attribute (use jQuery .data('backend') or .attr('data-backend')) and directly
return schemas[backendValue] (or the matching key) instead of normalizing
labels; keep the existing normalize/fuzzy logic only as a fallback when the
data-backend attribute is absent and apply the same change to the other
identical block referenced around the second occurrence (the similar
readonly-to-schema lookup later in the file).

In `@openwisp_controller/config/tests/test_selenium.py`:
- Around line 676-682: The test test_vpn_add_backend_switches_related_fields is
selecting "OpenVPN" when the form already defaults to OpenVPN, so it doesn't
exercise the change handler; update the test to first select a different backend
option (e.g., "WireGuard" or any option not equal to "OpenVPN") using the Select
on id_backend and wait for the related fields (field-webhook_endpoint,
field-auth_token) to become visible, then select "OpenVPN" again and assert
those fields become invisible using the existing wait_for_invisibility calls;
locate and modify the test_vpn_add_backend_switches_related_fields function to
implement the two-step switch (other backend -> OpenVPN) and the corresponding
visibility checks.

In `@openwisp_controller/config/views.py`:
- Around line 74-82: The exception handlers in
openwisp_controller/config/views.py currently catch only Config.DoesNotExist and
ValueError when reading Config.objects.get(pk=config_id); add
django.core.exceptions.ValidationError to the imports and include
ValidationError in both except tuples (the one handling the get at the block
around backend = config_queryset.get(pk=config_id).backend and the similar
handler later at the other get) so malformed UUIDs are caught and handled
gracefully.
🪄 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: bc31b709-b4a5-4c66-9d58-954e2f884d86

📥 Commits

Reviewing files that changed from the base of the PR and between 991e13f and 6fece20.

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

📄 CodeRabbit inference engine (Custom checks)

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

Files:

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

Applied to files:

  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/tests/test_admin.py
📚 Learning: 2026-03-14T19:34:33.773Z
Learnt from: CR
Repo: openwisp/openwisp-controller PR: 0
File: coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt:0-0
Timestamp: 2026-03-14T19:34:33.773Z
Learning: Applies to **/*{.test,.spec,_test,test_}.{js,ts,tsx,jsx,py,java,go,cs,rb} : Tests must be updated to cover non-trivial changes and ensure proper validation of modified behavior

Applied to files:

  • openwisp_controller/config/tests/test_admin.py
🔇 Additional comments (10)
openwisp_controller/config/static/config/js/vpn.js (1)

71-76: Nice guard for readonly change forms.

Skipping the change binding when #id_backend is absent avoids pointless selector work and JS edge cases on the new readonly pages.

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

174-261: Good coverage for config-backed template inference.

These cases pin down same-org, foreign-org, and org-change behavior around the config_id fallback, which should make future refactors of get_relevant_templates() much safer.

openwisp_controller/config/tests/test_admin.py (2)

1440-1478: Strong change-form immutability coverage.

The GET + POST checks validate both halves of the contract: the backend input disappears on change forms, and backend mutations are ignored while ordinary fields still save.

Also applies to: 1571-1767


1480-1509: Recover-view branch is now well pinned down.

The helper-based assertions make the intended exception explicit for device, template, and vpn admins, which is exactly the kind of branch that tends to regress later.

Also applies to: 1611-1623, 1769-1777

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

273-279: Readonly Selenium checks look good.

Checking both the missing backend input and the still-visible backend-dependent UI gives good end-to-end coverage for the new change-form behavior.

Also applies to: 668-674

openwisp_controller/config/static/config/js/relevant_templates.js (1)

13-17: The readonly-backend fallback here looks solid.

Using config-0-id to distinguish add vs change and letting change pages fetch templates without an editable backend keeps the sortedm2m flow aligned with the new admin UI.

Also applies to: 128-133, 199-215

openwisp_controller/config/admin.py (4)

69-75: Recover-view detection is nicely centralized.

This keeps the readonly exception aligned for both real admin requests and the resolver_match.url_name-based test setup.


361-366: Good defense-in-depth on the form layer.

Disabling backend here complements the admin readonly rendering and keeps POSTed values from changing the field if this form is reused outside the standard inline path.


475-484: The inline formset gate matches the add-vs-change requirement.

Using obj._has_config() here keeps backend editable when a device is getting its first config, while making existing configs immutable outside recover flows.


497-504: Readonly gating is consistent across Device, Template, and VPN admins.

The obj check preserves editable add forms, and the shared recover exception avoids blocking legitimate rollback/recovery flows.

Also applies to: 1118-1122, 1303-1307

@hypnoastic hypnoastic force-pushed the issues/789-disallow-changing-configuration-backend-ui branch 2 times, most recently from 374058e to d868824 Compare March 19, 2026 05:06
Make the backend read-only on Django admin change forms for
device configuration, template, and VPN server, while keeping
it selectable on create forms.

Also add recover-view regression coverage and ensure device
inline backend changes are ignored on change-form submissions.

Fixes openwisp#789
@hypnoastic hypnoastic force-pushed the issues/789-disallow-changing-configuration-backend-ui branch from 94578f9 to 4651ba3 Compare March 19, 2026 05:16
@atif09
Copy link
Contributor

atif09 commented Mar 20, 2026

also lets avoid force pushing, unless the maintainers ask to do so 👍

@hypnoastic
Copy link
Author

Thanks, I will keep that in mind

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[change] Disallow changing configuration backend from UI

3 participants