Formset with filter_by/exclude_by

Example Overview

This example demonstrates filter_by and exclude_by inside Django formsets, where each row needs its own independent dependent-field relationships - a selection in row 1 should only affect row 1’s dependent fields. It covers automatic form-prefix handling, dynamically added rows, and using exclude_by to prevent circular references in model formsets (such as a category becoming its own parent). Use it for bulk data-entry forms or any formset interface that needs cascading dropdown behavior per row.

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__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

Implementation Notes

  • 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.