[feature] Introduced standalone certificate templates and device bindings#1378
[feature] Introduced standalone certificate templates and device bindings#1378stktyagi wants to merge 55 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR adds standalone certificate templates with Sequence Diagram(s)sequenceDiagram
participant AdminAPI
participant TemplateSerializer
participant Config
participant manage_device_certs
participant DeviceCertificate
participant regenerate_device_certificates_task
AdminAPI->>TemplateSerializer: create/update cert template
TemplateSerializer->>TemplateSerializer: validate ca and blueprint_cert
AdminAPI->>Config: patch templates on device config
Config->>manage_device_certs: m2m_changed
manage_device_certs->>DeviceCertificate: create or delete rows
Config->>regenerate_device_certificates_task: hardware drift detected
Estimated code review effort🎯 5 (Critical) | ⏱️ ~90 minutes Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
Caution Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional.
❌ Failed checks (1 error)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Incremental Review (since 52c7544)The new commit (
No production request handlers, serializers, migrations, tenant-filtering, or security-relevant code paths are modified in this delta. No new critical bugs or security vulnerabilities. Files Reviewed (incremental, 2 files)
Previous Review Summaries (7 snapshots, latest commit 52c7544)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 52c7544)Status: No Issues Found | Recommendation: Merge Incremental Review (since b486eb5)The new commit (
No production request handlers, serializers, migrations, or tenant-filtering code paths are changed in this delta. No new critical bugs or security vulnerabilities were introduced. Files Reviewed (incremental, 3 files)
Previous review (commit b486eb5)Status: No Issues Found | Recommendation: Merge Incremental Review (since b52fe63)The new commits are low-risk follow-ups:
All changes are confined to the test Files Reviewed (incremental)
Previous review (commit b52fe63)Status: No Issues Found | Recommendation: Merge Incremental Review (since fb54051)The new commit A corresponding regression test, No new critical bugs or security vulnerabilities were introduced. Files Reviewed (incremental)
Previous review (commit fb54051)Status: No Issues Found | Recommendation: Merge Incremental Review (since 8c32282)The new commit
No new critical bugs or security vulnerabilities were introduced. Files Reviewed (incremental)
Carried-forward from prior reviewAll previously reviewed production files and their findings carry forward unchanged. Prior outstanding notes (cert-template validation regression test and Previous review (commit 8c32282)Status: No Issues Found | Recommendation: Merge Incremental Review (since a819721)The new commit
No new critical bugs or security vulnerabilities were introduced by this commit. Files Reviewed (incremental)
Carried-forward from prior reviewAll previously reviewed files and their findings carry forward unchanged. The prior outstanding note (missing regression test for cert-template validation in Previous review (commit a819721)Status: No Issues Found | Recommendation: Merge Incremental Review (since 298572f)The new commits add a large body of work: certificate regeneration on hardware drift (#1359), deactivated-device enforcement across config/connection/WHOIS/geo, pagination consolidation to The previously flagged WARNING (missing regression test for cert-template validation in Review highlights:
Extensive new tests accompany each change and Files Reviewed (incremental)
Previous review (commit 298572f)Status: 1 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Other Observations (not in diff)Previous findings on No additional issues in unchanged code. Files Reviewed (5 files)
Reviewed by glm-5.2-20260616 · Input: 170.8K · Output: 2.2K · Cached: 64K |
|
Caution Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted. Error details |
There was a problem hiding this comment.
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/template.py`:
- Around line 265-267: The help text for the auto_cert field is out of date (it
still says it's only valid for VPN templates) — update the auto_cert field's
help/verbose/help_text in the Template definition in
openwisp_controller/config/base/template.py so it matches the new behavior
(auto_cert is allowed when type == "cert" as well as when type == "vpn"); locate
the auto_cert attribute (and any admin/API serializer or form label/help_text
referencing it) and change the message to something like "Valid for 'vpn' and
'cert' template types" or equivalent clear wording that includes both types.
🪄 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: 33bb61f8-c083-446e-8e45-44d753e7ff7b
📒 Files selected for processing (3)
openwisp_controller/config/base/template.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}
📄 CodeRabbit inference engine (Custom checks)
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}: Flag potential security vulnerabilities in code
Avoid unnecessary comments or docstrings for code that is already clear
Code formatting is compact and readable. Do not add excessive blank lines, especially inside function or method bodies
Flag unused or redundant code
Ensure variables, functions, classes, and files have descriptive and consistent names
New code must handle errors properly: log errors that cannot be resolved by the user with error level, log unusual conditions with warning level, log important background actions with info level, and provide user-facing messages for errors that the user can solve autonomously
Files:
tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.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:
tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.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:
tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.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:
tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
🧠 Learnings (4)
📚 Learning: 2026-01-12T22:27:40.078Z
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:40.078Z
Learning: In test migrations under tests/openwisp2/sample_config/migrations, verify scenarios where a swappable model (CONFIG_WHOISINFO_MODEL) is extended with extra fields (e.g., an additional 'details' field) to ensure compatibility and no errors when swapping to a custom implementation. This pattern helps confirm that extending AbstractWHOISInfo via a custom model works as intended.
Applied to files:
tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.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:
tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
📚 Learning: 2026-01-15T15:05:49.557Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/management/commands/clear_last_ip.py:38-42
Timestamp: 2026-01-15T15:05:49.557Z
Learning: In Django projects, when using select_related() to traverse relations (for example, select_related("organization__config_settings")), the traversed relation must not be deferred. If you also use .only() in the same query, include the relation name or FK field (e.g., "organization" or "organization_id") in the .only() list to avoid the error "Field X cannot be both deferred and traversed using select_related at the same time." Apply this guideline to Django code in openwisp_controller/config/management/commands/clear_last_ip.py and similar modules by ensuring any select_related with an accompanying only() includes the related field names to prevent deferred/traversed conflicts.
Applied to files:
openwisp_controller/config/base/template.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.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/template.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
🔇 Additional comments (3)
openwisp_controller/config/base/template.py (1)
25-29: LGTM!Also applies to: 62-83, 251-253, 271-312
openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py (1)
1-59: LGTM!tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py (1)
1-58: LGTM!
{
"failures": [
{
"type": "Build/Infrastructure/Other",
"message": "Migrations check failed! Models' changes not migrated, please run './manage.py makemigrations' to solve the issue!",
"details": "The CI environment detected that the database migrations are out of sync. This is indicated by the error message 'Migrations check failed! Models' changes not migrated, please run './manage.py makemigrations' to solve the issue!' and the mention of a specific migration file '0005_user_expiration_date_user_user_active_expiry_idx.py'."
}
]
}CI Failure AnalysisHello @stktyagi,
Fix: Run |
Updated previous tests and added new tests for implemetation. Fixes #1356
Fixed help text for auto cert and updated migration files
Migrations Check FailedHello @stktyagi, The CI failed because there are unapplied database migrations. This means that the database schema is out of sync with the current state of the code. Fix: ./manage.py makemigrations |
|
The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3). |
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/template.py`:
- Around line 252-253: The validation of certificate relations (calls to
self._validate_org_relation("ca") and
self._validate_org_relation("blueprint_cert")) is running unconditionally and
can fail on stale relations when the object is being switched away from the
"cert" type; restrict these validations to the cert branch so they only run when
the template's type is "cert" (e.g., wrap or move the
self._validate_org_relation(...) calls inside the same conditional that handles
the "cert" branch or after the type check that preserves/clears ca and
blueprint_cert), ensuring they do not run when the code path clears those fields
(see the branch that clears ca and blueprint_cert).
- Around line 304-305: The current check "if not self.config" coerces any falsy
value ([], "", False) into {}, bypassing BaseConfig.clean() validation; change
the condition to only handle missing configs by checking "if self.config is
None" (or equivalent explicit None check) so only absent configs are replaced
with {} and invalid/falsy payloads are left intact for
full_clean()/BaseConfig.clean() to reject.
🪄 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: d3d93328-e58d-41dd-a374-dffebd6d1e38
📒 Files selected for processing (5)
openwisp_controller/config/base/template.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/tests/test_template.pyopenwisp_controller/pki/tests/test_api.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.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.12 | django~=5.1.0
- GitHub Check: Python==3.11 | django~=4.2.0
- GitHub Check: Python==3.10 | django~=5.1.0
- GitHub Check: Python==3.13 | django~=5.1.0
- GitHub Check: Python==3.10 | django~=4.2.0
- GitHub Check: Python==3.12 | django~=5.2.0
- GitHub Check: Python==3.10 | django~=5.2.0
- GitHub Check: Python==3.13 | django~=5.2.0
- GitHub Check: Python==3.11 | django~=5.2.0
- GitHub Check: Python==3.11 | django~=5.1.0
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}
📄 CodeRabbit inference engine (Custom checks)
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}: Flag potential security vulnerabilities in code
Avoid unnecessary comments or docstrings for code that is already clear
Code formatting is compact and readable. Do not add excessive blank lines, especially inside function or method bodies
Flag unused or redundant code
Ensure variables, functions, classes, and files have descriptive and consistent names
New code must handle errors properly: log errors that cannot be resolved by the user with error level, log unusual conditions with warning level, log important background actions with info level, and provide user-facing messages for errors that the user can solve autonomously
Files:
openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tests/test_template.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/migrations/0064_template_blueprint_cert_template_ca_and_more.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tests/test_template.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/migrations/0064_template_blueprint_cert_template_ca_and_more.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tests/test_template.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/migrations/0064_template_blueprint_cert_template_ca_and_more.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tests/test_template.py
🧠 Learnings (4)
📚 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/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tests/test_template.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/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tests/test_template.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/migrations/0064_template_blueprint_cert_template_ca_and_more.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tests/test_template.py
📚 Learning: 2026-01-12T22:27:40.078Z
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:40.078Z
Learning: In test migrations under tests/openwisp2/sample_config/migrations, verify scenarios where a swappable model (CONFIG_WHOISINFO_MODEL) is extended with extra fields (e.g., an additional 'details' field) to ensure compatibility and no errors when swapping to a custom implementation. This pattern helps confirm that extending AbstractWHOISInfo via a custom model works as intended.
Applied to files:
tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
🔇 Additional comments (4)
openwisp_controller/pki/tests/test_api.py (1)
155-155: LGTM!Also applies to: 275-275
openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py (1)
12-16: LGTM!Also applies to: 19-44, 45-58, 59-74
tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py (1)
11-14: LGTM!Also applies to: 17-42, 43-56, 57-72
openwisp_controller/config/base/template.py (1)
25-29: LGTM!Also applies to: 62-83, 119-120
Migrations Check FailedHello @stktyagi, The CI failed because there are unapplied database migrations. Failure: Migrations check failed! Models' changes not migrated, please run './manage.py makemigrations' to solve the issue! Fix: ./manage.py makemigrations |
…s/1356-extend-abstract-template
Validate cert relations only inside the cert branch and Only coerce missing cert configs, not every falsy value. Fixes #1356
Added test for the validation branch that now skips ca / blueprint_cert checks for non-cert templates Fixes #1356
Fixed line too long flake error Fixes #1356
Updated test by joining the list of strings into one sentence. Fixes #1377
Fixed image path for certficate generator templates
Added missing certificate details in the device admin Fixes #1357
Fixed failing tests due to admin update
|
The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3). |
Test Failure in
|
Fixed failing tests due to admin update
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
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 `@docs/user/certificate-templates.rst`:
- Around line 34-36: The screenshot reference in the certificate templates docs
should follow the project’s versioned media convention. Update the image URL in
the certificate template documentation to point to the versioned directory used
for screenshots, keeping the same asset name but moving it under the appropriate
versioned feature folder. Use the existing documentation image references in the
same docs area as the pattern to match.
In `@openwisp_controller/config/api/serializers.py`:
- Around line 106-135: The normalization in the serializer’s template handling
is causing the mutation-protection lookup to run for non-cert templates even on
unrelated updates. In `ConfigSerializer`’s validation flow, preserve whether
`ca`, `blueprint_cert`, or `type` were originally present in the incoming data
before the `elif template_type != "cert"` branch clears them, and use those
original flags to gate the
`Config.objects.filter(templates=self.instance)...exists()` check so the
optimized path is not re-triggered for generic/VPN template edits.
In `@openwisp_controller/config/base/device_certificate.py`:
- Around line 107-123: In _clone_blueprint_attrs, the digest assignment is
asymmetric and unclear because the CA fallback is cast to string while the
blueprint path is not. Either normalize digest handling for both ca and
blueprint sources, or add a brief inline comment explaining why source.digest is
converted only in the fallback case and why that behavior is intentional. Keep
the logic centered on _clone_blueprint_attrs and the digest variable.
In `@openwisp_controller/config/handlers.py`:
- Around line 58-67: Add an early raw-save guard to both hardware-drift
receivers so they do nothing during fixture loading: in
capture_old_hardware_properties and the matching post_save receiver, return
immediately when raw is true before any DB lookup, caching, or
certificate-regeneration logic runs. Use the existing receiver functions and
their kwargs handling to locate the change, and keep the guard alongside the
current pk/update_fields checks.
In `@openwisp_controller/config/tests/test_device.py`:
- Around line 989-1002: The test in
test_device_certificate_common_name_format_fallback can pass with an ambient
COMMON_NAME_FORMAT that only uses mac_address, so make the fallback branch
explicit by patching the setting inside this test. Use the existing test setup
around DeviceCertificate.common_name and DeviceCertificate.objects.get to force
a format that would otherwise not start with the device MAC alone, then assert
the fallback behavior directly so the test validates the {mac_address}-{name}
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: 23011ea0-0a4d-48e0-9fa7-6c1abfe638a1
📒 Files selected for processing (31)
docs/developer/extending.rstdocs/index.rstdocs/user/certificate-templates.rstdocs/user/intro.rstdocs/user/rest-api.rstdocs/user/settings.rstdocs/user/templates.rstopenwisp_controller/config/admin.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/apps.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/models.pyopenwisp_controller/config/settings.pyopenwisp_controller/config/static/config/js/switcher.jsopenwisp_controller/config/tasks.pyopenwisp_controller/config/templates/admin/config/device_certificates_table.htmlopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/tests/test_config.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/tests/test_template.pyopenwisp_controller/pki/tests/test_api.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pytests/openwisp2/sample_config/models.pytests/openwisp2/sample_config/tests.pytests/openwisp2/settings.py
📜 Review details
⚠️ CI failures not shown inline (11)
GitHub Actions: OpenWISP Controller CI Build / 3_Python==3.12 django~=4.2.0.txt: [feature] Introduced standalone certificate templates and device bindings
Conclusion: failure
5 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
Collecting propcache>=0.2.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (16 kB)
Requirement already satisfied: typing_extensions>=4.4 in /opt/hostedtoolcache/Python/3.12.13/x64/lib/python3.12/site-packages (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0) (4.15.0)
Collecting yarl<2.0,>=1.17.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached yarl-1.24.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (94 kB)
Collecting geographiclib<3,>=1.52 (from geopy~=2.4.1->django-loci @ https://github.com/openwisp/django-loci/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
Using cached geographiclib-2.1-py3-none-any.whl.metadata (1.6 kB)
Requirement already satisfied: MarkupSafe>=2.0 in /opt/hostedtoolcache/Python/3.12.13/x64/lib/python3.12/site-packages (from jinja2~=3.1.6->netjsonconfig @ https://github.com/openwisp/netjsonconfig/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0) (3.0.3)
Collecting jsonschema-specifications>=2023.03.6 (from jsonschema~=4.26.0->netjsonconfig @ https://github.com/openwisp/netjsonconfig/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl.metadata (2.9 kB)
Collecting referencing>=0.28.4 (from jsonschema~=4.26.0->netjsonconfig @ https://github.com/openwisp/netjsonconfig/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
Using cached referencing-0.37.0-py3-none-any.whl.metadata (2.8 kB)
Collecting rpds-py>=0.25.0 (from jsonschema~=4.26.0->netjsonconfig @ h...
GitHub Actions: OpenWISP Controller CI Build / 8_Python==3.10 django~=4.2.0.txt: [feature] Introduced standalone certificate templates and device bindings
Conclusion: failure
nwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
Using cached pytz-2026.2-py2.py3-none-any.whl.metadata (22 kB)
Requirement already satisfied: pyyaml>=5.1 in /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages (from drf-yasg<1.22.0,>=1.21.14->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0) (6.0.3)
Collecting uritemplate>=3.0.0 (from drf-yasg<1.22.0,>=1.21.14->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
Using cached uritemplate-4.2.0-py3-none-any.whl.metadata (2.6 kB)
Collecting aiohttp<4.0.0,>=3.6.2 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Downloading aiohttp-3.14.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (8.3 kB)
Collecting maxminddb<4.0.0,>=3.0.0 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached maxminddb-3.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
Collecting aiohappyeyeballs>=2.5.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached aiohappyeyeballs-2.6.2-py3-none-any.whl.metadata (5.9 kB)
Collecting aiosignal>=1.4.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached aiosignal-1.4.0-py3-none-any.whl.metadata (3.7 kB)
Requirement already satisfied: async-timeout<6.0,>=4.0 in /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0) (5.0.1)
Requirement already satisfied: attrs>=17.3.0 in /opt/hostedtoolcache/Python/3.10.20/x...
GitHub Actions: OpenWISP Controller CI Build / 2_Python==3.10 django~=5.2.0.txt: [feature] Introduced standalone certificate templates and device bindings
Conclusion: failure
penwisp-controller==1.3a0)
Using cached diff_match_patch-20241021-py3-none-any.whl.metadata (5.5 kB)
Collecting tablib>=3.7.0 (from django-import-export~=4.4.1->openwisp-controller==1.3a0)
Using cached tablib-3.9.0-py3-none-any.whl.metadata (3.8 kB)
Requirement already satisfied: rjsmin<2.0.0,>=1.2.0 in /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages (from django-minify-compress-staticfiles~=1.1.1->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0) (1.2.5)
Requirement already satisfied: rcssmin<2.0.0,>=1.1.0 in /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages (from django-minify-compress-staticfiles~=1.1.1->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0) (1.2.2)
Requirement already satisfied: brotli<2.0.0,>=1.2.0 in /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages (from django-minify-compress-staticfiles~=1.1.1->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0) (1.2.0)
Collecting inflection>=0.3.1 (from drf-yasg<1.22.0,>=1.21.14->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
Using cached inflection-0.5.1-py2.py3-none-any.whl.metadata (1.7 kB)
Collecting pytz>=2021.1 (from drf-yasg<1.22.0,>=1.21.14->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->o...
GitHub Actions: OpenWISP Controller CI Build / 6_Python==3.12 django~=5.2.0.txt: [feature] Introduced standalone certificate templates and device bindings
Conclusion: failure
ostedtoolcache/Python/3.12.13/x64/lib/python3.12/site-packages (from drf-yasg<1.22.0,>=1.21.14->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0) (6.0.3)
Collecting uritemplate>=3.0.0 (from drf-yasg<1.22.0,>=1.21.14->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
Using cached uritemplate-4.2.0-py3-none-any.whl.metadata (2.6 kB)
Collecting aiohttp<4.0.0,>=3.6.2 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Downloading aiohttp-3.14.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (8.3 kB)
Collecting maxminddb<4.0.0,>=3.0.0 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached maxminddb-3.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
Collecting aiohappyeyeballs>=2.5.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached aiohappyeyeballs-2.6.2-py3-none-any.whl.metadata (5.9 kB)
Collecting aiosignal>=1.4.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached aiosignal-1.4.0-py3-none-any.whl.metadata (3.7 kB)
Requirement already satisfied: attrs>=17.3.0 in /opt/hostedtoolcache/Python/3.12.13/x64/lib/python3.12/site-packages (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0) (26.1.0)
Collecting frozenlist>=1.1.1 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (20 kB)
Collecting multidict<7.0,>=4.5 (from aiohttp<4.0.0,>=3.6....
GitHub Actions: OpenWISP Controller CI Build / 7_Python==3.12 django~=5.1.0.txt: [feature] Introduced standalone certificate templates and device bindings
Conclusion: failure
->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
Using cached uritemplate-4.2.0-py3-none-any.whl.metadata (2.6 kB)
Collecting aiohttp<4.0.0,>=3.6.2 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Downloading aiohttp-3.14.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (8.3 kB)
Collecting maxminddb<4.0.0,>=3.0.0 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached maxminddb-3.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
Collecting aiohappyeyeballs>=2.5.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached aiohappyeyeballs-2.6.2-py3-none-any.whl.metadata (5.9 kB)
Collecting aiosignal>=1.4.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached aiosignal-1.4.0-py3-none-any.whl.metadata (3.7 kB)
Requirement already satisfied: attrs>=17.3.0 in /opt/hostedtoolcache/Python/3.12.13/x64/lib/python3.12/site-packages (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0) (26.1.0)
Collecting frozenlist>=1.1.1 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (20 kB)
Collecting multidict<7.0,>=4.5 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
Collecting propcache>=0.2.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28...
GitHub Actions: OpenWISP Controller CI Build / 1_Python==3.11 django~=4.2.0.txt: [feature] Introduced standalone certificate templates and device bindings
Conclusion: failure
linux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
Collecting propcache>=0.2.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (16 kB)
Requirement already satisfied: typing_extensions>=4.4 in /opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0) (4.15.0)
Collecting yarl<2.0,>=1.17.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached yarl-1.24.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (94 kB)
Collecting geographiclib<3,>=1.52 (from geopy~=2.4.1->django-loci @ https://github.com/openwisp/django-loci/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
Using cached geographiclib-2.1-py3-none-any.whl.metadata (1.6 kB)
Requirement already satisfied: MarkupSafe>=2.0 in /opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages (from jinja2~=3.1.6->netjsonconfig @ https://github.com/openwisp/netjsonconfig/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0) (3.0.3)
Collecting jsonschema-specifications>=2023.03.6 (from jsonschema~=4.26.0->netjsonconfig @ https://github.com/openwisp/netjsonconfig/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl.metadata (2.9 kB)
Collecting referencing>=0.28.4 (from jsonschema~=4.26.0->netjsonconfig @ https://github.com/openwisp/netjsonconfig/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
Using cached referencing-0.37.0-py3-none-any.whl.metadata (2.8 kB)
Collecting rpds-py>=0.25.0 (from jsonschema~=4.26.0->netjsonconfig @ https://github.com/openwisp/netjsonconfig/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
Using cached rpds_py-2026.5.1-cp311-cp311-manyl...
GitHub Actions: OpenWISP Controller CI Build / 9_Python==3.13 django~=5.2.0.txt: [feature] Introduced standalone certificate templates and device bindings
Conclusion: failure
tz-2026.2-py2.py3-none-any.whl.metadata (22 kB)
Requirement already satisfied: pyyaml>=5.1 in /opt/hostedtoolcache/Python/3.13.14/x64/lib/python3.13/site-packages (from drf-yasg<1.22.0,>=1.21.14->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0) (6.0.3)
Collecting uritemplate>=3.0.0 (from drf-yasg<1.22.0,>=1.21.14->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
Using cached uritemplate-4.2.0-py3-none-any.whl.metadata (2.6 kB)
Collecting aiohttp<4.0.0,>=3.6.2 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached aiohttp-3.14.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (8.3 kB)
Collecting maxminddb<4.0.0,>=3.0.0 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached maxminddb-3.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
Collecting aiohappyeyeballs>=2.5.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached aiohappyeyeballs-2.6.2-py3-none-any.whl.metadata (5.9 kB)
Collecting aiosignal>=1.4.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached aiosignal-1.4.0-py3-none-any.whl.metadata (3.7 kB)
Requirement already satisfied: attrs>=17.3.0 in /opt/hostedtoolcache/Python/3.13.14/x64/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0) (26.1.0)
Collecting frozenlist>=1.1.1 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_6...
GitHub Actions: OpenWISP Controller CI Build / 4_Python==3.11 django~=5.2.0.txt: [feature] Introduced standalone certificate templates and device bindings
Conclusion: failure
already satisfied: pyyaml>=5.1 in /opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages (from drf-yasg<1.22.0,>=1.21.14->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0) (6.0.3)
Collecting uritemplate>=3.0.0 (from drf-yasg<1.22.0,>=1.21.14->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
Using cached uritemplate-4.2.0-py3-none-any.whl.metadata (2.6 kB)
Collecting aiohttp<4.0.0,>=3.6.2 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Downloading aiohttp-3.14.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (8.3 kB)
Collecting maxminddb<4.0.0,>=3.0.0 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached maxminddb-3.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
Collecting aiohappyeyeballs>=2.5.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached aiohappyeyeballs-2.6.2-py3-none-any.whl.metadata (5.9 kB)
Collecting aiosignal>=1.4.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached aiosignal-1.4.0-py3-none-any.whl.metadata (3.7 kB)
Requirement already satisfied: attrs>=17.3.0 in /opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0) (26.1.0)
Collecting frozenlist>=1.1.1 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (20 kB)
Collecting multi...
GitHub Actions: OpenWISP Controller CI Build / 5_Python==3.10 django~=5.1.0.txt: [feature] Introduced standalone certificate templates and device bindings
Conclusion: failure
found for IP address 172.217.22.14
WHOIS lookup failed. Details: Authentication failed for GeoIP2 service. Check your OPENWISP_CONTROLLER_WHOIS_GEOIP_ACCOUNT and OPENWISP_CONTROLLER_WHOIS_GEOIP_KEY settings.
WHOIS lookup failed. Details: Your account does not have permission to access this service.
WHOIS lookup failed. Details: Authentication failed for GeoIP2 service. Check your OPENWISP_CONTROLLER_WHOIS_GEOIP_ACCOUNT and OPENWISP_CONTROLLER_WHOIS_GEOIP_KEY settings.
WHOIS lookup failed. Details: No WHOIS information found for IP address 172.217.22.14
WHOIS lookup failed. Details: Your account has run out of queries for the GeoIP2 service.
WHOIS lookup failed. Details: Your account does not have permission to access this service.
WHOIS lookup failed. Details: Your account does not have permission to access this service.
WHOIS lookup failed. Details: No WHOIS information found for IP address 172.217.22.14
WHOIS lookup failed. Details: Your account has run out of queries for the GeoIP2 service.
WHOIS lookup failed. Details: Authentication failed for GeoIP2 service. Check your OPENWISP_CONTROLLER_WHOIS_GEOIP_ACCOUNT and OPENWISP_CONTROLLER_WHOIS_GEOIP_KEY settings.
....................Failed to persist DeviceConnection 64e177dc-c0ef-4bf6-98bf-9af7a847af96
..An exception was raised while executing command 281db5ca-c02d-40c6-8129-4c4deb830ce9
Traceback (most recent call last):
File "/home/runner/work/openwisp-controller/openwisp-controller/openwisp_controller/connection/tasks.py", line 83, in launch_command
command.execute()
File "/home/runner/work/openwisp-controller/openwisp-controller/openwisp_controller/connection/tests/test_tasks.py", line 313, in _delete_then_raise
raise RuntimeError("boom")
RuntimeError: boom
........./home/runner/work/openwisp-controller/openwisp-controller/openwisp_controller/geo/estimated_location/service.py:70: AlwaysEagerIgnored: task_always_eager has no effect on send_task
current_app.send_task(
Creati...
GitHub Actions: OpenWISP Controller CI Build / 11_Python==3.13 django~=5.1.0.txt: [feature] Introduced standalone certificate templates and device bindings
Conclusion: failure
b.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
Using cached uritemplate-4.2.0-py3-none-any.whl.metadata (2.6 kB)
Collecting aiohttp<4.0.0,>=3.6.2 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached aiohttp-3.14.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (8.3 kB)
Collecting maxminddb<4.0.0,>=3.0.0 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached maxminddb-3.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
Collecting aiohappyeyeballs>=2.5.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached aiohappyeyeballs-2.6.2-py3-none-any.whl.metadata (5.9 kB)
Collecting aiosignal>=1.4.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached aiosignal-1.4.0-py3-none-any.whl.metadata (3.7 kB)
Requirement already satisfied: attrs>=17.3.0 in /opt/hostedtoolcache/Python/3.13.14/x64/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0) (26.1.0)
Collecting frozenlist>=1.1.1 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (20 kB)
Collecting multidict<7.0,>=4.5 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
Collecting propcache>=0.2.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
Using cached propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (16 kB)
Collecting yarl<2.0,>=1.17.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
...
GitHub Actions: OpenWISP Controller CI Build / 10_Python==3.11 django~=5.1.0.txt: [feature] Introduced standalone certificate templates and device bindings
Conclusion: failure
te_api_blueprint_already_assigned
�[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_create_api_blueprint_ca_mismatch)�[0m
�[33;1m (0.44s)�[0m test_template_create_api_blueprint_ca_mismatch
�[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_create_api_org_scoping)�[0m
�[33;1m (0.33s)�[0m test_template_create_api_org_scoping
�[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_create_cert_blueprint_assignment)�[0m
�[33;1m (0.44s)�[0m test_template_create_cert_blueprint_assignment
�[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_create_cert_type_api)�[0m
�[33;1m (0.43s)�[0m test_template_create_cert_type_api
�[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_create_of_vpn_type)�[0m
�[33;1m (0.49s)�[0m test_template_create_of_vpn_type
�[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_create_vpn_with_type_as_generic)�[0m
�[33;1m (0.75s)�[0m test_template_create_vpn_with_type_as_generic
�[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_create_with_shared_vpn)�[0m
�[33;1m (0.79s)�[0m test_template_create_with_shared_vpn
�[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_creation_with_no_org_by_operator)�[0m
�[33;1m (0.63s)�[0m test_template_creation_with_no_org_by_operator
�[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_list_api_filter)�[0m
�[33;1m (0.54s)�[0m test_template_list_api_filter
�[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_list_for_shared_objects)�[0m
�[33;1m (0.93s)�[0m test_template_list_for_shared_objects
�[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_patch_api)�[0m
�[33;1m (0.32s)�[0m test_template_patch_api
�[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_put_api)�[0m
�[33;1m (0.32s)�[0m test_template_put_api
�[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_update_api_active_change_blocked)�[0m
�[33;1m (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:
tests/openwisp2/sample_config/tests.pytests/openwisp2/sample_config/models.pyopenwisp_controller/config/tests/test_config.pyopenwisp_controller/config/models.pyopenwisp_controller/config/static/config/js/switcher.jsopenwisp_controller/config/settings.pyopenwisp_controller/config/apps.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pytests/openwisp2/settings.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tasks.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tests/test_template.pyopenwisp_controller/config/admin.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/tests/test_device.pyopenwisp_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:
tests/openwisp2/sample_config/tests.pytests/openwisp2/sample_config/models.pyopenwisp_controller/config/tests/test_config.pyopenwisp_controller/config/models.pyopenwisp_controller/config/static/config/js/switcher.jsopenwisp_controller/config/settings.pyopenwisp_controller/config/apps.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pytests/openwisp2/settings.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tasks.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tests/test_template.pyopenwisp_controller/config/admin.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/tests/test_device.pyopenwisp_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:
tests/openwisp2/sample_config/tests.pytests/openwisp2/sample_config/models.pyopenwisp_controller/config/tests/test_config.pyopenwisp_controller/config/models.pyopenwisp_controller/config/static/config/js/switcher.jsopenwisp_controller/config/settings.pyopenwisp_controller/config/apps.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pytests/openwisp2/settings.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tasks.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tests/test_template.pyopenwisp_controller/config/admin.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/tests/test_device.pyopenwisp_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:
tests/openwisp2/sample_config/tests.pytests/openwisp2/sample_config/models.pyopenwisp_controller/config/templates/admin/config/device_certificates_table.htmlopenwisp_controller/config/tests/test_config.pyopenwisp_controller/config/models.pyopenwisp_controller/config/settings.pyopenwisp_controller/config/apps.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pytests/openwisp2/settings.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tasks.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tests/test_template.pyopenwisp_controller/config/admin.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_selenium.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:
tests/openwisp2/sample_config/tests.pytests/openwisp2/sample_config/models.pyopenwisp_controller/config/tests/test_config.pyopenwisp_controller/config/models.pyopenwisp_controller/config/settings.pyopenwisp_controller/config/apps.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pytests/openwisp2/settings.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tasks.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tests/test_template.pyopenwisp_controller/config/admin.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_selenium.py
🧠 Learnings (9)
📚 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:
tests/openwisp2/sample_config/tests.pytests/openwisp2/sample_config/models.pyopenwisp_controller/config/tests/test_config.pyopenwisp_controller/config/models.pyopenwisp_controller/config/settings.pyopenwisp_controller/config/apps.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pytests/openwisp2/settings.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tasks.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tests/test_template.pyopenwisp_controller/config/admin.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_selenium.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:
tests/openwisp2/sample_config/tests.pytests/openwisp2/sample_config/models.pyopenwisp_controller/config/tests/test_config.pytests/openwisp2/settings.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/pki/tests/test_api.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/tests/test_template.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_selenium.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:
tests/openwisp2/sample_config/tests.pytests/openwisp2/sample_config/models.pyopenwisp_controller/config/tests/test_config.pytests/openwisp2/settings.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/pki/tests/test_api.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/tests/test_template.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_selenium.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:
tests/openwisp2/sample_config/tests.pyopenwisp_controller/config/tests/test_config.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_template.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_selenium.py
📚 Learning: 2026-01-15T15:05:49.557Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/management/commands/clear_last_ip.py:38-42
Timestamp: 2026-01-15T15:05:49.557Z
Learning: In Django projects, when using select_related() to traverse relations (for example, select_related("organization__config_settings")), the traversed relation must not be deferred. If you also use .only() in the same query, include the relation name or FK field (e.g., "organization" or "organization_id") in the .only() list to avoid the error "Field X cannot be both deferred and traversed using select_related at the same time." Apply this guideline to Django code in openwisp_controller/config/management/commands/clear_last_ip.py and similar modules by ensuring any select_related with an accompanying only() includes the related field names to prevent deferred/traversed conflicts.
Applied to files:
openwisp_controller/config/tests/test_config.pyopenwisp_controller/config/models.pyopenwisp_controller/config/settings.pyopenwisp_controller/config/apps.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tasks.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tests/test_template.pyopenwisp_controller/config/admin.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/tests/test_device.pyopenwisp_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/tests/test_config.pyopenwisp_controller/config/models.pyopenwisp_controller/config/settings.pyopenwisp_controller/config/apps.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tasks.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tests/test_template.pyopenwisp_controller/config/admin.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_selenium.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.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_template.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_selenium.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.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_template.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_selenium.py
📚 Learning: 2026-01-12T22:27:40.078Z
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:40.078Z
Learning: In test migrations under tests/openwisp2/sample_config/migrations, verify scenarios where a swappable model (CONFIG_WHOISINFO_MODEL) is extended with extra fields (e.g., an additional 'details' field) to ensure compatibility and no errors when swapping to a custom implementation. This pattern helps confirm that extending AbstractWHOISInfo via a custom model works as intended.
Applied to files:
tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
🪛 ast-grep (0.44.0)
openwisp_controller/config/handlers.py
[warning] 94-94: 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", "DeviceCertificate")
Note: [CWE-502] Deserialization of Untrusted Data.
(keras-load-model-python)
openwisp_controller/config/tasks.py
[warning] 232-232: 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", "Device")
Note: [CWE-502] Deserialization of Untrusted Data.
(keras-load-model-python)
[warning] 233-233: 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", "DeviceCertificate")
Note: [CWE-502] Deserialization of Untrusted Data.
(keras-load-model-python)
tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
[info] 106-106: use help_text to document model columns
Context: models.CharField(blank=True, max_length=64, null=True)
Note: [CWE-710] Improper Adherence to Coding Standards.
(model-help-text)
openwisp_controller/config/base/template.py
[warning] 40-40: 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("django_x509", "Cert")
Note: [CWE-502] Deserialization of Untrusted Data.
(keras-load-model-python)
[warning] 41-41: 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", "DeviceCertificate")
Note: [CWE-502] Deserialization of Untrusted Data.
(keras-load-model-python)
[warning] 274-274: 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", "Config")
Note: [CWE-502] Deserialization of Untrusted Data.
(keras-load-model-python)
[warning] 328-328: 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", "DeviceCertificate")
Note: [CWE-502] Deserialization of Untrusted Data.
(keras-load-model-python)
openwisp_controller/config/base/device_certificate.py
[warning] 41-41: 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/tests/test_device.py
[warning] 30-30: 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("django_x509", "Cert")
Note: [CWE-502] Deserialization of Untrusted Data.
(keras-load-model-python)
[warning] 31-31: 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("django_x509", "Ca")
Note: [CWE-502] Deserialization of Untrusted Data.
(keras-load-model-python)
[warning] 32-32: 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)
openwisp_controller/config/tests/test_selenium.py
[warning] 25-25: 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_notifications", "Notification")
Note: [CWE-502] Deserialization of Untrusted Data.
(keras-load-model-python)
🪛 HTMLHint (1.9.2)
openwisp_controller/config/templates/admin/config/device_certificates_table.html
[error] 1-1: Doctype must be declared before any non-comment content.
(doctype-first)
Addressed coderabbit's minor improvements
Removed blueprint check before str conversion
Added missing documentation promised in proposal
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
openwisp_controller/config/static/config/js/switcher.js (1)
13-39: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winRe-show the VPN helper when switching back to
vpn.After
vpn -> cert/generic -> vpn,.autovpnalready exists, so this branch skips creation and never shows the hidden helper again. The JSON editor stays visible even though the empty-VPN flow should restore the “save to auto-generate” message.💡 Minimal fix
toggle_specific_fields = function (changed) { var val = type_select.val(); if (val === "vpn") { vpn_specific.show(); - if ( - changed === true && - $(".autovpn").length < 1 && - $("`#id_config`").val() === "{}" - ) { - var p1 = gettext( - "Click on Save to automatically generate the " + - "VPN client configuration (will be based on " + - "the configuration of the server).", - ), - p2 = gettext( - "You can then tweak the VPN client " + "configuration in the next step.", - ); - $(".jsoneditor-wrapper").hide().after('<div class="form-row autovpn"></div>'); - $(".autovpn").html( - "<p><strong>" + p1 + "</strong></p>" + "<p><strong>" + p2 + "</strong></p>", - ); + if ($("`#id_config`").val() === "{}") { + if (changed === true && $(".autovpn").length < 1) { + var p1 = gettext( + "Click on Save to automatically generate the " + + "VPN client configuration (will be based on " + + "the configuration of the server).", + ), + p2 = gettext( + "You can then tweak the VPN client " + + "configuration in the next step.", + ); + $(".jsoneditor-wrapper").after('<div class="form-row autovpn"></div>'); + $(".autovpn").html( + "<p><strong>" + p1 + "</strong></p>" + + "<p><strong>" + p2 + "</strong></p>", + ); + } + $(".jsoneditor-wrapper").hide(); + $(".autovpn").show(); } } else { vpn_specific.hide();🤖 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/static/config/js/switcher.js` around lines 13 - 39, The toggle_specific_fields handler in switcher.js hides the VPN helper when leaving vpn, but when switching back to vpn it only creates .autovpn the first time and never re-shows it if it already exists. Update the vpn branch so that after vpn -> cert/generic -> vpn the existing .autovpn element is shown again and the jsoneditor-wrapper stays hidden for the empty-vpn flow; use the toggle_specific_fields, vpn_specific, jsoneditor-wrapper, and .autovpn logic to restore the helper whenever val === "vpn" and the config is still {}.
🤖 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 `@docs/user/certificate-templates.rst`:
- Around line 106-109: The renewal example in the certificate templates docs
uses the old PKI endpoint path, which conflicts with the documented REST API.
Update the standalone certificate renewal example in the paragraph referencing
Cert.renew() to use the /api/v1/controller/cert/{id}/renew/ path so it matches
docs/user/rest-api.rst and stays consistent with the rest of the API
documentation.
- Around line 241-243: The cross-reference in the certificate templates docs
points to a missing REST anchor, so update the reference in the related prose to
use an existing target from the REST docs or add the missing anchor in the REST
endpoint documentation. Verify the link target used by the Device endpoint
reference resolves from docs/user/rest-api.rst, and keep the wording in
certificate-templates.rst consistent with the actual endpoint name.
In `@openwisp_controller/config/admin.py`:
- Around line 458-463: The new admin label for certificate_details is currently
falling back to the method name and bypassing Django i18n. Update the
certificate_details admin accessor in admin.py to use an explicit translatable
description, such as via `@admin.display`(description=_("Certificate details")),
and ensure any related admin text in the affected admin class follows the same
pattern.
In `@openwisp_controller/config/api/serializers.py`:
- Around line 87-171: The validate() method in the serializer is duplicating
cert-template rules that already exist in
AbstractTemplate._clean_cert_template() and _validate_cert_template_changes(),
so the API path can drift from model/admin validation. Refactor this logic to
centralize the CA requirement, CA/blueprint compatibility, and active-template
mutation locks in a shared helper or rely on the model validation path, and keep
validate() in ConfigSerializer as a thin normalization layer that delegates to
the shared cert-template checks.
In `@openwisp_controller/config/base/template.py`:
- Around line 319-341: The template validation in clean() only checks that
blueprint_cert is unassigned and matches the CA, so revoked certificates can
still pass model-level validation. Update the Template.clean logic to explicitly
reject revoked blueprint certificates using the same rules as
get_unassigned_certs(), and keep the validation tied to the
blueprint_cert/blueprint_cert_id checks so Template(...,
blueprint_cert=revoked_cert).full_clean() fails consistently.
---
Outside diff comments:
In `@openwisp_controller/config/static/config/js/switcher.js`:
- Around line 13-39: The toggle_specific_fields handler in switcher.js hides the
VPN helper when leaving vpn, but when switching back to vpn it only creates
.autovpn the first time and never re-shows it if it already exists. Update the
vpn branch so that after vpn -> cert/generic -> vpn the existing .autovpn
element is shown again and the jsoneditor-wrapper stays hidden for the empty-vpn
flow; use the toggle_specific_fields, vpn_specific, jsoneditor-wrapper, and
.autovpn logic to restore the helper whenever val === "vpn" and the config is
still {}.
🪄 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: 73cb1f6d-d963-4deb-a0ed-70050aed6bcf
📒 Files selected for processing (31)
docs/developer/extending.rstdocs/index.rstdocs/user/certificate-templates.rstdocs/user/intro.rstdocs/user/rest-api.rstdocs/user/settings.rstdocs/user/templates.rstopenwisp_controller/config/admin.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/apps.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/models.pyopenwisp_controller/config/settings.pyopenwisp_controller/config/static/config/js/switcher.jsopenwisp_controller/config/tasks.pyopenwisp_controller/config/templates/admin/config/device_certificates_table.htmlopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/tests/test_config.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/tests/test_template.pyopenwisp_controller/pki/tests/test_api.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pytests/openwisp2/sample_config/models.pytests/openwisp2/sample_config/tests.pytests/openwisp2/settings.py
📜 Review details
⏰ Context from checks skipped due to timeout. (11)
- GitHub Check: Python==3.12 | django~=5.1.0
- GitHub Check: Python==3.12 | django~=4.2.0
- GitHub Check: Python==3.11 | django~=5.1.0
- GitHub Check: Python==3.11 | django~=4.2.0
- GitHub Check: Python==3.10 | django~=5.2.0
- GitHub Check: Python==3.12 | django~=5.2.0
- GitHub Check: Python==3.11 | django~=5.2.0
- GitHub Check: Python==3.10 | django~=4.2.0
- GitHub Check: Python==3.10 | django~=5.1.0
- GitHub Check: Python==3.13 | django~=5.1.0
- GitHub Check: Python==3.13 | 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:
tests/openwisp2/sample_config/tests.pyopenwisp_controller/config/settings.pyopenwisp_controller/config/apps.pyopenwisp_controller/config/tests/test_config.pytests/openwisp2/settings.pyopenwisp_controller/config/static/config/js/switcher.jsopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/models.pytests/openwisp2/sample_config/models.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/tests/test_admin.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tasks.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_template.pyopenwisp_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:
tests/openwisp2/sample_config/tests.pyopenwisp_controller/config/settings.pyopenwisp_controller/config/apps.pyopenwisp_controller/config/tests/test_config.pytests/openwisp2/settings.pyopenwisp_controller/config/static/config/js/switcher.jsopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/models.pytests/openwisp2/sample_config/models.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/tests/test_admin.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tasks.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_template.pyopenwisp_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:
tests/openwisp2/sample_config/tests.pyopenwisp_controller/config/settings.pyopenwisp_controller/config/apps.pyopenwisp_controller/config/tests/test_config.pytests/openwisp2/settings.pyopenwisp_controller/config/static/config/js/switcher.jsopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/models.pytests/openwisp2/sample_config/models.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/tests/test_admin.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tasks.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_template.pyopenwisp_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:
tests/openwisp2/sample_config/tests.pyopenwisp_controller/config/settings.pyopenwisp_controller/config/templates/admin/config/device_certificates_table.htmlopenwisp_controller/config/apps.pyopenwisp_controller/config/tests/test_config.pytests/openwisp2/settings.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/models.pytests/openwisp2/sample_config/models.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/tests/test_admin.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tasks.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_template.pyopenwisp_controller/config/admin.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:
tests/openwisp2/sample_config/tests.pyopenwisp_controller/config/settings.pyopenwisp_controller/config/apps.pyopenwisp_controller/config/tests/test_config.pytests/openwisp2/settings.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/models.pytests/openwisp2/sample_config/models.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/tests/test_admin.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tasks.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_template.pyopenwisp_controller/config/admin.py
🧠 Learnings (9)
📚 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:
tests/openwisp2/sample_config/tests.pyopenwisp_controller/config/settings.pyopenwisp_controller/config/apps.pyopenwisp_controller/config/tests/test_config.pytests/openwisp2/settings.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/models.pytests/openwisp2/sample_config/models.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/tests/test_admin.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tasks.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_template.pyopenwisp_controller/config/admin.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:
tests/openwisp2/sample_config/tests.pyopenwisp_controller/config/tests/test_config.pytests/openwisp2/settings.pytests/openwisp2/sample_config/models.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/tests/test_admin.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_template.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:
tests/openwisp2/sample_config/tests.pyopenwisp_controller/config/tests/test_config.pytests/openwisp2/settings.pytests/openwisp2/sample_config/models.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/tests/test_admin.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_template.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:
tests/openwisp2/sample_config/tests.pyopenwisp_controller/config/tests/test_config.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_template.py
📚 Learning: 2026-01-15T15:05:49.557Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/management/commands/clear_last_ip.py:38-42
Timestamp: 2026-01-15T15:05:49.557Z
Learning: In Django projects, when using select_related() to traverse relations (for example, select_related("organization__config_settings")), the traversed relation must not be deferred. If you also use .only() in the same query, include the relation name or FK field (e.g., "organization" or "organization_id") in the .only() list to avoid the error "Field X cannot be both deferred and traversed using select_related at the same time." Apply this guideline to Django code in openwisp_controller/config/management/commands/clear_last_ip.py and similar modules by ensuring any select_related with an accompanying only() includes the related field names to prevent deferred/traversed conflicts.
Applied to files:
openwisp_controller/config/settings.pyopenwisp_controller/config/apps.pyopenwisp_controller/config/tests/test_config.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/models.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tasks.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_template.pyopenwisp_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/settings.pyopenwisp_controller/config/apps.pyopenwisp_controller/config/tests/test_config.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/models.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/tasks.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_template.pyopenwisp_controller/config/admin.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.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_template.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.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_template.py
📚 Learning: 2026-01-12T22:27:40.078Z
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:40.078Z
Learning: In test migrations under tests/openwisp2/sample_config/migrations, verify scenarios where a swappable model (CONFIG_WHOISINFO_MODEL) is extended with extra fields (e.g., an additional 'details' field) to ensure compatibility and no errors when swapping to a custom implementation. This pattern helps confirm that extending AbstractWHOISInfo via a custom model works as intended.
Applied to files:
tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
🪛 ast-grep (0.44.0)
openwisp_controller/config/tests/test_selenium.py
[warning] 25-25: 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_notifications", "Notification")
Note: [CWE-502] Deserialization of Untrusted Data.
(keras-load-model-python)
openwisp_controller/config/handlers.py
[warning] 98-98: 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", "DeviceCertificate")
Note: [CWE-502] Deserialization of Untrusted Data.
(keras-load-model-python)
tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
[info] 106-106: use help_text to document model columns
Context: models.CharField(blank=True, max_length=64, null=True)
Note: [CWE-710] Improper Adherence to Coding Standards.
(model-help-text)
openwisp_controller/config/base/template.py
[warning] 40-40: 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("django_x509", "Cert")
Note: [CWE-502] Deserialization of Untrusted Data.
(keras-load-model-python)
[warning] 41-41: 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", "DeviceCertificate")
Note: [CWE-502] Deserialization of Untrusted Data.
(keras-load-model-python)
[warning] 274-274: 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", "Config")
Note: [CWE-502] Deserialization of Untrusted Data.
(keras-load-model-python)
[warning] 328-328: 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", "DeviceCertificate")
Note: [CWE-502] Deserialization of Untrusted Data.
(keras-load-model-python)
openwisp_controller/config/tasks.py
[warning] 232-232: 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", "Device")
Note: [CWE-502] Deserialization of Untrusted Data.
(keras-load-model-python)
[warning] 233-233: 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", "DeviceCertificate")
Note: [CWE-502] Deserialization of Untrusted Data.
(keras-load-model-python)
openwisp_controller/config/base/device_certificate.py
[warning] 41-41: 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/tests/test_device.py
[warning] 30-30: 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("django_x509", "Cert")
Note: [CWE-502] Deserialization of Untrusted Data.
(keras-load-model-python)
[warning] 31-31: 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("django_x509", "Ca")
Note: [CWE-502] Deserialization of Untrusted Data.
(keras-load-model-python)
[warning] 32-32: 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)
🪛 HTMLHint (1.9.2)
openwisp_controller/config/templates/admin/config/device_certificates_table.html
[error] 1-1: Doctype must be declared before any non-comment content.
(doctype-first)
🔇 Additional comments (10)
openwisp_controller/config/base/template.py (1)
267-299: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winGuard active
* -> certtype flips too, or reconcile existing assignments.Line 270 only blocks
cert -> *. An already-assigned active template can still be switched fromgeneric/vpntocert, but the save hooks in this file only recompute config status; they do not createDeviceCertificaterows for the existingConfig.templatesrelations. That leaves active configs attached to a certificate template without the cert relation/context until the template is removed and re-added. As per coding guidelines, preserve validation around templates.Suggested fix
- changing_protected_fields = ( - current.ca_id != self.ca_id - or current.blueprint_cert_id != self.blueprint_cert_id - or (current.type == "cert" and self.type != "cert") - ) + changing_protected_fields = ( + current.ca_id != self.ca_id + or current.blueprint_cert_id != self.blueprint_cert_id + or (current.type != self.type and "cert" in {current.type, self.type}) + ) @@ - if current.type == "cert" and self.type != "cert": + if current.type != self.type and "cert" in {current.type, self.type}: errors["type"] = _( "This template is already assigned to active devices. " - "You cannot change the template type from certificate " + "You cannot change the template type to or from certificate " "on an active template." )> Likely an incorrect or invalid review comment.Source: Coding guidelines
openwisp_controller/config/base/device_certificate.py (1)
125-147: Custom hardware OIDs are still encoded in a django-x509-incompatible shape.
_build_extensions()appends{"oid": ..., "value": ..., "critical": ...}items, but the current django-x509 validation path expects extension dicts keyed byname. That still leaves certificate creation failing withUnsupported extension: None, so template assignment and hardware-drift regeneration remain blocked until these OIDs are emitted in a format supported by the repo-pinned django-x509 version. Please verify this against the pinned dependency before merging.openwisp_controller/config/tests/test_config.py (1)
864-871: LGTM!tests/openwisp2/sample_config/models.py (1)
99-105: LGTM!openwisp_controller/pki/tests/test_api.py (1)
155-155: LGTM!Also applies to: 256-256, 267-275, 292-292
openwisp_controller/config/tests/test_selenium.py (1)
25-26: LGTM!Also applies to: 491-562, 846-878
openwisp_controller/config/handlers.py (1)
48-114: LGTM!openwisp_controller/config/tests/test_admin.py (1)
11-11: LGTM!Also applies to: 53-53, 1806-1830, 2363-2364, 2458-2458
openwisp_controller/config/tasks.py (1)
226-294: LGTM!openwisp_controller/config/base/config.py (1)
66-72: LGTM!Also applies to: 476-489, 502-547, 978-1003, 1032-1032
Addressed improvements and docs correction.
|
The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3). |
|
@coderabbitai resolve |
✅ Action performedComments resolved and changes approved. |
Changed certificate details to a separate tab in device admin
Fixed failed tests after changes
|
The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3). |
Checklist
Reference to Existing Issue
Closes #1356
Closes #1377
Closes #1357
Closes #1361
Closes #1358
Closes #1360
Closes #1359
Description of Changes
This PR establishes the database architecture, UI, API and lifecycle for standalone X.509 certificate templates.
Manual test plan
Setup
PKI->Certification Authoritiesand create two CAs:CA-1andCA-2.PKI->Certificatesand create two certificates to act as blueprints:Blueprint-1(Must useCA-1)Blueprint-2(Must useCA-2)Devicesand create a device (test-device).Template Creation and Validation
Configuration->Templatesand clickADD TEMPLATE.Certificate.CA-1.Blueprint-2(which belongs toCA-2). Try to save.Blueprint-1. Name the templateActive-Cert-Template. Save it.Device Provisioning
test-device.Active-Cert-Template. Save.PKI->Certificates.test-device. Its status should be valid (not revoked).Active Mutation Locks
Configuration->Templatesand editActive-Cert-Template(which is now assigned to an active device).Generic. Try to save.CA-2. Try to save.Blueprint-2(ensure you also change the CA so they match, triggering the active lock). Try to save.Revocation on Removal
test-device.Active-Cert-Templateentirely from the templates list. Save.PKI->Certificatesand locate the device's certificate.Context Configuration Injection
Go to
Configuration->Templates, openActive-Cert-Template, and copy its UUID from the URL bar (removing the dashes so it is a 32-character hex string).In the JSON configuration editor for the template, add a configuration block that references the certificate's UUID variables:
(Note: Replace
<uuid>with the actual 32-character hex string of the template).Click Save.
Go back to the Configuration page for
test-device(which has this template assigned) and click thePreview configurationbutton.Expected Result: The variables should be successfully resolved. In the preview, you should see the generated path (e.g.,
/etc/x509/cert-<uuid>.pem) and the literal-----BEGIN CERTIFICATE-----text instead of the raw{{ }}template tags.output.mp4