recipe_custom_action.rst 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. Creating a Custom Admin Action
  2. ==============================
  3. This is a full working example of creating a custom list action for SonataAdmin.
  4. The example is based on an existing ``CarAdmin`` class in an ``AppBundle``.
  5. It is assumed you already have an admin service up and running.
  6. The recipe
  7. ----------
  8. SonataAdmin provides a very straight-forward way of adding your own custom actions.
  9. To do this we need to:
  10. - extend the ``SonataAdmin:CRUD`` Controller and tell our admin class to use it
  11. - create the custom action in our Controller
  12. - create a template to show the action in the list view
  13. - add the route and the new action in the Admin class
  14. Extending the Admin Controller
  15. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  16. First you need to create your own Controller extending the one from SonataAdmin
  17. .. code-block:: php
  18. <?php
  19. // src/AppBundle/Controller/CRUDController.php
  20. namespace AppBundle\Controller;
  21. use Sonata\AdminBundle\Controller\CRUDController as Controller;
  22. class CRUDController extends Controller
  23. {
  24. // ...
  25. }
  26. Admin classes by default use the ``SonataAdmin:CRUD`` controller, this is the third parameter
  27. of an admin service definition, you need to change it to your own.
  28. Register the Admin as a Service
  29. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  30. Either by using XML:
  31. .. code-block:: xml
  32. <!-- src/AppBundle/Resources/config/admin.xml -->
  33. <service id="app.admin.car" class="AppBundle\Admin\CarAdmin">
  34. <tag name="sonata.admin" manager_type="orm" group="Demo" label="Car" />
  35. <argument />
  36. <argument>AppBundle\Entity\Car</argument>
  37. <argument>AppBundle:CRUD</argument>
  38. </service>
  39. or by adding it to your ``admin.yml``:
  40. .. code-block:: yaml
  41. # src/AppBundle/Resources/config/admin.yml
  42. services:
  43. app.admin.car:
  44. class: AppBundle\Admin\CarAdmin
  45. tags:
  46. - { name: sonata.admin, manager_type: orm, group: Demo, label: Car }
  47. arguments:
  48. - null
  49. - AppBundle\Entity\Car
  50. - AppBundle:CRUD
  51. public: true
  52. For more information about service configuration please refer to Step 3 of :doc:`../reference/getting_started`
  53. Create the custom action in your Controller
  54. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  55. Now it is time to actually create your custom action here, for this example I chose
  56. to implement a ``clone`` action.
  57. .. code-block:: php
  58. <?php
  59. // src/AppBundle/Controller/CRUDController.php
  60. namespace AppBundle\Controller;
  61. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  62. use Sonata\AdminBundle\Controller\CRUDController as Controller;
  63. use Symfony\Component\HttpFoundation\RedirectResponse;
  64. class CRUDController extends Controller
  65. {
  66. /**
  67. * @param $id
  68. */
  69. public function cloneAction($id)
  70. {
  71. $object = $this->admin->getSubject();
  72. if (!$object) {
  73. throw new NotFoundHttpException(sprintf('unable to find the object with id: %s', $id));
  74. }
  75. // Be careful, you may need to overload the __clone method of your object
  76. // to set its id to null !
  77. $clonedObject = clone $object;
  78. $clonedObject->setName($object->getName().' (Clone)');
  79. $this->admin->create($clonedObject);
  80. $this->addFlash('sonata_flash_success', 'Cloned successfully');
  81. return new RedirectResponse($this->admin->generateUrl('list'));
  82. // if you have a filtered list and want to keep your filters after the redirect
  83. // return new RedirectResponse($this->admin->generateUrl('list', $this->admin->getFilterParameters()));
  84. }
  85. }
  86. Here we first get the object, see if it exists then clone it and insert the clone
  87. as a new object. Finally we set a flash message indicating success and redirect to the list view.
  88. If you want to add the current filter parameters to the redirect url you can add them to the `generateUrl` method:
  89. .. code-block:: php
  90. return new RedirectResponse($this->admin->generateUrl('list', array('filter' => $this->admin->getFilterParameters())));
  91. Using template in new controller
  92. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  93. If you want to render something here you can create new template anywhere, extend sonata layout
  94. and use `sonata_admin_content` block.
  95. .. code-block:: html+jinja
  96. {% extends 'SonataAdminBundle::standard_layout.html.twig' %}
  97. {% block sonata_admin_content %}
  98. Your content here
  99. {% endblock %}
  100. Create a template for the new action
  101. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  102. You need to tell SonataAdmin how to render your new action. You do that by
  103. creating a ``list__action_clone.html.twig`` in the namespace of your custom
  104. Admin Controller.
  105. .. code-block:: html+jinja
  106. {# src/AppBundle/Resources/views/CRUD/list__action_clone.html.twig #}
  107. <a class="btn btn-sm" href="{{ admin.generateObjectUrl('clone', object) }}">clone</a>
  108. Right now ``clone`` is not a known route, we define it in the next step.
  109. Bringing it all together
  110. ^^^^^^^^^^^^^^^^^^^^^^^^
  111. What is left now is actually adding your custom action to the admin class.
  112. You have to add the new route in ``configureRoutes``:
  113. .. code-block:: php
  114. // ...
  115. use Sonata\AdminBundle\Route\RouteCollection;
  116. protected function configureRoutes(RouteCollection $collection)
  117. {
  118. $collection->add('clone', $this->getRouterIdParameter().'/clone');
  119. }
  120. This gives us a route like ``../admin/app/car/1/clone``.
  121. You could also just write ``$collection->add('clone');`` to get a route like ``../admin/app/car/clone?id=1``
  122. Next we have to add the action in ``configureListFields`` specifying the template we created.
  123. .. code-block:: php
  124. protected function configureListFields(ListMapper $listMapper)
  125. {
  126. $listMapper
  127. // other fields...
  128. ->add('_action', null, array(
  129. 'actions' => array(
  130. // ...
  131. 'clone' => array(
  132. 'template' => 'AppBundle:CRUD:list__action_clone.html.twig'
  133. )
  134. )
  135. ))
  136. ;
  137. }
  138. The full ``CarAdmin.php`` example looks like this:
  139. .. code-block:: php
  140. <?php
  141. // src/AppBundle/Admin/CarAdmin.php
  142. namespace AppBundle\Admin;
  143. use Sonata\AdminBundle\Admin\AbstractAdmin;
  144. use Sonata\AdminBundle\Datagrid\DatagridMapper;
  145. use Sonata\AdminBundle\Datagrid\ListMapper;
  146. use Sonata\AdminBundle\Form\FormMapper;
  147. use Sonata\AdminBundle\Route\RouteCollection;
  148. use Sonata\AdminBundle\Show\ShowMapper;
  149. class CarAdmin extends AbstractAdmin
  150. {
  151. protected function configureRoutes(RouteCollection $collection)
  152. {
  153. $collection->add('clone', $this->getRouterIdParameter().'/clone');
  154. }
  155. protected function configureDatagridFilters(DatagridMapper $datagridMapper)
  156. {
  157. // ...
  158. }
  159. protected function configureFormFields(FormMapper $formMapper)
  160. {
  161. // ...
  162. }
  163. protected function configureListFields(ListMapper $listMapper)
  164. {
  165. $listMapper
  166. ->addIdentifier('name')
  167. ->add('engine')
  168. ->add('rescueEngine')
  169. ->add('createdAt')
  170. ->add('_action', null, array(
  171. 'actions' => array(
  172. 'show' => array(),
  173. 'edit' => array(),
  174. 'delete' => array(),
  175. 'clone' => array(
  176. 'template' => 'AppBundle:CRUD:list__action_clone.html.twig'
  177. )
  178. )
  179. ));
  180. }
  181. protected function configureShowFields(ShowMapper $showMapper)
  182. {
  183. // ...
  184. }
  185. }
  186. .. note::
  187. If you want to render a custom controller action in a template by using the
  188. render function in twig you need to add ``_sonata_admin`` as an attribute. For
  189. example; ``{{ render(controller('AppBundle:XxxxCRUD:comment', {'_sonata_admin':
  190. 'sonata.admin.xxxx' })) }}``. This has to be done because the moment the
  191. rendering should happen the routing, which usually sets the value of this
  192. parameter, is not involved at all, and then you will get an error "There is no
  193. _sonata_admin defined for the controller
  194. AppBundle\Controller\XxxxCRUDController and the current route ' '."