Skip to content

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

Open
stktyagi wants to merge 55 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 55 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

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: 2b2bf4d7-6c28-4ceb-b577-8b2a7a8398b3

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

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds standalone certificate templates with type="cert", ca, and blueprint_cert support. It introduces DeviceCertificate and wires it through template assignment, config context rendering, admin/API exposure, and hardware-change certificate regeneration. It also updates migrations, sample-app configuration, docs, and tests across API, admin, Selenium, model, and task paths.

Sequence Diagram(s)

sequenceDiagram
  participant AdminAPI
  participant TemplateSerializer
  participant Config
  participant manage_device_certs
  participant DeviceCertificate
  participant regenerate_device_certificates_task

  AdminAPI->>TemplateSerializer: create/update cert template
  TemplateSerializer->>TemplateSerializer: validate ca and blueprint_cert
  AdminAPI->>Config: patch templates on device config
  Config->>manage_device_certs: m2m_changed
  manage_device_certs->>DeviceCertificate: create or delete rows
  Config->>regenerate_device_certificates_task: hardware drift detected
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90 minutes

Possibly related issues

Possibly related PRs

Suggested labels

docs

Suggested reviewers

  • devkapilbansal
  • codesankalp
  • nemesifier

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 Template.clean() still lacks a revoked-blueprint check, so the model-level root cause remains; there's no regression test for that scenario. Add a ValidationError in AbstractTemplate._clean_cert_template() for revoked blueprint_certs and a deterministic regression test covering full_clean()/API save failure.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is descriptive, matches the feature work, and uses the required [feature] prefix.
Description check ✅ Passed The PR description follows the template with checklist, linked issues, change summary, and manual test plan/screenshot.
Linked Issues check ✅ Passed The changes cover the certificate template, DeviceCertificate, admin/API, lifecycle, context, and hardware-regeneration requirements.
Out of Scope Changes check ✅ Passed All notable changes map to the certificate-template work; no unrelated code paths stand out.
✨ 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.

@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 52c7544)

The new commit (b9a9d2a) fixes failing tests introduced by the prior certificate-details work. Only test plumbing and whitespace change:

  • openwisp_controller/config/admin.py — Removes a single blank line inside certificate_details; no functional change.
  • openwisp_controller/config/tests/test_admin.py — In _verify_template_queries, adds ContentType.objects.get_for_model(Device) before assertNumQueries to warm the content-type cache and make the query count deterministic, and bumps expected_count from 23 to 25 to account for the new certificate_details readonly queries. Also bumps _deactivated_device_expected_readonly_fields from 22 to 23 to reflect the new readonly field on ConfigInline. Device and ContentType are already imported; no duplicate imports introduced.

No production request handlers, serializers, migrations, tenant-filtering, or security-relevant code paths are modified in this delta. No new critical bugs or security vulnerabilities.

Files Reviewed (incremental, 2 files)
  • openwisp_controller/config/admin.py - 0 issues
  • openwisp_controller/config/tests/test_admin.py - 0 issues
Previous Review Summaries (7 snapshots, latest commit 52c7544)

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

Previous review (commit 52c7544)

Status: No Issues Found | Recommendation: Merge

Incremental Review (since b486eb5)

The new commit (52c7544) adds a read-only "certificate details" panel to the device admin, surfacing the X.509 certificates generated for a device's certificate templates.

  • openwisp_controller/config/admin.py — Registers a certificate_details readonly field on ConfigInline that queries DeviceCertificate rows for the inline's config (with select_related on cert and template) and renders admin/config/device_certificates_table.html. The method guards the obj is None/new-instance case and the dc.cert is-null case. HTML output is produced via render_to_string, which returns a SafeString (same as the existing system_context readonly method), so the table renders correctly without escaping. Admin links are built with reverse, and template variables are auto-escaped by the template engine, so no XSS vector is introduced. A new regression/feature test is included.
  • admin/config/device_certificates_table.html — New template rendering the certificate table; uses i18n tags and auto-escaped variables. Safe.
  • tests/.../test_admin.py — Adds test_device_certificate_details_visible_in_admin, which creates a cert template + device config and asserts the template name, common name, cert UUID, status, and "View Certificate" link appear on the device change page. Coverage for the new feature is present.

No production request handlers, serializers, migrations, or tenant-filtering code paths are changed in this delta. No new critical bugs or security vulnerabilities were introduced.

Files Reviewed (incremental, 3 files)
  • openwisp_controller/config/admin.py - 0 issues
  • openwisp_controller/config/templates/admin/config/device_certificates_table.html - 0 issues
  • openwisp_controller/config/tests/test_admin.py - 0 issues

Previous review (commit b486eb5)

Status: No Issues Found | Recommendation: Merge

Incremental Review (since b52fe63)

The new commits are low-risk follow-ups:

  • docs/user/certificate-templates.rst — Fixes a broken image URL: the certificate-template screenshot path dropped the stale docs/docs/1.4/ segment and now points to docs/certificate-templates/certificate-template.png, matching the actual branch path. No behavior change.
  • tests/openwisp2/sample_users/migrations/0004_default_groups.py — Adds a RunPython step calling add_api_key_permissions_to_admins (reverse = noop) to seed API-key permissions for the admin group in the test sample app. Test scaffolding only.
  • tests/openwisp2/sample_users/migrations/0005_user_expiration_date_user_user_active_expiry_idx.py — Inserts a CreateModel for the ApiKey proxy model (bases=("authtoken.token",)) so the sample_users app ships a migration matching the proxy model declared in models.py. The proxy is created with empty indexes/constraints, consistent with a no-schema proxy.
  • tests/openwisp2/sample_users/models.py — Imports ApiKey from openwisp_users.base.models so the proxy model is registered with the app. Standard Django pattern for proxy models living in a base/swappable package.

All changes are confined to the test sample_users app and a docs image link; no production code paths, request handlers, serializers, or migrations under openwisp_controller/ are touched in this delta. No new critical bugs or security vulnerabilities were introduced.

Files Reviewed (incremental)
  • docs/user/certificate-templates.rst - 0 issues
  • tests/openwisp2/sample_users/migrations/0004_default_groups.py - 0 issues
  • tests/openwisp2/sample_users/migrations/0005_user_expiration_date_user_user_active_expiry_idx.py - 0 issues
  • tests/openwisp2/sample_users/models.py - 0 issues

Previous review (commit b52fe63)

Status: No Issues Found | Recommendation: Merge

Incremental Review (since fb54051)

The new commit [fix] Added missing organizational_unit_name is a focused one-line bug fix: the blueprint cert's organizational_unit_name was not being copied into the generated DeviceCertificate cert in copy_from (openwisp_controller/config/base/device_certificate.py:121). It is now included alongside the other copied fields.

A corresponding regression test, test_cert_generation_copies_blueprint_organizational_unit_name, is added in test_template.py. It creates a cert template bound to a blueprint cert with organizational_unit_name="Network Operations", provisions a device, and asserts the generated cert carries the same organizational_unit_name.

No new critical bugs or security vulnerabilities were introduced.

Files Reviewed (incremental)
  • openwisp_controller/config/base/device_certificate.py - 0 issues
  • openwisp_controller/config/tests/test_template.py - 0 issues

Previous review (commit fb54051)

Status: No Issues Found | Recommendation: Merge

Incremental Review (since 8c32282)

The new commit [tests] Improved coverage adds focused regression/coverage tests only — no production code changes. The added tests in test_device.py and test_template.py correctly reference existing symbols and exercise genuine code paths:

  • test_device_certificate_str_pending_generation — verifies the Pending Generation string for a DeviceCertificate with cert=None.
  • test_cert_used_as_blueprint_blocked — confirms a blueprint cert already bound to a device cannot be reassigned via DeviceCertificate.full_clean() (validation error on cert).
  • test_device_certificate_auto_x509_early_return — covers the early-return branch of _auto_x509() when a cert already exists.
  • test_device_certificate_common_name_format_fallback — exercises the common-name fallback format when device name equals mac address.
  • test_device_certificate_post_delete_object_does_not_exist — covers the ObjectDoesNotExist branch of post_delete.
  • test_regeneration_task_setting_disabled / test_regeneration_task_device_does_not_exist — cover the two early-return paths of regenerate_device_certificates_task.
  • test_regeneration_task_expected_cert_id_mismatch — verifies certs are not regenerated when expected_cert_ids do not match.
  • test_regeneration_task_notification_failure — verifies the notification failure is logged via logger.warning and does not crash the task.
  • test_auto_add_template_to_existing_configs_task_timeout — covers the SoftTimeLimitExceeded error path in the auto-add task.
  • test_get_value_for_comparison — covers the OrderedDict-to-dict normalization helper.
  • test_cert_template_config_none — verifies config=None is coerced to {} for cert templates.
  • test_validate_cert_template_changes_deleted_instance — confirms a stale (deleted) instance bypasses active-template validation cleanly.

No new critical bugs or security vulnerabilities were introduced.

Files Reviewed (incremental)
  • openwisp_controller/config/tests/test_device.py - 0 issues
  • openwisp_controller/config/tests/test_template.py - 0 issues
Carried-forward from prior review

All previously reviewed production files and their findings carry forward unchanged. Prior outstanding notes (cert-template validation regression test and template_ui.js browser coverage) were already resolved in earlier cycles.

Previous review (commit 8c32282)

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 (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 · Input: 170.8K · Output: 2.2K · Cached: 64K

@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.63%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 openwisp deleted a comment from openwisp-companion Bot Jun 23, 2026
Fixed image path for certficate generator templates
@openwisp openwisp deleted a comment from openwisp-companion Bot Jun 24, 2026
Added missing certificate details in the device admin

Fixes #1357
@openwisp openwisp deleted a comment from openwisp-companion Bot Jun 24, 2026
Fixed failing tests due to admin update
@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

Test Failure in test_device_with_config_change_deactivate_deactivate

Hello @stktyagi,
(Analysis for commit b9a9d2a)

There is one test failure:

  • Test Failure: The test test_device_with_config_change_deactivate_deactivate in openwisp_controller/config/tests/test_admin.py failed with an AssertionError. The response contained 24 instances of <div class="readonly"> when it expected 23.

  • Fix: This indicates an unexpected read-only field was rendered in the response. You'll need to investigate the test's setup and the DeviceAdmin configuration to identify which field is causing this discrepancy and correct it. Check the readonly_fields or any methods that dynamically add fields to the admin form in the DeviceAdmin or related mixins.

Fixed failing tests due to admin update
@stktyagi

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 26, 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: 5

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

Inline comments:
In `@docs/user/certificate-templates.rst`:
- Around line 34-36: The screenshot reference in the certificate templates docs
should follow the project’s versioned media convention. Update the image URL in
the certificate template documentation to point to the versioned directory used
for screenshots, keeping the same asset name but moving it under the appropriate
versioned feature folder. Use the existing documentation image references in the
same docs area as the pattern to match.

In `@openwisp_controller/config/api/serializers.py`:
- Around line 106-135: The normalization in the serializer’s template handling
is causing the mutation-protection lookup to run for non-cert templates even on
unrelated updates. In `ConfigSerializer`’s validation flow, preserve whether
`ca`, `blueprint_cert`, or `type` were originally present in the incoming data
before the `elif template_type != "cert"` branch clears them, and use those
original flags to gate the
`Config.objects.filter(templates=self.instance)...exists()` check so the
optimized path is not re-triggered for generic/VPN template edits.

In `@openwisp_controller/config/base/device_certificate.py`:
- Around line 107-123: In _clone_blueprint_attrs, the digest assignment is
asymmetric and unclear because the CA fallback is cast to string while the
blueprint path is not. Either normalize digest handling for both ca and
blueprint sources, or add a brief inline comment explaining why source.digest is
converted only in the fallback case and why that behavior is intentional. Keep
the logic centered on _clone_blueprint_attrs and the digest variable.

In `@openwisp_controller/config/handlers.py`:
- Around line 58-67: Add an early raw-save guard to both hardware-drift
receivers so they do nothing during fixture loading: in
capture_old_hardware_properties and the matching post_save receiver, return
immediately when raw is true before any DB lookup, caching, or
certificate-regeneration logic runs. Use the existing receiver functions and
their kwargs handling to locate the change, and keep the guard alongside the
current pk/update_fields checks.

In `@openwisp_controller/config/tests/test_device.py`:
- Around line 989-1002: The test in
test_device_certificate_common_name_format_fallback can pass with an ambient
COMMON_NAME_FORMAT that only uses mac_address, so make the fallback branch
explicit by patching the setting inside this test. Use the existing test setup
around DeviceCertificate.common_name and DeviceCertificate.objects.get to force
a format that would otherwise not start with the device MAC alone, then assert
the fallback behavior directly so the test validates the {mac_address}-{name}
path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 23011ea0-0a4d-48e0-9fa7-6c1abfe638a1

📥 Commits

Reviewing files that changed from the base of the PR and between 9ca18cc and 8efc405.

📒 Files selected for processing (31)
  • 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/templates/admin/config/device_certificates_table.html
  • 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/sample_config/tests.py
  • tests/openwisp2/settings.py
📜 Review details
⚠️ CI failures not shown inline (11)

GitHub Actions: OpenWISP Controller CI Build / 3_Python==3.12 django~=4.2.0.txt: [feature] Introduced standalone certificate templates and device bindings

Conclusion: failure

View job details

5 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
 Collecting propcache>=0.2.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (16 kB)
 Requirement already satisfied: typing_extensions>=4.4 in /opt/hostedtoolcache/Python/3.12.13/x64/lib/python3.12/site-packages (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0) (4.15.0)
 Collecting yarl<2.0,>=1.17.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached yarl-1.24.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (94 kB)
 Collecting geographiclib<3,>=1.52 (from geopy~=2.4.1->django-loci @ https://github.com/openwisp/django-loci/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
   Using cached geographiclib-2.1-py3-none-any.whl.metadata (1.6 kB)
 Requirement already satisfied: MarkupSafe>=2.0 in /opt/hostedtoolcache/Python/3.12.13/x64/lib/python3.12/site-packages (from jinja2~=3.1.6->netjsonconfig @ https://github.com/openwisp/netjsonconfig/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0) (3.0.3)
 Collecting jsonschema-specifications>=2023.03.6 (from jsonschema~=4.26.0->netjsonconfig @ https://github.com/openwisp/netjsonconfig/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
   Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl.metadata (2.9 kB)
 Collecting referencing>=0.28.4 (from jsonschema~=4.26.0->netjsonconfig @ https://github.com/openwisp/netjsonconfig/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
   Using cached referencing-0.37.0-py3-none-any.whl.metadata (2.8 kB)
 Collecting rpds-py>=0.25.0 (from jsonschema~=4.26.0->netjsonconfig @ h...

GitHub Actions: OpenWISP Controller CI Build / 8_Python==3.10 django~=4.2.0.txt: [feature] Introduced standalone certificate templates and device bindings

Conclusion: failure

View job details

nwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
   Using cached pytz-2026.2-py2.py3-none-any.whl.metadata (22 kB)
 Requirement already satisfied: pyyaml>=5.1 in /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages (from drf-yasg<1.22.0,>=1.21.14->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0) (6.0.3)
 Collecting uritemplate>=3.0.0 (from drf-yasg<1.22.0,>=1.21.14->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
   Using cached uritemplate-4.2.0-py3-none-any.whl.metadata (2.6 kB)
 Collecting aiohttp<4.0.0,>=3.6.2 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Downloading aiohttp-3.14.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (8.3 kB)
 Collecting maxminddb<4.0.0,>=3.0.0 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached maxminddb-3.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
 Collecting aiohappyeyeballs>=2.5.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached aiohappyeyeballs-2.6.2-py3-none-any.whl.metadata (5.9 kB)
 Collecting aiosignal>=1.4.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached aiosignal-1.4.0-py3-none-any.whl.metadata (3.7 kB)
 Requirement already satisfied: async-timeout<6.0,>=4.0 in /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0) (5.0.1)
 Requirement already satisfied: attrs>=17.3.0 in /opt/hostedtoolcache/Python/3.10.20/x...

GitHub Actions: OpenWISP Controller CI Build / 2_Python==3.10 django~=5.2.0.txt: [feature] Introduced standalone certificate templates and device bindings

Conclusion: failure

View job details

penwisp-controller==1.3a0)
   Using cached diff_match_patch-20241021-py3-none-any.whl.metadata (5.5 kB)
 Collecting tablib>=3.7.0 (from django-import-export~=4.4.1->openwisp-controller==1.3a0)
   Using cached tablib-3.9.0-py3-none-any.whl.metadata (3.8 kB)
 Requirement already satisfied: rjsmin<2.0.0,>=1.2.0 in /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages (from django-minify-compress-staticfiles~=1.1.1->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0) (1.2.5)
 Requirement already satisfied: rcssmin<2.0.0,>=1.1.0 in /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages (from django-minify-compress-staticfiles~=1.1.1->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0) (1.2.2)
 Requirement already satisfied: brotli<2.0.0,>=1.2.0 in /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages (from django-minify-compress-staticfiles~=1.1.1->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0) (1.2.0)
 Collecting inflection>=0.3.1 (from drf-yasg<1.22.0,>=1.21.14->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
   Using cached inflection-0.5.1-py2.py3-none-any.whl.metadata (1.7 kB)
 Collecting pytz>=2021.1 (from drf-yasg<1.22.0,>=1.21.14->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->o...

GitHub Actions: OpenWISP Controller CI Build / 6_Python==3.12 django~=5.2.0.txt: [feature] Introduced standalone certificate templates and device bindings

Conclusion: failure

View job details

ostedtoolcache/Python/3.12.13/x64/lib/python3.12/site-packages (from drf-yasg<1.22.0,>=1.21.14->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0) (6.0.3)
 Collecting uritemplate>=3.0.0 (from drf-yasg<1.22.0,>=1.21.14->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
   Using cached uritemplate-4.2.0-py3-none-any.whl.metadata (2.6 kB)
 Collecting aiohttp<4.0.0,>=3.6.2 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Downloading aiohttp-3.14.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (8.3 kB)
 Collecting maxminddb<4.0.0,>=3.0.0 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached maxminddb-3.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
 Collecting aiohappyeyeballs>=2.5.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached aiohappyeyeballs-2.6.2-py3-none-any.whl.metadata (5.9 kB)
 Collecting aiosignal>=1.4.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached aiosignal-1.4.0-py3-none-any.whl.metadata (3.7 kB)
 Requirement already satisfied: attrs>=17.3.0 in /opt/hostedtoolcache/Python/3.12.13/x64/lib/python3.12/site-packages (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0) (26.1.0)
 Collecting frozenlist>=1.1.1 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (20 kB)
 Collecting multidict<7.0,>=4.5 (from aiohttp<4.0.0,>=3.6....

GitHub Actions: OpenWISP Controller CI Build / 7_Python==3.12 django~=5.1.0.txt: [feature] Introduced standalone certificate templates and device bindings

Conclusion: failure

View job details

->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
   Using cached uritemplate-4.2.0-py3-none-any.whl.metadata (2.6 kB)
 Collecting aiohttp<4.0.0,>=3.6.2 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Downloading aiohttp-3.14.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (8.3 kB)
 Collecting maxminddb<4.0.0,>=3.0.0 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached maxminddb-3.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
 Collecting aiohappyeyeballs>=2.5.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached aiohappyeyeballs-2.6.2-py3-none-any.whl.metadata (5.9 kB)
 Collecting aiosignal>=1.4.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached aiosignal-1.4.0-py3-none-any.whl.metadata (3.7 kB)
 Requirement already satisfied: attrs>=17.3.0 in /opt/hostedtoolcache/Python/3.12.13/x64/lib/python3.12/site-packages (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0) (26.1.0)
 Collecting frozenlist>=1.1.1 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (20 kB)
 Collecting multidict<7.0,>=4.5 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
 Collecting propcache>=0.2.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28...

GitHub Actions: OpenWISP Controller CI Build / 1_Python==3.11 django~=4.2.0.txt: [feature] Introduced standalone certificate templates and device bindings

Conclusion: failure

View job details

linux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
 Collecting propcache>=0.2.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (16 kB)
 Requirement already satisfied: typing_extensions>=4.4 in /opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0) (4.15.0)
 Collecting yarl<2.0,>=1.17.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached yarl-1.24.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (94 kB)
 Collecting geographiclib<3,>=1.52 (from geopy~=2.4.1->django-loci @ https://github.com/openwisp/django-loci/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
   Using cached geographiclib-2.1-py3-none-any.whl.metadata (1.6 kB)
 Requirement already satisfied: MarkupSafe>=2.0 in /opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages (from jinja2~=3.1.6->netjsonconfig @ https://github.com/openwisp/netjsonconfig/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0) (3.0.3)
 Collecting jsonschema-specifications>=2023.03.6 (from jsonschema~=4.26.0->netjsonconfig @ https://github.com/openwisp/netjsonconfig/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
   Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl.metadata (2.9 kB)
 Collecting referencing>=0.28.4 (from jsonschema~=4.26.0->netjsonconfig @ https://github.com/openwisp/netjsonconfig/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
   Using cached referencing-0.37.0-py3-none-any.whl.metadata (2.8 kB)
 Collecting rpds-py>=0.25.0 (from jsonschema~=4.26.0->netjsonconfig @ https://github.com/openwisp/netjsonconfig/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
   Using cached rpds_py-2026.5.1-cp311-cp311-manyl...

GitHub Actions: OpenWISP Controller CI Build / 9_Python==3.13 django~=5.2.0.txt: [feature] Introduced standalone certificate templates and device bindings

Conclusion: failure

View job details

tz-2026.2-py2.py3-none-any.whl.metadata (22 kB)
 Requirement already satisfied: pyyaml>=5.1 in /opt/hostedtoolcache/Python/3.13.14/x64/lib/python3.13/site-packages (from drf-yasg<1.22.0,>=1.21.14->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0) (6.0.3)
 Collecting uritemplate>=3.0.0 (from drf-yasg<1.22.0,>=1.21.14->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
   Using cached uritemplate-4.2.0-py3-none-any.whl.metadata (2.6 kB)
 Collecting aiohttp<4.0.0,>=3.6.2 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached aiohttp-3.14.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (8.3 kB)
 Collecting maxminddb<4.0.0,>=3.0.0 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached maxminddb-3.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
 Collecting aiohappyeyeballs>=2.5.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached aiohappyeyeballs-2.6.2-py3-none-any.whl.metadata (5.9 kB)
 Collecting aiosignal>=1.4.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached aiosignal-1.4.0-py3-none-any.whl.metadata (3.7 kB)
 Requirement already satisfied: attrs>=17.3.0 in /opt/hostedtoolcache/Python/3.13.14/x64/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0) (26.1.0)
 Collecting frozenlist>=1.1.1 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_6...

GitHub Actions: OpenWISP Controller CI Build / 4_Python==3.11 django~=5.2.0.txt: [feature] Introduced standalone certificate templates and device bindings

Conclusion: failure

View job details

 already satisfied: pyyaml>=5.1 in /opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages (from drf-yasg<1.22.0,>=1.21.14->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0) (6.0.3)
 Collecting uritemplate>=3.0.0 (from drf-yasg<1.22.0,>=1.21.14->openwisp-utils @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-utils[celery,channels] @ https://github.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
   Using cached uritemplate-4.2.0-py3-none-any.whl.metadata (2.6 kB)
 Collecting aiohttp<4.0.0,>=3.6.2 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Downloading aiohttp-3.14.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (8.3 kB)
 Collecting maxminddb<4.0.0,>=3.0.0 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached maxminddb-3.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
 Collecting aiohappyeyeballs>=2.5.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached aiohappyeyeballs-2.6.2-py3-none-any.whl.metadata (5.9 kB)
 Collecting aiosignal>=1.4.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached aiosignal-1.4.0-py3-none-any.whl.metadata (3.7 kB)
 Requirement already satisfied: attrs>=17.3.0 in /opt/hostedtoolcache/Python/3.11.15/x64/lib/python3.11/site-packages (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0) (26.1.0)
 Collecting frozenlist>=1.1.1 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (20 kB)
 Collecting multi...

GitHub Actions: OpenWISP Controller CI Build / 5_Python==3.10 django~=5.1.0.txt: [feature] Introduced standalone certificate templates and device bindings

Conclusion: failure

View job details

 found for IP address 172.217.22.14
 WHOIS lookup failed. Details: Authentication failed for GeoIP2 service. Check your OPENWISP_CONTROLLER_WHOIS_GEOIP_ACCOUNT and OPENWISP_CONTROLLER_WHOIS_GEOIP_KEY settings.
 WHOIS lookup failed. Details: Your account does not have permission to access this service.
 WHOIS lookup failed. Details: Authentication failed for GeoIP2 service. Check your OPENWISP_CONTROLLER_WHOIS_GEOIP_ACCOUNT and OPENWISP_CONTROLLER_WHOIS_GEOIP_KEY settings.
 WHOIS lookup failed. Details: No WHOIS information found for IP address 172.217.22.14
 WHOIS lookup failed. Details: Your account has run out of queries for the GeoIP2 service.
 WHOIS lookup failed. Details: Your account does not have permission to access this service.
 WHOIS lookup failed. Details: Your account does not have permission to access this service.
 WHOIS lookup failed. Details: No WHOIS information found for IP address 172.217.22.14
 WHOIS lookup failed. Details: Your account has run out of queries for the GeoIP2 service.
 WHOIS lookup failed. Details: Authentication failed for GeoIP2 service. Check your OPENWISP_CONTROLLER_WHOIS_GEOIP_ACCOUNT and OPENWISP_CONTROLLER_WHOIS_GEOIP_KEY settings.
 ....................Failed to persist DeviceConnection 64e177dc-c0ef-4bf6-98bf-9af7a847af96
 ..An exception was raised while executing command 281db5ca-c02d-40c6-8129-4c4deb830ce9
 Traceback (most recent call last):
   File "/home/runner/work/openwisp-controller/openwisp-controller/openwisp_controller/connection/tasks.py", line 83, in launch_command
     command.execute()
   File "/home/runner/work/openwisp-controller/openwisp-controller/openwisp_controller/connection/tests/test_tasks.py", line 313, in _delete_then_raise
     raise RuntimeError("boom")
 RuntimeError: boom
 ........./home/runner/work/openwisp-controller/openwisp-controller/openwisp_controller/geo/estimated_location/service.py:70: AlwaysEagerIgnored: task_always_eager has no effect on send_task
   current_app.send_task(
 Creati...

GitHub Actions: OpenWISP Controller CI Build / 11_Python==3.13 django~=5.1.0.txt: [feature] Introduced standalone certificate templates and device bindings

Conclusion: failure

View job details

b.com/openwisp/openwisp-utils/archive/refs/heads/1.3.tar.gz->openwisp-controller==1.3a0)
   Using cached uritemplate-4.2.0-py3-none-any.whl.metadata (2.6 kB)
 Collecting aiohttp<4.0.0,>=3.6.2 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached aiohttp-3.14.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (8.3 kB)
 Collecting maxminddb<4.0.0,>=3.0.0 (from geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached maxminddb-3.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
 Collecting aiohappyeyeballs>=2.5.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached aiohappyeyeballs-2.6.2-py3-none-any.whl.metadata (5.9 kB)
 Collecting aiosignal>=1.4.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached aiosignal-1.4.0-py3-none-any.whl.metadata (3.7 kB)
 Requirement already satisfied: attrs>=17.3.0 in /opt/hostedtoolcache/Python/3.13.14/x64/lib/python3.13/site-packages (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0) (26.1.0)
 Collecting frozenlist>=1.1.1 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (20 kB)
 Collecting multidict<7.0,>=4.5 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
 Collecting propcache>=0.2.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
   Using cached propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (16 kB)
 Collecting yarl<2.0,>=1.17.0 (from aiohttp<4.0.0,>=3.6.2->geoip2<6.0.0,>=5.2.0->openwisp-controller==1.3a0)
 ...

GitHub Actions: OpenWISP Controller CI Build / 10_Python==3.11 django~=5.1.0.txt: [feature] Introduced standalone certificate templates and device bindings

Conclusion: failure

View job details

te_api_blueprint_already_assigned
 �[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_create_api_blueprint_ca_mismatch)�[0m
 �[33;1m  (0.44s)�[0m test_template_create_api_blueprint_ca_mismatch
 �[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_create_api_org_scoping)�[0m
 �[33;1m  (0.33s)�[0m test_template_create_api_org_scoping
 �[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_create_cert_blueprint_assignment)�[0m
 �[33;1m  (0.44s)�[0m test_template_create_cert_blueprint_assignment
 �[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_create_cert_type_api)�[0m
 �[33;1m  (0.43s)�[0m test_template_create_cert_type_api
 �[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_create_of_vpn_type)�[0m
 �[33;1m  (0.49s)�[0m test_template_create_of_vpn_type
 �[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_create_vpn_with_type_as_generic)�[0m
 �[33;1m  (0.75s)�[0m test_template_create_vpn_with_type_as_generic
 �[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_create_with_shared_vpn)�[0m
 �[33;1m  (0.79s)�[0m test_template_create_with_shared_vpn
 �[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_creation_with_no_org_by_operator)�[0m
 �[33;1m  (0.63s)�[0m test_template_creation_with_no_org_by_operator
 �[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_list_api_filter)�[0m
 �[33;1m  (0.54s)�[0m test_template_list_api_filter
 �[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_list_for_shared_objects)�[0m
 �[33;1m  (0.93s)�[0m test_template_list_for_shared_objects
 �[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_patch_api)�[0m
 �[33;1m  (0.32s)�[0m test_template_patch_api
 �[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_put_api)�[0m
 �[33;1m  (0.32s)�[0m test_template_put_api
 �[33;1m(openwisp2.sample_config.tests.TestConfigApi.test_template_update_api_active_change_blocked)�[0m
 �[33;1m  (0...
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}

📄 CodeRabbit inference engine (Custom checks)

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

Files:

  • tests/openwisp2/sample_config/tests.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/static/config/js/switcher.js
  • openwisp_controller/config/settings.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tasks.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_selenium.py
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp,sql}

📄 CodeRabbit inference engine (Custom checks)

Flag obvious performance regressions, such as heavy loops, repeated I/O, or unoptimized queries

Files:

  • tests/openwisp2/sample_config/tests.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/static/config/js/switcher.js
  • openwisp_controller/config/settings.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tasks.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_selenium.py
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp,sh,bash,sql}

📄 CodeRabbit inference engine (Custom checks)

Cryptic or non-obvious code (regex, complex bash commands, or hard-to-read code) must include a concise comment explaining why it is needed and why the complexity is acceptable

Files:

  • tests/openwisp2/sample_config/tests.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/static/config/js/switcher.js
  • openwisp_controller/config/settings.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tasks.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_selenium.py
**/*.{py,html}

📄 CodeRabbit inference engine (Custom checks)

For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework

Files:

  • tests/openwisp2/sample_config/tests.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/templates/admin/config/device_certificates_table.html
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/settings.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tasks.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_selenium.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • tests/openwisp2/sample_config/tests.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/settings.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tasks.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_selenium.py
🧠 Learnings (9)
📚 Learning: 2026-01-15T15:07:17.354Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/geo/estimated_location/tests/tests.py:172-175
Timestamp: 2026-01-15T15:07:17.354Z
Learning: In this repository, flake8 enforces E501 (line too long) via setup.cfg (max-line-length = 88) while ruff ignores E501 via ruff.toml. Therefore, use '# noqa: E501' on lines that intentionally exceed 88 characters to satisfy flake8 without affecting ruff checks. This applies to Python files across the project (any .py) and is relevant for tests as well. Use sparingly and only where breaking lines is not feasible without hurting readability or functionality.

Applied to files:

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

Applied to files:

  • tests/openwisp2/sample_config/tests.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/config/tests/test_config.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/pki/tests/test_api.py
  • tests/openwisp2/sample_config/migrations/0010_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/tests/test_device.py
  • openwisp_controller/config/tests/test_selenium.py
📚 Learning: 2026-06-25T12:20:18.414Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/base/models.py:571-572
Timestamp: 2026-06-25T12:20:18.414Z
Learning: When writing or reviewing tests that override pagination behavior via OpenWispPagination.paginate_queryset(), patch `view.pagination_page_size` (not `page_size`). The method uses `getattr(view, "pagination_page_size", self.page_size)`, so tests must set the attribute on the view to affect pagination. If the view class does not define `pagination_page_size`, using `unittest.mock.patch(..., create=True)` is intentional and correct because the attribute may not exist until patched.

Applied to files:

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

Applied to files:

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

Applied to files:

  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/models.py
  • openwisp_controller/config/settings.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tasks.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/admin.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_selenium.py
📚 Learning: 2026-02-17T19:13:10.088Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/whois/commands.py:0-0
Timestamp: 2026-02-17T19:13:10.088Z
Learning: In reviews for the openwisp/openwisp-controller repository, do not propose changes based on Ruff warnings. The project does not use Ruff as its linter; ignore Ruff-related suggestions and follow the repository’s established linting and configuration rules. This guidance applies to all Python files under the openwisp_controller directory.

Applied to files:

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

Applied to files:

  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_selenium.py
📚 Learning: 2026-06-25T12:20:45.387Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/tests/test_api.py:916-932
Timestamp: 2026-06-25T12:20:45.387Z
Learning: When reviewing API pagination behavior in openwisp-controller, assume `OpenWispPagination.paginate_queryset()` allows a per-view page-size override via `getattr(view, "pagination_page_size", self.page_size)` (so `view.pagination_page_size`, if present, should affect pagination). In Python tests, it is valid to patch `pagination_page_size` on a view class even if the attribute isn’t declared on the class by default, by using `unittest.mock.patch.object(..., "pagination_page_size", ..., create=True)` so the override is available for the pagination logic during the test.

Applied to files:

  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_selenium.py
📚 Learning: 2026-01-12T22:27:40.078Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1175
File: tests/openwisp2/sample_config/migrations/0008_whoisinfo_organizationconfigsettings_whois_enabled.py:18-67
Timestamp: 2026-01-12T22:27:40.078Z
Learning: In test migrations under tests/openwisp2/sample_config/migrations, verify scenarios where a swappable model (CONFIG_WHOISINFO_MODEL) is extended with extra fields (e.g., an additional 'details' field) to ensure compatibility and no errors when swapping to a custom implementation. This pattern helps confirm that extending AbstractWHOISInfo via a custom model works as intended.

Applied to files:

  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
🪛 ast-grep (0.44.0)
openwisp_controller/config/handlers.py

[warning] 94-94: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("config", "DeviceCertificate")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)

openwisp_controller/config/tasks.py

[warning] 232-232: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("config", "Device")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)


[warning] 233-233: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("config", "DeviceCertificate")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)

tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py

[info] 106-106: use help_text to document model columns
Context: models.CharField(blank=True, max_length=64, null=True)
Note: [CWE-710] Improper Adherence to Coding Standards.

(model-help-text)

openwisp_controller/config/base/template.py

[warning] 40-40: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("django_x509", "Cert")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)


[warning] 41-41: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("config", "DeviceCertificate")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)


[warning] 274-274: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("config", "Config")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)


[warning] 328-328: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("config", "DeviceCertificate")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)

openwisp_controller/config/base/device_certificate.py

[warning] 41-41: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("config", "Template")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)

openwisp_controller/config/tests/test_device.py

[warning] 30-30: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("django_x509", "Cert")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)


[warning] 31-31: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("django_x509", "Ca")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)


[warning] 32-32: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("config", "OrganizationConfigSettings")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)

openwisp_controller/config/tests/test_selenium.py

[warning] 25-25: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("openwisp_notifications", "Notification")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)

🪛 HTMLHint (1.9.2)
openwisp_controller/config/templates/admin/config/device_certificates_table.html

[error] 1-1: Doctype must be declared before any non-comment content.

(doctype-first)

Comment thread docs/user/certificate-templates.rst
Comment thread openwisp_controller/config/api/serializers.py
Comment thread openwisp_controller/config/base/device_certificate.py
Comment thread openwisp_controller/config/handlers.py
Comment thread openwisp_controller/config/tests/test_device.py
stktyagi added 2 commits June 29, 2026 20:37
Addressed coderabbit's minor improvements
Removed blueprint check before str conversion
Added missing documentation promised in proposal
@stktyagi

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot added the docs Related to documentation label Jun 29, 2026

@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: 5

Caution

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

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

13-39: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Re-show the VPN helper when switching back to vpn.

After vpn -> cert/generic -> vpn, .autovpn already exists, so this branch skips creation and never shows the hidden helper again. The JSON editor stays visible even though the empty-VPN flow should restore the “save to auto-generate” message.

💡 Minimal fix
     toggle_specific_fields = function (changed) {
       var val = type_select.val();
       if (val === "vpn") {
         vpn_specific.show();
-        if (
-          changed === true &&
-          $(".autovpn").length < 1 &&
-          $("`#id_config`").val() === "{}"
-        ) {
-          var p1 = gettext(
-              "Click on Save to automatically generate the " +
-                "VPN client configuration (will be based on " +
-                "the configuration of the server).",
-            ),
-            p2 = gettext(
-              "You can then tweak the VPN client " + "configuration in the next step.",
-            );
-          $(".jsoneditor-wrapper").hide().after('<div class="form-row autovpn"></div>');
-          $(".autovpn").html(
-            "<p><strong>" + p1 + "</strong></p>" + "<p><strong>" + p2 + "</strong></p>",
-          );
+        if ($("`#id_config`").val() === "{}") {
+          if (changed === true && $(".autovpn").length < 1) {
+            var p1 = gettext(
+                "Click on Save to automatically generate the " +
+                  "VPN client configuration (will be based on " +
+                  "the configuration of the server).",
+              ),
+              p2 = gettext(
+                "You can then tweak the VPN client " +
+                  "configuration in the next step.",
+              );
+            $(".jsoneditor-wrapper").after('<div class="form-row autovpn"></div>');
+            $(".autovpn").html(
+              "<p><strong>" + p1 + "</strong></p>" +
+                "<p><strong>" + p2 + "</strong></p>",
+            );
+          }
+          $(".jsoneditor-wrapper").hide();
+          $(".autovpn").show();
         }
       } else {
         vpn_specific.hide();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openwisp_controller/config/static/config/js/switcher.js` around lines 13 -
39, The toggle_specific_fields handler in switcher.js hides the VPN helper when
leaving vpn, but when switching back to vpn it only creates .autovpn the first
time and never re-shows it if it already exists. Update the vpn branch so that
after vpn -> cert/generic -> vpn the existing .autovpn element is shown again
and the jsoneditor-wrapper stays hidden for the empty-vpn flow; use the
toggle_specific_fields, vpn_specific, jsoneditor-wrapper, and .autovpn logic to
restore the helper whenever val === "vpn" and the config is still {}.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/user/certificate-templates.rst`:
- Around line 106-109: The renewal example in the certificate templates docs
uses the old PKI endpoint path, which conflicts with the documented REST API.
Update the standalone certificate renewal example in the paragraph referencing
Cert.renew() to use the /api/v1/controller/cert/{id}/renew/ path so it matches
docs/user/rest-api.rst and stays consistent with the rest of the API
documentation.
- Around line 241-243: The cross-reference in the certificate templates docs
points to a missing REST anchor, so update the reference in the related prose to
use an existing target from the REST docs or add the missing anchor in the REST
endpoint documentation. Verify the link target used by the Device endpoint
reference resolves from docs/user/rest-api.rst, and keep the wording in
certificate-templates.rst consistent with the actual endpoint name.

In `@openwisp_controller/config/admin.py`:
- Around line 458-463: The new admin label for certificate_details is currently
falling back to the method name and bypassing Django i18n. Update the
certificate_details admin accessor in admin.py to use an explicit translatable
description, such as via `@admin.display`(description=_("Certificate details")),
and ensure any related admin text in the affected admin class follows the same
pattern.

In `@openwisp_controller/config/api/serializers.py`:
- Around line 87-171: The validate() method in the serializer is duplicating
cert-template rules that already exist in
AbstractTemplate._clean_cert_template() and _validate_cert_template_changes(),
so the API path can drift from model/admin validation. Refactor this logic to
centralize the CA requirement, CA/blueprint compatibility, and active-template
mutation locks in a shared helper or rely on the model validation path, and keep
validate() in ConfigSerializer as a thin normalization layer that delegates to
the shared cert-template checks.

In `@openwisp_controller/config/base/template.py`:
- Around line 319-341: The template validation in clean() only checks that
blueprint_cert is unassigned and matches the CA, so revoked certificates can
still pass model-level validation. Update the Template.clean logic to explicitly
reject revoked blueprint certificates using the same rules as
get_unassigned_certs(), and keep the validation tied to the
blueprint_cert/blueprint_cert_id checks so Template(...,
blueprint_cert=revoked_cert).full_clean() fails consistently.

---

Outside diff comments:
In `@openwisp_controller/config/static/config/js/switcher.js`:
- Around line 13-39: The toggle_specific_fields handler in switcher.js hides the
VPN helper when leaving vpn, but when switching back to vpn it only creates
.autovpn the first time and never re-shows it if it already exists. Update the
vpn branch so that after vpn -> cert/generic -> vpn the existing .autovpn
element is shown again and the jsoneditor-wrapper stays hidden for the empty-vpn
flow; use the toggle_specific_fields, vpn_specific, jsoneditor-wrapper, and
.autovpn logic to restore the helper whenever val === "vpn" and the config is
still {}.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 73cb1f6d-d963-4deb-a0ed-70050aed6bcf

📥 Commits

Reviewing files that changed from the base of the PR and between 9ca18cc and 147c4b5.

📒 Files selected for processing (31)
  • 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/templates/admin/config/device_certificates_table.html
  • 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/sample_config/tests.py
  • tests/openwisp2/settings.py
📜 Review details
⏰ Context from checks skipped due to timeout. (11)
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.13 | django~=5.2.0
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp}

📄 CodeRabbit inference engine (Custom checks)

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

Files:

  • tests/openwisp2/sample_config/tests.py
  • openwisp_controller/config/settings.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/tests/test_config.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/static/config/js/switcher.js
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/models.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_admin.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/tasks.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/admin.py
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp,sql}

📄 CodeRabbit inference engine (Custom checks)

Flag obvious performance regressions, such as heavy loops, repeated I/O, or unoptimized queries

Files:

  • tests/openwisp2/sample_config/tests.py
  • openwisp_controller/config/settings.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/tests/test_config.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/static/config/js/switcher.js
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/models.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_admin.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/tasks.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/admin.py
**/*.{js,ts,tsx,jsx,py,java,go,cs,rb,php,c,cpp,h,hpp,sh,bash,sql}

📄 CodeRabbit inference engine (Custom checks)

Cryptic or non-obvious code (regex, complex bash commands, or hard-to-read code) must include a concise comment explaining why it is needed and why the complexity is acceptable

Files:

  • tests/openwisp2/sample_config/tests.py
  • openwisp_controller/config/settings.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/tests/test_config.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/static/config/js/switcher.js
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/models.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_admin.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/tasks.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/admin.py
**/*.{py,html}

📄 CodeRabbit inference engine (Custom checks)

For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework

Files:

  • tests/openwisp2/sample_config/tests.py
  • openwisp_controller/config/settings.py
  • openwisp_controller/config/templates/admin/config/device_certificates_table.html
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/tests/test_config.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/models.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_admin.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/tasks.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/admin.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • tests/openwisp2/sample_config/tests.py
  • openwisp_controller/config/settings.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/tests/test_config.py
  • tests/openwisp2/settings.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/models.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_admin.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/tasks.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/admin.py
🧠 Learnings (9)
📚 Learning: 2026-01-15T15:07:17.354Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/geo/estimated_location/tests/tests.py:172-175
Timestamp: 2026-01-15T15:07:17.354Z
Learning: In this repository, flake8 enforces E501 (line too long) via setup.cfg (max-line-length = 88) while ruff ignores E501 via ruff.toml. Therefore, use '# noqa: E501' on lines that intentionally exceed 88 characters to satisfy flake8 without affecting ruff checks. This applies to Python files across the project (any .py) and is relevant for tests as well. Use sparingly and only where breaking lines is not feasible without hurting readability or functionality.

Applied to files:

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

Applied to files:

  • tests/openwisp2/sample_config/tests.py
  • openwisp_controller/config/tests/test_config.py
  • tests/openwisp2/settings.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_admin.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_template.py
📚 Learning: 2026-06-25T12:20:18.414Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/base/models.py:571-572
Timestamp: 2026-06-25T12:20:18.414Z
Learning: When writing or reviewing tests that override pagination behavior via OpenWispPagination.paginate_queryset(), patch `view.pagination_page_size` (not `page_size`). The method uses `getattr(view, "pagination_page_size", self.page_size)`, so tests must set the attribute on the view to affect pagination. If the view class does not define `pagination_page_size`, using `unittest.mock.patch(..., create=True)` is intentional and correct because the attribute may not exist until patched.

Applied to files:

  • tests/openwisp2/sample_config/tests.py
  • openwisp_controller/config/tests/test_config.py
  • tests/openwisp2/settings.py
  • tests/openwisp2/sample_config/models.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_admin.py
  • tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py
  • 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:

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

Applied to files:

  • openwisp_controller/config/settings.py
  • openwisp_controller/config/apps.py
  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/config/api/serializers.py
  • openwisp_controller/config/models.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/migrations/0064_template_blueprint_cert_template_ca_and_more.py
  • openwisp_controller/config/handlers.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/config/base/template.py
  • openwisp_controller/config/tasks.py
  • openwisp_controller/config/base/config.py
  • openwisp_controller/config/base/device_certificate.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_template.py
  • openwisp_controller/config/admin.py
📚 Learning: 2026-02-17T19:13:10.088Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/whois/commands.py:0-0
Timestamp: 2026-02-17T19:13:10.088Z
Learning: In reviews for the openwisp/openwisp-controller repository, do not propose changes based on Ruff warnings. The project does not use Ruff as its linter; ignore Ruff-related suggestions and follow the repository’s established linting and configuration rules. This guidance applies to all Python files under the openwisp_controller directory.

Applied to files:

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

Applied to files:

  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/config/tests/test_device.py
  • openwisp_controller/config/tests/test_template.py
📚 Learning: 2026-06-25T12:20:45.387Z
Learnt from: dee077
Repo: openwisp/openwisp-controller PR: 1395
File: openwisp_controller/connection/tests/test_api.py:916-932
Timestamp: 2026-06-25T12:20:45.387Z
Learning: When reviewing API pagination behavior in openwisp-controller, assume `OpenWispPagination.paginate_queryset()` allows a per-view page-size override via `getattr(view, "pagination_page_size", self.page_size)` (so `view.pagination_page_size`, if present, should affect pagination). In Python tests, it is valid to patch `pagination_page_size` on a view class even if the attribute isn’t declared on the class by default, by using `unittest.mock.patch.object(..., "pagination_page_size", ..., create=True)` so the override is available for the pagination logic during the test.

Applied to files:

  • openwisp_controller/config/tests/test_config.py
  • openwisp_controller/pki/tests/test_api.py
  • openwisp_controller/config/tests/test_selenium.py
  • openwisp_controller/config/tests/test_api.py
  • openwisp_controller/config/tests/test_admin.py
  • openwisp_controller/config/tests/test_device.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
🪛 ast-grep (0.44.0)
openwisp_controller/config/tests/test_selenium.py

[warning] 25-25: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("openwisp_notifications", "Notification")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)

openwisp_controller/config/handlers.py

[warning] 98-98: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("config", "DeviceCertificate")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)

tests/openwisp2/sample_config/migrations/0010_template_blueprint_cert_template_ca_and_more.py

[info] 106-106: use help_text to document model columns
Context: models.CharField(blank=True, max_length=64, null=True)
Note: [CWE-710] Improper Adherence to Coding Standards.

(model-help-text)

openwisp_controller/config/base/template.py

[warning] 40-40: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("django_x509", "Cert")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)


[warning] 41-41: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("config", "DeviceCertificate")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)


[warning] 274-274: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("config", "Config")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)


[warning] 328-328: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("config", "DeviceCertificate")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)

openwisp_controller/config/tasks.py

[warning] 232-232: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("config", "Device")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)


[warning] 233-233: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("config", "DeviceCertificate")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)

openwisp_controller/config/base/device_certificate.py

[warning] 41-41: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("config", "Template")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)

openwisp_controller/config/tests/test_device.py

[warning] 30-30: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("django_x509", "Cert")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)


[warning] 31-31: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("django_x509", "Ca")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)


[warning] 32-32: Loading a Keras model from an untrusted file can execute arbitrary code via Lambda layers or custom objects. Load only trusted models and avoid deserializing custom objects from untrusted sources.
Context: load_model("config", "OrganizationConfigSettings")
Note: [CWE-502] Deserialization of Untrusted Data.

(keras-load-model-python)

🪛 HTMLHint (1.9.2)
openwisp_controller/config/templates/admin/config/device_certificates_table.html

[error] 1-1: Doctype must be declared before any non-comment content.

(doctype-first)

🔇 Additional comments (10)
openwisp_controller/config/base/template.py (1)

267-299: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Guard active * -> cert type flips too, or reconcile existing assignments.

Line 270 only blocks cert -> *. An already-assigned active template can still be switched from generic/vpn to cert, but the save hooks in this file only recompute config status; they do not create DeviceCertificate rows for the existing Config.templates relations. That leaves active configs attached to a certificate template without the cert relation/context until the template is removed and re-added. As per coding guidelines, preserve validation around templates.

Suggested fix
-        changing_protected_fields = (
-            current.ca_id != self.ca_id
-            or current.blueprint_cert_id != self.blueprint_cert_id
-            or (current.type == "cert" and self.type != "cert")
-        )
+        changing_protected_fields = (
+            current.ca_id != self.ca_id
+            or current.blueprint_cert_id != self.blueprint_cert_id
+            or (current.type != self.type and "cert" in {current.type, self.type})
+        )
@@
-        if current.type == "cert" and self.type != "cert":
+        if current.type != self.type and "cert" in {current.type, self.type}:
             errors["type"] = _(
                 "This template is already assigned to active devices. "
-                "You cannot change the template type from certificate "
+                "You cannot change the template type to or from certificate "
                 "on an active template."
             )
			> Likely an incorrect or invalid review comment.

Source: Coding guidelines

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

125-147: Custom hardware OIDs are still encoded in a django-x509-incompatible shape.

_build_extensions() appends {"oid": ..., "value": ..., "critical": ...} items, but the current django-x509 validation path expects extension dicts keyed by name. That still leaves certificate creation failing with Unsupported extension: None, so template assignment and hardware-drift regeneration remain blocked until these OIDs are emitted in a format supported by the repo-pinned django-x509 version. Please verify this against the pinned dependency before merging.

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

864-871: LGTM!

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

99-105: LGTM!

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

155-155: LGTM!

Also applies to: 256-256, 267-275, 292-292

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

25-26: LGTM!

Also applies to: 491-562, 846-878

openwisp_controller/config/handlers.py (1)

48-114: LGTM!

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

11-11: LGTM!

Also applies to: 53-53, 1806-1830, 2363-2364, 2458-2458

openwisp_controller/config/tasks.py (1)

226-294: LGTM!

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

66-72: LGTM!

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

Comment thread docs/user/certificate-templates.rst Outdated
Comment thread docs/user/certificate-templates.rst Outdated
Comment thread openwisp_controller/config/admin.py Outdated
Comment thread openwisp_controller/config/api/serializers.py
Comment thread openwisp_controller/config/base/template.py
Addressed improvements and docs correction.
@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 resolve

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Comments resolved and changes approved.

stktyagi added 2 commits June 30, 2026 21:45
Changed certificate details to a separate tab in device admin
Fixed failed tests after changes
@openwisp openwisp deleted a comment from openwisp-companion Bot Jun 30, 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).

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

Labels

docs Related to documentation enhancement gsoc Part of a Google Summer of Code project

Projects

Development

Successfully merging this pull request may close these issues.

4 participants