Skip to content

[feature] Introduced standalone certificate templates and device bindings#1378

Open
stktyagi wants to merge 27 commits into
gsoc26-x509-certificate-generator-templatesfrom
issues/1356-extend-abstract-template
Open

[feature] Introduced standalone certificate templates and device bindings#1378
stktyagi wants to merge 27 commits into
gsoc26-x509-certificate-generator-templatesfrom
issues/1356-extend-abstract-template

Conversation

@stktyagi
Copy link
Copy Markdown
Member

@stktyagi stktyagi commented May 26, 2026

Checklist

  • I have read the OpenWISP Contributing Guidelines.
  • I have manually tested the changes proposed in this pull request.
  • I have written new test cases for new code and/or updated existing tests for changes to existing code.
  • I have updated the documentation.

Reference to Existing Issue

Closes #1356

Description of Changes

This PR establishes the database architecture, UI, API and lifecycle for standalone X.509 certificate templates.

Closes #1356
Closes #1377
Closes #1357
Closes #1361
Closes #1358

Manual test plan

Setup

  • Go to PKI -> Certification Authorities and create two CAs: CA-1 and CA-2.
  • Go to PKI -> Certificates and create two certificates to act as blueprints:
  • Blueprint-1 (Must use CA-1)
  • Blueprint-2 (Must use CA-2)
  • Go to Devices and create a device (test-device).

Template Creation and Validation

  • Configuration -> Templates and click ADD TEMPLATE.
  • Set Type to Certificate.
    • Leave CA blank and try to save.
    • Expected Result: Validation error stating a CA is required.
  • Set CA to CA-1.
    • Set Blueprint to Blueprint-2 (which belongs to CA-2). Try to save.
    • When opening drop-down for blueprint you'll only see unassigned and unrevoked certificates.
    • Expected Result: Validation error stating the Blueprint must match the selected CA.
  • Change Blueprint to Blueprint-1. Name the template Active-Cert-Template. Save it.

Device Provisioning

  • Add configuration for test-device.
  • In the templates field, add Active-Cert-Template. Save.
  • Go to PKI -> Certificates.
  • Expected Result: You should see a brand new certificate automatically generated for test-device. Its status should be valid (not revoked).

Active Mutation Locks

  • Go back to Configuration -> Templates and edit Active-Cert-Template (which is now assigned to an active device).
  • Change the Type to Generic. Try to save.
  • Expected Result: Validation error: "You cannot change the template type from certificate on an active template."
  • Change the CA to CA-2. Try to save.
  • Expected Result: Validation error blocking the CA change.
  • Change the Blueprint to Blueprint-2 (ensure you also change the CA so they match, triggering the active lock). Try to save.
  • Expected Result: Validation error blocking the Blueprint change.

Revocation on Removal

  • Go to the Configuration for test-device.
  • Remove Active-Cert-Template entirely from the templates list. Save.
  • Go to PKI -> Certificates and locate the device's certificate.
  • Expected Result: The certificate should still exist in the database, but its status should now be marked as Revoked.
output.mp4

…1356

- Added 'cert' to TYPE_CHOICES.
- Introduced 'ca' and 'blueprint_cert' ForeignKeys with organization validation.
- Updated the clean() method to clear unneeded relations, require a CA for cert types, and validate that a blueprint certificate is not already assigned to a device.

Fixes #1356
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 761226be-2b41-45db-a9ed-0053d4b5bc3d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds a "cert" template type with CA and blueprint_cert relations, introduces an abstract DeviceCertificate model and a concrete swappable DeviceCertificate, wires AbstractConfig.device_certificates M2M with an m2m_changed handler, implements certificate auto-provisioning and revocation, updates admin/serializer/API surfaces and JS, provides production and sample migrations, and adds unit and Selenium tests covering validation, lifecycle, and regressions.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client/API
  participant Config as ConfigModel
  participant Template as TemplateModel
  participant DeviceCert as DeviceCertificate
  participant X509 as django-x509.Cert

  Client->>Config: assign cert Template (m2m add)
  Config->>DeviceCert: manage_device_certs (post_add) -> get_or_create DeviceCertificate
  DeviceCert->>Template: read ca, blueprint_cert, auto_cert
  DeviceCert->>X509: _auto_create_cert (clone blueprint or CA defaults)
  X509-->>DeviceCert: saved Cert instance assigned
  Client->>Config: unassign cert Template (m2m remove)
  Config->>DeviceCert: delete DeviceCertificate
  DeviceCert->>X509: post_delete -> revoke() if auto_cert
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • openwisp/openwisp-controller#1382: Implements overlapping DeviceCertificate + certificate-template changes including device_certificates M2M wiring, Template ca/blueprint_cert fields with unassigned-choice constraints, and validation/serializer behavior.

Suggested reviewers

  • nemesifier
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title '[feature] Introduced standalone certificate templates and device bindings' clearly describes the main change—adding certificate template support with device relationships.
Linked Issues check ✅ Passed The PR comprehensively addresses all linked issues: #1356 (AbstractTemplate extension with ca/blueprint_cert), #1377 (DeviceCertificate model), #1357 (admin UI), #1361 (REST API), and #1358 (lifecycle management).
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing certificate templates: model extensions, migrations, admin UI, API serializers, lifecycle handlers, and comprehensive test coverage—no extraneous changes detected.
Bug Fixes ✅ Passed This is a feature PR (not a bug-fix PR). Bugs found during implementation have regression tests.
Description check ✅ Passed PR description is mostly complete with all critical sections filled: checklist items checked, reference to issue #1356 provided, comprehensive description of changes and manual test plan included.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issues/1356-extend-abstract-template

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

❤️ Share

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

@stktyagi
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

✅ Actions performed

Review triggered.

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

@kilo-code-bot
Copy link
Copy Markdown

kilo-code-bot Bot commented May 26, 2026

Code Review Summary

Status: 1 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
openwisp_controller/config/base/template.py 301 The certificate-template validation change still lacks a targeted regression test
Other Observations (not in diff)

Previous findings on openwisp_controller/config/api/serializers.py and openwisp_controller/config/static/config/js/template_ui.js are resolved in this revision.

No additional issues in unchanged code.

Files Reviewed (5 files)
  • openwisp_controller/config/api/serializers.py - 0 issues
  • openwisp_controller/config/base/template.py - 1 issue
  • openwisp_controller/config/static/config/js/template_ui.js - 0 issues
  • openwisp_controller/config/tests/test_selenium.py - 0 issues
  • openwisp_controller/pki/tests/test_api.py - 0 issues

Fix these issues in Kilo Cloud


Reviewed by gpt-5.4-20260305 · 41,749 tokens

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":500,"request":{"method":"PATCH","url":"https://api.github.com/repos/openwisp/openwisp-controller/issues/comments/4548211157","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- review_stack_entry_start -->\n\n[![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/openwisp/openwisp-controller/pull/1378?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)\n\n<!-- review_stack_entry_end -->\n<!-- This is an auto-generated comment: review in progress by coderabbit.ai -->\n\n> [!NOTE]\n> Currently processing new changes in this PR. This may take a few minutes, please wait...\n> \n> <details>\n> <summary>⚙️ Run configuration</summary>\n> \n> **Configuration used**: Organization UI\n> \n> **Review profile**: ASSERTIVE\n> \n> **Plan**: Pro\n> \n> **Run ID**: `33bb61f8-c083-446e-8e45-44d753e7ff7b`\n> \n> </details>\n> \n> <details>\n> <summary>📥 Commits</summary>\n> \n> Reviewing files that changed from the base of the PR and between dc55622dfd09741ac51aad38afaaa206714ca875 and 25f1a213225299ecb5dc0ae4960630f68f8d8480.\n> \n> </details>\n> \n> <details>\n> <summary>📒 Files selected for processing (3)</summary>\n> \n> * `openwisp_controller/config/base/template.py`\n> * `openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py`\n> * `tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py`\n> \n> </details>\n> \n> ```ascii\n>  __________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________\n> < I've seen things you people wouldn't believe. Inefficient loops on fire off the shoulder of Orion. I've observed algorithms unfold in the dark near the Tannhäuser Gate, and watched data structures dissolve into the void of garbage collection. All those moments will be lost in my transient GPU cache, like tears in rain. >\n>  ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n>   \\\n>    \\   (\\__/)\n>        (•ㅅ•)\n>        /   づ\n> ```\n\n<!-- end of auto-generated comment: review in progress by coderabbit.ai -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing Touches</summary>\n\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-4548221491\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"6ba7b810-9dad-11d1-80b4-00c04fd430c8\", \"radioGroupId\": \"utg-output-choice-group-4548221491\"} -->   Commit unit tests in branch `issues/1356-extend-abstract-template`\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=openwisp/openwisp-controller&utm_content=1378)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->"},"request":{"retryCount":3,"signal":{},"retries":3,"retryAfter":16}}}

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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

📥 Commits

Reviewing files that changed from the base of the PR and between dc55622 and 25f1a21.

📒 Files selected for processing (3)
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • tests/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.py
  • openwisp_controller/config/base/template.py
  • openwisp_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.py
  • openwisp_controller/config/base/template.py
  • openwisp_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.py
  • openwisp_controller/config/base/template.py
  • openwisp_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.py
  • openwisp_controller/config/base/template.py
  • openwisp_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.py
  • openwisp_controller/config/base/template.py
  • openwisp_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.py
  • openwisp_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.py
  • openwisp_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!

Comment thread openwisp_controller/config/base/template.py Outdated
@openwisp-companion
Copy link
Copy Markdown

{
  "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 Analysis

Hello @stktyagi,
(Analysis for commit 25f1a21)

  • Migrations Out of Sync: The CI build failed because the database migrations are not up-to-date. This is indicated by the error message "Migrations check failed! Models' changes not migrated, please run './manage.py makemigrations' to solve the issue!".

Fix: Run ./manage.py makemigrations to generate the necessary migration files and then commit them to the repository.

stktyagi added 2 commits May 27, 2026 01:58
Updated previous tests and added new tests for implemetation.

Fixes #1356
Fixed help text for auto cert and updated migration files
@openwisp-companion
Copy link
Copy Markdown

Migrations Check Failed

Hello @stktyagi,
(Analysis for commit a795e09)

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:
Run the following command to apply the pending migrations:

./manage.py makemigrations

@openwisp-companion
Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3).

@stktyagi
Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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

📥 Commits

Reviewing files that changed from the base of the PR and between dc55622 and b946d26.

📒 Files selected for processing (5)
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/pki/tests/test_api.py
  • tests/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.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/base/template.py
  • openwisp_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.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/base/template.py
  • openwisp_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.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/base/template.py
  • openwisp_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.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/base/template.py
  • openwisp_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.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/base/template.py
  • openwisp_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.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/base/template.py
  • openwisp_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.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/base/template.py
  • openwisp_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

Comment thread openwisp_controller/config/base/template.py Outdated
Comment thread openwisp_controller/config/base/template.py Outdated
@openwisp-companion
Copy link
Copy Markdown

Migrations Check Failed

Hello @stktyagi,
(Analysis for commit b946d26)

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:
Run the following command to generate the missing migrations:

./manage.py makemigrations

stktyagi and others added 2 commits May 27, 2026 09:34
Validate cert relations only inside the cert branch and Only coerce missing cert configs, not every falsy value.

Fixes #1356
Comment thread openwisp_controller/config/base/template.py
Added test for the validation branch that now skips ca / blueprint_cert checks for non-cert templates

Fixes #1356
@coveralls
Copy link
Copy Markdown

coveralls commented May 27, 2026

Coverage Status

Coverage is 98.404%issues/1356-extend-abstract-template into gsoc26-x509-certificate-generator-templates. No base build found for gsoc26-x509-certificate-generator-templates.

Fixed line too long flake error

Fixes #1356
…1377

Implemented the DeviceCertificate M2M through-model to act as a strict relational bridge between Config, Template, and django_x509.Cert.

Fixes #1377
@stktyagi stktyagi added the gsoc Part of a Google Summer of Code project label May 28, 2026
Updated test by joining the list of strings into one sentence.

Fixes #1377
@stktyagi stktyagi changed the title [feature] Extended Template model for standalone X.509 certificates #1356 [feature] Introduced standalone certificate templates and device bindings May 29, 2026
Copy link
Copy Markdown
Member

@Aryamanz29 Aryamanz29 left a comment

Choose a reason for hiding this comment

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

Good progress! @stktyagi - requested changes below:

Tip: also, don't hesitate to add one-liner comments to complex if-else ladders, or abstract them out into small functions/methods for reusability. It helps us follow the code more easily and keeps things maintainable!

Automatically creates and assigns a client x509 certificate
using Blueprint cloning and custom hardware OID injection.
"""
import copy
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

could you please move inline imports to top of the file 🙏

Comment on lines +52 to +56
def save(self, *args, **kwargs):
"""Performs automatic provisioning if ``auto_cert`` is True."""
if self.auto_cert and not self.cert:
self._auto_x509()
super().save(*args, **kwargs)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

_auto_create_cert is not transactional ^

cert.save() runs, then super().save() (the DeviceCertificate). If the second save raises (constraint, signal handler error), you've leaked a Cert row with no DeviceCertificate pointing at it. Should be wrapped in transaction.atomic() eg:

from django.db import transaction

def save(self, *args, **kwargs):
    with transaction.atomic():
        if self.auto_cert and not self.cert:
            self._auto_x509()
        super().save(*args, **kwargs)

Comment on lines +111 to +125
# inject MAC and UUID as custom OIDs, prerequisite: #228 in django-x509
# mac_oid = "1.3.6.1.4.1.65901.1"
# uuid_oid = "1.3.6.1.4.1.65901.2"
# extensions.extend([
# {
# "oid": mac_oid,
# "value": f"ASN1:UTF8:string:{device.mac_address}",
# "critical": False
# },
# {
# "oid": uuid_oid,
# "value": f"ASN1:UTF8:string:{device.id}",
# "critical": False
# }
# ])
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

^ Dead commented-out code committed

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

this is blocked by openwisp/django-x509#228 so i commented it out to make it easier to implement later and just uncomment. If that seems better i can remove it for now.

Comment on lines +1248 to +1274
# uncomment after #228 in django-x509
# with self.subTest("Injects custom MAC and UUID OIDs"):
# extensions = generated_cert.extensions
# mac_oid = "1.3.6.1.4.1.65901.1"
# uuid_oid = "1.3.6.1.4.1.65901.2"
# mac_ext = next(
# (
# ext
# for ext in extensions
# if ext.get("oid") == mac_oid
# or ext.get("name") == mac_oid
# ),
# None,
# )
# uuid_ext = next(
# (
# ext
# for ext in extensions
# if ext.get("oid") == uuid_oid
# or ext.get("name") == uuid_oid
# ),
# None,
# )
# self.assertIsNotNone(mac_ext, "MAC OID extension missing")
# self.assertIn(device.mac_address, mac_ext["value"])
# self.assertIsNotNone(uuid_ext, "UUID OID extension missing")
# self.assertIn(str(device.id), uuid_ext["value"])
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Dead commented-out code committed

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

same here, this test is to verify custom oid implementation currently blocked by openwisp/django-x509#228

Comment on lines +506 to +535
def manage_device_certs(cls, sender, instance, action, pk_set, **kwargs):
"""
Syncs DeviceCertificate objects when templates are added/removed
"""
if instance._state.adding or action not in [
"post_add",
"post_remove",
"post_clear",
]:
return
if action == "post_clear":
if instance.is_deactivating_or_deactivated():
instance.devicecertificate_set.all().delete()
return
if isinstance(pk_set, set):
template_model = cls.get_template_model()
templates = template_model.objects.filter(pk__in=list(pk_set)).order_by(
"created"
)
else:
templates = pk_set
if len(pk_set) != templates.filter(required=True).count():
instance.devicecertificate_set.exclude(
template_id__in=instance.templates.values_list("id", flat=True)
).delete()
if action == "post_add":
for template in templates.filter(type="cert"):
instance.devicecertificate_set.get_or_create(
template=template, defaults={"auto_cert": template.auto_cert}
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can you please add a code comment to explain this if-else ladder in manage_device_certs? 🙏

Comment on lines +262 to +265
if (
current.ca_id != self.ca_id
or current.blueprint_cert_id != self.blueprint_cert_id
):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we only blocks ca_id / blueprint_cert_id mutation here. If a user keeps ca the same but changes type from cert -> generic, _clean_cert_template then sets ca = None and blueprint_cert = None on the active template - bypassing the protection and leaving DeviceCertificate rows that won't get cleaned up by the template save (only m2m signals clean them). Either block type-change-off-cert on active templates or trigger the cert cleanup explicitly.

Comment on lines +52 to +61
extra_kwargs = {
"blueprint_cert": {
"error_messages": {
"does_not_exist": _(
"This certificate does not exist or is already "
"assigned to an active device."
)
}
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Misleading error message^

does_not_exist message says "already assigned to an active device" but the queryset filter (get_unassigned_certs()) excludes any cert with a DeviceCertificate, regardless of whether the device is active/deactivated. Either tighten the filter or relax the message.

Copy link
Copy Markdown
Member Author

@stktyagi stktyagi May 31, 2026

Choose a reason for hiding this comment

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

Thank you for this one, let me share a bit of context on why I did that in the first place:
If a user tries to assign a certificate that is already in use, DRF falls back to its native, generic relational error:
{'blueprint_cert': [ErrorDetail(string='Invalid pk "37" - object does not exist.', code='does_not_exist')]} so to avoid unhelpful error message I added that but you are right and we can improve the wording.
I will update the string to:
"This certificate does not exist or is already assigned to a device configuration profile."
This keeps our helpful error override intact while making the text technically accurate. If you have any other suggestion to do this better I'll appreciate that 🙏

Comment on lines +85 to +88
def validate(self, data):
"""
Explicitly validate certificate template fields and locks for the API.
"""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can you please add a code comment below for each if-else ladder in validate()? 🙏

@openwisp-companion
Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3).

- Wrap DeviceCertificate.save() in transaction.atomic() to ensure
  automatic X.509 provisioning does not leak orphaned rows on failure.
- Add validation to block changing the template type away from 'cert'
  on templates currently assigned to active devices.
@stktyagi
Copy link
Copy Markdown
Member Author

stktyagi commented Jun 1, 2026

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
openwisp_controller/config/base/config.py (1)

518-522: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

post_clear still leaks stale DeviceCertificate rows on real clears.

For active configs this branch returns without any reconciliation, so any authoritative templates.clear() path leaves old devicecertificate_set rows behind. DeviceDetailSerializer.update() still does exactly that during organization changes, and direct config.templates.clear() has the same effect, so certificates can remain linked and unreconciled even though their templates are no longer assigned. Please add an explicit cleanup path for non-reorder clears, or pass enough context to distinguish sortedm2m reordering from a real clear.

🤖 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/config.py` around lines 518 - 522, The
post_clear branch currently only deletes DeviceCertificate rows when
instance.is_deactivating_or_deactivated() is true, which allows real clears
(e.g., Config.templates.clear() or DeviceDetailSerializer.update() during org
changes) to leak stale devicecertificate_set entries; modify the handler for
action == "post_clear" in the config cleanup logic so that it also performs an
explicit reconciliation/cleanup of instance.devicecertificate_set for
non-deactivate clears (or use passed context to detect a sortedm2m reorder vs a
true clear) — specifically, update the post_clear logic that checks
instance.is_deactivating_or_deactivated() to also remove or reconcile
certificates when templates are cleared (and/or accept a flag from
DeviceDetailSerializer.update()/templates.clear() to indicate a real clear) so
stale DeviceCertificate rows are deleted.
🤖 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 40-44: get_unassigned_certs() currently builds assigned_cert_ids
from DeviceCertificate.objects.values_list("cert_id", flat=True) which can
include NULL and break the exclude(id__in=...) query; update the query to omit
NULL cert IDs (e.g., use
DeviceCertificate.objects.exclude(cert_id__isnull=True).values_list("cert_id",
flat=True) or .filter(cert_id__isnull=False).values_list(...)) so
assigned_cert_ids contains only real IDs before using it in
Cert.objects.exclude(id__in=assigned_cert_ids) in get_unassigned_certs().

---

Duplicate comments:
In `@openwisp_controller/config/base/config.py`:
- Around line 518-522: The post_clear branch currently only deletes
DeviceCertificate rows when instance.is_deactivating_or_deactivated() is true,
which allows real clears (e.g., Config.templates.clear() or
DeviceDetailSerializer.update() during org changes) to leak stale
devicecertificate_set entries; modify the handler for action == "post_clear" in
the config cleanup logic so that it also performs an explicit
reconciliation/cleanup of instance.devicecertificate_set for non-deactivate
clears (or use passed context to detect a sortedm2m reorder vs a true clear) —
specifically, update the post_clear logic that checks
instance.is_deactivating_or_deactivated() to also remove or reconcile
certificates when templates are cleared (and/or accept a flag from
DeviceDetailSerializer.update()/templates.clear() to indicate a real clear) so
stale DeviceCertificate rows are deleted.
🪄 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: 4b38b811-4f6b-4a60-91c5-a30808887443

📥 Commits

Reviewing files that changed from the base of the PR and between 897429b and 212b839.

📒 Files selected for processing (16)
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/static/config/js/template_ui.js
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/pki/tests/test_api.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • tests/openwisp2/sample_config/models.py
  • tests/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). (11)
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.1.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:

  • tests/openwisp2/settings.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/static/config/js/template_ui.js
  • openwisp_controller/config/admin.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • tests/openwisp2/sample_config/migrations/0010_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/settings.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/static/config/js/template_ui.js
  • openwisp_controller/config/admin.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • tests/openwisp2/sample_config/migrations/0010_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/settings.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/static/config/js/template_ui.js
  • openwisp_controller/config/admin.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • tests/openwisp2/sample_config/migrations/0010_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/settings.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
🧠 Learnings (4)
📚 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/settings.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • tests/openwisp2/sample_config/migrations/0010_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/models.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_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/models.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.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 (16)
tests/openwisp2/settings.py (1)

275-275: LGTM!

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

1-25: LGTM!

openwisp_controller/config/admin.py (2)

118-126: LGTM!


1067-1068: LGTM!

Also applies to: 1083-1083, 1090-1091, 1103-1103

openwisp_controller/config/apps.py (2)

72-72: LGTM!


98-102: LGTM!

Also applies to: 124-128

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

772-805: LGTM!

openwisp_controller/config/base/device_certificate.py (4)

13-52: LGTM!


54-84: LGTM!


86-151: LGTM!


153-163: LGTM!

tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py (1)

13-150: LGTM!

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

155-155: ⚡ Quick win

Confirm the delete query bump matches Django’s related-object handling

openwisp_controller/pki/tests/test_api.py now expects more queries for deletes (test_ca_delete_api: 6, test_cert_delete_api: 8). With the new relations:

  • DeviceCertificate.certdjango_x509.Cert uses on_delete=CASCADE
  • Template.blueprint_certdjango_x509.Cert uses on_delete=SET_NULL
  • DeviceCertificate has a post_delete hook that can call instance.cert.revoke() when auto_cert=True

That explains why cert deletion can add more work than CA deletion (CASCADE on DeviceCertificate.cert plus SET_NULL updates for Template.blueprint_cert). Since pki/tests/test_api.py doesn’t explicitly create Template/DeviceCertificate objects, the increase should be coming from Django’s delete collector existence checks/SET_NULL handling—not per-row churn.

Check the actual SQL for these two DELETE endpoints and confirm the extra queries are constant-time (no N+1) even when multiple related DeviceCertificate/Template rows exist.

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

25-29: LGTM!

Also applies to: 69-92, 128-129, 251-341, 351-355, 369-370, 378-378

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

33-33: LGTM!

Also applies to: 742-742, 1333-1540

openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py (1)

1-152: LGTM!

Comment thread openwisp_controller/config/base/template.py Outdated
Fixed NULL handling in get_unassigned_certs() to avoid empty blueprint_cert choices
@openwisp-companion
Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3).

@openwisp-companion
Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (2/3).

Updated get_unassigned_certs to include revoked certificates
@openwisp-companion
Copy link
Copy Markdown

Dependency Conflict in pip install

Hello @stktyagi,
(Analysis for commit ef90a6c)

There is a dependency conflict between openwisp-controller and openwisp-ipam.
openwisp-controller requires django-reversion~=6.1.0, while openwisp-ipam requires django-reversion~=6.2.0.

To resolve this, you should update the version specifiers in your requirements.txt or setup.py to be compatible. For example, you could try loosening the range for django-reversion to allow either version, or align them to a common compatible version.

@nemesifier
Copy link
Copy Markdown
Member

@coderabbitai any outstanding issue?

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 2, 2026

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get all review comments on PR `#1378`
gh pr view 1378 --repo openwisp/openwisp-controller --json reviews,comments | jq '.'

Length of output: 229590


🏁 Script executed:

#!/bin/bash
# Get review comments (inline) on PR `#1378`
gh api repos/openwisp/openwisp-controller/pulls/1378/comments \
  --paginate \
  | jq '[.[] | select(.user.login == "coderabbitai[bot]") | {id: .id, path: .path, line: .line, body: .body[0:300]}]'

Length of output: 21777


🏁 Script executed:

#!/bin/bash
# Get all issue comments on PR `#1378` to find CodeRabbit summary/walkthrough
gh api repos/openwisp/openwisp-controller/issues/1378/comments \
  --paginate \
  | jq '[.[] | select(.user.login == "coderabbitai[bot]") | {id: .id, created_at: .created_at, body: .body[0:500]}]'

Length of output: 3351



@nemesifier There are 2 outstanding issues from the latest review (commit 212b8394):


❌ Pre-merge failure (blocker)

get_unassigned_certs() — missing cert_id__isnull=False filter

In openwisp_controller/config/base/template.py, the function queries DeviceCertificate.objects.values_list("cert_id", flat=True) without filtering out NULL values. When a DeviceCertificate exists with no cert yet assigned (cert=NULL), the resulting exclude(id__in=[..., None, ...]) query on Cert breaks (Django emits WHERE id NOT IN (..., NULL, ...), which returns no rows due to SQL NULL semantics), so all certs become invisible as blueprint choices. No regression test covers this path.

Fix:

# change:
assigned_cert_ids = DeviceCertificate.objects.values_list("cert_id", flat=True)
# to:
assigned_cert_ids = DeviceCertificate.objects.filter(
    cert_id__isnull=False
).values_list("cert_id", flat=True)

A regression test should create a DeviceCertificate with cert=None and assert that blueprint_cert choices still appear.


⚠️ Major lifecycle issue

post_clear branch leaks stale DeviceCertificate rows

In openwisp_controller/config/base/config.py (around line 518), the post_clear handler only deletes DeviceCertificate rows when instance.is_deactivating_or_deactivated() is True. For all other real-clear paths (e.g. config.templates.clear() from DeviceDetailSerializer.update() during organization changes, or sorted-M2M reorder → clear → re-add), the branch returns early, leaving stale rows that won't be reconciled even if their templates are not re-added in post_add.


Everything else from earlier rounds has been addressed: the auto_cert help text, org-relation validation ordering, if self.config is None fix, template_ui.js VPN admin guard, dynamic ValidationError field mapping, blueprint_cert mutation block test, and revoke-before-delete assertion. The commented-out OID code and cert-renewal propagation are deferred to separate issues by agreement.

stktyagi and others added 2 commits June 3, 2026 14:20
Updated post_clear to prevent stale DeviceCertificate rows
@stktyagi
Copy link
Copy Markdown
Member Author

stktyagi commented Jun 3, 2026

@coderabbitai full review or resolve

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 3, 2026

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check current state of get_unassigned_certs in template.py
rg -n "get_unassigned_certs|cert_id__isnull|assigned_cert_ids" openwisp_controller/config/base/template.py

echo "---"
# Check post_clear branch in config.py
rg -n "post_clear|post_add|post_remove|manage_device_certs|is_deactivating" openwisp_controller/config/base/config.py -A 5

echo "---"
# List open CodeRabbit inline review comments
gh api repos/openwisp/openwisp-controller/pulls/1378/comments \
  --paginate \
  | jq '[.[] | select(.user.login == "coderabbitai[bot]") | {id: .id, path: .path, line: .line, body: .body[0:200]}]'

Length of output: 23572



@stktyagi Both blockers I flagged in the reply to @nemesifier have been addressed in the current branch:

  1. get_unassigned_certs() NULL filtercert_id__isnull=False is now present (lines 43–44 of template.py).
  2. post_clear stale rows — the else branch with transaction.on_commit(...) now handles the non-deactivating clear path in manage_device_certs (lines 522–525 of config.py).

There are still several open inline comments from the most recent review rounds (ranging from nitpicks to majors). Triggering a fresh full review now to get an up-to-date assessment of everything remaining.

✅ Action performed

Full review finished.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

359-375: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear vpn before validating its organization scope.

self._validate_org_relation("vpn") still runs before Lines 372-375 null out self.vpn. A save that changes a template from "vpn" to "cert"/"generic" and moves it to another organization can still fail on the stale VPN relation even though this code would clear it later in the same clean() call. Move the VPN org check inside the self.type == "vpn" branch, or run it after the non-VPN cleanup.

🐛 Minimal fix
-        self._validate_org_relation("vpn")
         if not self.default_values:
             self.default_values = {}
@@
-        if self.type == "vpn" and not self.vpn:
-            raise ValidationError(
-                {"vpn": _('A VPN must be selected when template type is "VPN"')}
-            )
-        elif self.type != "vpn":
+        if self.type == "vpn":
+            self._validate_org_relation("vpn")
+            if not self.vpn:
+                raise ValidationError(
+                    {"vpn": _('A VPN must be selected when template type is "VPN"')}
+                )
+        else:
             self.vpn = None
             if self.type != "cert":
                 self.auto_cert = False
🤖 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 359 - 375, The VPN
organization relation check is performed too early
(self._validate_org_relation("vpn")) before the code clears self.vpn for non-VPN
templates, causing stale VPN relations to block saves; move the VPN-specific
validation so it only runs when the template type is "vpn" (i.e., call
self._validate_org_relation("vpn") inside the self.type == "vpn" branch) or
alternatively run it after the block that sets self.vpn = None/auto_cert =
False; update the logic around the type checks in the clean()/validation path
(references: _validate_cert_template_changes, _clean_cert_template,
_validate_org_relation("vpn"), and the conditional branches using self.type) so
non-VPN templates have their vpn cleared before any organization-relation
validation runs.
openwisp_controller/config/api/serializers.py (1)

74-83: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use the instance type in validate_config() for partial updates.

On PATCH requests that only send config, self.initial_data.get("type") is None, so existing "generic" templates miss this field-level check and fall back to the model-level non-field error instead. Please fall back to self.instance.type here as well.

💡 Minimal fix
-        template_type = self.initial_data.get("type")
+        template_type = self.initial_data.get(
+            "type", getattr(self.instance, "type", None)
+        )
🤖 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 74 - 83,
validate_config currently only reads template_type from self.initial_data, which
is None on PATCH requests that only include config; update validate_config to
fall back to the serializer instance's type when initial_data lacks it (e.g.,
template_type = self.initial_data.get("type") or (getattr(self.instance, "type",
None))) and ensure you handle self.instance being None before accessing its type
so the empty-{} check for "generic" templates still runs on partial updates.
♻️ Duplicate comments (1)
openwisp_controller/config/api/serializers.py (1)

104-107: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep the active-template lock keyed to the original request fields.

Lines 105-107 always inject ca=None and blueprint_cert=None for non-cert updates, so the guard at Lines 122-126 becomes true even for unrelated PATCHes. That reintroduces the extra Config.objects.filter(...).exists() query on every update of an assigned non-cert template. Capture whether type/ca/blueprint_cert were actually submitted before normalizing data, and use that flag for the lock check.

♻️ Minimal fix
         template_type = data.get("type", getattr(self.instance, "type", "generic"))
+        protected_fields_supplied = any(
+            field in data for field in ["type", "ca", "blueprint_cert"]
+        )
         ca = data.get("ca", getattr(self.instance, "ca", None))
         blueprint_cert = data.get(
             "blueprint_cert", getattr(self.instance, "blueprint_cert", None)
         )
@@
         if (
             self.instance
             and self.instance.pk
-            and ("ca" in data or "blueprint_cert" in data or "type" in data)
+            and protected_fields_supplied
         ):

Also applies to: 121-133

🤖 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 104 - 107, The
serializer currently unconditionally sets data["ca"]=None and
data["blueprint_cert"]=None when template_type != "cert", which causes the
active-template lock check (the Config.objects.filter(...).exists() branch) to
trigger for unrelated PATCHes; instead, detect whether the incoming request
actually included any of the template key fields before normalization (e.g.,
check for presence of "type", "ca", or "blueprint_cert" in the input payload),
store a boolean flag like submitted_template_fields, then perform the
normalization (setting to None) but only run the lock existence check when
submitted_template_fields is True; apply the same presence-flag logic for the
other similar block around the 121-133 region so the extra DB query runs only
when template-related fields were actually submitted.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openwisp_controller/config/tests/test_template.py`:
- Around line 1335-1375: Add a new test that covers the partial-replacement
path: when config.templates.set([...], clear=True) removes a cert template but
leaves other templates selected, ensure the existing DeviceCertificate is not
deleted/revoked; implement it alongside
test_cert_template_reorder_does_not_revoke as e.g.
test_cert_template_partial_replace_does_not_revoke, create
org/ca/templates/device/config, call config.templates.set to establish a cert
then call config.templates.set with a new list that drops the cert-template
while keeping other templates, and after the operation assert the
DeviceCertificate (config.devicecertificate_set.first()) still has the same pk,
the underlying cert pk is unchanged, and cert.revoked is False to exercise the
post_clear / transaction.on_commit branch in
openwisp_controller/config/base/config.py.

---

Outside diff comments:
In `@openwisp_controller/config/api/serializers.py`:
- Around line 74-83: validate_config currently only reads template_type from
self.initial_data, which is None on PATCH requests that only include config;
update validate_config to fall back to the serializer instance's type when
initial_data lacks it (e.g., template_type = self.initial_data.get("type") or
(getattr(self.instance, "type", None))) and ensure you handle self.instance
being None before accessing its type so the empty-{} check for "generic"
templates still runs on partial updates.

In `@openwisp_controller/config/base/template.py`:
- Around line 359-375: The VPN organization relation check is performed too
early (self._validate_org_relation("vpn")) before the code clears self.vpn for
non-VPN templates, causing stale VPN relations to block saves; move the
VPN-specific validation so it only runs when the template type is "vpn" (i.e.,
call self._validate_org_relation("vpn") inside the self.type == "vpn" branch) or
alternatively run it after the block that sets self.vpn = None/auto_cert =
False; update the logic around the type checks in the clean()/validation path
(references: _validate_cert_template_changes, _clean_cert_template,
_validate_org_relation("vpn"), and the conditional branches using self.type) so
non-VPN templates have their vpn cleared before any organization-relation
validation runs.

---

Duplicate comments:
In `@openwisp_controller/config/api/serializers.py`:
- Around line 104-107: The serializer currently unconditionally sets
data["ca"]=None and data["blueprint_cert"]=None when template_type != "cert",
which causes the active-template lock check (the
Config.objects.filter(...).exists() branch) to trigger for unrelated PATCHes;
instead, detect whether the incoming request actually included any of the
template key fields before normalization (e.g., check for presence of "type",
"ca", or "blueprint_cert" in the input payload), store a boolean flag like
submitted_template_fields, then perform the normalization (setting to None) but
only run the lock existence check when submitted_template_fields is True; apply
the same presence-flag logic for the other similar block around the 121-133
region so the extra DB query runs only when template-related fields were
actually submitted.
🪄 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: cdcf57db-c535-46f4-aaf1-4830eecceb77

📥 Commits

Reviewing files that changed from the base of the PR and between 1fdd9d1 and bca3809.

📒 Files selected for processing (16)
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/static/config/js/template_ui.js
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/pki/tests/test_api.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • tests/openwisp2/sample_config/models.py
  • tests/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). (11)
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.12 | 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.11 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.2.0
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}

📄 CodeRabbit inference engine (Custom checks)

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

Files:

  • openwisp_controller/config/static/config/js/template_ui.js
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/models.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/base/config.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/base/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/static/config/js/template_ui.js
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/models.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/base/config.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/base/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/static/config/js/template_ui.js
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/models.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/base/config.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/base/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/tests/test_selenium.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/models.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/base/config.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/base/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/tests/test_selenium.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/models.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/base/config.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/base/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/tests/test_selenium.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/base/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/tests/test_selenium.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/base/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/tests/test_selenium.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/models.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/base/config.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/base/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 (17)
openwisp_controller/config/admin.py (1)

120-125: LGTM!

Also applies to: 1067-1068, 1083-1083, 1090-1091, 1103-1103

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

1-24: LGTM!

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

66-72: LGTM!

Also applies to: 505-554

openwisp_controller/config/apps.py (1)

72-72: LGTM!

Also applies to: 98-128

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

33-33: LGTM!


742-742: LGTM!


1333-1351: LGTM!


1352-1368: LGTM!


1369-1389: LGTM!


1390-1414: LGTM!


1415-1447: LGTM!


1448-1482: LGTM!


1483-1503: LGTM!


1504-1540: LGTM!

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

772-804: LGTM!

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

155-155: LGTM!


275-275: LGTM!

Comment thread openwisp_controller/config/tests/test_template.py
A companion regression for “old cert removed, other templates kept”
@stktyagi
Copy link
Copy Markdown
Member Author

stktyagi commented Jun 3, 2026

@coderabbitai resolve

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 3, 2026

✅ Actions performed

Comments resolved and changes approved.

@openwisp-companion
Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3).

@openwisp-companion
Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (2/3).

Copy link
Copy Markdown
Member

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

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

@stktyagi start drafting a bit of docs, nothing major but start drafting so we keep on iterating on it in the next PRs.

Copy link
Copy Markdown
Member

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

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

GPT 5.5 assisted review.

Findings

Medium

docs/user/settings.rst:310 and docs/user/rest-api.rst:1114

The code adds the new cert template type and allows auto_cert for certificate templates, but the user docs still say auto_cert is valid only for VPN templates and the template API docs still list only vpn or generic as type examples. This will confuse users trying to use the new feature from the admin or API, and it also leaves the current maintainer request for minimal docs unresolved.

I would add a small section to docs/user/templates.rst explaining certificate templates, the required CA, optional blueprint certificate, and what happens when the template is assigned or removed. I would also update docs/user/settings.rst and docs/user/rest-api.rst so cert is documented as a valid template type.

Notes

I do not see a remaining blocking code issue in the current branch. The two CodeRabbit blockers about get_unassigned_certs() NULL handling and stale DeviceCertificate rows after post_clear are addressed in the latest code and covered by regression tests.

stktyagi and others added 2 commits June 4, 2026 15:06
Uncommented code for custom oid extensions used for mac address and uuid support in certificate with relevant test

Fixes #1377
@openwisp-companion
Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3).

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

Labels

enhancement gsoc Part of a Google Summer of Code project

Projects

Development

Successfully merging this pull request may close these issues.

4 participants