Autocomplete Views

This module provides the view classes that handle autocomplete requests from TomSelect widgets. These views manage data loading, searching, filtering, and permission checks.

Model Autocomplete Views

AutocompleteModelView Processing

            sequenceDiagram
        participant User
        participant Widget
        participant AutocompleteView
        participant QuerySet
        participant Paginator

        User->>Widget: Type Search Term
        Widget->>AutocompleteView: GET Request with Query

        activate AutocompleteView
        AutocompleteView->>AutocompleteView: hook_queryset()
        AutocompleteView->>QuerySet: apply_filters()
        QuerySet-->>AutocompleteView: Filtered Results

        AutocompleteView->>QuerySet: search()
        QuerySet-->>AutocompleteView: Search Results

        AutocompleteView->>QuerySet: order_queryset()
        QuerySet-->>AutocompleteView: Ordered Results

        AutocompleteView->>Paginator: paginate_queryset()
        Paginator-->>AutocompleteView: Paginated Results

        AutocompleteView->>AutocompleteView: prepare_results()
        AutocompleteView->>AutocompleteView: hook_prepare_results()

        AutocompleteView-->>Widget: JSON Response
        deactivate AutocompleteView

        Widget-->>User: Update Dropdown

        note over AutocompleteView: Hooks allow customization<br/>at various stages
    

AutocompleteModelView

class django_tomselect.autocompletes.AutocompleteModelView(**kwargs)[source]

Bases: JSONEncoderMixin, View

Base view for handling autocomplete requests.

Intended to be flexible enough for many use cases, but can be subclassed for more specific needs.

model

The Django model class to query for autocomplete results.

Type:

type[Model] | None

search_lookups

List of field lookups to search against when the user types. Uses Django’s ORM lookup syntax. Example: [‘name__icontains’, ‘email__icontains’] Multiple lookups are combined with OR logic.

Type:

list[str]

ordering

Field(s) to order results by. Can be a string, list, or tuple. Example: ‘name’ or [‘-created’, ‘name’]

Type:

str | list[str] | tuple[str, …] | None

page_size

Number of results to return per page. Default: 20

Type:

int

value_fields

List of model field names to include in the JSON response. These fields will be available to JavaScript for custom rendering. Example: [‘id’, ‘name’, ‘email’, ‘avatar_url’]

Type:

list[str]

virtual_fields

List of non-model field names that will be computed dynamically. Use this for calculated/derived values that don’t exist on the model. To populate virtual fields, override prepare_results() or define a prepare_{field_name} method. Example: [‘full_name’, ‘display_label’]

Type:

list[str]

list_url

URL name for the list view. Rendered as a “View All” link in the dropdown footer when show_list=True and PluginDropdownFooter are set in the widget’s TomSelectConfig.

Type:

str | None

create_url

URL name for the create view. Used in two contexts: (a) rendered as a “Create New” link in the dropdown footer when show_create=True and PluginDropdownFooter are set in TomSelectConfig; (b) used as the HTMX POST target for inline creation when create=True and create_with_htmx=True are set in TomSelectConfig. Both require this attribute to be set to a valid URL name.

Type:

str | None

detail_url

URL name for the detail view (used for item detail links)

Type:

str | None

update_url

URL name for the update view (used for item edit links)

Type:

str | None

delete_url

URL name for the delete view (used for item delete links)

Type:

str | None

permission_required

Permission string(s) required to access this view. Can be a single string or list/tuple of permission strings.

Type:

str | list[str] | tuple[str, …] | None

allow_anonymous

If True, unauthenticated users can access this view. Default: False

Type:

bool

skip_authorization

If True, skip all permission checks. Default: False

Type:

bool

create_field

The field name used when creating new objects via the autocomplete.

Type:

str

Note

Filter/Exclude Syntax

The filter_by and exclude_by URL parameters allow dynamic filtering of results based on another form field’s value. This is useful for dependent dropdowns.

Format: 'dependent_field__lookup_field=value'

Where:

  • dependent_field: The name of the form field that triggers filtering

  • lookup_field: The model field to filter on (can include lookups like __id)

  • value: The value to filter by (usually from the dependent field)

Example URL parameters:

?filter_by=category__category_id=5  - Filter where category_id equals 5
?exclude_by=author__author_id=3     - Exclude where author_id equals 3

In JavaScript/HTML, use data attributes on the widget:

data-filter-by="category__category_id"  - Will filter by selected category
data-exclude-by="author__author_id"     - Will exclude by selected author
classmethod __init_subclass__(**kwargs)[source]

Initialize subclass with default mutable attributes if not already set.

This prevents mutable class attributes (lists) from being shared across subclasses, which could cause unexpected behavior when one subclass modifies the list.

Parameters:

kwargs (Any)

Return type:

None

apply_filters(queryset)[source]

Apply additional filters to the queryset.

Supports multiple filter_by and exclude_by parameters. Each parameter can be in one of: - Field filter: ‘dependent_field__lookup_field=value’ - Constant filter: ‘__const__lookup_field=value’

Multiple filters are combined with AND logic.

Parameters:

queryset (QuerySet)

Return type:

QuerySet

dispatch(request, *args, **kwargs)[source]

Check permissions before dispatching request.

Parameters:
  • request (HttpRequest)

  • args (Any)

  • kwargs (Any)

Return type:

HttpResponse

get(request, *args, **kwargs)[source]

Handle GET requests.

Parameters:
  • request (HttpRequest)

  • args (Any)

  • kwargs (Any)

Return type:

JsonResponse

get_permission_required()[source]

Get the permissions required for this view.

If permission_required is None, no permissions are required. Otherwise, use the specified permissions or fall back to model-based defaults.

Return type:

list[str]

get_queryset()[source]

Get the base queryset for the view.

Return type:

QuerySet

get_value_fields()[source]

Get list of fields to include in values() query.

Return type:

list[str]

handle_no_permission(request)[source]

Handle cases where permission is denied.

Can be overridden to customize behavior.

Parameters:

request (HttpRequest)

Return type:

HttpResponse

has_add_permission(request)[source]

Check if the user has permission to add objects.

Parameters:

request (HttpRequest | Any)

Return type:

bool

has_object_permission(request, obj, action='view')[source]

Check object-level permissions.

Can be overridden for custom object-level permissions.

Parameters:
  • request (HttpRequest | Any)

  • obj (Model)

  • action (str)

Return type:

bool

has_permission(request, action='view')[source]

Check if user has all required permissions.

Supports custom auth backends via Django’s auth system.

Parameters:
  • request (HttpRequest | Any)

  • action (str)

Return type:

bool

hook_prepare_results(results)[source]

Hook method for customizing the prepared results.

This method is called at the end of prepare_results after all standard processing is complete. Override this method to modify the results without losing the base functionality.

Parameters:

results (list[dict[str, Any]]) – List of dictionaries containing the prepared results

Returns:

The modified results list

Return type:

list[dict[str, Any]]

hook_queryset(queryset)[source]

Hook to allow for additional queryset manipulation before filtering, searching, and ordering.

For example, this could be used to prefetch related objects or add annotations that will later be used in filtering, searching, or ordering.

Parameters:

queryset (QuerySet)

Return type:

QuerySet

classmethod invalidate_permissions(user=None)[source]

Invalidate cached permissions.

If user is provided, only invalidate that user’s permissions.

Parameters:

user (User | None)

Return type:

None

order_queryset(queryset)[source]

Apply ordering to the queryset.

Handles string and list/tuple ordering values correctly. For strings: Converts single field string to list For lists/tuples: Uses as-is If no ordering specified: Falls back to model default

Parameters:

queryset (QuerySet)

Return type:

QuerySet

paginate_queryset(queryset)[source]

Paginate the queryset with improved page handling.

Parameters:

queryset (QuerySet)

Return type:

PaginatedResponse

post(request, *args, **kwargs)[source]

Handle POST requests.

Parameters:
  • request (HttpRequest)

  • args (Any)

  • kwargs (Any)

Return type:

JsonResponse

prepare_results(results)[source]

Prepare the results for JSON serialization.

This method: 1. Gets values for specified fields 2. Ensures each result has an ‘id’ key 3. Adds view/update/delete URLs if configured 4. Calls hook_prepare_results for any custom processing

Important: This method should not reorder results, as order is already established by order_queryset.

Parameters:

results (QuerySet)

Return type:

list[dict[str, Any]]

search(queryset, query)[source]

Apply search filtering to the queryset.

With split_search=False (default) the entire query is OR’d across search_lookups as a single icontains.

With split_search=True the query is whitespace-split via the shared quote-aware tokenizer (django_tomselect._tokenize); each term is OR’d across search_lookups and the per-term Qs are ANDed together. Quoted phrases remain single terms.

Parameters:
Return type:

QuerySet

setup(request, *args, **kwargs)[source]

Set up the view with request parameters.

Parameters:
  • request (HttpRequest | Any)

  • args (Any)

  • kwargs (Any)

Return type:

None

The AutocompleteModelView is the primary view for handling model-based autocomplete requests. It provides a flexible foundation for creating searchable, paginated, and permission-controlled autocomplete endpoints.

Basic Usage

from django_tomselect.autocompletes import AutocompleteModelView
from myapp.models import Book

class BookAutocomplete(AutocompleteModelView):
    model = Book
    search_lookups = ['title__icontains', 'author__name__icontains']
    ordering = 'title'
    page_size = 20
    # Important: Include any fields you'll use as label_field in the widget
    value_fields = ['id', 'title', 'author__name']
    virtual_fields = ['computed_field']  # Fields that should not be queried from the database

    # Optional: Restrict which fields users can filter/order by via request parameters
    allowed_filter_fields = ['category', 'author']
    allowed_ordering_fields = ['title', 'publication_date']

URL Configuration

# urls.py
from django.urls import path
from .views import BookAutocomplete

urlpatterns = [
    path('book-autocomplete/', BookAutocomplete.as_view(), name='book-autocomplete'),
]

Advanced Configuration

from django.db.models import Prefetch
from myapp.models import Book, Author

class BookAutocomplete(AutocompleteModelView):
    model = Book
    search_lookups = ['title__icontains', 'author__name__icontains']
    ordering = ['title', 'publication_date']
    page_size = 20
    # Include all fields needed for display and filtering
    value_fields = ['id', 'title', 'author__name', 'publication_date', 'isbn']

    # URLs for CRUD operations
    list_url = 'book-list'
    create_url = 'book-create'
    detail_url = 'book-detail'
    update_url = 'book-update'
    delete_url = 'book-delete'

    # Custom permission settings
    permission_required = ('myapp.view_book', 'myapp.search_book')
    allow_anonymous = False

    def hook_queryset(self, queryset):
        """Customize the base queryset before filtering and searching."""
        return queryset.select_related('author').prefetch_related(
            Prefetch('categories', queryset=Category.objects.only('name'))
        )

    def hook_prepare_results(self, results):
        """Customize the prepared results before sending to the client."""
        for result in results:
            result['author_name'] = result.pop('author__name', '')
            result['category_count'] = len(result.get('categories', []))
        return results

Key Features

  1. Restricting Filter and Ordering Fields

By default, users can pass arbitrary field names via filter_by, exclude_by, and ordering request parameters. To restrict which fields are accepted, set allowed_filter_fields and allowed_ordering_fields:

class BookAutocomplete(AutocompleteModelView):
    model = Book
    search_lookups = ['title__icontains']
    value_fields = ['id', 'title', 'category__name', 'publication_date']

    # Only allow filtering/excluding by these fields
    allowed_filter_fields = ['category', 'author', 'status']

    # Only allow ordering by these fields
    allowed_ordering_fields = ['title', 'publication_date']

When allowed_filter_fields is set, any filter_by or exclude_by parameter referencing a field not in the list is silently rejected (the base field name is checked, so category__id is allowed when category is in the list). When allowed_ordering_fields is set, disallowed ordering fields fall back to the view’s default ordering.

When these attributes are None (the default), all valid model fields are accepted.

Tip

Setting these attributes is recommended for production deployments to prevent users from probing your model structure through filter/ordering parameters.

  1. Search Configuration

The search_lookups attribute defines how searching works:

class AuthorAutocomplete(AutocompleteModelView):
    model = Author
    # Search in multiple fields
    search_lookups = [
        'name__icontains',          # Case-insensitive name search
        'email__istartswith',       # Email starting with query
        'books__title__icontains',  # Search in related books
    ]
  1. Value Fields Configuration

The value_fields attribute determines which fields are included in the autocomplete results. This is critical for the widget’s functionality:

class AuthorAutocomplete(AutocompleteModelView):
    model = Author
    # Define fields to include in results
    value_fields = [
        'id',                # Primary key (always required)
        'name',              # For use as label_field in widget
        'email',             # Optional additional field
        'profile_picture',   # Optional additional field
        'books__count',      # Can include annotations
    ]
    virtual_fields = [
        'books__count',      # Computed or annotated fields
    ]

⚠️ Important: The value_fields attribute should include all fields you need in your results, but for fields that don’t exist in the database (computed properties, annotations added later, etc.), add them to virtual_fields to prevent database query errors. The widget will automatically add your label_field to value_fields and detect if it should be in virtual_fields.

For example, if your widget uses:

config=TomSelectConfig(label_field="name")

Then your autocomplete view should include “name” in its value_fields list.

Warning

Do not use label_field="__str__" (or any Python dunder). django-tomselect serializes options with QuerySet.values(), and __str__ is not a selectable column, so option labels would render empty. This now raises ImproperlyConfigured at configuration time.

When you are tempted to reach for the model’s __str__, use a real field or an annotation instead. For a composite/computed label, reproduce it as a queryable annotation in hook_queryset() and point label_field at that annotation:

from django.db.models import CharField, Value
from django.db.models.functions import Concat

class SubscriptionAutocomplete(AutocompleteModelView):
    model = Subscription
    value_fields = ["id", "display_name"]
    search_lookups = ["monitor__name__icontains"]

    def hook_queryset(self, queryset):
        # Reproduce the model's __str__ as a queryable annotation. Pass an
        # explicit output_field so Django can resolve the type even when a
        # concatenated part is not itself text (an int/date column otherwise
        # raises FieldError: unknown output_field).
        return queryset.annotate(
            display_name=Concat(
                "monitor__name", Value(" - "), "metric",
                output_field=CharField(),
            ),
        )

# Widget config:
config = TomSelectConfig(url="subscription-autocomplete", label_field="display_name")

Because display_name is a real annotation it flows through both the AJAX results (.values()) and the pre-selected option rendering, and it stays searchable and sortable. If a single column already captures the label, just use it directly (e.g. label_field="name") - no annotation needed. (The annotation’s relation traversal, monitor__name, is JOINed automatically; only add select_related in hook_queryset() if you also access the related object on the result instances for other reasons.)

  1. Queryset Customization

Use hook_queryset to optimize or customize the queryset:

def hook_queryset(self, queryset):
    """Customize the base queryset before filtering and searching.

    This is the ideal place to add select_related, prefetch_related,
    or annotations that should apply to all results.
    """
    return queryset.select_related('publisher')\
                   .prefetch_related('categories')\
                   .annotate(book_count=Count('books'))\
                   .filter(is_active=True)

Note

When the widget’s label_field points to a relation (e.g., label_field="author"), the widget automatically adds select_related() for that field to prevent N+1 queries when rendering selected options. You do not need to add it manually in hook_queryset for this case.

  1. Permission Handling

Multiple ways to configure permissions:

class BookAutocomplete(AutocompleteModelView):
    # Option 1: Specify required permissions
    permission_required = ('myapp.view_book', 'myapp.search_book')

    # Option 2: Allow anonymous access
    allow_anonymous = True

    # Option 3: Skip all permission checks
    skip_authorization = False

    # Option 4: Custom permission checking
    def has_permission(self, request, action="view"):
        if action == "create":
            return request.user.is_staff
        return super().has_permission(request, action)
  1. Result Preparation

Customize the data sent to the client:

def hook_prepare_results(self, results):
    for result in results:
        # Add computed fields
        result['display_label'] = f"{result['title']} ({result['year']})"
        # Transform data
        result['author_info'] = {
            'name': result.pop('author__name'),
            'email': result.pop('author__email')
        }
        # Add custom URLs
        result['preview_url'] = reverse('book-preview', args=[result['id']])
    return results
  1. Custom JSON Encoder

If your model has fields with non-serializable types (e.g., a PhoneNumber from the django-phonenumber-field package), you can specify a custom JSON encoder:

import json

class PhoneNumberEncoder(json.JSONEncoder):
    """Encoder that handles PhoneNumber objects."""

    def default(self, obj):
        if hasattr(obj, 'as_e164'):
            return obj.as_e164
        return super().default(obj)

class ContactAutocomplete(AutocompleteModelView):
    model = Contact
    search_lookups = ['name__icontains']
    value_fields = ['id', 'name', 'phone']

    # Use the custom encoder for this view
    json_encoder = PhoneNumberEncoder

The encoder can also be specified as a dotted string path:

class ContactAutocomplete(AutocompleteModelView):
    model = Contact
    json_encoder = 'myapp.encoders.PhoneNumberEncoder'

See Configuration documentation for details on setting a global default encoder.

Iterables Autocomplete Views

AutocompleteIterablesView Processing

            sequenceDiagram
        participant User
        participant Widget
        participant AutocompleteIterablesView
        participant Iterable
        participant Paginator

        User->>Widget: Type Search Term
        Widget->>AutocompleteIterablesView: GET Request with Query

        activate AutocompleteIterablesView
        AutocompleteIterablesView->>AutocompleteIterablesView: get_iterable()

        alt TextChoices/IntegerChoices
            AutocompleteIterablesView->>Iterable: Access choices attribute
            Iterable-->>AutocompleteIterablesView: Return choices list
            AutocompleteIterablesView->>AutocompleteIterablesView: Format as {value, label}
        else Tuple Iterables
            AutocompleteIterablesView->>Iterable: Access tuple items
            Iterable-->>AutocompleteIterablesView: Return tuple list
            AutocompleteIterablesView->>AutocompleteIterablesView: Format as {value, label}
        else Simple Iterables
            AutocompleteIterablesView->>Iterable: Access items
            Iterable-->>AutocompleteIterablesView: Return items
            AutocompleteIterablesView->>AutocompleteIterablesView: Format as {value, label}
        end

        AutocompleteIterablesView->>AutocompleteIterablesView: search()
        Note over AutocompleteIterablesView: Filter items based on query

        AutocompleteIterablesView->>Paginator: paginate_iterable()
        Paginator-->>AutocompleteIterablesView: Paginated Results

        AutocompleteIterablesView-->>Widget: JSON Response
        deactivate AutocompleteIterablesView

        Widget-->>User: Update Dropdown

        note over AutocompleteIterablesView: Handles three types of iterables:<br/>1. Django Choices (Text/Integer)<br/>2. Tuple Iterables<br/>3. Simple Iterables
    

AutocompleteIterablesView

class django_tomselect.autocompletes.AutocompleteIterablesView(**kwargs)[source]

Bases: JSONEncoderMixin, View

Autocomplete view for iterables and django choices classes.

get(request, *args, **kwargs)[source]

Handle GET requests.

Parameters:
  • request (HttpRequest)

  • args (Any)

  • kwargs (Any)

Return type:

JsonResponse

get_iterable()[source]

Get the choices from the iterable or choices class.

Return type:

list[dict[str, str | int]]

paginate_iterable(results)[source]

Paginate the filtered results.

Parameters:

results (list[dict[str, str | int]])

Return type:

PaginatedResponse

post(request, *args, **kwargs)[source]

Handle POST requests.

Parameters:
  • request (HttpRequest)

  • args (Any)

  • kwargs (Any)

Return type:

JsonResponse

search(items)[source]

Apply search filtering to the items.

Parameters:

items (list[dict[str, str | int]])

Return type:

list[dict[str, str | int]]

setup(request, *args, **kwargs)[source]

Set up the view with request parameters.

Parameters:
  • request (HttpRequest)

  • args (Any)

  • kwargs (Any)

Return type:

None

This view handles autocomplete for choices, iterables, and enums.

Basic Usage

from django_tomselect.autocompletes import AutocompleteIterablesView
from django.db.models import TextChoices

class Status(TextChoices):
    DRAFT = 'D', 'Draft'
    PUBLISHED = 'P', 'Published'
    ARCHIVED = 'A', 'Archived'

class StatusAutocomplete(AutocompleteIterablesView):
    iterable = Status
    page_size = 10

Custom Iterables

class YearAutocomplete(AutocompleteIterablesView):
    iterable = [
        2020,
        2021,
        2022,
        2023,
        2024,
        2025,
    ]

class TiersAutocomplete(AutocompleteIterablesView):
    iterable = [
        (1, "Tier 1"),
        (2, "Tier 2"),
        (3, "Tier 3"),
    ]

With Dictionary

class ClassStandingsAutocomplete(AutocompleteIterablesView):
    iterable = {
        "FR": "Freshman",
        "SO": "Sophomore",
        "JR": "Junior",
        "SR": "Senior",
        "GR": "Graduate",
    }

Custom Iterable Formatting

Autocompletes can be customized to modify formatting by overriding the get_iterable method.

For an example where we use this to display tuples as ranges, see the autcompletes.py code in the example app.

Response Format

Both view types return JSON responses in this format:

{
    "results": [
        {
            "id": "1",
            "name": "Example Item",
            "can_view": true,    # Permission flags for the current user
            "can_update": true,
            "can_delete": false,
            "detail_url": "/items/1/",
            "update_url": "/items/1/update/",
            # No delete_url since can_delete is false
            # ... additional fields from hook_prepare_results
        }
    ],
    "page": 1,
    "has_more": true,
    "next_page": 2,
    "total_pages": 5
}

Error Handling

The views include built-in error handling:

  1. Invalid permissions return 403 Forbidden

  2. Unauthenticated users are redirected to login

  3. Invalid queries return empty results

  4. Database errors return a 200 response with an error message and empty results

Caching

The views support permission caching to improve performance:

# settings.py
PERMISSION_CACHE = {
    'TIMEOUT': 300,  # Cache permissions for 5 minutes
    'KEY_PREFIX': 'myapp',
    'NAMESPACE': 'permissions'
}

To invalidate the cache:

from django_tomselect.autocompletes import AutocompleteModelView

# Invalidate for specific user
AutocompleteModelView.invalidate_permissions(user=request.user)

# Invalidate all cached permissions
AutocompleteModelView.invalidate_permissions()

Security Considerations

The package already includes built-in protections:

  1. All user-provided values are automatically escaped using Django’s escape() function

  2. URLs are sanitized through the safe_url() utility which prevents unsafe schemes

  3. Dictionary values are recursively sanitized via the sanitize_dict() utility

These protections work together to prevent XSS vulnerabilities when customizing rendering templates.

When creating custom templates and renderers for Tom Select widgets, always ensure proper escaping of user-provided values:

  1. Use the escape() function for any user data inserted into HTML content

  2. For URL attributes (href, src), always escape the URLs using the escape() function

  3. Avoid using new Function() with user-provided content whenever possible

  4. When customizing rendering templates, validate and sanitize all input

This is particularly important when using custom rendering templates with data_template_option and data_template_item.

CompositeAutocompleteView (token widget backend)

Multiplexes multiple AutocompleteModelView and/or AutocompleteIterablesView subclasses into a single endpoint that powers TomSelectTokenWidget. Three GET routes by mode= query param:

  • ?mode=operators - JSON list of registered operator metadata.

  • ?mode=value&op=<key>&q=... - delegates to the operator’s bound view.

  • ?mode=resolve&op=<k>&id=<v>[...] - batch label resolution for chip rehydration.

from django_tomselect import CompositeAutocompleteView, Operator


class ArticleTokenQueryView(CompositeAutocompleteView):
    operators = [
        Operator(
            key="author",
            view=AuthorAutocompleteView,         # class ref or "url-name"
            value_field="id",                    # JSON key in prepare_results()
            label_field="name",                  # JSON key in prepare_results()
            filter_lookup="authors__id",         # parent-QS exact-match field path
            label=_("Author"),
            max_count=3,
        ),
        Operator(
            key="status",
            view=ArticleStatusAutocompleteView,  # iterables view
            value_field="value",
            label_field="label",
            filter_lookup="status",
            multi=True,                          # comma-separated: status:a,b
        ),
    ]
    free_text_lookups = ["title__icontains"]

Operator contract

  • Required: key, view, value_field, label_field, exactly one of filter_lookup or q_translator.

  • bound_lookup: ORM lookup field for chip resolution. Defaults to value_field. Override when prepare_results() projects renamed/computed keys.

  • filter_lookup: exact-match field path (e.g. "authors__id", "status"). For multi=True the per-token lookup is field__in=[values]. For non-exact behavior (icontains, gt/lt, custom expressions), use q_translator instead.

  • q_translator: callable (operator, [values]) -> Q. Maximum flexibility for custom filtering.

  • search_lookups: None inherits the bound view’s lookups; [] deliberately disables search; non-empty list overrides for this operator only (instance-scoped per-request, no cross-request leakage).

  • max_count / min_count: enforced by TomSelectTokenField.clean().

split_search flag (opt-in)

AutocompleteModelView gained a split_search: bool = False class attribute. When True, search() splits the query on whitespace using a quote-aware tokenizer; each term is OR’d across search_lookups, and per-term Qs are ANDed together. Quoted phrases stay single terms. Default False preserves existing behavior verbatim.

Permission caveats

  • Iterables operators have no has_permission() hook - they are public-by-default. The composite view emits a one-time logger warning on subclass registration. Gate sensitive iterables at the form/view layer.

  • Object-level permissions are NOT enforced row-by-row. The resolve flow honors queryset-level scoping (whatever your get_queryset() returns) and dispatch-level has_permission(), but has_object_permission() is not applied per-row by prepare_results(). Override get_queryset() for per-row checks.

See Article Token-Style Search for an end-to-end demo.