diff --git a/.pylintrc b/.pylintrc index 5a30127..66b3c38 100644 --- a/.pylintrc +++ b/.pylintrc @@ -2,7 +2,37 @@ [MESSAGES CONTROL] disable=fixme, - -[SIMILARITIES] -# nb. cuts out some noise for duplicate-code -min-similarity-lines=5 + abstract-method, + arguments-differ, + arguments-renamed, + assignment-from-no-return, + attribute-defined-outside-init, + consider-using-dict-comprehension, + consider-using-dict-items, + consider-using-generator, + consider-using-get, + consider-using-set-comprehension, + duplicate-code, + isinstance-second-argument-not-valid-type, + keyword-arg-before-vararg, + missing-function-docstring, + missing-module-docstring, + no-else-raise, + no-member, + not-callable, + protected-access, + redefined-outer-name, + simplifiable-if-expression, + singleton-comparison, + super-init-not-called, + too-few-public-methods, + too-many-arguments, + too-many-lines, + too-many-locals, + too-many-nested-blocks, + too-many-positional-arguments, + too-many-public-methods, + too-many-statements, + ungrouped-imports, + unidiomatic-typecheck, + unnecessary-comprehension, diff --git a/docs/index.rst b/docs/index.rst index ed834f0..43ede8c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,9 +11,6 @@ project. .. _test coverage: https://buildbot.rattailproject.org/coverage/wuttaweb/ -.. image:: https://img.shields.io/badge/linting-pylint-yellowgreen - :target: https://github.com/pylint-dev/pylint - .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/psf/black diff --git a/src/wuttaweb/_version.py b/src/wuttaweb/_version.py index 0b9e2e5..8423504 100644 --- a/src/wuttaweb/_version.py +++ b/src/wuttaweb/_version.py @@ -1,7 +1,4 @@ # -*- coding: utf-8; -*- -""" -Package Version -""" from importlib.metadata import version diff --git a/src/wuttaweb/app.py b/src/wuttaweb/app.py index 75d00f2..1593095 100644 --- a/src/wuttaweb/app.py +++ b/src/wuttaweb/app.py @@ -65,13 +65,13 @@ class WebAppProvider(AppProvider): :returns: Instance of :class:`~wuttaweb.handler.WebHandler`. """ - if "web" not in self.app.handlers: + if "web_handler" not in self.__dict__: spec = self.config.get( f"{self.appname}.web.handler_spec", default="wuttaweb.handler:WebHandler", ) - self.app.handlers["web"] = self.app.load_object(spec)(self.config) - return self.app.handlers["web"] + self.web_handler = self.app.load_object(spec)(self.config) + return self.web_handler def make_wutta_config(settings, config_maker=None, **kwargs): @@ -239,14 +239,14 @@ def make_wsgi_app(main_app=None, config=None): # determine the app factory if isinstance(main_app, str): - factory = app.load_object(main_app) + make_wsgi_app = app.load_object(main_app) elif callable(main_app): - factory = main_app + make_wsgi_app = main_app else: raise ValueError("main_app must be spec or callable") # construct a pyramid app "per usual" - return factory({}, **settings) + return make_wsgi_app({}, **settings) def make_asgi_app(main_app=None, config=None): diff --git a/src/wuttaweb/auth.py b/src/wuttaweb/auth.py index 26370ba..e6222c0 100644 --- a/src/wuttaweb/auth.py +++ b/src/wuttaweb/auth.py @@ -105,8 +105,7 @@ class WuttaSecurityPolicy: self.identity_cache = RequestLocalCache(self.load_identity) self.db_session = db_session or Session() - def load_identity(self, request): # pylint: disable=empty-docstring - """ """ + def load_identity(self, request): config = request.registry.settings["wutta_config"] app = config.get_app() model = app.model @@ -123,29 +122,22 @@ class WuttaSecurityPolicy: return user - def identity(self, request): # pylint: disable=empty-docstring - """ """ + def identity(self, request): return self.identity_cache.get_or_create(request) - def authenticated_userid(self, request): # pylint: disable=empty-docstring - """ """ + def authenticated_userid(self, request): user = self.identity(request) if user is not None: return user.uuid return None - def remember(self, request, userid, **kw): # pylint: disable=empty-docstring - """ """ + def remember(self, request, userid, **kw): return self.session_helper.remember(request, userid, **kw) - def forget(self, request, **kw): # pylint: disable=empty-docstring - """ """ + def forget(self, request, **kw): return self.session_helper.forget(request, **kw) - def permits( # pylint: disable=unused-argument,empty-docstring - self, request, context, permission - ): - """ """ + def permits(self, request, context, permission): # pylint: disable=unused-argument # nb. root user can do anything if getattr(request, "is_root", False): diff --git a/src/wuttaweb/emails.py b/src/wuttaweb/emails.py index 3552352..eb34bb3 100644 --- a/src/wuttaweb/emails.py +++ b/src/wuttaweb/emails.py @@ -27,7 +27,7 @@ from wuttjamaican.email import EmailSetting -class feedback(EmailSetting): # pylint: disable=invalid-name,too-few-public-methods +class feedback(EmailSetting): # pylint: disable=invalid-name """ Sent when user submits feedback via the web app. """ diff --git a/src/wuttaweb/forms/base.py b/src/wuttaweb/forms/base.py index 6365aa8..a4a469a 100644 --- a/src/wuttaweb/forms/base.py +++ b/src/wuttaweb/forms/base.py @@ -23,7 +23,6 @@ """ Base form classes """ -# pylint: disable=too-many-lines import logging from collections import OrderedDict @@ -37,19 +36,13 @@ from colanderalchemy import SQLAlchemySchemaNode from pyramid.renderers import render from webhelpers2.html import HTML -from wuttaweb.util import ( - FieldList, - get_form_data, - get_model_fields, - make_json_safe, - render_vue_finalize, -) +from wuttaweb.util import FieldList, get_form_data, get_model_fields, make_json_safe log = logging.getLogger(__name__) -class Form: # pylint: disable=too-many-instance-attributes,too-many-public-methods +class Form: # pylint: disable=too-many-instance-attributes """ Base class for all forms. @@ -270,12 +263,11 @@ class Form: # pylint: disable=too-many-instance-attributes,too-many-public-meth If the :meth:`validate()` method was called, and it succeeded, this will be set to the validated data dict. + + Note that in all other cases, this attribute may not exist. """ - deform_form = None - validated = None - - def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments,too-many-locals + def __init__( self, request, fields=None, @@ -338,7 +330,7 @@ class Form: # pylint: disable=too-many-instance-attributes,too-many-public-meth self.model_class = model_class self.model_instance = model_instance if self.model_instance and not self.model_class: - if not isinstance(self.model_instance, dict): + if type(self.model_instance) is not dict: self.model_class = type(self.model_instance) self.set_fields(fields or self.get_fields()) @@ -881,7 +873,7 @@ class Form: # pylint: disable=too-many-instance-attributes,too-many-public-meth Return the :class:`deform:deform.Form` instance for the form, generating it automatically if necessary. """ - if not self.deform_form: + if not hasattr(self, "deform_form"): schema = self.get_schema() kwargs = {} @@ -990,7 +982,7 @@ class Form: # pylint: disable=too-many-instance-attributes,too-many-public-meth output = render(template, context) return HTML.literal(output) - def render_vue_field( # pylint: disable=unused-argument,too-many-locals + def render_vue_field( # pylint: disable=unused-argument self, fieldname, readonly=None, @@ -1101,7 +1093,12 @@ class Form: # pylint: disable=too-many-instance-attributes,too-many-public-meth The actual output may depend on various form attributes, in particular :attr:`vue_tagname`. """ - return render_vue_finalize(self.vue_tagname, self.vue_component) + set_data = f"{self.vue_component}.data = function() {{ return {self.vue_component}Data }}" + make_component = f"Vue.component('{self.vue_tagname}', {self.vue_component})" + return HTML.tag( + "script", + c=["\n", HTML.literal(set_data), "\n", HTML.literal(make_component), "\n"], + ) def get_vue_model_data(self): """ @@ -1127,7 +1124,7 @@ class Form: # pylint: disable=too-many-instance-attributes,too-many-public-meth # for now we explicitly translate here, ugh. also # note this does not yet allow for null values.. :( if isinstance(field.typ, colander.Boolean): - value = value == field.typ.true_val + value = True if value == field.typ.true_val else False model_data[field.oid] = make_json_safe(value) @@ -1176,9 +1173,9 @@ class Form: # pylint: disable=too-many-instance-attributes,too-many-public-meth :attr:`validated` attribute. However if the data is not valid, ``False`` is returned, and - the :attr:`validated` attribute will be ``None``. In that - case you should inspect the form errors to learn/display what - went wrong for the user's sake. See also + there will be no :attr:`validated` attribute. In that case + you should inspect the form errors to learn/display what went + wrong for the user's sake. See also :meth:`get_field_errors()`. This uses :meth:`deform:deform.Field.validate()` under the @@ -1194,7 +1191,8 @@ class Form: # pylint: disable=too-many-instance-attributes,too-many-public-meth :returns: Data dict, or ``False``. """ - self.validated = None + if hasattr(self, "validated"): + del self.validated if self.request.method != "POST": return False diff --git a/src/wuttaweb/forms/schema.py b/src/wuttaweb/forms/schema.py index e42ac33..dc839fb 100644 --- a/src/wuttaweb/forms/schema.py +++ b/src/wuttaweb/forms/schema.py @@ -69,7 +69,7 @@ class WuttaDateTime(colander.DateTime): node.raise_invalid("Invalid date and/or time") -class ObjectNode(colander.SchemaNode): # pylint: disable=abstract-method +class ObjectNode(colander.SchemaNode): """ Custom schema node class which adds methods for compatibility with ColanderAlchemy. This is a direct subclass of @@ -183,7 +183,7 @@ class WuttaDictEnum(colander.String): def widget_maker(self, **kwargs): # pylint: disable=empty-docstring """ """ if "values" not in kwargs: - kwargs["values"] = list(self.enum_dct.items()) + kwargs["values"] = [(k, v) for k, v in self.enum_dct.items()] return widgets.SelectWidget(**kwargs) @@ -288,8 +288,13 @@ class ObjectRef(colander.SchemaType): default_empty_option = ("", "(none)") - def __init__(self, request, *args, **kwargs): - empty_option = kwargs.pop("empty_option", None) + def __init__( + self, + request, + empty_option=None, + *args, + **kwargs, + ): # nb. allow session injection for tests self.session = kwargs.pop("session", Session()) super().__init__(*args, **kwargs) @@ -376,9 +381,7 @@ class ObjectRef(colander.SchemaType): if not value: return None - if isinstance( # pylint: disable=isinstance-second-argument-not-valid-type - value, self.model_class - ): + if isinstance(value, self.model_class): return value # fetch object from DB @@ -472,9 +475,8 @@ class PersonRef(ObjectRef): """ """ return query.order_by(self.model_class.full_name) - def get_object_url(self, obj): # pylint: disable=empty-docstring + def get_object_url(self, person): # pylint: disable=empty-docstring """ """ - person = obj return self.request.route_url("people.view", uuid=person.uuid) @@ -497,9 +499,8 @@ class RoleRef(ObjectRef): """ """ return query.order_by(self.model_class.name) - def get_object_url(self, obj): # pylint: disable=empty-docstring + def get_object_url(self, role): # pylint: disable=empty-docstring """ """ - role = obj return self.request.route_url("roles.view", uuid=role.uuid) @@ -522,9 +523,8 @@ class UserRef(ObjectRef): """ """ return query.order_by(self.model_class.username) - def get_object_url(self, obj): # pylint: disable=empty-docstring + def get_object_url(self, user): # pylint: disable=empty-docstring """ """ - user = obj return self.request.route_url("users.view", uuid=user.uuid) @@ -557,7 +557,7 @@ class RoleRefs(WuttaSet): auth.get_role_authenticated(session), auth.get_role_anonymous(session), } - avoid = {role.uuid for role in avoid} + avoid = set([role.uuid for role in avoid]) # also avoid admin unless current user is root if not self.request.is_root: diff --git a/src/wuttaweb/forms/widgets.py b/src/wuttaweb/forms/widgets.py index 55eea47..108572f 100644 --- a/src/wuttaweb/forms/widgets.py +++ b/src/wuttaweb/forms/widgets.py @@ -103,8 +103,7 @@ class ObjectRefWidget(SelectWidget): readonly_template = "readonly/objectref" - def __init__(self, request, *args, **kwargs): - url = kwargs.pop("url", None) + def __init__(self, request, url=None, *args, **kwargs): super().__init__(*args, **kwargs) self.request = request self.url = url @@ -300,7 +299,7 @@ class WuttaMoneyInputWidget(MoneyInputWidget): return super().serialize(field, cstruct, **kw) -class FileDownloadWidget(Widget): # pylint: disable=abstract-method +class FileDownloadWidget(Widget): """ Widget for use with :class:`~wuttaweb.forms.schema.FileDownload` fields. @@ -358,7 +357,7 @@ class FileDownloadWidget(Widget): # pylint: disable=abstract-method return humanize.naturalsize(size) -class GridWidget(Widget): # pylint: disable=abstract-method +class GridWidget(Widget): """ Widget for fields whose data is represented by a :term:`grid`. @@ -407,7 +406,6 @@ class RoleRefsWidget(WuttaCheckboxChoiceWidget): """ readonly_template = "readonly/rolerefs" - session = None def serialize(self, field, cstruct, **kw): # pylint: disable=empty-docstring """ """ @@ -464,7 +462,6 @@ class PermissionsWidget(WuttaCheckboxChoiceWidget): template = "permissions" readonly_template = "readonly/permissions" - permissions = None def serialize(self, field, cstruct, **kw): # pylint: disable=empty-docstring """ """ @@ -515,7 +512,7 @@ class EmailRecipientsWidget(TextAreaWidget): return ", ".join(values) -class BatchIdWidget(Widget): # pylint: disable=abstract-method +class BatchIdWidget(Widget): """ Widget for use with the :attr:`~wuttjamaican:wuttjamaican.db.model.batch.BatchMixin.id` diff --git a/src/wuttaweb/grids/base.py b/src/wuttaweb/grids/base.py index 9461b23..fb764c9 100644 --- a/src/wuttaweb/grids/base.py +++ b/src/wuttaweb/grids/base.py @@ -23,7 +23,6 @@ """ Base grid classes """ -# pylint: disable=too-many-lines import functools import logging @@ -39,12 +38,7 @@ from pyramid.renderers import render from webhelpers2.html import HTML from wuttjamaican.db.util import UUID -from wuttaweb.util import ( - FieldList, - get_model_fields, - make_json_safe, - render_vue_finalize, -) +from wuttaweb.util import FieldList, get_model_fields, make_json_safe from wuttaweb.grids.filters import default_sqlalchemy_filters, VerbNotSupported @@ -59,7 +53,7 @@ Elements of :attr:`~Grid.sort_defaults` will be of this type. """ -class Grid: # pylint: disable=too-many-instance-attributes,too-many-public-methods +class Grid: # pylint: disable=too-many-instance-attributes """ Base class for all :term:`grids `. @@ -269,7 +263,7 @@ class Grid: # pylint: disable=too-many-instance-attributes,too-many-public-meth ``active_sorters`` defines the "current/effective" sorters. This attribute is set by :meth:`load_settings()`; until that is - called its value will be ``None``. + called it will not exist. This is conceptually a "subset" of :attr:`sorters` although a different format is used here:: @@ -377,11 +371,7 @@ class Grid: # pylint: disable=too-many-instance-attributes,too-many-public-meth See also :meth:`add_tool()` and :meth:`set_tools()`. """ - active_sorters = None - joined = None - pager = None - - def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments,too-many-locals + def __init__( self, request, vue_tagname="wutta-grid", @@ -698,7 +688,7 @@ class Grid: # pylint: disable=too-many-instance-attributes,too-many-public-meth "percent": self.render_percent, } - if renderer in builtins: # pylint: disable=consider-using-get + if renderer in builtins: renderer = builtins[renderer] if kwargs: @@ -1096,8 +1086,8 @@ class Grid: # pylint: disable=too-many-instance-attributes,too-many-public-meth # TODO: this should be improved; is needed in tailbone for # multi-column sorting with sqlalchemy queries if model_property: - sorter._class = model_class # pylint: disable=protected-access - sorter._column = model_property # pylint: disable=protected-access + sorter._class = model_class + sorter._column = model_property return sorter @@ -1382,10 +1372,10 @@ class Grid: # pylint: disable=too-many-instance-attributes,too-many-public-meth if filterinfo and callable(filterinfo): # filtr = filterinfo raise NotImplementedError - - kwargs["key"] = key - kwargs.setdefault("label", self.get_label(key)) - filtr = self.make_filter(filterinfo or key, **kwargs) + else: + kwargs["key"] = key + kwargs.setdefault("label", self.get_label(key)) + filtr = self.make_filter(filterinfo or key, **kwargs) self.filters[key] = filtr @@ -1483,9 +1473,7 @@ class Grid: # pylint: disable=too-many-instance-attributes,too-many-public-meth # configuration methods ############################## - def load_settings( # pylint: disable=too-many-branches,too-many-statements - self, persist=True - ): + def load_settings(self, persist=True): # pylint: disable=too-many-branches """ Load all effective settings for the grid. @@ -1638,7 +1626,7 @@ class Grid: # pylint: disable=too-many-instance-attributes,too-many-public-meth return False - def get_setting( # pylint: disable=empty-docstring,too-many-arguments,too-many-positional-arguments + def get_setting( # pylint: disable=empty-docstring self, settings, key, src="session", default=None, normalize=lambda v: v ): """ """ @@ -2217,7 +2205,12 @@ class Grid: # pylint: disable=too-many-instance-attributes,too-many-public-meth The actual output may depend on various grid attributes, in particular :attr:`vue_tagname`. """ - return render_vue_finalize(self.vue_tagname, self.vue_component) + set_data = f"{self.vue_component}.data = function() {{ return {self.vue_component}Data }}" + make_component = f"Vue.component('{self.vue_tagname}', {self.vue_component})" + return HTML.tag( + "script", + c=["\n", HTML.literal(set_data), "\n", HTML.literal(make_component), "\n"], + ) def get_vue_columns(self): """ @@ -2297,11 +2290,12 @@ class Grid: # pylint: disable=too-many-instance-attributes,too-many-public-meth :returns: The first sorter in format ``[sortkey, sortdir]``, or ``None``. """ - if self.active_sorters: - sorter = self.active_sorters[0] - return [sorter["key"], sorter["dir"]] + if hasattr(self, "active_sorters"): + if self.active_sorters: + sorter = self.active_sorters[0] + return [sorter["key"], sorter["dir"]] - if self.sort_defaults: + elif self.sort_defaults: sorter = self.sort_defaults[0] return [sorter.sortkey, sorter.sortdir] @@ -2392,9 +2386,9 @@ class Grid: # pylint: disable=too-many-instance-attributes,too-many-public-meth record = make_json_safe(record, warn=False) # customize value rendering where applicable - for key, renderer in self.renderers.items(): + for key in self.renderers: value = record.get(key, None) - record[key] = renderer(original_record, key, value) + record[key] = self.renderers[key](original_record, key, value) # add action urls to each record for action in self.actions: @@ -2543,7 +2537,7 @@ class GridAction: # pylint: disable=too-many-instance-attributes Optional HTML class attribute for the action's ```` tag. """ - def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments + def __init__( self, request, key, diff --git a/src/wuttaweb/grids/filters.py b/src/wuttaweb/grids/filters.py index 170fbd9..331aebc 100644 --- a/src/wuttaweb/grids/filters.py +++ b/src/wuttaweb/grids/filters.py @@ -59,10 +59,9 @@ class GridFilter: # pylint: disable=too-many-instance-attributes :param request: Current :term:`request` object. - :param nullable: Boolean indicating whether the filter should - include ``is_null`` and ``is_not_null`` verbs. If not - specified, the column will be inspected (if possible) and use - its nullable flag. + :param model_property: Property of a model class, representing the + column by which to filter. For instance, + ``model.Person.full_name``. :param \\**kwargs: Any additional kwargs will be set as attributes on the filter instance. @@ -170,14 +169,13 @@ class GridFilter: # pylint: disable=too-many-instance-attributes "is_not_null", ] - def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments + def __init__( self, request, key, label=None, verbs=None, choices=None, - nullable=None, default_active=False, default_verb=None, default_value=None, @@ -198,14 +196,10 @@ class GridFilter: # pylint: disable=too-many-instance-attributes self.verbs = verbs if default_verb: self.default_verb = default_verb - self.verb = None # active verb is set later # choices self.set_choices(choices or {}) - # nullable - self.nullable = nullable - # value self.default_value = default_value self.value = self.default_value @@ -254,7 +248,7 @@ class GridFilter: # pylint: disable=too-many-instance-attributes Returns a dict of all defined verb labels. """ # TODO: should traverse hierarchy - labels = {verb: verb for verb in self.get_verbs()} + labels = dict([(verb, verb) for verb in self.get_verbs()]) labels.update(self.default_verb_labels) return labels @@ -384,7 +378,7 @@ class GridFilter: # pylint: disable=too-many-instance-attributes raise VerbNotSupported(verb) # invoke filter method - return func(data, value) # pylint: disable=not-callable + return func(data, value) def filter_is_any(self, data, value): # pylint: disable=unused-argument """ @@ -404,12 +398,18 @@ class AlchemyFilter(GridFilter): :param model_property: Property of a model class, representing the column by which to filter. For instance, ``model.Person.full_name``. + + :param nullable: Boolean indicating whether the filter should + include ``is_null`` and ``is_not_null`` verbs. If not + specified, the column will be inspected and use its nullable + flag. """ def __init__(self, *args, **kwargs): - self.model_property = kwargs.pop("model_property") + nullable = kwargs.pop("nullable", None) super().__init__(*args, **kwargs) + self.nullable = nullable if self.nullable is None: columns = self.model_property.prop.columns if len(columns) == 1: @@ -446,7 +446,7 @@ class AlchemyFilter(GridFilter): # probably does not expect that, so explicitly include them. return query.filter( sa.or_( - self.model_property == None, # pylint: disable=singleton-comparison + self.model_property == None, self.model_property != value, ) ) @@ -491,18 +491,14 @@ class AlchemyFilter(GridFilter): """ Filter data with an ``IS NULL`` query. The value is ignored. """ - return query.filter( - self.model_property == None # pylint: disable=singleton-comparison - ) + return query.filter(self.model_property == None) def filter_is_not_null(self, query, value): # pylint: disable=unused-argument """ Filter data with an ``IS NOT NULL`` query. The value is ignored. """ - return query.filter( - self.model_property != None # pylint: disable=singleton-comparison - ) + return query.filter(self.model_property != None) class StringAlchemyFilter(AlchemyFilter): @@ -554,12 +550,7 @@ class StringAlchemyFilter(AlchemyFilter): # sql probably excludes null values from results, but user # probably does not expect that, so explicitly include them. - return query.filter( - sa.or_( - self.model_property == None, # pylint: disable=singleton-comparison - sa.and_(*criteria), - ) - ) + return query.filter(sa.or_(self.model_property == None, sa.and_(*criteria))) class NumericAlchemyFilter(AlchemyFilter): @@ -637,18 +628,14 @@ class BooleanAlchemyFilter(AlchemyFilter): Filter data with an "is true" condition. The value is ignored. """ - return query.filter( - self.model_property == True # pylint: disable=singleton-comparison - ) + return query.filter(self.model_property == True) def filter_is_false(self, query, value): # pylint: disable=unused-argument """ Filter data with an "is false" condition. The value is ignored. """ - return query.filter( - self.model_property == False # pylint: disable=singleton-comparison - ) + return query.filter(self.model_property == False) def filter_is_false_null(self, query, value): # pylint: disable=unused-argument """ @@ -656,10 +643,7 @@ class BooleanAlchemyFilter(AlchemyFilter): ignored. """ return query.filter( - sa.or_( - self.model_property == False, # pylint: disable=singleton-comparison - self.model_property == None, # pylint: disable=singleton-comparison - ) + sa.or_(self.model_property == False, self.model_property == None) ) diff --git a/src/wuttaweb/menus.py b/src/wuttaweb/menus.py index 8e04296..10e9a97 100644 --- a/src/wuttaweb/menus.py +++ b/src/wuttaweb/menus.py @@ -229,7 +229,7 @@ class MenuHandler(GenericHandler): # that somewhat to produce our final menus self._mark_allowed(request, raw_menus) final_menus = [] - for topitem in raw_menus: # pylint: disable=too-many-nested-blocks + for topitem in raw_menus: if topitem["allowed"]: @@ -323,7 +323,7 @@ class MenuHandler(GenericHandler): Traverse the menu set, and mark each item as "allowed" (or not) based on current user permissions. """ - for topitem in menus: # pylint: disable=too-many-nested-blocks + for topitem in menus: if topitem.get("type", "menu") == "link": topitem["allowed"] = True diff --git a/src/wuttaweb/progress.py b/src/wuttaweb/progress.py index d09a057..e041be1 100644 --- a/src/wuttaweb/progress.py +++ b/src/wuttaweb/progress.py @@ -91,7 +91,7 @@ class SessionProgress(ProgressBase): # pylint: disable=too-many-instance-attrib :attr:`success_url`. """ - def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments,super-init-not-called + def __init__( self, request, key, success_msg=None, success_url=None, error_url=None ): self.request = request diff --git a/src/wuttaweb/static/__init__.py b/src/wuttaweb/static/__init__.py index 95166ca..8dcfdf5 100644 --- a/src/wuttaweb/static/__init__.py +++ b/src/wuttaweb/static/__init__.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. # @@ -70,5 +70,5 @@ testing = Resource(img, "testing.png", renderer=True) # TODO: should consider deprecating this? -def includeme(config): # pylint: disable=missing-function-docstring +def includeme(config): config.add_static_view("wuttaweb", "wuttaweb:static") diff --git a/src/wuttaweb/subscribers.py b/src/wuttaweb/subscribers.py index 5dba500..25ea614 100644 --- a/src/wuttaweb/subscribers.py +++ b/src/wuttaweb/subscribers.py @@ -159,20 +159,20 @@ def new_request(event): 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 not hasattr(request, "_wuttaweb_registered_components"): + request._wuttaweb_registered_components = OrderedDict() - if tagname in request.wuttaweb_registered_components: + 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], + request._wuttaweb_registered_components[tagname], classname, ) - request.wuttaweb_registered_components[tagname] = classname + request._wuttaweb_registered_components[tagname] = classname request.register_component = register_component @@ -411,7 +411,7 @@ def before_render(event): context["available_themes"] = get_available_themes(config) -def includeme(config): # pylint: disable=missing-function-docstring +def includeme(config): config.add_subscriber(new_request, "pyramid.events.NewRequest") config.add_subscriber(new_request_set_user, "pyramid.events.NewRequest") config.add_subscriber(before_render, "pyramid.events.BeforeRender") diff --git a/src/wuttaweb/templates/themes/butterfly/base.mako b/src/wuttaweb/templates/themes/butterfly/base.mako index 944c541..76e9b5b 100644 --- a/src/wuttaweb/templates/themes/butterfly/base.mako +++ b/src/wuttaweb/templates/themes/butterfly/base.mako @@ -58,8 +58,8 @@ const app = createApp() app.component('vue-fontawesome', FontAwesomeIcon) - % if hasattr(request, 'wuttaweb_registered_components'): - % for tagname, classname in request.wuttaweb_registered_components.items(): + % if hasattr(request, '_wuttaweb_registered_components'): + % for tagname, classname in request._wuttaweb_registered_components.items(): app.component('${tagname}', ${classname}) % endfor % endif diff --git a/src/wuttaweb/testing.py b/src/wuttaweb/testing.py index ab13273..d6f6767 100644 --- a/src/wuttaweb/testing.py +++ b/src/wuttaweb/testing.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. # @@ -39,14 +39,10 @@ class WebTestCase(DataTestCase): Base class for test suites requiring a full (typical) web app. """ - def setUp(self): # pylint: disable=empty-docstring - """ """ + def setUp(self): self.setup_web() def setup_web(self): - """ - Perform setup for the testing web app. - """ self.setup_db() self.request = self.make_request() self.pyramid_config = testing.setUp( @@ -91,14 +87,8 @@ class WebTestCase(DataTestCase): self.teardown_web() def teardown_web(self): - """ - Perform teardown for the testing web app. - """ testing.tearDown() self.teardown_db() def make_request(self): - """ - Make and return a new dummy request object. - """ return testing.DummyRequest() diff --git a/src/wuttaweb/util.py b/src/wuttaweb/util.py index 5492485..caa41f6 100644 --- a/src/wuttaweb/util.py +++ b/src/wuttaweb/util.py @@ -618,71 +618,6 @@ def make_json_safe(value, key=None, warn=True): return value -def render_vue_finalize(vue_tagname, vue_component): - """ - Render the Vue "finalize" script for a form or grid component. - - This is a convenience for shared logic; it returns e.g.: - - .. code-block:: html - - - """ - set_data = f"{vue_component}.data = function() {{ return {vue_component}Data }}" - make_component = f"Vue.component('{vue_tagname}', {vue_component})" - return HTML.tag( - "script", - c=["\n", HTML.literal(set_data), "\n", HTML.literal(make_component), "\n"], - ) - - -def make_users_grid(request, **kwargs): - """ - Make and return a users (sub)grid. - - This grid is shown for the Users field when viewing a Person or - Role, for instance. It is called by the following methods: - - * :meth:`wuttaweb.views.people.PersonView.make_users_grid()` - * :meth:`wuttaweb.views.roles.RoleView.make_users_grid()` - - :returns: Fully configured :class:`~wuttaweb.grids.base.Grid` - instance. - """ - config = request.wutta_config - app = config.get_app() - model = app.model - web = app.get_web_handler() - - if "key" not in kwargs: - route_prefix = kwargs.pop("route_prefix") - kwargs["key"] = f"{route_prefix}.view.users" - - kwargs.setdefault("model_class", model.User) - grid = web.make_grid(request, **kwargs) - - if request.has_perm("users.view"): - - def view_url(user, i): # pylint: disable=unused-argument - return request.route_url("users.view", uuid=user.uuid) - - grid.add_action("view", icon="eye", url=view_url) - grid.set_link("person") - grid.set_link("username") - - if request.has_perm("users.edit"): - - def edit_url(user, i): # pylint: disable=unused-argument - return request.route_url("users.edit", uuid=user.uuid) - - grid.add_action("edit", url=edit_url) - - return grid - - ############################## # theme functions ############################## @@ -822,14 +757,14 @@ def set_app_theme(request, theme, session=None): # 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("/page.mako") + 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() # pylint: disable=protected-access + lookup._collection.clear() # persist current theme in db settings with app.short_session(session=session) as s: diff --git a/src/wuttaweb/views/__init__.py b/src/wuttaweb/views/__init__.py index 16b6be9..6b59940 100644 --- a/src/wuttaweb/views/__init__.py +++ b/src/wuttaweb/views/__init__.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. # @@ -34,5 +34,5 @@ from .base import View from .master import MasterView -def includeme(config): # pylint: disable=missing-function-docstring +def includeme(config): config.include("wuttaweb.views.essential") diff --git a/src/wuttaweb/views/auth.py b/src/wuttaweb/views/auth.py index f7b1d0e..c2a52cb 100644 --- a/src/wuttaweb/views/auth.py +++ b/src/wuttaweb/views/auth.py @@ -95,8 +95,7 @@ class AuthView(View): # 'referrer': referrer, } - def login_make_schema(self): # pylint: disable=empty-docstring - """ """ + def login_make_schema(self): schema = colander.Schema() # nb. we must explicitly declare the widgets in order to also @@ -221,19 +220,13 @@ class AuthView(View): return schema - def change_password_validate_current_password( # pylint: disable=empty-docstring - self, node, value - ): - """ """ + def change_password_validate_current_password(self, node, value): auth = self.app.get_auth_handler() user = self.request.user if not auth.check_user_password(user, value): node.raise_invalid("Current password is incorrect.") - def change_password_validate_new_password( # pylint: disable=empty-docstring - self, node, value - ): - """ """ + def change_password_validate_new_password(self, node, value): auth = self.app.get_auth_handler() user = self.request.user if auth.check_user_password(user, value): @@ -284,8 +277,7 @@ class AuthView(View): return self.redirect(url) @classmethod - def defaults(cls, config): # pylint: disable=empty-docstring - """ """ + def defaults(cls, config): cls._auth_defaults(config) @classmethod @@ -319,14 +311,12 @@ class AuthView(View): config.add_view(cls, attr="stop_root", route_name="stop_root") -def defaults(config, **kwargs): # pylint: disable=missing-function-docstring +def defaults(config, **kwargs): base = globals() - AuthView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name - "AuthView", base["AuthView"] - ) + AuthView = kwargs.get("AuthView", base["AuthView"]) # pylint: disable=invalid-name AuthView.defaults(config) -def includeme(config): # pylint: disable=missing-function-docstring +def includeme(config): defaults(config) diff --git a/src/wuttaweb/views/batch.py b/src/wuttaweb/views/batch.py index 79bea75..3690a20 100644 --- a/src/wuttaweb/views/batch.py +++ b/src/wuttaweb/views/batch.py @@ -51,8 +51,6 @@ class BatchMasterView(MasterView): from :meth:`get_batch_handler()`. """ - executable = True - labels = { "id": "Batch ID", "status_code": "Status", @@ -123,9 +121,8 @@ class BatchMasterView(MasterView): return super().render_to_response(template, context) - def configure_grid(self, grid): # pylint: disable=empty-docstring + def configure_grid(self, g): # pylint: disable=empty-docstring """ """ - g = grid super().configure_grid(g) model = self.app.model @@ -156,17 +153,15 @@ class BatchMasterView(MasterView): return f"{batch_id:08d}" return None - def get_instance_title(self, instance): # pylint: disable=empty-docstring + def get_instance_title(self, batch): # pylint: disable=empty-docstring """ """ - batch = instance if batch.description: return f"{batch.id_str} {batch.description}" return batch.id_str - def configure_form(self, form): # pylint: disable=too-many-branches,empty-docstring + def configure_form(self, f): # pylint: disable=too-many-branches,empty-docstring """ """ - super().configure_form(form) - f = form + super().configure_form(f) batch = f.model_instance # id @@ -240,11 +235,13 @@ class BatchMasterView(MasterView): batch = schema.objectify(form.validated, context=form.model_instance) # then we collect attributes from the new batch - kw = { - key: getattr(batch, key) - for key in form.validated - if hasattr(batch, key) - } + kw = dict( + [ + (key, getattr(batch, key)) + for key in form.validated + if hasattr(batch, key) + ] + ) # and set attribute for user creating the batch kw["created_by"] = self.request.user @@ -258,7 +255,7 @@ class BatchMasterView(MasterView): # when not creating, normal logic is fine return super().objectify(form) - def redirect_after_create(self, obj): + def redirect_after_create(self, batch): """ If the new batch requires initial population, we launch a thread for that and show the "progress" page. @@ -266,8 +263,6 @@ class BatchMasterView(MasterView): Otherwise this will do the normal thing of redirecting to the "view" page for the new batch. """ - batch = obj - # just view batch if should not populate if not self.batch_handler.should_populate(batch): return self.redirect(self.get_action_url("view", batch)) @@ -288,7 +283,7 @@ class BatchMasterView(MasterView): thread.start() return self.render_progress(progress) - def delete_instance(self, obj): + def delete_instance(self, batch): """ Delete the given batch instance. @@ -296,7 +291,6 @@ class BatchMasterView(MasterView): :meth:`~wuttjamaican:wuttjamaican.batch.BatchHandler.do_delete()` on the :attr:`batch_handler`. """ - batch = obj self.batch_handler.do_delete(batch, self.request.user) ############################## @@ -332,22 +326,29 @@ class BatchMasterView(MasterView): raise RuntimeError("can't find the batch") time.sleep(0.1) - def onerror(): + try: + # populate the batch + self.batch_handler.do_populate(batch, progress=progress) + session.flush() + + except Exception as error: # pylint: disable=broad-exception-caught + session.rollback() log.warning( "failed to populate %s: %s", self.get_model_title(), batch, exc_info=True, ) + if progress: + progress.handle_error(error) - self.do_thread_body( - self.batch_handler.do_populate, - (batch,), - {"progress": progress}, - onerror, - session=session, - progress=progress, - ) + else: + session.commit() + if progress: + progress.handle_success() + + finally: + session.close() ############################## # execute methods @@ -387,21 +388,20 @@ class BatchMasterView(MasterView): model_class = cls.get_model_class() return model_class.__row_class__ - def get_row_grid_data(self, obj): + def get_row_grid_data(self, batch): """ Returns the base query for the batch :attr:`~wuttjamaican:wuttjamaican.db.model.batch.BatchMixin.rows` data. """ - session = self.Session() - batch = obj row_model_class = self.get_row_model_class() - query = session.query(row_model_class).filter(row_model_class.batch == batch) + query = self.Session.query(row_model_class).filter( + row_model_class.batch == batch + ) return query - def configure_row_grid(self, grid): # pylint: disable=empty-docstring + def configure_row_grid(self, g): # pylint: disable=empty-docstring """ """ - g = grid super().configure_row_grid(g) g.set_label("sequence", "Seq.", column_only=True) @@ -413,3 +413,36 @@ class BatchMasterView(MasterView): ): """ """ return row.STATUS.get(value, value) + + ############################## + # configuration + ############################## + + @classmethod + def defaults(cls, config): # pylint: disable=empty-docstring + """ """ + cls._defaults(config) + cls._batch_defaults(config) + + @classmethod + def _batch_defaults(cls, config): + route_prefix = cls.get_route_prefix() + permission_prefix = cls.get_permission_prefix() + model_title = cls.get_model_title() + instance_url_prefix = cls.get_instance_url_prefix() + + # execute + config.add_route( + f"{route_prefix}.execute", + f"{instance_url_prefix}/execute", + request_method="POST", + ) + config.add_view( + cls, + attr="execute", + route_name=f"{route_prefix}.execute", + permission=f"{permission_prefix}.execute", + ) + config.add_wutta_permission( + permission_prefix, f"{permission_prefix}.execute", f"Execute {model_title}" + ) diff --git a/src/wuttaweb/views/common.py b/src/wuttaweb/views/common.py index 74c6858..71ef57e 100644 --- a/src/wuttaweb/views/common.py +++ b/src/wuttaweb/views/common.py @@ -135,7 +135,7 @@ class CommonView(View): """ """ self.app.send_email("feedback", context) - def setup(self, session=None): # pylint: disable=too-many-locals + def setup(self, session=None): """ View for first-time app setup, to create admin user. @@ -294,8 +294,7 @@ class CommonView(View): return self.redirect(referrer) @classmethod - def defaults(cls, config): # pylint: disable=empty-docstring - """ """ + def defaults(cls, config): cls._defaults(config) @classmethod @@ -343,14 +342,14 @@ class CommonView(View): ) -def defaults(config, **kwargs): # pylint: disable=missing-function-docstring +def defaults(config, **kwargs): base = globals() - CommonView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name + CommonView = kwargs.get( # pylint: disable=invalid-name "CommonView", base["CommonView"] ) CommonView.defaults(config) -def includeme(config): # pylint: disable=missing-function-docstring +def includeme(config): defaults(config) diff --git a/src/wuttaweb/views/email.py b/src/wuttaweb/views/email.py index 981faff..a50977f 100644 --- a/src/wuttaweb/views/email.py +++ b/src/wuttaweb/views/email.py @@ -30,7 +30,7 @@ from wuttaweb.views import MasterView from wuttaweb.forms.schema import EmailRecipients -class EmailSettingView(MasterView): # pylint: disable=abstract-method +class EmailSettingView(MasterView): """ Master view for :term:`email settings `. """ @@ -107,9 +107,8 @@ class EmailSettingView(MasterView): # pylint: disable=abstract-method "enabled": self.email_handler.is_enabled(key), } - def configure_grid(self, grid): # pylint: disable=empty-docstring + def configure_grid(self, g): # pylint: disable=empty-docstring """ """ - g = grid super().configure_grid(g) # key @@ -137,9 +136,7 @@ class EmailSettingView(MasterView): # pylint: disable=abstract-method recips = ", ".join(recips[:2]) return f"{recips}, ..." - def get_instance( # pylint: disable=empty-docstring,arguments-differ,unused-argument - self, **kwargs - ): + def get_instance(self): # pylint: disable=empty-docstring """ """ key = self.request.matchdict["key"] setting = self.email_handler.get_email_setting(key, instance=False) @@ -148,14 +145,12 @@ class EmailSettingView(MasterView): # pylint: disable=abstract-method raise self.notfound() - def get_instance_title(self, instance): # pylint: disable=empty-docstring + def get_instance_title(self, setting): # pylint: disable=empty-docstring """ """ - setting = instance return setting["subject"] - def configure_form(self, form): # pylint: disable=empty-docstring + def configure_form(self, f): # pylint: disable=empty-docstring """ """ - f = form super().configure_form(f) # description @@ -180,9 +175,7 @@ class EmailSettingView(MasterView): # pylint: disable=abstract-method # enabled f.set_node("enabled", colander.Boolean()) - def persist( # pylint: disable=too-many-branches,empty-docstring,arguments-differ,unused-argument - self, setting, **kwargs - ): + def persist(self, setting): # pylint: disable=too-many-branches,empty-docstring """ """ session = self.Session() key = self.request.matchdict["key"] @@ -307,14 +300,14 @@ class EmailSettingView(MasterView): # pylint: disable=abstract-method ) -def defaults(config, **kwargs): # pylint: disable=missing-function-docstring +def defaults(config, **kwargs): base = globals() - EmailSettingView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name + EmailSettingView = kwargs.get( # pylint: disable=invalid-name "EmailSettingView", base["EmailSettingView"] ) EmailSettingView.defaults(config) -def includeme(config): # pylint: disable=missing-function-docstring +def includeme(config): defaults(config) diff --git a/src/wuttaweb/views/essential.py b/src/wuttaweb/views/essential.py index e8eac32..b46a658 100644 --- a/src/wuttaweb/views/essential.py +++ b/src/wuttaweb/views/essential.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. # @@ -41,7 +41,7 @@ That will in turn include the following modules: """ -def defaults(config, **kwargs): # pylint: disable=missing-function-docstring +def defaults(config, **kwargs): def mod(spec): return kwargs.get(spec, spec) @@ -57,5 +57,5 @@ def defaults(config, **kwargs): # pylint: disable=missing-function-docstring config.include(mod("wuttaweb.views.upgrades")) -def includeme(config): # pylint: disable=missing-function-docstring +def includeme(config): defaults(config) diff --git a/src/wuttaweb/views/master.py b/src/wuttaweb/views/master.py index 500a276..a356534 100644 --- a/src/wuttaweb/views/master.py +++ b/src/wuttaweb/views/master.py @@ -23,7 +23,6 @@ """ Base Logic for Master Views """ -# pylint: disable=too-many-lines import logging import os @@ -45,7 +44,7 @@ from wuttaweb.progress import SessionProgress log = logging.getLogger(__name__) -class MasterView(View): # pylint: disable=too-many-public-methods +class MasterView(View): """ Base class for "master" views. @@ -399,8 +398,6 @@ class MasterView(View): # pylint: disable=too-many-public-methods # attributes ############################## - model_class = None - # features listable = True has_grid = True @@ -441,7 +438,6 @@ class MasterView(View): # pylint: disable=too-many-public-methods viewing = False editing = False deleting = False - executing = False configuring = False # default DB session @@ -528,12 +524,11 @@ class MasterView(View): # pylint: disable=too-many-public-methods * :meth:`redirect_after_create()` """ self.creating = True - session = self.Session() form = self.make_model_form(cancel_url_fallback=self.get_index_url()) if form.validate(): obj = self.create_save_form(form) - session.flush() + self.Session.flush() return self.redirect_after_create(obj) context = { @@ -822,25 +817,33 @@ class MasterView(View): # pylint: disable=too-many-public-methods self, query, progress=None ): """ """ + model_title_plural = self.get_model_title_plural() + + # nb. use new session, separate from web transaction session = self.app.make_session() records = query.with_session(session).all() - def onerror(): + try: + self.delete_bulk_action(records, progress=progress) + + except Exception as error: # pylint: disable=broad-exception-caught + session.rollback() log.warning( "failed to delete %s results for %s", len(records), - self.get_model_title_plural(), + model_title_plural, exc_info=True, ) + if progress: + progress.handle_error(error) - self.do_thread_body( - self.delete_bulk_action, - (records,), - {"progress": progress}, - onerror, - session=session, - progress=progress, - ) + else: + session.commit() + if progress: + progress.handle_success() + + finally: + session.close() def delete_bulk_action(self, data, progress=None): """ @@ -914,7 +917,7 @@ class MasterView(View): # pylint: disable=too-many-public-methods if not term: return [] - data = self.autocomplete_data(term) # pylint: disable=assignment-from-none + data = self.autocomplete_data(term) if not data: return [] @@ -928,7 +931,7 @@ class MasterView(View): # pylint: disable=too-many-public-methods return results - def autocomplete_data(self, term): # pylint: disable=unused-argument + def autocomplete_data(self, term): """ Should return the data/query for the "matching" model records, based on autocomplete search term. This is called by @@ -940,7 +943,6 @@ class MasterView(View): # pylint: disable=too-many-public-methods :returns: List of data records, or SQLAlchemy query. """ - return None def autocomplete_normalize(self, obj): """ @@ -1004,13 +1006,13 @@ class MasterView(View): # pylint: disable=too-many-public-methods obj = self.get_instance() filename = self.request.GET.get("filename", None) - path = self.download_path(obj, filename) # pylint: disable=assignment-from-none + path = self.download_path(obj, filename) if not path or not os.path.exists(path): return self.notfound() return self.file_response(path) - def download_path(self, obj, filename): # pylint: disable=unused-argument + def download_path(self, obj, filename): """ Should return absolute path on disk, for the given object and filename. Result will be used to return a file response to @@ -1031,7 +1033,6 @@ class MasterView(View): # pylint: disable=too-many-public-methods the :meth:`download()` view will return a 404 not found response. """ - return None ############################## # execute methods @@ -1303,7 +1304,6 @@ class MasterView(View): # pylint: disable=too-many-public-methods Note that their order does not matter since the template must explicitly define field layout etc. """ - return [] def configure_gather_settings( self, @@ -2244,9 +2244,9 @@ class MasterView(View): # pylint: disable=too-many-public-methods :returns: The dict of route kwargs for the object. """ try: - return {key: obj[key] for key in self.get_model_key()} + return dict([(key, obj[key]) for key in self.get_model_key()]) except TypeError: - return {key: getattr(obj, key) for key in self.get_model_key()} + return dict([(key, getattr(obj, key)) for key in self.get_model_key()]) def get_action_url(self, action, obj, **kwargs): """ @@ -2477,60 +2477,6 @@ class MasterView(View): # pylint: disable=too-many-public-methods session = session or self.Session() session.add(obj) - def do_thread_body( # pylint: disable=too-many-arguments,too-many-positional-arguments - self, func, args, kwargs, onerror=None, session=None, progress=None - ): - """ - Generic method to invoke for thread operations. - - :param func: Callable which performs the actual logic. This - will be wrapped with a try/except statement for error - handling. - - :param args: Tuple of positional arguments to pass to the - ``func`` callable. - - :param kwargs: Dict of keyword arguments to pass to the - ``func`` callable. - - :param onerror: Optional callback to invoke if ``func`` raises - an error. It should not expect any arguments. - - :param session: Optional :term:`db session` in effect. Note - that if supplied, it will be *committed* (or rolled back on - error) and *closed* by this method. If you need more - specialized handling, do not use this method (or don't - specify the ``session``). - - :param progress: Optional progress factory. If supplied, this - is assumed to be a - :class:`~wuttaweb.progress.SessionProgress` instance, and - it will be updated per success or failure of ``func`` - invocation. - """ - try: - func(*args, **kwargs) - - except Exception as error: # pylint: disable=broad-exception-caught - if session: - session.rollback() - if onerror: - onerror() - else: - log.warning("failed to invoke thread callable: %s", func, exc_info=True) - if progress: - progress.handle_error(error) - - else: - if session: - session.commit() - if progress: - progress.handle_success() - - finally: - if session: - session.close() - ############################## # row methods ############################## @@ -2751,7 +2697,9 @@ class MasterView(View): # pylint: disable=too-many-public-methods do not set the :attr:`model_class`, then you *must* set the :attr:`model_name`. """ - return cls.model_class + if hasattr(cls, "model_class"): + return cls.model_class + return None @classmethod def get_model_name(cls): @@ -2860,9 +2808,11 @@ class MasterView(View): # pylint: disable=too-many-public-methods inspector = sa.inspect(model_class) keys = [col.name for col in inspector.primary_key] return tuple( - prop.key - for prop in inspector.column_attrs - if all(col.name in keys for col in prop.columns) + [ + prop.key + for prop in inspector.column_attrs + if all(col.name in keys for col in prop.columns) + ] ) raise AttributeError(f"you must define model_key for view class: {cls}") diff --git a/src/wuttaweb/views/people.py b/src/wuttaweb/views/people.py index b726772..d8cc189 100644 --- a/src/wuttaweb/views/people.py +++ b/src/wuttaweb/views/people.py @@ -28,10 +28,9 @@ import sqlalchemy as sa from wuttjamaican.db.model import Person from wuttaweb.views import MasterView -from wuttaweb.util import make_users_grid -class PersonView(MasterView): # pylint: disable=abstract-method +class PersonView(MasterView): """ Master view for people. @@ -71,9 +70,8 @@ class PersonView(MasterView): # pylint: disable=abstract-method "users", ] - def configure_grid(self, grid): # pylint: disable=empty-docstring + def configure_grid(self, g): # pylint: disable=empty-docstring """ """ - g = grid super().configure_grid(g) # full_name @@ -85,9 +83,8 @@ class PersonView(MasterView): # pylint: disable=abstract-method # last_name g.set_link("last_name") - def configure_form(self, form): # pylint: disable=empty-docstring + def configure_form(self, f): # pylint: disable=empty-docstring """ """ - f = form super().configure_form(f) person = f.model_instance @@ -108,9 +105,12 @@ class PersonView(MasterView): # pylint: disable=abstract-method :returns: Fully configured :class:`~wuttaweb.grids.base.Grid` instance. """ - return make_users_grid( - self.request, - route_prefix=self.get_route_prefix(), + model = self.app.model + route_prefix = self.get_route_prefix() + + grid = self.make_grid( + key=f"{route_prefix}.view.users", + model_class=model.User, data=person.users, columns=[ "username", @@ -118,6 +118,23 @@ class PersonView(MasterView): # pylint: disable=abstract-method ], ) + if self.request.has_perm("users.view"): + + def view_url(user, i): # pylint: disable=unused-argument + return self.request.route_url("users.view", uuid=user.uuid) + + grid.add_action("view", icon="eye", url=view_url) + grid.set_link("username") + + if self.request.has_perm("users.edit"): + + def edit_url(user, i): # pylint: disable=unused-argument + return self.request.route_url("users.edit", uuid=user.uuid) + + grid.add_action("edit", url=edit_url) + + return grid + def objectify(self, form): # pylint: disable=empty-docstring """ """ person = super().objectify(form) @@ -196,14 +213,14 @@ class PersonView(MasterView): # pylint: disable=abstract-method ) -def defaults(config, **kwargs): # pylint: disable=missing-function-docstring +def defaults(config, **kwargs): base = globals() - PersonView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name + PersonView = kwargs.get( # pylint: disable=invalid-name "PersonView", base["PersonView"] ) PersonView.defaults(config) -def includeme(config): # pylint: disable=missing-function-docstring +def includeme(config): defaults(config) diff --git a/src/wuttaweb/views/progress.py b/src/wuttaweb/views/progress.py index fb419ec..06aa456 100644 --- a/src/wuttaweb/views/progress.py +++ b/src/wuttaweb/views/progress.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. # @@ -63,15 +63,13 @@ def progress(request): return session -def defaults(config, **kwargs): # pylint: disable=missing-function-docstring +def defaults(config, **kwargs): base = globals() - progress = kwargs.get( # pylint: disable=redefined-outer-name - "progress", base["progress"] - ) + progress = kwargs.get("progress", base["progress"]) config.add_route("progress", "/progress/{key}") config.add_view(progress, route_name="progress", renderer="json") -def includeme(config): # pylint: disable=missing-function-docstring +def includeme(config): defaults(config) diff --git a/src/wuttaweb/views/reports.py b/src/wuttaweb/views/reports.py index 90ff204..a79b262 100644 --- a/src/wuttaweb/views/reports.py +++ b/src/wuttaweb/views/reports.py @@ -37,7 +37,7 @@ from wuttaweb.views import MasterView log = logging.getLogger(__name__) -class ReportView(MasterView): # pylint: disable=abstract-method +class ReportView(MasterView): """ Master view for :term:`reports `; route prefix is ``reports``. @@ -89,9 +89,8 @@ class ReportView(MasterView): # pylint: disable=abstract-method "help_text": report.__doc__, } - def configure_grid(self, grid): # pylint: disable=empty-docstring + def configure_grid(self, g): # pylint: disable=empty-docstring """ """ - g = grid super().configure_grid(g) # report_key @@ -104,9 +103,7 @@ class ReportView(MasterView): # pylint: disable=abstract-method # help_text g.set_searchable("help_text") - def get_instance( # pylint: disable=empty-docstring,arguments-differ,unused-argument - self, **kwargs - ): + def get_instance(self): # pylint: disable=empty-docstring """ """ key = self.request.matchdict["report_key"] report = self.report_handler.get_report(key) @@ -115,9 +112,8 @@ class ReportView(MasterView): # pylint: disable=abstract-method raise self.notfound() - def get_instance_title(self, instance): # pylint: disable=empty-docstring + def get_instance_title(self, report): # pylint: disable=empty-docstring """ """ - report = instance return report["report_title"] def view(self): @@ -155,9 +151,8 @@ class ReportView(MasterView): # pylint: disable=abstract-method return self.render_to_response("view", context) - def configure_form(self, form): # pylint: disable=empty-docstring + def configure_form(self, f): # pylint: disable=empty-docstring """ """ - f = form super().configure_form(f) key = self.request.matchdict["report_key"] report = self.report_handler.get_report(key) @@ -266,14 +261,14 @@ class ReportView(MasterView): # pylint: disable=abstract-method ) -def defaults(config, **kwargs): # pylint: disable=missing-function-docstring +def defaults(config, **kwargs): base = globals() - ReportView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name + ReportView = kwargs.get( # pylint: disable=invalid-name "ReportView", base["ReportView"] ) ReportView.defaults(config) -def includeme(config): # pylint: disable=missing-function-docstring +def includeme(config): defaults(config) diff --git a/src/wuttaweb/views/roles.py b/src/wuttaweb/views/roles.py index f1f9b1e..62d2dd0 100644 --- a/src/wuttaweb/views/roles.py +++ b/src/wuttaweb/views/roles.py @@ -29,10 +29,9 @@ from wuttaweb.views import MasterView from wuttaweb.db import Session from wuttaweb.forms import widgets from wuttaweb.forms.schema import Permissions, RoleRef -from wuttaweb.util import make_users_grid -class RoleView(MasterView): # pylint: disable=abstract-method +class RoleView(MasterView): """ Master view for roles. @@ -59,8 +58,6 @@ class RoleView(MasterView): # pylint: disable=abstract-method } sort_defaults = "name" - wutta_permissions = None - # TODO: master should handle this, possibly via configure_form() def get_query(self, session=None): # pylint: disable=empty-docstring """ """ @@ -68,9 +65,8 @@ class RoleView(MasterView): # pylint: disable=abstract-method query = super().get_query(session=session) return query.order_by(model.Role.name) - def configure_grid(self, grid): # pylint: disable=empty-docstring + def configure_grid(self, g): # pylint: disable=empty-docstring """ """ - g = grid super().configure_grid(g) # name @@ -79,9 +75,8 @@ class RoleView(MasterView): # pylint: disable=abstract-method # notes g.set_renderer("notes", self.grid_render_notes) - def is_editable(self, obj): # pylint: disable=empty-docstring + def is_editable(self, role): # pylint: disable=empty-docstring """ """ - role = obj session = self.app.get_session(role) auth = self.app.get_auth_handler() @@ -98,9 +93,8 @@ class RoleView(MasterView): # pylint: disable=abstract-method return True - def is_deletable(self, obj): # pylint: disable=empty-docstring + def is_deletable(self, role): # pylint: disable=empty-docstring """ """ - role = obj session = self.app.get_session(role) auth = self.app.get_auth_handler() @@ -114,9 +108,8 @@ class RoleView(MasterView): # pylint: disable=abstract-method return True - def configure_form(self, form): # pylint: disable=empty-docstring + def configure_form(self, f): # pylint: disable=empty-docstring """ """ - f = form super().configure_form(f) role = f.model_instance @@ -152,9 +145,12 @@ class RoleView(MasterView): # pylint: disable=abstract-method :returns: Fully configured :class:`~wuttaweb.grids.base.Grid` instance. """ - return make_users_grid( - self.request, - route_prefix=self.get_route_prefix(), + model = self.app.model + route_prefix = self.get_route_prefix() + + grid = self.make_grid( + key=f"{route_prefix}.view.users", + model_class=model.User, data=role.users, columns=[ "username", @@ -163,6 +159,24 @@ class RoleView(MasterView): # pylint: disable=abstract-method ], ) + if self.request.has_perm("users.view"): + + def view_url(user, i): # pylint: disable=unused-argument + return self.request.route_url("users.view", uuid=user.uuid) + + grid.add_action("view", icon="eye", url=view_url) + grid.set_link("person") + grid.set_link("username") + + if self.request.has_perm("users.edit"): + + def edit_url(user, i): # pylint: disable=unused-argument + return self.request.route_url("users.edit", uuid=user.uuid) + + grid.add_action("edit", url=edit_url) + + return grid + def unique_name(self, node, value): # pylint: disable=empty-docstring """ """ model = self.app.model @@ -303,7 +317,7 @@ class RoleView(MasterView): # pylint: disable=abstract-method ) -class PermissionView(MasterView): # pylint: disable=abstract-method +class PermissionView(MasterView): """ Master view for permissions. @@ -332,7 +346,7 @@ class PermissionView(MasterView): # pylint: disable=abstract-method "permission", ] - def get_query(self, **kwargs): # pylint: disable=empty-docstring,arguments-differ + def get_query(self, **kwargs): # pylint: disable=empty-docstring """ """ query = super().get_query(**kwargs) model = self.app.model @@ -342,9 +356,8 @@ class PermissionView(MasterView): # pylint: disable=abstract-method return query - def configure_grid(self, grid): # pylint: disable=empty-docstring + def configure_grid(self, g): # pylint: disable=empty-docstring """ """ - g = grid super().configure_grid(g) model = self.app.model @@ -356,28 +369,25 @@ class PermissionView(MasterView): # pylint: disable=abstract-method # permission g.set_link("permission") - def configure_form(self, form): # pylint: disable=empty-docstring + def configure_form(self, f): # pylint: disable=empty-docstring """ """ - f = form super().configure_form(f) # role f.set_node("role", RoleRef(self.request)) -def defaults(config, **kwargs): # pylint: disable=missing-function-docstring +def defaults(config, **kwargs): base = globals() - RoleView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name - "RoleView", base["RoleView"] - ) + RoleView = kwargs.get("RoleView", base["RoleView"]) # pylint: disable=invalid-name RoleView.defaults(config) - PermissionView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name + PermissionView = kwargs.get( # pylint: disable=invalid-name "PermissionView", base["PermissionView"] ) PermissionView.defaults(config) -def includeme(config): # pylint: disable=missing-function-docstring +def includeme(config): defaults(config) diff --git a/src/wuttaweb/views/settings.py b/src/wuttaweb/views/settings.py index 11cb0d0..7cfd576 100644 --- a/src/wuttaweb/views/settings.py +++ b/src/wuttaweb/views/settings.py @@ -35,7 +35,7 @@ from wuttaweb.views import MasterView from wuttaweb.util import get_libver, get_liburl -class AppInfoView(MasterView): # pylint: disable=abstract-method +class AppInfoView(MasterView): """ Master view for the core app info, to show/edit config etc. @@ -93,9 +93,8 @@ class AppInfoView(MasterView): # pylint: disable=abstract-method return data - def configure_grid(self, grid): # pylint: disable=empty-docstring + def configure_grid(self, g): # pylint: disable=empty-docstring """ """ - g = grid super().configure_grid(g) g.sort_multiple = False @@ -174,9 +173,7 @@ class AppInfoView(MasterView): # pylint: disable=abstract-method return simple_settings - def configure_get_context( # pylint: disable=empty-docstring,arguments-differ - self, **kwargs - ): + def configure_get_context(self, **kwargs): # pylint: disable=empty-docstring """ """ context = super().configure_get_context(**kwargs) @@ -223,7 +220,7 @@ class AppInfoView(MasterView): # pylint: disable=abstract-method return context -class SettingView(MasterView): # pylint: disable=abstract-method +class SettingView(MasterView): """ Master view for the "raw" settings table. @@ -245,17 +242,15 @@ class SettingView(MasterView): # pylint: disable=abstract-method sort_defaults = "name" # TODO: master should handle this (per model key) - def configure_grid(self, grid): # pylint: disable=empty-docstring + def configure_grid(self, g): # pylint: disable=empty-docstring """ """ - g = grid super().configure_grid(g) # name g.set_link("name") - def configure_form(self, form): # pylint: disable=empty-docstring + def configure_form(self, f): # pylint: disable=empty-docstring """ """ - f = form super().configure_form(f) # name @@ -280,19 +275,19 @@ class SettingView(MasterView): # pylint: disable=abstract-method node.raise_invalid("Setting name must be unique") -def defaults(config, **kwargs): # pylint: disable=missing-function-docstring +def defaults(config, **kwargs): base = globals() - AppInfoView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name + AppInfoView = kwargs.get( # pylint: disable=invalid-name "AppInfoView", base["AppInfoView"] ) AppInfoView.defaults(config) - SettingView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name + SettingView = kwargs.get( # pylint: disable=invalid-name "SettingView", base["SettingView"] ) SettingView.defaults(config) -def includeme(config): # pylint: disable=missing-function-docstring +def includeme(config): defaults(config) diff --git a/src/wuttaweb/views/upgrades.py b/src/wuttaweb/views/upgrades.py index c591b45..bc4e29c 100644 --- a/src/wuttaweb/views/upgrades.py +++ b/src/wuttaweb/views/upgrades.py @@ -41,7 +41,7 @@ from wuttaweb.progress import get_progress_session log = logging.getLogger(__name__) -class UpgradeView(MasterView): # pylint: disable=abstract-method +class UpgradeView(MasterView): """ Master view for upgrades. @@ -72,9 +72,8 @@ class UpgradeView(MasterView): # pylint: disable=abstract-method sort_defaults = ("created", "desc") - def configure_grid(self, grid): # pylint: disable=empty-docstring + def configure_grid(self, g): # pylint: disable=empty-docstring """ """ - g = grid super().configure_grid(g) model = self.app.model enum = self.app.enum @@ -122,9 +121,8 @@ class UpgradeView(MasterView): # pylint: disable=abstract-method return "has-background-warning" return None - def configure_form(self, form): # pylint: disable=empty-docstring + def configure_form(self, f): # pylint: disable=empty-docstring """ """ - f = form super().configure_form(f) enum = self.app.enum upgrade = f.model_instance @@ -206,12 +204,11 @@ class UpgradeView(MasterView): # pylint: disable=abstract-method "stderr_file", self.get_upgrade_filepath(upgrade, "stderr.log") ) - def delete_instance(self, obj): + def delete_instance(self, upgrade): """ We override this method to delete any files associated with the upgrade, in addition to deleting the upgrade proper. """ - upgrade = obj path = self.get_upgrade_filepath(upgrade, create=False) if os.path.exists(path): shutil.rmtree(path) @@ -230,9 +227,8 @@ class UpgradeView(MasterView): # pylint: disable=abstract-method return upgrade - def download_path(self, obj, filename): # pylint: disable=empty-docstring + def download_path(self, upgrade, filename): # pylint: disable=empty-docstring """ """ - upgrade = obj if filename: return self.get_upgrade_filepath(upgrade, filename) return None @@ -249,7 +245,7 @@ class UpgradeView(MasterView): # pylint: disable=abstract-method path = os.path.join(path, filename) return path - def execute_instance(self, obj, user, progress=None): + def execute_instance(self, upgrade, user, progress=None): """ This method runs the actual upgrade. @@ -262,7 +258,6 @@ class UpgradeView(MasterView): # pylint: disable=abstract-method The upgrade itself is marked as "executed" with status of either ``SUCCESS`` or ``FAILURE``. """ - upgrade = obj enum = self.app.enum # locate file paths @@ -382,14 +377,14 @@ class UpgradeView(MasterView): # pylint: disable=abstract-method ) -def defaults(config, **kwargs): # pylint: disable=missing-function-docstring +def defaults(config, **kwargs): base = globals() - UpgradeView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name + UpgradeView = kwargs.get( # pylint: disable=invalid-name "UpgradeView", base["UpgradeView"] ) UpgradeView.defaults(config) -def includeme(config): # pylint: disable=missing-function-docstring +def includeme(config): defaults(config) diff --git a/src/wuttaweb/views/users.py b/src/wuttaweb/views/users.py index fca830f..eefc4af 100644 --- a/src/wuttaweb/views/users.py +++ b/src/wuttaweb/views/users.py @@ -30,7 +30,7 @@ from wuttaweb.forms import widgets from wuttaweb.forms.schema import PersonRef, RoleRefs -class UserView(MasterView): # pylint: disable=abstract-method +class UserView(MasterView): """ Master view for users. @@ -82,9 +82,8 @@ class UserView(MasterView): # pylint: disable=abstract-method return query - def configure_grid(self, grid): # pylint: disable=empty-docstring + def configure_grid(self, g): # pylint: disable=empty-docstring """ """ - g = grid super().configure_grid(g) model = self.app.model @@ -107,9 +106,8 @@ class UserView(MasterView): # pylint: disable=abstract-method return "has-background-warning" return None - def is_editable(self, obj): # pylint: disable=empty-docstring + def is_editable(self, user): # pylint: disable=empty-docstring """ """ - user = obj # only root can edit certain users if user.prevent_edit and not self.request.is_root: @@ -117,9 +115,8 @@ class UserView(MasterView): # pylint: disable=abstract-method return True - def configure_form(self, form): # pylint: disable=empty-docstring + def configure_form(self, f): # pylint: disable=empty-docstring """ """ - f = form super().configure_form(f) user = f.model_instance @@ -236,7 +233,7 @@ class UserView(MasterView): # pylint: disable=abstract-method session = self.Session() auth = self.app.get_auth_handler() - old_roles = {role.uuid for role in user.roles} + old_roles = set([role.uuid for role in user.roles]) new_roles = data["roles"] admin = auth.get_role_administrator(session) @@ -420,14 +417,12 @@ class UserView(MasterView): # pylint: disable=abstract-method ) -def defaults(config, **kwargs): # pylint: disable=missing-function-docstring +def defaults(config, **kwargs): base = globals() - UserView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name - "UserView", base["UserView"] - ) + UserView = kwargs.get("UserView", base["UserView"]) # pylint: disable=invalid-name UserView.defaults(config) -def includeme(config): # pylint: disable=missing-function-docstring +def includeme(config): defaults(config) diff --git a/tests/forms/test_base.py b/tests/forms/test_base.py index 73bc47a..eaa2286 100644 --- a/tests/forms/test_base.py +++ b/tests/forms/test_base.py @@ -363,7 +363,7 @@ class TestForm(TestCase): # basic form = self.make_form(schema=schema) - self.assertIsNone(form.deform_form) + self.assertFalse(hasattr(form, "deform_form")) dform = form.get_deform() self.assertIsInstance(dform, deform.Form) self.assertIs(form.deform_form, dform) @@ -684,7 +684,7 @@ class TestForm(TestCase): def test_validate(self): schema = self.make_schema() form = self.make_form(schema=schema) - self.assertIsNone(form.validated) + self.assertFalse(hasattr(form, "validated")) # will not validate unless request is POST self.request.POST = {"foo": "blarg", "bar": "baz"} diff --git a/tests/grids/test_base.py b/tests/grids/test_base.py index 0498d49..ffc2645 100644 --- a/tests/grids/test_base.py +++ b/tests/grids/test_base.py @@ -497,7 +497,7 @@ class TestGrid(WebTestCase): # settings are loaded, applied, saved self.assertEqual(grid.sort_defaults, []) - self.assertIsNone(grid.active_sorters) + self.assertFalse(hasattr(grid, "active_sorters")) self.request.GET = {"sort1key": "name", "sort1dir": "desc"} grid.load_settings() self.assertEqual(grid.active_sorters, [{"key": "name", "dir": "desc"}]) @@ -525,7 +525,7 @@ class TestGrid(WebTestCase): sort_on_backend=True, sort_defaults="name", ) - self.assertIsNone(grid.active_sorters) + self.assertFalse(hasattr(grid, "active_sorters")) grid.load_settings() self.assertEqual(grid.active_sorters, [{"key": "name", "dir": "asc"}]) @@ -537,7 +537,7 @@ class TestGrid(WebTestCase): mod.SortInfo("name", "asc"), mod.SortInfo("value", "desc"), ] - self.assertIsNone(grid.active_sorters) + self.assertFalse(hasattr(grid, "active_sorters")) grid.load_settings() self.assertEqual(grid.active_sorters, [{"key": "name", "dir": "asc"}]) @@ -556,7 +556,7 @@ class TestGrid(WebTestCase): paginated=True, paginate_on_backend=True, ) - self.assertIsNone(grid.active_sorters) + self.assertFalse(hasattr(grid, "active_sorters")) grid.load_settings() self.assertEqual(grid.active_sorters, [{"key": "name", "dir": "desc"}]) diff --git a/tests/grids/test_filters.py b/tests/grids/test_filters.py index efb58eb..1247715 100644 --- a/tests/grids/test_filters.py +++ b/tests/grids/test_filters.py @@ -52,7 +52,7 @@ class TestGridFilter(WebTestCase): # verb is not set by default, but can be set filtr = self.make_filter(model.Setting.name) - self.assertIsNone(filtr.verb) + self.assertFalse(hasattr(filtr, "verb")) filtr = self.make_filter(model.Setting.name, verb="foo") self.assertEqual(filtr.verb, "foo") diff --git a/tests/test_subscribers.py b/tests/test_subscribers.py index 501e160..9fd7729 100644 --- a/tests/test_subscribers.py +++ b/tests/test_subscribers.py @@ -76,23 +76,23 @@ class TestNewRequest(TestCase): subscribers.new_request(event) # component tracking dict is missing at first - self.assertFalse(hasattr(self.request, "wuttaweb_registered_components")) + 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.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" + 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(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" + self.request._wuttaweb_registered_components["foo-example"], "FooExample" ) def test_get_referrer(self): diff --git a/tests/test_util.py b/tests/test_util.py index a0285a1..48c9695 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -16,7 +16,6 @@ from wuttjamaican.util import resource_path from wuttaweb import util as mod from wuttaweb.app import establish_theme -from wuttaweb.grids import Grid from wuttaweb.testing import WebTestCase @@ -666,52 +665,6 @@ class TestMakeJsonSafe(TestCase): ) -class TestRenderVueFinalize(TestCase): - - def basic(self): - html = mod.render_vue_finalize("wutta-grid", "WuttaGrid") - self.assertIn("