parent
783d4dc8ef
commit
16ed125113
|
@ -29,6 +29,7 @@ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||||
intersphinx_mapping = {
|
intersphinx_mapping = {
|
||||||
'colander': ('https://docs.pylonsproject.org/projects/colander/en/latest/', None),
|
'colander': ('https://docs.pylonsproject.org/projects/colander/en/latest/', None),
|
||||||
'deform': ('https://docs.pylonsproject.org/projects/deform/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),
|
'pyramid': ('https://docs.pylonsproject.org/projects/pyramid/en/latest/', None),
|
||||||
'python': ('https://docs.python.org/3/', None),
|
'python': ('https://docs.python.org/3/', None),
|
||||||
'rattail-manual': ('https://rattailproject.org/docs/rattail-manual/', None),
|
'rattail-manual': ('https://rattailproject.org/docs/rattail-manual/', None),
|
||||||
|
|
|
@ -13,3 +13,18 @@ Glossary
|
||||||
tools.
|
tools.
|
||||||
|
|
||||||
See also the :class:`~wuttaweb.grids.base.Grid` base class.
|
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.
|
||||||
|
|
|
@ -53,6 +53,10 @@ docs = ["Sphinx", "furo"]
|
||||||
tests = ["pytest-cov", "tox"]
|
tests = ["pytest-cov", "tox"]
|
||||||
|
|
||||||
|
|
||||||
|
[project.entry-points."fanstatic.libraries"]
|
||||||
|
wuttaweb_img = "wuttaweb.static:img"
|
||||||
|
|
||||||
|
|
||||||
[project.entry-points."paste.app_factory"]
|
[project.entry-points."paste.app_factory"]
|
||||||
main = "wuttaweb.app:main"
|
main = "wuttaweb.app:main"
|
||||||
|
|
||||||
|
|
|
@ -22,21 +22,88 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
"""
|
"""
|
||||||
Web Handler
|
Web Handler
|
||||||
|
|
||||||
This defines the :term:`handler` for the web layer.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from wuttjamaican.app import GenericHandler
|
from wuttjamaican.app import GenericHandler
|
||||||
|
|
||||||
|
from wuttaweb import static
|
||||||
|
|
||||||
|
|
||||||
class WebHandler(GenericHandler):
|
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
|
This is responsible for determining the :term:`menu handler` and
|
||||||
(eventually) possibly other things.
|
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):
|
def get_menu_handler(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Get the configured "menu" handler for the web app.
|
Get the configured "menu" handler for the web app.
|
||||||
|
|
|
@ -35,7 +35,7 @@ log = logging.getLogger(__name__)
|
||||||
|
|
||||||
class MenuHandler(GenericHandler):
|
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
|
It is assumed that most apps will override the menu handler with
|
||||||
their own subclass. In particular the subclass will override
|
their own subclass. In particular the subclass will override
|
||||||
|
|
|
@ -23,15 +23,52 @@
|
||||||
"""
|
"""
|
||||||
Static Assets
|
Static Assets
|
||||||
|
|
||||||
It is assumed that all (i.e. even custom) apps will include this
|
Note that (for now?) It is assumed that *all* (i.e. even custom) apps
|
||||||
module somewhere during startup. For instance this happens within
|
will include this module somewhere during startup. For instance this
|
||||||
:func:`~wuttaweb.app.main()`::
|
happens within :func:`wuttaweb.app.main()`::
|
||||||
|
|
||||||
pyramid_config.include('wuttaweb.static')
|
pyramid_config.include('wuttaweb.static')
|
||||||
|
|
||||||
This allows for certain common assets to be available for all apps.
|
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):
|
def includeme(config):
|
||||||
config.add_static_view('wuttaweb', 'wuttaweb:static')
|
config.add_static_view('wuttaweb', 'wuttaweb:static')
|
||||||
|
|
|
@ -260,13 +260,17 @@ def before_render(event):
|
||||||
|
|
||||||
Here are the keys added to context dict by this hook:
|
Here are the keys added to context dict by this hook:
|
||||||
|
|
||||||
|
.. data:: 'config'
|
||||||
|
|
||||||
|
Reference to the app :term:`config object`.
|
||||||
|
|
||||||
.. data:: 'app'
|
.. data:: 'app'
|
||||||
|
|
||||||
Reference to the :term:`app handler`.
|
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'
|
.. data:: 'h'
|
||||||
|
|
||||||
|
@ -293,8 +297,9 @@ def before_render(event):
|
||||||
web = app.get_web_handler()
|
web = app.get_web_handler()
|
||||||
|
|
||||||
context = event
|
context = event
|
||||||
context['app'] = app
|
|
||||||
context['config'] = config
|
context['config'] = config
|
||||||
|
context['app'] = app
|
||||||
|
context['web'] = web
|
||||||
context['h'] = helpers
|
context['h'] = helpers
|
||||||
context['url'] = request.route_url
|
context['url'] = request.route_url
|
||||||
context['json'] = json
|
context['json'] = json
|
||||||
|
|
|
@ -5,15 +5,15 @@
|
||||||
<%def name="extra_styles()"></%def>
|
<%def name="extra_styles()"></%def>
|
||||||
|
|
||||||
<%def name="favicon()">
|
<%def name="favicon()">
|
||||||
<link rel="icon" type="image/x-icon" href="${config.get('wuttaweb.favicon_url', default=request.static_url('wuttaweb:static/img/favicon.ico'))}" />
|
<link rel="icon" type="image/x-icon" href="${web.get_favicon_url(request)}" />
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="header_logo()">
|
<%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>
|
||||||
|
|
||||||
<%def name="full_logo(image_url=None)">
|
<%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>
|
||||||
|
|
||||||
<%def name="footer()">
|
<%def name="footer()">
|
||||||
|
|
|
@ -1,20 +1,64 @@
|
||||||
# -*- coding: utf-8; -*-
|
# -*- coding: utf-8; -*-
|
||||||
|
|
||||||
from unittest import TestCase
|
from wuttaweb import handler as mod, static
|
||||||
|
|
||||||
from wuttjamaican.conf import WuttaConfig
|
|
||||||
|
|
||||||
from wuttaweb import handler as mod
|
|
||||||
from wuttaweb.menus import MenuHandler
|
from wuttaweb.menus import MenuHandler
|
||||||
|
from tests.util import WebTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestWebHandler(TestCase):
|
class TestWebHandler(WebTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def make_handler(self):
|
||||||
self.config = WuttaConfig()
|
return mod.WebHandler(self.config)
|
||||||
self.app = self.config.get_app()
|
|
||||||
self.handler = 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):
|
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)
|
self.assertIsInstance(menus, MenuHandler)
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
import fanstatic
|
||||||
from pyramid import testing
|
from pyramid import testing
|
||||||
|
|
||||||
from wuttjamaican.conf import WuttaConfig
|
from wuttjamaican.conf import WuttaConfig
|
||||||
|
@ -66,6 +67,10 @@ class WebTestCase(DataTestCase):
|
||||||
'pyramid.events.BeforeRender')
|
'pyramid.events.BeforeRender')
|
||||||
self.pyramid_config.include('wuttaweb.static')
|
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
|
# setup new request w/ anonymous user
|
||||||
event = MagicMock(request=self.request)
|
event = MagicMock(request=self.request)
|
||||||
subscribers.new_request(event)
|
subscribers.new_request(event)
|
||||||
|
|
|
@ -1028,7 +1028,7 @@ class TestMasterView(WebTestCase):
|
||||||
self.session.commit()
|
self.session.commit()
|
||||||
|
|
||||||
def get_instance():
|
def get_instance():
|
||||||
setting = self.session.query(model.Setting).get('foo.bar')
|
setting = self.session.get(model.Setting, 'foo.bar')
|
||||||
return {
|
return {
|
||||||
'name': setting.name,
|
'name': setting.name,
|
||||||
'value': setting.value,
|
'value': setting.value,
|
||||||
|
@ -1092,7 +1092,7 @@ class TestMasterView(WebTestCase):
|
||||||
self.assertEqual(self.session.query(model.Setting).count(), 1)
|
self.assertEqual(self.session.query(model.Setting).count(), 1)
|
||||||
|
|
||||||
def get_instance():
|
def get_instance():
|
||||||
setting = self.session.query(model.Setting).get('foo.bar')
|
setting = self.session.get(model.Setting, 'foo.bar')
|
||||||
return {
|
return {
|
||||||
'name': setting.name,
|
'name': setting.name,
|
||||||
'value': setting.value,
|
'value': setting.value,
|
||||||
|
|
Loading…
Reference in a new issue