Repository: jupyterhub/jupyterhub

Issue:
Make the logo image easily customizable
Right now you have to modify page.html template (or replace the static/images/jupyter.png. This is a common customization, so we should make this be available as a configurable parameter.



Discussion:
Makes sense. The only hairy bit is the separation of Hub and single-user servers, which means that the only reasonable choice is for it to be a URL if we want it to affect both. Should be easy enough, though.


Resolving patch:
diff --git a/jupyterhub/app.py b/jupyterhub/app.py
--- a/jupyterhub/app.py
+++ b/jupyterhub/app.py
@@ -42,7 +42,7 @@
 
 import jupyterhub
 from . import handlers, apihandlers
-from .handlers.static import CacheControlStaticFilesHandler
+from .handlers.static import CacheControlStaticFilesHandler, LogoHandler
 
 from . import orm
 from .user import User, UserDict
@@ -238,6 +238,11 @@ def _template_paths_default(self):
     base_url = URLPrefix('/', config=True,
         help="The base URL of the entire application"
     )
+    logo_file = Unicode('', config=True,
+        help="Specify path to a logo image to override the Jupyter logo in the banner."
+    )
+    def _logo_file_default(self):
+        return os.path.join(self.data_files_path, 'static', 'images', 'jupyter.png')
     
     jinja_environment_options = Dict(config=True,
         help="Supply extra arguments that will be passed to Jinja environment."
@@ -483,6 +488,8 @@ def init_handlers(self):
         # set default handlers
         h.extend(handlers.default_handlers)
         h.extend(apihandlers.default_handlers)
+        
+        h.append((r'/logo', LogoHandler, {'path': self.logo_file}))
         self.handlers = self.add_url_prefix(self.hub_prefix, h)
         # some extra handlers, outside hub_prefix
         self.handlers.extend([
diff --git a/jupyterhub/handlers/static.py b/jupyterhub/handlers/static.py
--- a/jupyterhub/handlers/static.py
+++ b/jupyterhub/handlers/static.py
@@ -1,6 +1,7 @@
 # Copyright (c) Jupyter Development Team.
 # Distributed under the terms of the Modified BSD License.
 
+import os
 from tornado.web import StaticFileHandler
 
 class CacheControlStaticFilesHandler(StaticFileHandler):
@@ -14,4 +15,14 @@ def compute_etag(self):
     def set_extra_headers(self, path):
         if "v" not in self.request.arguments:
             self.add_header("Cache-Control", "no-cache")
-    
\ No newline at end of file
+
+class LogoHandler(StaticFileHandler):
+    """A singular handler for serving the logo."""
+    def get(self):
+        return super().get('')
+
+    @classmethod
+    def get_absolute_path(cls, root, path):
+        """We only serve one file, ignore relative path"""
+        return os.path.abspath(root)
+


Test patch:
diff --git a/jupyterhub/tests/test_pages.py b/jupyterhub/tests/test_pages.py
--- a/jupyterhub/tests/test_pages.py
+++ b/jupyterhub/tests/test_pages.py
@@ -147,3 +147,18 @@ def test_spawn_form_with_file(app, io_loop):
                       'content_type': 'application/unknown'},
         }
 
+
+def test_static_files(app):
+    base_url = ujoin(app.proxy.public_server.url, app.hub.server.base_url)
+    print(base_url)
+    r = requests.get(ujoin(base_url, 'logo'))
+    r.raise_for_status()
+    assert r.headers['content-type'] == 'image/png'
+    r = requests.get(ujoin(base_url, 'static', 'images', 'jupyter.png'))
+    r.raise_for_status()
+    assert r.headers['content-type'] == 'image/png'
+    r = requests.get(ujoin(base_url, 'static', 'css', 'style.min.css'))
+    r.raise_for_status()
+    assert r.headers['content-type'] == 'text/css'
+
+     
\ No newline at end of file