diff --git a/CHANGELOG.md b/CHANGELOG.md index 69b842c..90fc273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,18 +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.4.0 (2024-08-05) - -### Feat - -- add basic App Info view (index only) -- add initial `MasterView` support - -### Fix - -- add `notfound()` View method; auto-append trailing slash -- bump min version for wuttjamaican - ## v0.3.0 (2024-08-05) ### Feat diff --git a/docs/api/wuttaweb/index.rst b/docs/api/wuttaweb/index.rst index 5afd18b..204864e 100644 --- a/docs/api/wuttaweb/index.rst +++ b/docs/api/wuttaweb/index.rst @@ -23,5 +23,3 @@ views.base views.common views.essential - views.master - views.settings diff --git a/docs/api/wuttaweb/views.master.rst b/docs/api/wuttaweb/views.master.rst deleted file mode 100644 index a1e5d5b..0000000 --- a/docs/api/wuttaweb/views.master.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``wuttaweb.views.master`` -========================= - -.. automodule:: wuttaweb.views.master - :members: diff --git a/docs/api/wuttaweb/views.settings.rst b/docs/api/wuttaweb/views.settings.rst deleted file mode 100644 index 7923331..0000000 --- a/docs/api/wuttaweb/views.settings.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``wuttaweb.views.settings`` -=========================== - -.. automodule:: wuttaweb.views.settings - :members: diff --git a/pyproject.toml b/pyproject.toml index 1708f3b..2c921be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "WuttaWeb" -version = "0.4.0" +version = "0.3.0" description = "Web App for Wutta Framework" readme = "README.md" authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] diff --git a/src/wuttaweb/menus.py b/src/wuttaweb/menus.py index 0fe780d..c0e7ae1 100644 --- a/src/wuttaweb/menus.py +++ b/src/wuttaweb/menus.py @@ -118,9 +118,8 @@ class MenuHandler(GenericHandler): 'type': 'menu', 'items': [ { - 'title': "App Info", - 'route': 'appinfo', - 'perm': 'appinfo.list', + 'title': "TODO!", + 'url': '#', }, ], } diff --git a/src/wuttaweb/subscribers.py b/src/wuttaweb/subscribers.py index 92d40d9..63fe428 100644 --- a/src/wuttaweb/subscribers.py +++ b/src/wuttaweb/subscribers.py @@ -249,7 +249,6 @@ def before_render(event): context['h'] = helpers context['url'] = request.route_url context['json'] = json - context['b'] = 'o' if request.use_oruga else 'b' # for buefy # TODO: this should be avoided somehow, for non-traditional web # apps, esp. "API" web apps. (in the meantime can configure the diff --git a/src/wuttaweb/templates/appinfo/index.mako b/src/wuttaweb/templates/appinfo/index.mako deleted file mode 100644 index 7354514..0000000 --- a/src/wuttaweb/templates/appinfo/index.mako +++ /dev/null @@ -1,56 +0,0 @@ -## -*- coding: utf-8; -*- -<%inherit file="/master/index.mako" /> - -<%def name="page_content()"> - - - - - - - -<%def name="modify_this_page_vars()"> - ${parent.modify_this_page_vars()} - - - - -${parent.body()} diff --git a/src/wuttaweb/templates/master/index.mako b/src/wuttaweb/templates/master/index.mako deleted file mode 100644 index fd3d573..0000000 --- a/src/wuttaweb/templates/master/index.mako +++ /dev/null @@ -1,13 +0,0 @@ -## -*- coding: utf-8; -*- -<%inherit file="/page.mako" /> - -<%def name="title()">${index_title} - -<%def name="content_title()"> - -<%def name="page_content()"> -

TODO: index page content

- - - -${parent.body()} diff --git a/src/wuttaweb/views/__init__.py b/src/wuttaweb/views/__init__.py index 845ff49..68fdd77 100644 --- a/src/wuttaweb/views/__init__.py +++ b/src/wuttaweb/views/__init__.py @@ -27,11 +27,9 @@ For convenience, from this ``wuttaweb.views`` namespace you can access the following: * :class:`~wuttaweb.views.base.View` -* :class:`~wuttaweb.views.master.MasterView` """ from .base import View -from .master import MasterView def includeme(config): diff --git a/src/wuttaweb/views/base.py b/src/wuttaweb/views/base.py index 94e22b2..e412ed2 100644 --- a/src/wuttaweb/views/base.py +++ b/src/wuttaweb/views/base.py @@ -73,14 +73,6 @@ class View: """ return forms.Form(self.request, **kwargs) - def notfound(self): - """ - Convenience method, to raise a HTTP 404 Not Found exception:: - - raise self.notfound() - """ - return httpexceptions.HTTPNotFound() - def redirect(self, url, **kwargs): """ Convenience method to return a HTTP 302 response. diff --git a/src/wuttaweb/views/common.py b/src/wuttaweb/views/common.py index b5c108e..153bc5c 100644 --- a/src/wuttaweb/views/common.py +++ b/src/wuttaweb/views/common.py @@ -53,10 +53,7 @@ class CommonView(View): @classmethod def _defaults(cls, config): - # auto-correct URLs which require trailing slash - config.add_notfound_view(cls, attr='notfound', append_slash=True) - - # home page + # home config.add_route('home', '/') config.add_view(cls, attr='home', route_name='home', diff --git a/src/wuttaweb/views/essential.py b/src/wuttaweb/views/essential.py index 0d4ec35..93c8149 100644 --- a/src/wuttaweb/views/essential.py +++ b/src/wuttaweb/views/essential.py @@ -39,7 +39,6 @@ def defaults(config, **kwargs): config.include(mod('wuttaweb.views.auth')) config.include(mod('wuttaweb.views.common')) - config.include(mod('wuttaweb.views.settings')) def includeme(config): diff --git a/src/wuttaweb/views/master.py b/src/wuttaweb/views/master.py deleted file mode 100644 index 9ba1572..0000000 --- a/src/wuttaweb/views/master.py +++ /dev/null @@ -1,443 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# wuttaweb -- Web App for Wutta Framework -# Copyright © 2024 Lance Edgar -# -# This file is part of Wutta Framework. -# -# Wutta Framework is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# Wutta Framework is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# Wutta Framework. If not, see . -# -################################################################################ -""" -Base Logic for Master Views -""" - -from pyramid.renderers import render_to_response - -from wuttaweb.views import View - - -class MasterView(View): - """ - Base class for "master" views. - - Master views typically map to a table in a DB, though not always. - They essentially are a set of CRUD views for a certain type of - data record. - - Many attributes may be overridden in subclass. For instance to - define :attr:`model_class`:: - - from wuttaweb.views import MasterView - from wuttjamaican.db.model import Person - - class MyPersonView(MasterView): - model_class = Person - - def includeme(config): - MyPersonView.defaults(config) - - .. note:: - - Many of these attributes will only exist if they have been - explicitly defined in a subclass. There are corresponding - ``get_xxx()`` methods which should be used instead of accessing - these attributes directly. - - .. attribute:: model_class - - Optional reference to a data model class. While not strictly - required, most views will set this to a SQLAlchemy mapped - class, - e.g. :class:`wuttjamaican:wuttjamaican.db.model.auth.User`. - - Code should not access this directly but instead call - :meth:`get_model_class()`. - - .. attribute:: model_name - - Optional override for the view's data model name, - e.g. ``'WuttaWidget'``. - - Code should not access this directly but instead call - :meth:`get_model_name()`. - - .. attribute:: model_name_normalized - - Optional override for the view's "normalized" data model name, - e.g. ``'wutta_widget'``. - - Code should not access this directly but instead call - :meth:`get_model_name_normalized()`. - - .. attribute:: model_title - - Optional override for the view's "humanized" (singular) model - title, e.g. ``"Wutta Widget"``. - - Code should not access this directly but instead call - :meth:`get_model_title()`. - - .. attribute:: model_title_plural - - Optional override for the view's "humanized" (plural) model - title, e.g. ``"Wutta Widgets"``. - - Code should not access this directly but instead call - :meth:`get_model_title_plural()`. - - .. attribute:: route_prefix - - Optional override for the view's route prefix, - e.g. ``'wutta_widgets'``. - - Code should not access this directly but instead call - :meth:`get_route_prefix()`. - - .. attribute:: url_prefix - - Optional override for the view's URL prefix, - e.g. ``'/widgets'``. - - Code should not access this directly but instead call - :meth:`get_url_prefix()`. - - .. attribute:: template_prefix - - Optional override for the view's template prefix, - e.g. ``'/widgets'``. - - Code should not access this directly but instead call - :meth:`get_template_prefix()`. - - .. attribute:: listable - - Boolean indicating whether the view model supports "listing" - - i.e. it should have an :meth:`index()` view. - """ - - ############################## - # attributes - ############################## - - listable = True - - ############################## - # view methods - ############################## - - def index(self): - """ - View to "list" (filter/browse) the model data. - - This is the "default" view for the model and is what user sees - when visiting the "root" path under the :attr:`url_prefix`, - e.g. ``/widgets/``. - """ - return self.render_to_response('index', {}) - - ############################## - # support methods - ############################## - - def get_index_title(self): - """ - Returns the main index title for the master view. - - By default this returns the value from - :meth:`get_model_title_plural()`. Subclass may override as - needed. - """ - return self.get_model_title_plural() - - def render_to_response(self, template, context): - """ - Locate and render an appropriate template, with the given - context, and return a :term:`response`. - - The specified ``template`` should be only the "base name" for - the template - e.g. ``'index'`` or ``'edit'``. This method - will then try to locate a suitable template file, based on - values from :meth:`get_template_prefix()` and - :meth:`get_fallback_templates()`. - - In practice this *usually* means two different template paths - will be attempted, e.g. if ``template`` is ``'edit'`` and - :attr:`template_prefix` is ``'/widgets'``: - - * ``/widgets/edit.mako`` - * ``/master/edit.mako`` - - The first template found to exist will be used for rendering. - It then calls - :func:`pyramid:pyramid.renderers.render_to_response()` and - returns the result. - - :param template: Base name for the template. - - :param context: Data dict to be used as template context. - - :returns: Response object containing the rendered template. - """ - defaults = { - 'index_title': self.get_index_title(), - } - - # merge defaults + caller-provided context - defaults.update(context) - context = defaults - - # first try the template path most specific to this view - template_prefix = self.get_template_prefix() - mako_path = f'{template_prefix}/{template}.mako' - try: - return render_to_response(mako_path, context, request=self.request) - except IOError: - - # failing that, try one or more fallback templates - for fallback in self.get_fallback_templates(template): - try: - return render_to_response(fallback, context, request=self.request) - except IOError: - pass - - # if we made it all the way here, then we found no - # templates at all, in which case re-attempt the first and - # let that error raise on up - return render_to_response(mako_path, context, request=self.request) - - def get_fallback_templates(self, template): - """ - Returns a list of "fallback" template paths which may be - attempted for rendering a view. This is used within - :meth:`render_to_response()` if the "first guess" template - file was not found. - - :param template: Base name for a template (without prefix), e.g. - ``'custom'``. - - :returns: List of full template paths to be tried, based on - the specified template. For instance if ``template`` is - ``'custom'`` this will (by default) return:: - - ['/master/custom.mako'] - """ - return [f'/master/{template}.mako'] - - ############################## - # class methods - ############################## - - @classmethod - def get_model_class(cls): - """ - Returns the model class for the view (if defined). - - A model class will *usually* be a SQLAlchemy mapped class, - e.g. :class:`wuttjamaican:wuttjamaican.db.model.base.Person`. - - There is no default value here, but a subclass may override by - assigning :attr:`model_class`. - - Note that the model class is not *required* - however if you - do not set the :attr:`model_class`, then you *must* set the - :attr:`model_name`. - """ - if hasattr(cls, 'model_class'): - return cls.model_class - - @classmethod - def get_model_name(cls): - """ - Returns the model name for the view. - - A model name should generally be in the format of a Python - class name, e.g. ``'WuttaWidget'``. (Note this is - *singular*, not plural.) - - The default logic will call :meth:`get_model_class()` and - return that class name as-is. A subclass may override by - assigning :attr:`model_name`. - """ - if hasattr(cls, 'model_name'): - return cls.model_name - - return cls.get_model_class().__name__ - - @classmethod - def get_model_name_normalized(cls): - """ - Returns the "normalized" model name for the view. - - A normalized model name should generally be in the format of a - Python variable name, e.g. ``'wutta_widget'``. (Note this is - *singular*, not plural.) - - The default logic will call :meth:`get_model_name()` and - simply lower-case the result. A subclass may override by - assigning :attr:`model_name_normalized`. - """ - if hasattr(cls, 'model_name_normalized'): - return cls.model_name_normalized - - return cls.get_model_name().lower() - - @classmethod - def get_model_title(cls): - """ - Returns the "humanized" (singular) model title for the view. - - The model title will be displayed to the user, so should have - proper grammar and capitalization, e.g. ``"Wutta Widget"``. - (Note this is *singular*, not plural.) - - The default logic will call :meth:`get_model_name()` and use - the result as-is. A subclass may override by assigning - :attr:`model_title`. - """ - if hasattr(cls, 'model_title'): - return cls.model_title - - return cls.get_model_name() - - @classmethod - def get_model_title_plural(cls): - """ - Returns the "humanized" (plural) model title for the view. - - The model title will be displayed to the user, so should have - proper grammar and capitalization, e.g. ``"Wutta Widgets"``. - (Note this is *plural*, not singular.) - - The default logic will call :meth:`get_model_title()` and - simply add a ``'s'`` to the end. A subclass may override by - assigning :attr:`model_title_plural`. - """ - if hasattr(cls, 'model_title_plural'): - return cls.model_title_plural - - model_title = cls.get_model_title() - return f"{model_title}s" - - @classmethod - def get_route_prefix(cls): - """ - Returns the "route prefix" for the master view. This prefix - is used for all named routes defined by the view class. - - For instance if route prefix is ``'widgets'`` then a view - might have these routes: - - * ``'widgets'`` - * ``'widgets.create'`` - * ``'widgets.edit'`` - * ``'widgets.delete'`` - - The default logic will call - :meth:`get_model_name_normalized()` and simply add an ``'s'`` - to the end, making it plural. A subclass may override by - assigning :attr:`route_prefix`. - """ - if hasattr(cls, 'route_prefix'): - return cls.route_prefix - - model_name = cls.get_model_name_normalized() - return f'{model_name}s' - - @classmethod - def get_url_prefix(cls): - """ - Returns the "URL prefix" for the master view. This prefix is - used for all URLs defined by the view class. - - Using the same example as in :meth:`get_route_prefix()`, the - URL prefix would be ``'/widgets'`` and the view would have - defined routes for these URLs: - - * ``/widgets/`` - * ``/widgets/new`` - * ``/widgets/XXX/edit`` - * ``/widgets/XXX/delete`` - - The default logic will call :meth:`get_route_prefix()` and - simply add a ``'/'`` to the beginning. A subclass may - override by assigning :attr:`url_prefix`. - """ - if hasattr(cls, 'url_prefix'): - return cls.url_prefix - - route_prefix = cls.get_route_prefix() - return f'/{route_prefix}' - - @classmethod - def get_template_prefix(cls): - """ - Returns the "template prefix" for the master view. This - prefix is used to guess which template path to render for a - given view. - - Using the same example as in :meth:`get_url_prefix()`, the - template prefix would also be ``'/widgets'`` and the templates - assumed for those routes would be: - - * ``/widgets/index.mako`` - * ``/widgets/create.mako`` - * ``/widgets/edit.mako`` - * ``/widgets/delete.mako`` - - The default logic will call :meth:`get_url_prefix()` and - return that value as-is. A subclass may override by assigning - :attr:`template_prefix`. - """ - if hasattr(cls, 'template_prefix'): - return cls.template_prefix - - return cls.get_url_prefix() - - ############################## - # configuration - ############################## - - @classmethod - def defaults(cls, config): - """ - Provide default Pyramid configuration for a master view. - - This is generally called from within the module's - ``includeme()`` function, e.g.:: - - from wuttaweb.views import MasterView - - class WidgetView(MasterView): - model_name = 'Widget' - - def includeme(config): - WidgetView.defaults(config) - - :param config: Reference to the app's - :class:`pyramid:pyramid.config.Configurator` instance. - """ - cls._defaults(config) - - @classmethod - def _defaults(cls, config): - route_prefix = cls.get_route_prefix() - url_prefix = cls.get_url_prefix() - - # index view - if cls.listable: - config.add_route(route_prefix, f'{url_prefix}/') - config.add_view(cls, attr='index', - route_name=route_prefix) diff --git a/src/wuttaweb/views/settings.py b/src/wuttaweb/views/settings.py deleted file mode 100644 index 42ce834..0000000 --- a/src/wuttaweb/views/settings.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# wuttaweb -- Web App for Wutta Framework -# Copyright © 2024 Lance Edgar -# -# This file is part of Wutta Framework. -# -# Wutta Framework is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# Wutta Framework is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# Wutta Framework. If not, see . -# -################################################################################ -""" -Views for app settings -""" - -from wuttaweb.views import MasterView - - -class AppInfoView(MasterView): - """ - Master view for the overall app, to show/edit config etc. - """ - model_name = 'AppInfo' - model_title_plural = "App Info" - route_prefix = 'appinfo' - - -def defaults(config, **kwargs): - base = globals() - - AppInfoView = kwargs.get('AppInfoView', base['AppInfoView']) - AppInfoView.defaults(config) - - -def includeme(config): - defaults(config) diff --git a/tests/forms/test_base.py b/tests/forms/test_base.py index 5e29ad6..27e2109 100644 --- a/tests/forms/test_base.py +++ b/tests/forms/test_base.py @@ -46,10 +46,8 @@ class TestFieldList(TestCase): class TestForm(TestCase): def setUp(self): - self.config = WuttaConfig(defaults={ - 'wutta.web.menus.handler_spec': 'tests.utils:NullMenuHandler', - }) - self.request = testing.DummyRequest(wutta_config=self.config, use_oruga=False) + self.config = WuttaConfig() + self.request = testing.DummyRequest(wutta_config=self.config) self.pyramid_config = testing.setUp(request=self.request, settings={ 'mako.directories': ['wuttaweb:templates'], diff --git a/tests/test_subscribers.py b/tests/test_subscribers.py index c991383..419e130 100644 --- a/tests/test_subscribers.py +++ b/tests/test_subscribers.py @@ -214,12 +214,10 @@ class TestNewRequestSetUser(TestCase): class TestBeforeRender(TestCase): def setUp(self): - self.config = WuttaConfig(defaults={ - 'wutta.web.menus.handler_spec': 'tests.utils:NullMenuHandler', - }) + self.config = WuttaConfig() def make_request(self): - request = testing.DummyRequest(use_oruga=False) + request = testing.DummyRequest() request.registry.settings = {'wutta_config': self.config} request.wutta_config = self.config return request diff --git a/tests/utils.py b/tests/utils.py deleted file mode 100644 index ed66cc1..0000000 --- a/tests/utils.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8; -*- - -from wuttaweb.menus import MenuHandler - - -class NullMenuHandler(MenuHandler): - """ - Dummy menu handler for testing. - """ - def make_menus(self, request, **kwargs): - return [] diff --git a/tests/views/test_base.py b/tests/views/test_base.py index 6e2f126..103e005 100644 --- a/tests/views/test_base.py +++ b/tests/views/test_base.py @@ -3,7 +3,7 @@ from unittest import TestCase from pyramid import testing -from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPNotFound +from pyramid.httpexceptions import HTTPFound, HTTPForbidden from wuttjamaican.conf import WuttaConfig from wuttaweb.views import base @@ -31,10 +31,6 @@ class TestView(TestCase): form = self.view.make_form() self.assertIsInstance(form, Form) - def test_notfound(self): - error = self.view.notfound() - self.assertIsInstance(error, HTTPNotFound) - def test_redirect(self): error = self.view.redirect('/') self.assertIsInstance(error, HTTPFound) diff --git a/tests/views/test_master.py b/tests/views/test_master.py deleted file mode 100644 index 8fe4c47..0000000 --- a/tests/views/test_master.py +++ /dev/null @@ -1,282 +0,0 @@ -# -*- coding: utf-8; -*- - -from unittest import TestCase -from unittest.mock import MagicMock - -from pyramid import testing -from pyramid.response import Response - -from wuttjamaican.conf import WuttaConfig -from wuttaweb.views import master -from wuttaweb.subscribers import new_request_set_user - - -class TestMasterView(TestCase): - - def setUp(self): - self.config = WuttaConfig(defaults={ - 'wutta.web.menus.handler_spec': 'tests.utils:NullMenuHandler', - }) - self.app = self.config.get_app() - 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'], - }) - self.pyramid_config.include('pyramid_mako') - self.pyramid_config.include('wuttaweb.static') - self.pyramid_config.include('wuttaweb.views.essential') - self.pyramid_config.add_subscriber('wuttaweb.subscribers.before_render', - 'pyramid.events.BeforeRender') - - event = MagicMock(request=self.request) - new_request_set_user(event) - - def tearDown(self): - testing.tearDown() - - def test_defaults(self): - master.MasterView.model_name = 'Widget' - # TODO: should inspect pyramid routes after this, to be certain - master.MasterView.defaults(self.pyramid_config) - del master.MasterView.model_name - - ############################## - # class methods - ############################## - - def test_get_model_class(self): - - # no model class by default - self.assertIsNone(master.MasterView.get_model_class()) - - # subclass may specify - MyModel = MagicMock() - master.MasterView.model_class = MyModel - self.assertIs(master.MasterView.get_model_class(), MyModel) - del master.MasterView.model_class - - def test_get_model_name(self): - - # error by default (since no model class) - self.assertRaises(AttributeError, master.MasterView.get_model_name) - - # subclass may specify model name - master.MasterView.model_name = 'Widget' - self.assertEqual(master.MasterView.get_model_name(), 'Widget') - del master.MasterView.model_name - - # or it may specify model class - MyModel = MagicMock(__name__='Blaster') - master.MasterView.model_class = MyModel - self.assertEqual(master.MasterView.get_model_name(), 'Blaster') - del master.MasterView.model_class - - def test_get_model_name_normalized(self): - - # error by default (since no model class) - self.assertRaises(AttributeError, master.MasterView.get_model_name_normalized) - - # subclass may specify *normalized* model name - master.MasterView.model_name_normalized = 'widget' - self.assertEqual(master.MasterView.get_model_name_normalized(), 'widget') - del master.MasterView.model_name_normalized - - # or it may specify *standard* model name - master.MasterView.model_name = 'Blaster' - self.assertEqual(master.MasterView.get_model_name_normalized(), 'blaster') - del master.MasterView.model_name - - # or it may specify model class - MyModel = MagicMock(__name__='Dinosaur') - master.MasterView.model_class = MyModel - self.assertEqual(master.MasterView.get_model_name_normalized(), 'dinosaur') - del master.MasterView.model_class - - def test_get_model_title(self): - - # error by default (since no model class) - self.assertRaises(AttributeError, master.MasterView.get_model_title) - - # subclass may specify model title - master.MasterView.model_title = 'Wutta Widget' - self.assertEqual(master.MasterView.get_model_title(), "Wutta Widget") - del master.MasterView.model_title - - # or it may specify model name - master.MasterView.model_name = 'Blaster' - self.assertEqual(master.MasterView.get_model_title(), "Blaster") - del master.MasterView.model_name - - # or it may specify model class - MyModel = MagicMock(__name__='Dinosaur') - master.MasterView.model_class = MyModel - self.assertEqual(master.MasterView.get_model_title(), "Dinosaur") - del master.MasterView.model_class - - def test_get_model_title_plural(self): - - # error by default (since no model class) - self.assertRaises(AttributeError, master.MasterView.get_model_title_plural) - - # subclass may specify *plural* model title - master.MasterView.model_title_plural = 'People' - self.assertEqual(master.MasterView.get_model_title_plural(), "People") - del master.MasterView.model_title_plural - - # or it may specify *singular* model title - master.MasterView.model_title = 'Wutta Widget' - self.assertEqual(master.MasterView.get_model_title_plural(), "Wutta Widgets") - del master.MasterView.model_title - - # or it may specify model name - master.MasterView.model_name = 'Blaster' - self.assertEqual(master.MasterView.get_model_title_plural(), "Blasters") - del master.MasterView.model_name - - # or it may specify model class - MyModel = MagicMock(__name__='Dinosaur') - master.MasterView.model_class = MyModel - self.assertEqual(master.MasterView.get_model_title_plural(), "Dinosaurs") - del master.MasterView.model_class - - def test_get_route_prefix(self): - - # error by default (since no model class) - self.assertRaises(AttributeError, master.MasterView.get_route_prefix) - - # subclass may specify route prefix - master.MasterView.route_prefix = 'widgets' - self.assertEqual(master.MasterView.get_route_prefix(), 'widgets') - del master.MasterView.route_prefix - - # subclass may specify *normalized* model name - master.MasterView.model_name_normalized = 'blaster' - self.assertEqual(master.MasterView.get_route_prefix(), 'blasters') - del master.MasterView.model_name_normalized - - # or it may specify *standard* model name - master.MasterView.model_name = 'Dinosaur' - self.assertEqual(master.MasterView.get_route_prefix(), 'dinosaurs') - del master.MasterView.model_name - - # or it may specify model class - MyModel = MagicMock(__name__='Truck') - master.MasterView.model_class = MyModel - self.assertEqual(master.MasterView.get_route_prefix(), 'trucks') - del master.MasterView.model_class - - def test_get_url_prefix(self): - - # error by default (since no model class) - self.assertRaises(AttributeError, master.MasterView.get_url_prefix) - - # subclass may specify url prefix - master.MasterView.url_prefix = '/widgets' - self.assertEqual(master.MasterView.get_url_prefix(), '/widgets') - del master.MasterView.url_prefix - - # or it may specify route prefix - master.MasterView.route_prefix = 'trucks' - self.assertEqual(master.MasterView.get_url_prefix(), '/trucks') - del master.MasterView.route_prefix - - # or it may specify *normalized* model name - master.MasterView.model_name_normalized = 'blaster' - self.assertEqual(master.MasterView.get_url_prefix(), '/blasters') - del master.MasterView.model_name_normalized - - # or it may specify *standard* model name - master.MasterView.model_name = 'Dinosaur' - self.assertEqual(master.MasterView.get_url_prefix(), '/dinosaurs') - del master.MasterView.model_name - - # or it may specify model class - MyModel = MagicMock(__name__='Machine') - master.MasterView.model_class = MyModel - self.assertEqual(master.MasterView.get_url_prefix(), '/machines') - del master.MasterView.model_class - - def test_get_template_prefix(self): - - # error by default (since no model class) - self.assertRaises(AttributeError, master.MasterView.get_template_prefix) - - # subclass may specify template prefix - master.MasterView.template_prefix = '/widgets' - self.assertEqual(master.MasterView.get_template_prefix(), '/widgets') - del master.MasterView.template_prefix - - # or it may specify url prefix - master.MasterView.url_prefix = '/trees' - self.assertEqual(master.MasterView.get_template_prefix(), '/trees') - del master.MasterView.url_prefix - - # or it may specify route prefix - master.MasterView.route_prefix = 'trucks' - self.assertEqual(master.MasterView.get_template_prefix(), '/trucks') - del master.MasterView.route_prefix - - # or it may specify *normalized* model name - master.MasterView.model_name_normalized = 'blaster' - self.assertEqual(master.MasterView.get_template_prefix(), '/blasters') - del master.MasterView.model_name_normalized - - # or it may specify *standard* model name - master.MasterView.model_name = 'Dinosaur' - self.assertEqual(master.MasterView.get_template_prefix(), '/dinosaurs') - del master.MasterView.model_name - - # or it may specify model class - MyModel = MagicMock(__name__='Machine') - master.MasterView.model_class = MyModel - self.assertEqual(master.MasterView.get_template_prefix(), '/machines') - del master.MasterView.model_class - - ############################## - # support methods - ############################## - - def test_get_index_title(self): - master.MasterView.model_title_plural = "Wutta Widgets" - view = master.MasterView(self.request) - self.assertEqual(view.get_index_title(), "Wutta Widgets") - del master.MasterView.model_title_plural - - def test_render_to_response(self): - - # basic sanity check using /master/index.mako - # (nb. it skips /widgets/index.mako since that doesn't exist) - master.MasterView.model_name = 'Widget' - view = master.MasterView(self.request) - response = view.render_to_response('index', {}) - self.assertIsInstance(response, Response) - del master.MasterView.model_name - - # basic sanity check using /appinfo/index.mako - master.MasterView.model_name = 'AppInfo' - master.MasterView.template_prefix = '/appinfo' - view = master.MasterView(self.request) - response = view.render_to_response('index', {}) - self.assertIsInstance(response, Response) - del master.MasterView.model_name - del master.MasterView.template_prefix - - # bad template name causes error - master.MasterView.model_name = 'Widget' - self.assertRaises(IOError, view.render_to_response, 'nonexistent', {}) - del master.MasterView.model_name - - ############################## - # view methods - ############################## - - def test_index(self): - - # basic sanity check using /appinfo - master.MasterView.model_name = 'AppInfo' - master.MasterView.template_prefix = '/appinfo' - view = master.MasterView(self.request) - response = view.index() - del master.MasterView.model_name - del master.MasterView.template_prefix diff --git a/tests/views/test_settings.py b/tests/views/test_settings.py deleted file mode 100644 index 321364b..0000000 --- a/tests/views/test_settings.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8; -*- - -from tests.views.utils import WebTestCase - -from wuttaweb.views import settings - - -class TestAppInfoView(WebTestCase): - - def test_index(self): - # just a sanity check - view = settings.AppInfoView(self.request) - response = view.index() diff --git a/tests/views/utils.py b/tests/views/utils.py deleted file mode 100644 index 495f5cb..0000000 --- a/tests/views/utils.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8; -*- - -from unittest import TestCase -from unittest.mock import MagicMock - -from pyramid import testing - -from wuttjamaican.conf import WuttaConfig -from wuttaweb import subscribers - - -class WebTestCase(TestCase): - """ - Base class for test suites requiring a full (typical) web app. - """ - - def setUp(self): - self.setup_web() - - def setup_web(self): - self.config = WuttaConfig(defaults={ - 'wutta.db.default.url': 'sqlite://', - 'wutta.web.menus.handler_spec': 'tests.utils:NullMenuHandler', - }) - - self.request = testing.DummyRequest() - - self.pyramid_config = testing.setUp(request=self.request, settings={ - 'wutta_config': self.config, - 'mako.directories': ['wuttaweb:templates'], - }) - - # init db - self.app = self.config.get_app() - model = self.app.model - model.Base.metadata.create_all(bind=self.config.appdb_engine) - self.session = self.app.make_session() - - # init web - self.pyramid_config.include('pyramid_mako') - self.pyramid_config.include('wuttaweb.static') - self.pyramid_config.include('wuttaweb.views.essential') - self.pyramid_config.add_subscriber('wuttaweb.subscribers.before_render', - 'pyramid.events.BeforeRender') - - # setup new request w/ anonymous user - event = MagicMock(request=self.request) - subscribers.new_request(event) - def user_getter(request, **kwargs): pass - subscribers.new_request_set_user(event, db_session=self.session, - user_getter=user_getter) - - def tearDown(self): - self.teardown_web() - - def teardown_web(self): - testing.tearDown()