thclark
thclark

Reputation: 5502

Can I trigger an action in python from a wagtail page button

In wagtail, it's possible to add a button to a page, using the register_page_listing_buttons hook.

However, those examples simply take you to a url of your choosing.

In my case, I have a ProjectPage model, and I have a function regenerate_project_geo_features() (presently a management command, although it doesn't need to be).

I want a button, either in the page listing or in the page edit view itself, which can be used to trigger an action in python.

Is it possible to adapt that hook, or use another that I'm not aware of, to simply call a function in python? (preferably with some parameter like a ProjectPage id to tell the function what page it was called on)?

Upvotes: 2

Views: 1019

Answers (1)

LB Ben Johnston
LB Ben Johnston

Reputation: 5196

A way to do this would be with Javascript, essentially adding a listener to the link/button being clicked. This would then trigger a POST request of some kind to a URL you manage which would do the 'work' of the regeneration.

Essentially as this is a web framework (Django), everything should be considered as a bunch of views (pages) with request/response handling.

Approach

  • Create a custom admin URL & view (intentionally accessible via admin only)
  • This view would basically handle POST requests and when called with the id of the page, it will do the work you require.
  • The link now becomes a button in purpose only, clicking it will call with Javascript fetch (happens in the background)
  • You can then update your view to do whatever you want to the page in the background.
  • Note: example is rough and does not have any error handling or messaging to the user (ie. when they click it, they do not know if it has worked/completed etc).

Example Code

  • This would be in your wagtail_hooks.py file
from django.conf.urls import url
from django.http import HttpResponse
from django.urls import reverse
from django.utils.html import format_html
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import csrf_exempt

from wagtail.admin.widgets import PageListingButton
from wagtail.core import hooks


@csrf_exempt # not recommended - but helpful to get to the POC stage
@require_http_methods(["POST"])
def regnerate_admin_features(request):
    page_pk = request.GET.get('id', '')
    # do whatever you need here with the PK to process the action

    return HttpResponse(
        "Success/Error handling goes here",
        content_type="text/plain")

@hooks.register('register_admin_urls')
def urlconf_time():
  return [
    url(r'^regenerate_geo_features/$', regnerate_admin_features, name='regenerate_geo_features'),
  ]

@hooks.register('register_page_listing_buttons')
def page_listing_buttons(page, page_perms, is_parent=False):

    attrs = {
        'data-id': page.pk, # note - may want to html encode this for a more secure implementation
    }

    yield PageListingButton(
        'Regenerate Geo Features',
        reverse('regenerate_geo_features'),
        attrs=attrs,
        classes=["action-regenerate-geo-features"],
        priority=100
    )

@hooks.register('insert_global_admin_js')
def global_admin_js():
    # note - this is very rough, no error, loading or sucess messaging
    # reminder - using format_html means all `{` must be written as `{{`
    return format_html(
        """
        <script>
        const onClickHandler = function(event) {{
            event.preventDefault(); // ensure the hash does not change
            const url = event.target.href + '?id=' + event.target.dataset.id;
            console.log('button clicked - about to POST to URL:', url);
            fetch(url, {{
                method: 'POST', // or 'PUT'
            }})
        }};

        window.addEventListener('DOMContentLoaded', function(event) {{
            const actionButtons = Array.from(document.getElementsByClassName('action-regenerate-geo-features'));

            actionButtons.forEach(function(element) {{
                element.addEventListener('click', onClickHandler);
            }});
        }});
        </script>
        """,
    )

Upvotes: 2

Related Questions