1
0
Fork 0

feat: add initial views for upgrades

CRUD only so far, still need execute features
This commit is contained in:
Lance Edgar 2024-08-24 11:29:52 -05:00
parent 1804e74d13
commit 6650ee698e
14 changed files with 656 additions and 117 deletions

View file

@ -32,4 +32,5 @@
views.people views.people
views.roles views.roles
views.settings views.settings
views.upgrades
views.users views.users

View file

@ -0,0 +1,6 @@
``wuttaweb.views.upgrades``
===========================
.. automodule:: wuttaweb.views.upgrades
:members:

View file

@ -92,6 +92,53 @@ class ObjectNode(colander.SchemaNode):
raise NotImplementedError(f"you must define {class_name}.objectify()") raise NotImplementedError(f"you must define {class_name}.objectify()")
class WuttaEnum(colander.Enum):
"""
Custom schema type for enum fields.
This is a subclass of :class:`colander.Enum`, but adds a
default widget (``SelectWidget``) with enum choices.
:param request: Current :term:`request` object.
"""
def __init__(self, request, *args, **kwargs):
super().__init__(*args, **kwargs)
self.request = request
self.config = self.request.wutta_config
self.app = self.config.get_app()
def widget_maker(self, **kwargs):
""" """
if 'values' not in kwargs:
kwargs['values'] = [(getattr(e, self.attr), getattr(e, self.attr))
for e in self.enum_cls]
return widgets.SelectWidget(**kwargs)
class WuttaSet(colander.Set):
"""
Custom schema type for :class:`python:set` fields.
This is a subclass of :class:`colander.Set`, but adds
Wutta-related params to the constructor.
:param request: Current :term:`request` object.
:param session: Optional :term:`db session` to use instead of
:class:`wuttaweb.db.Session`.
"""
def __init__(self, request, session=None):
super().__init__()
self.request = request
self.config = self.request.wutta_config
self.app = self.config.get_app()
self.session = session or Session()
class ObjectRef(colander.SchemaType): class ObjectRef(colander.SchemaType):
""" """
Custom schema type for a model class reference field. Custom schema type for a model class reference field.
@ -199,7 +246,7 @@ class ObjectRef(colander.SchemaType):
# fetch object from DB # fetch object from DB
model = self.app.model model = self.app.model
obj = self.session.query(self.model_class).get(value) obj = self.session.get(self.model_class, value)
# raise error if not found # raise error if not found
if not obj: if not obj:
@ -247,14 +294,28 @@ class ObjectRef(colander.SchemaType):
kwargs['values'] = values kwargs['values'] = values
if 'url' not in kwargs: if 'url' not in kwargs:
kwargs['url'] = lambda person: self.request.route_url('people.view', uuid=person.uuid) kwargs['url'] = self.get_object_url
return widgets.ObjectRefWidget(self.request, **kwargs) return widgets.ObjectRefWidget(self.request, **kwargs)
def get_object_url(self, obj):
"""
Returns the "view" URL for the given object, if applicable.
This is used when rendering the field readonly. If this
method returns a URL then the field text will be wrapped with
a hyperlink, otherwise it will be shown as-is.
Default logic always returns ``None``; subclass should
override as needed.
"""
class PersonRef(ObjectRef): class PersonRef(ObjectRef):
""" """
Custom schema type for a ``Person`` reference field. Custom schema type for a
:class:`~wuttjamaican:wuttjamaican.db.model.base.Person` reference
field.
This is a subclass of :class:`ObjectRef`. This is a subclass of :class:`ObjectRef`.
""" """
@ -269,26 +330,33 @@ class PersonRef(ObjectRef):
""" """ """ """
return query.order_by(self.model_class.full_name) return query.order_by(self.model_class.full_name)
def get_object_url(self, person):
""" """
return self.request.route_url('people.view', uuid=person.uuid)
class WuttaSet(colander.Set):
class UserRef(ObjectRef):
""" """
Custom schema type for :class:`python:set` fields. Custom schema type for a
:class:`~wuttjamaican:wuttjamaican.db.model.auth.User` reference
field.
This is a subclass of :class:`colander.Set`, but adds This is a subclass of :class:`ObjectRef`.
Wutta-related params to the constructor.
:param request: Current :term:`request` object.
:param session: Optional :term:`db session` to use instead of
:class:`wuttaweb.db.Session`.
""" """
def __init__(self, request, session=None): @property
super().__init__() def model_class(self):
self.request = request """ """
self.config = self.request.wutta_config model = self.app.model
self.app = self.config.get_app() return model.User
self.session = session or Session()
def sort_query(self, query):
""" """
return query.order_by(self.model_class.username)
def get_object_url(self, user):
""" """
return self.request.route_url('users.view', uuid=user.uuid)
class RoleRefs(WuttaSet): class RoleRefs(WuttaSet):

View file

@ -1078,6 +1078,7 @@ class Grid:
:returns: A :class:`~wuttaweb.grids.filters.GridFilter` :returns: A :class:`~wuttaweb.grids.filters.GridFilter`
instance. instance.
""" """
key = kwargs.pop('key', None)
# model_property is required # model_property is required
model_property = None model_property = None
@ -1102,7 +1103,7 @@ class Grid:
# make filter # make filter
kwargs['model_property'] = model_property kwargs['model_property'] = model_property
return factory(self.request, model_property.key, **kwargs) return factory(self.request, key or model_property.key, **kwargs)
def set_filter(self, key, filterinfo=None, **kwargs): def set_filter(self, key, filterinfo=None, **kwargs):
""" """
@ -1132,6 +1133,7 @@ class Grid:
# filtr = filterinfo # filtr = filterinfo
raise NotImplementedError raise NotImplementedError
else: else:
kwargs['key'] = key
kwargs.setdefault('label', self.get_label(key)) kwargs.setdefault('label', self.get_label(key))
filtr = self.make_filter(filterinfo or key, **kwargs) filtr = self.make_filter(filterinfo or key, **kwargs)

View file

@ -168,6 +168,11 @@ class MenuHandler(GenericHandler):
'route': 'settings', 'route': 'settings',
'perm': 'settings.list', 'perm': 'settings.list',
}, },
{
'title': "Upgrades",
'route': 'upgrades',
'perm': 'upgrades.list',
},
], ],
} }

View file

@ -83,6 +83,32 @@ class FieldList(list):
field, newfield) field, newfield)
self.append(newfield) self.append(newfield)
def set_sequence(self, fields):
"""
Sort the list such that it matches the same sequence as the
given fields list.
This does not add or remove any elements, it just
(potentially) rearranges the internal list elements.
Therefore you do not need to explicitly declare *all* fields;
just the ones you care about.
The resulting field list will have the requested fields in
order, at the *beginning* of the list. Any unrequested fields
will remain in the same order as they were previously, but
will be placed *after* the requested fields.
:param fields: List of fields in the desired order.
"""
unimportant = len(self) + 1
def getkey(field):
if field in fields:
return fields.index(field)
return unimportant
self.sort(key=getkey)
def get_form_data(request): def get_form_data(request):
""" """

View file

@ -154,6 +154,11 @@ class CommonView(View):
'settings.view', 'settings.view',
'settings.edit', 'settings.edit',
'settings.delete', 'settings.delete',
'upgrades.list',
'upgrades.create',
'upgrades.view',
'upgrades.edit',
'upgrades.delete',
'users.list', 'users.list',
'users.create', 'users.create',
'users.view', 'users.view',

View file

@ -35,6 +35,7 @@ That will in turn include the following modules:
* :mod:`wuttaweb.views.people` * :mod:`wuttaweb.views.people`
* :mod:`wuttaweb.views.roles` * :mod:`wuttaweb.views.roles`
* :mod:`wuttaweb.views.users` * :mod:`wuttaweb.views.users`
* :mod:`wuttaweb.views.upgrades`
""" """
@ -47,6 +48,7 @@ def defaults(config, **kwargs):
config.include(mod('wuttaweb.views.people')) config.include(mod('wuttaweb.views.people'))
config.include(mod('wuttaweb.views.roles')) config.include(mod('wuttaweb.views.roles'))
config.include(mod('wuttaweb.views.users')) config.include(mod('wuttaweb.views.users'))
config.include(mod('wuttaweb.views.upgrades'))
def includeme(config): def includeme(config):

View file

@ -1040,6 +1040,56 @@ class MasterView(View):
fmt = f"${{:0,.{scale}f}}" fmt = f"${{:0,.{scale}f}}"
return fmt.format(value) return fmt.format(value)
def grid_render_datetime(self, record, key, value, fmt=None):
"""
Custom grid value renderer for
:class:`~python:datetime.datetime` fields.
:param fmt: Optional format string to use instead of the
default: ``'%Y-%m-%d %I:%M:%S %p'``
To use this feature for your grid::
grid.set_renderer('my_datetime_field', self.grid_render_datetime)
# you can also override format
grid.set_renderer('my_datetime_field', self.grid_render_datetime,
fmt='%Y-%m-%d %H:%M:%S')
"""
# nb. get new value since the one provided will just be a
# (json-safe) *string* if the original type was datetime
value = record[key]
if value is None:
return
return value.strftime(fmt or '%Y-%m-%d %I:%M:%S %p')
def grid_render_enum(self, record, key, value, enum=None):
"""
Custom grid value renderer for "enum" fields.
:param enum: Enum class for the field. This should be an
instance of :class:`~python:enum.Enum`.
To use this feature for your grid::
from enum import Enum
class MyEnum(Enum):
ONE = 1
TWO = 2
THREE = 3
grid.set_renderer('my_enum_field', self.grid_render_enum, enum=MyEnum)
"""
if enum:
original = record[key]
if original:
return original.name
return value
def grid_render_notes(self, record, key, value, maxlen=100): def grid_render_notes(self, record, key, value, maxlen=100):
""" """
Custom grid value renderer for "notes" fields. Custom grid value renderer for "notes" fields.

View file

@ -0,0 +1,187 @@
# -*- 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 <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Upgrade Views
"""
from sqlalchemy import orm
from wuttjamaican.db.model import Upgrade
from wuttaweb.views import MasterView
from wuttaweb.forms import widgets
from wuttaweb.forms.schema import UserRef, WuttaEnum
class UpgradeView(MasterView):
"""
Master view for upgrades.
Default route prefix is ``upgrades``.
Notable URLs provided by this class:
* ``/upgrades/``
* ``/upgrades/new``
* ``/upgrades/XXX``
* ``/upgrades/XXX/edit``
* ``/upgrades/XXX/delete``
"""
model_class = Upgrade
grid_columns = [
'created',
'description',
'status',
'executed',
'executed_by',
]
sort_defaults = ('created', 'desc')
def configure_grid(self, g):
""" """
super().configure_grid(g)
model = self.app.model
enum = self.app.enum
# description
g.set_link('description')
# created
g.set_renderer('created', self.grid_render_datetime)
# created_by
g.set_link('created_by')
Creator = orm.aliased(model.User)
g.set_joiner('created_by', lambda q: q.join(Creator,
Creator.uuid == model.Upgrade.created_by_uuid))
g.set_filter('created_by', Creator.username,
label="Created By Username")
# status
g.set_renderer('status', self.grid_render_enum, enum=enum.UpgradeStatus)
# executed_by
g.set_link('executed_by')
Executor = orm.aliased(model.User)
g.set_joiner('executed_by', lambda q: q.outerjoin(Executor,
Executor.uuid == model.Upgrade.executed_by_uuid))
g.set_filter('executed_by', Executor.username,
label="Executed By Username")
def grid_row_class(self, upgrade, data, i):
""" """
enum = self.app.enum
if upgrade.status == enum.UpgradeStatus.EXECUTING:
return 'has-background-warning'
if upgrade.status == enum.UpgradeStatus.FAILURE:
return 'has-background-warning'
def configure_form(self, f):
""" """
super().configure_form(f)
enum = self.app.enum
upgrade = f.model_instance
# never show these
f.remove('created_by_uuid',
'executing',
'executed_by_uuid')
# sequence sanity
f.fields.set_sequence([
'description',
'notes',
'status',
'created',
'created_by',
'executed',
'executed_by',
])
# created
if self.creating or self.editing:
f.remove('created')
# created_by
if self.creating or self.editing:
f.remove('created_by')
else:
f.set_node('created_by', UserRef(self.request))
# notes
f.set_widget('notes', widgets.NotesWidget())
# status
if self.creating:
f.remove('status')
else:
f.set_node('status', WuttaEnum(self.request, enum.UpgradeStatus))
# exit_code
if self.creating or not upgrade.executed:
f.remove('exit_code')
# executed
if self.creating or self.editing or not upgrade.executed:
f.remove('executed')
# executed_by
if self.creating or self.editing or not upgrade.executed:
f.remove('executed_by')
else:
f.set_node('executed_by', UserRef(self.request))
def objectify(self, form):
""" """
upgrade = super().objectify(form)
enum = self.app.enum
# set user, status when creating
if self.creating:
upgrade.created_by = self.request.user
upgrade.status = enum.UpgradeStatus.PENDING
return upgrade
@classmethod
def defaults(cls, config):
""" """
# nb. Upgrade may come from custom model
wutta_config = config.registry.settings['wutta_config']
app = wutta_config.get_app()
cls.model_class = app.model.Upgrade
cls._defaults(config)
def defaults(config, **kwargs):
base = globals()
UpgradeView = kwargs.get('UpgradeView', base['UpgradeView'])
UpgradeView.defaults(config)
def includeme(config):
defaults(config)

View file

@ -10,7 +10,7 @@ from sqlalchemy import orm
from wuttjamaican.conf import WuttaConfig from wuttjamaican.conf import WuttaConfig
from wuttaweb.forms import schema as mod from wuttaweb.forms import schema as mod
from wuttaweb.forms import widgets from wuttaweb.forms import widgets
from tests.util import DataTestCase from tests.util import DataTestCase, WebTestCase
class TestObjectNode(DataTestCase): class TestObjectNode(DataTestCase):
@ -47,6 +47,15 @@ class TestObjectNode(DataTestCase):
self.assertIs(value, person) self.assertIs(value, person)
class TestWuttaEnum(WebTestCase):
def test_widget_maker(self):
enum = self.app.enum
typ = mod.WuttaEnum(self.request, enum.UpgradeStatus)
widget = typ.widget_maker()
self.assertIsInstance(widget, widgets.SelectWidget)
class TestObjectRef(DataTestCase): class TestObjectRef(DataTestCase):
def setUp(self): def setUp(self):
@ -140,10 +149,17 @@ class TestObjectRef(DataTestCase):
self.session.commit() self.session.commit()
self.assertIsNotNone(person.uuid) self.assertIsNotNone(person.uuid)
with patch.object(mod.ObjectRef, 'model_class', new=model.Person): with patch.object(mod.ObjectRef, 'model_class', new=model.Person):
# can specify as uuid
typ = mod.ObjectRef(self.request, session=self.session) typ = mod.ObjectRef(self.request, session=self.session)
value = typ.objectify(person.uuid) value = typ.objectify(person.uuid)
self.assertIs(value, person) self.assertIs(value, person)
# or can specify object proper
typ = mod.ObjectRef(self.request, session=self.session)
value = typ.objectify(person)
self.assertIs(value, person)
# error if not found # error if not found
with patch.object(mod.ObjectRef, 'model_class', new=model.Person): with patch.object(mod.ObjectRef, 'model_class', new=model.Person):
typ = mod.ObjectRef(self.request, session=self.session) typ = mod.ObjectRef(self.request, session=self.session)
@ -186,11 +202,7 @@ class TestObjectRef(DataTestCase):
self.assertEqual(widget.values[1][1], "Betty Boop") self.assertEqual(widget.values[1][1], "Betty Boop")
class TestPersonRef(DataTestCase): class TestPersonRef(WebTestCase):
def setUp(self):
self.setup_db()
self.request = testing.DummyRequest(wutta_config=self.config)
def test_sort_query(self): def test_sort_query(self):
typ = mod.PersonRef(self.request, session=self.session) typ = mod.PersonRef(self.request, session=self.session)
@ -200,6 +212,43 @@ class TestPersonRef(DataTestCase):
self.assertIsInstance(sorted_query, orm.Query) self.assertIsInstance(sorted_query, orm.Query)
self.assertIsNot(sorted_query, query) self.assertIsNot(sorted_query, query)
def test_get_object_url(self):
self.pyramid_config.add_route('people.view', '/people/{uuid}')
model = self.app.model
typ = mod.PersonRef(self.request, session=self.session)
person = model.Person(full_name="Barney Rubble")
self.session.add(person)
self.session.commit()
url = typ.get_object_url(person)
self.assertIsNotNone(url)
self.assertIn(f'/people/{person.uuid}', url)
class TestUserRef(WebTestCase):
def test_sort_query(self):
typ = mod.UserRef(self.request, session=self.session)
query = typ.get_query()
self.assertIsInstance(query, orm.Query)
sorted_query = typ.sort_query(query)
self.assertIsInstance(sorted_query, orm.Query)
self.assertIsNot(sorted_query, query)
def test_get_object_url(self):
self.pyramid_config.add_route('users.view', '/users/{uuid}')
model = self.app.model
typ = mod.UserRef(self.request, session=self.session)
user = model.User(username='barney')
self.session.add(user)
self.session.commit()
url = typ.get_object_url(user)
self.assertIsNotNone(url)
self.assertIn(f'/users/{user.uuid}', url)
class TestUserRefs(DataTestCase): class TestUserRefs(DataTestCase):

View file

@ -9,13 +9,13 @@ from fanstatic import Library, Resource
from pyramid import testing from pyramid import testing
from wuttjamaican.conf import WuttaConfig from wuttjamaican.conf import WuttaConfig
from wuttaweb import util from wuttaweb import util as mod
class TestFieldList(TestCase): class TestFieldList(TestCase):
def test_insert_before(self): def test_insert_before(self):
fields = util.FieldList(['f1', 'f2']) fields = mod.FieldList(['f1', 'f2'])
self.assertEqual(fields, ['f1', 'f2']) self.assertEqual(fields, ['f1', 'f2'])
# typical # typical
@ -29,7 +29,7 @@ class TestFieldList(TestCase):
self.assertEqual(fields, ['XXX', 'f1', 'YYY', 'f2', 'ZZZ']) self.assertEqual(fields, ['XXX', 'f1', 'YYY', 'f2', 'ZZZ'])
def test_insert_after(self): def test_insert_after(self):
fields = util.FieldList(['f1', 'f2']) fields = mod.FieldList(['f1', 'f2'])
self.assertEqual(fields, ['f1', 'f2']) self.assertEqual(fields, ['f1', 'f2'])
# typical # typical
@ -42,6 +42,14 @@ class TestFieldList(TestCase):
fields.insert_after('f3', 'ZZZ') fields.insert_after('f3', 'ZZZ')
self.assertEqual(fields, ['f1', 'XXX', 'YYY', 'f2', 'ZZZ']) self.assertEqual(fields, ['f1', 'XXX', 'YYY', 'f2', 'ZZZ'])
def test_set_sequence(self):
fields = mod.FieldList(['f5', 'f1', 'f3', 'f4', 'f2'])
# setting sequence will only "sort" for explicit fields.
# other fields remain in original order, but at the end.
fields.set_sequence(['f1', 'f2', 'f3'])
self.assertEqual(fields, ['f1', 'f2', 'f3', 'f5', 'f4'])
class TestGetLibVer(TestCase): class TestGetLibVer(TestCase):
@ -51,153 +59,153 @@ class TestGetLibVer(TestCase):
self.request.wutta_config = self.config self.request.wutta_config = self.config
def test_buefy_default(self): def test_buefy_default(self):
version = util.get_libver(self.request, 'buefy') version = mod.get_libver(self.request, 'buefy')
self.assertEqual(version, 'latest') self.assertEqual(version, 'latest')
def test_buefy_custom_old(self): def test_buefy_custom_old(self):
self.config.setdefault('wuttaweb.buefy_version', '0.9.29') self.config.setdefault('wuttaweb.buefy_version', '0.9.29')
version = util.get_libver(self.request, 'buefy') version = mod.get_libver(self.request, 'buefy')
self.assertEqual(version, '0.9.29') self.assertEqual(version, '0.9.29')
def test_buefy_custom_old_tailbone(self): def test_buefy_custom_old_tailbone(self):
self.config.setdefault('tailbone.libver.buefy', '0.9.28') self.config.setdefault('tailbone.libver.buefy', '0.9.28')
version = util.get_libver(self.request, 'buefy', prefix='tailbone') version = mod.get_libver(self.request, 'buefy', prefix='tailbone')
self.assertEqual(version, '0.9.28') self.assertEqual(version, '0.9.28')
def test_buefy_custom_new(self): def test_buefy_custom_new(self):
self.config.setdefault('wuttaweb.libver.buefy', '0.9.29') self.config.setdefault('wuttaweb.libver.buefy', '0.9.29')
version = util.get_libver(self.request, 'buefy') version = mod.get_libver(self.request, 'buefy')
self.assertEqual(version, '0.9.29') self.assertEqual(version, '0.9.29')
def test_buefy_configured_only(self): def test_buefy_configured_only(self):
version = util.get_libver(self.request, 'buefy', configured_only=True) version = mod.get_libver(self.request, 'buefy', configured_only=True)
self.assertIsNone(version) self.assertIsNone(version)
def test_buefy_default_only(self): def test_buefy_default_only(self):
self.config.setdefault('wuttaweb.libver.buefy', '0.9.29') self.config.setdefault('wuttaweb.libver.buefy', '0.9.29')
version = util.get_libver(self.request, 'buefy', default_only=True) version = mod.get_libver(self.request, 'buefy', default_only=True)
self.assertEqual(version, 'latest') self.assertEqual(version, 'latest')
def test_buefy_css_default(self): def test_buefy_css_default(self):
version = util.get_libver(self.request, 'buefy.css') version = mod.get_libver(self.request, 'buefy.css')
self.assertEqual(version, 'latest') self.assertEqual(version, 'latest')
def test_buefy_css_custom_old(self): def test_buefy_css_custom_old(self):
# nb. this uses same setting as buefy (js) # nb. this uses same setting as buefy (js)
self.config.setdefault('wuttaweb.buefy_version', '0.9.29') self.config.setdefault('wuttaweb.buefy_version', '0.9.29')
version = util.get_libver(self.request, 'buefy.css') version = mod.get_libver(self.request, 'buefy.css')
self.assertEqual(version, '0.9.29') self.assertEqual(version, '0.9.29')
def test_buefy_css_custom_new(self): def test_buefy_css_custom_new(self):
# nb. this uses same setting as buefy (js) # nb. this uses same setting as buefy (js)
self.config.setdefault('wuttaweb.libver.buefy', '0.9.29') self.config.setdefault('wuttaweb.libver.buefy', '0.9.29')
version = util.get_libver(self.request, 'buefy.css') version = mod.get_libver(self.request, 'buefy.css')
self.assertEqual(version, '0.9.29') self.assertEqual(version, '0.9.29')
def test_buefy_css_configured_only(self): def test_buefy_css_configured_only(self):
version = util.get_libver(self.request, 'buefy.css', configured_only=True) version = mod.get_libver(self.request, 'buefy.css', configured_only=True)
self.assertIsNone(version) self.assertIsNone(version)
def test_buefy_css_default_only(self): def test_buefy_css_default_only(self):
self.config.setdefault('wuttaweb.libver.buefy', '0.9.29') self.config.setdefault('wuttaweb.libver.buefy', '0.9.29')
version = util.get_libver(self.request, 'buefy.css', default_only=True) version = mod.get_libver(self.request, 'buefy.css', default_only=True)
self.assertEqual(version, 'latest') self.assertEqual(version, 'latest')
def test_vue_default(self): def test_vue_default(self):
version = util.get_libver(self.request, 'vue') version = mod.get_libver(self.request, 'vue')
self.assertEqual(version, '2.6.14') self.assertEqual(version, '2.6.14')
def test_vue_custom_old(self): def test_vue_custom_old(self):
self.config.setdefault('wuttaweb.vue_version', '3.4.31') self.config.setdefault('wuttaweb.vue_version', '3.4.31')
version = util.get_libver(self.request, 'vue') version = mod.get_libver(self.request, 'vue')
self.assertEqual(version, '3.4.31') self.assertEqual(version, '3.4.31')
def test_vue_custom_new(self): def test_vue_custom_new(self):
self.config.setdefault('wuttaweb.libver.vue', '3.4.31') self.config.setdefault('wuttaweb.libver.vue', '3.4.31')
version = util.get_libver(self.request, 'vue') version = mod.get_libver(self.request, 'vue')
self.assertEqual(version, '3.4.31') self.assertEqual(version, '3.4.31')
def test_vue_configured_only(self): def test_vue_configured_only(self):
version = util.get_libver(self.request, 'vue', configured_only=True) version = mod.get_libver(self.request, 'vue', configured_only=True)
self.assertIsNone(version) self.assertIsNone(version)
def test_vue_default_only(self): def test_vue_default_only(self):
self.config.setdefault('wuttaweb.libver.vue', '3.4.31') self.config.setdefault('wuttaweb.libver.vue', '3.4.31')
version = util.get_libver(self.request, 'vue', default_only=True) version = mod.get_libver(self.request, 'vue', default_only=True)
self.assertEqual(version, '2.6.14') self.assertEqual(version, '2.6.14')
def test_vue_resource_default(self): def test_vue_resource_default(self):
version = util.get_libver(self.request, 'vue_resource') version = mod.get_libver(self.request, 'vue_resource')
self.assertEqual(version, 'latest') self.assertEqual(version, 'latest')
def test_vue_resource_custom(self): def test_vue_resource_custom(self):
self.config.setdefault('wuttaweb.libver.vue_resource', '1.5.3') self.config.setdefault('wuttaweb.libver.vue_resource', '1.5.3')
version = util.get_libver(self.request, 'vue_resource') version = mod.get_libver(self.request, 'vue_resource')
self.assertEqual(version, '1.5.3') self.assertEqual(version, '1.5.3')
def test_fontawesome_default(self): def test_fontawesome_default(self):
version = util.get_libver(self.request, 'fontawesome') version = mod.get_libver(self.request, 'fontawesome')
self.assertEqual(version, '5.3.1') self.assertEqual(version, '5.3.1')
def test_fontawesome_custom(self): def test_fontawesome_custom(self):
self.config.setdefault('wuttaweb.libver.fontawesome', '5.6.3') self.config.setdefault('wuttaweb.libver.fontawesome', '5.6.3')
version = util.get_libver(self.request, 'fontawesome') version = mod.get_libver(self.request, 'fontawesome')
self.assertEqual(version, '5.6.3') self.assertEqual(version, '5.6.3')
def test_bb_vue_default(self): def test_bb_vue_default(self):
version = util.get_libver(self.request, 'bb_vue') version = mod.get_libver(self.request, 'bb_vue')
self.assertEqual(version, '3.4.31') self.assertEqual(version, '3.4.31')
def test_bb_vue_custom(self): def test_bb_vue_custom(self):
self.config.setdefault('wuttaweb.libver.bb_vue', '3.4.30') self.config.setdefault('wuttaweb.libver.bb_vue', '3.4.30')
version = util.get_libver(self.request, 'bb_vue') version = mod.get_libver(self.request, 'bb_vue')
self.assertEqual(version, '3.4.30') self.assertEqual(version, '3.4.30')
def test_bb_oruga_default(self): def test_bb_oruga_default(self):
version = util.get_libver(self.request, 'bb_oruga') version = mod.get_libver(self.request, 'bb_oruga')
self.assertEqual(version, '0.8.12') self.assertEqual(version, '0.8.12')
def test_bb_oruga_custom(self): def test_bb_oruga_custom(self):
self.config.setdefault('wuttaweb.libver.bb_oruga', '0.8.11') self.config.setdefault('wuttaweb.libver.bb_oruga', '0.8.11')
version = util.get_libver(self.request, 'bb_oruga') version = mod.get_libver(self.request, 'bb_oruga')
self.assertEqual(version, '0.8.11') self.assertEqual(version, '0.8.11')
def test_bb_oruga_bulma_default(self): def test_bb_oruga_bulma_default(self):
version = util.get_libver(self.request, 'bb_oruga_bulma') version = mod.get_libver(self.request, 'bb_oruga_bulma')
self.assertEqual(version, '0.3.0') self.assertEqual(version, '0.3.0')
version = util.get_libver(self.request, 'bb_oruga_bulma_css') version = mod.get_libver(self.request, 'bb_oruga_bulma_css')
self.assertEqual(version, '0.3.0') self.assertEqual(version, '0.3.0')
def test_bb_oruga_bulma_custom(self): def test_bb_oruga_bulma_custom(self):
self.config.setdefault('wuttaweb.libver.bb_oruga_bulma', '0.2.11') self.config.setdefault('wuttaweb.libver.bb_oruga_bulma', '0.2.11')
version = util.get_libver(self.request, 'bb_oruga_bulma') version = mod.get_libver(self.request, 'bb_oruga_bulma')
self.assertEqual(version, '0.2.11') self.assertEqual(version, '0.2.11')
def test_bb_fontawesome_svg_core_default(self): def test_bb_fontawesome_svg_core_default(self):
version = util.get_libver(self.request, 'bb_fontawesome_svg_core') version = mod.get_libver(self.request, 'bb_fontawesome_svg_core')
self.assertEqual(version, '6.5.2') self.assertEqual(version, '6.5.2')
def test_bb_fontawesome_svg_core_custom(self): def test_bb_fontawesome_svg_core_custom(self):
self.config.setdefault('wuttaweb.libver.bb_fontawesome_svg_core', '6.5.1') self.config.setdefault('wuttaweb.libver.bb_fontawesome_svg_core', '6.5.1')
version = util.get_libver(self.request, 'bb_fontawesome_svg_core') version = mod.get_libver(self.request, 'bb_fontawesome_svg_core')
self.assertEqual(version, '6.5.1') self.assertEqual(version, '6.5.1')
def test_bb_free_solid_svg_icons_default(self): def test_bb_free_solid_svg_icons_default(self):
version = util.get_libver(self.request, 'bb_free_solid_svg_icons') version = mod.get_libver(self.request, 'bb_free_solid_svg_icons')
self.assertEqual(version, '6.5.2') self.assertEqual(version, '6.5.2')
def test_bb_free_solid_svg_icons_custom(self): def test_bb_free_solid_svg_icons_custom(self):
self.config.setdefault('wuttaweb.libver.bb_free_solid_svg_icons', '6.5.1') self.config.setdefault('wuttaweb.libver.bb_free_solid_svg_icons', '6.5.1')
version = util.get_libver(self.request, 'bb_free_solid_svg_icons') version = mod.get_libver(self.request, 'bb_free_solid_svg_icons')
self.assertEqual(version, '6.5.1') self.assertEqual(version, '6.5.1')
def test_bb_vue_fontawesome_default(self): def test_bb_vue_fontawesome_default(self):
version = util.get_libver(self.request, 'bb_vue_fontawesome') version = mod.get_libver(self.request, 'bb_vue_fontawesome')
self.assertEqual(version, '3.0.6') self.assertEqual(version, '3.0.6')
def test_bb_vue_fontawesome_custom(self): def test_bb_vue_fontawesome_custom(self):
self.config.setdefault('wuttaweb.libver.bb_vue_fontawesome', '3.0.8') self.config.setdefault('wuttaweb.libver.bb_vue_fontawesome', '3.0.8')
version = util.get_libver(self.request, 'bb_vue_fontawesome') version = mod.get_libver(self.request, 'bb_vue_fontawesome')
self.assertEqual(version, '3.0.8') self.assertEqual(version, '3.0.8')
@ -238,191 +246,191 @@ class TestGetLibUrl(TestCase):
self.request.script_name = '/wutta' self.request.script_name = '/wutta'
def test_buefy_default(self): def test_buefy_default(self):
url = util.get_liburl(self.request, 'buefy') url = mod.get_liburl(self.request, 'buefy')
self.assertEqual(url, 'https://unpkg.com/buefy@latest/dist/buefy.min.js') self.assertEqual(url, 'https://unpkg.com/buefy@latest/dist/buefy.min.js')
def test_buefy_custom(self): def test_buefy_custom(self):
self.config.setdefault('wuttaweb.liburl.buefy', '/lib/buefy.js') self.config.setdefault('wuttaweb.liburl.buefy', '/lib/buefy.js')
url = util.get_liburl(self.request, 'buefy') url = mod.get_liburl(self.request, 'buefy')
self.assertEqual(url, '/lib/buefy.js') self.assertEqual(url, '/lib/buefy.js')
def test_buefy_custom_tailbone(self): def test_buefy_custom_tailbone(self):
self.config.setdefault('tailbone.liburl.buefy', '/tailbone/buefy.js') self.config.setdefault('tailbone.liburl.buefy', '/tailbone/buefy.js')
url = util.get_liburl(self.request, 'buefy', prefix='tailbone') url = mod.get_liburl(self.request, 'buefy', prefix='tailbone')
self.assertEqual(url, '/tailbone/buefy.js') self.assertEqual(url, '/tailbone/buefy.js')
def test_buefy_default_only(self): def test_buefy_default_only(self):
self.config.setdefault('wuttaweb.liburl.buefy', '/lib/buefy.js') self.config.setdefault('wuttaweb.liburl.buefy', '/lib/buefy.js')
url = util.get_liburl(self.request, 'buefy', default_only=True) url = mod.get_liburl(self.request, 'buefy', default_only=True)
self.assertEqual(url, 'https://unpkg.com/buefy@latest/dist/buefy.min.js') self.assertEqual(url, 'https://unpkg.com/buefy@latest/dist/buefy.min.js')
def test_buefy_configured_only(self): def test_buefy_configured_only(self):
url = util.get_liburl(self.request, 'buefy', configured_only=True) url = mod.get_liburl(self.request, 'buefy', configured_only=True)
self.assertIsNone(url) self.assertIsNone(url)
def test_buefy_fanstatic(self): def test_buefy_fanstatic(self):
self.setup_fanstatic() self.setup_fanstatic()
url = util.get_liburl(self.request, 'buefy') url = mod.get_liburl(self.request, 'buefy')
self.assertEqual(url, '/wutta/fanstatic/buefy.js') self.assertEqual(url, '/wutta/fanstatic/buefy.js')
def test_buefy_fanstatic_tailbone(self): def test_buefy_fanstatic_tailbone(self):
self.setup_fanstatic(register=False) self.setup_fanstatic(register=False)
self.config.setdefault('tailbone.static_libcache.module', 'tests.test_util') self.config.setdefault('tailbone.static_libcache.module', 'tests.test_util')
url = util.get_liburl(self.request, 'buefy', prefix='tailbone') url = mod.get_liburl(self.request, 'buefy', prefix='tailbone')
self.assertEqual(url, '/wutta/fanstatic/buefy.js') self.assertEqual(url, '/wutta/fanstatic/buefy.js')
def test_buefy_css_default(self): def test_buefy_css_default(self):
url = util.get_liburl(self.request, 'buefy.css') url = mod.get_liburl(self.request, 'buefy.css')
self.assertEqual(url, 'https://unpkg.com/buefy@latest/dist/buefy.min.css') self.assertEqual(url, 'https://unpkg.com/buefy@latest/dist/buefy.min.css')
def test_buefy_css_custom(self): def test_buefy_css_custom(self):
self.config.setdefault('wuttaweb.liburl.buefy.css', '/lib/buefy.css') self.config.setdefault('wuttaweb.liburl.buefy.css', '/lib/buefy.css')
url = util.get_liburl(self.request, 'buefy.css') url = mod.get_liburl(self.request, 'buefy.css')
self.assertEqual(url, '/lib/buefy.css') self.assertEqual(url, '/lib/buefy.css')
def test_buefy_css_fanstatic(self): def test_buefy_css_fanstatic(self):
self.setup_fanstatic() self.setup_fanstatic()
url = util.get_liburl(self.request, 'buefy.css') url = mod.get_liburl(self.request, 'buefy.css')
self.assertEqual(url, '/wutta/fanstatic/buefy.css') self.assertEqual(url, '/wutta/fanstatic/buefy.css')
def test_vue_default(self): def test_vue_default(self):
url = util.get_liburl(self.request, 'vue') url = mod.get_liburl(self.request, 'vue')
self.assertEqual(url, 'https://unpkg.com/vue@2.6.14/dist/vue.min.js') self.assertEqual(url, 'https://unpkg.com/vue@2.6.14/dist/vue.min.js')
def test_vue_custom(self): def test_vue_custom(self):
self.config.setdefault('wuttaweb.liburl.vue', '/lib/vue.js') self.config.setdefault('wuttaweb.liburl.vue', '/lib/vue.js')
url = util.get_liburl(self.request, 'vue') url = mod.get_liburl(self.request, 'vue')
self.assertEqual(url, '/lib/vue.js') self.assertEqual(url, '/lib/vue.js')
def test_vue_fanstatic(self): def test_vue_fanstatic(self):
self.setup_fanstatic() self.setup_fanstatic()
url = util.get_liburl(self.request, 'vue') url = mod.get_liburl(self.request, 'vue')
self.assertEqual(url, '/wutta/fanstatic/vue.js') self.assertEqual(url, '/wutta/fanstatic/vue.js')
def test_vue_resource_default(self): def test_vue_resource_default(self):
url = util.get_liburl(self.request, 'vue_resource') url = mod.get_liburl(self.request, 'vue_resource')
self.assertEqual(url, 'https://cdn.jsdelivr.net/npm/vue-resource@latest') self.assertEqual(url, 'https://cdn.jsdelivr.net/npm/vue-resource@latest')
def test_vue_resource_custom(self): def test_vue_resource_custom(self):
self.config.setdefault('wuttaweb.liburl.vue_resource', '/lib/vue-resource.js') self.config.setdefault('wuttaweb.liburl.vue_resource', '/lib/vue-resource.js')
url = util.get_liburl(self.request, 'vue_resource') url = mod.get_liburl(self.request, 'vue_resource')
self.assertEqual(url, '/lib/vue-resource.js') self.assertEqual(url, '/lib/vue-resource.js')
def test_vue_resource_fanstatic(self): def test_vue_resource_fanstatic(self):
self.setup_fanstatic() self.setup_fanstatic()
url = util.get_liburl(self.request, 'vue_resource') url = mod.get_liburl(self.request, 'vue_resource')
self.assertEqual(url, '/wutta/fanstatic/vue_resource.js') self.assertEqual(url, '/wutta/fanstatic/vue_resource.js')
def test_fontawesome_default(self): def test_fontawesome_default(self):
url = util.get_liburl(self.request, 'fontawesome') url = mod.get_liburl(self.request, 'fontawesome')
self.assertEqual(url, 'https://use.fontawesome.com/releases/v5.3.1/js/all.js') self.assertEqual(url, 'https://use.fontawesome.com/releases/v5.3.1/js/all.js')
def test_fontawesome_custom(self): def test_fontawesome_custom(self):
self.config.setdefault('wuttaweb.liburl.fontawesome', '/lib/fontawesome.js') self.config.setdefault('wuttaweb.liburl.fontawesome', '/lib/fontawesome.js')
url = util.get_liburl(self.request, 'fontawesome') url = mod.get_liburl(self.request, 'fontawesome')
self.assertEqual(url, '/lib/fontawesome.js') self.assertEqual(url, '/lib/fontawesome.js')
def test_fontawesome_fanstatic(self): def test_fontawesome_fanstatic(self):
self.setup_fanstatic() self.setup_fanstatic()
url = util.get_liburl(self.request, 'fontawesome') url = mod.get_liburl(self.request, 'fontawesome')
self.assertEqual(url, '/wutta/fanstatic/fontawesome.js') self.assertEqual(url, '/wutta/fanstatic/fontawesome.js')
def test_bb_vue_default(self): def test_bb_vue_default(self):
url = util.get_liburl(self.request, 'bb_vue') url = mod.get_liburl(self.request, 'bb_vue')
self.assertEqual(url, 'https://unpkg.com/vue@3.4.31/dist/vue.esm-browser.prod.js') self.assertEqual(url, 'https://unpkg.com/vue@3.4.31/dist/vue.esm-browser.prod.js')
def test_bb_vue_custom(self): def test_bb_vue_custom(self):
self.config.setdefault('wuttaweb.liburl.bb_vue', '/lib/vue.js') self.config.setdefault('wuttaweb.liburl.bb_vue', '/lib/vue.js')
url = util.get_liburl(self.request, 'bb_vue') url = mod.get_liburl(self.request, 'bb_vue')
self.assertEqual(url, '/lib/vue.js') self.assertEqual(url, '/lib/vue.js')
def test_bb_vue_fanstatic(self): def test_bb_vue_fanstatic(self):
self.setup_fanstatic() self.setup_fanstatic()
url = util.get_liburl(self.request, 'bb_vue') url = mod.get_liburl(self.request, 'bb_vue')
self.assertEqual(url, '/wutta/fanstatic/bb_vue.js') self.assertEqual(url, '/wutta/fanstatic/bb_vue.js')
def test_bb_oruga_default(self): def test_bb_oruga_default(self):
url = util.get_liburl(self.request, 'bb_oruga') url = mod.get_liburl(self.request, 'bb_oruga')
self.assertEqual(url, 'https://unpkg.com/@oruga-ui/oruga-next@0.8.12/dist/oruga.mjs') self.assertEqual(url, 'https://unpkg.com/@oruga-ui/oruga-next@0.8.12/dist/oruga.mjs')
def test_bb_oruga_custom(self): def test_bb_oruga_custom(self):
self.config.setdefault('wuttaweb.liburl.bb_oruga', '/lib/oruga.js') self.config.setdefault('wuttaweb.liburl.bb_oruga', '/lib/oruga.js')
url = util.get_liburl(self.request, 'bb_oruga') url = mod.get_liburl(self.request, 'bb_oruga')
self.assertEqual(url, '/lib/oruga.js') self.assertEqual(url, '/lib/oruga.js')
def test_bb_oruga_fanstatic(self): def test_bb_oruga_fanstatic(self):
self.setup_fanstatic() self.setup_fanstatic()
url = util.get_liburl(self.request, 'bb_oruga') url = mod.get_liburl(self.request, 'bb_oruga')
self.assertEqual(url, '/wutta/fanstatic/bb_oruga.js') self.assertEqual(url, '/wutta/fanstatic/bb_oruga.js')
def test_bb_oruga_bulma_default(self): def test_bb_oruga_bulma_default(self):
url = util.get_liburl(self.request, 'bb_oruga_bulma') url = mod.get_liburl(self.request, 'bb_oruga_bulma')
self.assertEqual(url, 'https://unpkg.com/@oruga-ui/theme-bulma@0.3.0/dist/bulma.mjs') self.assertEqual(url, 'https://unpkg.com/@oruga-ui/theme-bulma@0.3.0/dist/bulma.mjs')
def test_bb_oruga_bulma_custom(self): def test_bb_oruga_bulma_custom(self):
self.config.setdefault('wuttaweb.liburl.bb_oruga_bulma', '/lib/oruga_bulma.js') self.config.setdefault('wuttaweb.liburl.bb_oruga_bulma', '/lib/oruga_bulma.js')
url = util.get_liburl(self.request, 'bb_oruga_bulma') url = mod.get_liburl(self.request, 'bb_oruga_bulma')
self.assertEqual(url, '/lib/oruga_bulma.js') self.assertEqual(url, '/lib/oruga_bulma.js')
def test_bb_oruga_bulma_fanstatic(self): def test_bb_oruga_bulma_fanstatic(self):
self.setup_fanstatic() self.setup_fanstatic()
url = util.get_liburl(self.request, 'bb_oruga_bulma') url = mod.get_liburl(self.request, 'bb_oruga_bulma')
self.assertEqual(url, '/wutta/fanstatic/bb_oruga_bulma.js') self.assertEqual(url, '/wutta/fanstatic/bb_oruga_bulma.js')
def test_bb_oruga_bulma_css_default(self): def test_bb_oruga_bulma_css_default(self):
url = util.get_liburl(self.request, 'bb_oruga_bulma_css') url = mod.get_liburl(self.request, 'bb_oruga_bulma_css')
self.assertEqual(url, 'https://unpkg.com/@oruga-ui/theme-bulma@0.3.0/dist/bulma.css') self.assertEqual(url, 'https://unpkg.com/@oruga-ui/theme-bulma@0.3.0/dist/bulma.css')
def test_bb_oruga_bulma_css_custom(self): def test_bb_oruga_bulma_css_custom(self):
self.config.setdefault('wuttaweb.liburl.bb_oruga_bulma_css', '/lib/oruga-bulma.css') self.config.setdefault('wuttaweb.liburl.bb_oruga_bulma_css', '/lib/oruga-bulma.css')
url = util.get_liburl(self.request, 'bb_oruga_bulma_css') url = mod.get_liburl(self.request, 'bb_oruga_bulma_css')
self.assertEqual(url, '/lib/oruga-bulma.css') self.assertEqual(url, '/lib/oruga-bulma.css')
def test_bb_oruga_bulma_css_fanstatic(self): def test_bb_oruga_bulma_css_fanstatic(self):
self.setup_fanstatic() self.setup_fanstatic()
url = util.get_liburl(self.request, 'bb_oruga_bulma_css') url = mod.get_liburl(self.request, 'bb_oruga_bulma_css')
self.assertEqual(url, '/wutta/fanstatic/bb_oruga_bulma.css') self.assertEqual(url, '/wutta/fanstatic/bb_oruga_bulma.css')
def test_bb_fontawesome_svg_core_default(self): def test_bb_fontawesome_svg_core_default(self):
url = util.get_liburl(self.request, 'bb_fontawesome_svg_core') url = mod.get_liburl(self.request, 'bb_fontawesome_svg_core')
self.assertEqual(url, 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-svg-core@6.5.2/+esm') self.assertEqual(url, 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-svg-core@6.5.2/+esm')
def test_bb_fontawesome_svg_core_custom(self): def test_bb_fontawesome_svg_core_custom(self):
self.config.setdefault('wuttaweb.liburl.bb_fontawesome_svg_core', '/lib/fontawesome-svg-core.js') self.config.setdefault('wuttaweb.liburl.bb_fontawesome_svg_core', '/lib/fontawesome-svg-core.js')
url = util.get_liburl(self.request, 'bb_fontawesome_svg_core') url = mod.get_liburl(self.request, 'bb_fontawesome_svg_core')
self.assertEqual(url, '/lib/fontawesome-svg-core.js') self.assertEqual(url, '/lib/fontawesome-svg-core.js')
def test_bb_fontawesome_svg_core_fanstatic(self): def test_bb_fontawesome_svg_core_fanstatic(self):
self.setup_fanstatic() self.setup_fanstatic()
url = util.get_liburl(self.request, 'bb_fontawesome_svg_core') url = mod.get_liburl(self.request, 'bb_fontawesome_svg_core')
self.assertEqual(url, '/wutta/fanstatic/bb_fontawesome_svg_core.js') self.assertEqual(url, '/wutta/fanstatic/bb_fontawesome_svg_core.js')
def test_bb_free_solid_svg_icons_default(self): def test_bb_free_solid_svg_icons_default(self):
url = util.get_liburl(self.request, 'bb_free_solid_svg_icons') url = mod.get_liburl(self.request, 'bb_free_solid_svg_icons')
self.assertEqual(url, 'https://cdn.jsdelivr.net/npm/@fortawesome/free-solid-svg-icons@6.5.2/+esm') self.assertEqual(url, 'https://cdn.jsdelivr.net/npm/@fortawesome/free-solid-svg-icons@6.5.2/+esm')
def test_bb_free_solid_svg_icons_custom(self): def test_bb_free_solid_svg_icons_custom(self):
self.config.setdefault('wuttaweb.liburl.bb_free_solid_svg_icons', '/lib/free-solid-svg-icons.js') self.config.setdefault('wuttaweb.liburl.bb_free_solid_svg_icons', '/lib/free-solid-svg-icons.js')
url = util.get_liburl(self.request, 'bb_free_solid_svg_icons') url = mod.get_liburl(self.request, 'bb_free_solid_svg_icons')
self.assertEqual(url, '/lib/free-solid-svg-icons.js') self.assertEqual(url, '/lib/free-solid-svg-icons.js')
def test_bb_free_solid_svg_icons_fanstatic(self): def test_bb_free_solid_svg_icons_fanstatic(self):
self.setup_fanstatic() self.setup_fanstatic()
url = util.get_liburl(self.request, 'bb_free_solid_svg_icons') url = mod.get_liburl(self.request, 'bb_free_solid_svg_icons')
self.assertEqual(url, '/wutta/fanstatic/bb_free_solid_svg_icons.js') self.assertEqual(url, '/wutta/fanstatic/bb_free_solid_svg_icons.js')
def test_bb_vue_fontawesome_default(self): def test_bb_vue_fontawesome_default(self):
url = util.get_liburl(self.request, 'bb_vue_fontawesome') url = mod.get_liburl(self.request, 'bb_vue_fontawesome')
self.assertEqual(url, 'https://cdn.jsdelivr.net/npm/@fortawesome/vue-fontawesome@3.0.6/+esm') self.assertEqual(url, 'https://cdn.jsdelivr.net/npm/@fortawesome/vue-fontawesome@3.0.6/+esm')
def test_bb_vue_fontawesome_custom(self): def test_bb_vue_fontawesome_custom(self):
self.config.setdefault('wuttaweb.liburl.bb_vue_fontawesome', '/lib/vue-fontawesome.js') self.config.setdefault('wuttaweb.liburl.bb_vue_fontawesome', '/lib/vue-fontawesome.js')
url = util.get_liburl(self.request, 'bb_vue_fontawesome') url = mod.get_liburl(self.request, 'bb_vue_fontawesome')
self.assertEqual(url, '/lib/vue-fontawesome.js') self.assertEqual(url, '/lib/vue-fontawesome.js')
def test_bb_vue_fontawesome_fanstatic(self): def test_bb_vue_fontawesome_fanstatic(self):
self.setup_fanstatic() self.setup_fanstatic()
url = util.get_liburl(self.request, 'bb_vue_fontawesome') url = mod.get_liburl(self.request, 'bb_vue_fontawesome')
self.assertEqual(url, '/wutta/fanstatic/bb_vue_fontawesome.js') self.assertEqual(url, '/wutta/fanstatic/bb_vue_fontawesome.js')
@ -439,17 +447,17 @@ class TestGetFormData(TestCase):
def test_default(self): def test_default(self):
request = self.make_request() request = self.make_request()
data = util.get_form_data(request) data = mod.get_form_data(request)
self.assertEqual(data, {'foo1': 'bar'}) self.assertEqual(data, {'foo1': 'bar'})
def test_is_xhr(self): def test_is_xhr(self):
request = self.make_request(POST=None, is_xhr=True) request = self.make_request(POST=None, is_xhr=True)
data = util.get_form_data(request) data = mod.get_form_data(request)
self.assertEqual(data, {'foo2': 'baz'}) self.assertEqual(data, {'foo2': 'baz'})
def test_content_type(self): def test_content_type(self):
request = self.make_request(POST=None, content_type='application/json') request = self.make_request(POST=None, content_type='application/json')
data = util.get_form_data(request) data = mod.get_form_data(request)
self.assertEqual(data, {'foo2': 'baz'}) self.assertEqual(data, {'foo2': 'baz'})
@ -460,16 +468,16 @@ class TestGetModelFields(TestCase):
self.app = self.config.get_app() self.app = self.config.get_app()
def test_empty_model_class(self): def test_empty_model_class(self):
fields = util.get_model_fields(self.config) fields = mod.get_model_fields(self.config)
self.assertIsNone(fields) self.assertIsNone(fields)
def test_unknown_model_class(self): def test_unknown_model_class(self):
fields = util.get_model_fields(self.config, TestCase) fields = mod.get_model_fields(self.config, TestCase)
self.assertIsNone(fields) self.assertIsNone(fields)
def test_basic(self): def test_basic(self):
model = self.app.model model = self.app.model
fields = util.get_model_fields(self.config, model.Setting) fields = mod.get_model_fields(self.config, model.Setting)
self.assertEqual(fields, ['name', 'value']) self.assertEqual(fields, ['name', 'value'])
@ -484,9 +492,9 @@ class TestGetCsrfToken(TestCase):
# same token returned for same request # same token returned for same request
# TODO: dummy request is always returning same token! # TODO: dummy request is always returning same token!
# so this isn't really testing anything.. :( # so this isn't really testing anything.. :(
first = util.get_csrf_token(self.request) first = mod.get_csrf_token(self.request)
self.assertIsNotNone(first) self.assertIsNotNone(first)
second = util.get_csrf_token(self.request) second = mod.get_csrf_token(self.request)
self.assertEqual(first, second) self.assertEqual(first, second)
# TODO: ideally would make a new request here and confirm it # TODO: ideally would make a new request here and confirm it
@ -497,7 +505,7 @@ class TestGetCsrfToken(TestCase):
# nb. dummy request always returns same token, so must # nb. dummy request always returns same token, so must
# trick it into thinking it doesn't have one yet # trick it into thinking it doesn't have one yet
with patch.object(self.request.session, 'get_csrf_token', return_value=None): with patch.object(self.request.session, 'get_csrf_token', return_value=None):
token = util.get_csrf_token(self.request) token = mod.get_csrf_token(self.request)
self.assertIsNotNone(token) self.assertIsNotNone(token)
@ -508,10 +516,10 @@ class TestRenderCsrfToken(TestCase):
self.request = testing.DummyRequest(wutta_config=self.config) self.request = testing.DummyRequest(wutta_config=self.config)
def test_basics(self): def test_basics(self):
html = util.render_csrf_token(self.request) html = mod.render_csrf_token(self.request)
self.assertIn('type="hidden"', html) self.assertIn('type="hidden"', html)
self.assertIn('name="_csrf"', html) self.assertIn('name="_csrf"', html)
token = util.get_csrf_token(self.request) token = mod.get_csrf_token(self.request)
self.assertIn(f'value="{token}"', html) self.assertIn(f'value="{token}"', html)
@ -522,17 +530,17 @@ class TestMakeJsonSafe(TestCase):
self.app = self.config.get_app() self.app = self.config.get_app()
def test_null(self): def test_null(self):
value = util.make_json_safe(colander.null) value = mod.make_json_safe(colander.null)
self.assertIsNone(value) self.assertIsNone(value)
value = util.make_json_safe(None) value = mod.make_json_safe(None)
self.assertIsNone(value) self.assertIsNone(value)
def test_invalid(self): def test_invalid(self):
model = self.app.model model = self.app.model
person = model.Person(full_name="Betty Boop") person = model.Person(full_name="Betty Boop")
self.assertRaises(TypeError, json.dumps, person) self.assertRaises(TypeError, json.dumps, person)
value = util.make_json_safe(person, key='person') value = mod.make_json_safe(person, key='person')
self.assertEqual(value, "Betty Boop") self.assertEqual(value, "Betty Boop")
def test_dict(self): def test_dict(self):
@ -545,7 +553,7 @@ class TestMakeJsonSafe(TestCase):
} }
self.assertRaises(TypeError, json.dumps, data) self.assertRaises(TypeError, json.dumps, data)
value = util.make_json_safe(data) value = mod.make_json_safe(data)
self.assertEqual(value, { self.assertEqual(value, {
'foo': 'bar', 'foo': 'bar',
'person': "Betty Boop", 'person': "Betty Boop",

View file

@ -1,5 +1,6 @@
# -*- coding: utf-8; -*- # -*- coding: utf-8; -*-
import datetime
import decimal import decimal
import functools import functools
from unittest import TestCase from unittest import TestCase
@ -579,7 +580,6 @@ class TestMasterView(WebTestCase):
self.assertEqual(value, "No") self.assertEqual(value, "No")
def test_grid_render_currency(self): def test_grid_render_currency(self):
model = self.app.model
view = self.make_view() view = self.make_view()
obj = {'amount': None} obj = {'amount': None}
@ -597,6 +597,33 @@ class TestMasterView(WebTestCase):
value = view.grid_render_currency(obj, 'amount', '-100.42') value = view.grid_render_currency(obj, 'amount', '-100.42')
self.assertEqual(value, "($100.42)") self.assertEqual(value, "($100.42)")
def test_grid_render_datetime(self):
view = self.make_view()
obj = {'dt': None}
# null
value = view.grid_render_datetime(obj, 'dt', None)
self.assertIsNone(value)
# normal
obj['dt'] = datetime.datetime(2024, 8, 24, 11)
value = view.grid_render_datetime(obj, 'dt', '2024-08-24T11:00:00')
self.assertEqual(value, '2024-08-24 11:00:00 AM')
def test_grid_render_enum(self):
enum = self.app.enum
view = self.make_view()
obj = {'status': None}
# null
value = view.grid_render_enum(obj, 'status', None, enum=enum.UpgradeStatus)
self.assertIsNone(value)
# normal
obj['status'] = enum.UpgradeStatus.SUCCESS
value = view.grid_render_enum(obj, 'status', 'SUCCESS', enum=enum.UpgradeStatus)
self.assertEqual(value, 'SUCCESS')
def test_grid_render_notes(self): def test_grid_render_notes(self):
model = self.app.model model = self.app.model
view = self.make_view() view = self.make_view()

View file

@ -0,0 +1,103 @@
# -*- coding: utf-8; -*-
import datetime
from unittest.mock import patch, MagicMock
from wuttaweb.views import upgrades as mod
from tests.util import WebTestCase
class TestUpgradeView(WebTestCase):
def make_view(self):
return mod.UpgradeView(self.request)
def test_includeme(self):
self.pyramid_config.include('wuttaweb.views.upgrades')
def test_configure_grid(self):
model = self.app.model
view = self.make_view()
# sanity / coverage check
grid = view.make_grid(model_class=model.Upgrade)
view.configure_grid(grid)
def test_grid_row_class(self):
model = self.app.model
enum = self.app.enum
upgrade = model.Upgrade(description="test", status=enum.UpgradeStatus.PENDING)
data = dict(upgrade)
view = self.make_view()
self.assertIsNone(view.grid_row_class(upgrade, data, 1))
upgrade.status = enum.UpgradeStatus.EXECUTING
self.assertEqual(view.grid_row_class(upgrade, data, 1), 'has-background-warning')
upgrade.status = enum.UpgradeStatus.SUCCESS
self.assertIsNone(view.grid_row_class(upgrade, data, 1))
upgrade.status = enum.UpgradeStatus.FAILURE
self.assertEqual(view.grid_row_class(upgrade, data, 1), 'has-background-warning')
def test_configure_form(self):
model = self.app.model
enum = self.app.enum
user = model.User(username='barney')
self.session.add(user)
upgrade = model.Upgrade(description='test', created_by=user,
status=enum.UpgradeStatus.PENDING)
self.session.add(upgrade)
self.session.commit()
view = self.make_view()
# some fields exist when viewing
with patch.object(view, 'viewing', new=True):
form = view.make_form(model_class=model.Upgrade, model_instance=upgrade)
self.assertIn('created', form)
view.configure_form(form)
self.assertIn('created', form)
# but then are removed when creating
with patch.object(view, 'creating', new=True):
form = view.make_form(model_class=model.Upgrade)
self.assertIn('created', form)
view.configure_form(form)
self.assertNotIn('created', form)
# test executed field when viewing
with patch.object(view, 'viewing', new=True):
# executed is *not* shown by default
form = view.make_form(model_class=model.Upgrade, model_instance=upgrade)
self.assertIn('executed', form)
view.configure_form(form)
self.assertNotIn('executed', form)
# but it *is* shown if upgrade is executed
upgrade.executed = datetime.datetime.now()
form = view.make_form(model_class=model.Upgrade, model_instance=upgrade)
self.assertIn('executed', form)
view.configure_form(form)
self.assertIn('executed', form)
def test_objectify(self):
model = self.app.model
enum = self.app.enum
user = model.User(username='barney')
self.session.add(user)
self.session.commit()
view = self.make_view()
# user and status are auto-set when creating
self.request.user = user
self.request.method = 'POST'
self.request.POST = {'description': "new one"}
with patch.object(view, 'creating', new=True):
form = view.make_model_form()
self.assertTrue(form.validate())
upgrade = view.objectify(form)
self.assertEqual(upgrade.description, "new one")
self.assertIs(upgrade.created_by, user)
self.assertEqual(upgrade.status, enum.UpgradeStatus.PENDING)