Formset with filter_by/exclude_by

Example Overview

  • Objective: This example demonstrates how to use filter_by and exclude_by parameters within Django formsets. When working with formsets, each row needs its own independent dependent field relationships - selecting a value in row 1 should only affect the dependent fields in row 1, not in other rows.

    • Problem Solved: Enabling dependent/chained fields within formsets where each row operates independently.

    • Features Highlighted:

      • Using filter_by to create dependent fields in formsets

      • Using exclude_by to prevent circular references in model formsets

      • Automatic form prefix handling for multi-row forms

      • Dynamic row addition with working filter relationships

  • Use Case:

    • Bulk data entry forms where each row has related fields (e.g., magazine/edition pairs)

    • Model formsets where you need to prevent invalid selections (e.g., a category being its own parent)

    • Any formset-based interface requiring cascading dropdown behavior

Key Code Segments

Forms with filter_by in Formsets

This example uses TomSelectModelChoiceField for both magazine and edition fields. The edition field is configured with the filter_by parameter, and the formset handles each row independently.

Explanation:

  • The filter_by=("magazine", "magazine_id") parameter tells the edition field to watch the magazine field in the same form row

  • When a magazine is selected, the edition field will query the autocomplete endpoint with ?f=magazine__id=<selected_value>

  • Form prefixes (e.g., myformset-0-magazine, myformset-1-magazine) are automatically handled by the JavaScript

Using exclude_by in Model Formsets

For model formsets, you can use exclude_by to prevent invalid selections. A common use case is preventing a category from being selected as its own parent:

Explanation:

  • The exclude_by=("id", "id") parameter tells the parent field to exclude the category with the same ID as the hidden id field in the form row

  • This prevents users from selecting a category as its own parent

  • For existing records, this automatically excludes the current record from the dropdown

Note

Why prevent circular references?

In hierarchical data models (like categories with parent-child relationships), allowing a record to reference itself as its parent creates a circular reference that can cause:

  • Infinite loops when traversing the hierarchy

  • Database integrity issues

  • UI rendering problems (infinite nesting)

The exclude_by parameter provides a clean, declarative way to prevent this at the UI level. You can see this in action in the Model Formset Demo page of the example app, where editing existing categories automatically hides the current category from the parent dropdown.

View Implementation

Template with Dynamic Row Addition

How Form Prefix Handling Works

When using filter_by or exclude_by in formsets, the widget automatically handles form prefixes:

  1. Standard form: A field named magazine has ID id_magazine

  2. Formset row 0: The same field has ID id_myprefix-0-magazine

  3. Formset row 1: The same field has ID id_myprefix-1-magazine

The JavaScript automatically:

  • Detects when a field is part of a formset by looking at the field name pattern

  • Extracts the prefix and index from the field name

  • Updates filter_by references to point to the correct field in the same row

Design and Implementation Notes

  • Key Features:

    • Independent filtering per formset row

    • Automatic form prefix detection and handling

    • Support for dynamically added rows

    • Works with both regular formsets and model formsets

  • Technical Details:

    • The widget’s JavaScript listens for changes on the parent field

    • When the parent field changes, it updates the autocomplete URL with the filter parameter

    • Each row’s filter relationship is independent of other rows

  • Best Practices:

    • Always use a prefix when instantiating formsets to avoid ID conflicts

    • When dynamically adding rows, clone the TomSelect configuration and reinitialize

    • For model formsets with exclude_by, ensure the hidden id field is present in the form

See the Filter-By Magazine example for basic filter_by usage, and the Exclude-By Primary Author example for basic exclude_by usage.