Skip to content

[feature] Implemented sub-filter functionality in Django admin#700

Open
pandafy wants to merge 2 commits into
masterfrom
sub-filter
Open

[feature] Implemented sub-filter functionality in Django admin#700
pandafy wants to merge 2 commits into
masterfrom
sub-filter

Conversation

@pandafy

@pandafy pandafy commented Jun 9, 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.

Description of Changes

This change introduces SubFilterMixin, a reusable mixin that allows admin filters to be displayed and applied only when a parent filter has specific values.

To support this functionality, the filter rendering system has been enhanced to group parent filters with their sub-filters, rendering sub-filters vertically below their parent. Client-side logic has been added to dynamically show or hide sub-filters as parent filter values change.

Invalid sub-filter usage is now rejected by raising IncorrectLookupParameters when a sub-filter value is supplied while its parent filter is inactive. Misconfigured orphaned sub-filters are not rendered and are logged as errors to help identify configuration issues.

Comprehensive unit and Selenium tests have been added to verify filter visibility, queryset filtering, invalid parameter handling, rendering behavior, and orphaned sub-filter detection.

Screenshot

The sub-filter is shown when the "Type of Book" is set to "HORROR"
image

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements conditional admin "sub-filters": a SubFilterMixin enforces parent-dependent filter application on the backend; template tag logic groups parent filters with their sub-filters and emits metadata; JS/CSS control sub-filter visibility and layout; a test-project CreatedSubFilter demonstrates usage; unit and Selenium tests cover behavior and rendering; and Sphinx docs were added.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant JS as ow-filter.js
  participant Template as ow_render_filters
  participant DjangoAdmin
  participant DB

  Browser->>JS: DOMContentLoaded -> initSubFilterVisibility()
  JS->>Template: read data-sub-filter-parent / data-sub-filter-active-values
  Browser->>JS: user selects parent filter
  JS->>JS: updateSubFilters() -> show/hide .ow-sub-filter
  Browser->>DjangoAdmin: submit changelist with parent(+optional sub) query params
  DjangoAdmin->>DjangoAdmin: SubFilterMixin.is_parent_active checks request params
  DjangoAdmin->>DjangoAdmin: SubFilterMixin.filter_queryset() applies when parent active
  DjangoAdmin->>DB: execute filtered queryset
  DB-->>DjangoAdmin: results
  DjangoAdmin-->>Browser: render changelist (templates include sub-filter metadata)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main feature addition by describing the implementation of sub-filter functionality in Django admin, which aligns with the substantial changes across multiple files and the primary objective of this pull request.
Description check ✅ Passed The description covers all required checklist items (marked complete), includes a detailed explanation of changes, references the feature implementation with technical specifics, and provides a screenshot demonstrating the functionality. However, it lacks an explicit issue reference.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Bug Fixes ✅ Passed This PR implements a new feature (SubFilterMixin for Django admin sub-filters), not a bug fix. The bug fix check only applies to bug fix PRs, making it not applicable here.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sub-filter

Comment @coderabbitai help to get the list of available commands and usage tips.

@pandafy pandafy moved this from To do (general) to In progress in OpenWISP Contributor's Board Jun 9, 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).

@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 `@openwisp_utils/admin_theme/filters.py`:
- Around line 154-156: Add a unit test that exercises the is_parent_active
branch where request.GET contains the "{parent_parameter_name}__exact" key:
create a mock request with GET including f"{parent_parameter_name}__exact"
(matching a parent id/value used by the test), instantiate or subclass
SubFilterMixin (as other tests do), call is_parent_active and assert the
expected boolean result; name the test similar to existing ones (e.g.,
test_sub_filter_is_parent_active_exact_parent_parameter_name) and follow the
setup pattern from test_sub_filter_is_parent_active_no_parent to ensure it hits
the "__exact" branch.
🪄 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: 9e928198-7da3-407c-b0c2-3534437284fe

📥 Commits

Reviewing files that changed from the base of the PR and between 764ea49 and 9870be8.

📒 Files selected for processing (11)
  • docs/developer/admin-utilities.rst
  • docs/user/admin-filters.rst
  • openwisp_utils/admin_theme/filters.py
  • openwisp_utils/admin_theme/static/admin/css/ow-filters.css
  • openwisp_utils/admin_theme/static/admin/js/ow-filter.js
  • openwisp_utils/admin_theme/templates/admin/change_list.html
  • openwisp_utils/admin_theme/templates/admin/filter.html
  • openwisp_utils/admin_theme/templatetags/ow_tags.py
  • tests/test_project/admin.py
  • tests/test_project/tests/test_admin.py
  • tests/test_project/tests/test_selenium.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.10 | 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.12 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.0.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{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_theme/templates/admin/change_list.html
  • openwisp_utils/admin_theme/templates/admin/filter.html
  • tests/test_project/tests/test_selenium.py
  • openwisp_utils/admin_theme/filters.py
  • tests/test_project/admin.py
  • tests/test_project/tests/test_admin.py
  • openwisp_utils/admin_theme/templatetags/ow_tags.py
**/*.{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/user/admin-filters.rst
  • docs/developer/admin-utilities.rst
**/*.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_selenium.py
  • openwisp_utils/admin_theme/filters.py
  • tests/test_project/admin.py
  • tests/test_project/tests/test_admin.py
  • openwisp_utils/admin_theme/templatetags/ow_tags.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_selenium.py
  • tests/test_project/admin.py
  • tests/test_project/tests/test_admin.py
🪛 HTMLHint (1.9.2)
openwisp_utils/admin_theme/templates/admin/filter.html

[error] 3-3: Special characters must be escaped : [ < ].

(spec-char-escape)


[error] 3-3: Special characters must be escaped : [ > ].

(spec-char-escape)

🪛 OpenGrep (1.22.0)
openwisp_utils/admin_theme/templatetags/ow_tags.py

[WARNING] 96-96: Django mark_safe() with dynamic content can lead to XSS. Only use mark_safe() with trusted, pre-escaped content.

(coderabbit.xss.python-mark-safe)

🔇 Additional comments (19)
docs/developer/admin-utilities.rst (1)

228-279: LGTM!

docs/user/admin-filters.rst (1)

20-28: LGTM!

tests/test_project/admin.py (1)

1-1: LGTM!

Also applies to: 4-4, 7-7, 21-21, 55-94

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

3-3: LGTM!

Also applies to: 8-8, 11-23, 41-42, 628-776

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

721-803: LGTM!

openwisp_utils/admin_theme/filters.py (2)

165-170: LGTM!


172-173: LGTM!

openwisp_utils/admin_theme/templatetags/ow_tags.py (5)

1-1: LGTM!

Also applies to: 6-6, 8-8


12-27: LGTM!


30-32: LGTM!


65-67: Well-designed parent-child matching logic.

The matching accommodates both exact parameter names and Django's __exact suffix pattern, ensuring compatibility with FieldListFilter behavior.


86-96: Review mark_safe() safety in ow_render_filters()

openwisp_utils/admin_theme/templatetags/ow_tags.py marks the final concatenated HTML as safe (mark_safe("".join(output))). That string includes both hardcoded wrapper <div> tags and the HTML returned by _render_single_filter() (which renders tpl = get_template(spec.template) with context like title, selected_choice / choice["display"], choices, and spec). This is only safe if all filter templates referenced by spec.template rely on Django auto-escaping and never disable it or mark user-controlled values as safe.

Ensure the filter templates (including admin/filter.html, admin/input_filter.html, admin/auto_filter.html, and any other templates reachable via spec.template) do not use {% autoescape off %} or |safe, and that spec.title / choice["display"] are not pre-wrapped as SafeString/mark_safe content.

openwisp_utils/admin_theme/templates/admin/change_list.html (1)

56-56: LGTM!

openwisp_utils/admin_theme/static/admin/css/ow-filters.css (1)

243-254: LGTM!

openwisp_utils/admin_theme/static/admin/js/ow-filter.js (4)

18-18: LGTM!


134-134: LGTM!


229-255: Sub-filter visibility logic is well-structured.

The functions correctly traverse the DOM to find parent-child relationships and apply visibility rules. However, the correctness depends on getFilterValue() returning parameter values rather than display text (see previous comment).


220-227: Reconsider: getFilterValue() uses selected.title for link-based filters, but current tests suggest values match for shelf__books_type

openwisp_utils/admin_theme/static/admin/js/ow-filter.js (lines 220-227) returns selected.title for non-select filters, and the filter template sets that title from choice.display. If choice.display can differ from the actual query-parameter value, the parent_active_values comparison could break.

However, the existing Selenium coverage for CreatedSubFilter (test_sub_filter_visible_when_parent_active using ?shelf__books_type=HORROR and test_sub_filter_hidden_when_parent_inactive using ?shelf__books_type=FANTASY) indicates getFilterValue() matches "HORROR"/"FANTASY" correctly for the test fixture, so the reported issue doesn’t manifest here. Confirm that this holds for any cases where choice.display ≠ option value; otherwise update getFilterValue() to read the underlying value (e.g., from the option’s query string or a data attribute).

openwisp_utils/admin_theme/templates/admin/filter.html (1)

3-3: data-sub-filter-* values are rendered through Django auto-escaping (no request-driven injection)

data-sub-filter-parent and data-sub-filter-active-values in openwisp_utils/admin_theme/templates/admin/filter.html are output via normal Django {{ ... }} expressions (spec.parent_parameter_name and spec.parent_active_values|join:','). The values originate from filter class attributes (SubFilterMixin.parent_parameter_name / parent_active_values, and examples in tests use simple literals), not from request.GET (which is used only to compute spec.is_parent_active). Django’s escaping should prevent breaking out of the attribute and injecting HTML/JS via quotes/markup.

If a filter class is populated with attacker-controlled input (i.e., filter authors aren’t trusted), validate/sanitize parent_parameter_name and parent_active_values to match the expected formats even though Django will escape them in the template.

Comment thread openwisp_utils/admin_theme/filters.py
@openwisp-companion

Copy link
Copy Markdown

ReST Formatting and Test Failures

Hello @pandafy,
(Analysis for commit 9870be8)

  1. Code Style/QA: The ReStructuredText check failed because two files in the docs/ directory (admin-utilities.rst and admin-filters.rst) could be reformatted.
  • Fix: Run openwisp-qa-format to automatically fix these files.
  1. Test Failure: The test test_sub_filter_visible_when_parent_active in test_project.tests.test_selenium.TestSubFilter failed due to a TimeoutException when waiting for an element with the CSS selector .ow-filter.created-date. This indicates that the element did not become visible within the expected timeout.
  • Fix: Investigate why the .ow-filter.created-date element is not becoming visible in the test environment. This might be due to a change in the UI, a timing issue, or an incorrect selector.
  1. Test Failure: The tests test_sub_filter_empty_active_values, test_sub_filter_invalid_when_parent_not_active, and test_sub_filter_invalid_with_wrong_parent_value in test_project.tests.test_admin.TestAdmin failed with AssertionError.
  • test_sub_filter_empty_active_values: True != False
  • test_sub_filter_invalid_when_parent_not_active: 200 != 302
  • test_sub_filter_invalid_with_wrong_parent_value: 200 != 302
  • Fix: Review the logic in the test_admin.py file for these tests and the corresponding code that handles sub-filters to understand why the assertions are failing. Adjust the test or the code to meet the expected behavior.
  1. Infrastructure: The cat geckodriver.log command failed with "No such file or directory". This suggests that the geckodriver.log file was not created, possibly due to an earlier failure in the Firefox/GeckoDriver setup or execution. This is likely a transient infrastructure issue.

This change introduces ``SubFilterMixin``, a reusable mixin that allows
admin filters to be displayed and applied only when a parent filter has
specific values.

To support this functionality, the filter rendering system has been
enhanced to group parent filters with their sub-filters, rendering
sub-filters vertically below their parent. Client-side logic has been
added to dynamically show or hide sub-filters as parent filter values
change.

Invalid sub-filter usage is now rejected by raising
``IncorrectLookupParameters`` when a sub-filter value is supplied while
its parent filter is inactive. Misconfigured orphaned sub-filters are
not rendered and are logged as errors to help identify configuration
issues.

Comprehensive unit and Selenium tests have been added to verify
filter visibility, queryset filtering, invalid parameter handling,
rendering behavior, and orphaned sub-filter detection.
@openwisp-companion

Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3).

@openwisp-companion

Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (2/3).

@openwisp-companion

Copy link
Copy Markdown

The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (3/3).

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

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

Caution

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

⚠️ Outside diff range comments (1)
tests/test_project/tests/test_admin.py (1)

688-704: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Misleading test name and missing coverage for parent_parameter_name=None.

test_sub_filter_is_parent_active_no_parent (line 688) uses CreatedSubFilter, which HAS parent_parameter_name="shelf__books_type". The test name implies testing a filter without a parent, but it actually tests the request=None case. Additionally, test_sub_filter_is_parent_active_no_request (line 694) covers the same scenario, making these tests largely duplicate.

More importantly, the branch at openwisp_utils/admin_theme/filters.py:156-157 (if not self.parent_parameter_name: return True) is never exercised by any test. This branch handles filters that legitimately have no parent dependency.

🧪 Recommended changes
  1. Rename test_sub_filter_is_parent_active_no_parent to test_sub_filter_is_parent_active_with_parent_and_no_request or consolidate with the test at line 694.

  2. Add a new test that actually exercises the parent_parameter_name=None branch:

def test_sub_filter_is_parent_active_no_parent_parameter(self):
    class NoParentParameterSubFilter(SubFilterMixin, SimpleInputFilter):
        title = "Test"
        parameter_name = "test"
        parent_parameter_name = None  # or ""
        parent_active_values = ()
    
    request = HttpRequest()
    request.GET = {"some_filter": "value"}
    filter = NoParentParameterSubFilter(
        request=request, params={}, model=Book, model_admin=BookAdmin
    )
    self.assertEqual(filter.is_parent_active, True)
🤖 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 `@tests/test_project/tests/test_admin.py` around lines 688 - 704, Rename or
consolidate the misleading test test_sub_filter_is_parent_active_no_parent
(which actually uses CreatedSubFilter with parent_parameter_name set) to a name
like test_sub_filter_is_parent_active_with_parent_and_no_request or merge it
with test_sub_filter_is_parent_active_no_request, and add a new unit test that
instantiates a filter class (subclassing SubFilterMixin and SimpleInputFilter,
e.g., NoParentParameterSubFilter) with parent_parameter_name set to None (or
empty string) and verifies its is_parent_active property returns True to
exercise the branch in SubFilterMixin.is_parent_active that checks if not
self.parent_parameter_name.

Source: Coding guidelines

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

Outside diff comments:
In `@tests/test_project/tests/test_admin.py`:
- Around line 688-704: Rename or consolidate the misleading test
test_sub_filter_is_parent_active_no_parent (which actually uses CreatedSubFilter
with parent_parameter_name set) to a name like
test_sub_filter_is_parent_active_with_parent_and_no_request or merge it with
test_sub_filter_is_parent_active_no_request, and add a new unit test that
instantiates a filter class (subclassing SubFilterMixin and SimpleInputFilter,
e.g., NoParentParameterSubFilter) with parent_parameter_name set to None (or
empty string) and verifies its is_parent_active property returns True to
exercise the branch in SubFilterMixin.is_parent_active that checks if not
self.parent_parameter_name.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c875e7c3-c084-4c6a-b6b2-8e01f4116d91

📥 Commits

Reviewing files that changed from the base of the PR and between 6cafacf and 585c601.

📒 Files selected for processing (3)
  • openwisp_utils/admin_theme/filters.py
  • tests/test_project/tests/test_admin.py
  • tests/test_project/tests/test_selenium.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.11 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.0.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.0.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.11 | 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.10 | django~=5.0.0
🧰 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:

  • openwisp_utils/admin_theme/filters.py
  • tests/test_project/tests/test_admin.py
  • tests/test_project/tests/test_selenium.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:

  • openwisp_utils/admin_theme/filters.py
  • tests/test_project/tests/test_admin.py
  • tests/test_project/tests/test_selenium.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_admin.py
  • tests/test_project/tests/test_selenium.py
🔇 Additional comments (10)
openwisp_utils/admin_theme/filters.py (2)

150-152: LGTM!


170-170: LGTM!

tests/test_project/tests/test_admin.py (7)

628-647: LGTM!


649-665: LGTM!


667-686: LGTM!


706-718: LGTM!


720-747: LGTM!


749-757: LGTM!


759-789: LGTM!

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

721-802: LGTM!

The TestSubFilter class provides comprehensive coverage of the sub-filter feature. The tests properly verify:

  • Default hidden state
  • Conditional visibility based on parent filter values (HORROR vs FANTASY)
  • Backend queryset filtering when both parent and sub-filter are active
  • Proper use of Selenium waits and assertions

As per coding guidelines, please ensure you run openwisp-qa-format after editing this file.

Source: Coding guidelines

@coveralls

Copy link
Copy Markdown

Coverage Status

coverage: 97.619% (-0.01%) from 97.629% — sub-filter into master

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.

2 participants