batch_actions.rst 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. Batch actions
  2. =============
  3. Batch actions are actions triggered on a set of selected objects. By default,
  4. Admins have a ``delete`` action which allows you to remove several entries at once.
  5. Defining new actions
  6. --------------------
  7. To create a new custom batch action which appears in the list view follow these steps:
  8. Override ``configureBatchActions()`` in your ``Admin`` class to define the new batch actions
  9. by adding them to the ``$actions`` array. Each key represent a batch action and could contain these settings:
  10. - **label**: The name to use when offering this option to users, should be passed through the translator
  11. (default: the label is generated via the labelTranslatorStrategy)
  12. - **translation_domain**: The domain which will be used to translate the key.
  13. (default: the translation domain of the admin)
  14. - **ask_confirmation**: defaults to true and means that the user will be asked
  15. for confirmation before the batch action is processed
  16. For example, lets define a new ``merge`` action which takes a number of source items and
  17. merges them onto a single target item. It should only be available when two conditions are met:
  18. - the EDIT and DELETE routes exist for this Admin (have not been disabled)
  19. - the logged in administrator has EDIT and DELETE permissions
  20. .. code-block:: php
  21. <?php
  22. // in your Admin class
  23. public function configureBatchActions($actions)
  24. {
  25. if (
  26. $this->hasRoute('edit') && $this->hasAccess('edit') &&
  27. $this->hasRoute('delete') && $this->hasAccess('delete')
  28. ) {
  29. $actions['merge'] = array(
  30. 'ask_confirmation' => true
  31. );
  32. }
  33. return $actions;
  34. }
  35. Define the core action logic
  36. ----------------------------
  37. The method ``batchAction<MyAction>`` will be executed to process your batch in your ``CRUDController`` class. The selected
  38. objects are passed to this method through a query argument which can be used to retrieve them.
  39. If for some reason it makes sense to perform your batch action without the default selection
  40. method (for example you defined another way, at template level, to select model at a lower
  41. granularity), the passed query is ``null``.
  42. .. note::
  43. You can check how to declare your own ``CRUDController`` class in the Architecture section.
  44. .. code-block:: php
  45. <?php
  46. // src/AppBundle/Controller/CRUDController.php
  47. namespace AppBundle\Controller;
  48. use Sonata\AdminBundle\Controller\CRUDController as BaseController;
  49. use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
  50. use Symfony\Component\HttpFoundation\RedirectResponse;
  51. use Symfony\Component\HttpFoundation\Request;
  52. use Symfony\Component\Security\Core\Exception\AccessDeniedException;
  53. class CRUDController extends BaseController
  54. {
  55. /**
  56. * @param ProxyQueryInterface $selectedModelQuery
  57. * @param Request $request
  58. *
  59. * @return RedirectResponse
  60. */
  61. public function batchActionMerge(ProxyQueryInterface $selectedModelQuery, Request $request = null)
  62. {
  63. $this->admin->checkAccess('edit');
  64. $this->admin->checkAccess('delete');
  65. $modelManager = $this->admin->getModelManager();
  66. $target = $modelManager->find($this->admin->getClass(), $request->get('targetId'));
  67. if ($target === null){
  68. $this->addFlash('sonata_flash_info', 'flash_batch_merge_no_target');
  69. return new RedirectResponse(
  70. $this->admin->generateUrl('list', array('filter' => $this->admin->getFilterParameters()))
  71. );
  72. }
  73. $selectedModels = $selectedModelQuery->execute();
  74. // do the merge work here
  75. try {
  76. foreach ($selectedModels as $selectedModel) {
  77. $modelManager->delete($selectedModel);
  78. }
  79. $modelManager->update($selectedModel);
  80. } catch (\Exception $e) {
  81. $this->addFlash('sonata_flash_error', 'flash_batch_merge_error');
  82. return new RedirectResponse(
  83. $this->admin->generateUrl('list', array('filter' => $this->admin->getFilterParameters()))
  84. );
  85. }
  86. $this->addFlash('sonata_flash_success', 'flash_batch_merge_success');
  87. return new RedirectResponse(
  88. $this->admin->generateUrl('list', array('filter' => $this->admin->getFilterParameters()))
  89. );
  90. }
  91. // ...
  92. }
  93. (Optional) Overriding the batch selection template
  94. --------------------------------------------------
  95. A merge action requires two kinds of selection: a set of source objects to merge from
  96. and a target object to merge into. By default, batch_actions only let you select one set
  97. of objects to manipulate. We can override this behavior by changing our list template
  98. (``list__batch.html.twig``) and adding a radio button to choose the target object.
  99. .. code-block:: html+jinja
  100. {# src/AppBundle/Resources/views/CRUD/list__batch.html.twig #}
  101. {# see SonataAdminBundle:CRUD:list__batch.html.twig for the current default template #}
  102. {% extends admin.getTemplate('base_list_field') %}
  103. {% block field %}
  104. <input type="checkbox" name="idx[]" value="{{ admin.id(object) }}" />
  105. {# the new radio button #}
  106. <input type="radio" name="targetId" value="{{ admin.id(object) }}" />
  107. {% endblock %}
  108. And add this:
  109. .. code-block:: php
  110. <?php
  111. // src/AppBundle/AppBundle.php
  112. public function getParent()
  113. {
  114. return 'SonataAdminBundle';
  115. }
  116. See the `Symfony bundle overriding mechanism`_
  117. for further explanation of overriding bundle templates.
  118. (Optional) Overriding the default relevancy check function
  119. ----------------------------------------------------------
  120. By default, batch actions are not executed if no object was selected, and the user is notified of
  121. this lack of selection. If your custom batch action needs more complex logic to determine if
  122. an action can be performed or not, just define a ``batchAction<MyAction>IsRelevant`` method
  123. (e.g. ``batchActionMergeIsRelevant``) in your ``CRUDController`` class. This check is performed
  124. before the user is asked for confirmation, to make sure there is actually something to confirm.
  125. This method may return three different values:
  126. - ``true``: The batch action is relevant and can be applied.
  127. - ``false``: Same as above, with the default "action aborted, no model selected" notification message.
  128. - ``string``: The batch action is not relevant given the current request parameters
  129. (for example the ``target`` is missing for a ``merge`` action).
  130. The returned string is a message displayed to the user.
  131. .. code-block:: php
  132. <?php
  133. // src/AppBundle/Controller/CRUDController.php
  134. namespace AppBundle\Controller;
  135. use Sonata\AdminBundle\Controller\CRUDController as BaseController;
  136. use Symfony\Component\HttpFoundation\Request;
  137. class CRUDController extends BaseController
  138. {
  139. public function batchActionMergeIsRelevant(array $selectedIds, $allEntitiesSelected, Request $request = null)
  140. {
  141. // here you have access to all POST parameters, if you use some custom ones
  142. // POST parameters are kept even after the confirmation page.
  143. $parameterBag = $request->request;
  144. // check that a target has been chosen
  145. if (!$parameterBag->has('targetId')) {
  146. return 'flash_batch_merge_no_target';
  147. }
  148. $targetId = $parameterBag->get('targetId');
  149. // if all entities are selected, a merge can be done
  150. if ($allEntitiesSelected) {
  151. return true;
  152. }
  153. // filter out the target from the selected models
  154. $selectedIds = array_filter($selectedIds,
  155. function($selectedId) use($targetId){
  156. return $selectedId !== $targetId;
  157. }
  158. );
  159. // if at least one but not the target model is selected, a merge can be done.
  160. return count($selectedIds) > 0;
  161. }
  162. // ...
  163. }
  164. (Optional) Executing a pre batch hook
  165. -------------------------------------
  166. In your admin class you can create a ``preBatchAction`` method to execute something before doing the batch action.
  167. The main purpose of this method is to alter the query or the list of selected ids.
  168. .. code-block:: php
  169. <?php
  170. // in your Admin class
  171. public function preBatchAction($actionName, ProxyQueryInterface $query, array & $idx, $allElements)
  172. {
  173. // altering the query or the idx array
  174. $foo = $query->getParameter('foo')->getValue();
  175. // Doing something with the foo object
  176. // ...
  177. $query->setParameter('foo', $bar);
  178. }
  179. .. _Symfony bundle overriding mechanism: http://symfony.com/doc/current/cookbook/bundles/inheritance.html