Skip to content

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

Open
stktyagi wants to merge 36 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 36 commits into
gsoc26-x509-certificate-generator-templatesfrom
issues/1356-extend-abstract-template

Conversation

@stktyagi

@stktyagi stktyagi commented May 26, 2026

Copy link
Copy Markdown
Member

Checklist

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

Reference to Existing Issue

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

Description of Changes

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

Manual test plan

Setup

  • 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.

Context Configuration Injection

  • Go to Configuration -> Templates, open Active-Cert-Template, and copy its UUID from the URL bar (removing the dashes so it is a 32-character hex string).

  • In the JSON configuration editor for the template, add a configuration block that references the certificate's UUID variables:

    {
        "files": [
            {
                "path": "{{ cert_<uuid>_path }}",
                "mode": "0600",
                "contents": "{{ cert_<uuid>_pem }}"
            }
        ]
    }
    

    (Note: Replace <uuid> with the actual 32-character hex string of the template).

  • Click Save.

  • Go back to the Configuration page for test-device (which has this template assigned) and click the Preview configuration button.

  • Expected Result: The variables should be successfully resolved. In the preview, you should see the generated path (e.g., /etc/x509/cert-<uuid>.pem) and the literal -----BEGIN CERTIFICATE----- text instead of the raw {{ }} template tags.

output.mp4

…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

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

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: 8328749a-3532-46c0-98e8-84237dbf5b09

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 introduces a complete X.509 certificate template system for OpenWISP. It extends AbstractTemplate with a "cert" type, adds certificate authority (ca) and optional blueprint_cert fields, and implements a DeviceCertificate through model to track device-to-template certificate assignments. The system automatically generates, provisions, and revokes client certificates based on template assignment, with customizable common names, device-specific MAC/UUID OID extensions, and optional blueprint property cloning. All changes are integrated into the admin UI (with type-dependent field visibility), REST API (with validation and mutation protection), configuration context (exposing PEM and private keys), and database migrations for both production and sample apps, backed by comprehensive test coverage including validation, lifecycle, atomicity, and API scenarios.

Sequence Diagram(s)

sequenceDiagram
  participant Admin/API
  participant AbstractTemplate
  participant AbstractConfig
  participant manage_device_certs
  participant AbstractDeviceCertificate
  participant X509Cert
  
  Admin/API->>AbstractTemplate: save cert template with ca
  AbstractTemplate->>AbstractTemplate: clean() validates ca & blueprint_cert
  Admin/API->>AbstractConfig: attach to device config
  AbstractConfig->>manage_device_certs: m2m_changed post_add
  manage_device_certs->>AbstractDeviceCertificate: create(config, template, auto_cert=True)
  AbstractDeviceCertificate->>AbstractDeviceCertificate: save() calls _auto_x509()
  AbstractDeviceCertificate->>X509Cert: create client cert with device CN & OIDs
  X509Cert->>AbstractDeviceCertificate: assign to cert field
  AbstractDeviceCertificate->>AbstractConfig: device context now includes cert PEM/key
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • nemesifier
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title follows the required format [type] with 'feature' prefix and clearly summarizes the main change: introducing standalone certificate templates and device bindings.
Description check ✅ Passed The PR description includes all required template sections: completed checklist, reference to multiple linked issues, detailed description of changes, and comprehensive manual test plan.
Linked Issues check ✅ Passed The code changes comprehensively implement all objectives from linked issues: #1356 (extend AbstractTemplate), #1377 (DeviceCertificate model), #1357 (admin UI), #1361 (API integration), #1358 (lifecycle management), and #1360 (context injection).
Out of Scope Changes check ✅ Passed All changes are directly scoped to the linked issues. Query count adjustments in test files are necessary housekeeping; no unrelated features or refactoring were introduced.
Bug Fixes ✅ Passed Bug rooted in NULL SQL semantics for blueprint filtering: get_unassigned_certs now filters DeviceCertificate.cert_id__isnull=False, with regression test test_get_unassigned_certs_with_null_device_c...

✏️ 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

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor
✅ 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

kilo-code-bot Bot commented May 26, 2026

Copy link
Copy Markdown

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

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

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}}}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openwisp_controller/config/base/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

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

coveralls commented May 27, 2026

Copy link
Copy Markdown

Coverage Status

Coverage is 98.443%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
@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 tests with increased number of queries to pass CI
@stktyagi

stktyagi commented Jun 7, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

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

⚠️ Outside diff range comments (1)
openwisp_controller/config/base/template.py (1)

362-367: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Only normalize missing default_values, not every falsy payload.

if not self.default_values rewrites [], "", and False to {}, so invalid JSON values bypass the object-type validation on Line 364 instead of being rejected.

🐛 Minimal fix
-        if not self.default_values:
+        if self.default_values is None:
             self.default_values = {}
         if not isinstance(self.default_values, dict):
             raise ValidationError(
                 {"default_values": _("the supplied value is not a JSON object")}
             )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openwisp_controller/config/base/template.py` around lines 362 - 367, The code
currently treats any falsy value (e.g. [], "", False) as missing and replaces it
with {}, bypassing the subsequent dict type check; change the normalization to
only set self.default_values = {} when it is None (use "if self.default_values
is None:") and keep the existing isinstance(self.default_values, dict)
ValidationError for all other non-dict values so []/""/False are rejected;
reference self.default_values and the ValidationError raised to locate and
update the logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/user/certificate-templates.rst`:
- Around line 51-54: Rename the misleading UI label "Automatic tunnel
provisioning" to "Automatic certificate provisioning" in the docs; update the
:guilabel:`Automatic tunnel provisioning` entry (the one showing the auto_cert
field) to :guilabel:`Automatic certificate provisioning` and ensure the
accompanying description still references the auto_cert option and that wording
consistently describes X.509 certificate provisioning rather than VPN/tunnel
certificates (adjust any nearby sentences that mention "tunnel" to "certificate"
as needed).
- Around line 116-119: The RST list for fields that cannot be changed is
currently on a single line; split it into a proper reStructuredText bulleted
list so each item is on its own line and properly indented, preserving the
:guilabel:`Type`, :guilabel:`Certificate Authority`, and :guilabel:`Blueprint
Certificate` roles and inline code (``cert``) markup; replace the single-line
sequence with a vertical list (e.g., using hyphens or asterisks) so each of
those three items becomes its own list item and the surrounding sentence
punctuation/layout is adjusted accordingly.

In `@openwisp_controller/config/api/serializers.py`:
- Around line 78-80: The validate_config() guard currently reads template_type =
self.initial_data.get("type") which allows a PATCH/PUT to bypass validation if
"type" is not re-submitted; change the logic to fall back to the serializer
instance's type (e.g., getattr(self.instance, "type", None) or
self.instance.type) when initial_data doesn't include "type" so that the check
(template_type == "generic" and value == {}) uses the real object type and
rejects empty generic configs accordingly.
- Around line 104-119: The compatibility check uses stale local variables ca and
blueprint_cert after you clear data["ca"] and data["blueprint_cert"] when
template_type != "cert", causing erroneous ValidationError; update the code so
that after you null out data["ca"] and data["blueprint_cert"] you also set the
local variables ca and blueprint_cert to None (or re-read them from data) or
simply skip the CA/blueprint_cert validation when template_type != "cert" so the
check in the block that references blueprint_cert, ca, template_type, and data
reflects the normalized values.

In `@openwisp_controller/config/base/config.py`:
- Around line 509-520: The post_clear on_commit callback runs before the final
template set is persisted in non-atomic clear→readd flows, causing
DeviceCertificate rows to be deleted; ensure the org-change clear/reapply path
is executed inside a transaction so transaction.on_commit sees the final
templates — specifically, wrap the template-clear and the subsequent call to
Config.enforce_required_templates (called from DeviceDetailSerializer.update) in
an outer transaction.atomic() so the lambda registered on transaction.on_commit
in the block handling action == "post_clear" observes the final
instance.templates and does not prematurely delete
instance.devicecertificate_set.

In `@openwisp_controller/config/base/device_certificate.py`:
- Around line 38-59: The clean() guard that prevents assigning a certificate
already used as a template blueprint isn't invoked on ORM saves; update the save
method (in DeviceCertificate.save) to call self.clean() (or
self.full_clean(validate_unique=False) if you prefer model validation) inside
the transaction before any auto-cert provisioning and before super().save so the
cert_id/Template.objects.filter(blueprint_cert_id=...) invariant is enforced on
create/update via the ORM; keep the existing auto-cert logic (_auto_x509),
transaction.atomic block, and final super().save(*args, **kwargs).

In `@openwisp_controller/config/tests/test_admin.py`:
- Line 2335: The inline comment describing the query breakdown is out of date
for the updated expected_count = 23; update the comment near expected_count to
explain that the TemplateAdmin list_display now includes the added fields ca and
blueprint_cert (in TemplateAdmin), which introduces the extra query(s) —
explicitly state which query(s) now account for the +1 (e.g., that
template-related fetching now results in an extra query when retrieving
templates/fields), so the comment documents that the total is 23 instead of 22
and references TemplateAdmin/list_display and the ca and blueprint_cert
additions.

In `@openwisp_controller/config/tests/test_config.py`:
- Around line 864-865: The test reveals an extra DB query introduced in
_check_changes(): identify and short-circuit the cert-related branch so it
doesn't perform the extra lookup when certificate checks aren't required; modify
Config._check_changes() to first determine (via existing attributes or a new
helper like _cert_checks_needed() or _cert_fields_changed()) whether cert
validation is necessary and return/skip the DB access early when not needed,
moving any certificate model/query logic into that guarded block (also apply
same short-circuit to the other path referenced near lines 870-871).

In `@openwisp_controller/config/tests/test_template.py`:
- Around line 1373-1407: In test_cert_template_partial_replacement_cleans_up add
an assertion that the underlying Cert for the removed template was revoked when
calling config.templates.set([cert_template_1], clear=True): locate the removed
template via removed_cert_id (or lookup DeviceCertificate for cert_template_2
before deletion) and assert the related Cert object has its revoked flag (or
revoked_at) set (e.g., Cert.objects.filter(pk=<cert_pk>).exists() and
Cert.objects.get(pk=<cert_pk>).revoked is True or revoked_at is not None),
ensuring the revoke-before-delete lifecycle is enforced when
config.templates.set(..., clear=True) removes a DeviceCertificate.

In `@openwisp_controller/pki/tests/test_api.py`:
- Line 155: The assertion in test_ca_delete_api expecting six DB queries needs
an inline comment explaining why an extra query occurs: document that
config.Template rows reference the CA via AbstractTemplate.ca (ForeignKey to
django_x509.Ca with on_delete=CASCADE), so when deleting a Ca the Django cascade
delete collector issues an additional query to find and cascade related Template
rows; add this explanation directly next to the with self.assertNumQueries(6):
line so future readers understand the Template FK cascade is the source of the
extra query.

---

Outside diff comments:
In `@openwisp_controller/config/base/template.py`:
- Around line 362-367: The code currently treats any falsy value (e.g. [], "",
False) as missing and replaces it with {}, bypassing the subsequent dict type
check; change the normalization to only set self.default_values = {} when it is
None (use "if self.default_values is None:") and keep the existing
isinstance(self.default_values, dict) ValidationError for all other non-dict
values so []/""/False are rejected; reference self.default_values and the
ValidationError raised to locate and update the logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 88ef4903-9bee-4102-b807-03fcbc9099e8

📥 Commits

Reviewing files that changed from the base of the PR and between 977b7b6 and b9db466.

📒 Files selected for processing (20)
  • docs/user/certificate-templates.rst
  • docs/user/templates.rst
  • 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/switcher.js
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_config.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). (1)
  • GitHub Check: Python==3.10 | django~=4.2.0
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}

📄 CodeRabbit inference engine (Custom checks)

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

Files:

  • openwisp_controller/config/apps.py
  • tests/openwisp2/sample_config/models.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/config/models.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/static/config/js/switcher.js
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/admin.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.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/base/template.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/base/device_certificate.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/apps.py
  • tests/openwisp2/sample_config/models.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/config/models.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/static/config/js/switcher.js
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/admin.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.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/base/template.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/base/device_certificate.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/apps.py
  • tests/openwisp2/sample_config/models.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/config/models.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/static/config/js/switcher.js
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/admin.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.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/base/template.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/base/device_certificate.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/apps.py
  • tests/openwisp2/sample_config/models.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/config/models.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/admin.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.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/base/template.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/tests/test_template.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • openwisp_controller/config/apps.py
  • tests/openwisp2/sample_config/models.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/config/models.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/admin.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.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/base/template.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/base/device_certificate.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/apps.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/config/models.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/admin.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/base/template.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/base/device_certificate.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/apps.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/config/models.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/admin.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/base/template.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/base/device_certificate.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/apps.py
  • tests/openwisp2/sample_config/models.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/config/models.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/admin.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.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/base/template.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/base/device_certificate.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 (5)
docs/user/templates.rst (1)

213-229: LGTM!

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

974-1466: LGTM!

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

34-35: LGTM!

Also applies to: 557-558, 743-744, 1334-1541

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

772-804: LGTM!

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

275-275: Document why test_cert_delete_api query count increased (6 → 8)

openwisp_controller/pki/tests/test_api.py expects with self.assertNumQueries(8): (up from 6). Please add an inline comment explaining what ORM work added the extra 2 queries. The earlier assumption about a DeviceCertificate OneToOneField to Cert isn’t supported by the current openwisp_controller/config/ search (OneToOneField.*Cert.*on_delete produced no matches), so the comment should reference the actual relationship/model/deletion path that causes the extra queries.

Comment thread docs/user/certificate-templates.rst Outdated
Comment thread docs/user/certificate-templates.rst Outdated
Comment thread openwisp_controller/config/api/serializers.py Outdated
Comment thread openwisp_controller/config/api/serializers.py
Comment thread openwisp_controller/config/base/config.py
Comment thread openwisp_controller/config/base/device_certificate.py
Comment thread openwisp_controller/config/tests/test_admin.py
Comment thread openwisp_controller/config/tests/test_config.py
Comment thread openwisp_controller/config/tests/test_template.py
Comment thread openwisp_controller/pki/tests/test_api.py
Added dynamic JS logic in 'switcher.js' to change the auto-cert label text between 'Automatic tunnel provisioning' and 'Automatic certificate provisioning'
@stktyagi

stktyagi commented Jun 7, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai resolve

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Comments resolved and changes approved.

stktyagi and others added 2 commits June 9, 2026 18:07
Added documentation for context injection

Fixes #1360
@stktyagi stktyagi force-pushed the issues/1356-extend-abstract-template branch from b69465f to 1e9e813 Compare June 10, 2026 10:24
@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 openwisp deleted a comment from openwisp-companion Bot Jun 10, 2026
@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).

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

@openwisp openwisp deleted a comment from openwisp-companion Bot Jun 10, 2026
@openwisp openwisp deleted a comment from openwisp-companion Bot Jun 10, 2026

@nemesifier nemesifier left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Great progress @stktyagi, see my comments below, I'll be submitting more soon.

@@ -0,0 +1,219 @@
X.509 Certificate Generator Templates

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.

Add this page to the /docs/index.rst, please also add a mention of this feature in /docs/user/intro.rst.

OpenWISP admin and set the **Type** to :guilabel:`Certificate` (``cert``).
This will reveal the certificate-specific configuration fields.

.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/certificate-template.png

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.

For consistency:

Suggested change
.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/certificate-template.png
.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/1.4/certificate-templates/<screenshot-name>.png
  • We're separating images for different versions as the UI may be different in different versions.
  • We use a dir to group several images for the same feature, as we'll likely show multiple media files, not just one


.. _certificate_templates_api:

Certificate Templates via the REST API

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.

Check if the rest api docs page needs to be updated.
Let's also double check that both the swagger API docs (/api/v1/docs/) and the DRF browsable API reflect the new fields properly.

data["blueprint_cert"] = None
ca = None
blueprint_cert = None

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.

Suggested change

)
}
)

@nemesifier nemesifier Jun 12, 2026

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.

Suggested change

instance.devicecertificate_set.exclude(
template_id__in=instance.templates.values_list("id", flat=True)
).delete()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If there's comments we don't need an extra blank line to separate blocks of code.

abstract = False


class DeviceCertificate(DetailsModel, AbstractDeviceCertificate):

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.

The addition of this model requires updating the extending.rst docs page.

@nemesifier nemesifier left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

More comments below.

cert_context.update(
{
f"{prefix}_path": cert_path,
f"{prefix}_pem": dc.cert.certificate,

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 do not see the standalone certificate renewal path wired into the config status update logic. certificate_updated() still only resolves instance.vpnclient.config, but standalone certificate templates now expose DeviceCertificate.cert material in the rendered context here. If a standalone certificate is renewed or otherwise saved through the existing PKI path, the generated PEM/private key in the context changes, but the related config is not marked modified and its checksum cache is not invalidated. This leaves devices with stale config state after renewal, which is one of the lifecycle requirements in #1358.

Are you sure this is covered? Are there tests for this aspect? If not please add them (TDD is great here).

Comment thread docs/user/templates.rst
Certificate Templates
---------------------

A Certificate Template is a :doc:`Template </controller/user/templates>`

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.

This new section mentions certificate templates, but it does not link to the detailed certificate-templates.rst page, and that new page is not added to the user docs toctree in docs/index.rst, so it is not reachable from the built docs. I would add the new page to the toctree and link to it from here.


- ``{{ cert_<template_uuid_hex>_pem }}``: The public certificate.
- ``{{ cert_<template_uuid_hex>_key }}``: The private key.
- ``{{ cert_<template_uuid_hex>_uuid }}``: The UUID of the

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.

This says cert_<template_uuid_hex>_uuid is the UUID of the DeviceCertificate object, but the implementation returns str(dc.cert.id). Please change this to say it is the generated certificate UUID. While updating docs, please also update the stale auto_cert text in docs/user/settings.rst and the template type examples in docs/user/rest-api.rst, which still list only VPN/generic.

Implemented certificate regeneration if device property change.

Fixes #1359
@openwisp-companion

Copy link
Copy Markdown

Test Failure: test_restoring_deleted_device

Hello @stktyagi,
(Analysis for commit 717c3e1)

The test openwisp_controller.tests.test_selenium.TestDevice.test_restoring_deleted_device failed because of a TypeError: 'NoneType' object is not iterable. This indicates that the self.get_browser_logs() function in tests/test_selenium.py is returning None when it's expected to return an iterable object.

Fix:
Review the get_browser_logs() method in tests/test_selenium.py to ensure it always returns an iterable (e.g., an empty list) even if no logs are found. This will prevent the TypeError.

@stktyagi

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (2)
openwisp_controller/config/static/config/js/switcher.js (1)

47-62: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Collapse the duplicated auto_cert toggle into one branch.

These two blocks both show/hide auto_cert_field; only the second one also updates the label. Keeping them separate adds redundant DOM writes and makes the visibility logic easy to drift.

♻️ Proposed refactor
-      if (val === "vpn" || val === "cert") {
-        auto_cert_field.show();
-      } else {
-        auto_cert_field.hide();
-      }
       if (val === "vpn" || val === "cert") {
         auto_cert_field.show();

         if (val === "vpn") {
           auto_cert_label.text(gettext("Automatic tunnel provisioning"));

As per coding guidelines: "Flag unused or redundant code."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openwisp_controller/config/static/config/js/switcher.js` around lines 47 -
62, The code duplicates show/hide logic for auto_cert_field; collapse into a
single branch that checks if val === "vpn" || val === "cert" once, calls
auto_cert_field.show() or auto_cert_field.hide() accordingly, and inside the
true branch update auto_cert_label.text(...) based on whether val === "vpn" or
val === "cert"; remove the redundant earlier if/else block so only one place
mutates auto_cert_field and the label (refer to symbols auto_cert_field,
auto_cert_label, and the val variable).

Source: Coding guidelines

openwisp_controller/config/api/serializers.py (1)

138-148: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Block all active-template type changes, not just cert -> non-cert.

The current guard only rejects switching an active template away from cert. It still allows generic/vpncert, but the certificate lifecycle is wired to template add/remove flows in this file, not to template type mutation. Existing configs that already reference the template would therefore miss DeviceCertificate creation entirely.

🐛 Proposed fix
-                # block changing a certificate template to a generic template
-                if self.instance.type == "cert" and template_type != "cert":
+                # block changing the type of any template already assigned
+                # to active or activating devices
+                if "type" in data and template_type != self.instance.type:
                     raise serializers.ValidationError(
                         {
                             "type": _(
-                                "This template is already assigned to active devices. "
-                                "You cannot change the template type from certificate "
-                                "on an active template."
+                                "This template is already assigned to active devices. "
+                                "You cannot change the template type on an active template."
                             )
                         }
                     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openwisp_controller/config/api/serializers.py` around lines 138 - 148, The
guard currently only prevents changing self.instance.type from "cert" to
non-"cert"; change it to block any mutation of the template type when the
template is active/assigned to devices by checking if self.instance.type !=
template_type and the template is active (or assigned to devices) and raise
serializers.ValidationError (same message text adapted to "You cannot change the
template type on an active template.") so type changes like generic/vpn -> cert
are also rejected and thus prevent missing DeviceCertificate lifecycle actions;
update the validation block that references self.instance.type and template_type
to perform this broader check and raise the error.
♻️ Duplicate comments (1)
openwisp_controller/config/api/serializers.py (1)

442-453: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear the bound DeviceCertificate rows in the organization-change path too.

_update_config() explicitly deletes stale DeviceCertificate rows before config.templates.set(..., clear=True), but this branch calls instance.config.templates.clear() directly and never performs the same cleanup. On an organization change without a config payload, old certificate bindings can survive even though the templates were cleared.

🐛 Proposed fix
                 with transaction.atomic():
+                    instance.config.devicecertificate_set.all().delete()
                     instance.config.device.organization = validated_data.get(
                         "organization"
                     )
                     instance.config.templates.clear()
                     Config.enforce_required_templates(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openwisp_controller/config/api/serializers.py` around lines 442 - 453, The
organization-change branch that calls instance.config.templates.clear() must
also remove any stale DeviceCertificate bindings like the _update_config path
does: before calling instance.config.templates.clear(), delete DeviceCertificate
rows associated with this instance and its config (the same cleanup
_update_config performs), then proceed to clear templates and call
Config.enforce_required_templates with raw_data_for_signal_handlers; locate the
cleanup logic in _update_config and replicate the DeviceCertificate deletion
here so old certificate bindings cannot survive an organization-only change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openwisp_controller/config/api/serializers.py`:
- Around line 78-80: The serializer's template_type fallback is inconsistent:
currently template_type is derived with self.initial_data.get("type",
getattr(self.instance, "type", None)) while validate() treats a missing type as
"generic", so validate_config() can see None and skip the empty-{} guard. Change
the template_type fallback to the serializer's default ("generic")—e.g. use
self.initial_data.get("type", getattr(self.instance, "type", "generic")) or
otherwise normalize None to "generic" before calling validate_config()—so
validate_config(), validate(), and the {}-guard all use the same default type;
update references in validate_config()/validate() accordingly.

In `@openwisp_controller/config/tasks.py`:
- Around line 227-289: The task is vulnerable to concurrent runs; to fix it,
acquire a row lock on the DeviceCertificate set before rotating so two workers
can't operate on the same row: in regenerate_device_certificates_task update the
active_device_certs queryset (the one created with
DeviceCertificate.objects.filter(config__device=device, auto_cert=True,
cert__revoked=False, template__type="cert").select_related(...)) to include
.select_for_update() (and optionally .order_by('pk') for deterministic locking)
inside the existing transaction.atomic() block, re-evaluate .exists() after
locking, and then proceed to revoke/save each locked dc so overlapping tasks
serialize safely.
- Around line 256-285: The new Cert creation in the rotation path only sets
name, ca, organization, common_name and extensions, which drops other
certificate metadata (key_length, digest, country_code, state, city,
organization_name, email) that the blueprint/CA may define; update the block
that constructs new_cert (the Cert(...) instantiation) to copy those fields from
the source blueprint/CA (use dc.template.blueprint_cert and/or old_cert as the
source) so that key_length, digest, country_code, state, city, organization_name
and email are preserved, keep using dc._get_common_name() and the computed
extensions, then call new_cert.full_clean() and new_cert.save() as before.

---

Outside diff comments:
In `@openwisp_controller/config/api/serializers.py`:
- Around line 138-148: The guard currently only prevents changing
self.instance.type from "cert" to non-"cert"; change it to block any mutation of
the template type when the template is active/assigned to devices by checking if
self.instance.type != template_type and the template is active (or assigned to
devices) and raise serializers.ValidationError (same message text adapted to
"You cannot change the template type on an active template.") so type changes
like generic/vpn -> cert are also rejected and thus prevent missing
DeviceCertificate lifecycle actions; update the validation block that references
self.instance.type and template_type to perform this broader check and raise the
error.

In `@openwisp_controller/config/static/config/js/switcher.js`:
- Around line 47-62: The code duplicates show/hide logic for auto_cert_field;
collapse into a single branch that checks if val === "vpn" || val === "cert"
once, calls auto_cert_field.show() or auto_cert_field.hide() accordingly, and
inside the true branch update auto_cert_label.text(...) based on whether val ===
"vpn" or val === "cert"; remove the redundant earlier if/else block so only one
place mutates auto_cert_field and the label (refer to symbols auto_cert_field,
auto_cert_label, and the val variable).

---

Duplicate comments:
In `@openwisp_controller/config/api/serializers.py`:
- Around line 442-453: The organization-change branch that calls
instance.config.templates.clear() must also remove any stale DeviceCertificate
bindings like the _update_config path does: before calling
instance.config.templates.clear(), delete DeviceCertificate rows associated with
this instance and its config (the same cleanup _update_config performs), then
proceed to clear templates and call Config.enforce_required_templates with
raw_data_for_signal_handlers; locate the cleanup logic in _update_config and
replicate the DeviceCertificate deletion here so old certificate bindings cannot
survive an organization-only change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7bacd21d-939e-425e-8675-416099614e8c

📥 Commits

Reviewing files that changed from the base of the PR and between b9db466 and 717c3e1.

📒 Files selected for processing (9)
  • docs/user/certificate-templates.rst
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/settings.py
  • openwisp_controller/config/static/config/js/switcher.js
  • openwisp_controller/config/tasks.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_template.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}

📄 CodeRabbit inference engine (Custom checks)

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

Files:

  • openwisp_controller/config/settings.py
  • openwisp_controller/config/static/config/js/switcher.js
  • openwisp_controller/config/tasks.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/base/device_certificate.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/settings.py
  • openwisp_controller/config/static/config/js/switcher.js
  • openwisp_controller/config/tasks.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/base/device_certificate.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/settings.py
  • openwisp_controller/config/static/config/js/switcher.js
  • openwisp_controller/config/tasks.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/base/device_certificate.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/settings.py
  • openwisp_controller/config/tasks.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/tests/test_template.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_template.py
🔇 Additional comments (2)
docs/user/certificate-templates.rst (2)

34-35: Use the versioned docs image asset.

This still points at the unversioned docs/docs/certificate-template.png path; that tends to drift across release branches.


164-165: Correct the *_uuid variable description.

This variable is the generated certificate UUID, not the DeviceCertificate UUID. The current wording will mislead template authors.

Comment thread openwisp_controller/config/api/serializers.py
Comment thread openwisp_controller/config/tasks.py
Comment thread openwisp_controller/config/tasks.py
Made task safe against overlapping runs for the same device.
@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).

@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 (3/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