[feature] Introduced standalone certificate templates and device bindings#1378
[feature] Introduced standalone certificate templates and device bindings#1378stktyagi wants to merge 36 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 introduces a complete X.509 certificate template system for OpenWISP. It extends Sequence Diagram(s)sequenceDiagram
participant Admin/API
participant AbstractTemplate
participant AbstractConfig
participant manage_device_certs
participant AbstractDeviceCertificate
participant X509Cert
Admin/API->>AbstractTemplate: save cert template with ca
AbstractTemplate->>AbstractTemplate: clean() validates ca & blueprint_cert
Admin/API->>AbstractConfig: attach to device config
AbstractConfig->>manage_device_certs: m2m_changed post_add
manage_device_certs->>AbstractDeviceCertificate: create(config, template, auto_cert=True)
AbstractDeviceCertificate->>AbstractDeviceCertificate: save() calls _auto_x509()
AbstractDeviceCertificate->>X509Cert: create client cert with device CN & OIDs
X509Cert->>AbstractDeviceCertificate: assign to cert field
AbstractDeviceCertificate->>AbstractConfig: device context now includes cert PEM/key
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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: 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)
Fix these issues in Kilo Cloud Reviewed by gpt-5.4-20260305 · 41,749 tokens |
|
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
|
The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (2/3). |
Updated tests with increased number of queries to pass CI
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
openwisp_controller/config/base/template.py (1)
362-367:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winOnly normalize missing
default_values, not every falsy payload.
if not self.default_valuesrewrites[],"", andFalseto{}, so invalid JSON values bypass the object-type validation on Line 364 instead of being rejected.🐛 Minimal fix
- if not self.default_values: + if self.default_values is None: self.default_values = {} if not isinstance(self.default_values, dict): raise ValidationError( {"default_values": _("the supplied value is not a JSON object")} )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@openwisp_controller/config/base/template.py` around lines 362 - 367, The code currently treats any falsy value (e.g. [], "", False) as missing and replaces it with {}, bypassing the subsequent dict type check; change the normalization to only set self.default_values = {} when it is None (use "if self.default_values is None:") and keep the existing isinstance(self.default_values, dict) ValidationError for all other non-dict values so []/""/False are rejected; reference self.default_values and the ValidationError raised to locate and update the logic.
🤖 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 51-54: Rename the misleading UI label "Automatic tunnel
provisioning" to "Automatic certificate provisioning" in the docs; update the
:guilabel:`Automatic tunnel provisioning` entry (the one showing the auto_cert
field) to :guilabel:`Automatic certificate provisioning` and ensure the
accompanying description still references the auto_cert option and that wording
consistently describes X.509 certificate provisioning rather than VPN/tunnel
certificates (adjust any nearby sentences that mention "tunnel" to "certificate"
as needed).
- Around line 116-119: The RST list for fields that cannot be changed is
currently on a single line; split it into a proper reStructuredText bulleted
list so each item is on its own line and properly indented, preserving the
:guilabel:`Type`, :guilabel:`Certificate Authority`, and :guilabel:`Blueprint
Certificate` roles and inline code (``cert``) markup; replace the single-line
sequence with a vertical list (e.g., using hyphens or asterisks) so each of
those three items becomes its own list item and the surrounding sentence
punctuation/layout is adjusted accordingly.
In `@openwisp_controller/config/api/serializers.py`:
- Around line 78-80: The validate_config() guard currently reads template_type =
self.initial_data.get("type") which allows a PATCH/PUT to bypass validation if
"type" is not re-submitted; change the logic to fall back to the serializer
instance's type (e.g., getattr(self.instance, "type", None) or
self.instance.type) when initial_data doesn't include "type" so that the check
(template_type == "generic" and value == {}) uses the real object type and
rejects empty generic configs accordingly.
- Around line 104-119: The compatibility check uses stale local variables ca and
blueprint_cert after you clear data["ca"] and data["blueprint_cert"] when
template_type != "cert", causing erroneous ValidationError; update the code so
that after you null out data["ca"] and data["blueprint_cert"] you also set the
local variables ca and blueprint_cert to None (or re-read them from data) or
simply skip the CA/blueprint_cert validation when template_type != "cert" so the
check in the block that references blueprint_cert, ca, template_type, and data
reflects the normalized values.
In `@openwisp_controller/config/base/config.py`:
- Around line 509-520: The post_clear on_commit callback runs before the final
template set is persisted in non-atomic clear→readd flows, causing
DeviceCertificate rows to be deleted; ensure the org-change clear/reapply path
is executed inside a transaction so transaction.on_commit sees the final
templates — specifically, wrap the template-clear and the subsequent call to
Config.enforce_required_templates (called from DeviceDetailSerializer.update) in
an outer transaction.atomic() so the lambda registered on transaction.on_commit
in the block handling action == "post_clear" observes the final
instance.templates and does not prematurely delete
instance.devicecertificate_set.
In `@openwisp_controller/config/base/device_certificate.py`:
- Around line 38-59: The clean() guard that prevents assigning a certificate
already used as a template blueprint isn't invoked on ORM saves; update the save
method (in DeviceCertificate.save) to call self.clean() (or
self.full_clean(validate_unique=False) if you prefer model validation) inside
the transaction before any auto-cert provisioning and before super().save so the
cert_id/Template.objects.filter(blueprint_cert_id=...) invariant is enforced on
create/update via the ORM; keep the existing auto-cert logic (_auto_x509),
transaction.atomic block, and final super().save(*args, **kwargs).
In `@openwisp_controller/config/tests/test_admin.py`:
- Line 2335: The inline comment describing the query breakdown is out of date
for the updated expected_count = 23; update the comment near expected_count to
explain that the TemplateAdmin list_display now includes the added fields ca and
blueprint_cert (in TemplateAdmin), which introduces the extra query(s) —
explicitly state which query(s) now account for the +1 (e.g., that
template-related fetching now results in an extra query when retrieving
templates/fields), so the comment documents that the total is 23 instead of 22
and references TemplateAdmin/list_display and the ca and blueprint_cert
additions.
In `@openwisp_controller/config/tests/test_config.py`:
- Around line 864-865: The test reveals an extra DB query introduced in
_check_changes(): identify and short-circuit the cert-related branch so it
doesn't perform the extra lookup when certificate checks aren't required; modify
Config._check_changes() to first determine (via existing attributes or a new
helper like _cert_checks_needed() or _cert_fields_changed()) whether cert
validation is necessary and return/skip the DB access early when not needed,
moving any certificate model/query logic into that guarded block (also apply
same short-circuit to the other path referenced near lines 870-871).
In `@openwisp_controller/config/tests/test_template.py`:
- Around line 1373-1407: In test_cert_template_partial_replacement_cleans_up add
an assertion that the underlying Cert for the removed template was revoked when
calling config.templates.set([cert_template_1], clear=True): locate the removed
template via removed_cert_id (or lookup DeviceCertificate for cert_template_2
before deletion) and assert the related Cert object has its revoked flag (or
revoked_at) set (e.g., Cert.objects.filter(pk=<cert_pk>).exists() and
Cert.objects.get(pk=<cert_pk>).revoked is True or revoked_at is not None),
ensuring the revoke-before-delete lifecycle is enforced when
config.templates.set(..., clear=True) removes a DeviceCertificate.
In `@openwisp_controller/pki/tests/test_api.py`:
- Line 155: The assertion in test_ca_delete_api expecting six DB queries needs
an inline comment explaining why an extra query occurs: document that
config.Template rows reference the CA via AbstractTemplate.ca (ForeignKey to
django_x509.Ca with on_delete=CASCADE), so when deleting a Ca the Django cascade
delete collector issues an additional query to find and cascade related Template
rows; add this explanation directly next to the with self.assertNumQueries(6):
line so future readers understand the Template FK cascade is the source of the
extra query.
---
Outside diff comments:
In `@openwisp_controller/config/base/template.py`:
- Around line 362-367: The code currently treats any falsy value (e.g. [], "",
False) as missing and replaces it with {}, bypassing the subsequent dict type
check; change the normalization to only set self.default_values = {} when it is
None (use "if self.default_values is None:") and keep the existing
isinstance(self.default_values, dict) ValidationError for all other non-dict
values so []/""/False are rejected; reference self.default_values and the
ValidationError raised to locate and update the logic.
🪄 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: 88ef4903-9bee-4102-b807-03fcbc9099e8
📒 Files selected for processing (20)
docs/user/certificate-templates.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/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/models.pyopenwisp_controller/config/static/config/js/switcher.jsopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/tests/test_config.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/settings.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). (1)
- GitHub Check: Python==3.10 | django~=4.2.0
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}
📄 CodeRabbit inference engine (Custom checks)
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}: Flag potential security vulnerabilities in code
Avoid unnecessary comments or docstrings for code that is already clear
Code formatting is compact and readable. Do not add excessive blank lines, especially inside function or method bodies
Flag unused or redundant code
Ensure variables, functions, classes, and files have descriptive and consistent names
New code must handle errors properly: log errors that cannot be resolved by the user with error level, log unusual conditions with warning level, log important background actions with info level, and provide user-facing messages for errors that the user can solve autonomously
Files:
openwisp_controller/config/apps.pytests/openwisp2/sample_config/models.pytests/openwisp2/settings.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/config/models.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_config.pyopenwisp_controller/config/static/config/js/switcher.jsopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/admin.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/base/device_certificate.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/apps.pytests/openwisp2/sample_config/models.pytests/openwisp2/settings.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/config/models.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_config.pyopenwisp_controller/config/static/config/js/switcher.jsopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/admin.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/base/device_certificate.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/apps.pytests/openwisp2/sample_config/models.pytests/openwisp2/settings.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/config/models.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_config.pyopenwisp_controller/config/static/config/js/switcher.jsopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/admin.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/base/device_certificate.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/apps.pytests/openwisp2/sample_config/models.pytests/openwisp2/settings.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/config/models.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_config.pyopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/admin.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/tests/test_template.py
**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
**/*.py: Mark user-facing strings for translation with Django i18n helpers in Django code
Avoid unnecessary blank lines inside function and method bodies
Be careful with authentication, authorization, queryset filtering, serializers, admin behavior, cache invalidation, signals, Celery tasks, and websocket updates in Django code
Preserve validation around templates, VPN/PKI material, SSH credentials, device commands, uploaded files, URLs, and subnet/IP data
Write comments and docstrings only when they explain why code is shaped a certain way, placing them before the relevant code block instead of scattering them inside it
Files:
openwisp_controller/config/apps.pytests/openwisp2/sample_config/models.pytests/openwisp2/settings.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/config/models.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_config.pyopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/admin.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/base/device_certificate.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/apps.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/config/models.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_config.pyopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/admin.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/base/device_certificate.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/apps.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/config/models.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_config.pyopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/admin.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/base/device_certificate.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/apps.pytests/openwisp2/sample_config/models.pytests/openwisp2/settings.pyopenwisp_controller/config/tests/test_admin.pyopenwisp_controller/config/models.pyopenwisp_controller/pki/tests/test_api.pyopenwisp_controller/config/tests/test_config.pyopenwisp_controller/config/tests/test_selenium.pyopenwisp_controller/config/admin.pytests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/base/config.pyopenwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.pyopenwisp_controller/config/tests/test_api.pyopenwisp_controller/config/base/template.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/base/device_certificate.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 (5)
docs/user/templates.rst (1)
213-229: LGTM!openwisp_controller/config/tests/test_template.py (1)
974-1466: LGTM!openwisp_controller/config/tests/test_api.py (1)
34-35: LGTM!Also applies to: 557-558, 743-744, 1334-1541
openwisp_controller/config/tests/test_selenium.py (1)
772-804: LGTM!openwisp_controller/pki/tests/test_api.py (1)
275-275: Document whytest_cert_delete_apiquery count increased (6 → 8)
openwisp_controller/pki/tests/test_api.pyexpectswith self.assertNumQueries(8):(up from 6). Please add an inline comment explaining what ORM work added the extra 2 queries. The earlier assumption about aDeviceCertificateOneToOneFieldtoCertisn’t supported by the currentopenwisp_controller/config/search (OneToOneField.*Cert.*on_deleteproduced no matches), so the comment should reference the actual relationship/model/deletion path that causes the extra queries.
Added dynamic JS logic in 'switcher.js' to change the auto-cert label text between 'Automatic tunnel provisioning' and 'Automatic certificate provisioning'
|
@coderabbitai resolve |
✅ Action performedComments resolved and changes approved. |
…s/1356-extend-abstract-template
Added documentation for context injection Fixes #1360
b69465f to
1e9e813
Compare
|
The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3). |
|
The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (2/3). |
|
The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3). |
|
The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (2/3). |
nemesifier
left a comment
There was a problem hiding this comment.
Great progress @stktyagi, see my comments below, I'll be submitting more soon.
| @@ -0,0 +1,219 @@ | |||
| X.509 Certificate Generator Templates | |||
There was a problem hiding this comment.
Add this page to the /docs/index.rst, please also add a mention of this feature in /docs/user/intro.rst.
| OpenWISP admin and set the **Type** to :guilabel:`Certificate` (``cert``). | ||
| This will reveal the certificate-specific configuration fields. | ||
|
|
||
| .. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/certificate-template.png |
There was a problem hiding this comment.
For consistency:
| .. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/certificate-template.png | |
| .. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/1.4/certificate-templates/<screenshot-name>.png |
- We're separating images for different versions as the UI may be different in different versions.
- We use a dir to group several images for the same feature, as we'll likely show multiple media files, not just one
|
|
||
| .. _certificate_templates_api: | ||
|
|
||
| Certificate Templates via the REST API |
There was a problem hiding this comment.
Check if the rest api docs page needs to be updated.
Let's also double check that both the swagger API docs (/api/v1/docs/) and the DRF browsable API reflect the new fields properly.
| data["blueprint_cert"] = None | ||
| ca = None | ||
| blueprint_cert = None | ||
|
|
| ) | ||
| } | ||
| ) | ||
|
|
| instance.devicecertificate_set.exclude( | ||
| template_id__in=instance.templates.values_list("id", flat=True) | ||
| ).delete() | ||
|
|
There was a problem hiding this comment.
If there's comments we don't need an extra blank line to separate blocks of code.
| abstract = False | ||
|
|
||
|
|
||
| class DeviceCertificate(DetailsModel, AbstractDeviceCertificate): |
There was a problem hiding this comment.
The addition of this model requires updating the extending.rst docs page.
| cert_context.update( | ||
| { | ||
| f"{prefix}_path": cert_path, | ||
| f"{prefix}_pem": dc.cert.certificate, |
There was a problem hiding this comment.
I do not see the standalone certificate renewal path wired into the config status update logic. certificate_updated() still only resolves instance.vpnclient.config, but standalone certificate templates now expose DeviceCertificate.cert material in the rendered context here. If a standalone certificate is renewed or otherwise saved through the existing PKI path, the generated PEM/private key in the context changes, but the related config is not marked modified and its checksum cache is not invalidated. This leaves devices with stale config state after renewal, which is one of the lifecycle requirements in #1358.
Are you sure this is covered? Are there tests for this aspect? If not please add them (TDD is great here).
| Certificate Templates | ||
| --------------------- | ||
|
|
||
| A Certificate Template is a :doc:`Template </controller/user/templates>` |
There was a problem hiding this comment.
This new section mentions certificate templates, but it does not link to the detailed certificate-templates.rst page, and that new page is not added to the user docs toctree in docs/index.rst, so it is not reachable from the built docs. I would add the new page to the toctree and link to it from here.
|
|
||
| - ``{{ cert_<template_uuid_hex>_pem }}``: The public certificate. | ||
| - ``{{ cert_<template_uuid_hex>_key }}``: The private key. | ||
| - ``{{ cert_<template_uuid_hex>_uuid }}``: The UUID of the |
There was a problem hiding this comment.
This says cert_<template_uuid_hex>_uuid is the UUID of the DeviceCertificate object, but the implementation returns str(dc.cert.id). Please change this to say it is the generated certificate UUID. While updating docs, please also update the stale auto_cert text in docs/user/settings.rst and the template type examples in docs/user/rest-api.rst, which still list only VPN/generic.
Implemented certificate regeneration if device property change. Fixes #1359
Test Failure:
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
openwisp_controller/config/static/config/js/switcher.js (1)
47-62: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winCollapse the duplicated
auto_certtoggle into one branch.These two blocks both show/hide
auto_cert_field; only the second one also updates the label. Keeping them separate adds redundant DOM writes and makes the visibility logic easy to drift.♻️ Proposed refactor
- if (val === "vpn" || val === "cert") { - auto_cert_field.show(); - } else { - auto_cert_field.hide(); - } if (val === "vpn" || val === "cert") { auto_cert_field.show(); if (val === "vpn") { auto_cert_label.text(gettext("Automatic tunnel provisioning"));As per coding guidelines: "Flag unused or redundant code."
🤖 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 47 - 62, The code duplicates show/hide logic for auto_cert_field; collapse into a single branch that checks if val === "vpn" || val === "cert" once, calls auto_cert_field.show() or auto_cert_field.hide() accordingly, and inside the true branch update auto_cert_label.text(...) based on whether val === "vpn" or val === "cert"; remove the redundant earlier if/else block so only one place mutates auto_cert_field and the label (refer to symbols auto_cert_field, auto_cert_label, and the val variable).Source: Coding guidelines
openwisp_controller/config/api/serializers.py (1)
138-148:⚠️ Potential issue | 🟠 Major | ⚡ Quick winBlock all active-template type changes, not just
cert -> non-cert.The current guard only rejects switching an active template away from
cert. It still allowsgeneric/vpn→cert, but the certificate lifecycle is wired to template add/remove flows in this file, not to template type mutation. Existing configs that already reference the template would therefore missDeviceCertificatecreation entirely.🐛 Proposed fix
- # block changing a certificate template to a generic template - if self.instance.type == "cert" and template_type != "cert": + # block changing the type of any template already assigned + # to active or activating devices + if "type" in data and template_type != self.instance.type: raise serializers.ValidationError( { "type": _( - "This template is already assigned to active devices. " - "You cannot change the template type from certificate " - "on an active template." + "This template is already assigned to active devices. " + "You cannot change the template type on an active template." ) } )🤖 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/api/serializers.py` around lines 138 - 148, The guard currently only prevents changing self.instance.type from "cert" to non-"cert"; change it to block any mutation of the template type when the template is active/assigned to devices by checking if self.instance.type != template_type and the template is active (or assigned to devices) and raise serializers.ValidationError (same message text adapted to "You cannot change the template type on an active template.") so type changes like generic/vpn -> cert are also rejected and thus prevent missing DeviceCertificate lifecycle actions; update the validation block that references self.instance.type and template_type to perform this broader check and raise the error.
♻️ Duplicate comments (1)
openwisp_controller/config/api/serializers.py (1)
442-453:⚠️ Potential issue | 🟠 Major | ⚡ Quick winClear the bound
DeviceCertificaterows in the organization-change path too.
_update_config()explicitly deletes staleDeviceCertificaterows beforeconfig.templates.set(..., clear=True), but this branch callsinstance.config.templates.clear()directly and never performs the same cleanup. On an organization change without aconfigpayload, old certificate bindings can survive even though the templates were cleared.🐛 Proposed fix
with transaction.atomic(): + instance.config.devicecertificate_set.all().delete() instance.config.device.organization = validated_data.get( "organization" ) instance.config.templates.clear() Config.enforce_required_templates(🤖 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/api/serializers.py` around lines 442 - 453, The organization-change branch that calls instance.config.templates.clear() must also remove any stale DeviceCertificate bindings like the _update_config path does: before calling instance.config.templates.clear(), delete DeviceCertificate rows associated with this instance and its config (the same cleanup _update_config performs), then proceed to clear templates and call Config.enforce_required_templates with raw_data_for_signal_handlers; locate the cleanup logic in _update_config and replicate the DeviceCertificate deletion here so old certificate bindings cannot survive an organization-only change.
🤖 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/api/serializers.py`:
- Around line 78-80: The serializer's template_type fallback is inconsistent:
currently template_type is derived with self.initial_data.get("type",
getattr(self.instance, "type", None)) while validate() treats a missing type as
"generic", so validate_config() can see None and skip the empty-{} guard. Change
the template_type fallback to the serializer's default ("generic")—e.g. use
self.initial_data.get("type", getattr(self.instance, "type", "generic")) or
otherwise normalize None to "generic" before calling validate_config()—so
validate_config(), validate(), and the {}-guard all use the same default type;
update references in validate_config()/validate() accordingly.
In `@openwisp_controller/config/tasks.py`:
- Around line 227-289: The task is vulnerable to concurrent runs; to fix it,
acquire a row lock on the DeviceCertificate set before rotating so two workers
can't operate on the same row: in regenerate_device_certificates_task update the
active_device_certs queryset (the one created with
DeviceCertificate.objects.filter(config__device=device, auto_cert=True,
cert__revoked=False, template__type="cert").select_related(...)) to include
.select_for_update() (and optionally .order_by('pk') for deterministic locking)
inside the existing transaction.atomic() block, re-evaluate .exists() after
locking, and then proceed to revoke/save each locked dc so overlapping tasks
serialize safely.
- Around line 256-285: The new Cert creation in the rotation path only sets
name, ca, organization, common_name and extensions, which drops other
certificate metadata (key_length, digest, country_code, state, city,
organization_name, email) that the blueprint/CA may define; update the block
that constructs new_cert (the Cert(...) instantiation) to copy those fields from
the source blueprint/CA (use dc.template.blueprint_cert and/or old_cert as the
source) so that key_length, digest, country_code, state, city, organization_name
and email are preserved, keep using dc._get_common_name() and the computed
extensions, then call new_cert.full_clean() and new_cert.save() as before.
---
Outside diff comments:
In `@openwisp_controller/config/api/serializers.py`:
- Around line 138-148: The guard currently only prevents changing
self.instance.type from "cert" to non-"cert"; change it to block any mutation of
the template type when the template is active/assigned to devices by checking if
self.instance.type != template_type and the template is active (or assigned to
devices) and raise serializers.ValidationError (same message text adapted to
"You cannot change the template type on an active template.") so type changes
like generic/vpn -> cert are also rejected and thus prevent missing
DeviceCertificate lifecycle actions; update the validation block that references
self.instance.type and template_type to perform this broader check and raise the
error.
In `@openwisp_controller/config/static/config/js/switcher.js`:
- Around line 47-62: The code duplicates show/hide logic for auto_cert_field;
collapse into a single branch that checks if val === "vpn" || val === "cert"
once, calls auto_cert_field.show() or auto_cert_field.hide() accordingly, and
inside the true branch update auto_cert_label.text(...) based on whether val ===
"vpn" or val === "cert"; remove the redundant earlier if/else block so only one
place mutates auto_cert_field and the label (refer to symbols auto_cert_field,
auto_cert_label, and the val variable).
---
Duplicate comments:
In `@openwisp_controller/config/api/serializers.py`:
- Around line 442-453: The organization-change branch that calls
instance.config.templates.clear() must also remove any stale DeviceCertificate
bindings like the _update_config path does: before calling
instance.config.templates.clear(), delete DeviceCertificate rows associated with
this instance and its config (the same cleanup _update_config performs), then
proceed to clear templates and call Config.enforce_required_templates with
raw_data_for_signal_handlers; locate the cleanup logic in _update_config and
replicate the DeviceCertificate deletion here so old certificate bindings cannot
survive an organization-only change.
🪄 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: 7bacd21d-939e-425e-8675-416099614e8c
📒 Files selected for processing (9)
docs/user/certificate-templates.rstopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/settings.pyopenwisp_controller/config/static/config/js/switcher.jsopenwisp_controller/config/tasks.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_template.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}
📄 CodeRabbit inference engine (Custom checks)
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}: Flag potential security vulnerabilities in code
Avoid unnecessary comments or docstrings for code that is already clear
Code formatting is compact and readable. Do not add excessive blank lines, especially inside function or method bodies
Flag unused or redundant code
Ensure variables, functions, classes, and files have descriptive and consistent names
New code must handle errors properly: log errors that cannot be resolved by the user with error level, log unusual conditions with warning level, log important background actions with info level, and provide user-facing messages for errors that the user can solve autonomously
Files:
openwisp_controller/config/settings.pyopenwisp_controller/config/static/config/js/switcher.jsopenwisp_controller/config/tasks.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/base/device_certificate.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/settings.pyopenwisp_controller/config/static/config/js/switcher.jsopenwisp_controller/config/tasks.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/base/device_certificate.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/settings.pyopenwisp_controller/config/static/config/js/switcher.jsopenwisp_controller/config/tasks.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/base/device_certificate.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/settings.pyopenwisp_controller/config/tasks.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/tests/test_template.py
**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
**/*.py: Mark user-facing strings for translation with Django i18n helpers in Django code
Avoid unnecessary blank lines inside function and method bodies
Be careful with authentication, authorization, queryset filtering, serializers, admin behavior, cache invalidation, signals, Celery tasks, and websocket updates in Django code
Preserve validation around templates, VPN/PKI material, SSH credentials, device commands, uploaded files, URLs, and subnet/IP data
Write comments and docstrings only when they explain why code is shaped a certain way, placing them before the relevant code block instead of scattering them inside it
Files:
openwisp_controller/config/settings.pyopenwisp_controller/config/tasks.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/tests/test_template.py
🧠 Learnings (6)
📚 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/tasks.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/base/device_certificate.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/settings.pyopenwisp_controller/config/tasks.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/base/device_certificate.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/settings.pyopenwisp_controller/config/tasks.pyopenwisp_controller/config/handlers.pyopenwisp_controller/config/api/serializers.pyopenwisp_controller/config/tests/test_device.pyopenwisp_controller/config/base/device_certificate.pyopenwisp_controller/config/tests/test_template.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_device.pyopenwisp_controller/config/tests/test_template.py
📚 Learning: 2026-06-07T12:07:24.608Z
Learnt from: stktyagi
Repo: openwisp/openwisp-controller PR: 1378
File: openwisp_controller/pki/tests/test_api.py:155-155
Timestamp: 2026-06-07T12:07:24.608Z
Learning: When reviewing Python test files in this repository, avoid recommending inline comments that explain or justify `assertNumQueries` (Django query count) expectations. Query counts can change frequently as implementations evolve, and inline explanations add maintenance burden; the expected count should be understandable without added comment blocks.
Applied to files:
openwisp_controller/config/tests/test_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:
openwisp_controller/config/tests/test_device.pyopenwisp_controller/config/tests/test_template.py
🔇 Additional comments (2)
docs/user/certificate-templates.rst (2)
34-35: Use the versioned docs image asset.This still points at the unversioned
docs/docs/certificate-template.pngpath; that tends to drift across release branches.
164-165: Correct the*_uuidvariable description.This variable is the generated certificate UUID, not the
DeviceCertificateUUID. The current wording will mislead template authors.
Made task safe against overlapping runs for the same device.
|
The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3). |
|
The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (2/3). |
|
The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (3/3). |
Checklist
Reference to Existing Issue
Closes #1356
Closes #1377
Closes #1357
Closes #1361
Closes #1358
Closes #1360
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