Closes #7604: Add filter modifier dropdowns for advanced lookup operators #919

Closed
opened 2026-04-05 18:49:47 +02:00 by MrUnknownDE · 0 comments
Owner

Originally created by @jnovinger on 11/4/2025

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
*Originally created by @jnovinger on 11/4/2025* ### 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
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github/netbox#919