We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Options form seems to kill page refresh
**How to reproduce the issue**

Use an options form in the spawner.

**What you expected to happen**

With no options form, when I start a server, I get a page with a blue refresh button until it actually starts the container (I am using JupyterHub plus a slightly-modified Kubespawner, with Lab inside the Kube container), and then I am redirected there.

This is the behavior I expect.

**What actually happens**

When I use an options form, it goes back to the "Start My Server" button and does not refresh.  Putting a refresh in the options form refreshes that form, but once you submit the form, you're back at "Start My Server" only minus any auto-refresh.  If you reload the page manually it works as expected to show "My Server" once it's started up, and then you can click it and go to your server.

**Share what version of JupyterHub you are using**

JupyterHub 0.8.0-dev0 (GitHub master branch, last pulled Thursday May 18).

Options form looks like


```python
# Add selection form
c.SQREKubeSpawner.options_form = """
<script>
     var time = new Date().getTime();
     $(document.body).bind("mousemove keypress", function(e) {
         time = new Date().getTime();
     });

     function refresh() {
         if(new Date().getTime() - time >= 60000)
             window.location.reload(true);
         else
             setTimeout(refresh, 10000);
     }

     setTimeout(refresh, 10000);
</script>
<label for="LSST Stack Selector">LSST Stack Selector</label></br>
<input type="radio" name="lsst_stack"
  value="lsstsqre/jld-lab-py2:latest"> Python 2
<input type="radio" name="lsst_stack"
  value="lsstsqre/jld-lab-py3:latest"> Python 3
"""
```

jupyter troubleshoot:

```
DEPRECATION: The default format will switch to columns in the future. You can use --format=(legacy|columns) (or define a format=(legacy|columns) in your pip.conf under the [list] section) to disable this warning.
$PATH:
	/usr/bin
	/usr/local/sbin
	/usr/local/bin
	/usr/sbin
	/usr/bin
	/sbin
	/bin

sys.path:
	/usr/bin
	/usr/lib64/python34.zip
	/usr/lib64/python3.4
	/usr/lib64/python3.4/plat-linux
	/usr/lib64/python3.4/lib-dynload
	/usr/lib64/python3.4/site-packages
	/usr/lib/python3.4/site-packages

sys.executable:
	/usr/bin/python3.4

sys.version:
	3.4.5 (default, Nov  9 2016, 16:24:59)
	[GCC 4.8.5 20150623 (Red Hat 4.8.5-11)]

platform.platform():
	Linux-4.4.35+-x86_64-with-centos-7.3.1611-Core

pip list:
	alembic (0.9.1)
	backports-abc (0.5)
	bleach (2.0.0)
	decorator (4.0.11)
	entrypoints (0.2.2)
	html5lib (0.999999999)
	ipykernel (4.6.1)
	ipython (6.0.0)
	ipython-genutils (0.2.0)
	jedi (0.10.2)
	Jinja2 (2.9.6)
	jsonschema (2.6.0)
	jupyter-client (5.0.1)
	jupyter-core (4.3.0)
	jupyterhub (0.8.0.dev0)
	jupyterlab (0.21.0)
	jupyterlab-launcher (0.2.8)
	Mako (1.0.6)
	MarkupSafe (1.0)
	mistune (0.7.4)
	nbconvert (5.1.1)
	nbformat (4.3.0)
	notebook (5.0.0)
	oauthenticator (0.5.1)
	pamela (0.3.0)
	pandocfilters (1.4.1)
	pexpect (4.2.1)
	pickleshare (0.7.4)
	pip (9.0.1)
	prompt-toolkit (1.0.14)
	ptyprocess (0.5.1)
	pycurl (7.43.0)
	Pygments (2.2.0)
	python-dateutil (2.6.0)
	python-editor (1.0.3)
	python-oauth2 (1.0.1)
	PyYAML (3.12)
	pyzmq (16.0.2)
	requests (2.14.2)
	setuptools (19.2)
	simplegeneric (0.8.1)
	six (1.10.0)
	SQLAlchemy (1.1.9)
	sqre-ghowlauth (0.0.9)
	sqrekubespawner (0.0.7)
	terminado (0.6)
	testpath (0.3)
	tornado (4.5.1)
	traitlets (4.3.2)
	typing (3.6.1)
	wcwidth (0.1.7)
	webencodings (0.5.1)

```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `jupyterhub/handlers/pages.py`
Content:
```
1 """Basic html-rendering handlers."""
2 
3 # Copyright (c) Jupyter Development Team.
4 # Distributed under the terms of the Modified BSD License.
5 
6 from http.client import responses
7 
8 from jinja2 import TemplateNotFound
9 from tornado import web, gen
10 from tornado.httputil import url_concat
11 
12 from .. import orm
13 from ..utils import admin_only, url_path_join
14 from .base import BaseHandler
15 
16 
17 class RootHandler(BaseHandler):
18     """Render the Hub root page.
19 
20     If next argument is passed by single-user server,
21     redirect to base_url + single-user page.
22 
23     If logged in, redirects to:
24 
25     - single-user server if running
26     - hub home, otherwise
27 
28     Otherwise, renders login page.
29     """
30     def get(self):
31         next_url = self.get_argument('next', '')
32         if next_url and not next_url.startswith('/'):
33             self.log.warning("Disallowing redirect outside JupyterHub: %r", next_url)
34             next_url = ''
35         if next_url and next_url.startswith(url_path_join(self.base_url, 'user/')):
36             # add /hub/ prefix, to ensure we redirect to the right user's server.
37             # The next request will be handled by UserSpawnHandler,
38             # ultimately redirecting to the logged-in user's server.
39             without_prefix = next_url[len(self.base_url):]
40             next_url = url_path_join(self.hub.server.base_url, without_prefix)
41             self.log.warning("Redirecting %s to %s. For sharing public links, use /user-redirect/",
42                 self.request.uri, next_url,
43             )
44             self.redirect(next_url)
45             return
46         user = self.get_current_user()
47         if user:
48             if user.running:
49                 url = user.url
50                 self.log.debug("User is running: %s", url)
51                 self.set_login_cookie(user) # set cookie
52             else:
53                 url = url_path_join(self.hub.server.base_url, 'home')
54                 self.log.debug("User is not running: %s", url)
55         else:
56             url = self.settings['login_url']
57         self.redirect(url)
58 
59 
60 class HomeHandler(BaseHandler):
61     """Render the user's home page."""
62 
63     @web.authenticated
64     @gen.coroutine
65     def get(self):
66         user = self.get_current_user()
67         if user.running:
68             # trigger poll_and_notify event in case of a server that died
69             yield user.spawner.poll_and_notify()
70             url = user.url
71         else:
72             url = url_concat(url_path_join(self.base_url, 'spawn'),
73                              {'next': self.request.uri})
74         html = self.render_template('home.html',
75             user=user,
76             url=url,
77         )
78         self.finish(html)
79 
80 
81 class SpawnHandler(BaseHandler):
82     """Handle spawning of single-user servers via form.
83 
84     GET renders the form, POST handles form submission.
85 
86     Only enabled when Spawner.options_form is defined.
87     """
88     def _render_form(self, message=''):
89         user = self.get_current_user()
90         return self.render_template('spawn.html',
91             user=user,
92             spawner_options_form=user.spawner.options_form,
93             error_message=message,
94             url=self.request.uri,
95         )
96 
97     @web.authenticated
98     def get(self):
99         """GET renders form for spawning with user-specified options"""
100         user = self.get_current_user()
101         if user.running:
102             url = user.url
103             self.log.debug("User is running: %s", url)
104             self.redirect(url)
105             return
106         if user.spawner.options_form:
107             self.finish(self._render_form())
108         else:
109             # not running, no form. Trigger spawn.
110             self.redirect(user.url)
111 
112     @web.authenticated
113     @gen.coroutine
114     def post(self):
115         """POST spawns with user-specified options"""
116         user = self.get_current_user()
117         if user.running:
118             url = user.url
119             self.log.warning("User is already running: %s", url)
120             self.redirect(url)
121             return
122         form_options = {}
123         for key, byte_list in self.request.body_arguments.items():
124             form_options[key] = [ bs.decode('utf8') for bs in byte_list ]
125         for key, byte_list in self.request.files.items():
126             form_options["%s_file"%key] = byte_list
127         try:
128             options = user.spawner.options_from_form(form_options)
129             yield self.spawn_single_user(user, options=options)
130         except Exception as e:
131             self.log.error("Failed to spawn single-user server with form", exc_info=True)
132             self.finish(self._render_form(str(e)))
133             return
134         self.set_login_cookie(user)
135         url = user.url
136 
137         next_url = self.get_argument('next', '')
138         if next_url and not next_url.startswith('/'):
139             self.log.warning("Disallowing redirect outside JupyterHub: %r", next_url)
140         elif next_url:
141             url = next_url
142 
143         self.redirect(url)
144 
145 class AdminHandler(BaseHandler):
146     """Render the admin page."""
147 
148     @admin_only
149     def get(self):
150         available = {'name', 'admin', 'running', 'last_activity'}
151         default_sort = ['admin', 'name']
152         mapping = {
153             'running': '_server_id'
154         }
155         default_order = {
156             'name': 'asc',
157             'last_activity': 'desc',
158             'admin': 'desc',
159             'running': 'desc',
160         }
161         sorts = self.get_arguments('sort') or default_sort
162         orders = self.get_arguments('order')
163 
164         for bad in set(sorts).difference(available):
165             self.log.warning("ignoring invalid sort: %r", bad)
166             sorts.remove(bad)
167         for bad in set(orders).difference({'asc', 'desc'}):
168             self.log.warning("ignoring invalid order: %r", bad)
169             orders.remove(bad)
170 
171         # add default sort as secondary
172         for s in default_sort:
173             if s not in sorts:
174                 sorts.append(s)
175         if len(orders) < len(sorts):
176             for col in sorts[len(orders):]:
177                 orders.append(default_order[col])
178         else:
179             orders = orders[:len(sorts)]
180 
181         # this could be one incomprehensible nested list comprehension
182         # get User columns
183         cols = [ getattr(orm.User, mapping.get(c, c)) for c in sorts ]
184         # get User.col.desc() order objects
185         ordered = [ getattr(c, o)() for c, o in zip(cols, orders) ]
186 
187         users = self.db.query(orm.User).order_by(*ordered)
188         users = [ self._user_from_orm(u) for u in users ]
189         running = [ u for u in users if u.running ]
190 
191         html = self.render_template('admin.html',
192             user=self.get_current_user(),
193             admin_access=self.settings.get('admin_access', False),
194             users=users,
195             running=running,
196             sort={s:o for s,o in zip(sorts, orders)},
197         )
198         self.finish(html)
199 
200 
201 class TokenPageHandler(BaseHandler):
202     """Handler for page requesting new API tokens"""
203 
204     @web.authenticated
205     def get(self):
206         html = self.render_template('token.html')
207         self.finish(html)
208 
209 
210 class ProxyErrorHandler(BaseHandler):
211     """Handler for rendering proxy error pages"""
212     
213     def get(self, status_code_s):
214         status_code = int(status_code_s)
215         status_message = responses.get(status_code, 'Unknown HTTP Error')
216         # build template namespace
217         
218         hub_home = url_path_join(self.hub.server.base_url, 'home')
219         message_html = ''
220         if status_code == 503:
221             message_html = ' '.join([
222                 "Your server appears to be down.",
223                 "Try restarting it <a href='%s'>from the hub</a>" % hub_home
224             ])
225         ns = dict(
226             status_code=status_code,
227             status_message=status_message,
228             message_html=message_html,
229             logo_url=hub_home,
230         )
231 
232         self.set_header('Content-Type', 'text/html')
233         # render the template
234         try:
235             html = self.render_template('%s.html' % status_code, **ns)
236         except TemplateNotFound:
237             self.log.debug("No template for %d", status_code)
238             html = self.render_template('error.html', **ns)
239 
240         self.write(html)
241 
242 
243 default_handlers = [
244     (r'/', RootHandler),
245     (r'/home', HomeHandler),
246     (r'/admin', AdminHandler),
247     (r'/spawn', SpawnHandler),
248     (r'/token', TokenPageHandler),
249     (r'/error/(\d+)', ProxyErrorHandler),
250 ]
251
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/jupyterhub/handlers/pages.py b/jupyterhub/handlers/pages.py
--- a/jupyterhub/handlers/pages.py
+++ b/jupyterhub/handlers/pages.py
@@ -67,13 +67,9 @@
         if user.running:
             # trigger poll_and_notify event in case of a server that died
             yield user.spawner.poll_and_notify()
-            url = user.url
-        else:
-            url = url_concat(url_path_join(self.base_url, 'spawn'),
-                             {'next': self.request.uri})
         html = self.render_template('home.html',
             user=user,
-            url=url,
+            url=user.url,
         )
         self.finish(html)

{"golden_diff": "diff --git a/jupyterhub/handlers/pages.py b/jupyterhub/handlers/pages.py\n--- a/jupyterhub/handlers/pages.py\n+++ b/jupyterhub/handlers/pages.py\n@@ -67,13 +67,9 @@\n         if user.running:\n             # trigger poll_and_notify event in case of a server that died\n             yield user.spawner.poll_and_notify()\n-            url = user.url\n-        else:\n-            url = url_concat(url_path_join(self.base_url, 'spawn'),\n-                             {'next': self.request.uri})\n         html = self.render_template('home.html',\n             user=user,\n-            url=url,\n+            url=user.url,\n         )\n         self.finish(html)\n", "issue": "Options form seems to kill page refresh\n**How to reproduce the issue**\r\n\r\nUse an options form in the spawner.\r\n\r\n**What you expected to happen**\r\n\r\nWith no options form, when I start a server, I get a page with a blue refresh button until it actually starts the container (I am using JupyterHub plus a slightly-modified Kubespawner, with Lab inside the Kube container), and then I am redirected there.\r\n\r\nThis is the behavior I expect.\r\n\r\n**What actually happens**\r\n\r\nWhen I use an options form, it goes back to the \"Start My Server\" button and does not refresh.  Putting a refresh in the options form refreshes that form, but once you submit the form, you're back at \"Start My Server\" only minus any auto-refresh.  If you reload the page manually it works as expected to show \"My Server\" once it's started up, and then you can click it and go to your server.\r\n\r\n**Share what version of JupyterHub you are using**\r\n\r\nJupyterHub 0.8.0-dev0 (GitHub master branch, last pulled Thursday May 18).\r\n\r\nOptions form looks like\r\n\r\n\r\n```python\r\n# Add selection form\r\nc.SQREKubeSpawner.options_form = \"\"\"\r\n<script>\r\n     var time = new Date().getTime();\r\n     $(document.body).bind(\"mousemove keypress\", function(e) {\r\n         time = new Date().getTime();\r\n     });\r\n\r\n     function refresh() {\r\n         if(new Date().getTime() - time >= 60000)\r\n             window.location.reload(true);\r\n         else\r\n             setTimeout(refresh, 10000);\r\n     }\r\n\r\n     setTimeout(refresh, 10000);\r\n</script>\r\n<label for=\"LSST Stack Selector\">LSST Stack Selector</label></br>\r\n<input type=\"radio\" name=\"lsst_stack\"\r\n  value=\"lsstsqre/jld-lab-py2:latest\"> Python 2\r\n<input type=\"radio\" name=\"lsst_stack\"\r\n  value=\"lsstsqre/jld-lab-py3:latest\"> Python 3\r\n\"\"\"\r\n```\r\n\r\njupyter troubleshoot:\r\n\r\n```\r\nDEPRECATION: The default format will switch to columns in the future. You can use --format=(legacy|columns) (or define a format=(legacy|columns) in your pip.conf under the [list] section) to disable this warning.\r\n$PATH:\r\n\t/usr/bin\r\n\t/usr/local/sbin\r\n\t/usr/local/bin\r\n\t/usr/sbin\r\n\t/usr/bin\r\n\t/sbin\r\n\t/bin\r\n\r\nsys.path:\r\n\t/usr/bin\r\n\t/usr/lib64/python34.zip\r\n\t/usr/lib64/python3.4\r\n\t/usr/lib64/python3.4/plat-linux\r\n\t/usr/lib64/python3.4/lib-dynload\r\n\t/usr/lib64/python3.4/site-packages\r\n\t/usr/lib/python3.4/site-packages\r\n\r\nsys.executable:\r\n\t/usr/bin/python3.4\r\n\r\nsys.version:\r\n\t3.4.5 (default, Nov  9 2016, 16:24:59)\r\n\t[GCC 4.8.5 20150623 (Red Hat 4.8.5-11)]\r\n\r\nplatform.platform():\r\n\tLinux-4.4.35+-x86_64-with-centos-7.3.1611-Core\r\n\r\npip list:\r\n\talembic (0.9.1)\r\n\tbackports-abc (0.5)\r\n\tbleach (2.0.0)\r\n\tdecorator (4.0.11)\r\n\tentrypoints (0.2.2)\r\n\thtml5lib (0.999999999)\r\n\tipykernel (4.6.1)\r\n\tipython (6.0.0)\r\n\tipython-genutils (0.2.0)\r\n\tjedi (0.10.2)\r\n\tJinja2 (2.9.6)\r\n\tjsonschema (2.6.0)\r\n\tjupyter-client (5.0.1)\r\n\tjupyter-core (4.3.0)\r\n\tjupyterhub (0.8.0.dev0)\r\n\tjupyterlab (0.21.0)\r\n\tjupyterlab-launcher (0.2.8)\r\n\tMako (1.0.6)\r\n\tMarkupSafe (1.0)\r\n\tmistune (0.7.4)\r\n\tnbconvert (5.1.1)\r\n\tnbformat (4.3.0)\r\n\tnotebook (5.0.0)\r\n\toauthenticator (0.5.1)\r\n\tpamela (0.3.0)\r\n\tpandocfilters (1.4.1)\r\n\tpexpect (4.2.1)\r\n\tpickleshare (0.7.4)\r\n\tpip (9.0.1)\r\n\tprompt-toolkit (1.0.14)\r\n\tptyprocess (0.5.1)\r\n\tpycurl (7.43.0)\r\n\tPygments (2.2.0)\r\n\tpython-dateutil (2.6.0)\r\n\tpython-editor (1.0.3)\r\n\tpython-oauth2 (1.0.1)\r\n\tPyYAML (3.12)\r\n\tpyzmq (16.0.2)\r\n\trequests (2.14.2)\r\n\tsetuptools (19.2)\r\n\tsimplegeneric (0.8.1)\r\n\tsix (1.10.0)\r\n\tSQLAlchemy (1.1.9)\r\n\tsqre-ghowlauth (0.0.9)\r\n\tsqrekubespawner (0.0.7)\r\n\tterminado (0.6)\r\n\ttestpath (0.3)\r\n\ttornado (4.5.1)\r\n\ttraitlets (4.3.2)\r\n\ttyping (3.6.1)\r\n\twcwidth (0.1.7)\r\n\twebencodings (0.5.1)\r\n\r\n```\r\n\n", "before_files": [{"content": "\"\"\"Basic html-rendering handlers.\"\"\"\n\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nfrom http.client import responses\n\nfrom jinja2 import TemplateNotFound\nfrom tornado import web, gen\nfrom tornado.httputil import url_concat\n\nfrom .. import orm\nfrom ..utils import admin_only, url_path_join\nfrom .base import BaseHandler\n\n\nclass RootHandler(BaseHandler):\n    \"\"\"Render the Hub root page.\n\n    If next argument is passed by single-user server,\n    redirect to base_url + single-user page.\n\n    If logged in, redirects to:\n\n    - single-user server if running\n    - hub home, otherwise\n\n    Otherwise, renders login page.\n    \"\"\"\n    def get(self):\n        next_url = self.get_argument('next', '')\n        if next_url and not next_url.startswith('/'):\n            self.log.warning(\"Disallowing redirect outside JupyterHub: %r\", next_url)\n            next_url = ''\n        if next_url and next_url.startswith(url_path_join(self.base_url, 'user/')):\n            # add /hub/ prefix, to ensure we redirect to the right user's server.\n            # The next request will be handled by UserSpawnHandler,\n            # ultimately redirecting to the logged-in user's server.\n            without_prefix = next_url[len(self.base_url):]\n            next_url = url_path_join(self.hub.server.base_url, without_prefix)\n            self.log.warning(\"Redirecting %s to %s. For sharing public links, use /user-redirect/\",\n                self.request.uri, next_url,\n            )\n            self.redirect(next_url)\n            return\n        user = self.get_current_user()\n        if user:\n            if user.running:\n                url = user.url\n                self.log.debug(\"User is running: %s\", url)\n                self.set_login_cookie(user) # set cookie\n            else:\n                url = url_path_join(self.hub.server.base_url, 'home')\n                self.log.debug(\"User is not running: %s\", url)\n        else:\n            url = self.settings['login_url']\n        self.redirect(url)\n\n\nclass HomeHandler(BaseHandler):\n    \"\"\"Render the user's home page.\"\"\"\n\n    @web.authenticated\n    @gen.coroutine\n    def get(self):\n        user = self.get_current_user()\n        if user.running:\n            # trigger poll_and_notify event in case of a server that died\n            yield user.spawner.poll_and_notify()\n            url = user.url\n        else:\n            url = url_concat(url_path_join(self.base_url, 'spawn'),\n                             {'next': self.request.uri})\n        html = self.render_template('home.html',\n            user=user,\n            url=url,\n        )\n        self.finish(html)\n\n\nclass SpawnHandler(BaseHandler):\n    \"\"\"Handle spawning of single-user servers via form.\n\n    GET renders the form, POST handles form submission.\n\n    Only enabled when Spawner.options_form is defined.\n    \"\"\"\n    def _render_form(self, message=''):\n        user = self.get_current_user()\n        return self.render_template('spawn.html',\n            user=user,\n            spawner_options_form=user.spawner.options_form,\n            error_message=message,\n            url=self.request.uri,\n        )\n\n    @web.authenticated\n    def get(self):\n        \"\"\"GET renders form for spawning with user-specified options\"\"\"\n        user = self.get_current_user()\n        if user.running:\n            url = user.url\n            self.log.debug(\"User is running: %s\", url)\n            self.redirect(url)\n            return\n        if user.spawner.options_form:\n            self.finish(self._render_form())\n        else:\n            # not running, no form. Trigger spawn.\n            self.redirect(user.url)\n\n    @web.authenticated\n    @gen.coroutine\n    def post(self):\n        \"\"\"POST spawns with user-specified options\"\"\"\n        user = self.get_current_user()\n        if user.running:\n            url = user.url\n            self.log.warning(\"User is already running: %s\", url)\n            self.redirect(url)\n            return\n        form_options = {}\n        for key, byte_list in self.request.body_arguments.items():\n            form_options[key] = [ bs.decode('utf8') for bs in byte_list ]\n        for key, byte_list in self.request.files.items():\n            form_options[\"%s_file\"%key] = byte_list\n        try:\n            options = user.spawner.options_from_form(form_options)\n            yield self.spawn_single_user(user, options=options)\n        except Exception as e:\n            self.log.error(\"Failed to spawn single-user server with form\", exc_info=True)\n            self.finish(self._render_form(str(e)))\n            return\n        self.set_login_cookie(user)\n        url = user.url\n\n        next_url = self.get_argument('next', '')\n        if next_url and not next_url.startswith('/'):\n            self.log.warning(\"Disallowing redirect outside JupyterHub: %r\", next_url)\n        elif next_url:\n            url = next_url\n\n        self.redirect(url)\n\nclass AdminHandler(BaseHandler):\n    \"\"\"Render the admin page.\"\"\"\n\n    @admin_only\n    def get(self):\n        available = {'name', 'admin', 'running', 'last_activity'}\n        default_sort = ['admin', 'name']\n        mapping = {\n            'running': '_server_id'\n        }\n        default_order = {\n            'name': 'asc',\n            'last_activity': 'desc',\n            'admin': 'desc',\n            'running': 'desc',\n        }\n        sorts = self.get_arguments('sort') or default_sort\n        orders = self.get_arguments('order')\n\n        for bad in set(sorts).difference(available):\n            self.log.warning(\"ignoring invalid sort: %r\", bad)\n            sorts.remove(bad)\n        for bad in set(orders).difference({'asc', 'desc'}):\n            self.log.warning(\"ignoring invalid order: %r\", bad)\n            orders.remove(bad)\n\n        # add default sort as secondary\n        for s in default_sort:\n            if s not in sorts:\n                sorts.append(s)\n        if len(orders) < len(sorts):\n            for col in sorts[len(orders):]:\n                orders.append(default_order[col])\n        else:\n            orders = orders[:len(sorts)]\n\n        # this could be one incomprehensible nested list comprehension\n        # get User columns\n        cols = [ getattr(orm.User, mapping.get(c, c)) for c in sorts ]\n        # get User.col.desc() order objects\n        ordered = [ getattr(c, o)() for c, o in zip(cols, orders) ]\n\n        users = self.db.query(orm.User).order_by(*ordered)\n        users = [ self._user_from_orm(u) for u in users ]\n        running = [ u for u in users if u.running ]\n\n        html = self.render_template('admin.html',\n            user=self.get_current_user(),\n            admin_access=self.settings.get('admin_access', False),\n            users=users,\n            running=running,\n            sort={s:o for s,o in zip(sorts, orders)},\n        )\n        self.finish(html)\n\n\nclass TokenPageHandler(BaseHandler):\n    \"\"\"Handler for page requesting new API tokens\"\"\"\n\n    @web.authenticated\n    def get(self):\n        html = self.render_template('token.html')\n        self.finish(html)\n\n\nclass ProxyErrorHandler(BaseHandler):\n    \"\"\"Handler for rendering proxy error pages\"\"\"\n    \n    def get(self, status_code_s):\n        status_code = int(status_code_s)\n        status_message = responses.get(status_code, 'Unknown HTTP Error')\n        # build template namespace\n        \n        hub_home = url_path_join(self.hub.server.base_url, 'home')\n        message_html = ''\n        if status_code == 503:\n            message_html = ' '.join([\n                \"Your server appears to be down.\",\n                \"Try restarting it <a href='%s'>from the hub</a>\" % hub_home\n            ])\n        ns = dict(\n            status_code=status_code,\n            status_message=status_message,\n            message_html=message_html,\n            logo_url=hub_home,\n        )\n\n        self.set_header('Content-Type', 'text/html')\n        # render the template\n        try:\n            html = self.render_template('%s.html' % status_code, **ns)\n        except TemplateNotFound:\n            self.log.debug(\"No template for %d\", status_code)\n            html = self.render_template('error.html', **ns)\n\n        self.write(html)\n\n\ndefault_handlers = [\n    (r'/', RootHandler),\n    (r'/home', HomeHandler),\n    (r'/admin', AdminHandler),\n    (r'/spawn', SpawnHandler),\n    (r'/token', TokenPageHandler),\n    (r'/error/(\\d+)', ProxyErrorHandler),\n]\n", "path": "jupyterhub/handlers/pages.py"}], "after_files": [{"content": "\"\"\"Basic html-rendering handlers.\"\"\"\n\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nfrom http.client import responses\n\nfrom jinja2 import TemplateNotFound\nfrom tornado import web, gen\nfrom tornado.httputil import url_concat\n\nfrom .. import orm\nfrom ..utils import admin_only, url_path_join\nfrom .base import BaseHandler\n\n\nclass RootHandler(BaseHandler):\n    \"\"\"Render the Hub root page.\n\n    If next argument is passed by single-user server,\n    redirect to base_url + single-user page.\n\n    If logged in, redirects to:\n\n    - single-user server if running\n    - hub home, otherwise\n\n    Otherwise, renders login page.\n    \"\"\"\n    def get(self):\n        next_url = self.get_argument('next', '')\n        if next_url and not next_url.startswith('/'):\n            self.log.warning(\"Disallowing redirect outside JupyterHub: %r\", next_url)\n            next_url = ''\n        if next_url and next_url.startswith(url_path_join(self.base_url, 'user/')):\n            # add /hub/ prefix, to ensure we redirect to the right user's server.\n            # The next request will be handled by UserSpawnHandler,\n            # ultimately redirecting to the logged-in user's server.\n            without_prefix = next_url[len(self.base_url):]\n            next_url = url_path_join(self.hub.server.base_url, without_prefix)\n            self.log.warning(\"Redirecting %s to %s. For sharing public links, use /user-redirect/\",\n                self.request.uri, next_url,\n            )\n            self.redirect(next_url)\n            return\n        user = self.get_current_user()\n        if user:\n            if user.running:\n                url = user.url\n                self.log.debug(\"User is running: %s\", url)\n                self.set_login_cookie(user) # set cookie\n            else:\n                url = url_path_join(self.hub.server.base_url, 'home')\n                self.log.debug(\"User is not running: %s\", url)\n        else:\n            url = self.settings['login_url']\n        self.redirect(url)\n\n\nclass HomeHandler(BaseHandler):\n    \"\"\"Render the user's home page.\"\"\"\n\n    @web.authenticated\n    @gen.coroutine\n    def get(self):\n        user = self.get_current_user()\n        if user.running:\n            # trigger poll_and_notify event in case of a server that died\n            yield user.spawner.poll_and_notify()\n        html = self.render_template('home.html',\n            user=user,\n            url=user.url,\n        )\n        self.finish(html)\n\n\nclass SpawnHandler(BaseHandler):\n    \"\"\"Handle spawning of single-user servers via form.\n\n    GET renders the form, POST handles form submission.\n\n    Only enabled when Spawner.options_form is defined.\n    \"\"\"\n    def _render_form(self, message=''):\n        user = self.get_current_user()\n        return self.render_template('spawn.html',\n            user=user,\n            spawner_options_form=user.spawner.options_form,\n            error_message=message,\n            url=self.request.uri,\n        )\n\n    @web.authenticated\n    def get(self):\n        \"\"\"GET renders form for spawning with user-specified options\"\"\"\n        user = self.get_current_user()\n        if user.running:\n            url = user.url\n            self.log.debug(\"User is running: %s\", url)\n            self.redirect(url)\n            return\n        if user.spawner.options_form:\n            self.finish(self._render_form())\n        else:\n            # not running, no form. Trigger spawn.\n            self.redirect(user.url)\n\n    @web.authenticated\n    @gen.coroutine\n    def post(self):\n        \"\"\"POST spawns with user-specified options\"\"\"\n        user = self.get_current_user()\n        if user.running:\n            url = user.url\n            self.log.warning(\"User is already running: %s\", url)\n            self.redirect(url)\n            return\n        form_options = {}\n        for key, byte_list in self.request.body_arguments.items():\n            form_options[key] = [ bs.decode('utf8') for bs in byte_list ]\n        for key, byte_list in self.request.files.items():\n            form_options[\"%s_file\"%key] = byte_list\n        try:\n            options = user.spawner.options_from_form(form_options)\n            yield self.spawn_single_user(user, options=options)\n        except Exception as e:\n            self.log.error(\"Failed to spawn single-user server with form\", exc_info=True)\n            self.finish(self._render_form(str(e)))\n            return\n        self.set_login_cookie(user)\n        url = user.url\n\n        next_url = self.get_argument('next', '')\n        if next_url and not next_url.startswith('/'):\n            self.log.warning(\"Disallowing redirect outside JupyterHub: %r\", next_url)\n        elif next_url:\n            url = next_url\n\n        self.redirect(url)\n\nclass AdminHandler(BaseHandler):\n    \"\"\"Render the admin page.\"\"\"\n\n    @admin_only\n    def get(self):\n        available = {'name', 'admin', 'running', 'last_activity'}\n        default_sort = ['admin', 'name']\n        mapping = {\n            'running': '_server_id'\n        }\n        default_order = {\n            'name': 'asc',\n            'last_activity': 'desc',\n            'admin': 'desc',\n            'running': 'desc',\n        }\n        sorts = self.get_arguments('sort') or default_sort\n        orders = self.get_arguments('order')\n\n        for bad in set(sorts).difference(available):\n            self.log.warning(\"ignoring invalid sort: %r\", bad)\n            sorts.remove(bad)\n        for bad in set(orders).difference({'asc', 'desc'}):\n            self.log.warning(\"ignoring invalid order: %r\", bad)\n            orders.remove(bad)\n\n        # add default sort as secondary\n        for s in default_sort:\n            if s not in sorts:\n                sorts.append(s)\n        if len(orders) < len(sorts):\n            for col in sorts[len(orders):]:\n                orders.append(default_order[col])\n        else:\n            orders = orders[:len(sorts)]\n\n        # this could be one incomprehensible nested list comprehension\n        # get User columns\n        cols = [ getattr(orm.User, mapping.get(c, c)) for c in sorts ]\n        # get User.col.desc() order objects\n        ordered = [ getattr(c, o)() for c, o in zip(cols, orders) ]\n\n        users = self.db.query(orm.User).order_by(*ordered)\n        users = [ self._user_from_orm(u) for u in users ]\n        running = [ u for u in users if u.running ]\n\n        html = self.render_template('admin.html',\n            user=self.get_current_user(),\n            admin_access=self.settings.get('admin_access', False),\n            users=users,\n            running=running,\n            sort={s:o for s,o in zip(sorts, orders)},\n        )\n        self.finish(html)\n\n\nclass TokenPageHandler(BaseHandler):\n    \"\"\"Handler for page requesting new API tokens\"\"\"\n\n    @web.authenticated\n    def get(self):\n        html = self.render_template('token.html')\n        self.finish(html)\n\n\nclass ProxyErrorHandler(BaseHandler):\n    \"\"\"Handler for rendering proxy error pages\"\"\"\n    \n    def get(self, status_code_s):\n        status_code = int(status_code_s)\n        status_message = responses.get(status_code, 'Unknown HTTP Error')\n        # build template namespace\n        \n        hub_home = url_path_join(self.hub.server.base_url, 'home')\n        message_html = ''\n        if status_code == 503:\n            message_html = ' '.join([\n                \"Your server appears to be down.\",\n                \"Try restarting it <a href='%s'>from the hub</a>\" % hub_home\n            ])\n        ns = dict(\n            status_code=status_code,\n            status_message=status_message,\n            message_html=message_html,\n            logo_url=hub_home,\n        )\n\n        self.set_header('Content-Type', 'text/html')\n        # render the template\n        try:\n            html = self.render_template('%s.html' % status_code, **ns)\n        except TemplateNotFound:\n            self.log.debug(\"No template for %d\", status_code)\n            html = self.render_template('error.html', **ns)\n\n        self.write(html)\n\n\ndefault_handlers = [\n    (r'/', RootHandler),\n    (r'/home', HomeHandler),\n    (r'/admin', AdminHandler),\n    (r'/spawn', SpawnHandler),\n    (r'/token', TokenPageHandler),\n    (r'/error/(\\d+)', ProxyErrorHandler),\n]\n", "path": "jupyterhub/handlers/pages.py"}]}