Skip to content

Conversation

@jnovinger
Copy link
Member

@jnovinger jnovinger commented Nov 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

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.

**Backend:**
- FilterModifierWidget: Wraps form widgets with lookup modifier dropdown
- FilterModifierMixin: Auto-enhances filterset fields with appropriate lookups
- Extended lookup support: Adds negation (n), regex, iregex, empty_true/false lookups
- Field-type-aware: CharField gets text lookups, IntegerField gets comparison operators, etc.

**Frontend:**
- TypeScript handler syncs modifier dropdown with URL parameters
- Dynamically updates form field names (serial → serial__ic) on modifier change
- Flexible-width modifier dropdowns with semantic CSS classes
@jnovinger jnovinger force-pushed the 7604-filter-modifiers-v4 branch from d1930b8 to cebb92b Compare November 4, 2025 17:08
@jnovinger jnovinger force-pushed the 7604-filter-modifiers-v4 branch from cebb92b to 5913ea8 Compare November 4, 2025 18:34
@jnovinger jnovinger marked this pull request as ready for review November 6, 2025 18:31
@jnovinger jnovinger requested review from a team and jeremystretch and removed request for a team November 6, 2025 18:31
Enable filter modifiers for single-choice ChoiceFields in addition to the
existing MultipleChoiceField support. ChoiceFields can now display modifier
dropdowns with "Is", "Is Not", "Is Empty", and "Is Not Empty" options when
the corresponding FilterSet defines those lookups.

The mixin correctly verifies lookup availability against the FilterSet, so
modifiers only appear when multiple lookup options are actually supported.
Currently most FilterSets only define 'exact' for single-choice fields, but
this change enables future FilterSet enhancements to expose additional
lookups for ChoiceFields.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants