Skip to content


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added signal which is fired when server needs restart

As we all know, adding, removing or changing apphook pages requires a
server restart. Until now, the maintainer/content-editors of a site
had to do and detect this manually.

I propose we add a signal ``cms.signals.urls_need_reloading`` which
fires when the CMS detects the server needs a restart.

This signal is just sent by the django CMS. The core itself does nothing
when this signal is sent. However it allows developers to write custom
listeners to that signal that take an appropriate action in their

For some common use cases, there might even be generic implementations
(outside djagno-cms core) which solve this issue (eg killing os.getppid
in single-server gunicorn systems).
  • Loading branch information...
commit 04022df04ffea0fe0a74344eae9c7bf166ceb486 1 parent 96003df
Jonas Obrist authored
32 cms/
@@ -20,6 +20,8 @@
# fired after page gets published - copied to public model - there may be more
# than one instances published before this signal gets called
post_publish = Signal(providing_args=["instance"])
+urls_need_reloading = Signal(providing_args=[])
def update_plugin_positions(**kwargs):
plugin = kwargs['instance']
@@ -285,3 +287,33 @@ def pre_save_delete_page(instance, **kwargs):
signals.pre_save.connect(pre_save_delete_page, sender=Page)
signals.pre_delete.connect(pre_save_delete_page, sender=Page)
+def apphook_pre_checker(instance, **kwargs):
+ """
+ Store the old application_urls and path on the instance
+ """
+ try:
+ instance._old_data = Title.objects.filter('application_urls', 'path')[0]
+ except IndexError:
+ instance._old_data = (None, None)
+def apphook_post_checker(instance, **kwargs):
+ """
+ Check if applciation_urls and path changed on the instance
+ """
+ old_apps, old_path = getattr(instance, '_old_data', (None, None))
+ if old_apps != instance.application_urls:
+ urls_need_reloading.send(sender=instance)
+ elif old_path != instance.path and instance.application_urls:
+ urls_need_reloading.send(sender=instance)
+def apphook_post_delete_checker(instance, **kwargs):
+ """
+ Check if this was an apphook
+ """
+ if instance.application_urls:
+ urls_need_reloading.send(sender=instance)
+signals.pre_save.connect(apphook_pre_checker, sender=Title)
+signals.post_save.connect(apphook_post_checker, sender=Title)
+signals.post_delete.connect(apphook_post_delete_checker, sender=Title)
3  cms/tests/
@@ -30,4 +30,5 @@
from import *
from cms.tests.menu_page_viewperm import *
from cms.tests.menu_page_viewperm_staff import *
-from cms.tests.nested_plugins import *
+from cms.tests.nested_plugins import *
+from cms.tests.signals import *
44 cms/tests/
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+from contextlib import contextmanager
+from cms.api import create_page
+from cms.signals import urls_need_reloading
+from django.contrib.auth.models import User
+from django.test import TestCase
+class SignalTester(object):
+ def __init__(self):
+ self.call_count = 0
+ self.calls = []
+ def __call__(self, *args, **kwargs):
+ self.call_count += 1
+ self.calls.append((args, kwargs))
+def signal_tester(signal):
+ env = SignalTester()
+ signal.connect(env, weak=True)
+ try:
+ yield env
+ finally:
+ signal.disconnect(env, weak=True)
+class SignalTests(TestCase):
+ def test_urls_need_reloading_signal_create(self):
+ superuser = User.objects.create_superuser('admin', '', 'admin')
+ with signal_tester(urls_need_reloading) as env:
+ create_page("apphooked-page", "nav_playground.html", "en",
+ created_by=superuser, published=True, apphook="SampleApp")
+ self.assertEqual(env.call_count, 1)
+ def test_urls_need_reloading_signal_delete(self):
+ superuser = User.objects.create_superuser('admin', '', 'admin')
+ page = create_page("apphooked-page", "nav_playground.html", "en",
+ created_by=superuser, published=True, apphook="SampleApp")
+ with signal_tester(urls_need_reloading) as env:
+ page.delete()
+ self.assertEqual(env.call_count, 1)
68 docs/extending_cms/app_integration.rst
@@ -56,34 +56,34 @@ The get_nodes function should return a list of
:class:`NavigationNode <menus.base.NavigationNode>` instances. A
:class:`NavigationNode` takes the following arguments:
-- title
+- ``title``
What the menu entry should read as
-- url,
+- ``url``,
Link if menu entry is clicked.
-- id
+- ``id``
A unique id for this menu.
-- parent_id=None
+- ``parent_id=None``
If this is a child of another node supply the the id of the parent here.
-- parent_namespace=None
+- ``parent_namespace=None``
If the parent node is not from this menu you can give it the parent
namespace. The namespace is the name of the class. In the above example that
would be: "TestMenu"
-- attr=None
+- ``attr=None``
A dictionary of additional attributes you may want to use in a modifier or
in the template.
-- visible=True
+- ``visible=True``
Whether or not this menu item should be visible.
@@ -177,10 +177,9 @@ under "Application". Save the page.
.. warning::
- If you are on a multi-threaded server (mostly all webservers,
- except the dev-server): Restart the server because the URLs are cached by
- Django and in a multi-threaded environment we don't know which caches are
- cleared yet.
+ Whenever you add or remove an apphook, change the slug of a page containing
+ an apphook or the slug if a page which has a descendant with an apphook,
+ you have to restart your server to re-load the URL caches.
.. note::
@@ -212,7 +211,7 @@ The ``main_view`` should now be available at ``/hello/world/`` and the
Apphook Menus
If you want to add a menu to that page as well that may represent some views
in your app add it to your apphook like this::
@@ -290,7 +289,7 @@ You get the static entries of :class:`MyAppMenu` and the dynamic entries of
Application and instance namespaces
If you'd like to use application namespaces to reverse the URLs related to
your app, you can assign a value to the `app_name` attribute of your app
@@ -310,7 +309,7 @@ As seen for Language Namespaces, you can reverse namespaced apps similarly:
{% url myapp_namespace:app_main %}
If you want to access the same url but in a different language use the language
+template tag:
.. code-block:: html+django
@@ -326,7 +325,7 @@ assigned to a page and uses it as instance namespace for the app hook.
To reverse the URLs you now have two different ways: explicitly by defining
the instance namespace, or implicitely by specifiyng the application namespace
-and letting the `url` templatetag resolving the correct application instance
+and letting the `url` template tag resolving the correct application instance
by looking at the currently set `current_app` value.
.. note::
@@ -340,19 +339,44 @@ as an argument. You can do so by looking up the `current_app` attribute of
the request instance::
def myviews(request):
- ...
+ # ...
reversed_url = reverse('myapp_namespace:app_main',
- ...
+ #...
Or, if you are rendering a plugin, of the context instance::
class MyPlugin(CMSPluginBase):
def render(self, context, instance, placeholder):
- ...
+ # ...
reversed_url = reverse('myapp_namespace:app_main',
- ...
+ # ...
+Automatically restart server on apphook changes
+As mentioned above, whenever you add or remove an apphook, change the slug of a
+page containing an apphook or the slug if a page which has a descendant with an
+apphook, you have to restart your server to re-load the URL caches. To allow
+you to automate this process, the django CMS provides a signal
+:obj:`cms.signals.urls_need_reloading` which you can listen on to detect when
+your server needs restarting.
+.. warning::
+ This signal does not actually do anything. To get automated server
+ restarting you need to implement logic in your project that gets
+ executed whenever this signal is fired. Because there are many ways of
+ deploying Django applications, there is no way we can provide a generic
+ solution for this problem that will always work.
+.. warning::
+ The signal is fired **during** the request, that caused this change. If
+ you restart the server, you should delay the restart to not interrupt the
+ request currently being handled.
@@ -366,7 +390,7 @@ menus.
An example use-case
A simple example: you have a news application that publishes pages
independently of django CMS. However, you would like to integrate the
@@ -377,7 +401,7 @@ In such a case, a Navigation Modifier is the solution.
How it works
Normally, you'd want to place modifiers in your application's
@@ -473,6 +497,8 @@ Here is an example of a built-in modifier that marks all node levels::
Custom Plugins
Please sign in to comment.
Something went wrong with that request. Please try again.