[PR #20689] [CLOSED] Closes #7604: Add filter modifier dropdowns for advanced lookup operators #16015

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

📋 Pull Request Information

Original PR: https://github.com/netbox-community/netbox/pull/20689
Author: @jnovinger
Created: 10/27/2025
Status: Closed

Base: mainHead: 7604-filter-modifiers-v3


📝 Commits (3)

  • af8f460 Fixes #7604: Add filter modifier dropdowns for advanced lookup operators
  • d89948b Remove extraneous TS comments
  • cf5bf9a Fix import order

📊 Changes

15 files changed (+872 additions, -18 deletions)

View changed files

📝 netbox/circuits/forms/filtersets.py (+8 -1)
📝 netbox/dcim/forms/filtersets.py (+12 -2)
📝 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 (+177 -0)
📝 netbox/project-static/src/forms/index.ts (+2 -1)
📝 netbox/project-static/styles/transitional/_forms.scss (+8 -0)
netbox/utilities/forms/filterset_mappings.py (+11 -0)
📝 netbox/utilities/forms/mixins.py (+172 -0)
📝 netbox/utilities/forms/widgets/__init__.py (+1 -0)
netbox/utilities/forms/widgets/modifiers.py (+105 -0)
netbox/utilities/templates/widgets/filter_modifier.html (+16 -0)
📝 netbox/utilities/templatetags/helpers.py (+53 -5)
netbox/utilities/tests/test_filter_modifiers.py (+298 -0)

📄 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, empty
  • DateField -> exact, gte, lte, gt, lt, negation, empty
  • ChoiceField/MultipleChoiceField -> exact, negation, 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/20689 **Author:** [@jnovinger](https://github.com/jnovinger) **Created:** 10/27/2025 **Status:** ❌ Closed **Base:** `main` ← **Head:** `7604-filter-modifiers-v3` --- ### 📝 Commits (3) - [`af8f460`](https://github.com/netbox-community/netbox/commit/af8f46028842f85cd996984471610f793d9812fb) Fixes #7604: Add filter modifier dropdowns for advanced lookup operators - [`d89948b`](https://github.com/netbox-community/netbox/commit/d89948b3abe73d904e7a3057665182cbcc1147e4) Remove extraneous TS comments - [`cf5bf9a`](https://github.com/netbox-community/netbox/commit/cf5bf9a4d0ce465cf8c589b5e4d4ec0964d4f471) Fix import order ### 📊 Changes **15 files changed** (+872 additions, -18 deletions) <details> <summary>View changed files</summary> 📝 `netbox/circuits/forms/filtersets.py` (+8 -1) 📝 `netbox/dcim/forms/filtersets.py` (+12 -2) 📝 `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` (+177 -0) 📝 `netbox/project-static/src/forms/index.ts` (+2 -1) 📝 `netbox/project-static/styles/transitional/_forms.scss` (+8 -0) ➕ `netbox/utilities/forms/filterset_mappings.py` (+11 -0) 📝 `netbox/utilities/forms/mixins.py` (+172 -0) 📝 `netbox/utilities/forms/widgets/__init__.py` (+1 -0) ➕ `netbox/utilities/forms/widgets/modifiers.py` (+105 -0) ➕ `netbox/utilities/templates/widgets/filter_modifier.html` (+16 -0) 📝 `netbox/utilities/templatetags/helpers.py` (+53 -5) ➕ `netbox/utilities/tests/test_filter_modifiers.py` (+298 -0) </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`, `empty` - **`DateField`** -> `exact`, `gte`, `lte`, `gt`, `lt`, `negation`, `empty` - **`ChoiceField`/`MultipleChoiceField`** -> `exact`, `negation`, `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:19 +01:00
adam closed this issue 2025-12-30 00:25:19 +01:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/netbox#16015