Skip to content

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

Open
stktyagi wants to merge 42 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 42 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
Closes #1359

Description of Changes

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

Manual test plan

Setup

  • 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

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) | ⏱️ ~65 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • nemesifier
  • devkapilbansal
  • codesankalp

Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Bug Fixes ❌ Error PR fixes some bugs (NULL filter in get_unassigned_certs, post_clear stale rows) with proper regression tests, but leaves 4 unresolved bugs identified by review: (1) Hardware drift detection compare... Implement the 4 proposed fixes from review comments: fix hardware field comparison logic, create template_ui.js file, add DeviceCertificate cleanup before templates.clear() in org change branch, and add proper fixtures to drift tests.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title uses the correct [feature] prefix and accurately describes the main change: introducing standalone certificate templates and device bindings.
Description check ✅ Passed The PR description includes all required template sections: checklist with all items checked, reference to 7 existing issues (Closes #1356, #1377, #1357, #1361, #1358, #1360, #1359), a clear description of changes, and a comprehensive manual test plan with screenshots.
Linked Issues check ✅ Passed The code changes comprehensively implement all objectives from the 7 linked issues: (#1356) extends AbstractTemplate with cert type, ca/blueprint_cert FKs, and validation; (#1377) introduces DeviceCertificate M2M model; (#1357) updates Django admin with dynamic field visibility; (#1361) integrates REST API support; (#1358) implements lifecycle management with generation/revocation; (#1359) adds hardware drift detection and regeneration; (#1360) exposes certificate data as template variables.
Out of Scope Changes check ✅ Passed All code changes are directly scoped to the linked objectives: certificate template infrastructure, DeviceCertificate model, admin UI, API serializers, lifecycle handlers, certificate context injection, and hardware drift detection. No unrelated modifications detected.
✨ 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: No Issues Found | Recommendation: Merge

Incremental Review (since a819721)

The new commit [chores] Minor improvements is a focused, low-risk follow-up:

  • handlers.py — Real bug fix for partial saves: detect_hardware_drift now only treats name/mac_address as changed when those fields were actually saved (via the new _hardware_field_was_saved helper). Previously, dirty in-memory values for fields not in update_fields could spuriously trigger cert regeneration. A dedicated regression test test_hardware_drift_partial_save_ignores_dirty_memory is added (passes with the fix, would fail without it).
  • api/serializers.py — On device organization change, DeviceCertificate rows for the config are now deleted before clearing templates. This prevents stale certificates issued under the previous org's CA from lingering (cross-tenant hygiene). Deletion fires the existing post_delete receiver which revokes the underlying x509 certs, consistent with the normal unassign path.
  • admin.py — Removed the now-nonexistent template_ui.js from BaseConfigAdmin.Media.js (the file is no longer shipped under static/config/js/), cleaning up a dangling reference. This also resolves the prior outstanding note about browser coverage for template_ui.js.
  • Teststest_device.py hardware-drift tests now build a real CA + cert template (so the signal path is genuinely exercised) and use captureOnCommitCallbacks(execute=True); test_selenium.py swaps the raw Ca.objects.create for the shared _create_ca helper with a common_name; test_template.py makes the cert=None scenario explicit (auto_cert=False + assertIsNone).

No new critical bugs or security vulnerabilities were introduced by this commit.

Files Reviewed (incremental)
  • openwisp_controller/config/admin.py - 0 issues
  • openwisp_controller/config/api/serializers.py - 0 issues
  • openwisp_controller/config/handlers.py - 0 issues
  • openwisp_controller/config/tests/test_device.py - 0 issues
  • openwisp_controller/config/tests/test_selenium.py - 0 issues
  • openwisp_controller/config/tests/test_template.py - 0 issues
  • openwisp_controller/config/base/device_certificate.py (context only) - 0 issues
Carried-forward from prior review

All previously reviewed files and their findings carry forward unchanged. The prior outstanding note (missing regression test for cert-template validation in template.py) was already resolved in the previous cycle by test_active_mutation_blocked, and the template_ui.js browser-coverage note is now resolved by this commit removing the file from the admin media bundle.

Previous Review Summaries (2 snapshots, latest commit a819721)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit a819721)

Status: No Issues Found | Recommendation: Merge

Incremental Review (since 298572f)

The new commits add a large body of work: certificate regeneration on hardware drift (#1359), deactivated-device enforcement across config/connection/WHOIS/geo, pagination consolidation to OpenWispPagination, defensive data-migration fixes for the JSONField change (#1061), and a DeviceConnection view rename with backward-compatible aliases.

The previously flagged WARNING (missing regression test for cert-template validation in template.py) is now resolvedtest_active_mutation_blocked covers CA/blueprint mutation and type-change-away-from-cert on active templates.

Review highlights:

  • regenerate_device_certificates_task is idempotent (locks rows, skips certs whose cert_id already drifted from the expected map, revokes old + mints new).
  • _sync_device_certificates post_clear path correctly defers orphan deletion via transaction.on_commit so reorders don't revoke certs.
  • _save_without_resurrecting guards against connection/command resurrection after concurrent deletion.
  • Migration 0061 adds nullable checksum_db then batched backfill (chunked .iterator() + bulk_update) — no locking DDL / unsafe NOT NULL.
  • Historical data migrations gain resolve_config() string-tolerant parsing without changing their one-shot semantics.
  • View rename keeps DeviceConnenctionListCreateView / deviceconnection_details_view aliases (TODO remove in 1.4) and wires URLs to the new names.

Extensive new tests accompany each change and certificate-templates.rst + settings.rst docs were added/updated.

Files Reviewed (incremental)
  • openwisp_controller/config/api/serializers.py - 0 issues
  • openwisp_controller/config/api/views.py - 0 issues
  • openwisp_controller/config/base/config.py - 0 issues
  • openwisp_controller/config/base/device.py - 0 issues
  • openwisp_controller/config/base/device_certificate.py - 0 issues
  • openwisp_controller/config/base/template.py - 0 issues (prior warning resolved)
  • openwisp_controller/config/handlers.py - 0 issues
  • openwisp_controller/config/tasks.py - 0 issues
  • openwisp_controller/config/controller/views.py - 0 issues
  • openwisp_controller/config/admin.py - 0 issues
  • openwisp_controller/config/settings.py - 0 issues
  • openwisp_controller/config/migrations/* - 0 issues
  • openwisp_controller/config/static/config/js/* - 0 issues
  • openwisp_controller/config/whois/* - 0 issues
  • openwisp_controller/connection/base/models.py - 0 issues
  • openwisp_controller/connection/api/views.py - 0 issues
  • openwisp_controller/connection/tasks.py - 0 issues
  • openwisp_controller/geo/* - 0 issues
  • openwisp_controller/pki/api/views.py - 0 issues

Previous review (commit 298572f)

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 glm-5.2-20260616 · 696,712 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.385%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
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).

Updated certificate_updated to synchronize config status
@openwisp openwisp deleted a comment from openwisp-companion Bot Jun 13, 2026
Addressed review comments for updating documentation
@openwisp openwisp deleted a comment from openwisp-companion Bot Jun 13, 2026
@openwisp openwisp deleted a comment from openwisp-companion Bot Jun 14, 2026
@openwisp openwisp deleted a comment from openwisp-companion Bot Jun 14, 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.

I left a few inline comments on the remaining issues.

Comment thread openwisp_controller/config/tasks.py Outdated
certs_regenerated = 0

with transaction.atomic():
active_device_certs = (

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 row lock prevents two workers from modifying the same DeviceCertificate at the same time, but the task is still not idempotent. If two hardware-drift tasks are queued for the same device, the second worker waits for the first one to commit, then this queryset matches the newly generated non-revoked certificate and rotates it again. A device may fetch the config generated by the first task and then have that certificate revoked immediately by the duplicate task.

I would add an event guard before revoking the current cert, for example pass the expected old cert IDs or expected old hardware values to the task and skip rows that no longer match that event. A regression test should queue or run the task twice for the same hardware change and verify only one replacement cert is generated.

Comment thread openwisp_controller/config/tasks.py Outdated
},
]
)
new_cert = Cert(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The regeneration path manually creates the replacement Cert, while initial provisioning goes through DeviceCertificate._auto_create_cert_extra(). This means a project using a swappable DeviceCertificate model to customize certificate creation will get different behavior during hardware-drift regeneration than during initial provisioning.

I would call dc._auto_create_cert_extra(new_cert) before full_clean() here, or ensure the logic is defined in a model and not duplicated.

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.

@stktyagi - lets refactor this duplication:

pull the build logic up into the model:

# in AbstractDeviceCertificate
def _build_cert(self, name, common_name):
    """Build (but do not save) a Cert instance from template + blueprint."""
    ca = self.template.ca
    blueprint = self.template.blueprint_cert
    cert_model = self.__class__.cert.field.related_model

    attrs = self._clone_blueprint_attrs(ca, blueprint)
    extensions = self._build_extensions(blueprint)
    cert = cert_model(
        name=name, ca=ca, common_name=common_name,
        extensions=extensions, **attrs,
    )
    return self._auto_create_cert_extra(cert)

def _clone_blueprint_attrs(self, ca, blueprint):
    source = blueprint or ca
    digest = str(source.digest) if not blueprint else source.digest
    return dict(
        key_length=source.key_length,
        digest=digest,
        country_code=source.country_code,
        state=source.state,
        city=source.city,
        organization_name=source.organization_name,
        email=source.email,
    )

def _build_extensions(self, blueprint):
    if blueprint and blueprint.extensions:
        extensions = copy.deepcopy(blueprint.extensions)
    else:
        extensions = [{"name": "nsCertType", "value": "client", "critical": False}]
    extensions.extend(self._get_hardware_oid_extensions())
    return extensions

def _get_hardware_oid_extensions(self):
    device = self.config.device
    return [
        {"oid": MAC_ADDRESS_OID, "value": f"ASN1:UTF8:string:{device.mac_address}", "critical": False},
        {"oid": DEVICE_UUID_OID, "value": f"ASN1:UTF8:string:{device.id}", "critical": False},
    ]

Then _auto_create_cert() and the regen task both collapse to:

cert = dc._build_cert(name=device.name, common_name=dc._get_common_name())
cert.full_clean()
cert.save()
dc.cert = cert
dc.save()

using the template's 32-character hexadecimal UUID (the UUID with dashes
removed).

The following variables are dynamically injected into the configuration

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 certificate context section says "the following variables" but omits cert_<template_uuid_hex>_path and cert_<template_uuid_hex>_key_path, which are exposed by get_cert_context() and tested. Please add these two public variables here.

CONTEXT = get_setting("CONTEXT", {})
assert isinstance(CONTEXT, dict), "OPENWISP_CONTROLLER_CONTEXT must be a dictionary"
DEFAULT_AUTO_CERT = get_setting("DEFAULT_AUTO_CERT", True)
REGENERATE_CERTS_ON_HARDWARE_CHANGE = get_setting(

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 adds the public OPENWISP_CONTROLLER_REGENERATE_CERTS_ON_HARDWARE_CHANGE setting, but I do not see it documented in docs/user/settings.rst. While updating that page, please also update OPENWISP_CONTROLLER_COMMON_NAME_FORMAT, which still says it only defines VPN client certificate common names even though standalone certificate templates also use it.

Addressed review feedback to ensure the hardware drift
certificate regeneration lifecycle is secure, extensible, and idempotent
@openwisp openwisp deleted a comment from openwisp-companion Bot Jun 16, 2026
@openwisp openwisp deleted a comment from openwisp-companion Bot Jun 16, 2026
@openwisp openwisp deleted a comment from openwisp-companion Bot Jun 16, 2026
@openwisp openwisp deleted a comment from openwisp-companion Bot Jun 16, 2026
@openwisp openwisp deleted a comment from openwisp-companion Bot Jun 16, 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 (1/3).

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

good progress - few minor suggestions:

Comment thread openwisp_controller/config/tasks.py Outdated
},
]
)
new_cert = Cert(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@stktyagi - lets refactor this duplication:

pull the build logic up into the model:

# in AbstractDeviceCertificate
def _build_cert(self, name, common_name):
    """Build (but do not save) a Cert instance from template + blueprint."""
    ca = self.template.ca
    blueprint = self.template.blueprint_cert
    cert_model = self.__class__.cert.field.related_model

    attrs = self._clone_blueprint_attrs(ca, blueprint)
    extensions = self._build_extensions(blueprint)
    cert = cert_model(
        name=name, ca=ca, common_name=common_name,
        extensions=extensions, **attrs,
    )
    return self._auto_create_cert_extra(cert)

def _clone_blueprint_attrs(self, ca, blueprint):
    source = blueprint or ca
    digest = str(source.digest) if not blueprint else source.digest
    return dict(
        key_length=source.key_length,
        digest=digest,
        country_code=source.country_code,
        state=source.state,
        city=source.city,
        organization_name=source.organization_name,
        email=source.email,
    )

def _build_extensions(self, blueprint):
    if blueprint and blueprint.extensions:
        extensions = copy.deepcopy(blueprint.extensions)
    else:
        extensions = [{"name": "nsCertType", "value": "client", "critical": False}]
    extensions.extend(self._get_hardware_oid_extensions())
    return extensions

def _get_hardware_oid_extensions(self):
    device = self.config.device
    return [
        {"oid": MAC_ADDRESS_OID, "value": f"ASN1:UTF8:string:{device.mac_address}", "critical": False},
        {"oid": DEVICE_UUID_OID, "value": f"ASN1:UTF8:string:{device.id}", "critical": False},
    ]

Then _auto_create_cert() and the regen task both collapse to:

cert = dc._build_cert(name=device.name, common_name=dc._get_common_name())
cert.full_clean()
cert.save()
dc.cert = cert
dc.save()


# inject MAC and UUID as custom OIDs, prerequisite: #228 in django-x509
mac_oid = "1.3.6.1.4.1.65901.1"
uuid_oid = "1.3.6.1.4.1.65901.2"

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.

lets make these named constants:

Side benefit: gives future maintainers a grep-able name and a place to add a comment about which OID arc this is

# device_certificate.py (top of file)
MAC_ADDRESS_OID = "1.3.6.1.4.1.65901.1"
DEVICE_UUID_OID = "1.3.6.1.4.1.65901.2"

Comment thread openwisp_controller/config/handlers.py Outdated
Comment on lines +57 to +59
if update_fields is not None:
if "name" not in update_fields and "mac_address" not in update_fields:
return

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.

refactor:

def _hardware_fields_changed(update_fields):
    return update_fields is None or ("name" in update_fields or "mac_address" in update_fields)

Comment thread openwisp_controller/config/handlers.py Outdated
Comment on lines +76 to +78
if update_fields is not None:
if "name" not in update_fields and "mac_address" not in update_fields:
return

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.

then use _hardware_fields_changed()

stktyagi and others added 2 commits June 18, 2026 14:16
Added selenium tests and refactored DeviceCertificate model
@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 Jun 18, 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: 6

🤖 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/admin.py`:
- Around line 118-125: The js list in BaseConfigAdmin.Media includes a reference
to template_ui.js which does not exist in the static files directory and will
cause 404 errors for all admin classes inheriting from BaseConfigAdmin. Either
remove the template_ui.js entry from the js list in the Media class if it was
added incorrectly, or if this file should exist, create it in the appropriate
static directory. Alternatively, if the intent was to reference a different
existing file such as relevant_templates.js, update the string reference in the
js list to point to the correct file name.

In `@openwisp_controller/config/api/serializers.py`:
- Around line 440-445: The code path that changes the organization and calls
instance.config.templates.clear() directly bypasses the _update_config() method,
which means the explicit DeviceCertificate deletion logic from Lines 324-326
does not execute. This allows stale device-certificate rows to persist after the
organization change. After calling instance.config.templates.clear() and before
or after Config.enforce_required_templates(), explicitly delete the associated
DeviceCertificate objects using the same deletion logic that exists in
_update_config() to ensure device certificates are properly cleaned up when
templates are cleared during an organization change.

In `@openwisp_controller/config/handlers.py`:
- Around line 48-51: The `_hardware_fields_changed` function correctly
identifies when hardware fields should be checked, but the
`detect_hardware_drift` handler still compares all hardware fields (name,
mac_address) unconditionally against the instance, treating in-memory unsaved
changes as drift. Modify the comparison logic in the `detect_hardware_drift`
handler to only compare the specific fields that were actually saved by checking
if they exist in the `update_fields` parameter when it is not None; only compare
all hardware fields when `update_fields` is None. Additionally, add a regression
test case that sets `mac_address` to a dirty value in memory, calls
`save(update_fields={"name"})` without modifying name, and verifies that the
regeneration task is not queued since mac_address was not persisted.

In `@openwisp_controller/config/tests/test_device.py`:
- Around line 699-705: The test
`test_hardware_drift_signal_ignores_unrelated_changes` does not properly
validate that the hardware drift task is not enqueued. The test lacks an
eligible DeviceCertificate instance and does not trigger on_commit callbacks, so
mocked_task.assert_not_called() passes regardless of regression in the enqueue
logic. Create a DeviceCertificate for the device before saving it, then ensure
on_commit callbacks are executed (using a context manager like
transaction.atomic() or a test utility method that handles database commits),
and verify that the task was genuinely not called because the change (the key
modification) is unrelated to hardware drift, not because the prerequisites were
missing.

In `@openwisp_controller/config/tests/test_selenium.py`:
- Line 25: The code is directly loading the Ca model using
load_model("django_x509", "Ca") which bypasses the existing validation in the
_create_ca() helper method. Replace all direct Ca fixture creation (at line 25
and also around lines 498-500 and 529-531) with calls to the _create_ca() helper
method instead. After replacing all direct Ca instantiations with the helper,
remove the direct Ca model binding from load_model if it is no longer referenced
elsewhere in the test file.

In `@openwisp_controller/config/tests/test_template.py`:
- Around line 1441-1460: The test_get_unassigned_certs_with_null_device_cert
test may not actually create a row with cert_id=NULL in the database because
DeviceCertificate.save() can auto-provision a certificate when auto_cert is true
and cert is missing. Add an explicit assertion immediately after creating the
DeviceCertificate object with cert=None to verify that the cert_id field in the
database is actually NULL before calling get_unassigned_certs(). This ensures
the regression test truly exercises SQL NULL semantics as intended.
🪄 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: 37e188c0-6bf4-4395-b0a7-deb6440e9313

📥 Commits

Reviewing files that changed from the base of the PR and between 464b04f and a819721.

📒 Files selected for processing (29)
  • docs/developer/extending.rst
  • docs/index.rst
  • docs/user/certificate-templates.rst
  • docs/user/intro.rst
  • docs/user/rest-api.rst
  • docs/user/settings.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/handlers.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/settings.py
  • openwisp_controller/config/static/config/js/switcher.js
  • openwisp_controller/config/tasks.py
  • 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_device.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
🧰 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/tests/test_admin.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/static/config/js/switcher.js
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/tasks.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_device.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/base/template.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/tests/test_selenium.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_template.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/device_certificate.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/tests/test_admin.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/static/config/js/switcher.js
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/tasks.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_device.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/base/template.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/tests/test_selenium.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_template.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/device_certificate.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/tests/test_admin.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/static/config/js/switcher.js
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/tasks.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_device.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/base/template.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/tests/test_selenium.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_template.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/device_certificate.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/tests/test_admin.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/tasks.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_device.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/base/template.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/tests/test_selenium.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_template.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/device_certificate.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/tests/test_admin.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/tasks.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_device.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/base/template.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/tests/test_selenium.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_template.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/device_certificate.py
🧠 Learnings (7)
📚 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/tests/test_admin.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/tasks.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/tests/test_selenium.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_template.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/device_certificate.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/tests/test_admin.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/tasks.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/tests/test_selenium.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_template.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/device_certificate.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/tests/test_admin.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/tasks.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_device.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/base/template.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/tests/test_selenium.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_template.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/device_certificate.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_admin.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/tests/test_api.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_admin.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_device.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/tests/test_api.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_admin.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/tests/test_api.py
📚 Learning: 2026-01-12T22:27:40.078Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1175
File: tests/openwisp2/sample_config/migrations/0008_whoisinfo_organizationconfigsettings_whois_enabled.py:18-67
Timestamp: 2026-01-12T22:27:40.078Z
Learning: In test migrations under tests/openwisp2/sample_config/migrations, verify scenarios where a swappable model (CONFIG_WHOISINFO_MODEL) is extended with extra fields (e.g., an additional 'details' field) to ensure compatibility and no errors when swapping to a custom implementation. This pattern helps confirm that extending AbstractWHOISInfo via a custom model works as intended.

Applied to files:

  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
🪛 ast-grep (0.43.0)
tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py

[info] 106-106: use help_text to document model columns
Context: models.CharField(blank=True, max_length=64, null=True)
Note: Security best practice.

(model-help-text)


[warning] 64-75: always specify max_length for a Charfield
Context: models.CharField(
choices=[
("generic", "Generic"),
("vpn", "VPN-client"),
("cert", "Certificate"),
],
db_index=True,
default="generic",
help_text="template type, determines which features are available",
max_length=16,
verbose_name="type",
)
Note: Security best practice.

(model-charfield-max-length)


[warning] 106-106: always specify max_length for a Charfield
Context: models.CharField(blank=True, max_length=64, null=True)
Note: Security best practice.

(model-charfield-max-length)

openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py

[warning] 66-77: always specify max_length for a Charfield
Context: models.CharField(
choices=[
("generic", "Generic"),
("vpn", "VPN-client"),
("cert", "Certificate"),
],
db_index=True,
default="generic",
help_text="template type, determines which features are available",
max_length=16,
verbose_name="type",
)
Note: Security best practice.

(model-charfield-max-length)

🔇 Additional comments (21)
openwisp_controller/config/tests/test_device.py (1)

8-8: LGTM!

Also applies to: 29-31, 660-694, 721-878

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

2335-2335: LGTM!

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

864-865: LGTM!

Also applies to: 867-870

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

155-155: LGTM!

Also applies to: 256-257, 267-276, 292-293

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

66-72: LGTM!

Also applies to: 475-489, 502-547, 978-1003, 1031-1032

openwisp_controller/config/apps.py (1)

72-72: LGTM!

Also applies to: 98-102, 124-128

openwisp_controller/config/settings.py (1)

37-39: LGTM!

openwisp_controller/config/handlers.py (1)

2-10: LGTM!

Also applies to: 54-69, 72-80, 85-100

openwisp_controller/config/tasks.py (1)

8-15: LGTM!

Also applies to: 226-294

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

1484-1503: Still cover blueprint_cert organization scoping in the API test.

Line 1484 only exercises a cross-organization ca. The previous API coverage request also called out blueprint_cert; add an isolated case using a valid/shared CA with a blueprint certificate outside the template organization so that RBAC/org scoping for blueprint material cannot regress.

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

25-29: LGTM!

Also applies to: 40-49, 74-97, 126-135, 256-300, 302-346, 348-384

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

1-55: LGTM!

Also applies to: 65-175


57-63: No partial-save calls to DeviceCertificate found—issue is theoretically valid but unmanifested in current codebase.

An examination of the codebase reveals no calls to DeviceCertificate.save(update_fields=...). The single existing DeviceCertificate.save() call at openwisp_controller/config/tasks.py:269 is a full save without update_fields. While the concern is theoretically sound—if save(update_fields=...) excludes cert, the newly assigned cert_id from _auto_x509() would not persist—no actual code path currently triggers this scenario.

The proposed fix is valid defensive programming and would prevent future regressions if partial saves are introduced. Consider implementing it if this model is expected to be subclassed or extended by external code, or if partial-save patterns are planned.

openwisp_controller/config/models.py (1)

5-5: LGTM!

Also applies to: 97-105

tests/openwisp2/sample_config/models.py (1)

5-5: LGTM!

Also applies to: 99-105

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

20-85: LGTM!

Also applies to: 87-172, 324-327

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

23-152: LGTM!

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

21-150: LGTM!

tests/openwisp2/settings.py (1)

284-284: LGTM!

openwisp_controller/config/admin.py (1)

1068-1104: LGTM!

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

4-67: LGTM!

Comment thread openwisp_controller/config/admin.py
Comment thread openwisp_controller/config/api/serializers.py
Comment thread openwisp_controller/config/handlers.py
Comment thread openwisp_controller/config/tests/test_device.py
Comment thread openwisp_controller/config/tests/test_selenium.py Outdated
Comment thread openwisp_controller/config/tests/test_template.py
Addressed coderabbit review comments.
@stktyagi

Copy link
Copy Markdown
Member Author

@coderabbitai resolve

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Comments resolved and changes approved.

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