diff --git a/docs/conf.py b/docs/conf.py
index 7212f98..cf15be8 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -29,6 +29,7 @@ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
intersphinx_mapping = {
'colander': ('https://docs.pylonsproject.org/projects/colander/en/latest/', None),
'deform': ('https://docs.pylonsproject.org/projects/deform/en/latest/', None),
+ 'fanstatic': ('https://www.fanstatic.org/en/latest/', None),
'pyramid': ('https://docs.pylonsproject.org/projects/pyramid/en/latest/', None),
'python': ('https://docs.python.org/3/', None),
'rattail-manual': ('https://rattailproject.org/docs/rattail-manual/', None),
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 0626c00..6eb7c56 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -13,3 +13,18 @@ Glossary
tools.
See also the :class:`~wuttaweb.grids.base.Grid` base class.
+
+ menu handler
+ This is the :term:`handler` responsible for constructing the main
+ app menu at top of page.
+
+ The menu handler is accessed by way of the :term:`web handler`.
+
+ See also the :class:`~wuttaweb.menus.MenuHandler` base class.
+
+ web handler
+ This is the :term:`handler` responsible for overall web layer
+ customizations, e.g. logo image and menu overrides. Although
+ the latter it delegates to the :term:`menu handler`.
+
+ See also the :class:`~wuttaweb.handler.WebHandler` base class.
diff --git a/pyproject.toml b/pyproject.toml
index 40ee298..0a0435c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -53,6 +53,10 @@ docs = ["Sphinx", "furo"]
tests = ["pytest-cov", "tox"]
+[project.entry-points."fanstatic.libraries"]
+wuttaweb_img = "wuttaweb.static:img"
+
+
[project.entry-points."paste.app_factory"]
main = "wuttaweb.app:main"
diff --git a/src/wuttaweb/handler.py b/src/wuttaweb/handler.py
index d0fa704..f5b4d71 100644
--- a/src/wuttaweb/handler.py
+++ b/src/wuttaweb/handler.py
@@ -22,21 +22,88 @@
################################################################################
"""
Web Handler
-
-This defines the :term:`handler` for the web layer.
"""
from wuttjamaican.app import GenericHandler
+from wuttaweb import static
+
class WebHandler(GenericHandler):
"""
- Base class and default implementation for the "web" :term:`handler`.
+ Base class and default implementation for the :term:`web handler`.
- This is responsible for determining the "menu handler" and
- (eventually) possibly other things.
+ This is responsible for determining the :term:`menu handler` and
+ various other customizations.
"""
+ def get_fanstatic_url(self, request, resource):
+ """
+ Returns the full URL to the given Fanstatic resource.
+
+ :param request: Current :term:`request` object.
+
+ :param resource: :class:`fanstatic:fanstatic.Resource`
+ instance representing an image file or other resource.
+ """
+ needed = request.environ['fanstatic.needed']
+ url = needed.library_url(resource.library) + '/'
+ if request.script_name:
+ url = request.script_name + url
+ return url + resource.relpath
+
+ def get_favicon_url(self, request):
+ """
+ Returns the canonical app favicon image URL.
+
+ This will return the fallback favicon from WuttaWeb unless
+ config specifies an override:
+
+ .. code-block:: ini
+
+ [wuttaweb]
+ favicon_url = http://example.com/favicon.ico
+ """
+ url = self.config.get('wuttaweb.favicon_url')
+ if url:
+ return url
+ return self.get_fanstatic_url(request, static.favicon)
+
+ def get_header_logo_url(self, request):
+ """
+ Returns the canonical app header image URL.
+
+ This will return the value from config if specified (as shown
+ below); otherwise it will just call :meth:`get_favicon_url()`
+ and return that.
+
+ .. code-block:: ini
+
+ [wuttaweb]
+ header_logo_url = http://example.com/logo.png
+ """
+ url = self.config.get('wuttaweb.header_logo_url')
+ if url:
+ return url
+ return self.get_favicon_url(request)
+
+ def get_main_logo_url(self, request):
+ """
+ Returns the canonical app logo image URL.
+
+ This will return the fallback logo from WuttaWeb unless config
+ specifies an override:
+
+ .. code-block:: ini
+
+ [wuttaweb]
+ logo_url = http://example.com/logo.png
+ """
+ url = self.config.get('wuttaweb.logo_url')
+ if url:
+ return url
+ return self.get_fanstatic_url(request, static.logo)
+
def get_menu_handler(self, **kwargs):
"""
Get the configured "menu" handler for the web app.
diff --git a/src/wuttaweb/menus.py b/src/wuttaweb/menus.py
index 8e824be..80b6c53 100644
--- a/src/wuttaweb/menus.py
+++ b/src/wuttaweb/menus.py
@@ -35,7 +35,7 @@ log = logging.getLogger(__name__)
class MenuHandler(GenericHandler):
"""
- Base class and default implementation for menu handler.
+ Base class and default implementation for :term:`menu handler`.
It is assumed that most apps will override the menu handler with
their own subclass. In particular the subclass will override
diff --git a/src/wuttaweb/static/__init__.py b/src/wuttaweb/static/__init__.py
index a2e7012..dd1ff45 100644
--- a/src/wuttaweb/static/__init__.py
+++ b/src/wuttaweb/static/__init__.py
@@ -23,15 +23,52 @@
"""
Static Assets
-It is assumed that all (i.e. even custom) apps will include this
-module somewhere during startup. For instance this happens within
-:func:`~wuttaweb.app.main()`::
+Note that (for now?) It is assumed that *all* (i.e. even custom) apps
+will include this module somewhere during startup. For instance this
+happens within :func:`wuttaweb.app.main()`::
pyramid_config.include('wuttaweb.static')
This allows for certain common assets to be available for all apps.
+
+However, an attempt is being made to incorporate Fanstatic for use
+with the built-in static assets. It is possible the above mechanism
+could be abandoned in the future.
+
+So on the Fanstatic front, we currently have defined:
+
+.. data:: img
+
+ A :class:`fanstatic:fanstatic.Library` representing the ``img``
+ static folder.
+
+.. data:: favicon
+
+ A :class:`fanstatic:fanstatic.Resource` representing the
+ ``img/favicon.ico`` image file.
+
+.. data:: logo
+
+ A :class:`fanstatic:fanstatic.Resource` representing the
+ ``img/logo.png`` image file.
+
+.. data:: testing
+
+ A :class:`fanstatic:fanstatic.Resource` representing the
+ ``img/testing.png`` image file.
"""
+from fanstatic import Library, Resource
+
+# fanstatic img library
+img = Library('wuttaweb_img', 'img')
+favicon = Resource(img, 'favicon.ico')
+# nb. mock out the renderers here, to appease fanstatic
+logo = Resource(img, 'logo.png', renderer=True)
+testing = Resource(img, 'testing.png', renderer=True)
+
+
+# TODO: should consider deprecating this?
def includeme(config):
config.add_static_view('wuttaweb', 'wuttaweb:static')
diff --git a/src/wuttaweb/subscribers.py b/src/wuttaweb/subscribers.py
index 8bbaaf0..79fefd2 100644
--- a/src/wuttaweb/subscribers.py
+++ b/src/wuttaweb/subscribers.py
@@ -260,13 +260,17 @@ def before_render(event):
Here are the keys added to context dict by this hook:
+ .. data:: 'config'
+
+ Reference to the app :term:`config object`.
+
.. data:: 'app'
Reference to the :term:`app handler`.
- .. data:: 'config'
+ .. data:: 'web'
- Reference to the app :term:`config object`.
+ Reference to the :term:`web handler`.
.. data:: 'h'
@@ -293,8 +297,9 @@ def before_render(event):
web = app.get_web_handler()
context = event
- context['app'] = app
context['config'] = config
+ context['app'] = app
+ context['web'] = web
context['h'] = helpers
context['url'] = request.route_url
context['json'] = json
diff --git a/src/wuttaweb/templates/base_meta.mako b/src/wuttaweb/templates/base_meta.mako
index 67739fa..d98445c 100644
--- a/src/wuttaweb/templates/base_meta.mako
+++ b/src/wuttaweb/templates/base_meta.mako
@@ -5,15 +5,15 @@
<%def name="extra_styles()">%def>
<%def name="favicon()">
-
+
%def>
<%def name="header_logo()">
- ${h.image(config.get('wuttaweb.header_logo_url', default=request.static_url('wuttaweb:static/img/favicon.ico')), "Header Logo", style="height: 49px;")}
+ ${h.image(web.get_header_logo_url(request), "Header Logo", style="height: 49px;")}
%def>
<%def name="full_logo(image_url=None)">
- ${h.image(image_url or config.get('wuttaweb.logo_url', default=request.static_url('wuttaweb:static/img/logo.png')), f"{app.get_title()} logo")}
+ ${h.image(image_url or web.get_main_logo_url(request), f"App Logo for {app.get_title()}")}
%def>
<%def name="footer()">
diff --git a/tests/test_handler.py b/tests/test_handler.py
index 79a0a64..9c4037f 100644
--- a/tests/test_handler.py
+++ b/tests/test_handler.py
@@ -1,20 +1,64 @@
# -*- coding: utf-8; -*-
-from unittest import TestCase
-
-from wuttjamaican.conf import WuttaConfig
-
-from wuttaweb import handler as mod
+from wuttaweb import handler as mod, static
from wuttaweb.menus import MenuHandler
+from tests.util import WebTestCase
-class TestWebHandler(TestCase):
+class TestWebHandler(WebTestCase):
- def setUp(self):
- self.config = WuttaConfig()
- self.app = self.config.get_app()
- self.handler = mod.WebHandler(self.config)
+ def make_handler(self):
+ return mod.WebHandler(self.config)
+
+ def test_get_fanstatic_url(self):
+ handler = self.make_handler()
+
+ # default with / root path
+ url = handler.get_fanstatic_url(self.request, static.logo)
+ self.assertEqual(url, '/fanstatic/wuttaweb_img/logo.png')
+
+ # what about a subpath
+ self.request.script_name = '/testing'
+ url = handler.get_fanstatic_url(self.request, static.logo)
+ self.assertEqual(url, '/testing/fanstatic/wuttaweb_img/logo.png')
+
+ def test_get_favicon_url(self):
+ handler = self.make_handler()
+
+ # default
+ url = handler.get_favicon_url(self.request)
+ self.assertEqual(url, '/fanstatic/wuttaweb_img/favicon.ico')
+
+ # config override
+ self.config.setdefault('wuttaweb.favicon_url', '/testing/other.ico')
+ url = handler.get_favicon_url(self.request)
+ self.assertEqual(url, '/testing/other.ico')
+
+ def test_get_header_logo_url(self):
+ handler = self.make_handler()
+
+ # default
+ url = handler.get_header_logo_url(self.request)
+ self.assertEqual(url, '/fanstatic/wuttaweb_img/favicon.ico')
+
+ # config override
+ self.config.setdefault('wuttaweb.header_logo_url', '/testing/header.png')
+ url = handler.get_header_logo_url(self.request)
+ self.assertEqual(url, '/testing/header.png')
+
+ def test_get_main_logo_url(self):
+ handler = self.make_handler()
+
+ # default
+ url = handler.get_main_logo_url(self.request)
+ self.assertEqual(url, '/fanstatic/wuttaweb_img/logo.png')
+
+ # config override
+ self.config.setdefault('wuttaweb.logo_url', '/testing/other.png')
+ url = handler.get_main_logo_url(self.request)
+ self.assertEqual(url, '/testing/other.png')
def test_menu_handler_default(self):
- menus = self.handler.get_menu_handler()
+ handler = self.make_handler()
+ menus = handler.get_menu_handler()
self.assertIsInstance(menus, MenuHandler)
diff --git a/tests/util.py b/tests/util.py
index 51a5768..e292253 100644
--- a/tests/util.py
+++ b/tests/util.py
@@ -3,6 +3,7 @@
from unittest import TestCase
from unittest.mock import MagicMock
+import fanstatic
from pyramid import testing
from wuttjamaican.conf import WuttaConfig
@@ -66,6 +67,10 @@ class WebTestCase(DataTestCase):
'pyramid.events.BeforeRender')
self.pyramid_config.include('wuttaweb.static')
+ # nb. mock out fanstatic env..good enough for now to avoid errors..
+ needed = fanstatic.init_needed()
+ self.request.environ[fanstatic.NEEDED] = needed
+
# setup new request w/ anonymous user
event = MagicMock(request=self.request)
subscribers.new_request(event)
diff --git a/tests/views/test_master.py b/tests/views/test_master.py
index 7e427e9..8e451ee 100644
--- a/tests/views/test_master.py
+++ b/tests/views/test_master.py
@@ -1028,7 +1028,7 @@ class TestMasterView(WebTestCase):
self.session.commit()
def get_instance():
- setting = self.session.query(model.Setting).get('foo.bar')
+ setting = self.session.get(model.Setting, 'foo.bar')
return {
'name': setting.name,
'value': setting.value,
@@ -1092,7 +1092,7 @@ class TestMasterView(WebTestCase):
self.assertEqual(self.session.query(model.Setting).count(), 1)
def get_instance():
- setting = self.session.query(model.Setting).get('foo.bar')
+ setting = self.session.get(model.Setting, 'foo.bar')
return {
'name': setting.name,
'value': setting.value,