# Article Bulk Actions
## Example Overview
This example uses `django_tomselect` to select multiple articles and apply bulk actions such as publishing, archiving, or assigning categories and authors. Filters for date range, category, and status dynamically narrow the multi-select list, and the action-specific fields appear only when relevant. Use this pattern for editorial or moderation tools that operate on many records at once.
**Visual Examples**


---
## Key Code Segments
### Forms
The bulk action form combines filters and actions with dynamic dropdowns for flexible workflows.
:::{admonition} Article Bulk Action Form
:class: dropdown
```python
class ArticleBulkActionForm(forms.Form):
"""Form for bulk article management."""
date_range = forms.ChoiceField(
required=False,
choices=[
("all", _("All Time")),
("today", _("Today")),
("week", _("Past Week")),
("month", _("Past Month")),
("quarter", _("Past Quarter")),
("year", _("Past Year")),
],
initial="all",
widget=forms.Select(attrs={"class": "form-select"}),
)
main_category = TomSelectModelChoiceField(
required=False,
config=TomSelectConfig(
url="autocomplete-category",
value_field="id",
label_field="name",
placeholder=_("Filter by category..."),
highlight=True,
plugin_dropdown_header=PluginDropdownHeader(
title=_("Categories"),
extra_columns={
"total_articles": _("Total Articles"),
},
),
),
)
status = TomSelectChoiceField(
required=False,
config=TomSelectConfig(
url="autocomplete-article-status",
value_field="value",
label_field="label",
placeholder=_("Filter by status..."),
highlight=True,
preload="focus",
),
)
def __init__(self, *args, **kwargs):
"""Initialize form and update selected_articles config with current filters."""
super().__init__(*args, **kwargs)
# Get initial filter values from kwargs or form data
data = kwargs.get("data") or kwargs.get("initial", {})
date_range = data.get("date_range", "all")
main_category = data.get("main_category", "")
status = data.get("status", "")
# Build the autocomplete_params string
params = []
if date_range and date_range != "all":
params.append(f"date_range={date_range}")
if main_category:
params.append(f"main_category={main_category}")
if status:
params.append(f"status={status}")
autocomplete_params = "&".join(params)
# Create the selected_articles field with dynamic filtering
self.fields["selected_articles"] = TomSelectModelMultipleChoiceField(
required=False,
config=TomSelectConfig(
url="autocomplete-article",
value_field="id",
label_field="title",
placeholder=_("Select articles..."),
highlight=True,
max_items=None,
plugin_dropdown_header=PluginDropdownHeader(
title=_("Articles"),
extra_columns={
"status": _("Status"),
"category": _("Category"),
},
),
# Pass filter parameters via attrs
attrs={
"autocomplete_params": autocomplete_params,
"data-depends-on": "date_range,main_category,status", # Fields this depends on
"class": "tomselect-with-filters", # Class for easy JS targeting
},
),
)
action = forms.ChoiceField(
choices=[
("", _("Select action...")),
("publish", _("Publish")),
("archive", _("Archive")),
("change_category", _("Change Category")),
("assign_author", _("Assign Author")),
],
required=False,
widget=forms.Select(attrs={"class": "form-select"}),
)
target_category = TomSelectModelChoiceField(
required=False,
config=TomSelectConfig(
url="autocomplete-category",
value_field="id",
label_field="name",
placeholder=_("Select target category..."),
),
)
target_author = TomSelectModelChoiceField(
required=False,
config=TomSelectConfig(
url="autocomplete-author",
value_field="id",
label_field="name",
placeholder=_("Select target author..."),
),
)
def clean(self):
"""Validate that the necessary fields are provided based on the selected action."""
cleaned_data = super().clean()
action = cleaned_data.get("action")
selected_articles = cleaned_data.get("selected_articles")
if action and not selected_articles:
raise ValidationError(_("Please select at least one article"))
if action == "change_category" and not cleaned_data.get("target_category"):
raise ValidationError(_("Please select a target category"))
if action == "assign_author" and not cleaned_data.get("target_author"):
raise ValidationError(_("Please select a target author"))
return cleaned_data
```
:::
---
### Templates
The form is rendered dynamically in the template, allowing users to filter articles and apply actions. When filters change, the article list updates accordingly via HTMX.
#### Main Template
:::{admonition} Bulk Action Form Template
:class: dropdown
```html
{% extends "example/base_with_bootstrap5.html" %}
{% block extra_header %}
{{ form.media }}
{% endblock %}
{% block content %}
Bulk Article Management
{{ paginator.count }} articles
Filters
Bulk Actions
{% block articles_table %}
Filtered Articles
{{ paginator.count }} articles
{% include "example/advanced_demos/articles_table.html" %}