From ac7ad067d2b10f44a8dbabd0e3f7afb64345aebd Mon Sep 17 00:00:00 2001 From: Gagaro Date: Sun, 5 Feb 2017 10:01:35 +0100 Subject: [PATCH] feat: can order hooks --- CHANGELOG.txt | 2 ++ docs/reference/hooks.rst | 15 ++++++++++ docs/releases/1.10.rst | 3 +- wagtail/tests/utils.py | 6 ++-- wagtail/wagtailcore/hooks.py | 13 ++++++--- wagtail/wagtailcore/tests/test_hooks.py | 38 +++++++++++++++++++++++++ 6 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 wagtail/wagtailcore/tests/test_hooks.py diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 6d7aaee361..cd287cd308 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -5,6 +5,8 @@ Changelog ~~~~~~~~~~~~~~~~~ * Use minified versions of jQuery and jQuery UI in the admin. Total savings without compression 371 KB (Tom Dyson) + * Hooks can now specify the order in which they are run (Gagaro) + 1.9 (xx.xx.xxxx) - IN DEVELOPMENT ~~~~~~~~~~~~~~~~ diff --git a/docs/reference/hooks.rst b/docs/reference/hooks.rst index 97772c8aa0..df6894da52 100644 --- a/docs/reference/hooks.rst +++ b/docs/reference/hooks.rst @@ -23,6 +23,21 @@ Alternatively, ``hooks.register`` can be called as an ordinary function, passing hooks.register('name_of_hook', my_hook_function) +If you need your hooks to run in a particular order, you can pass the ``order`` parameter: + +.. code-block:: python + + @hooks.register('name_of_hook', order=1) # This will run after every hook in the wagtail core + def my_hook_function(arg1, arg2...) + # your code here + + @hooks.register('name_of_hook', order=-1) # This will run before every hook in the wagtail core + def my_other_hook_function(arg1, arg2...) + # your code here + + @hooks.register('name_of_hook', order=2) # This will run after `my_hook_function` + def yet_another_hook_function(arg1, arg2...) + # your code here The available hooks are listed below. diff --git a/docs/releases/1.10.rst b/docs/releases/1.10.rst index 178448a875..fc154d7c01 100644 --- a/docs/releases/1.10.rst +++ b/docs/releases/1.10.rst @@ -14,7 +14,8 @@ What's new Other features ~~~~~~~~~~~~~~ -- Use minified versions of jQuery and jQuery UI in the admin. Total savings without compression 371 KB (Tom Dyson) + * Use minified versions of jQuery and jQuery UI in the admin. Total savings without compression 371 KB (Tom Dyson) + * Hooks can now specify the order in which they are run (Gagaro) Bug fixes ~~~~~~~~~ diff --git a/wagtail/tests/utils.py b/wagtail/tests/utils.py index 3e2d4bb61c..da90f80547 100644 --- a/wagtail/tests/utils.py +++ b/wagtail/tests/utils.py @@ -104,14 +104,14 @@ class WagtailTestUtils(object): return _AssertLogsContext(self, logger, level) @contextmanager - def register_hook(self, hook_name, fn): + def register_hook(self, hook_name, fn, order=0): from wagtail.wagtailcore import hooks - hooks.register(hook_name, fn) + hooks.register(hook_name, fn, order) try: yield finally: - hooks.get_hooks(hook_name).remove(fn) + hooks._hooks[hook_name].remove((fn, order)) class WagtailPageTests(WagtailTestUtils, TestCase): diff --git a/wagtail/wagtailcore/hooks.py b/wagtail/wagtailcore/hooks.py index db8b72d029..741afe7ce5 100644 --- a/wagtail/wagtailcore/hooks.py +++ b/wagtail/wagtailcore/hooks.py @@ -1,11 +1,13 @@ from __future__ import absolute_import, unicode_literals +from operator import itemgetter + from wagtail.utils.apps import get_app_submodules _hooks = {} -def register(hook_name, fn=None): +def register(hook_name, fn=None, order=0): """ Register hook for ``hook_name``. Can be used as a decorator:: @@ -23,13 +25,13 @@ def register(hook_name, fn=None): # Pretend to be a decorator if fn is not supplied if fn is None: def decorator(fn): - register(hook_name, fn) + register(hook_name, fn, order=order) return fn return decorator if hook_name not in _hooks: _hooks[hook_name] = [] - _hooks[hook_name].append(fn) + _hooks[hook_name].append((fn, order)) _searched_for_hooks = False @@ -43,5 +45,8 @@ def search_for_hooks(): def get_hooks(hook_name): + """ Return the hooks function sorted by their order. """ search_for_hooks() - return _hooks.get(hook_name, []) + hooks = _hooks.get(hook_name, []) + hooks = sorted(hooks, key=itemgetter(1)) + return [hook[0] for hook in hooks] diff --git a/wagtail/wagtailcore/tests/test_hooks.py b/wagtail/wagtailcore/tests/test_hooks.py new file mode 100644 index 0000000000..e42202ddcc --- /dev/null +++ b/wagtail/wagtailcore/tests/test_hooks.py @@ -0,0 +1,38 @@ +from __future__ import absolute_import, unicode_literals + +from django.test import TestCase + +from wagtail.tests.utils import WagtailTestUtils +from wagtail.wagtailcore import hooks + + +def test_hook(): + pass + + +class TestLoginView(TestCase, WagtailTestUtils): + fixtures = ['test.json'] + + @classmethod + def setUpClass(cls): + hooks.register('test_hook_name', test_hook) + + @classmethod + def tearDownClass(cls): + del hooks._hooks['test_hook_name'] + + def test_before_hook(self): + def before_hook(): + pass + + with self.register_hook('test_hook_name', before_hook, order=-1): + hook_fns = hooks.get_hooks('test_hook_name') + self.assertEqual(hook_fns, [before_hook, test_hook]) + + def test_after_hook(self): + def after_hook(): + pass + + with self.register_hook('test_hook_name', after_hook, order=1): + hook_fns = hooks.get_hooks('test_hook_name') + self.assertEqual(hook_fns, [test_hook, after_hook])