hooks.rst 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. .. _admin_hooks:
  2. Hooks
  3. =====
  4. On loading, Wagtail will search for any app with the file ``wagtail_hooks.py`` and execute the contents. This provides a way to register your own functions to execute at certain points in Wagtail's execution, such as when a ``Page`` object is saved or when the main menu is constructed.
  5. Registering functions with a Wagtail hook is done through the ``@hooks.register`` decorator:
  6. .. code-block:: python
  7. from wagtail.wagtailcore import hooks
  8. @hooks.register('name_of_hook')
  9. def my_hook_function(arg1, arg2...)
  10. # your code here
  11. Alternatively, ``hooks.register`` can be called as an ordinary function, passing in the name of the hook and a handler function defined elsewhere:
  12. .. code-block:: python
  13. hooks.register('name_of_hook', my_hook_function)
  14. If you need your hooks to run in a particular order, you can pass the ``order`` parameter:
  15. .. code-block:: python
  16. @hooks.register('name_of_hook', order=1) # This will run after every hook in the wagtail core
  17. def my_hook_function(arg1, arg2...)
  18. # your code here
  19. @hooks.register('name_of_hook', order=-1) # This will run before every hook in the wagtail core
  20. def my_other_hook_function(arg1, arg2...)
  21. # your code here
  22. @hooks.register('name_of_hook', order=2) # This will run after `my_hook_function`
  23. def yet_another_hook_function(arg1, arg2...)
  24. # your code here
  25. The available hooks are listed below.
  26. .. contents::
  27. :local:
  28. :depth: 1
  29. Admin modules
  30. -------------
  31. Hooks for building new areas of the admin interface (alongside pages, images, documents and so on).
  32. .. _construct_homepage_panels:
  33. ``construct_homepage_panels``
  34. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  35. Add or remove panels from the Wagtail admin homepage. The callable passed into this hook should take a ``request`` object and a list of ``panels``, objects which have a ``render()`` method returning a string. The objects also have an ``order`` property, an integer used for ordering the panels. The default panels use integers between ``100`` and ``300``.
  36. .. code-block:: python
  37. from django.utils.safestring import mark_safe
  38. from wagtail.wagtailcore import hooks
  39. class WelcomePanel(object):
  40. order = 50
  41. def render(self):
  42. return mark_safe("""
  43. <section class="panel summary nice-padding">
  44. <h3>No, but seriously -- welcome to the admin homepage.</h3>
  45. </section>
  46. """)
  47. @hooks.register('construct_homepage_panels')
  48. def add_another_welcome_panel(request, panels):
  49. return panels.append( WelcomePanel() )
  50. .. _construct_homepage_summary_items:
  51. ``construct_homepage_summary_items``
  52. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  53. Add or remove items from the 'site summary' bar on the admin homepage (which shows the number of pages and other object that exist on the site). The callable passed into this hook should take a ``request`` object and a list of ``SummaryItem`` objects to be modified as required. These objects have a ``render()`` method, which returns an HTML string, and an ``order`` property, which is an integer that specifies the order in which the items will appear.
  54. .. _construct_main_menu:
  55. ``construct_main_menu``
  56. ~~~~~~~~~~~~~~~~~~~~~~~
  57. Called just before the Wagtail admin menu is output, to allow the list of menu items to be modified. The callable passed to this hook will receive a ``request`` object and a list of ``menu_items``, and should modify ``menu_items`` in-place as required. Adding menu items should generally be done through the ``register_admin_menu_item`` hook instead - items added through ``construct_main_menu`` will be missing any associated JavaScript includes, and their ``is_shown`` check will not be applied.
  58. .. code-block:: python
  59. from wagtail.wagtailcore import hooks
  60. @hooks.register('construct_main_menu')
  61. def hide_explorer_menu_item_from_frank(request, menu_items):
  62. if request.user.username == 'frank':
  63. menu_items[:] = [item for item in menu_items if item.name != 'explorer']
  64. .. _describe_collection_contents:
  65. ``describe_collection_contents``
  66. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  67. Called when Wagtail needs to find out what objects exist in a collection, if any. Currently this happens on the confirmation before deleting a collection, to ensure that non-empty collections cannot be deleted. The callable passed to this hook will receive a ``collection`` object, and should return either ``None`` (to indicate no objects in this collection), or a dict containing the following keys:
  68. ``count``
  69. A numeric count of items in this collection
  70. ``count_text``
  71. A human-readable string describing the number of items in this collection, such as "3 documents". (Sites with multi-language support should return a translatable string here, most likely using the ``django.utils.translation.ungettext`` function.)
  72. ``url`` (optional)
  73. A URL to an index page that lists the objects being described.
  74. .. _register_admin_menu_item:
  75. ``register_admin_menu_item``
  76. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  77. Add an item to the Wagtail admin menu. The callable passed to this hook must return an instance of ``wagtail.wagtailadmin.menu.MenuItem``. New items can be constructed from the ``MenuItem`` class by passing in a ``label`` which will be the text in the menu item, and the URL of the admin page you want the menu item to link to (usually by calling ``reverse()`` on the admin view you've set up). Additionally, the following keyword arguments are accepted:
  78. :name: an internal name used to identify the menu item; defaults to the slugified form of the label.
  79. :classnames: additional classnames applied to the link, used to give it an icon
  80. :attrs: additional HTML attributes to apply to the link
  81. :order: an integer which determines the item's position in the menu
  82. ``MenuItem`` can be subclassed to customise the HTML output, specify JavaScript files required by the menu item, or conditionally show or hide the item for specific requests (for example, to apply permission checks); see the source code (``wagtail/wagtailadmin/menu.py``) for details.
  83. .. code-block:: python
  84. from django.core.urlresolvers import reverse
  85. from wagtail.wagtailcore import hooks
  86. from wagtail.wagtailadmin.menu import MenuItem
  87. @hooks.register('register_admin_menu_item')
  88. def register_frank_menu_item():
  89. return MenuItem('Frank', reverse('frank'), classnames='icon icon-folder-inverse', order=10000)
  90. .. _register_admin_urls:
  91. ``register_admin_urls``
  92. ~~~~~~~~~~~~~~~~~~~~~~~
  93. Register additional admin page URLs. The callable fed into this hook should return a list of Django URL patterns which define the structure of the pages and endpoints of your extension to the Wagtail admin. For more about vanilla Django URLconfs and views, see `url dispatcher`_.
  94. .. _url dispatcher: https://docs.djangoproject.com/en/dev/topics/http/urls/
  95. .. code-block:: python
  96. from django.http import HttpResponse
  97. from django.conf.urls import url
  98. from wagtail.wagtailcore import hooks
  99. def admin_view( request ):
  100. return HttpResponse( \
  101. "I have approximate knowledge of many things!", \
  102. content_type="text/plain")
  103. @hooks.register('register_admin_urls')
  104. def urlconf_time():
  105. return [
  106. url(r'^how_did_you_almost_know_my_name/$', admin_view, name='frank' ),
  107. ]
  108. .. _register_group_permission_panel:
  109. ``register_group_permission_panel``
  110. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  111. Add a new panel to the Groups form in the 'settings' area. The callable passed to this hook must return a ModelForm / ModelFormSet-like class, with a constructor that accepts a group object as its ``instance`` keyword argument, and which implements the methods ``save``, ``is_valid``, and ``as_admin_panel`` (which returns the HTML to be included on the group edit page).
  112. .. _register_settings_menu_item:
  113. ``register_settings_menu_item``
  114. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  115. As ``register_admin_menu_item``, but registers menu items into the 'Settings' sub-menu rather than the top-level menu.
  116. .. _register_admin_search_area:
  117. ``register_admin_search_area``
  118. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  119. Add an item to the Wagtail admin search "Other Searches". Behaviour of this hook is similar to ``register_admin_menu_item``. The callable passed to this hook must return an instance of ``wagtail.wagtailadmin.search.SearchArea``. New items can be constructed from the ``SearchArea`` class by passing the following parameters:
  120. :label: text displayed in the "Other Searches" option box.
  121. :name: an internal name used to identify the search option; defaults to the slugified form of the label.
  122. :url: the URL of the target search page.
  123. :classnames: additional CSS classnames applied to the link, used to give it an icon.
  124. :attrs: additional HTML attributes to apply to the link.
  125. :order: an integer which determines the item's position in the list of options.
  126. Setting the URL can be achieved using reverse() on the target search page. The GET parameter 'q' will be appended to the given URL.
  127. A template tag, ``search_other`` is provided by the ``wagtailadmin_tags`` template module. This tag takes a single, optional parameter, ``current``, which allows you to specify the ``name`` of the search option currently active. If the parameter is not given, the hook defaults to a reverse lookup of the page's URL for comparison against the ``url`` parameter.
  128. ``SearchArea`` can be subclassed to customise the HTML output, specify JavaScript files required by the option, or conditionally show or hide the item for specific requests (for example, to apply permission checks); see the source code (``wagtail/wagtailadmin/search.py``) for details.
  129. .. code-block:: python
  130. from django.core.urlresolvers import reverse
  131. from wagtail.wagtailcore import hooks
  132. from wagtail.wagtailadmin.search import SearchArea
  133. @hooks.register('register_admin_search_area')
  134. def register_frank_search_area():
  135. return SearchArea('Frank', reverse('frank'), classnames='icon icon-folder-inverse', order=10000)
  136. .. _register_permissions:
  137. ``register_permissions``
  138. ~~~~~~~~~~~~~~~~~~~~~~~~
  139. Return a queryset of ``Permission`` objects to be shown in the Groups administration area.
  140. .. _filter_form_submissions_for_user:
  141. ``filter_form_submissions_for_user``
  142. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  143. Allows access to form submissions to be customised on a per-user, per-form basis.
  144. This hook takes two parameters:
  145. - The user attempting to access form submissions
  146. - A ``QuerySet`` of form pages
  147. The hook must return a ``QuerySet`` containing a subset of these form pages which the user is allowed to access the submissions for.
  148. For example, to prevent non-superusers from accessing form submissions:
  149. .. code-block:: python
  150. from wagtail.wagtailcore import hooks
  151. @hooks.register('filter_form_submissions_for_user')
  152. def construct_forms_for_user(user, queryset):
  153. if not user.is_superuser:
  154. queryset = queryset.none()
  155. return queryset
  156. Editor interface
  157. ----------------
  158. Hooks for customising the editing interface for pages and snippets.
  159. .. _construct_whitelister_element_rules:
  160. ``construct_whitelister_element_rules``
  161. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  162. Customise the rules that define which HTML elements are allowed in rich text areas. By default only a limited set of HTML elements and attributes are whitelisted - all others are stripped out. The callables passed into this hook must return a dict, which maps element names to handler functions that will perform some kind of manipulation of the element. These handler functions receive the element as a `BeautifulSoup <http://www.crummy.com/software/BeautifulSoup/bs4/doc/>`_ Tag object.
  163. The ``wagtail.wagtailcore.whitelist`` module provides a few helper functions to assist in defining these handlers: ``allow_without_attributes``, a handler which preserves the element but strips out all of its attributes, and ``attribute_rule`` which accepts a dict specifying how to handle each attribute, and returns a handler function. This dict will map attribute names to either True (indicating that the attribute should be kept), False (indicating that it should be dropped), or a callable (which takes the initial attribute value and returns either a final value for the attribute, or None to drop the attribute).
  164. For example, the following hook function will add the ``<blockquote>`` element to the whitelist, and allow the ``target`` attribute on ``<a>`` elements:
  165. .. code-block:: python
  166. from wagtail.wagtailcore import hooks
  167. from wagtail.wagtailcore.whitelist import attribute_rule, check_url, allow_without_attributes
  168. @hooks.register('construct_whitelister_element_rules')
  169. def whitelister_element_rules():
  170. return {
  171. 'blockquote': allow_without_attributes,
  172. 'a': attribute_rule({'href': check_url, 'target': True}),
  173. }
  174. .. _insert_editor_css:
  175. ``insert_editor_css``
  176. ~~~~~~~~~~~~~~~~~~~~~
  177. Add additional CSS files or snippets to the page editor.
  178. .. code-block:: python
  179. from django.contrib.staticfiles.templatetags.staticfiles import static
  180. from django.utils.html import format_html
  181. from wagtail.wagtailcore import hooks
  182. @hooks.register('insert_editor_css')
  183. def editor_css():
  184. return format_html(
  185. '<link rel="stylesheet" href="{}">',
  186. static('demo/css/vendor/font-awesome/css/font-awesome.min.css')
  187. )
  188. .. _insert_global_admin_css:
  189. ``insert_global_admin_css``
  190. ~~~~~~~~~~~~~~~~~~~~~~~~~~~
  191. Add additional CSS files or snippets to all admin pages.
  192. .. code-block:: python
  193. from django.utils.html import format_html
  194. from django.contrib.staticfiles.templatetags.staticfiles import static
  195. from wagtail.wagtailcore import hooks
  196. @hooks.register('insert_global_admin_css')
  197. def global_admin_css():
  198. return format_html('<link rel="stylesheet" href="{}">', static('my/wagtail/theme.css'))
  199. .. _insert_editor_js:
  200. ``insert_editor_js``
  201. ~~~~~~~~~~~~~~~~~~~~
  202. Add additional JavaScript files or code snippets to the page editor.
  203. .. code-block:: python
  204. from django.utils.html import format_html, format_html_join
  205. from django.conf import settings
  206. from wagtail.wagtailcore import hooks
  207. @hooks.register('insert_editor_js')
  208. def editor_js():
  209. js_files = [
  210. 'demo/js/hallo-plugins/hallo-demo-plugin.js',
  211. ]
  212. js_includes = format_html_join('\n', '<script src="{0}{1}"></script>',
  213. ((settings.STATIC_URL, filename) for filename in js_files)
  214. )
  215. return js_includes + format_html(
  216. """
  217. <script>
  218. registerHalloPlugin('demoeditor');
  219. </script>
  220. """
  221. )
  222. .. _insert_global_admin_js:
  223. ``insert_global_admin_js``
  224. ~~~~~~~~~~~~~~~~~~~~~~~~~~
  225. Add additional JavaScript files or code snippets to all admin pages.
  226. .. code-block:: python
  227. from django.utils.html import format_html
  228. from wagtail.wagtailcore import hooks
  229. @hooks.register('insert_global_admin_js')
  230. def global_admin_js():
  231. return format_html(
  232. '<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r74/three.js"></script>',
  233. )
  234. Editor workflow
  235. ---------------
  236. Hooks for customising the way users are directed through the process of creating page content.
  237. .. _after_create_page:
  238. ``after_create_page``
  239. ~~~~~~~~~~~~~~~~~~~~~
  240. Do something with a ``Page`` object after it has been saved to the database (as a published page or a revision). The callable passed to this hook should take a ``request`` object and a ``page`` object. The function does not have to return anything, but if an object with a ``status_code`` property is returned, Wagtail will use it as a response object. By default, Wagtail will instead redirect to the Explorer page for the new page's parent.
  241. .. code-block:: python
  242. from django.http import HttpResponse
  243. from wagtail.wagtailcore import hooks
  244. @hooks.register('after_create_page')
  245. def do_after_page_create(request, page):
  246. return HttpResponse("Congrats on making content!", content_type="text/plain")
  247. .. _before_create_page:
  248. ``before_create_page``
  249. ~~~~~~~~~~~~~~~~~~~~~~
  250. Called at the beginning of the "create page" view passing in the request, the parent page and page model class.
  251. The function does not have to return anything, but if an object with a ``status_code`` property is returned, Wagtail will use it as a response object and skip the rest of the view.
  252. Unlike, ``after_create_page``, this is run both for both ``GET`` and ``POST`` requests.
  253. This can be used to completely override the editor on a per-view basis:
  254. .. code-block:: python
  255. from wagtail.wagtailcore import hooks
  256. from .models import AwesomePage
  257. from .admin_views import edit_awesome_page
  258. @hooks.register('before_create_page')
  259. def before_create_page(request, parent_page, page_class):
  260. # Use a custom create view for the AwesomePage model
  261. if page_class == AwesomePage:
  262. return create_awesome_page(request, parent_page)
  263. .. _after_delete_page:
  264. ``after_delete_page``
  265. ~~~~~~~~~~~~~~~~~~~~~
  266. Do something after a ``Page`` object is deleted. Uses the same behavior as ``after_create_page``.
  267. .. _before_delete_page:
  268. ``before_delete_page``
  269. ~~~~~~~~~~~~~~~~~~~~~~
  270. Called at the beginning of the "delete page" view passing in the request and the page object.
  271. Uses the same behavior as ``before_create_page``.
  272. .. _after_edit_page:
  273. ``after_edit_page``
  274. ~~~~~~~~~~~~~~~~~~~
  275. Do something with a ``Page`` object after it has been updated. Uses the same behavior as ``after_create_page``.
  276. .. _before_edit_page:
  277. ``before_edit_page``
  278. ~~~~~~~~~~~~~~~~~~~~~
  279. Called at the beginning of the "edit page" view passing in the request and the page object.
  280. Uses the same behavior as ``before_create_page``.
  281. .. _after_copy_page:
  282. ``after_copy_page``
  283. ~~~~~~~~~~~~~~~~~~~
  284. Do something with a ``Page`` object after it has been copied pasing in the request, page object and the new copied page. Uses the same behavior as ``after_create_page``.
  285. .. _before_copy_page:
  286. ``before_copy_page``
  287. ~~~~~~~~~~~~~~~~~~~~~
  288. Called at the beginning of the "copy page" view passing in the request and the page object.
  289. Uses the same behavior as ``before_create_page``.
  290. .. _construct_wagtail_userbar:
  291. ``construct_wagtail_userbar``
  292. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  293. .. versionchanged:: 1.0
  294. The hook was renamed from ``construct_wagtail_edit_bird``
  295. Add or remove items from the wagtail userbar. Add, edit, and moderation tools are provided by default. The callable passed into the hook must take the ``request`` object and a list of menu objects, ``items``. The menu item objects must have a ``render`` method which can take a ``request`` object and return the HTML string representing the menu item. See the userbar templates and menu item classes for more information.
  296. .. code-block:: python
  297. from wagtail.wagtailcore import hooks
  298. class UserbarPuppyLinkItem(object):
  299. def render(self, request):
  300. return '<li><a href="http://cuteoverload.com/tag/puppehs/" ' \
  301. + 'target="_parent" class="action icon icon-wagtail">Puppies!</a></li>'
  302. @hooks.register('construct_wagtail_userbar')
  303. def add_puppy_link_item(request, items):
  304. return items.append( UserbarPuppyLinkItem() )
  305. Choosers
  306. --------
  307. .. _construct_page_chooser_queryset:
  308. ``construct_page_chooser_queryset``
  309. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  310. Called when rendering the page chooser view, to allow the page listing queryset to be customised. The callable passed into the hook will receive the current page queryset and the request object, and must return a Page queryset (either the original one, or a new one).
  311. .. code-block:: python
  312. from wagtail.wagtailcore import hooks
  313. @hooks.register('construct_page_chooser_queryset')
  314. def show_my_pages_only(pages, request):
  315. # Only show own pages
  316. pages = pages.filter(owner=request.user)
  317. return pages
  318. .. _construct_document_chooser_queryset:
  319. ``construct_document_chooser_queryset``
  320. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  321. Called when rendering the document chooser view, to allow the document listing queryset to be customised. The callable passed into the hook will receive the current document queryset and the request object, and must return a Document queryset (either the original one, or a new one).
  322. .. code-block:: python
  323. from wagtail.wagtailcore import hooks
  324. @hooks.register('construct_document_chooser_queryset')
  325. def show_my_uploaded_documents_only(documents, request):
  326. # Only show uploaded documents
  327. documents = documents.filter(uploaded_by=request.user)
  328. return documents
  329. .. _construct_image_chooser_queryset:
  330. ``construct_image_chooser_queryset``
  331. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  332. Called when rendering the image chooser view, to allow the image listing queryset to be customised. The callable passed into the hook will receive the current image queryset and the request object, and must return a Document queryset (either the original one, or a new one).
  333. .. code-block:: python
  334. from wagtail.wagtailcore import hooks
  335. @hooks.register('construct_image_chooser_queryset')
  336. def show_my_uploaded_images_only(images, request):
  337. # Only show uploaded images
  338. images = images.filter(uploaded_by=request.user)
  339. return images
  340. Page explorer
  341. -------------
  342. .. _construct_explorer_page_queryset:
  343. ``construct_explorer_page_queryset``
  344. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  345. Called when rendering the page explorer view, to allow the page listing queryset to be customised. The callable passed into the hook will receive the parent page object, the current page queryset, and the request object, and must return a Page queryset (either the original one, or a new one).
  346. .. code-block:: python
  347. from wagtail.wagtailcore import hooks
  348. @hooks.register('construct_explorer_page_queryset')
  349. def show_my_profile_only(parent_page, pages, request):
  350. # If we're in the 'user-profiles' section, only show the user's own profile
  351. if parent_page.slug == 'user-profiles':
  352. pages = pages.filter(owner=request.user)
  353. return pages
  354. .. _register_page_listing_buttons:
  355. ``register_page_listing_buttons``
  356. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  357. Add buttons to the actions list for a page in the page explorer. This is useful when adding custom actions to the listing, such as translations or a complex workflow.
  358. This example will add a simple button to the listing:
  359. .. code-block:: python
  360. from wagtail.wagtailadmin import widgets as wagtailadmin_widgets
  361. @hooks.register('register_page_listing_buttons')
  362. def page_listing_buttons(page, page_perms, is_parent=False):
  363. yield wagtailadmin_widgets.PageListingButton(
  364. 'A page listing button',
  365. '/goes/to/a/url/',
  366. priority=10
  367. )
  368. The ``priority`` argument controls the order the buttons are displayed in. Buttons are ordered from low to high priority, so a button with ``priority=10`` will be displayed before a button with ``priority=20``.
  369. .. register_page_listing_more_buttons:
  370. ``register_page_listing_more_buttons``
  371. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  372. Add buttons to the "More" dropdown menu for a page in the page explorer. This works similarly to the ``register_page_listing_buttons`` hook but is useful for lesser-used custom actions that are better suited for the dropdown.
  373. This example will add a simple button to the dropdown menu:
  374. .. code-block:: python
  375. from wagtail.wagtailadmin import widgets as wagtailadmin_widgets
  376. @hooks.register('register_page_listing_more_buttons')
  377. def page_listing_more_buttons(page, page_perms, is_parent=False):
  378. yield wagtailadmin_widgets.PageListingButton(
  379. 'A dropdown button',
  380. '/goes/to/a/url/',
  381. priority=10
  382. )
  383. The ``priority`` argument controls the order the buttons are displayed in the dropdown. Buttons are ordered from low to high priority, so a button with ``priority=10`` will be displayed before a button with ``priority=20``.
  384. Buttons with dropdown lists
  385. ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  386. The admin widgets also provide ``ButtonWithDropdownFromHook``, which allows you to define a custom hook for generating a dropdown menu that gets attached to your button.
  387. Creating a button with a dropdown menu involves two steps. Firstly, you add your button to the ``register_page_listing_buttons`` hook, just like the example above.
  388. Secondly, you register a new hook that yields the contents of the dropdown menu.
  389. This example shows how Wagtail's default admin dropdown is implemented. You can also see how to register buttons conditionally, in this case by evaluating the ``page_perms``:
  390. .. code-block:: python
  391. @hooks.register('register_page_listing_buttons')
  392. def page_custom_listing_buttons(page, page_perms, is_parent=False):
  393. yield wagtailadmin_widgets.ButtonWithDropdownFromHook(
  394. 'More actions',
  395. hook_name='my_button_dropdown_hook',
  396. page=page,
  397. page_perms=page_perms,
  398. is_parent=is_parent,
  399. priority=50
  400. )
  401. @hooks.register('my_button_dropdown_hook')
  402. def page_custom_listing_more_buttons(page, page_perms, is_parent=False):
  403. if page_perms.can_move():
  404. yield Button('Move', reverse('wagtailadmin_pages:move', args=[page.id]), priority=10)
  405. if page_perms.can_delete():
  406. yield Button('Delete', reverse('wagtailadmin_pages:delete', args=[page.id]), priority=30)
  407. if page_perms.can_unpublish():
  408. yield Button('Unpublish', reverse('wagtailadmin_pages:unpublish', args=[page.id]), priority=40)
  409. The template for the dropdown button can be customised by overriding ``wagtailadmin/pages/listing/_button_with_dropdown.html``. The JavaScript that runs the dropdowns makes use of custom data attributes, so you should leave ``data-dropdown`` and ``data-dropdown-toggle`` in the markup if you customise it.
  410. Page serving
  411. ------------
  412. .. _before_serve_page:
  413. ``before_serve_page``
  414. ~~~~~~~~~~~~~~~~~~~~~
  415. Called when Wagtail is about to serve a page. The callable passed into the hook will receive the page object, the request object, and the ``args`` and ``kwargs`` that will be passed to the page's ``serve()`` method. If the callable returns an ``HttpResponse``, that response will be returned immediately to the user, and Wagtail will not proceed to call ``serve()`` on the page.
  416. .. code-block:: python
  417. from wagtail.wagtailcore import hooks
  418. @hooks.register('before_serve_page')
  419. def block_googlebot(page, request, serve_args, serve_kwargs):
  420. if request.META.get('HTTP_USER_AGENT') == 'GoogleBot':
  421. return HttpResponse("<h1>bad googlebot no cookie</h1>")