From 9c27095744d93740196f9bc01cbd7d9e59bf6829 Mon Sep 17 00:00:00 2001 From: Richard Darst Date: Tue, 22 May 2018 21:23:13 +0300 Subject: [PATCH 1/5] Add customizable announcement text on home,login,logout,spawn - Using the new template_vars setting (#1872), allow the variable `announcement` to create a header message on all the pages in the title, or the variables `announcement_{home,login,logout,spawn}` to set variables on these single pages. - This is not the most powerful method of putting an announcement into the templates, because it requires a server restart to change. But the invasiveness is very low, and allows minimal message without having to touch the templates themselves. - Closes: #1836 --- docs/source/reference/templates.md | 30 ++++++++++++++++++++++++++ share/jupyterhub/templates/home.html | 1 + share/jupyterhub/templates/login.html | 1 + share/jupyterhub/templates/logout.html | 4 ++++ share/jupyterhub/templates/page.html | 10 +++++++++ share/jupyterhub/templates/spawn.html | 1 + 6 files changed, 47 insertions(+) diff --git a/docs/source/reference/templates.md b/docs/source/reference/templates.md index f97b6fe7..76240261 100644 --- a/docs/source/reference/templates.md +++ b/docs/source/reference/templates.md @@ -59,3 +59,33 @@ text about the server starting up, place this content in a file named

Patience is a virtue.

{% endblock %} ``` + +## Simple configuration + +The most powerful way to control templates is to directly extend them +as you see above. However, some simple configuration is possible: + +### Announcement text + +If you set the configuration variable `JupyterHub.template_vars = +{'announcement': 'some_text}`, the given `some_text` will be placed on +the top of all pages. The more specific variables +`announcement_login`, `announcement_spawn`, `announcement_home`, and +`announcement_logout` are more specific and only show on their +respective pages (overriding the global `announcement` variable). +Note that changing these varables require a restart, unlike direct +template extension. + +You can get the same effect by extending templates, which allows you +to update the messages without restarting. Set +`c.JupyterHub.template_paths` as mentioned above, and then create a +template (for example, `login.html`) with: + +```html +{% extends "templates/login.html" %} +{% set announcement = 'some message' %} +``` + +Extending `page.html` puts the message on all pages, but note that +extending `page.html` take precedence over an extension of a specific +page (unlike the variable-based approach above). diff --git a/share/jupyterhub/templates/home.html b/share/jupyterhub/templates/home.html index 9d782898..df8eaaed 100644 --- a/share/jupyterhub/templates/home.html +++ b/share/jupyterhub/templates/home.html @@ -1,4 +1,5 @@ {% extends "page.html" %} +{% if announcement_home %}{% set announcement = announcement_home %}{% endif %} {% block main %} diff --git a/share/jupyterhub/templates/login.html b/share/jupyterhub/templates/login.html index 6f4b4e75..c5f5936a 100644 --- a/share/jupyterhub/templates/login.html +++ b/share/jupyterhub/templates/login.html @@ -1,4 +1,5 @@ {% extends "page.html" %} +{% if announcement_login %}{% set announcement = announcement_login %}{% endif %} {% block login_widget %} {% endblock %} diff --git a/share/jupyterhub/templates/logout.html b/share/jupyterhub/templates/logout.html index 53248c90..95d65017 100644 --- a/share/jupyterhub/templates/logout.html +++ b/share/jupyterhub/templates/logout.html @@ -1,8 +1,12 @@ {% extends "page.html" %} +{% if announcement_logout %}{% set announcement = announcement_logout %}{% endif %} + {% block main %} +

Successfully logged out.

+ {% endblock %} diff --git a/share/jupyterhub/templates/page.html b/share/jupyterhub/templates/page.html index 16357434..80472cac 100644 --- a/share/jupyterhub/templates/page.html +++ b/share/jupyterhub/templates/page.html @@ -140,6 +140,16 @@ {% endblock %} + +{% block announcement %} +{% if announcement %} +
+ {{ announcement | safe }} +
+{% endif %} +{% endblock %} + + {% block main %} {% endblock %} diff --git a/share/jupyterhub/templates/spawn.html b/share/jupyterhub/templates/spawn.html index 1fa4721c..4e2cb497 100644 --- a/share/jupyterhub/templates/spawn.html +++ b/share/jupyterhub/templates/spawn.html @@ -1,4 +1,5 @@ {% extends "page.html" %} +{% if announcement_spawn %}{% set announcement = announcement_spawn %}{% endif %} {% block main %} From e7808b50afd7bdd20be6c36ec21c02aeb2c4c22c Mon Sep 17 00:00:00 2001 From: Richard Darst Date: Sun, 27 May 2018 17:47:34 +0300 Subject: [PATCH 2/5] Add tests of page announcements - Adds test_pages.py:test_page_contents, which currently tests just the page annoucement variables. --- jupyterhub/tests/test_pages.py | 81 ++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/jupyterhub/tests/test_pages.py b/jupyterhub/tests/test_pages.py index b5995eb6..01dad21a 100644 --- a/jupyterhub/tests/test_pages.py +++ b/jupyterhub/tests/test_pages.py @@ -517,3 +517,84 @@ def test_oauth_token_page(app): def test_proxy_error(app, error_status): r = yield get_page('/error/%i' % error_status, app) assert r.status_code == 200 + + + +@pytest.mark.gen_test +def test_page_contents(app): + """Tests the contents of various pages. + + Currently includes tests for the template_vars variables: + announcement, announcement_login, announcement_home, + announcement_spawn, and announcement_logout. + + Other simple template tests could be put here. + """ + # Basic announcements - same on all pages + ann01 = 'ANNOUNCE01' + ann02 = 'ANNOUNCE02' + with mock.patch.dict(app.users.settings, + {'template_vars': {'announcement': ann01}, + 'spawner_class': FormSpawner}): + r = yield get_page('login', app) + r.raise_for_status() + assert ann01 in r.text + cookies = yield app.login_user('jones') + r = yield get_page('spawn', app, cookies=cookies) + r.raise_for_status() + assert ann01 in r.text + r = yield get_page('home', app, cookies=cookies) # hub/home + r.raise_for_status() + assert ann01 in r.text + r = yield get_page('logout', app, cookies=cookies) + r.raise_for_status() + assert ann01 in r.text + + # Different annoncements on one page + with mock.patch.dict(app.users.settings, + {'template_vars': {'announcement': ann01, + 'announcement_spawn': ann02}, + 'spawner_class': FormSpawner}): + r = yield get_page('login', app) + r.raise_for_status() + assert ann01 in r.text + assert ann02 not in r.text + + cookies = yield app.login_user('jones') + r = yield get_page('spawn', app, cookies=cookies) + r.raise_for_status() + assert ann01 not in r.text + assert ann02 in r.text + + # Different annoucements on all pages. Must give formspawner to + # ensure we get a message on the spawn page + with mock.patch.dict(app.users.settings, + {'template_vars': {'announcement': ann01, + 'announcement_spawn': 'ANN_spawn', + 'announcement_home': 'ANN_home', + 'announcement_login': 'ANN_login'}, + 'spawner_class': FormSpawner + }): + r = yield get_page('login', app) + assert 'ANN_login' in r.text + assert ann01 not in r.text + + cookies = yield app.login_user('jones') + r = yield get_page('spawn', app, cookies=cookies) + assert 'ANN_spawn' in r.text + assert ann01 not in r.text + + r = yield get_page('home', app, cookies=cookies) # hub/home + assert 'ANN_home' in r.text + assert ann01 not in r.text + # ... and must have auto_login=True in order to not be immediately + # redirected from the logout page: + with mock.patch.dict(app.users.settings, + {'template_vars': {'announcement': ann01, + 'announcement_logout': 'ANN_logout'}, + 'authenticator': Authenticator(auto_login=True), + 'spawner_class': FormSpawner + }): + r = yield get_page('logout', app, cookies=cookies) + assert 'ANN_logout' in r.text + assert ann01 not in r.text From 5f91ed044ef7d309d525dea385a6c1a37e16621e Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 12 Jun 2018 13:47:55 +0200 Subject: [PATCH 3/5] parametrize test_announcements --- jupyterhub/tests/test_pages.py | 116 +++++++++++++-------------------- 1 file changed, 44 insertions(+), 72 deletions(-) diff --git a/jupyterhub/tests/test_pages.py b/jupyterhub/tests/test_pages.py index 01dad21a..5cada932 100644 --- a/jupyterhub/tests/test_pages.py +++ b/jupyterhub/tests/test_pages.py @@ -17,6 +17,7 @@ from .mocking import FormSpawner, public_url, public_host from .test_api import api_request, add_user from .utils import async_requests + def get_page(path, app, hub=True, **kw): if hub: prefix = app.hub.base_url @@ -519,82 +520,53 @@ def test_proxy_error(app, error_status): assert r.status_code == 200 - @pytest.mark.gen_test -def test_page_contents(app): - """Tests the contents of various pages. +@pytest.mark.parametrize( + "announcements", + [ + "", + "spawn", + "spawn,home,login", + "login,logout", + ] +) +def test_announcements(app, announcements): + """Test announcements on various pages""" + # Default announcement - same on all pages + ann01 = "ANNOUNCE01" + template_vars = {"announcement": ann01} + announcements = announcements.split(",") + for name in announcements: + template_vars["announcement_" + name] = "ANN_" + name - Currently includes tests for the template_vars variables: - announcement, announcement_login, announcement_home, - announcement_spawn, and announcement_logout. + def assert_announcement(name, text): + if name in announcements: + assert template_vars["announcement_" + name] in text + assert ann01 not in text + else: + assert ann01 in text - Other simple template tests could be put here. - """ - # Basic announcements - same on all pages - ann01 = 'ANNOUNCE01' - ann02 = 'ANNOUNCE02' - with mock.patch.dict(app.users.settings, - {'template_vars': {'announcement': ann01}, - 'spawner_class': FormSpawner}): - r = yield get_page('login', app) + cookies = yield app.login_user("jones") + + with mock.patch.dict( + app.tornado_settings, + {"template_vars": template_vars, "spawner_class": FormSpawner}, + ): + r = yield get_page("login", app) r.raise_for_status() - assert ann01 in r.text - cookies = yield app.login_user('jones') - r = yield get_page('spawn', app, cookies=cookies) + assert_announcement("login", r.text) + r = yield get_page("spawn", app, cookies=cookies) r.raise_for_status() - assert ann01 in r.text - r = yield get_page('home', app, cookies=cookies) # hub/home + assert_announcement("spawn", r.text) + r = yield get_page("home", app, cookies=cookies) # hub/home r.raise_for_status() - assert ann01 in r.text - r = yield get_page('logout', app, cookies=cookies) + assert_announcement("home", r.text) + # need auto_login=True to get logout page + auto_login = app.authenticator.auto_login + app.authenticator.auto_login = True + try: + r = yield get_page("logout", app, cookies=cookies) + finally: + app.authenticator.auto_login = auto_login r.raise_for_status() - assert ann01 in r.text - - # Different annoncements on one page - with mock.patch.dict(app.users.settings, - {'template_vars': {'announcement': ann01, - 'announcement_spawn': ann02}, - 'spawner_class': FormSpawner}): - r = yield get_page('login', app) - r.raise_for_status() - assert ann01 in r.text - assert ann02 not in r.text - - cookies = yield app.login_user('jones') - r = yield get_page('spawn', app, cookies=cookies) - r.raise_for_status() - assert ann01 not in r.text - assert ann02 in r.text - - # Different annoucements on all pages. Must give formspawner to - # ensure we get a message on the spawn page - with mock.patch.dict(app.users.settings, - {'template_vars': {'announcement': ann01, - 'announcement_spawn': 'ANN_spawn', - 'announcement_home': 'ANN_home', - 'announcement_login': 'ANN_login'}, - 'spawner_class': FormSpawner - }): - r = yield get_page('login', app) - assert 'ANN_login' in r.text - assert ann01 not in r.text - - cookies = yield app.login_user('jones') - r = yield get_page('spawn', app, cookies=cookies) - assert 'ANN_spawn' in r.text - assert ann01 not in r.text - - r = yield get_page('home', app, cookies=cookies) # hub/home - assert 'ANN_home' in r.text - assert ann01 not in r.text - # ... and must have auto_login=True in order to not be immediately - # redirected from the logout page: - with mock.patch.dict(app.users.settings, - {'template_vars': {'announcement': ann01, - 'announcement_logout': 'ANN_logout'}, - 'authenticator': Authenticator(auto_login=True), - 'spawner_class': FormSpawner - }): - r = yield get_page('logout', app, cookies=cookies) - assert 'ANN_logout' in r.text - assert ann01 not in r.text + assert_announcement("logout", r.text) From d22626906b67a6f6409219509a8802040c61bbde Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 12 Jun 2018 13:48:24 +0200 Subject: [PATCH 4/5] multiline conditionals setting announcement variable in templates for readability per review by willingc --- share/jupyterhub/templates/home.html | 4 +++- share/jupyterhub/templates/login.html | 4 +++- share/jupyterhub/templates/logout.html | 4 +++- share/jupyterhub/templates/spawn.html | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/share/jupyterhub/templates/home.html b/share/jupyterhub/templates/home.html index df8eaaed..815b6b9a 100644 --- a/share/jupyterhub/templates/home.html +++ b/share/jupyterhub/templates/home.html @@ -1,5 +1,7 @@ {% extends "page.html" %} -{% if announcement_home %}{% set announcement = announcement_home %}{% endif %} +{% if announcement_home %} + {% set announcement = announcement_home %} +{% endif %} {% block main %} diff --git a/share/jupyterhub/templates/login.html b/share/jupyterhub/templates/login.html index c5f5936a..130339c9 100644 --- a/share/jupyterhub/templates/login.html +++ b/share/jupyterhub/templates/login.html @@ -1,5 +1,7 @@ {% extends "page.html" %} -{% if announcement_login %}{% set announcement = announcement_login %}{% endif %} +{% if announcement_login %} + {% set announcement = announcement_login %} +{% endif %} {% block login_widget %} {% endblock %} diff --git a/share/jupyterhub/templates/logout.html b/share/jupyterhub/templates/logout.html index 95d65017..76fe0f8d 100644 --- a/share/jupyterhub/templates/logout.html +++ b/share/jupyterhub/templates/logout.html @@ -1,5 +1,7 @@ {% extends "page.html" %} -{% if announcement_logout %}{% set announcement = announcement_logout %}{% endif %} +{% if announcement_logout %} + {% set announcement = announcement_logout %} +{% endif %} {% block main %} diff --git a/share/jupyterhub/templates/spawn.html b/share/jupyterhub/templates/spawn.html index 4e2cb497..97511990 100644 --- a/share/jupyterhub/templates/spawn.html +++ b/share/jupyterhub/templates/spawn.html @@ -1,5 +1,7 @@ {% extends "page.html" %} -{% if announcement_spawn %}{% set announcement = announcement_spawn %}{% endif %} +{% if announcement_spawn %} + {% set announcement = announcement_spawn %} +{% endif %} {% block main %} From 5e93c7de4c7904fef4fbc57ed68047ee062829bb Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 12 Jun 2018 13:48:42 +0200 Subject: [PATCH 5/5] announcement doc language per willingc review --- docs/source/reference/templates.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/source/reference/templates.md b/docs/source/reference/templates.md index 76240261..34bdf1f0 100644 --- a/docs/source/reference/templates.md +++ b/docs/source/reference/templates.md @@ -60,12 +60,14 @@ text about the server starting up, place this content in a file named {% endblock %} ``` -## Simple configuration +## Page Announcements -The most powerful way to control templates is to directly extend them -as you see above. However, some simple configuration is possible: +To add announcements to be displayed on a page, you have two options: -### Announcement text +- Extend the page templates as described above +- Use configuration variables + +### Announcement Configuration Variables If you set the configuration variable `JupyterHub.template_vars = {'announcement': 'some_text}`, the given `some_text` will be placed on