Skip to content

[feat] Add Select2Widget for choice fields #254#642

Open
shivsubh wants to merge 1 commit intoopenwisp:masterfrom
shivsubh:issues/254-select2-autocomplete-fields
Open

[feat] Add Select2Widget for choice fields #254#642
shivsubh wants to merge 1 commit intoopenwisp:masterfrom
shivsubh:issues/254-select2-autocomplete-fields

Conversation

@shivsubh
Copy link
Copy Markdown

@shivsubh shivsubh commented Apr 7, 2026

Implement a reusable Select2Widget that uses Django's native admin assets to avoid extra dependencies.

Closes #254

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

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 7, 2026

📝 Walkthrough

Walkthrough

Adds a reusable Select2 integration: a new Select2Widget (subclassing Django's Select) that injects Select2 assets and ensures the ow-select2 CSS class on rendered selects; a new admin mixin Select2AdminMixin to apply the widget to configured choice fields; a client script select2.js that initializes Select2 on page load and when formset rows are added (skipping formset templates and already-initialized elements); tests (including Selenium checks) and test-admin updates to verify rendering and runtime initialization.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Browser
    participant DjangoAdmin as Django Admin
    participant Widget as Select2Widget
    participant JS as select2.js
    participant Select2 as Select2 Library

    User->>Browser: Request admin form page
    Browser->>DjangoAdmin: GET /admin/... (render form)
    DjangoAdmin->>Widget: Render select field with ow-select2 class
    Widget->>Browser: Serve HTML + Media (CSS/JS)
    Browser->>Browser: Load assets (jQuery, Select2, select2.js, i18n)
    Browser->>JS: Execute initialization on DOM ready
    JS->>JS: Find select.ow-select2 (exclude __prefix__)
    JS->>Select2: Call $el.select2() for uninitialized elements
    Select2->>Browser: Enhance UI (select2-container)
    User->>Browser: Interact with enhanced select
Loading
sequenceDiagram
    actor User
    participant Browser
    participant Form as Formset
    participant JS as select2.js
    participant Select2 as Select2 Library

    User->>Form: Click "Add row"
    Form->>Browser: Append new row and emit formset:added
    Browser->>JS: formset:added handler runs
    JS->>JS: Locate select.ow-select2 in new row (exclude __prefix__)
    JS->>Select2: Initialize Select2 if not select2-hidden-accessible
    Select2->>Browser: Enhance new select element
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title '[feat] Add Select2Widget for choice fields #254' follows the required [type] format with 'feat', provides a clear and descriptive summary of the main change, and includes the issue number.
Description check ✅ Passed The PR description includes all required template sections: completed checklist items, reference to issue #254, and a clear description of changes implementing a reusable Select2Widget using Django's native admin assets.
Linked Issues check ✅ Passed The code changes fully address issue #254 by implementing a reusable Select2Widget solution with associated admin mixin, JavaScript initialization, comprehensive tests, and proper integration.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the Select2Widget feature; no unrelated modifications to unrelated functionality were introduced.
Bug Fixes ✅ Passed This pull request implements a new Select2Widget feature (marked [feat]) rather than fixing an existing bug, making the bug-fix-specific custom check inapplicable.

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

✨ 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 and usage tips.

@kilo-code-bot
Copy link
Copy Markdown

kilo-code-bot Bot commented Apr 7, 2026

Code Review Summary

Status: No Issues Found | Recommendation: Merge

The implementation is clean and follows Django best practices. The PR introduces a reusable Select2Widget that uses Django's native admin assets to avoid extra dependencies.

Incremental Review (latest commit)

Changes since commit 2ccf8d15a50768781a027ebeebe75e5b9f4746ee:

  • Added Select2AdminMixin to openwisp_utils/admin.py for easy admin integration
  • Added Selenium tests for testing admin form rendering
  • All PR files now properly structured
Files Reviewed (5 files)
  • openwisp_utils/widgets.py - Clean implementation using Django's built-in Select2 assets
  • openwisp_utils/static/openwisp-utils/js/select2.js - Properly handles formsets and avoids double-initialization
  • openwisp_utils/admin.py - New Select2AdminMixin for easy admin integration
  • tests/test_project/admin.py - Correct usage example with select2_fields
  • tests/test_project/tests/test_widgets.py - Good test coverage including Selenium tests

Reviewed by kimi-k2.5-0127 · 302,104 tokens

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/test_project/tests/test_widgets.py`:
- Around line 17-23: The current test_select2_widget_media only checks default
asset inclusion; add two edge-case tests to cover minified assets and i18n:
create test_select2_widget_media_minified that patches
openwisp_utils.widgets.settings.DEBUG to False, instantiate Select2Widget and
assert the media string contains minified filenames (e.g., select2.min.css,
jquery.min.js, select2.full.min.js), and create test_select2_widget_media_i18n
that patches openwisp_utils.widgets.get_language to return a supported locale
like 'es', instantiate Select2Widget and assert the media string contains the
i18n path (e.g., i18n/es.js); use unittest.mock.patch to scope these changes and
reference Select2Widget and its media property in the assertions.
🪄 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: 9142d548-73bd-4704-b953-8e623f00fd20

📥 Commits

Reviewing files that changed from the base of the PR and between 98540a6 and a695791.

📒 Files selected for processing (4)
  • openwisp_utils/static/openwisp-utils/js/select2.js
  • openwisp_utils/widgets.py
  • tests/test_project/admin.py
  • tests/test_project/tests/test_widgets.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). (14)
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.0.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.0.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.0.0
  • GitHub Check: Python==3.12 | django~=5.2.0
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{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/admin.py
  • tests/test_project/tests/test_widgets.py
  • openwisp_utils/widgets.py
🔇 Additional comments (4)
openwisp_utils/static/openwisp-utils/js/select2.js (1)

1-18: LGTM!

The initialization script is well-structured with appropriate safeguards:

  • Correctly excludes formset template rows via __prefix__ check
  • Idempotency guard using select2-hidden-accessible class prevents double initialization
  • Proper handling of dynamically added formset rows via formset:added event
openwisp_utils/widgets.py (1)

1-36: LGTM!

The Select2Widget implementation is clean and well-structured:

  • Properly leverages Django's admin assets to avoid external dependencies
  • Correctly handles DEBUG setting for minified/non-minified assets
  • i18n support via SELECT2_TRANSLATIONS gracefully handles missing translations
  • Asset ordering ensures dependencies load before initialization script
  • Class attribute handling in __init__ correctly preserves user-provided classes
tests/test_project/admin.py (1)

126-129: LGTM!

The formfield_for_choice_field override correctly applies Select2Widget only to the books_type field while preserving default behavior for other choice fields via super().

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

1-23: LGTM overall - tests cover core widget functionality.

The test file verifies the essential behavior: CSS class handling and asset inclusion. The widget rendering and media composition are adequately tested for the basic use case.

Comment on lines +17 to +23
def test_select2_widget_media(self):
widget = Select2Widget()
media = str(widget.media)
self.assertIn('admin/css/vendor/select2/select2', media)
self.assertIn('admin/js/vendor/jquery/jquery', media)
self.assertIn('admin/js/vendor/select2/select2.full', media)
self.assertIn('openwisp-utils/js/select2.js', media)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider adding test coverage for edge cases.

The current test_select2_widget_media test verifies asset inclusion but could be more comprehensive:

  • No assertion for i18n file inclusion when get_language() returns a supported language
  • No verification of minified vs non-minified asset paths based on DEBUG setting
💡 Suggested additional test cases
from unittest.mock import patch

def test_select2_widget_media_minified(self):
    """Test that minified assets are used when DEBUG=False."""
    with patch('openwisp_utils.widgets.settings') as mock_settings:
        mock_settings.DEBUG = False
        widget = Select2Widget()
        media = str(widget.media)
        self.assertIn('select2.min.css', media)
        self.assertIn('jquery.min.js', media)
        self.assertIn('select2.full.min.js', media)

def test_select2_widget_media_i18n(self):
    """Test that i18n file is included for supported languages."""
    with patch('openwisp_utils.widgets.get_language', return_value='es'):
        widget = Select2Widget()
        media = str(widget.media)
        self.assertIn('i18n/es.js', media)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_project/tests/test_widgets.py` around lines 17 - 23, The current
test_select2_widget_media only checks default asset inclusion; add two edge-case
tests to cover minified assets and i18n: create
test_select2_widget_media_minified that patches
openwisp_utils.widgets.settings.DEBUG to False, instantiate Select2Widget and
assert the media string contains minified filenames (e.g., select2.min.css,
jquery.min.js, select2.full.min.js), and create test_select2_widget_media_i18n
that patches openwisp_utils.widgets.get_language to return a supported locale
like 'es', instantiate Select2Widget and assert the media string contains the
i18n path (e.g., i18n/es.js); use unittest.mock.patch to scope these changes and
reference Select2Widget and its media property in the assertions.

@shivsubh shivsubh force-pushed the issues/254-select2-autocomplete-fields branch from a695791 to 7ec95e2 Compare April 7, 2026 05:28
@openwisp-companion
Copy link
Copy Markdown

Code Style Failures

Hello @shivsubh,
(Analysis for commit a695791)

Your commit failed due to multiple code style violations. Please run the openwisp-qa-format command to automatically fix most of these issues.

Specifically, the following files have formatting errors that openwisp-qa-format should resolve:

  • tests/test_project/tests/test_widgets.py
  • openwisp_utils/widgets.py

Additionally, there are ReStructuredText (RST) formatting errors detected in openwisp_utils/widgets.py. Please manually correct these or ensure openwisp-qa-format addresses them.

@shivsubh shivsubh force-pushed the issues/254-select2-autocomplete-fields branch 2 times, most recently from fbf6555 to 780d3cb Compare April 7, 2026 05:34
@coveralls
Copy link
Copy Markdown

coveralls commented Apr 7, 2026

Coverage Status

coverage: 97.542% (+0.01%) from 97.529% — shivsubh:issues/254-select2-autocomplete-fields into openwisp:master

Copy link
Copy Markdown
Member

@pandafy pandafy left a comment

Choose a reason for hiding this comment

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

Thank you @shivsubh for contributing. We want to have a re-usable solution which means minimal code changes should be required in the downstream projects to utilize the new widget.

I did a quick review and found the following code problematic.

We will need new selenium tests which verifies the functioning of the added field.

Comment thread tests/test_project/admin.py Outdated
@github-project-automation github-project-automation Bot moved this from To do (general) to In progress in OpenWISP Contributor's Board Apr 8, 2026
@shivsubh shivsubh force-pushed the issues/254-select2-autocomplete-fields branch from 780d3cb to 2ccf8d1 Compare April 9, 2026 06:51
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@openwisp_utils/widgets.py`:
- Around line 31-34: The __init__ mutates the caller-provided attrs dict in
place; change it to copy attrs first (e.g., attrs = dict(attrs) or attrs.copy()
when attrs is truthy) before updating the "class" key so the original dict is
not modified; update the __init__ implementation (the attrs handling and the
super().__init__(attrs, choices) call) in the widget class to use the copied
dict.

In `@tests/test_project/tests/test_widgets.py`:
- Around line 37-41: The test test_select2_widget_renders_on_shelf_add_form
currently only checks for the select element and its .ow-select2 class; update
it to also assert the Select2 JS enhancement by waiting for the Select2
container for that field (for example by waiting for a selector such as
".select2-container" or the field-specific container tied to "id_books_type") to
be present in the DOM after opening reverse("admin:test_project_shelf_add"); use
the existing helper wait_for_presence to locate the Select2 container adjacent
to or tied to the select#id_books_type element to ensure initialization rather
than just markup.
🪄 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: 736a54cb-1fcd-4b9c-b43f-6add1068f1cd

📥 Commits

Reviewing files that changed from the base of the PR and between a695791 and 2ccf8d1.

📒 Files selected for processing (5)
  • openwisp_utils/admin.py
  • openwisp_utils/static/openwisp-utils/js/select2.js
  • openwisp_utils/widgets.py
  • tests/test_project/admin.py
  • tests/test_project/tests/test_widgets.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). (14)
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.0.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.0.0
  • GitHub Check: Python==3.10 | django~=5.0.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.1.0
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{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/admin.py
  • openwisp_utils/admin.py
  • tests/test_project/tests/test_widgets.py
  • openwisp_utils/widgets.py
🧠 Learnings (2)
📚 Learning: 2026-03-14T20:44:14.568Z
Learnt from: CR
Repo: openwisp/openwisp-utils PR: 0
File: coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt:0-0
Timestamp: 2026-03-14T20:44:14.568Z
Learning: Bug Fixes: If the bug affects the user interface, include a Selenium browser test; if missing, raise a warning

Applied to files:

  • tests/test_project/tests/test_widgets.py
📚 Learning: 2026-03-14T20:44:14.568Z
Learnt from: CR
Repo: openwisp/openwisp-utils PR: 0
File: coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt:0-0
Timestamp: 2026-03-14T20:44:14.568Z
Learning: Features: Add tests for new features and ensure coverage does not decrease significantly; prefer Selenium browser tests for UI-impacting features

Applied to files:

  • tests/test_project/tests/test_widgets.py
🔇 Additional comments (5)
openwisp_utils/admin.py (1)

223-231: Clean reusable admin hook for Select2-enabled choice fields.

This mixin-based opt-in is scoped and composable, and the formfield_for_choice_field override keeps behavior limited to choice fields.

tests/test_project/admin.py (1)

114-125: Test admin wiring is correct for feature coverage.

Applying Select2AdminMixin with select2_fields = ("books_type",) is the right setup to validate the feature end-to-end.

openwisp_utils/static/openwisp-utils/js/select2.js (1)

4-17: Initialization flow handles both initial render and dynamic formsets well.

The __prefix__ skip and already-initialized guard are good safeguards against duplicate/broken Select2 setup.

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

12-29: Widget-level tests are solid and focused.

These assertions cover class behavior and media asset inclusion clearly.

openwisp_utils/widgets.py (1)

10-29: Media assembly is correct and complete for admin Select2 usage.

The DEBUG-aware assets, optional i18n file, and custom initializer script are wired consistently.

Comment thread openwisp_utils/widgets.py
Comment on lines +31 to +34
def __init__(self, attrs=None, choices=()):
attrs = attrs or {}
attrs["class"] = "ow-select2 {0}".format(attrs.get("class", "")).strip()
super().__init__(attrs, choices)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Avoid mutating caller-provided attrs in place.

Copy attrs first to prevent side effects when the same dict instance is reused.

Suggested refactor
     def __init__(self, attrs=None, choices=()):
-        attrs = attrs or {}
+        attrs = dict(attrs or {})
         attrs["class"] = "ow-select2 {0}".format(attrs.get("class", "")).strip()
         super().__init__(attrs, choices)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def __init__(self, attrs=None, choices=()):
attrs = attrs or {}
attrs["class"] = "ow-select2 {0}".format(attrs.get("class", "")).strip()
super().__init__(attrs, choices)
def __init__(self, attrs=None, choices=()):
attrs = dict(attrs or {})
attrs["class"] = "ow-select2 {0}".format(attrs.get("class", "")).strip()
super().__init__(attrs, choices)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openwisp_utils/widgets.py` around lines 31 - 34, The __init__ mutates the
caller-provided attrs dict in place; change it to copy attrs first (e.g., attrs
= dict(attrs) or attrs.copy() when attrs is truthy) before updating the "class"
key so the original dict is not modified; update the __init__ implementation
(the attrs handling and the super().__init__(attrs, choices) call) in the widget
class to use the copied dict.

Comment on lines +37 to +41
def test_select2_widget_renders_on_shelf_add_form(self):
url = reverse("admin:test_project_shelf_add")
self.open(url)
self.wait_for_presence(By.CSS_SELECTOR, "select#id_books_type.ow-select2")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add-page Selenium test should also assert Select2 initialization, not just CSS class.

This test currently proves widget markup but not JS enhancement on add view. Please also assert the Select2 container is present on this path.

Suggested test tightening
     def test_select2_widget_renders_on_shelf_add_form(self):
         url = reverse("admin:test_project_shelf_add")
         self.open(url)
         self.wait_for_presence(By.CSS_SELECTOR, "select#id_books_type.ow-select2")
+        self.wait_for_presence(By.CSS_SELECTOR, ".select2-container")

Based on learnings: Features: Add tests for new features and ensure coverage does not decrease significantly; prefer Selenium browser tests for UI-impacting features.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_project/tests/test_widgets.py` around lines 37 - 41, The test
test_select2_widget_renders_on_shelf_add_form currently only checks for the
select element and its .ow-select2 class; update it to also assert the Select2
JS enhancement by waiting for the Select2 container for that field (for example
by waiting for a selector such as ".select2-container" or the field-specific
container tied to "id_books_type") to be present in the DOM after opening
reverse("admin:test_project_shelf_add"); use the existing helper
wait_for_presence to locate the Select2 container adjacent to or tied to the
select#id_books_type element to ensure initialization rather than just markup.

@shivsubh shivsubh requested a review from pandafy April 9, 2026 06:55
Implement a reusable Select2Widget that uses Django's native admin assets to avoid extra dependencies.

Closes openwisp#254
@shivsubh shivsubh force-pushed the issues/254-select2-autocomplete-fields branch from 2ccf8d1 to 0be733c Compare April 18, 2026 03:24
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
tests/test_project/tests/test_widgets.py (1)

37-41: ⚠️ Potential issue | 🟡 Minor

Assert Select2 initialization on the add page too.

This currently checks only the rendered <select> class; add the same container assertion used on the change page.

💚 Proposed test tightening
     def test_select2_widget_renders_on_shelf_add_form(self):
         url = reverse("admin:test_project_shelf_add")
         self.open(url)
         self.wait_for_presence(By.CSS_SELECTOR, "select#id_books_type.ow-select2")
+        self.wait_for_presence(By.CSS_SELECTOR, ".select2-container")

Based on learnings: Features: Add tests for new features and ensure coverage does not decrease significantly; prefer Selenium browser tests for UI-impacting features.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_project/tests/test_widgets.py` around lines 37 - 41, The add-page
test test_select2_widget_renders_on_shelf_add_form currently only asserts the
rendered <select> class; update it to also assert the Select2 container is
initialized by calling wait_for_presence with By.CSS_SELECTOR for the Select2
container (the same container selector used in the change-page test) immediately
after the existing select#id_books_type.ow-select2 check so the test verifies
both the original select element and the Select2 container are present.
openwisp_utils/widgets.py (1)

31-34: 🧹 Nitpick | 🔵 Trivial

Copy attrs before adding the Select2 class.

This mutates the caller-owned dict and can leak ow-select2 into reused attrs.

♻️ Proposed fix
     def __init__(self, attrs=None, choices=()):
-        attrs = attrs or {}
+        attrs = dict(attrs or {})
         attrs["class"] = "ow-select2 {0}".format(attrs.get("class", "")).strip()
         super().__init__(attrs, choices)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openwisp_utils/widgets.py` around lines 31 - 34, The __init__ currently
mutates the caller-supplied attrs dict which can leak the "ow-select2" class
into reused dicts; fix by making a shallow copy of attrs before modifying (e.g.,
use attrs = dict(attrs or {}) or attrs.copy()) so you add attrs["class"] =
"ow-select2 {0}".format(attrs.get("class", "")).strip() on the copied dict and
then call super().__init__(attrs, choices) — update the __init__ method in the
widget class to perform this non-mutating change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@openwisp_utils/static/openwisp-utils/js/select2.js`:
- Around line 15-17: The formset added handler in
openwisp-utils/static/openwisp-utils/js/select2.js assumes the added row is
passed as the second argument ($row) but Django 4.1 emits a CustomEvent with the
row at event.target; update the handler to normalize the row variable before
calling initSelect2—e.g., in the $(document).on("formset:added", ...) callback
determine rowEl = $row ? $row : $(event.target) (or wrap event.target with $ if
needed) and then call initSelect2(rowEl.find("select.ow-select2")); ensure you
reference the existing event handler and initSelect2 function names so
compatibility with Django >=2.2 and Django 4.1+ is preserved.

---

Duplicate comments:
In `@openwisp_utils/widgets.py`:
- Around line 31-34: The __init__ currently mutates the caller-supplied attrs
dict which can leak the "ow-select2" class into reused dicts; fix by making a
shallow copy of attrs before modifying (e.g., use attrs = dict(attrs or {}) or
attrs.copy()) so you add attrs["class"] = "ow-select2
{0}".format(attrs.get("class", "")).strip() on the copied dict and then call
super().__init__(attrs, choices) — update the __init__ method in the widget
class to perform this non-mutating change.

In `@tests/test_project/tests/test_widgets.py`:
- Around line 37-41: The add-page test
test_select2_widget_renders_on_shelf_add_form currently only asserts the
rendered <select> class; update it to also assert the Select2 container is
initialized by calling wait_for_presence with By.CSS_SELECTOR for the Select2
container (the same container selector used in the change-page test) immediately
after the existing select#id_books_type.ow-select2 check so the test verifies
both the original select element and the Select2 container are present.
🪄 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: 6f6811f8-d619-4264-aa9c-a5ebf336cd61

📥 Commits

Reviewing files that changed from the base of the PR and between 2ccf8d1 and 0be733c.

📒 Files selected for processing (5)
  • openwisp_utils/admin.py
  • openwisp_utils/static/openwisp-utils/js/select2.js
  • openwisp_utils/widgets.py
  • tests/test_project/admin.py
  • tests/test_project/tests/test_widgets.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). (15)
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.0.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.0.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.0.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Kilo Code Review
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{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:

  • openwisp_utils/admin.py
  • openwisp_utils/widgets.py
  • tests/test_project/admin.py
  • tests/test_project/tests/test_widgets.py
🧠 Learnings (3)
📚 Learning: 2026-03-14T20:44:14.568Z
Learnt from: CR
Repo: openwisp/openwisp-utils PR: 0
File: coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt:0-0
Timestamp: 2026-03-14T20:44:14.568Z
Learning: Features: Add tests for new features and ensure coverage does not decrease significantly; prefer Selenium browser tests for UI-impacting features

Applied to files:

  • tests/test_project/tests/test_widgets.py
📚 Learning: 2026-03-14T20:44:14.568Z
Learnt from: CR
Repo: openwisp/openwisp-utils PR: 0
File: coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt:0-0
Timestamp: 2026-03-14T20:44:14.568Z
Learning: Changes: Update tests to cover non-trivial changes and ensure proper validation of modified behavior

Applied to files:

  • tests/test_project/tests/test_widgets.py
📚 Learning: 2026-03-14T20:44:14.568Z
Learnt from: CR
Repo: openwisp/openwisp-utils PR: 0
File: coderabbit-custom-pre-merge-checks-unique-id-file-non-traceable-F7F2B60C-1728-4C9A-8889-4F2235E186CA.txt:0-0
Timestamp: 2026-03-14T20:44:14.568Z
Learning: Bug Fixes: If the bug affects the user interface, include a Selenium browser test; if missing, raise a warning

Applied to files:

  • tests/test_project/tests/test_widgets.py
🔇 Additional comments (2)
tests/test_project/admin.py (1)

10-10: LGTM: reusable Select2 admin configuration is applied to a real choice field.

books_type is a model choice field, so this exercises the new mixin without requiring per-admin formfield_for_choice_field overrides.

Also applies to: 113-125

openwisp_utils/admin.py (1)

5-5: LGTM: the mixin keeps the widget swap narrowly scoped to choice fields.

The super() delegation preserves Django admin’s normal choice-field handling while applying Select2Widget only for configured fields.

Also applies to: 223-231

Comment on lines +15 to +17
$(document).on("formset:added", function (event, $row) {
initSelect2($row.find("select.ow-select2"));
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect repository dependency constraints for supported Django versions.

set -euo pipefail

fd -H '^(setup\.py|setup\.cfg|pyproject\.toml|tox\.ini|requirements.*\.txt)$' \
  -x sh -c 'echo "### $1"; rg -n "Django|django" "$1" || true' sh {}

Repository: openwisp/openwisp-utils

Length of output: 595


🌐 Web query:

Django admin formset:added event signature event target row Django 4.1

💡 Result:

In Django 4.1 admin, the formset:added event is a JavaScript CustomEvent triggered when a new inline form row is added. Event signature: formset:added (dispatched on document). Event properties: - event.target: The newly added row element (HTMLElement). - event.detail.formsetName: The name of the formset (string). Usage example: document.addEventListener('formset:added', (event) => { if (event.detail.formsetName === 'my_formset') { // Access the new row const row = event.target; // Perform actions on the row } }); This changed in Django 4.1 from pre-4.1 jQuery events (event, $row, formsetName). For backward compatibility, check event.detail. Register in admin change_form.html by extending admin_change_form_document_ready block and including your JS.

Citations:


🏁 Script executed:

#!/bin/bash
# Search for the main Django version requirement in setup.py
cat setup.py | grep -A 5 -B 5 "install_requires" | head -30

Repository: openwisp/openwisp-utils

Length of output: 450


🏁 Script executed:

#!/bin/bash
# Check if Django version is specified anywhere else (requirements, tox, etc.)
rg "Django.*>=|Django.*<|django.*version" --no-heading

Repository: openwisp/openwisp-utils

Length of output: 684


🏁 Script executed:

#!/bin/bash
# Check the full select2.js file to see if there's any version handling or comments about Django
cat openwisp_utils/static/openwisp-utils/js/select2.js

Repository: openwisp/openwisp-utils

Length of output: 494


Normalize the added formset row before calling .find() for Django 4.1+ compatibility.

The formset:added event API changed in Django 4.1: the added row is now available via event.target as a CustomEvent, not as a second parameter ($row). The current code will fail with $row undefined and prevent Select2 initialization on dynamically added inline rows in Django 4.1+. The codebase supports Django >= 2.2 with no upper bound, so compatibility is required.

🐛 Proposed cross-version fix
-  $(document).on("formset:added", function (event, $row) {
-    initSelect2($row.find("select.ow-select2"));
+  $(document).on("formset:added", function (event, $row) {
+    var $targetRow = $row ? $($row) : $(event.target);
+    initSelect2($targetRow.find("select.ow-select2"));
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$(document).on("formset:added", function (event, $row) {
initSelect2($row.find("select.ow-select2"));
});
$(document).on("formset:added", function (event, $row) {
var $targetRow = $row ? $($row) : $(event.target);
initSelect2($targetRow.find("select.ow-select2"));
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openwisp_utils/static/openwisp-utils/js/select2.js` around lines 15 - 17, The
formset added handler in openwisp-utils/static/openwisp-utils/js/select2.js
assumes the added row is passed as the second argument ($row) but Django 4.1
emits a CustomEvent with the row at event.target; update the handler to
normalize the row variable before calling initSelect2—e.g., in the
$(document).on("formset:added", ...) callback determine rowEl = $row ? $row :
$(event.target) (or wrap event.target with $ if needed) and then call
initSelect2(rowEl.find("select.ow-select2")); ensure you reference the existing
event handler and initSelect2 function names so compatibility with Django >=2.2
and Django 4.1+ is preserved.

@openwisp-companion
Copy link
Copy Markdown

Hi @shivsubh 👋,

This is a friendly reminder that this pull request has had no activity for 7 days since changes were requested.

We'd love to see this contribution merged! Please take a moment to:

  • Address the review feedback
  • Push your changes
  • Let us know if you have any questions or need clarification

If you're busy or need more time, no worries! Just leave a comment to let us know you're still working on it.

Note: within 7 more days, the linked issue will be unassigned to allow other contributors to work on it.

Thank you for your contribution! 🙏

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

Labels

None yet

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

[feature] Add reusable solution for turning choice fields into select2 autocomplete fields

3 participants