From f9c9c2b47187611e49816909a680a262cd1043d0 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 17 Dec 2015 16:00:40 +0100 Subject: [PATCH] options_form is a regular configurable now that we can assume User.spawner exists at all times --- jupyterhub/handlers/base.py | 4 ++-- jupyterhub/handlers/login.py | 2 +- jupyterhub/handlers/pages.py | 6 +++--- jupyterhub/spawner.py | 31 ++++++++++++++++++++----------- jupyterhub/tests/mocking.py | 3 +-- jupyterhub/tests/test_pages.py | 4 ++-- 6 files changed, 29 insertions(+), 21 deletions(-) diff --git a/jupyterhub/handlers/base.py b/jupyterhub/handlers/base.py index 11b5f17c..e749731d 100644 --- a/jupyterhub/handlers/base.py +++ b/jupyterhub/handlers/base.py @@ -452,12 +452,12 @@ class UserSpawnHandler(BaseHandler): # spawn has supposedly finished, check on the status status = yield current_user.spawner.poll() if status is not None: - if self.spawner_class.options_form: + if current_user.spawner.options_form: self.redirect(url_path_join(self.hub.server.base_url, 'spawn')) else: yield self.spawn_single_user(current_user) else: - if self.spawner_class.options_form: + if current_user.spawner.options_form: self.redirect(url_path_join(self.hub.server.base_url, 'spawn')) else: yield self.spawn_single_user(current_user) diff --git a/jupyterhub/handlers/login.py b/jupyterhub/handlers/login.py index 32a7e3ef..8ab9a1bb 100644 --- a/jupyterhub/handlers/login.py +++ b/jupyterhub/handlers/login.py @@ -68,7 +68,7 @@ class LoginHandler(BaseHandler): if user.spawner: status = yield user.spawner.poll() already_running = (status == None) - if not already_running and not self.spawner_class.options_form: + if not already_running and not user.spawner.options_form: yield self.spawn_single_user(user) self.set_login_cookie(user) next_url = self.get_argument('next', default='') diff --git a/jupyterhub/handlers/pages.py b/jupyterhub/handlers/pages.py index 3b3ecaaa..fcd66cb0 100644 --- a/jupyterhub/handlers/pages.py +++ b/jupyterhub/handlers/pages.py @@ -63,10 +63,10 @@ class SpawnHandler(BaseHandler): self.log.debug("User is running: %s", url) self.redirect(url) return - if self.spawner_class.options_form: + if user.spawner.options_form: html = self.render_template('spawn.html', user=self.get_current_user(), - spawner_options_form=self.spawner_class.options_form, + spawner_options_form=user.spawner.options_form, ) self.finish(html) else: @@ -87,7 +87,7 @@ class SpawnHandler(BaseHandler): form_options = {} for key, byte_list in self.request.body_arguments.items(): form_options[key] = [ bs.decode('utf8') for bs in byte_list ] - options = self.spawner_class.options_from_form(form_options) + options = user.spawner.options_from_form(form_options) yield self.spawn_single_user(user, options=options) url = user.server.base_url self.redirect(url) diff --git a/jupyterhub/spawner.py b/jupyterhub/spawner.py index 1d7958a1..5870b822 100644 --- a/jupyterhub/spawner.py +++ b/jupyterhub/spawner.py @@ -73,24 +73,33 @@ class Spawner(LoggingConfigurable): help="Enable debug-logging of the single-user server" ) - # options_form is a class attribute, defining an HTML form snippet, - # which can be used to specify whether - # (i.e. just the elements, not submit button or the
tag). - # This is **not** a configurable, - options_form = "" - @classmethod - def options_from_form(cls, form_data): + options_form = Unicode("", config=True, help=""" + An HTML form for options a user can specify on launching their server. + The surrounding `` element and the submit button are already provided. + + For example: + + Set your key: + +
+ Choose a letter: + + """) + + def options_from_form(self, form_data): """Interpret HTTP form data Form data will always arrive as a dict of lists of strings. Override this function to understand single-values, numbers, etc. - This should coerce form data into the structure expected by self.options, + This should coerce form data into the structure expected by self.user_options, which must be a dict. - Instances will receive this data on self.user_options, after passing through this function. - - This must be a @classmethod. + Instances will receive this data on self.user_options, after passing through this function, + prior to `Spawner.start`. """ return form_data diff --git a/jupyterhub/tests/mocking.py b/jupyterhub/tests/mocking.py index c0695bb2..0d7a3586 100644 --- a/jupyterhub/tests/mocking.py +++ b/jupyterhub/tests/mocking.py @@ -76,8 +76,7 @@ class NeverSpawner(MockSpawner): class FormSpawner(MockSpawner): options_form = "IMAFORM" - @classmethod - def options_from_form(cls, form_data): + def options_from_form(self, form_data): options = {} options['notspecified'] = 5 if 'bounds' in form_data: diff --git a/jupyterhub/tests/test_pages.py b/jupyterhub/tests/test_pages.py index dc857186..ff4566b8 100644 --- a/jupyterhub/tests/test_pages.py +++ b/jupyterhub/tests/test_pages.py @@ -65,14 +65,14 @@ def test_spawn_redirect(app): assert r.url.endswith('/wash') def test_spawn_page(app): - with mock.patch.dict(app.tornado_application.settings, {'spawner_class': FormSpawner}): + with mock.patch.dict(app.users.settings, {'spawner_class': FormSpawner}): cookies = app.login_user('jones') r = get_page('spawn', app, cookies=cookies) assert r.url.endswith('/spawn') assert FormSpawner.options_form in r.text def test_spawn_form(app, io_loop): - with mock.patch.dict(app.tornado_application.settings, {'spawner_class': FormSpawner}): + with mock.patch.dict(app.users.settings, {'spawner_class': FormSpawner}): base_url = ujoin(app.proxy.public_server.host, app.hub.server.base_url) cookies = app.login_user('jones') orm_u = orm.User.find(app.db, 'jones')