Skip to content

[change] Make menu item registration idempotent #641#714

Merged
nemesifier merged 5 commits into
openwisp:masterfrom
CodingWithSaksham:issues/445-make-menu-item-registration-idempotent
Jul 18, 2026
Merged

[change] Make menu item registration idempotent #641#714
nemesifier merged 5 commits into
openwisp:masterfrom
CodingWithSaksham:issues/445-make-menu-item-registration-idempotent

Conversation

@CodingWithSaksham

@CodingWithSaksham CodingWithSaksham commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Re-initializing the Django app registery calls AppConfig.ready() again which re-invokes register_menu_group() / register_menu_subitem() with the same config. Both functions unconditionally raised ImproperlyConfigured whenever the target position was already occupied, even when it was the exact same app re-registering the exact same item, making it impossible to survive repeated app registry population.

Both functions now compare the incoming config against the config of the item already registered at that position and log at INFO level if identical config is found Closes

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 #641 .

Description of Changes

  • Previously, calling register_menu_group/register_menu_subitem twice with the same config at the same position always errored, even with app reloads or repeated ready() calls. Now each menu item stores its config, and both functions check for an identical config before raising, if it matches, they log an INFO message and skip instead of erroring.
  • Tests added for the new skip-and-log behavior.

CodingWithSaksham and others added 2 commits July 4, 2026 22:20
Re-initializing the Django app registery calls `AppConfig.ready()` again which re-invokes `register_menu_group()` / `register_menu_subitem()` with the same config. Both functions unconditionally raised `ImproperlyConfigured` whenever the target position was already occupied, even when it was the exact same app re-registering the exact same item, making it impossible to survive repeated app registry population.

Both functions now compare the incoming config against the config of the item already registered at that position and log at INFO level if identical config is found
Closes openwisp#641
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: de8fd380-3185-4a4b-be0d-e904712cc871

📥 Commits

Reviewing files that changed from the base of the PR and between 9f3639a and 4ba9ff0.

📒 Files selected for processing (3)
  • docs/developer/navigation-menu.rst
  • openwisp_utils/admin_theme/menu.py
  • tests/test_project/tests/test_menu.py
📜 Recent review details
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{md,rst,txt}

📄 CodeRabbit inference engine (AGENTS.md)

Update documentation when behavior, settings, public APIs, setup steps, QA rules, or supported versions change

Files:

  • docs/developer/navigation-menu.rst
**/*

📄 CodeRabbit inference engine (Custom checks)

**/*: For bug fixes in core user-facing functionality, fix the root cause rather than hiding or bypassing the problem.
Bug fixes must include a regression test that reproduces the bug and fails when the fix is removed, except for valid exceptions such as one-line fixes or impractical GitHub Actions workflow fixes.
User-interface bug fixes must include a Selenium browser regression test; missing coverage should be reported as a warning.
Tests must be deterministic and non-flaky; avoid dependence on timing, sleeps, system time, randomness without a fixed seed, race conditions, network services, filesystem state, environment-specific configuration, execution order, shared global state, hardcoded ports, or improperly awaited asynchronous operations.
When a change request modifies documented behavior, update the documentation to describe the new behavior.
Pull requests containing UI changes must include screenshots or screen recordings in the pull request description.
Update tests for non-trivial behavior changes and validate the modified behavior.
Flag changes that may break existing behavior or public APIs unless a clear migration path is provided.
Feature requests must have an issue explaining the feature, validated and accepted by an organization member through labels or project assignment.
New features must be documented; heavily UI-related features should have a dedicated documentation section or page.
Add tests for new features without significantly decreasing coverage; prefer Selenium browser tests for UI-impacting features.
Non-organization members must target maintainer-validated issues with at least one label other than wontfix or invalid and assignment to either the OpenWISP Contributor's Board or OpenWISP Priorities for next releases project.
Flag potential security vulnerabilities.
Flag obvious performance regressions such as heavy loops, repeated I/O, or unoptimized queries.
Cryptic or non-obvious code, including regexes, complex shell commands, and ...

Files:

  • docs/developer/navigation-menu.rst
  • openwisp_utils/admin_theme/menu.py
  • tests/test_project/tests/test_menu.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Place imports at the top of the file; only defer imports when necessary (e.g., Django model imports inside functions or methods where the app registry is not yet ready)
Avoid unnecessary blank lines inside function and method bodies
Add or update tests for every behavior change
Run openwisp-qa-format after editing
Prefer in-process tests so coverage tools can measure changed code
When checking coverage for a changed module, use python -m pytest <test_path> --cov=<dotted.module.path> --cov-report=term-missing
Watch for unsafe file paths, unsafe subprocess usage, token or secret exposure, and changes that could weaken QA or release safeguards
Write comments and docstrings only when they explain why code is shaped a certain way; place comments before the relevant code block instead of scattering them inside it

In Django code, mark all user-facing strings as translatable using the Django internationalization framework.

Files:

  • openwisp_utils/admin_theme/menu.py
  • tests/test_project/tests/test_menu.py
**/tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

For bug fixes, write the regression test first, run it against the unfixed code, confirm it fails for the expected reason, then implement the fix

Files:

  • tests/test_project/tests/test_menu.py
🧠 Learnings (1)
📚 Learning: 2026-06-15T22:45:01.529Z
Learnt from: nemesifier
Repo: openwisp/openwisp-utils PR: 701
File: setup.py:72-72
Timestamp: 2026-06-15T22:45:01.529Z
Learning: When reviewing changes that require a minimum Selenium Python version for specific APIs (e.g., BiDi features like `Options.enable_bidi`, console/message handlers like `Script.add_console_message_handler`, or event handlers like `BrowsingContext.add_event_handler`), do not use the Selenium API docs site (`https://www.selenium.dev/selenium/docs/api/py/`) as the source of introduction/version history, since it reflects the latest API shape. Instead, confirm feature availability by checking the official Python changelog (`py/CHANGES` in the Selenium repo) and/or inspecting the Selenium source (tags/commits corresponding to candidate versions). Only accept the stated minimum version after verifying that the referenced APIs exist in that Selenium version (e.g., Selenium 4.32.0 includes the BiDi APIs used by openwisp-utils’ BiDi-related PR `#701` as of that version).

Applied to files:

  • openwisp_utils/admin_theme/menu.py
  • tests/test_project/tests/test_menu.py
🔇 Additional comments (3)
openwisp_utils/admin_theme/menu.py (1)

1-11: LGTM!

Also applies to: 26-26, 67-68, 192-197, 257-263

docs/developer/navigation-menu.rst (1)

100-105: LGTM!

Also applies to: 285-288

tests/test_project/tests/test_menu.py (1)

91-116: LGTM!

Also applies to: 172-174, 187-198


📝 Walkthrough

Walkthrough

The change makes menu registration idempotent when identical configurations are registered at occupied group or item positions. BaseMenuItem stores configuration, registration functions log and return for exact matches, and documentation and tests cover duplicate and conflicting registrations.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant register_menu_group
  participant register_menu_subitem
  participant MENU
  participant logger

  Caller->>register_menu_group: register position and config
  register_menu_group->>MENU: compare existing group config
  alt identical config
    register_menu_group->>logger: log "already registered"
    register_menu_group-->>Caller: return
  else different config
    register_menu_group->>MENU: raise configuration conflict
  end

  Caller->>register_menu_subitem: register item position and config
  register_menu_subitem->>MENU: compare existing subitem config
  alt identical config
    register_menu_subitem->>logger: log "already registered"
    register_menu_subitem-->>Caller: return
  else different config
    register_menu_subitem->>MENU: raise configuration conflict
  end
Loading
🚥 Pre-merge checks | ✅ 8
✅ Passed checks (8 passed)
Check name Status Explanation
Title check ✅ Passed The title is descriptive, uses the required [change] prefix, and matches the main idempotent menu registration change.
Description check ✅ Passed The description includes the checklist, issue reference, and change summary; only the screenshot section is missing.
Linked Issues check ✅ Passed The code makes menu registration idempotent for identical configs at occupied positions and preserves conflict errors for mismatches.
Out of Scope Changes check ✅ Passed The added tests and documentation are directly related to the idempotent registration change and appear in scope.
Bug Fixes ✅ Passed Root cause is fixed by comparing configs and skipping identical re-registrations; deterministic regression tests reproduce the bug and fail without the patch.
Features ✅ Passed PASS: issue #641 is open with enhancement label and project-board status; docs mention idempotency; tests add the new skip-and-log cases.
Changes ✅ Passed Docs reflect the new no-op/INFO behavior, tests cover idempotent re-registration, and there are no UI changes or API-breaking updates.
General Rules ✅ Passed Issue #641 has an enhancement label and is on OpenWISP Contributor's Board; code/tests/docs match the idempotent logging behavior.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coveralls

coveralls commented Jul 4, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 97.645% (+0.004%) from 97.641% — CodingWithSaksham:issues/445-make-menu-item-registration-idempotent into openwisp:master

@CodingWithSaksham

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 `@tests/test_project/tests/test_menu.py`:
- Around line 91-100: Add idempotency coverage for occupied positions that
already contain non-MenuLink entries by extending the tests around
register_menu_group and the MENU registry. Create a subtest that re-registers
the same MenuGroup config at an already occupied top-level position, and
optionally a ModelLink config as well, then assert the existing entry remains
the same type and logs the “already registered” message. This should verify that
MenuGroup and ModelLink instances expose .config correctly and that
register_menu_group handles re-registration consistently for all stored menu
item 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: 555b58aa-6d60-4431-b810-e704b6ec5e1f

📥 Commits

Reviewing files that changed from the base of the PR and between 9f3639a and 985ea16.

📒 Files selected for processing (2)
  • openwisp_utils/admin_theme/menu.py
  • tests/test_project/tests/test_menu.py
📜 Review details
⚠️ CI failures not shown inline (14)

GitHub Actions: OpenWISP Utils CI Build / 2_Python==3.13 django~=5.2.0.txt: [change] Make menu item registration idempotent #641

Conclusion: failure

View job details

5-py3-none-any.whl (19 kB)
 Using cached pytest-9.1.1-py3-none-any.whl (386 kB)
 Using cached pluggy-1.6.0-py3-none-any.whl (20 kB)
 Using cached pytest_mock-3.15.1-py3-none-any.whl (10 kB)
 Using cached aiohappyeyeballs-2.7.1-py3-none-any.whl (15 kB)
 Using cached aiosignal-1.4.0-py3-none-any.whl (7.5 kB)
 Using cached attrs-26.1.0-py3-none-any.whl (67 kB)
 Using cached bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (278 kB)
 Using cached certifi-2026.6.17-py3-none-any.whl (133 kB)
 Using cached cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB)
 Using cached click_didyoumean-0.3.1-py3-none-any.whl (3.6 kB)
 Using cached click_plugins-1.1.1.2-py2.py3-none-any.whl (11 kB)
 Using cached click_repl-0.3.0-py3-none-any.whl (10 kB)
 Using cached daphne-4.2.2-py3-none-any.whl (29 kB)
 Using cached autobahn-26.6.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.2 MB)
 Downloading cbor2-6.1.3-cp313-cp313-manylinux_2_28_x86_64.whl (467 kB)
 Using cached frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (234 kB)
 Using cached hyperlink-21.0.0-py2.py3-none-any.whl (74 kB)
 Using cached inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
 Using cached iniconfig-2.3.0-py3-none-any.whl (7.5 kB)
 Using cached invoke-3.0.3-py3-none-any.whl (160 kB)
 Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB)
 Using cached kombu-5.6.2-py3-none-any.whl (214 kB)
 Using cached amqp-5.3.1-py3-none-any.whl (50 kB)
 Using cached markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
 Using cached prompt_toolkit-3.0.52-py3-none-any.whl (391 kB)
 Using cached propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (61 kB)
 Using cached pygments-2.20.0-py3-none-any.whl (1.2 MB)
 Using cached pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB)
 Using cached python_dateutil-2.9.0.post0-py2.py3-none-...

GitHub Actions: OpenWISP Utils CI Build / 4_Python==3.12 django~=5.2.0.txt: [change] Make menu item registration idempotent #641

Conclusion: failure

View job details

e-any.whl (10 kB)
 Using cached swapper-1.4.0-py2.py3-none-any.whl (7.1 kB)
 Using cached urllib3-2.7.0-py3-none-any.whl (131 kB)
 Using cached vine-5.1.0-py3-none-any.whl (9.6 kB)
 Using cached yarl-1.24.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (105 kB)
 Using cached freezegun-1.5.5-py3-none-any.whl (19 kB)
 Using cached pytest-9.1.1-py3-none-any.whl (386 kB)
 Using cached pluggy-1.6.0-py3-none-any.whl (20 kB)
 Using cached pytest_mock-3.15.1-py3-none-any.whl (10 kB)
 Using cached aiohappyeyeballs-2.7.1-py3-none-any.whl (15 kB)
 Using cached aiosignal-1.4.0-py3-none-any.whl (7.5 kB)
 Using cached attrs-26.1.0-py3-none-any.whl (67 kB)
 Using cached bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (278 kB)
 Using cached certifi-2026.6.17-py3-none-any.whl (133 kB)
 Using cached cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB)
 Using cached click_didyoumean-0.3.1-py3-none-any.whl (3.6 kB)
 Using cached click_plugins-1.1.1.2-py2.py3-none-any.whl (11 kB)
 Using cached click_repl-0.3.0-py3-none-any.whl (10 kB)
 Using cached daphne-4.2.2-py3-none-any.whl (29 kB)
 Using cached autobahn-26.6.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.2 MB)
 Downloading cbor2-6.1.3-cp312-cp312-manylinux_2_28_x86_64.whl (468 kB)
 Using cached frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (242 kB)
 Using cached hyperlink-21.0.0-py2.py3-none-any.whl (74 kB)
 Using cached inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
 Using cached iniconfig-2.3.0-py3-none-any.whl (7.5 kB)
 Using cached invoke-3.0.3-py3-none-any.whl (160 kB)
 Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB)
 Using cached kombu-5.6.2-py3-none-any.whl (214 kB)
 Using cached amqp-5.3.1-py3-none-any.whl (50 kB)
 Using cached markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
 Using cached prompt_toolkit-3.0.52-py3...

GitHub Actions: OpenWISP Utils CI Build / 5_Python==3.11 django~=5.0.0.txt: [change] Make menu item registration idempotent #641

Conclusion: failure

View job details

ading aiohappyeyeballs-2.7.1-py3-none-any.whl (15 kB)
 Using cached aiosignal-1.4.0-py3-none-any.whl (7.5 kB)
 Using cached attrs-26.1.0-py3-none-any.whl (67 kB)
 Using cached bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (278 kB)
 Using cached certifi-2026.6.17-py3-none-any.whl (133 kB)
 Using cached cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (215 kB)
 Using cached click_didyoumean-0.3.1-py3-none-any.whl (3.6 kB)
 Using cached click_plugins-1.1.1.2-py2.py3-none-any.whl (11 kB)
 Using cached click_repl-0.3.0-py3-none-any.whl (10 kB)
 Using cached daphne-4.2.2-py3-none-any.whl (29 kB)
 Using cached autobahn-26.6.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.2 MB)
 Downloading cbor2-6.1.3-cp311-cp311-manylinux_2_28_x86_64.whl (472 kB)
 Using cached frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (231 kB)
 Using cached hyperlink-21.0.0-py2.py3-none-any.whl (74 kB)
 Using cached inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
 Using cached iniconfig-2.3.0-py3-none-any.whl (7.5 kB)
 Using cached invoke-3.0.3-py3-none-any.whl (160 kB)
 Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB)
 Using cached kombu-5.6.2-py3-none-any.whl (214 kB)
 Using cached amqp-5.3.1-py3-none-any.whl (50 kB)
 Using cached markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
 Using cached prompt_toolkit-3.0.52-py3-none-any.whl (391 kB)
 Using cached propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (58 kB)
 Using cached pygments-2.20.0-py3-none-any.whl (1.2 MB)
 Using cached pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB)
 Using cached python_dateutil-2.9.0.post0-py2.py3-none-any.whl (229 kB)
 Using cached pytz-2026.2-py2.py3-none-any.whl (510 kB)
 Using cached pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (806 kB)
 Using c...

GitHub Actions: OpenWISP Utils CI Build / 1_Python==3.10 django~=5.2.0.txt: [change] Make menu item registration idempotent #641

Conclusion: failure

View job details

-5.2.0-py3-none-any.whl (28 kB)
 Using cached aiohttp-3.14.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (1.7 MB)
 Using cached async_timeout-5.0.1-py3-none-any.whl (6.2 kB)
 Using cached geopy-2.4.1-py3-none-any.whl (125 kB)
 Using cached geographiclib-2.1-py3-none-any.whl (40 kB)
 Using cached jinja2-3.1.6-py3-none-any.whl (134 kB)
 Using cached jsonschema-4.26.0-py3-none-any.whl (90 kB)
 Using cached markdown-3.10.2-py3-none-any.whl (108 kB)
 Using cached maxminddb-3.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (98 kB)
 Using cached msgpack-1.2.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (407 kB)
 Using cached multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (243 kB)
 Using cached netaddr-1.3.0-py3-none-any.whl (2.3 MB)
 Using cached oauthlib-3.3.1-py3-none-any.whl (160 kB)
 Using cached openpyxl-3.1.5-py2.py3-none-any.whl (250 kB)
 Using cached paramiko-5.0.0-py3-none-any.whl (208 kB)
 Using cached phonenumbers-9.0.34-py2.py3-none-any.whl (2.6 MB)
 Using cached pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (6.9 MB)
 Using cached pyjwt-2.13.0-py3-none-any.whl (31 kB)
 Using cached pyopenssl-26.3.0-py3-none-any.whl (56 kB)
 Using cached cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (4.7 MB)
 Using cached rcssmin-1.2.2-cp310-cp310-manylinux1_x86_64.whl (51 kB)
 Using cached requests-2.34.2-py3-none-any.whl (73 kB)
 Using cached charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (216 kB)
 Using cached idna-3.18-py3-none-any.whl (65 kB)
 Using cached rjsmin-1.2.5-cp310-cp310-manylinux1_x86_64.whl (35 kB)
 Using cached scp-0.15.0-py2.py3-none-any.whl (8.8 kB)
 Using cached shortuuid-1.0.13-py3-none-any.whl (10 kB)
 Using cached swapper-1.4.0-py2.py3-none-any.whl (7.1 kB)
 Using cached urllib3-2.7.0-py3-none-any.w...

GitHub Actions: OpenWISP Utils CI Build / 8_Python==3.12 django~=4.2.0.txt: [change] Make menu item registration idempotent #641

Conclusion: failure

View job details

.5-cp312-cp312-manylinux1_x86_64.whl (31 kB)
 Using cached scp-0.15.0-py2.py3-none-any.whl (8.8 kB)
 Using cached shortuuid-1.0.13-py3-none-any.whl (10 kB)
 Using cached swapper-1.4.0-py2.py3-none-any.whl (7.1 kB)
 Using cached urllib3-2.7.0-py3-none-any.whl (131 kB)
 Using cached vine-5.1.0-py3-none-any.whl (9.6 kB)
 Using cached yarl-1.24.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (105 kB)
 Using cached freezegun-1.5.5-py3-none-any.whl (19 kB)
 Using cached pytest-9.1.1-py3-none-any.whl (386 kB)
 Using cached pluggy-1.6.0-py3-none-any.whl (20 kB)
 Using cached pytest_mock-3.15.1-py3-none-any.whl (10 kB)
 Using cached aiohappyeyeballs-2.7.1-py3-none-any.whl (15 kB)
 Using cached aiosignal-1.4.0-py3-none-any.whl (7.5 kB)
 Using cached attrs-26.1.0-py3-none-any.whl (67 kB)
 Using cached bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (278 kB)
 Using cached certifi-2026.6.17-py3-none-any.whl (133 kB)
 Using cached cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB)
 Using cached click_didyoumean-0.3.1-py3-none-any.whl (3.6 kB)
 Using cached click_plugins-1.1.1.2-py2.py3-none-any.whl (11 kB)
 Using cached click_repl-0.3.0-py3-none-any.whl (10 kB)
 Using cached daphne-4.2.2-py3-none-any.whl (29 kB)
 Using cached autobahn-26.6.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.2 MB)
 Downloading cbor2-6.1.3-cp312-cp312-manylinux_2_28_x86_64.whl (468 kB)
 Using cached frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (242 kB)
 Using cached hyperlink-21.0.0-py2.py3-none-any.whl (74 kB)
 Using cached inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
 Using cached iniconfig-2.3.0-py3-none-any.whl (7.5 kB)
 Using cached invoke-3.0.3-py3-none-any.whl (160 kB)
 Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB)
 Using cached kombu-5.6.2-py3-none-any.whl (214 kB)
 Using cached amqp-5.3.1-py3-none-any.whl (50 kB)
 Using cached markups...

GitHub Actions: OpenWISP Utils CI Build / 6_Python==3.12 django~=5.1.0.txt: [change] Make menu item registration idempotent #641

Conclusion: failure

View job details

hed aiohappyeyeballs-2.7.1-py3-none-any.whl (15 kB)
 Using cached aiosignal-1.4.0-py3-none-any.whl (7.5 kB)
 Using cached attrs-26.1.0-py3-none-any.whl (67 kB)
 Using cached bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (278 kB)
 Using cached certifi-2026.6.17-py3-none-any.whl (133 kB)
 Using cached cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB)
 Using cached click_didyoumean-0.3.1-py3-none-any.whl (3.6 kB)
 Using cached click_plugins-1.1.1.2-py2.py3-none-any.whl (11 kB)
 Using cached click_repl-0.3.0-py3-none-any.whl (10 kB)
 Using cached daphne-4.2.2-py3-none-any.whl (29 kB)
 Using cached autobahn-26.6.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.2 MB)
 Downloading cbor2-6.1.3-cp312-cp312-manylinux_2_28_x86_64.whl (468 kB)
 Using cached frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (242 kB)
 Using cached hyperlink-21.0.0-py2.py3-none-any.whl (74 kB)
 Using cached inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
 Using cached iniconfig-2.3.0-py3-none-any.whl (7.5 kB)
 Using cached invoke-3.0.3-py3-none-any.whl (160 kB)
 Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB)
 Using cached kombu-5.6.2-py3-none-any.whl (214 kB)
 Using cached amqp-5.3.1-py3-none-any.whl (50 kB)
 Using cached markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
 Using cached prompt_toolkit-3.0.52-py3-none-any.whl (391 kB)
 Using cached propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (61 kB)
 Using cached pygments-2.20.0-py3-none-any.whl (1.2 MB)
 Using cached pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB)
 Using cached python_dateutil-2.9.0.post0-py2.py3-none-any.whl (229 kB)
 Using cached pytz-2026.2-py2.py3-none-any.whl (510 kB)
 Using cached pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (807 kB)
 Using cac...

GitHub Actions: OpenWISP Utils CI Build / 3_Python==3.13 django~=5.1.0.txt: [change] Make menu item registration idempotent #641

Conclusion: failure

View job details

3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB)
 Using cached click_didyoumean-0.3.1-py3-none-any.whl (3.6 kB)
 Using cached click_plugins-1.1.1.2-py2.py3-none-any.whl (11 kB)
 Using cached click_repl-0.3.0-py3-none-any.whl (10 kB)
 Using cached daphne-4.2.2-py3-none-any.whl (29 kB)
 Using cached autobahn-26.6.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.2 MB)
 Downloading cbor2-6.1.3-cp313-cp313-manylinux_2_28_x86_64.whl (467 kB)
 Using cached frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (234 kB)
 Using cached hyperlink-21.0.0-py2.py3-none-any.whl (74 kB)
 Using cached inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
 Using cached iniconfig-2.3.0-py3-none-any.whl (7.5 kB)
 Using cached invoke-3.0.3-py3-none-any.whl (160 kB)
 Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB)
 Using cached kombu-5.6.2-py3-none-any.whl (214 kB)
 Using cached amqp-5.3.1-py3-none-any.whl (50 kB)
 Using cached markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
 Using cached prompt_toolkit-3.0.52-py3-none-any.whl (391 kB)
 Using cached propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (61 kB)
 Using cached pygments-2.20.0-py3-none-any.whl (1.2 MB)
 Using cached pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB)
 Using cached python_dateutil-2.9.0.post0-py2.py3-none-any.whl (229 kB)
 Using cached pytz-2026.2-py2.py3-none-any.whl (510 kB)
 Using cached pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (801 kB)
 Using cached redis-8.0.1-py3-none-any.whl (502 kB)
 Using cached referencing-0.37.0-py3-none-any.whl (26 kB)
 Using cached rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (365 kB)
 Using cached six-1.17.0-py2.py3-none-any.whl (11 kB)
 Using cached sqlparse-0.5.5-py3-none-any.whl (46 kB)
 Using cached tabl...

GitHub Actions: OpenWISP Utils CI Build / 7_Python==3.12 django~=5.0.0.txt: [change] Make menu item registration idempotent #641

Conclusion: failure

View job details

l-1.4.0-py3-none-any.whl (7.5 kB)
 Using cached attrs-26.1.0-py3-none-any.whl (67 kB)
 Using cached bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (278 kB)
 Using cached certifi-2026.6.17-py3-none-any.whl (133 kB)
 Using cached cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB)
 Using cached click_didyoumean-0.3.1-py3-none-any.whl (3.6 kB)
 Using cached click_plugins-1.1.1.2-py2.py3-none-any.whl (11 kB)
 Using cached click_repl-0.3.0-py3-none-any.whl (10 kB)
 Using cached daphne-4.2.2-py3-none-any.whl (29 kB)
 Using cached autobahn-26.6.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.2 MB)
 Downloading cbor2-6.1.3-cp312-cp312-manylinux_2_28_x86_64.whl (468 kB)
 Using cached frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (242 kB)
 Using cached hyperlink-21.0.0-py2.py3-none-any.whl (74 kB)
 Using cached inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
 Using cached iniconfig-2.3.0-py3-none-any.whl (7.5 kB)
 Using cached invoke-3.0.3-py3-none-any.whl (160 kB)
 Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB)
 Using cached kombu-5.6.2-py3-none-any.whl (214 kB)
 Using cached amqp-5.3.1-py3-none-any.whl (50 kB)
 Using cached markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
 Using cached prompt_toolkit-3.0.52-py3-none-any.whl (391 kB)
 Using cached propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (61 kB)
 Using cached pygments-2.20.0-py3-none-any.whl (1.2 MB)
 Using cached pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB)
 Using cached python_dateutil-2.9.0.post0-py2.py3-none-any.whl (229 kB)
 Using cached pytz-2026.2-py2.py3-none-any.whl (510 kB)
 Using cached pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (807 kB)
 Using cached redis-8.0.1-py3-none-any.whl (502 kB)
 Using cached referencing-0.37.0...

GitHub Actions: OpenWISP Utils CI Build / 11_Python==3.11 django~=4.2.0.txt: [change] Make menu item registration idempotent #641

Conclusion: failure

View job details

.2.5-cp311-cp311-manylinux1_x86_64.whl (31 kB)
 Using cached scp-0.15.0-py2.py3-none-any.whl (8.8 kB)
 Using cached shortuuid-1.0.13-py3-none-any.whl (10 kB)
 Using cached swapper-1.4.0-py2.py3-none-any.whl (7.1 kB)
 Using cached urllib3-2.7.0-py3-none-any.whl (131 kB)
 Using cached vine-5.1.0-py3-none-any.whl (9.6 kB)
 Using cached yarl-1.24.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (106 kB)
 Using cached freezegun-1.5.5-py3-none-any.whl (19 kB)
 Using cached pytest-9.1.1-py3-none-any.whl (386 kB)
 Using cached pluggy-1.6.0-py3-none-any.whl (20 kB)
 Using cached pytest_mock-3.15.1-py3-none-any.whl (10 kB)
 Downloading aiohappyeyeballs-2.7.1-py3-none-any.whl (15 kB)
 Using cached aiosignal-1.4.0-py3-none-any.whl (7.5 kB)
 Using cached attrs-26.1.0-py3-none-any.whl (67 kB)
 Using cached bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (278 kB)
 Using cached certifi-2026.6.17-py3-none-any.whl (133 kB)
 Using cached cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (215 kB)
 Using cached click_didyoumean-0.3.1-py3-none-any.whl (3.6 kB)
 Using cached click_plugins-1.1.1.2-py2.py3-none-any.whl (11 kB)
 Using cached click_repl-0.3.0-py3-none-any.whl (10 kB)
 Using cached daphne-4.2.2-py3-none-any.whl (29 kB)
 Using cached autobahn-26.6.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.2 MB)
 Downloading cbor2-6.1.3-cp311-cp311-manylinux_2_28_x86_64.whl (472 kB)
 Using cached frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (231 kB)
 Using cached hyperlink-21.0.0-py2.py3-none-any.whl (74 kB)
 Using cached inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
 Using cached iniconfig-2.3.0-py3-none-any.whl (7.5 kB)
 Using cached invoke-3.0.3-py3-none-any.whl (160 kB)
 Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB)
 Using cached kombu-5.6.2-py3-none-any.whl (214 kB)
 Using cached amqp-5.3.1-py3-none-any.whl (50 kB)
 Using cached markup...

GitHub Actions: OpenWISP Utils CI Build / 13_Python==3.11 django~=5.1.0.txt: [change] Make menu item registration idempotent #641

Conclusion: failure

View job details

loading aiohappyeyeballs-2.7.1-py3-none-any.whl (15 kB)
 Using cached aiosignal-1.4.0-py3-none-any.whl (7.5 kB)
 Using cached attrs-26.1.0-py3-none-any.whl (67 kB)
 Using cached bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (278 kB)
 Using cached certifi-2026.6.17-py3-none-any.whl (133 kB)
 Using cached cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (215 kB)
 Using cached click_didyoumean-0.3.1-py3-none-any.whl (3.6 kB)
 Using cached click_plugins-1.1.1.2-py2.py3-none-any.whl (11 kB)
 Using cached click_repl-0.3.0-py3-none-any.whl (10 kB)
 Using cached daphne-4.2.2-py3-none-any.whl (29 kB)
 Using cached autobahn-26.6.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.2 MB)
 Downloading cbor2-6.1.3-cp311-cp311-manylinux_2_28_x86_64.whl (472 kB)
 Using cached frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (231 kB)
 Using cached hyperlink-21.0.0-py2.py3-none-any.whl (74 kB)
 Using cached inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
 Using cached iniconfig-2.3.0-py3-none-any.whl (7.5 kB)
 Using cached invoke-3.0.3-py3-none-any.whl (160 kB)
 Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB)
 Using cached kombu-5.6.2-py3-none-any.whl (214 kB)
 Using cached amqp-5.3.1-py3-none-any.whl (50 kB)
 Using cached markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
 Using cached prompt_toolkit-3.0.52-py3-none-any.whl (391 kB)
 Using cached propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (58 kB)
 Using cached pygments-2.20.0-py3-none-any.whl (1.2 MB)
 Using cached pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB)
 Using cached python_dateutil-2.9.0.post0-py2.py3-none-any.whl (229 kB)
 Using cached pytz-2026.2-py2.py3-none-any.whl (510 kB)
 Using cached pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (806 kB)
 Using...

GitHub Actions: OpenWISP Utils CI Build / 12_Python==3.10 django~=5.1.0.txt: [change] Make menu item registration idempotent #641

Conclusion: failure

View job details

8 kB)
 Using cached msgpack-1.2.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (407 kB)
 Using cached multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (243 kB)
 Using cached netaddr-1.3.0-py3-none-any.whl (2.3 MB)
 Using cached oauthlib-3.3.1-py3-none-any.whl (160 kB)
 Using cached openpyxl-3.1.5-py2.py3-none-any.whl (250 kB)
 Using cached paramiko-5.0.0-py3-none-any.whl (208 kB)
 Using cached phonenumbers-9.0.34-py2.py3-none-any.whl (2.6 MB)
 Using cached pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (6.9 MB)
 Using cached pyjwt-2.13.0-py3-none-any.whl (31 kB)
 Using cached pyopenssl-26.3.0-py3-none-any.whl (56 kB)
 Using cached cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (4.7 MB)
 Using cached rcssmin-1.2.2-cp310-cp310-manylinux1_x86_64.whl (51 kB)
 Using cached requests-2.34.2-py3-none-any.whl (73 kB)
 Using cached charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (216 kB)
 Using cached idna-3.18-py3-none-any.whl (65 kB)
 Using cached rjsmin-1.2.5-cp310-cp310-manylinux1_x86_64.whl (35 kB)
 Using cached scp-0.15.0-py2.py3-none-any.whl (8.8 kB)
 Using cached shortuuid-1.0.13-py3-none-any.whl (10 kB)
 Using cached swapper-1.4.0-py2.py3-none-any.whl (7.1 kB)
 Using cached urllib3-2.7.0-py3-none-any.whl (131 kB)
 Using cached vine-5.1.0-py3-none-any.whl (9.6 kB)
 Using cached yarl-1.24.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (106 kB)
 Using cached freezegun-1.5.5-py3-none-any.whl (19 kB)
 Using cached pytest-9.1.1-py3-none-any.whl (386 kB)
 Using cached pluggy-1.6.0-py3-none-any.whl (20 kB)
 Using cached pytest_mock-3.15.1-py3-none-any.whl (10 kB)
 Using cached aiohappyeyeballs-2.7.1-py3-none-any.whl (15 kB)
 Using cached aiosignal-1.4.0-py3-none-any.whl (7.5 kB)
 Using cached attrs-26.1.0-py3-none-any.whl (67 kB)
 Using cached bcrypt-5.0.0-cp39-...

GitHub Actions: OpenWISP Utils CI Build / 9_Python==3.10 django~=5.0.0.txt: [change] Make menu item registration idempotent #641

Conclusion: failure

View job details

10-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (98 kB)
 Using cached msgpack-1.2.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (407 kB)
 Using cached multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (243 kB)
 Using cached netaddr-1.3.0-py3-none-any.whl (2.3 MB)
 Using cached oauthlib-3.3.1-py3-none-any.whl (160 kB)
 Using cached openpyxl-3.1.5-py2.py3-none-any.whl (250 kB)
 Using cached paramiko-5.0.0-py3-none-any.whl (208 kB)
 Using cached phonenumbers-9.0.34-py2.py3-none-any.whl (2.6 MB)
 Using cached pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (6.9 MB)
 Using cached pyjwt-2.13.0-py3-none-any.whl (31 kB)
 Using cached pyopenssl-26.3.0-py3-none-any.whl (56 kB)
 Using cached cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (4.7 MB)
 Using cached rcssmin-1.2.2-cp310-cp310-manylinux1_x86_64.whl (51 kB)
 Using cached requests-2.34.2-py3-none-any.whl (73 kB)
 Using cached charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (216 kB)
 Using cached idna-3.18-py3-none-any.whl (65 kB)
 Using cached rjsmin-1.2.5-cp310-cp310-manylinux1_x86_64.whl (35 kB)
 Using cached scp-0.15.0-py2.py3-none-any.whl (8.8 kB)
 Using cached shortuuid-1.0.13-py3-none-any.whl (10 kB)
 Using cached swapper-1.4.0-py2.py3-none-any.whl (7.1 kB)
 Using cached urllib3-2.7.0-py3-none-any.whl (131 kB)
 Using cached vine-5.1.0-py3-none-any.whl (9.6 kB)
 Using cached yarl-1.24.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (106 kB)
 Using cached freezegun-1.5.5-py3-none-any.whl (19 kB)
 Using cached pytest-9.1.1-py3-none-any.whl (386 kB)
 Using cached pluggy-1.6.0-py3-none-any.whl (20 kB)
 Using cached pytest_mock-3.15.1-py3-none-any.whl (10 kB)
 Using cached aiohappyeyeballs-2.7.1-py3-none-any.whl (15 kB)
 Using cached aiosignal-1.4.0-py3-none-any.whl (7.5 kB)
 Using cac...

GitHub Actions: OpenWISP Utils CI Build / 14_Python==3.10 django~=4.2.0.txt: [change] Make menu item registration idempotent #641

Conclusion: failure

View job details

anylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (1.7 MB)
 Using cached async_timeout-5.0.1-py3-none-any.whl (6.2 kB)
 Using cached geopy-2.4.1-py3-none-any.whl (125 kB)
 Using cached geographiclib-2.1-py3-none-any.whl (40 kB)
 Using cached jinja2-3.1.6-py3-none-any.whl (134 kB)
 Using cached jsonschema-4.26.0-py3-none-any.whl (90 kB)
 Using cached markdown-3.10.2-py3-none-any.whl (108 kB)
 Using cached maxminddb-3.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (98 kB)
 Using cached msgpack-1.2.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (407 kB)
 Using cached multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (243 kB)
 Using cached netaddr-1.3.0-py3-none-any.whl (2.3 MB)
 Using cached oauthlib-3.3.1-py3-none-any.whl (160 kB)
 Using cached openpyxl-3.1.5-py2.py3-none-any.whl (250 kB)
 Using cached paramiko-5.0.0-py3-none-any.whl (208 kB)
 Using cached phonenumbers-9.0.34-py2.py3-none-any.whl (2.6 MB)
 Using cached pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (6.9 MB)
 Using cached pyjwt-2.13.0-py3-none-any.whl (31 kB)
 Using cached pyopenssl-26.3.0-py3-none-any.whl (56 kB)
 Using cached cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (4.7 MB)
 Using cached rcssmin-1.2.2-cp310-cp310-manylinux1_x86_64.whl (51 kB)
 Using cached requests-2.34.2-py3-none-any.whl (73 kB)
 Using cached charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (216 kB)
 Using cached idna-3.18-py3-none-any.whl (65 kB)
 Using cached rjsmin-1.2.5-cp310-cp310-manylinux1_x86_64.whl (35 kB)
 Using cached scp-0.15.0-py2.py3-none-any.whl (8.8 kB)
 Using cached shortuuid-1.0.13-py3-none-any.whl (10 kB)
 Using cached swapper-1.4.0-py2.py3-none-any.whl (7.1 kB)
 Using cached urllib3-2.7.0-py3-none-any.whl (131 kB)
 Using cached vine-5.1.0-py3-none-any.whl (9.6 kB)
 Using cach...

GitHub Actions: OpenWISP Utils CI Build / 10_Python==3.11 django~=5.2.0.txt: [change] Make menu item registration idempotent #641

Conclusion: failure

View job details

one-any.whl (10 kB)
 Using cached swapper-1.4.0-py2.py3-none-any.whl (7.1 kB)
 Using cached urllib3-2.7.0-py3-none-any.whl (131 kB)
 Using cached vine-5.1.0-py3-none-any.whl (9.6 kB)
 Using cached yarl-1.24.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (106 kB)
 Using cached freezegun-1.5.5-py3-none-any.whl (19 kB)
 Using cached pytest-9.1.1-py3-none-any.whl (386 kB)
 Using cached pluggy-1.6.0-py3-none-any.whl (20 kB)
 Using cached pytest_mock-3.15.1-py3-none-any.whl (10 kB)
 Downloading aiohappyeyeballs-2.7.1-py3-none-any.whl (15 kB)
 Using cached aiosignal-1.4.0-py3-none-any.whl (7.5 kB)
 Using cached attrs-26.1.0-py3-none-any.whl (67 kB)
 Using cached bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (278 kB)
 Using cached certifi-2026.6.17-py3-none-any.whl (133 kB)
 Using cached cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (215 kB)
 Using cached click_didyoumean-0.3.1-py3-none-any.whl (3.6 kB)
 Using cached click_plugins-1.1.1.2-py2.py3-none-any.whl (11 kB)
 Using cached click_repl-0.3.0-py3-none-any.whl (10 kB)
 Using cached daphne-4.2.2-py3-none-any.whl (29 kB)
 Using cached autobahn-26.6.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.2 MB)
 Downloading cbor2-6.1.3-cp311-cp311-manylinux_2_28_x86_64.whl (472 kB)
 Using cached frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (231 kB)
 Using cached hyperlink-21.0.0-py2.py3-none-any.whl (74 kB)
 Using cached inflection-0.5.1-py2.py3-none-any.whl (9.5 kB)
 Using cached iniconfig-2.3.0-py3-none-any.whl (7.5 kB)
 Using cached invoke-3.0.3-py3-none-any.whl (160 kB)
 Using cached jsonschema_specifications-2025.9.1-py3-none-any.whl (18 kB)
 Using cached kombu-5.6.2-py3-none-any.whl (214 kB)
 Using cached amqp-5.3.1-py3-none-any.whl (50 kB)
 Using cached markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
 Using cached prompt_toolkit-3.0.52-py...
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{py,html,txt}

📄 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/test_project/tests/test_menu.py
  • openwisp_utils/admin_theme/menu.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Place imports at the top of the file; only defer imports when necessary (e.g., Django model imports inside functions or methods where the app registry is not yet ready)
Avoid unnecessary blank lines inside function and method bodies
Add or update tests for every behavior change
Run openwisp-qa-format after editing
Prefer in-process tests so coverage tools can measure changed code
When checking coverage for a changed module, use python -m pytest <test_path> --cov=<dotted.module.path> --cov-report=term-missing
Watch for unsafe file paths, unsafe subprocess usage, token or secret exposure, and changes that could weaken QA or release safeguards
Write comments and docstrings only when they explain why code is shaped a certain way; place comments before the relevant code block instead of scattering them inside it

Files:

  • tests/test_project/tests/test_menu.py
  • openwisp_utils/admin_theme/menu.py
**/tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

For bug fixes, write the regression test first, run it against the unfixed code, confirm it fails for the expected reason, then implement the fix

Files:

  • tests/test_project/tests/test_menu.py
🧠 Learnings (1)
📚 Learning: 2026-06-15T22:45:01.529Z
Learnt from: nemesifier
Repo: openwisp/openwisp-utils PR: 701
File: setup.py:72-72
Timestamp: 2026-06-15T22:45:01.529Z
Learning: When reviewing changes that require a minimum Selenium Python version for specific APIs (e.g., BiDi features like `Options.enable_bidi`, console/message handlers like `Script.add_console_message_handler`, or event handlers like `BrowsingContext.add_event_handler`), do not use the Selenium API docs site (`https://www.selenium.dev/selenium/docs/api/py/`) as the source of introduction/version history, since it reflects the latest API shape. Instead, confirm feature availability by checking the official Python changelog (`py/CHANGES` in the Selenium repo) and/or inspecting the Selenium source (tags/commits corresponding to candidate versions). Only accept the stated minimum version after verifying that the referenced APIs exist in that Selenium version (e.g., Selenium 4.32.0 includes the BiDi APIs used by openwisp-utils’ BiDi-related PR `#701` as of that version).

Applied to files:

  • tests/test_project/tests/test_menu.py
  • openwisp_utils/admin_theme/menu.py
🔇 Additional comments (4)
openwisp_utils/admin_theme/menu.py (3)

1-11: LGTM!

Also applies to: 21-26, 47-67


221-270: LGTM!

Items stored in group.items are always MenuLink or ModelLink instances (lines 245-250), both of which set self.config via BaseMenuItem, so the .config comparison at line 257 is safe.


186-197: 🩺 Stability & Availability

No issue: MenuGroup sets .config
MenuGroup also calls super().__init__(config), so MENU[position].config is available for existing groups too.

			> Likely an incorrect or invalid review comment.
tests/test_project/tests/test_menu.py (1)

156-158: LGTM! Correctly switched to a config with a different label so this subtest still exercises the "different config at occupied position" raise, rather than accidentally hitting the new idempotent skip path (since the default item at position 1 already matches config's default values).

Comment thread tests/test_project/tests/test_menu.py

@nemesifier nemesifier left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

As @coderabbitai wrote in its pre-merge checks:

Update the navigation-menu docs to describe the idempotent identical-config case and keep the error description only for mismatched configs.

@asmodehn

Copy link
Copy Markdown
Member

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@nemesifier nemesifier left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@github-project-automation github-project-automation Bot moved this from In progress to Reviewer approved in OpenWISP Priorities for next releases Jul 18, 2026
@openwisp-companion

Copy link
Copy Markdown

Proposed change log entry:

[change] Make menu item registration idempotent #641

Previously, calling `register_menu_group` or `register_menu_subitem`
twice with the same configuration at the same position would always
raise an `ImproperlyConfigured` exception. This behavior prevented
re-registration, even when the same application was re-initializing its
menu items during testing.

This change modifies these functions to check if the configuration being
registered is identical to the one already present at that position. If
the configurations match, the function now logs an informational message
and skips the re-registration instead of raising an error. This makes
the menu item registration process idempotent, improving testability and
handling of repeated app initializations.

Tests have been added to verify this new behavior.

Closes #641

@nemesifier
nemesifier merged commit 6496fa3 into openwisp:master Jul 18, 2026
32 checks passed
@github-project-automation github-project-automation Bot moved this from Reviewer approved to Done in OpenWISP Priorities for next releases Jul 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

[change] Make register_menu_group(item) idempotent

4 participants