# django-admin-action-forms
<p float="left">
<a href="https://pypi.org/project/django-admin-action-forms/">
<img src="https://img.shields.io/pypi/v/django-admin-action-forms?color=0073b7"/>
</a>
<a href="https://www.djangoproject.com/">
<img src="https://img.shields.io/badge/3.2.x, 4.x.x, 5.x.x-a?style=flat&logo=django&label=django&labelColor=0c4b33&color=616161">
</a>
</p>
Extension for the Django admin panel that allows passing additional parameters to actions by creating intermediate pages with forms.
- [🚀 Overview](#-overview)
- [🎉 Features](#-features)
- [🔌 Installation](#-installation)
- [✏️ Examples](#️-examples)
- [Simple confirm form](#simple-confirm-form)
- [Action with parameters](#action-with-parameters)
- [Customizing action form layout](#customizing-action-form-layout)
- [Inlines](#inlines)
- [Testing action forms](#testing-action-forms)
- [📄 Reference](#-reference)
## 🚀 Overview
Do you need confirmation pages for your actions in Django admin?<br>
Does creating multiple actions in Django admin that only differ in arguments sound familiar?<br>
Have you ever added a somewhat hacky way to pass additional parameters to an action?
**If so, this package is for you!**
This is how it looks in action:
<img src="https://raw.githubusercontent.com/michalpokusa/django-admin-action-forms/main/docs/overview.gif" width="100%">
By adding a few lines of code, you can create actions with custom forms that will be displayed in an intermediate page before the action is executed. Data from the form will be passed to the action as an additional argument.
Simple and powerful!
### 🎉 Features
- Requires minimal configuration, easy to use
- Supports all modern Django versions (3.2.x, 4.x.x, 5.x.x)
- Built on top of Django's templates and forms, matches the Django admin style
- No additional dependencies
- Supports `fields`/`fieldsets`, `filter_horizontal`/`filter_vertical` and `autocomplete_fields`
- Works with custom widgets, validators and other Django form features
- Formset support using inlines known from Django admin
- Easy to test using Django's testing tools
- Compatible with [django-no-queryset-admin-actions](https://pypi.org/project/django-no-queryset-admin-actions/)
## 🔌 Installation
1. Install using ``pip``:
```bash
$ pip3 install django-admin-action-forms
```
2. Add `'django_admin_action_forms'` to your `INSTALLED_APPS` setting.
```python
INSTALLED_APPS = [
...
'django_admin_action_forms',
]
```
3. Include `'django_admin_action_forms.urls'` in your `urls.py` file. This is needed only if you want to use autocomplete.
If you want to include them under the same path as admin site, make sure to place them **before** the admin URLs.
```python
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/action-forms/", include("django_admin_action_forms.urls")),
path("admin/", admin.site.urls),
...
]
```
...or include them under any other path.
```python
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
...
path("any/other/path/", include("django_admin_action_forms.urls")),
]
```
## ✏️ Examples
### Simple confirm form
Sometimes you do not need any additional parameters, but you want to display a confirmation form before executing the action, just to make sure the user is aware of what they are doing. By default, Django displays such form for the built-in `delete_selected` action.
Let's create a simple action that will reset the password for selected users, but before that, we want to display a confirmation form.
```python
from django.contrib import admin
from django.contrib.auth.models import User
from django_admin_action_forms import AdminActionFormsMixin, AdminActionForm, action_with_form
class ResetUsersPasswordActionForm(AdminActionForm):
# No fields needed
class Meta:
list_objects = True
help_text = "Are you sure you want proceed with this action?"
@admin.register(User)
class UserAdmin(AdminActionFormsMixin, admin.ModelAdmin):
@action_with_form(
ResetUsersPasswordActionForm,
description="Reset password for selected users",
)
def reset_users_password_action(self, request, queryset, data):
self.message_user(request, f"Password reset for {queryset.count()} users.")
actions = [reset_users_password_action]
```
By doing this, we recreated the behavior of intermediate page from the built-in `delete_selected` action.
<img src="https://raw.githubusercontent.com/michalpokusa/django-admin-action-forms/main/docs/reset-users-password.gif" width="100%">
### Action with parameters
In many cases however, you will want to pass additional parameters to the action. This can be very useful for e.g.:
- Changing the status of `Order` to one of the predefined values
- Setting a discount that you input for selected `Product` objects
- Adding multiple tags to selected `Article` objects at once
- Sending mails to selected `User` objects with a custom message, title and attachments
...and many more!
Let's create an action that will change the status of selected `Order` to a value that we select using a dropdown.
```python
from django import forms
from django.contrib import admin
from django_admin_action_forms import action_with_form, AdminActionForm
from .models import Order
class ChangeOrderStatusActionForm(AdminActionForm):
status = forms.ChoiceField(
label="Status",
choices=[("new", "New"), ("processing", "Processing"), ("completed", "Completed")],
required=True,
)
@admin.register(Order)
class OrderAdmin(AdminActionFormsMixin, admin.ModelAdmin):
@action_with_form(
ChangeOrderStatusActionForm,
description="Change status for selected Orders",
)
def change_order_status_action(self, request, queryset, data):
for order in queryset:
order.status = data["status"]
order.save()
self.message_user(request, f'Status changed to {data["status"].upper()} for {queryset.count()} orders.')
actions = [change_order_status_action]
```
<img src="https://raw.githubusercontent.com/michalpokusa/django-admin-action-forms/main/docs/change-order-status.gif" width="100%">
You may think that this could be achieved by creating an action for each status, but what if you have 10 statuses? 100? This way you can create a single action that will work for all of them.
And how about parameter, that is not predefined, like a date or a number? It would be impossible to create an action for each possible value.
Let's create an action form that will accept a discount for selected `Products` and a date when the discount will end.
```python
from django import forms
from django_admin_action_forms import AdminActionForm
class SetProductDiscountActionForm(AdminActionForm):
discount = forms.DecimalField(
label="Discount (%)",
min_value=0,
max_value=100,
decimal_places=2,
required=True,
)
valid_until = forms.DateField(
label="Valid until",
required=True,
)
```
<img src="https://raw.githubusercontent.com/michalpokusa/django-admin-action-forms/main/docs/set-product-discount.gif" width="100%">
Now we can set any discount and any date, and because we subclassed [`AdminActionForm`](#adminactionform), we get a nice date picker.
### Customizing action form layout
If your form has many fields, you may want to group them into fieldsets or reorder them. You can do this by using the `fields`, `fieldsets`, or corresponding methods in `Meta`.
For `Model`-related fields, it might be useful to use `filter_horizontal`/`filter_vertical` or `autocomplete_fields`.
Let's create an action form for action that assigns selected `Tasks` to `Employee`, that we will select using autocomplete widget.
Also, let's add the field for setting the optional `Tags` for selected `Tasks`, and validate that no more than 3
are selected using <a href="https://docs.djangoproject.com/en/5.2/ref/forms/api/#using-forms-to-validate-data">Django's form validation</a>.
```python
from django import forms
from django_admin_action_forms import AdminActionForm
class AssignToEmployeeActionForm(AdminActionForm):
employee = forms.ModelChoiceField(
queryset=Employee.objects.all(),
required=True,
)
tags = forms.ModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False,
)
def clean_tags(self):
tags = self.cleaned_data["tags"]
if tags.count() > 3:
raise forms.ValidationError("You can't assign more than 3 tags to a task.")
return tags
class Meta:
autocomplete_fields = ["employee"]
filter_horizontal = ["tags"]
```
<img src="https://raw.githubusercontent.com/michalpokusa/django-admin-action-forms/main/docs/assign-to-employee.gif" width="100%">
### Inlines
In some cases, you may need to pass a list of values to the action. These values could be as simple as numbers or they could have a more complex structure.
By using `inlines`, you can add formsets to your action form.
This is very useful when you need multiple values that share the same fields, but you do not know exactly how many there will be.
Let's say you want to set the power level plan for a fan, based on the temperature.
You can do this by creating an action form with inlines, where each inline will represent a range of temperatures and the corresponding power level.
```python
from django import forms
from django_admin_action_forms import InlineAdminActionForm, TabularAdminActionInline, AdminActionForm
class FanPowerLevelInlineForm(InlineAdminActionForm):
temperature_from = forms.IntegerField(required=True)
temperature_to = forms.IntegerField(required=True)
power = forms.IntegerField(
min_value=0,
max_value=100,
required=True,
)
class FanPowerLevelsInline(TabularAdminActionInline):
name = "fan_power_levels"
form = FanPowerLevelInlineForm
extra = 0
initial = [
{"temperature_from": 0, "temperature_to": 50, "power": 40},
{"temperature_from": 50, "temperature_to": 80, "power": 70},
{"temperature_from": 80, "temperature_to": 100, "power": 100},
]
class ConfigureFanPowerLevelsActionForm(AdminActionForm):
scale = forms.ChoiceField(
choices=[
("c", "Celsius"),
("f", "Fahrenheit"),
],
required=True,
label="Scale",
)
class Meta:
inlines = [
FanPowerLevelsInline,
]
```
<img src="https://raw.githubusercontent.com/michalpokusa/django-admin-action-forms/main/docs/inlines.gif" width="100%">
Multiple inlines can be used in the same action form, and they will be displayed in the order they are defined in `inlines`.
### Testing action forms
To test action forms, you can use Django's test client to send POST requests to model changelist with required data. The `action` and `_selected_action` fields are required, and the rest of the fields should match the action form fields.
```python
from django.contrib.auth.models import User
from django.test import TestCase
from django.urls import reverse
class ShopProductsTests(TestCase):
def setUp(self):
User.objects.create_superuser(username="admin", password="password")
self.client.login(username="admin", password="password")
def test_set_product_discount_action_form_submit(self):
change_url = reverse("admin:shop_product_changelist")
data = {
"action": "set_product_discount",
"_selected_action": [10, 12, 14],
"discount": "20",
"valid_until": "2024-12-05",
}
response = self.client.post(change_url, data, follow=True)
self.assertContains(response.rendered_content, "Discount set to 20% for 3 products.")
```
## 📄 Reference
- [`AdminActionFormsMixin`](#class-adminactionformsmixin)
- [`@action_with_form`](#action_with_formform_class--permissionsnone-descriptionnone)
- [`ActionForm`](#class-actionform)
- [`__init__()`](#def-__init__self-args-kwargs)
- [`admin_action_view()`](#def-action_form_viewself-request-extra_contextnone)
- [`AdminActionForm`](#class-adminactionform)
- [`ActionForm.Meta`](#class-actionformmeta)
- [`list_objects`](#list_objects)
- [`objects_summary`](#objects_summary)
- [`help_text`](#help_text)
- [`fields`](#fields)
- [`get_fields()`](#def-get_fieldsrequest)
- [`fieldsets`](#fieldsets)
- [`get_fieldsets()`](#def-get_fieldsetsrequest)
- [`filter_horizontal`](#filter_horizontal)
- [`filter_vertical`](#filter_vertical)
- [`autocomplete_fields`](#autocomplete_fields)
- [`radio_fields`](#radio_fields)
- [`inlines`](#inlines)
- [`get_inlines()`](#def-get_inlinesrequest)
- [`confirm_button_text`](#confirm_button_text)
- [`cancel_button_text`](#cancel_button_text)
- [`InlineActionForm`](#class-inlineactionform)
- [`InlineAdminActionForm`](#class-inlineadminactionform)
- [`InlineAdminActionFormSet`](#class-inlineadminactionformset)
- [`StackedAdminActionInline`](#class-stackedadminactioninline)
- [`TabularAdminActionInline`](#class-tabularadminactioninline)
- [`name`](#name)
- [`form`](#form)
- [`extra`](#extra)
- [`get_extra()`](#def-get_extrarequest)
- [`min_num`](#min_num)
- [`get_min_num()`](#def-get_min_numrequest)
- [`max_num`](#max_num)
- [`get_max_num()`](#def-get_max_numrequest)
- [`verbose_name`](#verbose_name)
- [`verbose_name_plural`](#verbose_name_plural)
- [`classes`](#classes)
- [`initial`](#initial)
### _class_ AdminActionFormsMixin
Class that should be inherited by all `ModelAdmin` classes that will use action forms. It provides the logic for displaying the intermediate page and handling the form submission.
```python
from django.contrib import admin
from django_admin_action_forms import AdminActionFormsMixin
class ProductAdmin(AdminActionFormsMixin, admin.ModelAdmin):
...
```
#### @action_with_form(<i>form_class, *, permissions=None, description=None</i>)
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/actions/#the-action-decorator">
<code>@admin.action</code>
</a>
Decorator that can be used instead of `@admin.action` to create action with custom form.
Functions decorated with `@action_with_form` should accept additional argument `data` that will contain cleaned data from the form, `permissions` and `description` work the same.
```python
@action_with_form(
CustomActionForm,
description="Description of the action",
)
def custom_action(self, request, queryset, data):
value_of_field1 = data["field1"]
optional_value_of_field2 = data.get("field2")
...
```
### _class_ ActionForm
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/forms/api/#django.forms.Form">
<code>Form</code>
</a>
Base class for creating action forms responsible for all under the hood logic. Nearly always you will want to subclass `AdminActionForm` instead of `ActionForm`, as it provides additional features.
#### _def_ \_\_init\_\_(<i>self, *args, **kwargs</i>)
> From version 2.0.0 replaces `__post_init__` method
Constructor for action forms that can be used to dynamically modify the form based on the `modeladmin`, `request` and `queryset` that are passed to the constructor and accessible from `self`.
It is possible to add, modify and remove fields, change the layout of the form and other options from `Meta` class.
```python
class CustomActionForm(AdminActionForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.request.user.is_superuser:
self.fields["field1"].required = False
self.opts.fields = ["field2", "field1"]
self.opts.list_objects = self.queryset.count() > 10
```
#### _def_ action_form_view(<i>self, request, extra_context=None</i>)
> Added in version 2.0.0
Method used for rendering the intermediate page with form. It can be used to do some checks before displaying the form and e.g. redirect to another page if the user is not allowed to perform the action.
It can also be used for providing `extra_context` to the template, which can be especially useful when extending the action form template.
```python
class CustomActionForm(AdminActionForm):
def action_form_view(self, request, extra_context=None):
if self.queryset.count() > 3:
self.modeladmin.message_user(
request, "No more than 3 objects can be selected.", "error"
)
return HttpResponseRedirect(request.path)
return super().action_form_view(request, {"custom_context_value": ...})
```
### _class_ AdminActionForm
In addition to `ActionForm`, it replaces default widgets for most field types with corresponding Django admin widgets that e.g. add a interactive date picker or prepend a clickable link above URL fields.
Most of the time this is a class you want to subclass when creating custom action forms.
```python
class CustomActionForm(AdminActionForm):
field1 = forms.ChoiceField(
label="Field 1",
choices=[(1, "Option 1"), (2, "Option 2"), (3, "Option 3")],
)
field2 = forms.CharField(
label="Field 2",
required=False,
widget=forms.TextInput
)
field3 = forms.DateField(label="Field 3", initial="2024-07-15")
...
```
### _class_ ActionForm.Meta
> Works similar to some <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#modeladmin-options">
<code>ModelAdmin</code> options
</a>
Additional configuration for action forms. It can be used to customize the layout of the form, add help text, or display a list of objects that will be affected by the action.
```python
class CustomActionForm(AdminActionForm):
...
class Meta:
list_objects = True
help_text = "This is a help text"
...
```
Below you can find all available options:
#### list_objects
Default: `False`
If `True`, the intermediate page will display a list of objects that will be affected by the action similarly
to the intermediate page for built-in `delete_selected` action.
```python
class Meta:
list_objects = True
```
#### objects_summary
> _Added in version 2.2.0_
Default: `True` if `list_objects` is `True`, otherwise `False`
If `True`, the intermediate page will display a summary section showing the count of objects that will be affected by the action.
When `list_objects` is `True`, `objects_summary` defaults to `True` unless explicitly overridden.
```python
# Show both summary and individual objects (default behavior when list_objects=True)
class Meta:
list_objects = True
# objects_summary defaults to True
# Show only individual objects without summary
class Meta:
list_objects = True
objects_summary = False
# Show only summary without individual objects
class Meta:
list_objects = False
objects_summary = True
# Show neither summary nor objects
class Meta:
list_objects = False
objects_summary = False
```
#### help_text
Default: `None`
Text displayed between the form and the list of objects or form in the intermediate page.
```python
class Meta:
help_text = "This text will be displayed between the form and the list of objects"
```
#### fields
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.fields">
<code>ModelAdmin.fields</code>
</a>
Default: `None`
Specifies the fields that should be displayed in the form. If `fieldsets` is provided, `fields` will be ignored.
```python
class Meta:
fields = ["field1", ("field2", "field3")]
```
#### _def_ get_fields(<i>request</i>)
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_fields">
<code>ModelAdmin.get_fields()</code>
</a>
Method that can be used to dynamically determine fields that should be displayed in the form. Can be used to reorder, group or exclude fields based on the `request`. Should return a list of fields, as described above in the [`fields`](#fields).
```python
class Meta:
def get_fields(self, request):
if request.user.is_superuser:
return ["field1", "field2", "field3"]
else:
return ["field1", "field2"]
```
#### fieldsets
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.fieldsets">
<code>ModelAdmin.fieldsets</code>
</a>
Default: `None`
If both `fields` and `fieldsets` are provided, `fieldsets` will be used.
```python
class Meta:
fieldsets = [
(
None,
{
"fields": ["field1", "field2", ("field3", "field4")],
},
),
(
"Fieldset 2",
{
"classes": ["collapse"],
"fields": ["field5", ("field6", "field7")],
"description": "This is a description for fieldset 2",
},
),
]
```
#### _def_ get_fieldsets(<i>request</i>)
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_fieldsets">
<code>ModelAdmin.get_fieldsets()</code>
</a>
Method that can be used to dynamically determine fieldsets that should be displayed in the form. Can be used to reorder, group or exclude fields based on the `request`. Should return a list of fieldsets, as described above in the [`fieldsets`](#fieldsets).
```python
class Meta:
def get_fieldsets(self, request):
if request.user.is_superuser:
return [
(
None,
{
"fields": ["field1", "field2", ("field3", "field4")],
},
),
(
"Fieldset 2",
{
"classes": ["collapse"],
"fields": ["field5", ("field6", "field7")],
"description": "This is a description for fieldset 2",
},
),
]
else:
return [
(
None,
{
"fields": ["field1", "field2", ("field3", "field4")],
},
),
]
```
> [!NOTE]
> Only one of `get_fieldsets`, `fieldsets`, `get_fields` or `fields` should be defined in the `Meta` class.
> The order of precedence, from highest to lowest, is from left to right.
#### filter_horizontal
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.filter_horizontal">
<code>ModelAdmin.filter_horizontal</code>
</a>
Default: `None`
Sets fields that should use horizontal filter widget. It should be a list of field names.
```python
class Meta:
filter_horizontal = ["tags"]
```
#### filter_vertical
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.filter_vertical">
<code>ModelAdmin.filter_vertical</code>
</a>
Default: `None`
Sets fields that should use vertical filter widget. It should be a list of field names.
```python
class Meta:
filter_vertical = ["tags"]
```
#### autocomplete_fields
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.autocomplete_fields">
<code>ModelAdmin.autocomplete_fields</code>
</a>
Default: `None`
Sets fields that should use autocomplete widget. It should be a list of field names.
```python
class Meta:
autocomplete_fields = ["employee"]
```
> [!NOTE]
> Autocomplete requires including `'django_admin_action_forms.urls'` in your `urls.py` file.
> See [🔌 Installation](#-installation).
#### radio_fields
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.radio_fields">
<code>ModelAdmin.radio_fields</code>
</a>
> _Added in version 2.1.0_
Default: `{}`
Sets fields that should use a radio-button interface.
You have the choice of using `HORIZONTAL` or `VERTICAL` from the `django.contrib.admin` module.
Don’t include a field in `radio_fields` unless it’s a `ChoiceField` or its subclass.
```python
class Meta:
radio_fields = {
"field1": admin.HORIZONTAL,
"field2": admin.VERTICAL,
}
```
#### inlines
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.inlines">
<code>ModelAdmin.inlines</code>
</a>
> _Added in version 2.1.0_
Default: `[]`
Sets inlines that should used in the form.
It should be a list of classes based on `StackedAdminActionInline` or `TabularAdminActionInline`.
```python
class Meta:
inlines = [
CustomAdminActionInline,
]
```
#### _def_ get_inlines(<i>request</i>)
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_inlines">
<code>ModelAdmin.get_inlines()</code>
</a>
> _Added in version 2.1.0_
Method that can be used to dynamically determine inlines that should be used in the form.
Should return a list of classes based on `StackedAdminActionInline` or `TabularAdminActionInline`.
```python
class Meta:
def get_inlines(self, request):
if request.user.is_superuser:
return [
CustomAdminActionInline
]
else:
return []
```
#### confirm_button_text
> _Added in version 1.2.0_
Default: `"Confirm"`
Text displayed on the confirm button. It can be either a `str` or a lazy translation.
```python
from django.utils.translation import gettext_lazy as _
class Meta:
confirm_button_text = _("Proceed")
```
#### cancel_button_text
> _Added in version 1.2.0_
Default: `"Cancel"`
Text displayed on the cancel button. It can be either a `str` or a lazy translation.
```python
from django.utils.translation import gettext_lazy as _
class Meta:
cancel_button_text = _("Abort")
```
### _class_ InlineActionForm
### _class_ InlineAdminActionForm
> _Added in version 2.1.0_
Version of `ActionForm` and `AdminActionForm` that is used for inlines. Supports all of the features of `ActionForm` and selected `Meta` options.
Because some `Meta` options like `list_objects` or `help_text` do not make sense in the context of inlines, they do nothing.
That said, you can still use e.g. `fields`, `filter_horizontal`, or `autocomplete_fields`.
```python
class CustomInlineActionForm(InlineActionForm):
field1 = forms.ChoiceField(
label="Field 1",
choices=[(1, "Option 1"), (2, "Option 2"), (3, "Option 3")],
)
field2 = forms.CharField(
label="Field 2",
required=False,
widget=forms.TextInput
)
field3 = forms.DateField(label="Field 3", initial="2024-07-15")
...
```
### _class_ InlineAdminActionFormSet
### _class_ StackedAdminActionInline
### _class_ TabularAdminActionInline
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin">
<code>InlineModelAdmin</code>
</a>
> _Added in version 2.1.0_
Class that defines the layout of inline formset as well as its other options not directly related to the form.
All subclasses of `InlineAdminActionFormSet` should have `prefix` and `form` defined, all other attributes are optional.
```python
class CustomAdminActionInline(StackedAdminActionInline):
name = "something"
form = CustomActionInlineForm
verbose_name = "..."
verbose_name_plural = "..."
extra = 3
min_num = 1
max_num = 5
classes = ["collapse"]
initial = [
{"field1": ..., "field2": ...},
{"field1": ..., "field2": ...},
]
```
#### name
A string used internally by the formset. It should be a valid python identifier, and when using multiple inlines inside one action form, each inline should have a unique `name`. It is used as a key in `data` dictionary that is passed to the action.
This is required.
#### form
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.model">
<code>InlineModelAdmin.model</code>
</a>
Form used in the formset. It should be a subclass of `InlineAdminActionForm` or `InlineActionForm`.
This is required.
#### extra
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.extra">
<code>InlineModelAdmin.extra</code>
</a>
Default: `1`
Number of extra forms the formset will display in addition to the initial forms.
#### _def_ get_extra(<i>request</i>)
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.get_extra">
<code>InlineModelAdmin.get_extra()</code>
</a>
Returns the number of extra inline forms to use. By default, returns the `InlineAdminActionFormSet.extra` attribute.
Override this method to programmatically determine the number of extra inline forms.
#### min_num
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.min_num">
<code>InlineModelAdmin.min_num</code>
</a>
Default: `0`
Minimum number of forms to show in the inline.
#### _def_ get_min_num(<i>request</i>)
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.get_min_num">
<code>InlineModelAdmin.get_min_num()</code>
</a>
Returns the minimum number of inline forms to use. By default, returns the `InlineAdminActionFormSet.min_num` attribute.
Override this method to programmatically determine the minimum number of inline forms.
#### max_num
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.max_num">
<code>InlineModelAdmin.max_num</code>
</a>
Default: `1000`
Maximum number of forms to show in the inline.
#### _def_ get_max_num(<i>request</i>)
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.get_max_num">
<code>InlineModelAdmin.get_max_num()</code>
</a>
Returns the maximum number of extra inline forms to use. By default, returns the `InlineAdminActionFormSet.max_num` attribute.
Override this method to programmatically determine the maximum number of inline forms.
#### verbose_name
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.verbose_name">
<code>InlineModelAdmin.verbose_name</code>
</a>
Used in inline template to display the name of the formset. It should be a string or a lazy translation. By default it uses the `InlineAdminActionFormSet.prefix` attribute.
#### verbose_name_plural
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.verbose_name_plural">
<code>InlineModelAdmin.verbose_name_plural</code>
</a>
If this isn’t given and the `InlineAdminActionFormSet.verbose_name` is defined, it will be set to `InlineAdminActionFormSet.verbose_name` + `'s'`.
#### classes
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.classes">
<code>InlineModelAdmin.classes</code>
</a>
Default: `[]`
A list or tuple containing extra CSS classes to apply to the fieldset that is rendered for the inlines. As with classes configured in `ModelAdmin.fieldsets`, inlines with a `"collapse"` class will be initially collapsed using an expandable widget.
#### initial
> Works similar to <a href="https://docs.djangoproject.com/en/5.2/topics/forms/formsets/#using-initial-data-with-a-formset">
<code>FormSet.initial</code>
</a>
Default: `None`
List of dictionaries used to prepopulate the formset with initial data. Each dictionary should contain the same keys as the fields in the formset.
Raw data
{
"_id": null,
"home_page": null,
"name": "django-admin-action-forms",
"maintainer": "Micha\u0142 Pokusa",
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "django, admin, action, actions, form, forms, pass, intermediate, args, arguments, params, parameters, inline, inlines, formset, formsets",
"author": "Micha\u0142 Pokusa",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/b7/db/c8df2f3029a4a6132a89c3f4ee107335648e3b40d6719a2cf05c663d798a/django_admin_action_forms-2.2.0.tar.gz",
"platform": null,
"description": "\n# django-admin-action-forms\n\n<p float=\"left\">\n <a href=\"https://pypi.org/project/django-admin-action-forms/\">\n <img src=\"https://img.shields.io/pypi/v/django-admin-action-forms?color=0073b7\"/>\n </a>\n <a href=\"https://www.djangoproject.com/\">\n <img src=\"https://img.shields.io/badge/3.2.x, 4.x.x, 5.x.x-a?style=flat&logo=django&label=django&labelColor=0c4b33&color=616161\">\n </a>\n</p>\n\nExtension for the Django admin panel that allows passing additional parameters to actions by creating intermediate pages with forms.\n\n- [\ud83d\ude80 Overview](#-overview)\n- [\ud83c\udf89 Features](#-features)\n- [\ud83d\udd0c Installation](#-installation)\n- [\u270f\ufe0f Examples](#\ufe0f-examples)\n - [Simple confirm form](#simple-confirm-form)\n - [Action with parameters](#action-with-parameters)\n - [Customizing action form layout](#customizing-action-form-layout)\n - [Inlines](#inlines)\n - [Testing action forms](#testing-action-forms)\n- [\ud83d\udcc4 Reference](#-reference)\n\n## \ud83d\ude80 Overview\n\nDo you need confirmation pages for your actions in Django admin?<br>\nDoes creating multiple actions in Django admin that only differ in arguments sound familiar?<br>\nHave you ever added a somewhat hacky way to pass additional parameters to an action?\n\n**If so, this package is for you!**\n\nThis is how it looks in action:\n\n<img src=\"https://raw.githubusercontent.com/michalpokusa/django-admin-action-forms/main/docs/overview.gif\" width=\"100%\">\n\nBy adding a few lines of code, you can create actions with custom forms that will be displayed in an intermediate page before the action is executed. Data from the form will be passed to the action as an additional argument.\n\nSimple and powerful!\n\n### \ud83c\udf89 Features\n\n- Requires minimal configuration, easy to use\n- Supports all modern Django versions (3.2.x, 4.x.x, 5.x.x)\n- Built on top of Django's templates and forms, matches the Django admin style\n- No additional dependencies\n- Supports `fields`/`fieldsets`, `filter_horizontal`/`filter_vertical` and `autocomplete_fields`\n- Works with custom widgets, validators and other Django form features\n- Formset support using inlines known from Django admin\n- Easy to test using Django's testing tools\n- Compatible with [django-no-queryset-admin-actions](https://pypi.org/project/django-no-queryset-admin-actions/)\n\n## \ud83d\udd0c Installation\n\n1. Install using ``pip``:\n\n ```bash\n $ pip3 install django-admin-action-forms\n ```\n\n2. Add `'django_admin_action_forms'` to your `INSTALLED_APPS` setting.\n ```python\n INSTALLED_APPS = [\n ...\n 'django_admin_action_forms',\n ]\n ```\n\n3. Include `'django_admin_action_forms.urls'` in your `urls.py` file. This is needed only if you want to use autocomplete.\n\n If you want to include them under the same path as admin site, make sure to place them **before** the admin URLs.\n\n ```python\n from django.contrib import admin\n from django.urls import path, include\n\n\n urlpatterns = [\n path(\"admin/action-forms/\", include(\"django_admin_action_forms.urls\")),\n path(\"admin/\", admin.site.urls),\n ...\n ]\n ```\n ...or include them under any other path.\n\n ```python\n from django.contrib import admin\n from django.urls import path, include\n\n\n urlpatterns = [\n path(\"admin/\", admin.site.urls),\n ...\n path(\"any/other/path/\", include(\"django_admin_action_forms.urls\")),\n ]\n ```\n\n## \u270f\ufe0f Examples\n\n### Simple confirm form\n\nSometimes you do not need any additional parameters, but you want to display a confirmation form before executing the action, just to make sure the user is aware of what they are doing. By default, Django displays such form for the built-in `delete_selected` action.\n\nLet's create a simple action that will reset the password for selected users, but before that, we want to display a confirmation form.\n\n```python\nfrom django.contrib import admin\nfrom django.contrib.auth.models import User\n\nfrom django_admin_action_forms import AdminActionFormsMixin, AdminActionForm, action_with_form\n\n\nclass ResetUsersPasswordActionForm(AdminActionForm):\n # No fields needed\n\n class Meta:\n list_objects = True\n help_text = \"Are you sure you want proceed with this action?\"\n\n\n@admin.register(User)\nclass UserAdmin(AdminActionFormsMixin, admin.ModelAdmin):\n\n @action_with_form(\n ResetUsersPasswordActionForm,\n description=\"Reset password for selected users\",\n )\n def reset_users_password_action(self, request, queryset, data):\n self.message_user(request, f\"Password reset for {queryset.count()} users.\")\n\n actions = [reset_users_password_action]\n```\n\nBy doing this, we recreated the behavior of intermediate page from the built-in `delete_selected` action.\n\n<img src=\"https://raw.githubusercontent.com/michalpokusa/django-admin-action-forms/main/docs/reset-users-password.gif\" width=\"100%\">\n\n### Action with parameters\n\nIn many cases however, you will want to pass additional parameters to the action. This can be very useful for e.g.:\n- Changing the status of `Order` to one of the predefined values\n- Setting a discount that you input for selected `Product` objects\n- Adding multiple tags to selected `Article` objects at once\n- Sending mails to selected `User` objects with a custom message, title and attachments\n\n...and many more!\n\nLet's create an action that will change the status of selected `Order` to a value that we select using a dropdown.\n\n```python\nfrom django import forms\nfrom django.contrib import admin\n\nfrom django_admin_action_forms import action_with_form, AdminActionForm\n\nfrom .models import Order\n\n\nclass ChangeOrderStatusActionForm(AdminActionForm):\n status = forms.ChoiceField(\n label=\"Status\",\n choices=[(\"new\", \"New\"), (\"processing\", \"Processing\"), (\"completed\", \"Completed\")],\n required=True,\n )\n\n\n@admin.register(Order)\nclass OrderAdmin(AdminActionFormsMixin, admin.ModelAdmin):\n\n @action_with_form(\n ChangeOrderStatusActionForm,\n description=\"Change status for selected Orders\",\n )\n def change_order_status_action(self, request, queryset, data):\n for order in queryset:\n order.status = data[\"status\"]\n order.save()\n self.message_user(request, f'Status changed to {data[\"status\"].upper()} for {queryset.count()} orders.')\n\n actions = [change_order_status_action]\n```\n\n<img src=\"https://raw.githubusercontent.com/michalpokusa/django-admin-action-forms/main/docs/change-order-status.gif\" width=\"100%\">\n\nYou may think that this could be achieved by creating an action for each status, but what if you have 10 statuses? 100? This way you can create a single action that will work for all of them.\n\nAnd how about parameter, that is not predefined, like a date or a number? It would be impossible to create an action for each possible value.\n\nLet's create an action form that will accept a discount for selected `Products` and a date when the discount will end.\n\n```python\nfrom django import forms\n\nfrom django_admin_action_forms import AdminActionForm\n\n\nclass SetProductDiscountActionForm(AdminActionForm):\n discount = forms.DecimalField(\n label=\"Discount (%)\",\n min_value=0,\n max_value=100,\n decimal_places=2,\n required=True,\n )\n valid_until = forms.DateField(\n label=\"Valid until\",\n required=True,\n )\n```\n\n<img src=\"https://raw.githubusercontent.com/michalpokusa/django-admin-action-forms/main/docs/set-product-discount.gif\" width=\"100%\">\n\nNow we can set any discount and any date, and because we subclassed [`AdminActionForm`](#adminactionform), we get a nice date picker.\n\n### Customizing action form layout\n\nIf your form has many fields, you may want to group them into fieldsets or reorder them. You can do this by using the `fields`, `fieldsets`, or corresponding methods in `Meta`.\n\nFor `Model`-related fields, it might be useful to use `filter_horizontal`/`filter_vertical` or `autocomplete_fields`.\n\nLet's create an action form for action that assigns selected `Tasks` to `Employee`, that we will select using autocomplete widget.\nAlso, let's add the field for setting the optional `Tags` for selected `Tasks`, and validate that no more than 3\nare selected using <a href=\"https://docs.djangoproject.com/en/5.2/ref/forms/api/#using-forms-to-validate-data\">Django's form validation</a>.\n\n```python\nfrom django import forms\n\nfrom django_admin_action_forms import AdminActionForm\n\n\nclass AssignToEmployeeActionForm(AdminActionForm):\n employee = forms.ModelChoiceField(\n queryset=Employee.objects.all(),\n required=True,\n )\n tags = forms.ModelMultipleChoiceField(\n queryset=Tag.objects.all(),\n required=False,\n )\n\n def clean_tags(self):\n tags = self.cleaned_data[\"tags\"]\n if tags.count() > 3:\n raise forms.ValidationError(\"You can't assign more than 3 tags to a task.\")\n return tags\n\n class Meta:\n autocomplete_fields = [\"employee\"]\n filter_horizontal = [\"tags\"]\n```\n\n<img src=\"https://raw.githubusercontent.com/michalpokusa/django-admin-action-forms/main/docs/assign-to-employee.gif\" width=\"100%\">\n\n### Inlines\n\nIn some cases, you may need to pass a list of values to the action. These values could be as simple as numbers or they could have a more complex structure.\nBy using `inlines`, you can add formsets to your action form.\nThis is very useful when you need multiple values that share the same fields, but you do not know exactly how many there will be.\n\nLet's say you want to set the power level plan for a fan, based on the temperature.\nYou can do this by creating an action form with inlines, where each inline will represent a range of temperatures and the corresponding power level.\n\n```python\nfrom django import forms\n\nfrom django_admin_action_forms import InlineAdminActionForm, TabularAdminActionInline, AdminActionForm\n\n\nclass FanPowerLevelInlineForm(InlineAdminActionForm):\n temperature_from = forms.IntegerField(required=True)\n temperature_to = forms.IntegerField(required=True)\n power = forms.IntegerField(\n min_value=0,\n max_value=100,\n required=True,\n )\n\n\nclass FanPowerLevelsInline(TabularAdminActionInline):\n name = \"fan_power_levels\"\n form = FanPowerLevelInlineForm\n\n extra = 0\n\n initial = [\n {\"temperature_from\": 0, \"temperature_to\": 50, \"power\": 40},\n {\"temperature_from\": 50, \"temperature_to\": 80, \"power\": 70},\n {\"temperature_from\": 80, \"temperature_to\": 100, \"power\": 100},\n ]\n\n\nclass ConfigureFanPowerLevelsActionForm(AdminActionForm):\n scale = forms.ChoiceField(\n choices=[\n (\"c\", \"Celsius\"),\n (\"f\", \"Fahrenheit\"),\n ],\n required=True,\n label=\"Scale\",\n )\n\n class Meta:\n inlines = [\n FanPowerLevelsInline,\n ]\n```\n\n<img src=\"https://raw.githubusercontent.com/michalpokusa/django-admin-action-forms/main/docs/inlines.gif\" width=\"100%\">\n\nMultiple inlines can be used in the same action form, and they will be displayed in the order they are defined in `inlines`.\n\n### Testing action forms\n\nTo test action forms, you can use Django's test client to send POST requests to model changelist with required data. The `action` and `_selected_action` fields are required, and the rest of the fields should match the action form fields.\n\n```python\nfrom django.contrib.auth.models import User\nfrom django.test import TestCase\nfrom django.urls import reverse\n\n\nclass ShopProductsTests(TestCase):\n\n def setUp(self):\n User.objects.create_superuser(username=\"admin\", password=\"password\")\n self.client.login(username=\"admin\", password=\"password\")\n\n def test_set_product_discount_action_form_submit(self):\n change_url = reverse(\"admin:shop_product_changelist\")\n data = {\n \"action\": \"set_product_discount\",\n \"_selected_action\": [10, 12, 14],\n \"discount\": \"20\",\n \"valid_until\": \"2024-12-05\",\n }\n response = self.client.post(change_url, data, follow=True)\n\n self.assertContains(response.rendered_content, \"Discount set to 20% for 3 products.\")\n```\n\n## \ud83d\udcc4 Reference\n\n- [`AdminActionFormsMixin`](#class-adminactionformsmixin)\n- [`@action_with_form`](#action_with_formform_class--permissionsnone-descriptionnone)\n- [`ActionForm`](#class-actionform)\n - [`__init__()`](#def-__init__self-args-kwargs)\n - [`admin_action_view()`](#def-action_form_viewself-request-extra_contextnone)\n- [`AdminActionForm`](#class-adminactionform)\n- [`ActionForm.Meta`](#class-actionformmeta)\n - [`list_objects`](#list_objects)\n - [`objects_summary`](#objects_summary)\n - [`help_text`](#help_text)\n - [`fields`](#fields)\n - [`get_fields()`](#def-get_fieldsrequest)\n - [`fieldsets`](#fieldsets)\n - [`get_fieldsets()`](#def-get_fieldsetsrequest)\n - [`filter_horizontal`](#filter_horizontal)\n - [`filter_vertical`](#filter_vertical)\n - [`autocomplete_fields`](#autocomplete_fields)\n - [`radio_fields`](#radio_fields)\n - [`inlines`](#inlines)\n - [`get_inlines()`](#def-get_inlinesrequest)\n - [`confirm_button_text`](#confirm_button_text)\n - [`cancel_button_text`](#cancel_button_text)\n- [`InlineActionForm`](#class-inlineactionform)\n- [`InlineAdminActionForm`](#class-inlineadminactionform)\n- [`InlineAdminActionFormSet`](#class-inlineadminactionformset)\n- [`StackedAdminActionInline`](#class-stackedadminactioninline)\n- [`TabularAdminActionInline`](#class-tabularadminactioninline)\n - [`name`](#name)\n - [`form`](#form)\n - [`extra`](#extra)\n - [`get_extra()`](#def-get_extrarequest)\n - [`min_num`](#min_num)\n - [`get_min_num()`](#def-get_min_numrequest)\n - [`max_num`](#max_num)\n - [`get_max_num()`](#def-get_max_numrequest)\n - [`verbose_name`](#verbose_name)\n - [`verbose_name_plural`](#verbose_name_plural)\n - [`classes`](#classes)\n - [`initial`](#initial)\n\n### _class_ AdminActionFormsMixin\n\nClass that should be inherited by all `ModelAdmin` classes that will use action forms. It provides the logic for displaying the intermediate page and handling the form submission.\n\n```python\nfrom django.contrib import admin\n\nfrom django_admin_action_forms import AdminActionFormsMixin\n\n\nclass ProductAdmin(AdminActionFormsMixin, admin.ModelAdmin):\n ...\n\n```\n\n#### @action_with_form(<i>form_class, *, permissions=None, description=None</i>)\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/actions/#the-action-decorator\">\n <code>@admin.action</code>\n</a>\n\nDecorator that can be used instead of `@admin.action` to create action with custom form.\nFunctions decorated with `@action_with_form` should accept additional argument `data` that will contain cleaned data from the form, `permissions` and `description` work the same.\n\n```python\n@action_with_form(\n CustomActionForm,\n description=\"Description of the action\",\n)\ndef custom_action(self, request, queryset, data):\n value_of_field1 = data[\"field1\"]\n optional_value_of_field2 = data.get(\"field2\")\n ...\n```\n\n### _class_ ActionForm\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/forms/api/#django.forms.Form\">\n <code>Form</code>\n</a>\n\nBase class for creating action forms responsible for all under the hood logic. Nearly always you will want to subclass `AdminActionForm` instead of `ActionForm`, as it provides additional features.\n\n#### _def_ \\_\\_init\\_\\_(<i>self, *args, **kwargs</i>)\n\n> From version 2.0.0 replaces `__post_init__` method\n\nConstructor for action forms that can be used to dynamically modify the form based on the `modeladmin`, `request` and `queryset` that are passed to the constructor and accessible from `self`.\nIt is possible to add, modify and remove fields, change the layout of the form and other options from `Meta` class.\n\n```python\nclass CustomActionForm(AdminActionForm):\n\n def __init__(self, *args, **kwargs):\n super().__init__(*args, **kwargs)\n\n if self.request.user.is_superuser:\n self.fields[\"field1\"].required = False\n\n self.opts.fields = [\"field2\", \"field1\"]\n\n self.opts.list_objects = self.queryset.count() > 10\n```\n\n#### _def_ action_form_view(<i>self, request, extra_context=None</i>)\n\n> Added in version 2.0.0\n\nMethod used for rendering the intermediate page with form. It can be used to do some checks before displaying the form and e.g. redirect to another page if the user is not allowed to perform the action.\nIt can also be used for providing `extra_context` to the template, which can be especially useful when extending the action form template.\n\n```python\nclass CustomActionForm(AdminActionForm):\n\n def action_form_view(self, request, extra_context=None):\n\n if self.queryset.count() > 3:\n self.modeladmin.message_user(\n request, \"No more than 3 objects can be selected.\", \"error\"\n )\n return HttpResponseRedirect(request.path)\n\n return super().action_form_view(request, {\"custom_context_value\": ...})\n```\n\n### _class_ AdminActionForm\n\nIn addition to `ActionForm`, it replaces default widgets for most field types with corresponding Django admin widgets that e.g. add a interactive date picker or prepend a clickable link above URL fields.\n\nMost of the time this is a class you want to subclass when creating custom action forms.\n\n```python\nclass CustomActionForm(AdminActionForm):\n\n field1 = forms.ChoiceField(\n label=\"Field 1\",\n choices=[(1, \"Option 1\"), (2, \"Option 2\"), (3, \"Option 3\")],\n )\n field2 = forms.CharField(\n label=\"Field 2\",\n required=False,\n widget=forms.TextInput\n )\n field3 = forms.DateField(label=\"Field 3\", initial=\"2024-07-15\")\n\n ...\n```\n\n### _class_ ActionForm.Meta\n\n> Works similar to some <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#modeladmin-options\">\n <code>ModelAdmin</code> options\n</a>\n\nAdditional configuration for action forms. It can be used to customize the layout of the form, add help text, or display a list of objects that will be affected by the action.\n\n```python\nclass CustomActionForm(AdminActionForm):\n\n ...\n\n class Meta:\n list_objects = True\n help_text = \"This is a help text\"\n ...\n```\n\nBelow you can find all available options:\n\n#### list_objects\n\nDefault: `False`\n\nIf `True`, the intermediate page will display a list of objects that will be affected by the action similarly\nto the intermediate page for built-in `delete_selected` action.\n\n```python\nclass Meta:\n list_objects = True\n```\n\n#### objects_summary\n\n> _Added in version 2.2.0_\n\nDefault: `True` if `list_objects` is `True`, otherwise `False`\n\nIf `True`, the intermediate page will display a summary section showing the count of objects that will be affected by the action.\nWhen `list_objects` is `True`, `objects_summary` defaults to `True` unless explicitly overridden.\n\n```python\n# Show both summary and individual objects (default behavior when list_objects=True)\nclass Meta:\n list_objects = True\n # objects_summary defaults to True\n\n# Show only individual objects without summary\nclass Meta:\n list_objects = True\n objects_summary = False\n\n# Show only summary without individual objects\nclass Meta:\n list_objects = False\n objects_summary = True\n\n# Show neither summary nor objects\nclass Meta:\n list_objects = False\n objects_summary = False\n```\n\n#### help_text\n\nDefault: `None`\n\nText displayed between the form and the list of objects or form in the intermediate page.\n\n```python\nclass Meta:\n help_text = \"This text will be displayed between the form and the list of objects\"\n```\n\n#### fields\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.fields\">\n <code>ModelAdmin.fields</code>\n</a>\n\nDefault: `None`\n\nSpecifies the fields that should be displayed in the form. If `fieldsets` is provided, `fields` will be ignored.\n\n```python\nclass Meta:\n fields = [\"field1\", (\"field2\", \"field3\")]\n```\n\n#### _def_ get_fields(<i>request</i>)\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_fields\">\n <code>ModelAdmin.get_fields()</code>\n</a>\n\nMethod that can be used to dynamically determine fields that should be displayed in the form. Can be used to reorder, group or exclude fields based on the `request`. Should return a list of fields, as described above in the [`fields`](#fields).\n\n```python\nclass Meta:\n\n def get_fields(self, request):\n if request.user.is_superuser:\n return [\"field1\", \"field2\", \"field3\"]\n else:\n return [\"field1\", \"field2\"]\n```\n\n#### fieldsets\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.fieldsets\">\n <code>ModelAdmin.fieldsets</code>\n</a>\n\nDefault: `None`\n\nIf both `fields` and `fieldsets` are provided, `fieldsets` will be used.\n\n```python\nclass Meta:\n fieldsets = [\n (\n None,\n {\n \"fields\": [\"field1\", \"field2\", (\"field3\", \"field4\")],\n },\n ),\n (\n \"Fieldset 2\",\n {\n \"classes\": [\"collapse\"],\n \"fields\": [\"field5\", (\"field6\", \"field7\")],\n \"description\": \"This is a description for fieldset 2\",\n },\n ),\n ]\n```\n\n#### _def_ get_fieldsets(<i>request</i>)\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_fieldsets\">\n <code>ModelAdmin.get_fieldsets()</code>\n</a>\n\nMethod that can be used to dynamically determine fieldsets that should be displayed in the form. Can be used to reorder, group or exclude fields based on the `request`. Should return a list of fieldsets, as described above in the [`fieldsets`](#fieldsets).\n\n```python\nclass Meta:\n\n def get_fieldsets(self, request):\n if request.user.is_superuser:\n return [\n (\n None,\n {\n \"fields\": [\"field1\", \"field2\", (\"field3\", \"field4\")],\n },\n ),\n (\n \"Fieldset 2\",\n {\n \"classes\": [\"collapse\"],\n \"fields\": [\"field5\", (\"field6\", \"field7\")],\n \"description\": \"This is a description for fieldset 2\",\n },\n ),\n ]\n else:\n return [\n (\n None,\n {\n \"fields\": [\"field1\", \"field2\", (\"field3\", \"field4\")],\n },\n ),\n ]\n```\n\n> [!NOTE]\n> Only one of `get_fieldsets`, `fieldsets`, `get_fields` or `fields` should be defined in the `Meta` class.\n> The order of precedence, from highest to lowest, is from left to right.\n\n#### filter_horizontal\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.filter_horizontal\">\n <code>ModelAdmin.filter_horizontal</code>\n</a>\n\nDefault: `None`\n\nSets fields that should use horizontal filter widget. It should be a list of field names.\n\n```python\nclass Meta:\n filter_horizontal = [\"tags\"]\n```\n\n#### filter_vertical\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.filter_vertical\">\n <code>ModelAdmin.filter_vertical</code>\n</a>\n\nDefault: `None`\n\nSets fields that should use vertical filter widget. It should be a list of field names.\n\n```python\nclass Meta:\n filter_vertical = [\"tags\"]\n```\n\n#### autocomplete_fields\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.autocomplete_fields\">\n <code>ModelAdmin.autocomplete_fields</code>\n</a>\n\nDefault: `None`\n\nSets fields that should use autocomplete widget. It should be a list of field names.\n\n```python\nclass Meta:\n autocomplete_fields = [\"employee\"]\n```\n\n> [!NOTE]\n> Autocomplete requires including `'django_admin_action_forms.urls'` in your `urls.py` file.\n> See [\ud83d\udd0c Installation](#-installation).\n\n#### radio_fields\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.radio_fields\">\n <code>ModelAdmin.radio_fields</code>\n</a>\n\n> _Added in version 2.1.0_\n\nDefault: `{}`\n\nSets fields that should use a radio-button interface.\nYou have the choice of using `HORIZONTAL` or `VERTICAL` from the `django.contrib.admin` module.\nDon\u2019t include a field in `radio_fields` unless it\u2019s a `ChoiceField` or its subclass.\n\n```python\nclass Meta:\n radio_fields = {\n \"field1\": admin.HORIZONTAL,\n \"field2\": admin.VERTICAL,\n }\n```\n\n#### inlines\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.inlines\">\n <code>ModelAdmin.inlines</code>\n</a>\n\n> _Added in version 2.1.0_\n\nDefault: `[]`\n\nSets inlines that should used in the form.\nIt should be a list of classes based on `StackedAdminActionInline` or `TabularAdminActionInline`.\n\n```python\nclass Meta:\n inlines = [\n CustomAdminActionInline,\n ]\n```\n\n#### _def_ get_inlines(<i>request</i>)\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_inlines\">\n <code>ModelAdmin.get_inlines()</code>\n</a>\n\n> _Added in version 2.1.0_\n\nMethod that can be used to dynamically determine inlines that should be used in the form.\nShould return a list of classes based on `StackedAdminActionInline` or `TabularAdminActionInline`.\n\n```python\nclass Meta:\n\n def get_inlines(self, request):\n if request.user.is_superuser:\n return [\n CustomAdminActionInline\n ]\n else:\n return []\n```\n\n#### confirm_button_text\n\n> _Added in version 1.2.0_\n\nDefault: `\"Confirm\"`\n\nText displayed on the confirm button. It can be either a `str` or a lazy translation.\n\n```python\nfrom django.utils.translation import gettext_lazy as _\n\nclass Meta:\n confirm_button_text = _(\"Proceed\")\n```\n\n#### cancel_button_text\n\n> _Added in version 1.2.0_\n\nDefault: `\"Cancel\"`\n\nText displayed on the cancel button. It can be either a `str` or a lazy translation.\n\n```python\nfrom django.utils.translation import gettext_lazy as _\n\nclass Meta:\n cancel_button_text = _(\"Abort\")\n```\n\n### _class_ InlineActionForm\n### _class_ InlineAdminActionForm\n\n> _Added in version 2.1.0_\n\nVersion of `ActionForm` and `AdminActionForm` that is used for inlines. Supports all of the features of `ActionForm` and selected `Meta` options.\n\nBecause some `Meta` options like `list_objects` or `help_text` do not make sense in the context of inlines, they do nothing.\nThat said, you can still use e.g. `fields`, `filter_horizontal`, or `autocomplete_fields`.\n\n```python\nclass CustomInlineActionForm(InlineActionForm):\n\n field1 = forms.ChoiceField(\n label=\"Field 1\",\n choices=[(1, \"Option 1\"), (2, \"Option 2\"), (3, \"Option 3\")],\n )\n field2 = forms.CharField(\n label=\"Field 2\",\n required=False,\n widget=forms.TextInput\n )\n field3 = forms.DateField(label=\"Field 3\", initial=\"2024-07-15\")\n\n ...\n```\n\n### _class_ InlineAdminActionFormSet\n### _class_ StackedAdminActionInline\n### _class_ TabularAdminActionInline\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin\">\n <code>InlineModelAdmin</code>\n</a>\n\n> _Added in version 2.1.0_\n\nClass that defines the layout of inline formset as well as its other options not directly related to the form.\nAll subclasses of `InlineAdminActionFormSet` should have `prefix` and `form` defined, all other attributes are optional.\n\n```python\nclass CustomAdminActionInline(StackedAdminActionInline):\n name = \"something\"\n form = CustomActionInlineForm\n\n verbose_name = \"...\"\n verbose_name_plural = \"...\"\n\n extra = 3\n min_num = 1\n max_num = 5\n\n classes = [\"collapse\"]\n\n initial = [\n {\"field1\": ..., \"field2\": ...},\n {\"field1\": ..., \"field2\": ...},\n ]\n```\n\n#### name\n\nA string used internally by the formset. It should be a valid python identifier, and when using multiple inlines inside one action form, each inline should have a unique `name`. It is used as a key in `data` dictionary that is passed to the action.\nThis is required.\n\n#### form\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.model\">\n <code>InlineModelAdmin.model</code>\n</a>\n\nForm used in the formset. It should be a subclass of `InlineAdminActionForm` or `InlineActionForm`.\nThis is required.\n\n#### extra\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.extra\">\n <code>InlineModelAdmin.extra</code>\n</a>\n\nDefault: `1`\n\nNumber of extra forms the formset will display in addition to the initial forms.\n\n#### _def_ get_extra(<i>request</i>)\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.get_extra\">\n <code>InlineModelAdmin.get_extra()</code>\n</a>\n\nReturns the number of extra inline forms to use. By default, returns the `InlineAdminActionFormSet.extra` attribute.\n\nOverride this method to programmatically determine the number of extra inline forms.\n\n#### min_num\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.min_num\">\n <code>InlineModelAdmin.min_num</code>\n</a>\n\nDefault: `0`\n\nMinimum number of forms to show in the inline.\n\n#### _def_ get_min_num(<i>request</i>)\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.get_min_num\">\n <code>InlineModelAdmin.get_min_num()</code>\n</a>\n\nReturns the minimum number of inline forms to use. By default, returns the `InlineAdminActionFormSet.min_num` attribute.\n\nOverride this method to programmatically determine the minimum number of inline forms.\n\n#### max_num\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.max_num\">\n <code>InlineModelAdmin.max_num</code>\n</a>\n\nDefault: `1000`\n\nMaximum number of forms to show in the inline.\n\n#### _def_ get_max_num(<i>request</i>)\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.get_max_num\">\n <code>InlineModelAdmin.get_max_num()</code>\n</a>\n\nReturns the maximum number of extra inline forms to use. By default, returns the `InlineAdminActionFormSet.max_num` attribute.\n\nOverride this method to programmatically determine the maximum number of inline forms.\n\n#### verbose_name\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.verbose_name\">\n <code>InlineModelAdmin.verbose_name</code>\n</a>\n\nUsed in inline template to display the name of the formset. It should be a string or a lazy translation. By default it uses the `InlineAdminActionFormSet.prefix` attribute.\n\n#### verbose_name_plural\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.verbose_name_plural\">\n <code>InlineModelAdmin.verbose_name_plural</code>\n</a>\n\nIf this isn\u2019t given and the `InlineAdminActionFormSet.verbose_name` is defined, it will be set to `InlineAdminActionFormSet.verbose_name` + `'s'`.\n\n#### classes\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.classes\">\n <code>InlineModelAdmin.classes</code>\n</a>\n\nDefault: `[]`\n\nA list or tuple containing extra CSS classes to apply to the fieldset that is rendered for the inlines. As with classes configured in `ModelAdmin.fieldsets`, inlines with a `\"collapse\"` class will be initially collapsed using an expandable widget.\n\n#### initial\n\n> Works similar to <a href=\"https://docs.djangoproject.com/en/5.2/topics/forms/formsets/#using-initial-data-with-a-formset\">\n <code>FormSet.initial</code>\n</a>\n\nDefault: `None`\n\nList of dictionaries used to prepopulate the formset with initial data. Each dictionary should contain the same keys as the fields in the formset.\n",
"bugtrack_url": null,
"license": null,
"summary": "Extension for the Django admin panel that allows passing additional parameters to actions by creating intermediate pages with forms.",
"version": "2.2.0",
"project_urls": {
"Changelog": "https://github.com/michalpokusa/django-admin-action-forms/blob/main/CHANGELOG.md",
"Repository": "https://github.com/michalpokusa/django-admin-action-forms"
},
"split_keywords": [
"django",
" admin",
" action",
" actions",
" form",
" forms",
" pass",
" intermediate",
" args",
" arguments",
" params",
" parameters",
" inline",
" inlines",
" formset",
" formsets"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "a4a2e9638c51552139b9995e9b04bc3fc707277a30ea9fece23c8a1df34fe7a5",
"md5": "5971f20262f84021e74fbf205abc6d2c",
"sha256": "0b8be4f28d241c14c8cb0c9a498e5ce0665d8acf0896383d0b1fc22fb34c36d5"
},
"downloads": -1,
"filename": "django_admin_action_forms-2.2.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "5971f20262f84021e74fbf205abc6d2c",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 34312,
"upload_time": "2025-08-28T03:30:08",
"upload_time_iso_8601": "2025-08-28T03:30:08.477746Z",
"url": "https://files.pythonhosted.org/packages/a4/a2/e9638c51552139b9995e9b04bc3fc707277a30ea9fece23c8a1df34fe7a5/django_admin_action_forms-2.2.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "b7dbc8df2f3029a4a6132a89c3f4ee107335648e3b40d6719a2cf05c663d798a",
"md5": "02f81422c9192aef58356b6995d1c800",
"sha256": "8c8fbb809464ab53daf94fb6bd97ccf151989fc00321610157a3fcdd928de15b"
},
"downloads": -1,
"filename": "django_admin_action_forms-2.2.0.tar.gz",
"has_sig": false,
"md5_digest": "02f81422c9192aef58356b6995d1c800",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 31492,
"upload_time": "2025-08-28T03:30:09",
"upload_time_iso_8601": "2025-08-28T03:30:09.656820Z",
"url": "https://files.pythonhosted.org/packages/b7/db/c8df2f3029a4a6132a89c3f4ee107335648e3b40d6719a2cf05c663d798a/django_admin_action_forms-2.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-28 03:30:09",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "michalpokusa",
"github_project": "django-admin-action-forms",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "django-admin-action-forms"
}