Source code for flask_admin.actions
import typing as t
from typing import Any
from flask import redirect
from flask import request
from flask_admin import tools
from flask_admin._compat import text_type
from flask_admin._types import T_RESPONSE
from flask_admin.helpers import flash_errors
from flask_admin.helpers import get_redirect_target
[docs]
def action(
name: str, text: str, confirmation: str | None = None
) -> t.Callable[..., t.Any]:
"""
Use this decorator to expose actions that span more than one
entity (model, file, etc)
:param name:
Action name
:param text:
Action text.
:param confirmation:
Confirmation text. If not provided, action will be executed
unconditionally.
"""
def wrap(f: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:
f._action = (name, text, confirmation) # type: ignore[attr-defined]
return f
return wrap
[docs]
class ActionsMixin:
"""
Actions mixin.
In some cases, you might work with more than one "entity" (model, file, etc) in
your admin view and will want to perform actions on a group of entities
simultaneously.
In this case, you can add this functionality by doing this:
1. Add this mixin to your administrative view class
2. Call `init_actions` in your class constructor
3. Expose actions view
4. Import `actions.html` library and add call library macros in your template
"""
def __init__(self) -> None:
"""
Default constructor.
"""
self._actions: list[tuple[str, str]] = []
self._actions_data: dict[str, tuple[Any, str, str | None]] = {}
[docs]
def init_actions(self) -> None:
"""
Initialize list of actions for the current administrative view.
"""
self._actions: list[tuple[str, str]] = [] # type:ignore[no-redef]
self._actions_data: dict[str, tuple[Any, str, str | None]] = {} # type:ignore[no-redef]
for p in dir(self):
attr = tools.get_dict_attr(self, p)
if hasattr(attr, "_action"):
name, text, desc = attr._action # type: ignore[union-attr]
self._actions.append((name, text))
# TODO: Use namedtuple
# Reason why we need getattr here - what's in attr is not
# bound to the object.
self._actions_data[name] = (getattr(self, p), text, desc)
[docs]
def is_action_allowed(self, name: str) -> bool:
"""
Verify if action with `name` is allowed.
:param name:
Action name
"""
return True
[docs]
def get_actions_list(self) -> tuple[list[t.Any], dict[t.Any, t.Any]]:
"""
Return a list and a dictionary of allowed actions.
"""
actions = []
actions_confirmation = {}
for act in self._actions:
name, text = act
if self.is_action_allowed(name):
actions.append((name, text_type(text)))
confirmation = self._actions_data[name][2]
if confirmation:
actions_confirmation[name] = text_type(confirmation)
return actions, actions_confirmation
[docs]
def handle_action(self, return_view: str | None = None) -> T_RESPONSE:
"""
Handle action request.
:param return_view:
Name of the view to return to after the request.
If not provided, will return user to the return url in the form
or the list view.
"""
form = self.action_form() # type: ignore[attr-defined]
if self.validate_form(form): # type: ignore[attr-defined]
# using getlist instead of FieldList for backward compatibility
ids = request.form.getlist("rowid")
action = form.action.data
handler = self._actions_data.get(action)
if handler and self.is_action_allowed(action):
response = handler[0](ids)
if response is not None:
return response
else:
flash_errors(form, message="Failed to perform action. %(error)s")
if return_view:
url = self.get_url("." + return_view) # type: ignore[attr-defined]
else:
url = get_redirect_target() or self.get_url(".index_view") # type: ignore[attr-defined]
return redirect(url)