diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ceb1bd..3762a98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,17 +5,6 @@ All notable changes to wuttaweb will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## v0.22.0 (2025-06-29) - -### Feat - -- add basic theme system - -### Fix - -- improve styles for testing watermark background image -- fix timezone offset bug for datepicker - ## v0.21.5 (2025-02-21) ### Fix diff --git a/pyproject.toml b/pyproject.toml index 7ca867e..ca6f1a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "WuttaWeb" -version = "0.22.0" +version = "0.21.5" description = "Web App for Wutta Framework" readme = "README.md" authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}] @@ -44,7 +44,7 @@ dependencies = [ "pyramid_tm", "waitress", "WebHelpers2", - "WuttJamaican[db]>=0.20.6", + "WuttJamaican[db]>=0.20.4", "zope.sqlalchemy>=1.5", ] diff --git a/src/wuttaweb/app.py b/src/wuttaweb/app.py index 7c65f86..7546228 100644 --- a/src/wuttaweb/app.py +++ b/src/wuttaweb/app.py @@ -2,7 +2,7 @@ ################################################################################ # # wuttaweb -- Web App for Wutta Framework -# Copyright © 2024-2025 Lance Edgar +# Copyright © 2024 Lance Edgar # # This file is part of Wutta Framework. # @@ -35,7 +35,6 @@ from pyramid.config import Configurator import wuttaweb.db from wuttaweb.auth import WuttaSecurityPolicy -from wuttaweb.util import get_effective_theme, get_theme_template_path log = logging.getLogger(__name__) @@ -133,9 +132,6 @@ def make_pyramid_config(settings): settings.setdefault('pyramid_deform.template_search_path', 'wuttaweb:templates/deform') - # update settings per current theme - establish_theme(settings) - pyramid_config = Configurator(settings=settings) # configure user authorization / authentication @@ -252,33 +248,3 @@ def make_asgi_app(main_app=None, config=None): """ wsgi_app = make_wsgi_app(main_app, config=config) return WsgiToAsgi(wsgi_app) - - -def establish_theme(settings): - """ - Establishes initial theme on app startup. This mostly involves - updating the given ``settings`` dict. - - This function is called automatically from within - :func:`make_pyramid_config()`. - - It will first call :func:`~wuttaweb.util.get_effective_theme()` to - read the current theme from the :term:`settings table`, and store - this within ``settings['wuttaweb.theme']``. - - It then calls :func:`~wuttaweb.util.get_theme_template_path()` and - will update ``settings['mako.directories']`` such that the theme's - template path is listed first. - """ - config = settings['wutta_config'] - - theme = get_effective_theme(config) - settings['wuttaweb.theme'] = theme - - directories = settings['mako.directories'] - if isinstance(directories, str): - directories = config.parse_list(directories) - - path = get_theme_template_path(config) - directories.insert(0, path) - settings['mako.directories'] = directories diff --git a/src/wuttaweb/subscribers.py b/src/wuttaweb/subscribers.py index b0b1cc1..79fefd2 100644 --- a/src/wuttaweb/subscribers.py +++ b/src/wuttaweb/subscribers.py @@ -2,7 +2,7 @@ ################################################################################ # # wuttaweb -- Web App for Wutta Framework -# Copyright © 2024-2025 Lance Edgar +# Copyright © 2024 Lance Edgar # # This file is part of Wutta Framework. # @@ -38,13 +38,11 @@ hooks contained here, depending on the circumstance. import functools import json import logging -from collections import OrderedDict from pyramid import threadlocal from wuttaweb import helpers from wuttaweb.db import Session -from wuttaweb.util import get_available_themes log = logging.getLogger(__name__) @@ -81,43 +79,6 @@ def new_request(event): Flag indicating whether the frontend should be displayed using Vue 3 + Oruga (if ``True``), or else Vue 2 + Buefy (if ``False``). This flag is ``False`` by default. - - .. function:: request.register_component(tagname, classname) - - Request method which registers a Vue component for use within - the app templates. - - :param tagname: Component tag name as string. - - :param classname: Component class name as string. - - This is meant to be analogous to the ``Vue.component()`` call - which is part of Vue 2. It is good practice to always call - both at the same time/place: - - .. code-block:: mako - - ## define component template - - - """ request = event.request config = request.registry.settings['wutta_config'] @@ -143,34 +104,10 @@ def new_request(event): if spec: func = app.load_object(spec) return func(request) - - theme = request.registry.settings.get('wuttaweb.theme') - if theme == 'butterfly': - return True return False request.set_property(use_oruga, reify=True) - def register_component(tagname, classname): - """ - Register a Vue 3 component, so the base template knows to - declare it for use within the app (page). - """ - if not hasattr(request, '_wuttaweb_registered_components'): - request._wuttaweb_registered_components = OrderedDict() - - if tagname in request._wuttaweb_registered_components: - log.warning("component with tagname '%s' already registered " - "with class '%s' but we are replacing that " - "with class '%s'", - tagname, - request._wuttaweb_registered_components[tagname], - classname) - - request._wuttaweb_registered_components[tagname] = classname - - request.register_component = register_component - def default_user_getter(request, db_session=None): """ @@ -353,23 +290,6 @@ def before_render(event): Reference to the request method, :meth:`~pyramid:pyramid.request.Request.route_url()`. - - .. data:: 'theme' - - String name of the current theme. This will be ``'default'`` - unless a custom theme is in effect. - - .. data:: 'expose_theme_picker' - - Boolean indicating whether the theme picker should *ever* be - exposed. For a user to see it, this flag must be true *and* - the user must have permission to change theme. - - .. data:: 'available_themes' - - List of theme names from which user may choose, if they are - allowed to change theme. Only set/relevant if - ``expose_theme_picker`` is true (see above). """ request = event.get('request') or threadlocal.get_current_request() config = request.wutta_config @@ -391,13 +311,6 @@ def before_render(event): menus = web.get_menu_handler() context['menus'] = menus.do_make_menus(request) - # theme - context['theme'] = request.registry.settings.get('wuttaweb.theme', 'default') - context['expose_theme_picker'] = config.get_bool('wuttaweb.themes.expose_picker', - default=False) - if context['expose_theme_picker']: - context['available_themes'] = get_available_themes(config) - def includeme(config): config.add_subscriber(new_request, 'pyramid.events.NewRequest') diff --git a/src/wuttaweb/templates/appinfo/configure.mako b/src/wuttaweb/templates/appinfo/configure.mako index 9d52a7b..f761ade 100644 --- a/src/wuttaweb/templates/appinfo/configure.mako +++ b/src/wuttaweb/templates/appinfo/configure.mako @@ -39,13 +39,6 @@ @input="settingsNeedSaved = true"> Production Mode - - - Expose Theme Picker - diff --git a/src/wuttaweb/templates/base.mako b/src/wuttaweb/templates/base.mako index a1130ef..846843d 100644 --- a/src/wuttaweb/templates/base.mako +++ b/src/wuttaweb/templates/base.mako @@ -130,7 +130,7 @@ } % if not request.wutta_config.production(): - html, body, .navbar, .footer { + html, .navbar, .footer { background-image: url(${request.static_url('wuttaweb:static/img/testing.png')}); } % endif @@ -440,27 +440,7 @@ -<%def name="render_theme_picker()"> - % if expose_theme_picker and request.has_perm('common.change_theme'): -
- ${h.form(url('change_theme'), method='POST', ref='themePickerForm')} - ${h.csrf_token(request)} - -
- Theme: - <${b}-select name="theme" - v-model="globalTheme" - @input="changeTheme()"> - % for name in available_themes: - - % endfor - -
- ${h.end_form()} -
- % endif - - +<%def name="render_theme_picker()"> <%def name="render_feedback_button()"> % if request.has_perm('common.feedback'): @@ -479,8 +459,8 @@ Feedback - <${b}-modal has-modal-card - :active.sync="showDialog"> + - + @@ -651,12 +631,6 @@ }, % endif - - % if expose_theme_picker and request.has_perm('common.change_theme'): - changeTheme() { - this.$refs.themePickerForm.submit() - }, - % endif }, } @@ -664,10 +638,6 @@ contentTitleHTML: ${json.dumps(capture(self.content_title))|n}, referrer: location.href, mountedHooks: [], - - % if expose_theme_picker and request.has_perm('common.change_theme'): - globalTheme: ${json.dumps(theme or None)|n}, - % endif } ## declare nested menu visibility toggle flags @@ -804,13 +774,11 @@ % if request.has_perm('common.feedback'): % endif diff --git a/src/wuttaweb/templates/forms/vue_template.mako b/src/wuttaweb/templates/forms/vue_template.mako index facc89d..d039b76 100644 --- a/src/wuttaweb/templates/forms/vue_template.mako +++ b/src/wuttaweb/templates/forms/vue_template.mako @@ -95,5 +95,3 @@ } - -<% request.register_component(form.vue_tagname, form.vue_component) %> diff --git a/src/wuttaweb/templates/grids/vue_template.mako b/src/wuttaweb/templates/grids/vue_template.mako index 60a27d2..85dd468 100644 --- a/src/wuttaweb/templates/grids/vue_template.mako +++ b/src/wuttaweb/templates/grids/vue_template.mako @@ -739,5 +739,3 @@ } - -<% request.register_component(grid.vue_tagname, grid.vue_component) %> diff --git a/src/wuttaweb/templates/page.mako b/src/wuttaweb/templates/page.mako index 1cd8378..cd5b1da 100644 --- a/src/wuttaweb/templates/page.mako +++ b/src/wuttaweb/templates/page.mako @@ -50,6 +50,6 @@ diff --git a/src/wuttaweb/templates/themes/butterfly/base.mako b/src/wuttaweb/templates/themes/butterfly/base.mako deleted file mode 100644 index 76e9b5b..0000000 --- a/src/wuttaweb/templates/themes/butterfly/base.mako +++ /dev/null @@ -1,78 +0,0 @@ -## -*- coding: utf-8; -*- -<%inherit file="wuttaweb:templates/base.mako" /> -<%namespace file="/http-plugin.mako" import="make_http_plugin" /> -<%namespace file="/buefy-plugin.mako" import="make_buefy_plugin" /> -<%namespace file="/buefy-components.mako" import="make_buefy_components" /> - -<%def name="core_javascript()"> - - - - -<%def name="core_styles()"> - ${h.stylesheet_link(h.get_liburl(request, 'bb_oruga_bulma_css'))} - - -<%def name="render_vue_templates()"> - ${parent.render_vue_templates()} - ${make_buefy_components()} - - -<%def name="extra_styles()"> - ${parent.extra_styles()} - - - -<%def name="make_vue_app()"> - ${make_http_plugin()} - ${make_buefy_plugin()} - - diff --git a/src/wuttaweb/templates/themes/butterfly/buefy-components.mako b/src/wuttaweb/templates/themes/butterfly/buefy-components.mako deleted file mode 100644 index 61c4d94..0000000 --- a/src/wuttaweb/templates/themes/butterfly/buefy-components.mako +++ /dev/null @@ -1,760 +0,0 @@ - -<%def name="make_buefy_components()"> - ${self.make_b_autocomplete_component()} - ${self.make_b_button_component()} - ${self.make_b_checkbox_component()} - ${self.make_b_collapse_component()} - ${self.make_b_datepicker_component()} - ${self.make_b_dropdown_component()} - ${self.make_b_dropdown_item_component()} - ${self.make_b_field_component()} - ${self.make_b_icon_component()} - ${self.make_b_input_component()} - ${self.make_b_loading_component()} - ${self.make_b_modal_component()} - ${self.make_b_notification_component()} - ${self.make_b_radio_component()} - ${self.make_b_select_component()} - ${self.make_b_steps_component()} - ${self.make_b_step_item_component()} - ${self.make_b_table_component()} - ${self.make_b_table_column_component()} - ${self.make_b_tooltip_component()} - ${self.make_once_button_component()} - - -<%def name="make_b_autocomplete_component()"> - - - <% request.register_component('b-autocomplete', 'BAutocomplete') %> - - -<%def name="make_b_button_component()"> - - - <% request.register_component('b-button', 'BButton') %> - - -<%def name="make_b_checkbox_component()"> - - - <% request.register_component('b-checkbox', 'BCheckbox') %> - - -<%def name="make_b_collapse_component()"> - - - <% request.register_component('b-collapse', 'BCollapse') %> - - -<%def name="make_b_datepicker_component()"> - - - <% request.register_component('b-datepicker', 'BDatepicker') %> - - -<%def name="make_b_dropdown_component()"> - - - <% request.register_component('b-dropdown', 'BDropdown') %> - - -<%def name="make_b_dropdown_item_component()"> - - - <% request.register_component('b-dropdown-item', 'BDropdownItem') %> - - -<%def name="make_b_field_component()"> - - - <% request.register_component('b-field', 'BField') %> - - -<%def name="make_b_icon_component()"> - - - <% request.register_component('b-icon', 'BIcon') %> - - -<%def name="make_b_input_component()"> - - - <% request.register_component('b-input', 'BInput') %> - - -<%def name="make_b_loading_component()"> - - - <% request.register_component('b-loading', 'BLoading') %> - - -<%def name="make_b_modal_component()"> - - - <% request.register_component('b-modal', 'BModal') %> - - -<%def name="make_b_notification_component()"> - - - <% request.register_component('b-notification', 'BNotification') %> - - -<%def name="make_b_radio_component()"> - - - <% request.register_component('b-radio', 'BRadio') %> - - -<%def name="make_b_select_component()"> - - - <% request.register_component('b-select', 'BSelect') %> - - -<%def name="make_b_steps_component()"> - - - <% request.register_component('b-steps', 'BSteps') %> - - -<%def name="make_b_step_item_component()"> - - - <% request.register_component('b-step-item', 'BStepItem') %> - - -<%def name="make_b_table_component()"> - - - <% request.register_component('b-table', 'BTable') %> - - -<%def name="make_b_table_column_component()"> - - - <% request.register_component('b-table-column', 'BTableColumn') %> - - -<%def name="make_b_tooltip_component()"> - - - <% request.register_component('b-tooltip', 'BTooltip') %> - - -<%def name="make_once_button_component()"> - - - <% request.register_component('once-button', 'OnceButton') %> - diff --git a/src/wuttaweb/templates/themes/butterfly/buefy-plugin.mako b/src/wuttaweb/templates/themes/butterfly/buefy-plugin.mako deleted file mode 100644 index 4cbedfe..0000000 --- a/src/wuttaweb/templates/themes/butterfly/buefy-plugin.mako +++ /dev/null @@ -1,32 +0,0 @@ - -<%def name="make_buefy_plugin()"> - - diff --git a/src/wuttaweb/templates/themes/butterfly/http-plugin.mako b/src/wuttaweb/templates/themes/butterfly/http-plugin.mako deleted file mode 100644 index 06afc2b..0000000 --- a/src/wuttaweb/templates/themes/butterfly/http-plugin.mako +++ /dev/null @@ -1,100 +0,0 @@ - -<%def name="make_http_plugin()"> - - diff --git a/src/wuttaweb/templates/wutta-components.mako b/src/wuttaweb/templates/wutta-components.mako index 88847f9..fdc8af0 100644 --- a/src/wuttaweb/templates/wutta-components.mako +++ b/src/wuttaweb/templates/wutta-components.mako @@ -228,7 +228,6 @@ } Vue.component('wutta-autocomplete', WuttaAutocomplete) - <% request.register_component('wutta-autocomplete', 'WuttaAutocomplete') %> @@ -296,7 +295,6 @@ }, } Vue.component('wutta-button', WuttaButton) - <% request.register_component('wutta-button', 'WuttaButton') %> @@ -369,7 +367,6 @@ }, } Vue.component('wutta-datepicker', WuttaDatepicker) - <% request.register_component('wutta-datepicker', 'WuttaDatepicker') %> @@ -447,7 +444,6 @@ }, } Vue.component('wutta-timepicker', WuttaTimepicker) - <% request.register_component('wutta-timepicker', 'WuttaTimepicker') %> @@ -620,7 +616,7 @@ } Vue.component('wutta-filter', WuttaFilter) - <% request.register_component('wutta-filter', 'WuttaFilter') %> + @@ -659,7 +655,7 @@ } Vue.component('wutta-filter-value', WuttaFilterValue) - <% request.register_component('wutta-filter-value', 'WuttaFilterValue') %> + @@ -706,7 +702,7 @@ } Vue.component('wutta-filter-date-value', WuttaFilterDateValue) - <% request.register_component('wutta-filter-date-value', 'WuttaFilterDateValue') %> + @@ -731,6 +727,6 @@ } Vue.component('wutta-tool-panel', WuttaToolPanel) - <% request.register_component('wutta-tool-panel', 'WuttaToolPanel') %> + diff --git a/src/wuttaweb/util.py b/src/wuttaweb/util.py index ff65f38..f634c00 100644 --- a/src/wuttaweb/util.py +++ b/src/wuttaweb/util.py @@ -2,7 +2,7 @@ ################################################################################ # # wuttaweb -- Web App for Wutta Framework -# Copyright © 2024-2025 Lance Edgar +# Copyright © 2024 Lance Edgar # # This file is part of Wutta Framework. # @@ -35,12 +35,8 @@ import sqlalchemy as sa from sqlalchemy import orm import colander -from pyramid.renderers import get_renderer from webhelpers2.html import HTML, tags -from wuttjamaican.util import resource_path -from wuttaweb.db import Session - log = logging.getLogger(__name__) @@ -593,156 +589,3 @@ def make_json_safe(value, key=None, warn=True): log.warning("forced value to: %s", value) return value - - -############################## -# theme functions -############################## - -def get_available_themes(config): - """ - Returns the official list of theme names which are available for - use in the app. Privileged users may choose among these when - changing the global theme. - - If config specifies a list, that will be honored. Otherwise the - default list is: ``['default', 'butterfly']`` - - Note that the 'default' theme is Vue 2 + Buefy, while 'butterfly' - is Vue 3 + Oruga. - - You can specify via config by setting e.g.: - - .. code-block:: ini - - [wuttaweb] - themes.keys = default, butterfly, my-other-one - - :param config: App :term:`config object`. - """ - # get available list from config, if it has one - available = config.get_list('wuttaweb.themes.keys', - default=['default', 'butterfly']) - - # sort the list by name - available.sort() - - # make default theme the first option - if 'default' in available: - available.remove('default') - available.insert(0, 'default') - - return available - - -def get_effective_theme(config, theme=None, session=None): - """ - Validate and return the "effective" theme. - - If caller specifies a ``theme`` then it will be returned (if - "available" - see below). - - Otherwise the current theme will be read from db setting. (Note - we do not read simply from config object, we always read from db - setting - this allows for the theme setting to change dynamically - while app is running.) - - In either case if the theme is not listed in - :func:`get_available_themes()` then a ``ValueError`` is raised. - - :param config: App :term:`config object`. - - :param theme: Optional name of desired theme, instead of getting - current theme per db setting. - - :param session: Optional :term:`db session`. - - :returns: Name of theme. - """ - app = config.get_app() - - if not theme: - with app.short_session(session=session) as s: - theme = app.get_setting(s, 'wuttaweb.theme') or 'default' - - # confirm requested theme is available - available = get_available_themes(config) - if theme not in available: - raise ValueError(f"theme not available: {theme}") - - return theme - - -def get_theme_template_path(config, theme=None, session=None): - """ - Return the template path for effective theme. - - If caller specifies a ``theme`` then it will be used; otherwise - the current theme will be read from db setting. The logic for - that happens in :func:`get_effective_theme()`, which this function - will call first. - - Once we have the valid theme name, we check config in case it - specifies a template path override for it. But if not, a default - template path is assumed. - - The default path would be expected to live under - ``wuttaweb:templates/themes``; for instance the ``butterfly`` - theme has a default template path of - ``wuttaweb:templates/themes/butterfly``. - - :param config: App :term:`config object`. - - :param theme: Optional name of desired theme, instead of getting - current theme per db setting. - - :param session: Optional :term:`db session`. - - :returns: Path on disk to theme template folder. - """ - theme = get_effective_theme(config, theme=theme, session=session) - theme_path = config.get(f'wuttaweb.theme.{theme}', - default=f'wuttaweb:templates/themes/{theme}') - return resource_path(theme_path) - - -def set_app_theme(request, theme, session=None): - """ - Set the effective theme for the running app. - - This will modify the *global* Mako template lookup directories, - i.e. app templates will change for all users immediately. - - This will first validate the theme by calling - :func:`get_effective_theme()`. It then retrieves the template - path via :func:`get_theme_template_path()`. - - The theme template path is then injected into the app settings - registry such that it overrides the Mako lookup directories. - - It also will persist the theme name within db settings, so as to - ensure it survives app restart. - """ - config = request.wutta_config - app = config.get_app() - - theme = get_effective_theme(config, theme=theme, session=session) - theme_path = get_theme_template_path(config, theme=theme, session=session) - - # there's only one global template lookup; can get to it via any renderer - # but should *not* use /base.mako since that one is about to get volatile - renderer = get_renderer('/menu.mako') - lookup = renderer.lookup - - # overwrite first entry in lookup's directory list - lookup.directories[0] = theme_path - - # clear template cache for lookup object, so it will reload each (as needed) - lookup._collection.clear() - - # persist current theme in db settings - with app.short_session(session=session) as s: - app.save_setting(s, 'wuttaweb.theme', theme) - - # and cache in live app settings - request.registry.settings['wuttaweb.theme'] = theme diff --git a/src/wuttaweb/views/common.py b/src/wuttaweb/views/common.py index 27ca77f..f3f27d9 100644 --- a/src/wuttaweb/views/common.py +++ b/src/wuttaweb/views/common.py @@ -2,7 +2,7 @@ ################################################################################ # # wuttaweb -- Web App for Wutta Framework -# Copyright © 2024-2025 Lance Edgar +# Copyright © 2024 Lance Edgar # # This file is part of Wutta Framework. # @@ -32,7 +32,6 @@ from pyramid.renderers import render from wuttaweb.views import View from wuttaweb.forms import widgets from wuttaweb.db import Session -from wuttaweb.util import set_app_theme log = logging.getLogger(__name__) @@ -268,21 +267,6 @@ class CommonView(View): which was just created as part of initial setup. """ - def change_theme(self): - """ - This view will set the global app theme, then redirect back to - the referring page. - """ - theme = self.request.params.get('theme') - if theme: - try: - set_app_theme(self.request, theme, session=Session()) - except Exception as error: - error = self.app.render_error(error) - self.request.session.flash(f"Failed to set theme: {error}", 'error') - referrer = self.request.params.get('referrer') or self.request.get_referrer() - return self.redirect(referrer) - @classmethod def defaults(cls, config): cls._defaults(config) @@ -324,12 +308,6 @@ class CommonView(View): route_name='setup', renderer='/setup.mako') - # change theme - config.add_route('change_theme', '/change-theme', request_method='POST') - config.add_view(cls, attr='change_theme', route_name='change_theme') - config.add_wutta_permission('common', 'common.change_theme', - "Change global theme") - def defaults(config, **kwargs): base = globals() diff --git a/src/wuttaweb/views/settings.py b/src/wuttaweb/views/settings.py index 5b1ecb5..a8b8e0a 100644 --- a/src/wuttaweb/views/settings.py +++ b/src/wuttaweb/views/settings.py @@ -2,7 +2,7 @@ ################################################################################ # # wuttaweb -- Web App for Wutta Framework -# Copyright © 2024-2025 Lance Edgar +# Copyright © 2024 Lance Edgar # # This file is part of Wutta Framework. # @@ -129,8 +129,6 @@ class AppInfoView(MasterView): {'name': f'{self.config.appname}.node_title'}, {'name': f'{self.config.appname}.production', 'type': bool}, - {'name': 'wuttaweb.themes.expose_picker', - 'type': bool}, {'name': f'{self.config.appname}.web.menus.handler.spec'}, # nb. this is deprecated; we define so it is auto-deleted # when we replace with newer setting diff --git a/tests/forms/test_base.py b/tests/forms/test_base.py index 7b14088..bc229eb 100644 --- a/tests/forms/test_base.py +++ b/tests/forms/test_base.py @@ -11,7 +11,7 @@ from pyramid import testing from wuttjamaican.conf import WuttaConfig from wuttaweb.forms import base, widgets -from wuttaweb import helpers, subscribers +from wuttaweb import helpers from wuttaweb.grids import Grid @@ -25,14 +25,10 @@ class TestForm(TestCase): self.request = testing.DummyRequest(wutta_config=self.config, use_oruga=False) self.pyramid_config = testing.setUp(request=self.request, settings={ - 'wutta_config': self.config, 'mako.directories': ['wuttaweb:templates'], 'pyramid_deform.template_search_path': 'wuttaweb:templates/deform', }) - event = MagicMock(request=self.request) - subscribers.new_request(event) - def tearDown(self): testing.tearDown() diff --git a/tests/test_app.py b/tests/test_app.py index 591651a..8b092d8 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -3,7 +3,7 @@ from unittest import TestCase from unittest.mock import patch -from wuttjamaican.testing import FileTestCase, ConfigTestCase, DataTestCase +from wuttjamaican.testing import FileTestCase, ConfigTestCase from asgiref.wsgi import WsgiToAsgi from pyramid.config import Configurator @@ -11,8 +11,6 @@ from pyramid.router import Router from wuttaweb import app as mod from wuttjamaican.conf import WuttaConfig -from wuttjamaican.app import AppHandler -from wuttjamaican.util import resource_path class TestWebAppProvider(TestCase): @@ -47,25 +45,22 @@ class TestMakeWuttaConfig(FileTestCase): self.assertIs(settings['wutta_config'], config) -class TestMakePyramidConfig(DataTestCase): +class TestMakePyramidConfig(TestCase): def test_basic(self): - with patch.object(AppHandler, 'make_session', return_value=self.session): - settings = {'wutta_config': self.config} - config = mod.make_pyramid_config(settings) - self.assertIsInstance(config, Configurator) - self.assertEqual(settings['wuttaweb.theme'], 'default') + settings = {} + config = mod.make_pyramid_config(settings) + self.assertIsInstance(config, Configurator) -class TestMain(DataTestCase): +class TestMain(FileTestCase): def test_basic(self): - with patch.object(AppHandler, 'make_session', return_value=self.session): - global_config = None - myconf = self.write_file('my.conf', '') - settings = {'wutta.config': myconf} - app = mod.main(global_config, **settings) - self.assertIsInstance(app, Router) + global_config = None + myconf = self.write_file('my.conf', '') + settings = {'wutta.config': myconf} + app = mod.main(global_config, **settings) + self.assertIsInstance(app, Router) def mock_main(global_config, **settings): @@ -80,20 +75,19 @@ def mock_main(global_config, **settings): return pyramid_config.make_wsgi_app() -class TestMakeWsgiApp(DataTestCase): +class TestMakeWsgiApp(ConfigTestCase): def test_with_callable(self): - with patch.object(self.app, 'make_session', return_value=self.session): - # specify config - wsgi = mod.make_wsgi_app(mock_main, config=self.config) + # specify config + wsgi = mod.make_wsgi_app(mock_main, config=self.config) + self.assertIsInstance(wsgi, Router) + + # auto config + with patch.object(mod, 'make_config', return_value=self.config): + wsgi = mod.make_wsgi_app(mock_main) self.assertIsInstance(wsgi, Router) - # auto config - with patch.object(mod, 'make_config', return_value=self.config): - wsgi = mod.make_wsgi_app(mock_main) - self.assertIsInstance(wsgi, Router) - def test_with_spec(self): # specify config @@ -109,87 +103,29 @@ class TestMakeWsgiApp(DataTestCase): self.assertRaises(ValueError, mod.make_wsgi_app, 42, config=self.config) -class TestMakeAsgiApp(DataTestCase): +class TestMakeAsgiApp(ConfigTestCase): def test_with_callable(self): - with patch.object(self.app, 'make_session', return_value=self.session): - # specify config - asgi = mod.make_asgi_app(mock_main, config=self.config) + # specify config + asgi = mod.make_asgi_app(mock_main, config=self.config) + self.assertIsInstance(asgi, WsgiToAsgi) + + # auto config + with patch.object(mod, 'make_config', return_value=self.config): + asgi = mod.make_asgi_app(mock_main) self.assertIsInstance(asgi, WsgiToAsgi) - # auto config - with patch.object(mod, 'make_config', return_value=self.config): - asgi = mod.make_asgi_app(mock_main) - self.assertIsInstance(asgi, WsgiToAsgi) - def test_with_spec(self): - with patch.object(self.app, 'make_session', return_value=self.session): - # specify config - asgi = mod.make_asgi_app('tests.test_app:mock_main', config=self.config) + # specify config + asgi = mod.make_asgi_app('tests.test_app:mock_main', config=self.config) + self.assertIsInstance(asgi, WsgiToAsgi) + + # auto config + with patch.object(mod, 'make_config', return_value=self.config): + asgi = mod.make_asgi_app('tests.test_app:mock_main') self.assertIsInstance(asgi, WsgiToAsgi) - # auto config - with patch.object(mod, 'make_config', return_value=self.config): - asgi = mod.make_asgi_app('tests.test_app:mock_main') - self.assertIsInstance(asgi, WsgiToAsgi) - def test_invalid(self): self.assertRaises(ValueError, mod.make_asgi_app, 42, config=self.config) - - -class TestEstablishTheme(DataTestCase): - - def test_default(self): - settings = { - 'wutta_config': self.config, - 'mako.directories': ['wuttaweb:templates'], - } - mod.establish_theme(settings) - self.assertEqual(settings['wuttaweb.theme'], 'default') - self.assertEqual(settings['mako.directories'], [ - resource_path('wuttaweb:templates/themes/default'), - 'wuttaweb:templates', - ]) - - def test_mako_dirs_as_string(self): - settings = { - 'wutta_config': self.config, - 'mako.directories': 'wuttaweb:templates', - } - mod.establish_theme(settings) - self.assertEqual(settings['wuttaweb.theme'], 'default') - self.assertEqual(settings['mako.directories'], [ - resource_path('wuttaweb:templates/themes/default'), - 'wuttaweb:templates', - ]) - - def test_butterfly(self): - settings = { - 'wutta_config': self.config, - 'mako.directories': 'wuttaweb:templates', - } - self.app.save_setting(self.session, 'wuttaweb.theme', 'butterfly') - self.session.commit() - mod.establish_theme(settings) - self.assertEqual(settings['wuttaweb.theme'], 'butterfly') - self.assertEqual(settings['mako.directories'], [ - resource_path('wuttaweb:templates/themes/butterfly'), - 'wuttaweb:templates', - ]) - - def test_custom(self): - settings = { - 'wutta_config': self.config, - 'mako.directories': 'wuttaweb:templates', - } - self.config.setdefault('wuttaweb.themes.keys', 'anotherone') - self.app.save_setting(self.session, 'wuttaweb.theme', 'anotherone') - self.session.commit() - mod.establish_theme(settings) - self.assertEqual(settings['wuttaweb.theme'], 'anotherone') - self.assertEqual(settings['mako.directories'], [ - resource_path('wuttaweb:templates/themes/anotherone'), - 'wuttaweb:templates', - ]) diff --git a/tests/test_subscribers.py b/tests/test_subscribers.py index 547bf3a..09d7437 100644 --- a/tests/test_subscribers.py +++ b/tests/test_subscribers.py @@ -41,21 +41,13 @@ class TestNewRequest(TestCase): self.assertIs(self.request.wutta_config, self.config) def test_use_oruga_default(self): + event = MagicMock(request=self.request) # request gets a new attr, false by default self.assertFalse(hasattr(self.request, 'use_oruga')) - event = MagicMock(request=self.request) subscribers.new_request(event) self.assertFalse(self.request.use_oruga) - # nb. using 'butterfly' theme should cause the 'use_oruga' - # flag to be turned on by default - self.request = self.make_request() - self.request.registry.settings['wuttaweb.theme'] = 'butterfly' - event = MagicMock(request=self.request) - subscribers.new_request(event) - self.assertTrue(self.request.use_oruga) - def test_use_oruga_custom(self): self.config.setdefault('wuttaweb.oruga_detector.spec', 'tests.test_subscribers:custom_oruga_detector') event = MagicMock(request=self.request) @@ -65,26 +57,6 @@ class TestNewRequest(TestCase): subscribers.new_request(event) self.assertTrue(self.request.use_oruga) - def test_register_component(self): - event = MagicMock(request=self.request) - subscribers.new_request(event) - - # component tracking dict is missing at first - self.assertFalse(hasattr(self.request, '_wuttaweb_registered_components')) - - # registering a component - self.request.register_component('foo-example', 'FooExample') - self.assertTrue(hasattr(self.request, '_wuttaweb_registered_components')) - self.assertEqual(len(self.request._wuttaweb_registered_components), 1) - self.assertIn('foo-example', self.request._wuttaweb_registered_components) - self.assertEqual(self.request._wuttaweb_registered_components['foo-example'], 'FooExample') - - # re-registering same name - self.request.register_component('foo-example', 'FooExample') - self.assertEqual(len(self.request._wuttaweb_registered_components), 1) - self.assertIn('foo-example', self.request._wuttaweb_registered_components) - self.assertEqual(self.request._wuttaweb_registered_components['foo-example'], 'FooExample') - def test_get_referrer(self): event = MagicMock(request=self.request) @@ -374,7 +346,7 @@ class TestBeforeRender(TestCase): def setUp(self): self.config = WuttaConfig(defaults={ - 'wutta.web.menus.handler.spec': 'tests.util:NullMenuHandler', + 'wutta.web.menus.handler_spec': 'tests.util:NullMenuHandler', }) def make_request(self): @@ -406,24 +378,6 @@ class TestBeforeRender(TestCase): self.assertIn('json', event) self.assertIs(event['json'], json) - # current theme should be 'default' and picker not exposed - self.assertEqual(event['theme'], 'default') - self.assertFalse(event['expose_theme_picker']) - self.assertNotIn('available_themes', event) - - def test_custom_theme(self): - self.config.setdefault('wuttaweb.themes.expose_picker', 'true') - request = self.make_request() - request.registry.settings['wuttaweb.theme'] = 'butterfly' - event = {'request': request} - - # event dict will get populated with more context - subscribers.before_render(event) - self.assertEqual(event['theme'], 'butterfly') - self.assertTrue(event['expose_theme_picker']) - self.assertIn('available_themes', event) - self.assertEqual(event['available_themes'], ['default', 'butterfly']) - class TestIncludeMe(TestCase): diff --git a/tests/test_util.py b/tests/test_util.py index 94a7f65..b8c7ba7 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -11,12 +11,9 @@ from fanstatic import Library, Resource from pyramid import testing from wuttjamaican.conf import WuttaConfig -from wuttjamaican.testing import ConfigTestCase, DataTestCase -from wuttjamaican.util import resource_path +from wuttjamaican.testing import ConfigTestCase from wuttaweb import util as mod -from wuttaweb.app import establish_theme -from wuttaweb.testing import WebTestCase class TestFieldList(TestCase): @@ -624,89 +621,3 @@ class TestMakeJsonSafe(TestCase): 'bar', "Betty Boop", ]) - - -class TestGetAvailableThemes(TestCase): - - def setUp(self): - self.config = WuttaConfig() - self.app = self.config.get_app() - - def test_defaults(self): - themes = mod.get_available_themes(self.config) - self.assertEqual(themes, ['default', 'butterfly']) - - def test_sorting(self): - self.config.setdefault('wuttaweb.themes.keys', 'default, foo2, foo4, foo1') - themes = mod.get_available_themes(self.config) - self.assertEqual(themes, ['default', 'foo1', 'foo2', 'foo4']) - - def test_default_omitted(self): - self.config.setdefault('wuttaweb.themes.keys', 'butterfly, foo') - themes = mod.get_available_themes(self.config) - self.assertEqual(themes, ['default', 'butterfly', 'foo']) - - def test_default_notfirst(self): - self.config.setdefault('wuttaweb.themes.keys', 'butterfly, foo, default') - themes = mod.get_available_themes(self.config) - self.assertEqual(themes, ['default', 'butterfly', 'foo']) - - -class TestGetEffectiveTheme(DataTestCase): - - def test_default(self): - theme = mod.get_effective_theme(self.config) - self.assertEqual(theme, 'default') - - def test_override_config(self): - self.app.save_setting(self.session, 'wuttaweb.theme', 'butterfly') - self.session.commit() - theme = mod.get_effective_theme(self.config) - self.assertEqual(theme, 'butterfly') - - def test_override_param(self): - theme = mod.get_effective_theme(self.config, theme='butterfly') - self.assertEqual(theme, 'butterfly') - - def test_invalid(self): - self.assertRaises(ValueError, mod.get_effective_theme, self.config, theme='invalid') - - -class TestThemeTemplatePath(DataTestCase): - - def test_default(self): - path = mod.get_theme_template_path(self.config, theme='default') - # nb. even though the path does not exist, we still want to - # pretend like it does, hence prev call should return this: - expected = resource_path('wuttaweb:templates/themes/default') - self.assertEqual(path, expected) - - def test_default(self): - path = mod.get_theme_template_path(self.config, theme='butterfly') - expected = resource_path('wuttaweb:templates/themes/butterfly') - self.assertEqual(path, expected) - - def test_custom(self): - self.config.setdefault('wuttaweb.themes.keys', 'default, butterfly, poser') - self.config.setdefault('wuttaweb.theme.poser', '/tmp/poser-theme') - path = mod.get_theme_template_path(self.config, theme='poser') - self.assertEqual(path, '/tmp/poser-theme') - - -class TestSetAppTheme(WebTestCase): - - def test_basic(self): - - # establish default - settings = self.request.registry.settings - self.assertNotIn('wuttaweb.theme', settings) - establish_theme(settings) - self.assertEqual(settings['wuttaweb.theme'], 'default') - - # set to butterfly - mod.set_app_theme(self.request, 'butterfly', session=self.session) - self.assertEqual(settings['wuttaweb.theme'], 'butterfly') - - # set back to default - mod.set_app_theme(self.request, 'default', session=self.session) - self.assertEqual(settings['wuttaweb.theme'], 'default') diff --git a/tests/views/test_common.py b/tests/views/test_common.py index c435fbe..f889c00 100644 --- a/tests/views/test_common.py +++ b/tests/views/test_common.py @@ -6,7 +6,6 @@ import colander from wuttaweb.views import common as mod from wuttaweb.testing import WebTestCase -from wuttaweb.app import establish_theme class TestCommonView(WebTestCase): @@ -181,32 +180,3 @@ class TestCommonView(WebTestCase): self.assertEqual(person.first_name, "Barney") self.assertEqual(person.last_name, "Rubble") self.assertEqual(person.full_name, "Barney Rubble") - - def test_change_theme(self): - self.pyramid_config.add_route('home', '/') - settings = self.request.registry.settings - establish_theme(settings) - view = self.make_view() - - # theme is not changed if not provided by caller - self.assertEqual(settings['wuttaweb.theme'], 'default') - with patch.object(mod, 'set_app_theme') as set_app_theme: - view.change_theme() - set_app_theme.assert_not_called() - self.assertEqual(settings['wuttaweb.theme'], 'default') - - # but theme will change if provided - with patch.object(self.request, 'params', new={'theme': 'butterfly'}): - with patch.object(mod, 'Session', return_value=self.session): - view.change_theme() - self.assertEqual(settings['wuttaweb.theme'], 'butterfly') - - # flash error if invalid theme is provided - self.assertFalse(self.request.session.peek_flash('error')) - with patch.object(self.request, 'params', new={'theme': 'anotherone'}): - with patch.object(mod, 'Session', return_value=self.session): - view.change_theme() - self.assertEqual(settings['wuttaweb.theme'], 'butterfly') - self.assertTrue(self.request.session.peek_flash('error')) - messages = self.request.session.pop_flash('error') - self.assertIn('Failed to set theme', messages[0])