[PR #20747] [MERGED] Closes #7604: Add filter modifier dropdowns for advanced lookup operators #16040

Closed
opened 2025-12-30 00:25:27 +01:00 by adam · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/netbox-community/netbox/pull/20747
Author: @jnovinger
Created: 11/4/2025
Status: Merged
Merged: 12/5/2025
Merged by: @jeremystretch

Base: featureHead: 7604-filter-modifiers-v4


📝 Commits (10+)

  • fd67acc Fixes #7604: Add filter modifier dropdowns for advanced lookup operators
  • ad96fb3 Remove extraneous TS comments
  • 5458873 Fix import order
  • aeb6024 Fix CircuitFilterForm inheritance
  • 8d191b5 Enable filter form modifiers on DCIM models
  • 9cb7f4b Enable filter form modifiers on Tenancy models
  • 067c670 Enable filter form modifiers on Wireless models
  • e7ad66f Enable filter form modifiers on IPAM models
  • c0de874 Enable filter form modifiers on VPN models
  • 93b9347 Enable filter form modifiers on Virtualization models

📊 Changes

33 files changed (+1023 additions, -28 deletions)

View changed files

📝 docs/development/application-registry.md (+4 -0)
📝 docs/plugins/development/filtersets.md (+12 -1)
📝 netbox/circuits/filtersets.py (+12 -0)
📝 netbox/core/filtersets.py (+7 -0)
📝 netbox/dcim/filtersets.py (+45 -0)
📝 netbox/dcim/forms/filtersets.py (+1 -0)
📝 netbox/extras/filtersets.py (+19 -0)
📝 netbox/extras/forms/filtersets.py (+1 -0)
📝 netbox/ipam/filtersets.py (+20 -2)
📝 netbox/netbox/filtersets.py (+1 -0)
📝 netbox/netbox/forms/filtersets.py (+4 -3)
📝 netbox/netbox/registry.py (+1 -0)
📝 netbox/project-static/dist/netbox.css (+1 -1)
📝 netbox/project-static/dist/netbox.js (+4 -4)
📝 netbox/project-static/dist/netbox.js.map (+4 -4)
netbox/project-static/src/forms/filterModifiers.ts (+179 -0)
📝 netbox/project-static/src/forms/index.ts (+2 -1)
📝 netbox/project-static/styles/transitional/_forms.scss (+8 -0)
📝 netbox/tenancy/filtersets.py (+7 -0)
📝 netbox/users/filtersets.py (+7 -1)

...and 13 more files

📄 Description

Closes: #7604

Implements dynamic filter modifier UI that allows users to select lookup operators (exact, contains, starts with, regex, negation, empty/not empty) directly in filter forms without manual URL parameter editing.

Supports filters for all scalar types and strings, as well as some related object filters. Explicitly does not support filters on fields that use APIWidget. That has been broken out in to follow up work.

How It Works

User selects "contains" ─────────────────┐
                                         │
                                         ▼
┌──────────────────────────────────────────────────────────┐
│  Filter Form (Browser)                                   │
│  ┌────────────────┐  ┌──────────────────────────┐        │
│  │ [Contains ▼]   │  │ Input: ABC               │        │
│  └────────────────┘  └──────────────────────────┘        │
│         │                                                │
│         │ TypeScript handler (filterModifiers.ts)        │
│         ▼                                                │
│  Updates input name: "serial" -> "serial__ic"            │
│  Updates URL param:  ?serial__ic=ABC                     │
└──────────────────────────────────────────────────────────┘
                         │
                         │ Form submission
                         ▼
┌──────────────────────────────────────────────────────────┐
│  Django Backend                                          │
│                                                          │
│  FilterModifierWidget                                    │
│    ↓ Renders modifier dropdown + original widget         │
│                                                          │
│  FilterModifierMixin                                     │
│    ↓ Enhances filterset fields with appropriate lookups  │
│                                                          │
│  FilterSet processes: serial__ic=ABC                     │
│    ↓ Django ORM: .filter(serial__icontains='ABC')        │
│                                                          │
│  Results + Filter Pills                                  │
│    ↓ "Serial: contains ABC [×]"                          │
└──────────────────────────────────────────────────────────┘

Details

Backend: FilterModifierWidget

Wraps any Django form widget with a modifier dropdown. Key method:

def value_from_datadict(self, data, files, name):
    # Check all possible lookup variants (exact, ic, isw, n, etc.)
    for lookup_code, _ in self.lookups:
        param_name = f"{name}__{lookup_code}" if lookup_code != 'exact' else name
        if param_name in data:
            return data.get(param_name)

This allows the widget to find its value regardless of which lookup modifier is active in the URL.

Backend: FilterModifierMixin

Automatically enhances filterset form fields based on their type:

  • CharField -> exact, contains (ic), startswith (isw), endswith (iew), iexact (ie), negation (n), regex, iregex, empty
  • IntegerField -> exact, gte, lte, gt, lt, negation (n), empty
  • DecimalField -> exact, gte, lte, gt, lt, negation (n), empty
  • DateField -> exact, gte, lte, gt, lt, negation (n), empty
  • ChoiceField -> exact, negation (n), empty
  • MultipleChoiceField -> exact, negation (n), empty
  • ModelChoiceField -> exact, negation (n), empty
  • ColorField -> exact, negation (n), empty
  • TagFilterField -> exact, negation (n), empty
Frontend: filterModifiers.ts

Typescript handler that:

  1. Detects modifier selection change
  2. Updates the associated input's name attribute
  3. Syncs URL parameters when navigating with existing filters
Filter Pills Enhancement

Modified applied_filters template tag to:

  1. Detect lookup modifier in parameter name (serial__ic)
  2. Display human-readable label ("contains") instead of raw lookup code
  3. Maintain correct removal URL (preserves other filters)

Scope & Compatibility

What's changed:

  • All FilterForm subclasses now have modifier dropdowns
  • Filter pills show lookup type
  • URL parameters include lookup suffixes (__ic, __n, etc.)

What's NOT changed:

  • Existing URL parameters still work (backward compatible)
  • Django FilterSet behavior unchanged
  • No database schema changes
  • No API changes

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/netbox-community/netbox/pull/20747 **Author:** [@jnovinger](https://github.com/jnovinger) **Created:** 11/4/2025 **Status:** ✅ Merged **Merged:** 12/5/2025 **Merged by:** [@jeremystretch](https://github.com/jeremystretch) **Base:** `feature` ← **Head:** `7604-filter-modifiers-v4` --- ### 📝 Commits (10+) - [`fd67acc`](https://github.com/netbox-community/netbox/commit/fd67acc3ab7329e034fd8041ab149f24023951ff) Fixes #7604: Add filter modifier dropdowns for advanced lookup operators - [`ad96fb3`](https://github.com/netbox-community/netbox/commit/ad96fb3ab464c9b80680ed72ca87a2b0c8ebb829) Remove extraneous TS comments - [`5458873`](https://github.com/netbox-community/netbox/commit/5458873cdcad1e5adec1f0b24ac0d25625dadb25) Fix import order - [`aeb6024`](https://github.com/netbox-community/netbox/commit/aeb6024502dfc5078813eb9e555b1087c2599fb5) Fix CircuitFilterForm inheritance - [`8d191b5`](https://github.com/netbox-community/netbox/commit/8d191b5d5b49583889b40df06dfdd836d9ae2839) Enable filter form modifiers on DCIM models - [`9cb7f4b`](https://github.com/netbox-community/netbox/commit/9cb7f4b9adfc16ca95072465714cfc1c1fbb60bf) Enable filter form modifiers on Tenancy models - [`067c670`](https://github.com/netbox-community/netbox/commit/067c670243167acfa7262b7dc23e92ea05257de1) Enable filter form modifiers on Wireless models - [`e7ad66f`](https://github.com/netbox-community/netbox/commit/e7ad66f2ef31338f8bc48483f8eaaa157a66b1bb) Enable filter form modifiers on IPAM models - [`c0de874`](https://github.com/netbox-community/netbox/commit/c0de8748a25cc8d37adc130c8dabb1c159ef086a) Enable filter form modifiers on VPN models - [`93b9347`](https://github.com/netbox-community/netbox/commit/93b934701dbf6e615246d59785baef321da61987) Enable filter form modifiers on Virtualization models ### 📊 Changes **33 files changed** (+1023 additions, -28 deletions) <details> <summary>View changed files</summary> 📝 `docs/development/application-registry.md` (+4 -0) 📝 `docs/plugins/development/filtersets.md` (+12 -1) 📝 `netbox/circuits/filtersets.py` (+12 -0) 📝 `netbox/core/filtersets.py` (+7 -0) 📝 `netbox/dcim/filtersets.py` (+45 -0) 📝 `netbox/dcim/forms/filtersets.py` (+1 -0) 📝 `netbox/extras/filtersets.py` (+19 -0) 📝 `netbox/extras/forms/filtersets.py` (+1 -0) 📝 `netbox/ipam/filtersets.py` (+20 -2) 📝 `netbox/netbox/filtersets.py` (+1 -0) 📝 `netbox/netbox/forms/filtersets.py` (+4 -3) 📝 `netbox/netbox/registry.py` (+1 -0) 📝 `netbox/project-static/dist/netbox.css` (+1 -1) 📝 `netbox/project-static/dist/netbox.js` (+4 -4) 📝 `netbox/project-static/dist/netbox.js.map` (+4 -4) ➕ `netbox/project-static/src/forms/filterModifiers.ts` (+179 -0) 📝 `netbox/project-static/src/forms/index.ts` (+2 -1) 📝 `netbox/project-static/styles/transitional/_forms.scss` (+8 -0) 📝 `netbox/tenancy/filtersets.py` (+7 -0) 📝 `netbox/users/filtersets.py` (+7 -1) _...and 13 more files_ </details> ### 📄 Description ### Closes: #7604 Implements dynamic filter modifier UI that allows users to select lookup operators (exact, contains, starts with, regex, negation, empty/not empty) directly in filter forms without manual URL parameter editing. Supports filters for all scalar types and strings, as well as some related object filters. Explicitly does not support filters on fields that use `APIWidget`. That has been broken out in to follow up work. #### How It Works ``` User selects "contains" ─────────────────┐ │ ▼ ┌──────────────────────────────────────────────────────────┐ │ Filter Form (Browser) │ │ ┌────────────────┐ ┌──────────────────────────┐ │ │ │ [Contains ▼] │ │ Input: ABC │ │ │ └────────────────┘ └──────────────────────────┘ │ │ │ │ │ │ TypeScript handler (filterModifiers.ts) │ │ ▼ │ │ Updates input name: "serial" -> "serial__ic" │ │ Updates URL param: ?serial__ic=ABC │ └──────────────────────────────────────────────────────────┘ │ │ Form submission ▼ ┌──────────────────────────────────────────────────────────┐ │ Django Backend │ │ │ │ FilterModifierWidget │ │ ↓ Renders modifier dropdown + original widget │ │ │ │ FilterModifierMixin │ │ ↓ Enhances filterset fields with appropriate lookups │ │ │ │ FilterSet processes: serial__ic=ABC │ │ ↓ Django ORM: .filter(serial__icontains='ABC') │ │ │ │ Results + Filter Pills │ │ ↓ "Serial: contains ABC [×]" │ └──────────────────────────────────────────────────────────┘ ``` #### Details ##### Backend: `FilterModifierWidget` Wraps any Django form widget with a modifier dropdown. Key method: ```python def value_from_datadict(self, data, files, name): # Check all possible lookup variants (exact, ic, isw, n, etc.) for lookup_code, _ in self.lookups: param_name = f"{name}__{lookup_code}" if lookup_code != 'exact' else name if param_name in data: return data.get(param_name) ``` This allows the widget to find its value regardless of which lookup modifier is active in the URL. ##### Backend: `FilterModifierMixin` Automatically enhances filterset form fields based on their type: - **`CharField`** -> `exact`, `contains` (`ic`), `startswith` (`isw`), `endswith` (`iew`), `iexact` (`ie`), `negation` (`n`), `regex`, `iregex`, `empty` - **`IntegerField`** -> `exact`, `gte`, `lte`, `gt`, `lt`, `negation` (`n`), `empty` - **`DecimalField`** -> `exact`, `gte`, `lte`, `gt`, `lt`, `negation` (`n`), `empty` - **`DateField`** -> `exact`, `gte`, `lte`, `gt`, `lt`, `negation` (`n`), `empty` - **`ChoiceField`** -> `exact`, `negation` (`n`), `empty` - **`MultipleChoiceField`** -> `exact`, `negation` (`n`), `empty` - **`ModelChoiceField`** -> `exact`, `negation` (`n`), `empty` - **`ColorField`** -> `exact`, `negation` (`n`), `empty` - **`TagFilterField`** -> `exact`, `negation` (`n`), `empty` ##### Frontend: `filterModifiers.ts` Typescript handler that: 1. Detects modifier selection change 2. Updates the associated input's `name` attribute 3. Syncs URL parameters when navigating with existing filters ##### Filter Pills Enhancement Modified `applied_filters` template tag to: 1. Detect lookup modifier in parameter name (`serial__ic`) 2. Display human-readable label ("contains") instead of raw lookup code 3. Maintain correct removal URL (preserves other filters) #### Scope & Compatibility **What's changed:** - All `FilterForm` subclasses now have modifier dropdowns - Filter pills show lookup type - URL parameters include lookup suffixes (`__ic`, `__n`, etc.) **What's NOT changed:** - Existing URL parameters still work (backward compatible) - Django `FilterSet` behavior unchanged - No database schema changes - No API changes --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
adam added the pull-request label 2025-12-30 00:25:27 +01:00
adam closed this issue 2025-12-30 00:25:27 +01:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/netbox#16040