Compare commits
32 commits
601dec7777
...
0a08918657
Author | SHA1 | Date | |
---|---|---|---|
0a08918657 | |||
a1868e1f44 | |||
ad74bede04 | |||
dd25d98e7d | |||
92754a64c4 | |||
459c16ba4f | |||
e123b12cd9 | |||
deaf1976f3 | |||
1dd184622f | |||
95aeb87899 | |||
07e90229ce | |||
2624f9dce8 | |||
2bcdeb42cd | |||
48494ee5e4 | |||
6c66c8d57b | |||
e38f7ba293 | |||
ab35847f23 | |||
ebd44c55f5 | |||
4b4d81c4f3 | |||
ec982fe168 | |||
564ac318bc | |||
65849ad82d | |||
d6f7c19f71 | |||
11ec57387e | |||
5a6ed6135a | |||
d01c343a7c | |||
d7eacf0f52 | |||
0dc6b615e7 | |||
282fc61e50 | |||
1fec99bc92 | |||
d39f5afd4b | |||
5c23120795 |
38 changed files with 584 additions and 397 deletions
38
.pylintrc
38
.pylintrc
|
@ -2,37 +2,7 @@
|
|||
|
||||
[MESSAGES CONTROL]
|
||||
disable=fixme,
|
||||
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,
|
||||
|
||||
[SIMILARITIES]
|
||||
# nb. cuts out some noise for duplicate-code
|
||||
min-similarity-lines=5
|
||||
|
|
|
@ -11,6 +11,9 @@ 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
|
||||
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
"""
|
||||
Package Version
|
||||
"""
|
||||
|
||||
from importlib.metadata import version
|
||||
|
||||
|
|
|
@ -65,13 +65,13 @@ class WebAppProvider(AppProvider):
|
|||
|
||||
:returns: Instance of :class:`~wuttaweb.handler.WebHandler`.
|
||||
"""
|
||||
if "web_handler" not in self.__dict__:
|
||||
if "web" not in self.app.handlers:
|
||||
spec = self.config.get(
|
||||
f"{self.appname}.web.handler_spec",
|
||||
default="wuttaweb.handler:WebHandler",
|
||||
)
|
||||
self.web_handler = self.app.load_object(spec)(self.config)
|
||||
return self.web_handler
|
||||
self.app.handlers["web"] = self.app.load_object(spec)(self.config)
|
||||
return self.app.handlers["web"]
|
||||
|
||||
|
||||
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):
|
||||
make_wsgi_app = app.load_object(main_app)
|
||||
factory = app.load_object(main_app)
|
||||
elif callable(main_app):
|
||||
make_wsgi_app = main_app
|
||||
factory = main_app
|
||||
else:
|
||||
raise ValueError("main_app must be spec or callable")
|
||||
|
||||
# construct a pyramid app "per usual"
|
||||
return make_wsgi_app({}, **settings)
|
||||
return factory({}, **settings)
|
||||
|
||||
|
||||
def make_asgi_app(main_app=None, config=None):
|
||||
|
|
|
@ -105,7 +105,8 @@ class WuttaSecurityPolicy:
|
|||
self.identity_cache = RequestLocalCache(self.load_identity)
|
||||
self.db_session = db_session or Session()
|
||||
|
||||
def load_identity(self, request):
|
||||
def load_identity(self, request): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
config = request.registry.settings["wutta_config"]
|
||||
app = config.get_app()
|
||||
model = app.model
|
||||
|
@ -122,22 +123,29 @@ class WuttaSecurityPolicy:
|
|||
|
||||
return user
|
||||
|
||||
def identity(self, request):
|
||||
def identity(self, request): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
return self.identity_cache.get_or_create(request)
|
||||
|
||||
def authenticated_userid(self, request):
|
||||
def authenticated_userid(self, request): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
user = self.identity(request)
|
||||
if user is not None:
|
||||
return user.uuid
|
||||
return None
|
||||
|
||||
def remember(self, request, userid, **kw):
|
||||
def remember(self, request, userid, **kw): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
return self.session_helper.remember(request, userid, **kw)
|
||||
|
||||
def forget(self, request, **kw):
|
||||
def forget(self, request, **kw): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
return self.session_helper.forget(request, **kw)
|
||||
|
||||
def permits(self, request, context, permission): # pylint: disable=unused-argument
|
||||
def permits( # pylint: disable=unused-argument,empty-docstring
|
||||
self, request, context, permission
|
||||
):
|
||||
""" """
|
||||
|
||||
# nb. root user can do anything
|
||||
if getattr(request, "is_root", False):
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
from wuttjamaican.email import EmailSetting
|
||||
|
||||
|
||||
class feedback(EmailSetting): # pylint: disable=invalid-name
|
||||
class feedback(EmailSetting): # pylint: disable=invalid-name,too-few-public-methods
|
||||
"""
|
||||
Sent when user submits feedback via the web app.
|
||||
"""
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"""
|
||||
Base form classes
|
||||
"""
|
||||
# pylint: disable=too-many-lines
|
||||
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
|
@ -36,13 +37,19 @@ 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
|
||||
from wuttaweb.util import (
|
||||
FieldList,
|
||||
get_form_data,
|
||||
get_model_fields,
|
||||
make_json_safe,
|
||||
render_vue_finalize,
|
||||
)
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Form: # pylint: disable=too-many-instance-attributes
|
||||
class Form: # pylint: disable=too-many-instance-attributes,too-many-public-methods
|
||||
"""
|
||||
Base class for all forms.
|
||||
|
||||
|
@ -263,11 +270,12 @@ class Form: # pylint: disable=too-many-instance-attributes
|
|||
|
||||
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.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
deform_form = None
|
||||
validated = None
|
||||
|
||||
def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments,too-many-locals
|
||||
self,
|
||||
request,
|
||||
fields=None,
|
||||
|
@ -330,7 +338,7 @@ class Form: # pylint: disable=too-many-instance-attributes
|
|||
self.model_class = model_class
|
||||
self.model_instance = model_instance
|
||||
if self.model_instance and not self.model_class:
|
||||
if type(self.model_instance) is not dict:
|
||||
if not isinstance(self.model_instance, dict):
|
||||
self.model_class = type(self.model_instance)
|
||||
|
||||
self.set_fields(fields or self.get_fields())
|
||||
|
@ -873,7 +881,7 @@ class Form: # pylint: disable=too-many-instance-attributes
|
|||
Return the :class:`deform:deform.Form` instance for the form,
|
||||
generating it automatically if necessary.
|
||||
"""
|
||||
if not hasattr(self, "deform_form"):
|
||||
if not self.deform_form:
|
||||
schema = self.get_schema()
|
||||
kwargs = {}
|
||||
|
||||
|
@ -982,7 +990,7 @@ class Form: # pylint: disable=too-many-instance-attributes
|
|||
output = render(template, context)
|
||||
return HTML.literal(output)
|
||||
|
||||
def render_vue_field( # pylint: disable=unused-argument
|
||||
def render_vue_field( # pylint: disable=unused-argument,too-many-locals
|
||||
self,
|
||||
fieldname,
|
||||
readonly=None,
|
||||
|
@ -1093,12 +1101,7 @@ class Form: # pylint: disable=too-many-instance-attributes
|
|||
The actual output may depend on various form attributes, in
|
||||
particular :attr:`vue_tagname`.
|
||||
"""
|
||||
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"],
|
||||
)
|
||||
return render_vue_finalize(self.vue_tagname, self.vue_component)
|
||||
|
||||
def get_vue_model_data(self):
|
||||
"""
|
||||
|
@ -1124,7 +1127,7 @@ class Form: # pylint: disable=too-many-instance-attributes
|
|||
# for now we explicitly translate here, ugh. also
|
||||
# note this does not yet allow for null values.. :(
|
||||
if isinstance(field.typ, colander.Boolean):
|
||||
value = True if value == field.typ.true_val else False
|
||||
value = value == field.typ.true_val
|
||||
|
||||
model_data[field.oid] = make_json_safe(value)
|
||||
|
||||
|
@ -1173,9 +1176,9 @@ class Form: # pylint: disable=too-many-instance-attributes
|
|||
:attr:`validated` attribute.
|
||||
|
||||
However if the data is not valid, ``False`` is returned, and
|
||||
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
|
||||
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
|
||||
:meth:`get_field_errors()`.
|
||||
|
||||
This uses :meth:`deform:deform.Field.validate()` under the
|
||||
|
@ -1191,8 +1194,7 @@ class Form: # pylint: disable=too-many-instance-attributes
|
|||
|
||||
:returns: Data dict, or ``False``.
|
||||
"""
|
||||
if hasattr(self, "validated"):
|
||||
del self.validated
|
||||
self.validated = None
|
||||
|
||||
if self.request.method != "POST":
|
||||
return False
|
||||
|
|
|
@ -69,7 +69,7 @@ class WuttaDateTime(colander.DateTime):
|
|||
node.raise_invalid("Invalid date and/or time")
|
||||
|
||||
|
||||
class ObjectNode(colander.SchemaNode):
|
||||
class ObjectNode(colander.SchemaNode): # pylint: disable=abstract-method
|
||||
"""
|
||||
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"] = [(k, v) for k, v in self.enum_dct.items()]
|
||||
kwargs["values"] = list(self.enum_dct.items())
|
||||
|
||||
return widgets.SelectWidget(**kwargs)
|
||||
|
||||
|
@ -288,13 +288,8 @@ class ObjectRef(colander.SchemaType):
|
|||
|
||||
default_empty_option = ("", "(none)")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
request,
|
||||
empty_option=None,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
empty_option = kwargs.pop("empty_option", None)
|
||||
# nb. allow session injection for tests
|
||||
self.session = kwargs.pop("session", Session())
|
||||
super().__init__(*args, **kwargs)
|
||||
|
@ -381,7 +376,9 @@ class ObjectRef(colander.SchemaType):
|
|||
if not value:
|
||||
return None
|
||||
|
||||
if isinstance(value, self.model_class):
|
||||
if isinstance( # pylint: disable=isinstance-second-argument-not-valid-type
|
||||
value, self.model_class
|
||||
):
|
||||
return value
|
||||
|
||||
# fetch object from DB
|
||||
|
@ -475,8 +472,9 @@ class PersonRef(ObjectRef):
|
|||
""" """
|
||||
return query.order_by(self.model_class.full_name)
|
||||
|
||||
def get_object_url(self, person): # pylint: disable=empty-docstring
|
||||
def get_object_url(self, obj): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
person = obj
|
||||
return self.request.route_url("people.view", uuid=person.uuid)
|
||||
|
||||
|
||||
|
@ -499,8 +497,9 @@ class RoleRef(ObjectRef):
|
|||
""" """
|
||||
return query.order_by(self.model_class.name)
|
||||
|
||||
def get_object_url(self, role): # pylint: disable=empty-docstring
|
||||
def get_object_url(self, obj): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
role = obj
|
||||
return self.request.route_url("roles.view", uuid=role.uuid)
|
||||
|
||||
|
||||
|
@ -523,8 +522,9 @@ class UserRef(ObjectRef):
|
|||
""" """
|
||||
return query.order_by(self.model_class.username)
|
||||
|
||||
def get_object_url(self, user): # pylint: disable=empty-docstring
|
||||
def get_object_url(self, obj): # 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 = set([role.uuid for role in avoid])
|
||||
avoid = {role.uuid for role in avoid}
|
||||
|
||||
# also avoid admin unless current user is root
|
||||
if not self.request.is_root:
|
||||
|
|
|
@ -103,7 +103,8 @@ class ObjectRefWidget(SelectWidget):
|
|||
|
||||
readonly_template = "readonly/objectref"
|
||||
|
||||
def __init__(self, request, url=None, *args, **kwargs):
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
url = kwargs.pop("url", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.request = request
|
||||
self.url = url
|
||||
|
@ -299,7 +300,7 @@ class WuttaMoneyInputWidget(MoneyInputWidget):
|
|||
return super().serialize(field, cstruct, **kw)
|
||||
|
||||
|
||||
class FileDownloadWidget(Widget):
|
||||
class FileDownloadWidget(Widget): # pylint: disable=abstract-method
|
||||
"""
|
||||
Widget for use with :class:`~wuttaweb.forms.schema.FileDownload`
|
||||
fields.
|
||||
|
@ -357,7 +358,7 @@ class FileDownloadWidget(Widget):
|
|||
return humanize.naturalsize(size)
|
||||
|
||||
|
||||
class GridWidget(Widget):
|
||||
class GridWidget(Widget): # pylint: disable=abstract-method
|
||||
"""
|
||||
Widget for fields whose data is represented by a :term:`grid`.
|
||||
|
||||
|
@ -406,6 +407,7 @@ class RoleRefsWidget(WuttaCheckboxChoiceWidget):
|
|||
"""
|
||||
|
||||
readonly_template = "readonly/rolerefs"
|
||||
session = None
|
||||
|
||||
def serialize(self, field, cstruct, **kw): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
|
@ -462,6 +464,7 @@ class PermissionsWidget(WuttaCheckboxChoiceWidget):
|
|||
|
||||
template = "permissions"
|
||||
readonly_template = "readonly/permissions"
|
||||
permissions = None
|
||||
|
||||
def serialize(self, field, cstruct, **kw): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
|
@ -512,7 +515,7 @@ class EmailRecipientsWidget(TextAreaWidget):
|
|||
return ", ".join(values)
|
||||
|
||||
|
||||
class BatchIdWidget(Widget):
|
||||
class BatchIdWidget(Widget): # pylint: disable=abstract-method
|
||||
"""
|
||||
Widget for use with the
|
||||
:attr:`~wuttjamaican:wuttjamaican.db.model.batch.BatchMixin.id`
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"""
|
||||
Base grid classes
|
||||
"""
|
||||
# pylint: disable=too-many-lines
|
||||
|
||||
import functools
|
||||
import logging
|
||||
|
@ -38,7 +39,12 @@ 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
|
||||
from wuttaweb.util import (
|
||||
FieldList,
|
||||
get_model_fields,
|
||||
make_json_safe,
|
||||
render_vue_finalize,
|
||||
)
|
||||
from wuttaweb.grids.filters import default_sqlalchemy_filters, VerbNotSupported
|
||||
|
||||
|
||||
|
@ -53,7 +59,7 @@ Elements of :attr:`~Grid.sort_defaults` will be of this type.
|
|||
"""
|
||||
|
||||
|
||||
class Grid: # pylint: disable=too-many-instance-attributes
|
||||
class Grid: # pylint: disable=too-many-instance-attributes,too-many-public-methods
|
||||
"""
|
||||
Base class for all :term:`grids <grid>`.
|
||||
|
||||
|
@ -263,7 +269,7 @@ class Grid: # pylint: disable=too-many-instance-attributes
|
|||
``active_sorters`` defines the "current/effective" sorters.
|
||||
|
||||
This attribute is set by :meth:`load_settings()`; until that is
|
||||
called it will not exist.
|
||||
called its value will be ``None``.
|
||||
|
||||
This is conceptually a "subset" of :attr:`sorters` although a
|
||||
different format is used here::
|
||||
|
@ -371,7 +377,11 @@ class Grid: # pylint: disable=too-many-instance-attributes
|
|||
See also :meth:`add_tool()` and :meth:`set_tools()`.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
active_sorters = None
|
||||
joined = None
|
||||
pager = None
|
||||
|
||||
def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments,too-many-locals
|
||||
self,
|
||||
request,
|
||||
vue_tagname="wutta-grid",
|
||||
|
@ -688,7 +698,7 @@ class Grid: # pylint: disable=too-many-instance-attributes
|
|||
"percent": self.render_percent,
|
||||
}
|
||||
|
||||
if renderer in builtins:
|
||||
if renderer in builtins: # pylint: disable=consider-using-get
|
||||
renderer = builtins[renderer]
|
||||
|
||||
if kwargs:
|
||||
|
@ -1086,8 +1096,8 @@ class Grid: # pylint: disable=too-many-instance-attributes
|
|||
# TODO: this should be improved; is needed in tailbone for
|
||||
# multi-column sorting with sqlalchemy queries
|
||||
if model_property:
|
||||
sorter._class = model_class
|
||||
sorter._column = model_property
|
||||
sorter._class = model_class # pylint: disable=protected-access
|
||||
sorter._column = model_property # pylint: disable=protected-access
|
||||
|
||||
return sorter
|
||||
|
||||
|
@ -1372,10 +1382,10 @@ class Grid: # pylint: disable=too-many-instance-attributes
|
|||
if filterinfo and callable(filterinfo):
|
||||
# filtr = filterinfo
|
||||
raise NotImplementedError
|
||||
else:
|
||||
kwargs["key"] = key
|
||||
kwargs.setdefault("label", self.get_label(key))
|
||||
filtr = self.make_filter(filterinfo or key, **kwargs)
|
||||
|
||||
kwargs["key"] = key
|
||||
kwargs.setdefault("label", self.get_label(key))
|
||||
filtr = self.make_filter(filterinfo or key, **kwargs)
|
||||
|
||||
self.filters[key] = filtr
|
||||
|
||||
|
@ -1473,7 +1483,9 @@ class Grid: # pylint: disable=too-many-instance-attributes
|
|||
# configuration methods
|
||||
##############################
|
||||
|
||||
def load_settings(self, persist=True): # pylint: disable=too-many-branches
|
||||
def load_settings( # pylint: disable=too-many-branches,too-many-statements
|
||||
self, persist=True
|
||||
):
|
||||
"""
|
||||
Load all effective settings for the grid.
|
||||
|
||||
|
@ -1626,7 +1638,7 @@ class Grid: # pylint: disable=too-many-instance-attributes
|
|||
|
||||
return False
|
||||
|
||||
def get_setting( # pylint: disable=empty-docstring
|
||||
def get_setting( # pylint: disable=empty-docstring,too-many-arguments,too-many-positional-arguments
|
||||
self, settings, key, src="session", default=None, normalize=lambda v: v
|
||||
):
|
||||
""" """
|
||||
|
@ -2205,12 +2217,7 @@ class Grid: # pylint: disable=too-many-instance-attributes
|
|||
The actual output may depend on various grid attributes, in
|
||||
particular :attr:`vue_tagname`.
|
||||
"""
|
||||
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"],
|
||||
)
|
||||
return render_vue_finalize(self.vue_tagname, self.vue_component)
|
||||
|
||||
def get_vue_columns(self):
|
||||
"""
|
||||
|
@ -2290,12 +2297,11 @@ class Grid: # pylint: disable=too-many-instance-attributes
|
|||
:returns: The first sorter in format ``[sortkey, sortdir]``,
|
||||
or ``None``.
|
||||
"""
|
||||
if hasattr(self, "active_sorters"):
|
||||
if self.active_sorters:
|
||||
sorter = self.active_sorters[0]
|
||||
return [sorter["key"], sorter["dir"]]
|
||||
if self.active_sorters:
|
||||
sorter = self.active_sorters[0]
|
||||
return [sorter["key"], sorter["dir"]]
|
||||
|
||||
elif self.sort_defaults:
|
||||
if self.sort_defaults:
|
||||
sorter = self.sort_defaults[0]
|
||||
return [sorter.sortkey, sorter.sortdir]
|
||||
|
||||
|
@ -2386,9 +2392,9 @@ class Grid: # pylint: disable=too-many-instance-attributes
|
|||
record = make_json_safe(record, warn=False)
|
||||
|
||||
# customize value rendering where applicable
|
||||
for key in self.renderers:
|
||||
for key, renderer in self.renderers.items():
|
||||
value = record.get(key, None)
|
||||
record[key] = self.renderers[key](original_record, key, value)
|
||||
record[key] = renderer(original_record, key, value)
|
||||
|
||||
# add action urls to each record
|
||||
for action in self.actions:
|
||||
|
@ -2537,7 +2543,7 @@ class GridAction: # pylint: disable=too-many-instance-attributes
|
|||
Optional HTML class attribute for the action's ``<a>`` tag.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
||||
self,
|
||||
request,
|
||||
key,
|
||||
|
|
|
@ -59,9 +59,10 @@ class GridFilter: # pylint: disable=too-many-instance-attributes
|
|||
|
||||
:param request: Current :term:`request` object.
|
||||
|
||||
: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 (if possible) and use
|
||||
its nullable flag.
|
||||
|
||||
:param \\**kwargs: Any additional kwargs will be set as attributes
|
||||
on the filter instance.
|
||||
|
@ -169,13 +170,14 @@ class GridFilter: # pylint: disable=too-many-instance-attributes
|
|||
"is_not_null",
|
||||
]
|
||||
|
||||
def __init__(
|
||||
def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
||||
self,
|
||||
request,
|
||||
key,
|
||||
label=None,
|
||||
verbs=None,
|
||||
choices=None,
|
||||
nullable=None,
|
||||
default_active=False,
|
||||
default_verb=None,
|
||||
default_value=None,
|
||||
|
@ -196,10 +198,14 @@ 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
|
||||
|
@ -248,7 +254,7 @@ class GridFilter: # pylint: disable=too-many-instance-attributes
|
|||
Returns a dict of all defined verb labels.
|
||||
"""
|
||||
# TODO: should traverse hierarchy
|
||||
labels = dict([(verb, verb) for verb in self.get_verbs()])
|
||||
labels = {verb: verb for verb in self.get_verbs()}
|
||||
labels.update(self.default_verb_labels)
|
||||
return labels
|
||||
|
||||
|
@ -378,7 +384,7 @@ class GridFilter: # pylint: disable=too-many-instance-attributes
|
|||
raise VerbNotSupported(verb)
|
||||
|
||||
# invoke filter method
|
||||
return func(data, value)
|
||||
return func(data, value) # pylint: disable=not-callable
|
||||
|
||||
def filter_is_any(self, data, value): # pylint: disable=unused-argument
|
||||
"""
|
||||
|
@ -398,18 +404,12 @@ 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):
|
||||
nullable = kwargs.pop("nullable", None)
|
||||
self.model_property = kwargs.pop("model_property")
|
||||
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,
|
||||
self.model_property == None, # pylint: disable=singleton-comparison
|
||||
self.model_property != value,
|
||||
)
|
||||
)
|
||||
|
@ -491,14 +491,18 @@ class AlchemyFilter(GridFilter):
|
|||
"""
|
||||
Filter data with an ``IS NULL`` query. The value is ignored.
|
||||
"""
|
||||
return query.filter(self.model_property == None)
|
||||
return query.filter(
|
||||
self.model_property == None # pylint: disable=singleton-comparison
|
||||
)
|
||||
|
||||
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)
|
||||
return query.filter(
|
||||
self.model_property != None # pylint: disable=singleton-comparison
|
||||
)
|
||||
|
||||
|
||||
class StringAlchemyFilter(AlchemyFilter):
|
||||
|
@ -550,7 +554,12 @@ 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, sa.and_(*criteria)))
|
||||
return query.filter(
|
||||
sa.or_(
|
||||
self.model_property == None, # pylint: disable=singleton-comparison
|
||||
sa.and_(*criteria),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class NumericAlchemyFilter(AlchemyFilter):
|
||||
|
@ -628,14 +637,18 @@ class BooleanAlchemyFilter(AlchemyFilter):
|
|||
Filter data with an "is true" condition. The value is
|
||||
ignored.
|
||||
"""
|
||||
return query.filter(self.model_property == True)
|
||||
return query.filter(
|
||||
self.model_property == True # pylint: disable=singleton-comparison
|
||||
)
|
||||
|
||||
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)
|
||||
return query.filter(
|
||||
self.model_property == False # pylint: disable=singleton-comparison
|
||||
)
|
||||
|
||||
def filter_is_false_null(self, query, value): # pylint: disable=unused-argument
|
||||
"""
|
||||
|
@ -643,7 +656,10 @@ class BooleanAlchemyFilter(AlchemyFilter):
|
|||
ignored.
|
||||
"""
|
||||
return query.filter(
|
||||
sa.or_(self.model_property == False, self.model_property == None)
|
||||
sa.or_(
|
||||
self.model_property == False, # pylint: disable=singleton-comparison
|
||||
self.model_property == None, # pylint: disable=singleton-comparison
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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:
|
||||
for topitem in raw_menus: # pylint: disable=too-many-nested-blocks
|
||||
|
||||
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:
|
||||
for topitem in menus: # pylint: disable=too-many-nested-blocks
|
||||
|
||||
if topitem.get("type", "menu") == "link":
|
||||
topitem["allowed"] = True
|
||||
|
|
|
@ -91,7 +91,7 @@ class SessionProgress(ProgressBase): # pylint: disable=too-many-instance-attrib
|
|||
:attr:`success_url`.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments,super-init-not-called
|
||||
self, request, key, success_msg=None, success_url=None, error_url=None
|
||||
):
|
||||
self.request = request
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
################################################################################
|
||||
#
|
||||
# wuttaweb -- Web App for Wutta Framework
|
||||
# Copyright © 2024 Lance Edgar
|
||||
# Copyright © 2024-2025 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):
|
||||
def includeme(config): # pylint: disable=missing-function-docstring
|
||||
config.add_static_view("wuttaweb", "wuttaweb:static")
|
||||
|
|
|
@ -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):
|
||||
def includeme(config): # pylint: disable=missing-function-docstring
|
||||
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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
################################################################################
|
||||
#
|
||||
# wuttaweb -- Web App for Wutta Framework
|
||||
# Copyright © 2024 Lance Edgar
|
||||
# Copyright © 2024-2025 Lance Edgar
|
||||
#
|
||||
# This file is part of Wutta Framework.
|
||||
#
|
||||
|
@ -39,10 +39,14 @@ class WebTestCase(DataTestCase):
|
|||
Base class for test suites requiring a full (typical) web app.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
def setUp(self): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
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(
|
||||
|
@ -87,8 +91,14 @@ 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()
|
||||
|
|
|
@ -618,6 +618,71 @@ 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
|
||||
|
||||
<script>
|
||||
WuttaGrid.data = function() { return WuttaGridData }
|
||||
Vue.component('wutta-grid', WuttaGrid)
|
||||
</script>
|
||||
"""
|
||||
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
|
||||
##############################
|
||||
|
@ -757,14 +822,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("/menu.mako")
|
||||
renderer = get_renderer("/page.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()
|
||||
lookup._collection.clear() # pylint: disable=protected-access
|
||||
|
||||
# persist current theme in db settings
|
||||
with app.short_session(session=session) as s:
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
################################################################################
|
||||
#
|
||||
# wuttaweb -- Web App for Wutta Framework
|
||||
# Copyright © 2024 Lance Edgar
|
||||
# Copyright © 2024-2025 Lance Edgar
|
||||
#
|
||||
# This file is part of Wutta Framework.
|
||||
#
|
||||
|
@ -34,5 +34,5 @@ from .base import View
|
|||
from .master import MasterView
|
||||
|
||||
|
||||
def includeme(config):
|
||||
def includeme(config): # pylint: disable=missing-function-docstring
|
||||
config.include("wuttaweb.views.essential")
|
||||
|
|
|
@ -95,7 +95,8 @@ class AuthView(View):
|
|||
# 'referrer': referrer,
|
||||
}
|
||||
|
||||
def login_make_schema(self):
|
||||
def login_make_schema(self): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
schema = colander.Schema()
|
||||
|
||||
# nb. we must explicitly declare the widgets in order to also
|
||||
|
@ -220,13 +221,19 @@ class AuthView(View):
|
|||
|
||||
return schema
|
||||
|
||||
def change_password_validate_current_password(self, node, value):
|
||||
def change_password_validate_current_password( # pylint: disable=empty-docstring
|
||||
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(self, node, value):
|
||||
def change_password_validate_new_password( # pylint: disable=empty-docstring
|
||||
self, node, value
|
||||
):
|
||||
""" """
|
||||
auth = self.app.get_auth_handler()
|
||||
user = self.request.user
|
||||
if auth.check_user_password(user, value):
|
||||
|
@ -277,7 +284,8 @@ class AuthView(View):
|
|||
return self.redirect(url)
|
||||
|
||||
@classmethod
|
||||
def defaults(cls, config):
|
||||
def defaults(cls, config): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
cls._auth_defaults(config)
|
||||
|
||||
@classmethod
|
||||
|
@ -311,12 +319,14 @@ class AuthView(View):
|
|||
config.add_view(cls, attr="stop_root", route_name="stop_root")
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
def defaults(config, **kwargs): # pylint: disable=missing-function-docstring
|
||||
base = globals()
|
||||
|
||||
AuthView = kwargs.get("AuthView", base["AuthView"]) # pylint: disable=invalid-name
|
||||
AuthView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name
|
||||
"AuthView", base["AuthView"]
|
||||
)
|
||||
AuthView.defaults(config)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
def includeme(config): # pylint: disable=missing-function-docstring
|
||||
defaults(config)
|
||||
|
|
|
@ -51,6 +51,8 @@ class BatchMasterView(MasterView):
|
|||
from :meth:`get_batch_handler()`.
|
||||
"""
|
||||
|
||||
executable = True
|
||||
|
||||
labels = {
|
||||
"id": "Batch ID",
|
||||
"status_code": "Status",
|
||||
|
@ -121,8 +123,9 @@ class BatchMasterView(MasterView):
|
|||
|
||||
return super().render_to_response(template, context)
|
||||
|
||||
def configure_grid(self, g): # pylint: disable=empty-docstring
|
||||
def configure_grid(self, grid): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
g = grid
|
||||
super().configure_grid(g)
|
||||
model = self.app.model
|
||||
|
||||
|
@ -153,15 +156,17 @@ class BatchMasterView(MasterView):
|
|||
return f"{batch_id:08d}"
|
||||
return None
|
||||
|
||||
def get_instance_title(self, batch): # pylint: disable=empty-docstring
|
||||
def get_instance_title(self, instance): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
batch = instance
|
||||
if batch.description:
|
||||
return f"{batch.id_str} {batch.description}"
|
||||
return batch.id_str
|
||||
|
||||
def configure_form(self, f): # pylint: disable=too-many-branches,empty-docstring
|
||||
def configure_form(self, form): # pylint: disable=too-many-branches,empty-docstring
|
||||
""" """
|
||||
super().configure_form(f)
|
||||
super().configure_form(form)
|
||||
f = form
|
||||
batch = f.model_instance
|
||||
|
||||
# id
|
||||
|
@ -235,13 +240,11 @@ class BatchMasterView(MasterView):
|
|||
batch = schema.objectify(form.validated, context=form.model_instance)
|
||||
|
||||
# then we collect attributes from the new batch
|
||||
kw = dict(
|
||||
[
|
||||
(key, getattr(batch, key))
|
||||
for key in form.validated
|
||||
if hasattr(batch, key)
|
||||
]
|
||||
)
|
||||
kw = {
|
||||
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
|
||||
|
@ -255,7 +258,7 @@ class BatchMasterView(MasterView):
|
|||
# when not creating, normal logic is fine
|
||||
return super().objectify(form)
|
||||
|
||||
def redirect_after_create(self, batch):
|
||||
def redirect_after_create(self, obj):
|
||||
"""
|
||||
If the new batch requires initial population, we launch a
|
||||
thread for that and show the "progress" page.
|
||||
|
@ -263,6 +266,8 @@ 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))
|
||||
|
@ -283,7 +288,7 @@ class BatchMasterView(MasterView):
|
|||
thread.start()
|
||||
return self.render_progress(progress)
|
||||
|
||||
def delete_instance(self, batch):
|
||||
def delete_instance(self, obj):
|
||||
"""
|
||||
Delete the given batch instance.
|
||||
|
||||
|
@ -291,6 +296,7 @@ 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)
|
||||
|
||||
##############################
|
||||
|
@ -326,29 +332,22 @@ class BatchMasterView(MasterView):
|
|||
raise RuntimeError("can't find the batch")
|
||||
time.sleep(0.1)
|
||||
|
||||
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()
|
||||
def onerror():
|
||||
log.warning(
|
||||
"failed to populate %s: %s",
|
||||
self.get_model_title(),
|
||||
batch,
|
||||
exc_info=True,
|
||||
)
|
||||
if progress:
|
||||
progress.handle_error(error)
|
||||
|
||||
else:
|
||||
session.commit()
|
||||
if progress:
|
||||
progress.handle_success()
|
||||
|
||||
finally:
|
||||
session.close()
|
||||
self.do_thread_body(
|
||||
self.batch_handler.do_populate,
|
||||
(batch,),
|
||||
{"progress": progress},
|
||||
onerror,
|
||||
session=session,
|
||||
progress=progress,
|
||||
)
|
||||
|
||||
##############################
|
||||
# execute methods
|
||||
|
@ -388,20 +387,21 @@ class BatchMasterView(MasterView):
|
|||
model_class = cls.get_model_class()
|
||||
return model_class.__row_class__
|
||||
|
||||
def get_row_grid_data(self, batch):
|
||||
def get_row_grid_data(self, obj):
|
||||
"""
|
||||
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 = self.Session.query(row_model_class).filter(
|
||||
row_model_class.batch == batch
|
||||
)
|
||||
query = session.query(row_model_class).filter(row_model_class.batch == batch)
|
||||
return query
|
||||
|
||||
def configure_row_grid(self, g): # pylint: disable=empty-docstring
|
||||
def configure_row_grid(self, grid): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
g = grid
|
||||
super().configure_row_grid(g)
|
||||
|
||||
g.set_label("sequence", "Seq.", column_only=True)
|
||||
|
@ -413,36 +413,3 @@ 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}"
|
||||
)
|
||||
|
|
|
@ -135,7 +135,7 @@ class CommonView(View):
|
|||
""" """
|
||||
self.app.send_email("feedback", context)
|
||||
|
||||
def setup(self, session=None):
|
||||
def setup(self, session=None): # pylint: disable=too-many-locals
|
||||
"""
|
||||
View for first-time app setup, to create admin user.
|
||||
|
||||
|
@ -294,7 +294,8 @@ class CommonView(View):
|
|||
return self.redirect(referrer)
|
||||
|
||||
@classmethod
|
||||
def defaults(cls, config):
|
||||
def defaults(cls, config): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
cls._defaults(config)
|
||||
|
||||
@classmethod
|
||||
|
@ -342,14 +343,14 @@ class CommonView(View):
|
|||
)
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
def defaults(config, **kwargs): # pylint: disable=missing-function-docstring
|
||||
base = globals()
|
||||
|
||||
CommonView = kwargs.get( # pylint: disable=invalid-name
|
||||
CommonView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name
|
||||
"CommonView", base["CommonView"]
|
||||
)
|
||||
CommonView.defaults(config)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
def includeme(config): # pylint: disable=missing-function-docstring
|
||||
defaults(config)
|
||||
|
|
|
@ -30,7 +30,7 @@ from wuttaweb.views import MasterView
|
|||
from wuttaweb.forms.schema import EmailRecipients
|
||||
|
||||
|
||||
class EmailSettingView(MasterView):
|
||||
class EmailSettingView(MasterView): # pylint: disable=abstract-method
|
||||
"""
|
||||
Master view for :term:`email settings <email setting>`.
|
||||
"""
|
||||
|
@ -107,8 +107,9 @@ class EmailSettingView(MasterView):
|
|||
"enabled": self.email_handler.is_enabled(key),
|
||||
}
|
||||
|
||||
def configure_grid(self, g): # pylint: disable=empty-docstring
|
||||
def configure_grid(self, grid): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
g = grid
|
||||
super().configure_grid(g)
|
||||
|
||||
# key
|
||||
|
@ -136,7 +137,9 @@ class EmailSettingView(MasterView):
|
|||
recips = ", ".join(recips[:2])
|
||||
return f"{recips}, ..."
|
||||
|
||||
def get_instance(self): # pylint: disable=empty-docstring
|
||||
def get_instance( # pylint: disable=empty-docstring,arguments-differ,unused-argument
|
||||
self, **kwargs
|
||||
):
|
||||
""" """
|
||||
key = self.request.matchdict["key"]
|
||||
setting = self.email_handler.get_email_setting(key, instance=False)
|
||||
|
@ -145,12 +148,14 @@ class EmailSettingView(MasterView):
|
|||
|
||||
raise self.notfound()
|
||||
|
||||
def get_instance_title(self, setting): # pylint: disable=empty-docstring
|
||||
def get_instance_title(self, instance): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
setting = instance
|
||||
return setting["subject"]
|
||||
|
||||
def configure_form(self, f): # pylint: disable=empty-docstring
|
||||
def configure_form(self, form): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
f = form
|
||||
super().configure_form(f)
|
||||
|
||||
# description
|
||||
|
@ -175,7 +180,9 @@ class EmailSettingView(MasterView):
|
|||
# enabled
|
||||
f.set_node("enabled", colander.Boolean())
|
||||
|
||||
def persist(self, setting): # pylint: disable=too-many-branches,empty-docstring
|
||||
def persist( # pylint: disable=too-many-branches,empty-docstring,arguments-differ,unused-argument
|
||||
self, setting, **kwargs
|
||||
):
|
||||
""" """
|
||||
session = self.Session()
|
||||
key = self.request.matchdict["key"]
|
||||
|
@ -300,14 +307,14 @@ class EmailSettingView(MasterView):
|
|||
)
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
def defaults(config, **kwargs): # pylint: disable=missing-function-docstring
|
||||
base = globals()
|
||||
|
||||
EmailSettingView = kwargs.get( # pylint: disable=invalid-name
|
||||
EmailSettingView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name
|
||||
"EmailSettingView", base["EmailSettingView"]
|
||||
)
|
||||
EmailSettingView.defaults(config)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
def includeme(config): # pylint: disable=missing-function-docstring
|
||||
defaults(config)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
################################################################################
|
||||
#
|
||||
# wuttaweb -- Web App for Wutta Framework
|
||||
# Copyright © 2024 Lance Edgar
|
||||
# Copyright © 2024-2025 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):
|
||||
def defaults(config, **kwargs): # pylint: disable=missing-function-docstring
|
||||
|
||||
def mod(spec):
|
||||
return kwargs.get(spec, spec)
|
||||
|
@ -57,5 +57,5 @@ def defaults(config, **kwargs):
|
|||
config.include(mod("wuttaweb.views.upgrades"))
|
||||
|
||||
|
||||
def includeme(config):
|
||||
def includeme(config): # pylint: disable=missing-function-docstring
|
||||
defaults(config)
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"""
|
||||
Base Logic for Master Views
|
||||
"""
|
||||
# pylint: disable=too-many-lines
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
@ -44,7 +45,7 @@ from wuttaweb.progress import SessionProgress
|
|||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MasterView(View):
|
||||
class MasterView(View): # pylint: disable=too-many-public-methods
|
||||
"""
|
||||
Base class for "master" views.
|
||||
|
||||
|
@ -398,6 +399,8 @@ class MasterView(View):
|
|||
# attributes
|
||||
##############################
|
||||
|
||||
model_class = None
|
||||
|
||||
# features
|
||||
listable = True
|
||||
has_grid = True
|
||||
|
@ -438,6 +441,7 @@ class MasterView(View):
|
|||
viewing = False
|
||||
editing = False
|
||||
deleting = False
|
||||
executing = False
|
||||
configuring = False
|
||||
|
||||
# default DB session
|
||||
|
@ -524,11 +528,12 @@ class MasterView(View):
|
|||
* :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)
|
||||
self.Session.flush()
|
||||
session.flush()
|
||||
return self.redirect_after_create(obj)
|
||||
|
||||
context = {
|
||||
|
@ -817,33 +822,25 @@ class MasterView(View):
|
|||
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()
|
||||
|
||||
try:
|
||||
self.delete_bulk_action(records, progress=progress)
|
||||
|
||||
except Exception as error: # pylint: disable=broad-exception-caught
|
||||
session.rollback()
|
||||
def onerror():
|
||||
log.warning(
|
||||
"failed to delete %s results for %s",
|
||||
len(records),
|
||||
model_title_plural,
|
||||
self.get_model_title_plural(),
|
||||
exc_info=True,
|
||||
)
|
||||
if progress:
|
||||
progress.handle_error(error)
|
||||
|
||||
else:
|
||||
session.commit()
|
||||
if progress:
|
||||
progress.handle_success()
|
||||
|
||||
finally:
|
||||
session.close()
|
||||
self.do_thread_body(
|
||||
self.delete_bulk_action,
|
||||
(records,),
|
||||
{"progress": progress},
|
||||
onerror,
|
||||
session=session,
|
||||
progress=progress,
|
||||
)
|
||||
|
||||
def delete_bulk_action(self, data, progress=None):
|
||||
"""
|
||||
|
@ -917,7 +914,7 @@ class MasterView(View):
|
|||
if not term:
|
||||
return []
|
||||
|
||||
data = self.autocomplete_data(term)
|
||||
data = self.autocomplete_data(term) # pylint: disable=assignment-from-none
|
||||
if not data:
|
||||
return []
|
||||
|
||||
|
@ -931,7 +928,7 @@ class MasterView(View):
|
|||
|
||||
return results
|
||||
|
||||
def autocomplete_data(self, term):
|
||||
def autocomplete_data(self, term): # pylint: disable=unused-argument
|
||||
"""
|
||||
Should return the data/query for the "matching" model records,
|
||||
based on autocomplete search term. This is called by
|
||||
|
@ -943,6 +940,7 @@ class MasterView(View):
|
|||
|
||||
:returns: List of data records, or SQLAlchemy query.
|
||||
"""
|
||||
return None
|
||||
|
||||
def autocomplete_normalize(self, obj):
|
||||
"""
|
||||
|
@ -1006,13 +1004,13 @@ class MasterView(View):
|
|||
obj = self.get_instance()
|
||||
filename = self.request.GET.get("filename", None)
|
||||
|
||||
path = self.download_path(obj, filename)
|
||||
path = self.download_path(obj, filename) # pylint: disable=assignment-from-none
|
||||
if not path or not os.path.exists(path):
|
||||
return self.notfound()
|
||||
|
||||
return self.file_response(path)
|
||||
|
||||
def download_path(self, obj, filename):
|
||||
def download_path(self, obj, filename): # pylint: disable=unused-argument
|
||||
"""
|
||||
Should return absolute path on disk, for the given object and
|
||||
filename. Result will be used to return a file response to
|
||||
|
@ -1033,6 +1031,7 @@ class MasterView(View):
|
|||
the :meth:`download()` view will return a 404 not found
|
||||
response.
|
||||
"""
|
||||
return None
|
||||
|
||||
##############################
|
||||
# execute methods
|
||||
|
@ -1304,6 +1303,7 @@ class MasterView(View):
|
|||
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):
|
|||
:returns: The dict of route kwargs for the object.
|
||||
"""
|
||||
try:
|
||||
return dict([(key, obj[key]) for key in self.get_model_key()])
|
||||
return {key: obj[key] for key in self.get_model_key()}
|
||||
except TypeError:
|
||||
return dict([(key, getattr(obj, key)) for key in self.get_model_key()])
|
||||
return {key: getattr(obj, key) for key in self.get_model_key()}
|
||||
|
||||
def get_action_url(self, action, obj, **kwargs):
|
||||
"""
|
||||
|
@ -2477,6 +2477,60 @@ class MasterView(View):
|
|||
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
|
||||
##############################
|
||||
|
@ -2697,9 +2751,7 @@ class MasterView(View):
|
|||
do not set the :attr:`model_class`, then you *must* set the
|
||||
:attr:`model_name`.
|
||||
"""
|
||||
if hasattr(cls, "model_class"):
|
||||
return cls.model_class
|
||||
return None
|
||||
return cls.model_class
|
||||
|
||||
@classmethod
|
||||
def get_model_name(cls):
|
||||
|
@ -2808,11 +2860,9 @@ class MasterView(View):
|
|||
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}")
|
||||
|
|
|
@ -28,9 +28,10 @@ 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):
|
||||
class PersonView(MasterView): # pylint: disable=abstract-method
|
||||
"""
|
||||
Master view for people.
|
||||
|
||||
|
@ -70,8 +71,9 @@ class PersonView(MasterView):
|
|||
"users",
|
||||
]
|
||||
|
||||
def configure_grid(self, g): # pylint: disable=empty-docstring
|
||||
def configure_grid(self, grid): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
g = grid
|
||||
super().configure_grid(g)
|
||||
|
||||
# full_name
|
||||
|
@ -83,8 +85,9 @@ class PersonView(MasterView):
|
|||
# last_name
|
||||
g.set_link("last_name")
|
||||
|
||||
def configure_form(self, f): # pylint: disable=empty-docstring
|
||||
def configure_form(self, form): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
f = form
|
||||
super().configure_form(f)
|
||||
person = f.model_instance
|
||||
|
||||
|
@ -105,12 +108,9 @@ class PersonView(MasterView):
|
|||
:returns: Fully configured :class:`~wuttaweb.grids.base.Grid`
|
||||
instance.
|
||||
"""
|
||||
model = self.app.model
|
||||
route_prefix = self.get_route_prefix()
|
||||
|
||||
grid = self.make_grid(
|
||||
key=f"{route_prefix}.view.users",
|
||||
model_class=model.User,
|
||||
return make_users_grid(
|
||||
self.request,
|
||||
route_prefix=self.get_route_prefix(),
|
||||
data=person.users,
|
||||
columns=[
|
||||
"username",
|
||||
|
@ -118,23 +118,6 @@ class PersonView(MasterView):
|
|||
],
|
||||
)
|
||||
|
||||
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)
|
||||
|
@ -213,14 +196,14 @@ class PersonView(MasterView):
|
|||
)
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
def defaults(config, **kwargs): # pylint: disable=missing-function-docstring
|
||||
base = globals()
|
||||
|
||||
PersonView = kwargs.get( # pylint: disable=invalid-name
|
||||
PersonView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name
|
||||
"PersonView", base["PersonView"]
|
||||
)
|
||||
PersonView.defaults(config)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
def includeme(config): # pylint: disable=missing-function-docstring
|
||||
defaults(config)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
################################################################################
|
||||
#
|
||||
# wuttaweb -- Web App for Wutta Framework
|
||||
# Copyright © 2024 Lance Edgar
|
||||
# Copyright © 2024-2025 Lance Edgar
|
||||
#
|
||||
# This file is part of Wutta Framework.
|
||||
#
|
||||
|
@ -63,13 +63,15 @@ def progress(request):
|
|||
return session
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
def defaults(config, **kwargs): # pylint: disable=missing-function-docstring
|
||||
base = globals()
|
||||
|
||||
progress = kwargs.get("progress", base["progress"])
|
||||
progress = kwargs.get( # pylint: disable=redefined-outer-name
|
||||
"progress", base["progress"]
|
||||
)
|
||||
config.add_route("progress", "/progress/{key}")
|
||||
config.add_view(progress, route_name="progress", renderer="json")
|
||||
|
||||
|
||||
def includeme(config):
|
||||
def includeme(config): # pylint: disable=missing-function-docstring
|
||||
defaults(config)
|
||||
|
|
|
@ -37,7 +37,7 @@ from wuttaweb.views import MasterView
|
|||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ReportView(MasterView):
|
||||
class ReportView(MasterView): # pylint: disable=abstract-method
|
||||
"""
|
||||
Master view for :term:`reports <report>`; route prefix is
|
||||
``reports``.
|
||||
|
@ -89,8 +89,9 @@ class ReportView(MasterView):
|
|||
"help_text": report.__doc__,
|
||||
}
|
||||
|
||||
def configure_grid(self, g): # pylint: disable=empty-docstring
|
||||
def configure_grid(self, grid): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
g = grid
|
||||
super().configure_grid(g)
|
||||
|
||||
# report_key
|
||||
|
@ -103,7 +104,9 @@ class ReportView(MasterView):
|
|||
# help_text
|
||||
g.set_searchable("help_text")
|
||||
|
||||
def get_instance(self): # pylint: disable=empty-docstring
|
||||
def get_instance( # pylint: disable=empty-docstring,arguments-differ,unused-argument
|
||||
self, **kwargs
|
||||
):
|
||||
""" """
|
||||
key = self.request.matchdict["report_key"]
|
||||
report = self.report_handler.get_report(key)
|
||||
|
@ -112,8 +115,9 @@ class ReportView(MasterView):
|
|||
|
||||
raise self.notfound()
|
||||
|
||||
def get_instance_title(self, report): # pylint: disable=empty-docstring
|
||||
def get_instance_title(self, instance): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
report = instance
|
||||
return report["report_title"]
|
||||
|
||||
def view(self):
|
||||
|
@ -151,8 +155,9 @@ class ReportView(MasterView):
|
|||
|
||||
return self.render_to_response("view", context)
|
||||
|
||||
def configure_form(self, f): # pylint: disable=empty-docstring
|
||||
def configure_form(self, form): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
f = form
|
||||
super().configure_form(f)
|
||||
key = self.request.matchdict["report_key"]
|
||||
report = self.report_handler.get_report(key)
|
||||
|
@ -261,14 +266,14 @@ class ReportView(MasterView):
|
|||
)
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
def defaults(config, **kwargs): # pylint: disable=missing-function-docstring
|
||||
base = globals()
|
||||
|
||||
ReportView = kwargs.get( # pylint: disable=invalid-name
|
||||
ReportView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name
|
||||
"ReportView", base["ReportView"]
|
||||
)
|
||||
ReportView.defaults(config)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
def includeme(config): # pylint: disable=missing-function-docstring
|
||||
defaults(config)
|
||||
|
|
|
@ -29,9 +29,10 @@ 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):
|
||||
class RoleView(MasterView): # pylint: disable=abstract-method
|
||||
"""
|
||||
Master view for roles.
|
||||
|
||||
|
@ -58,6 +59,8 @@ class RoleView(MasterView):
|
|||
}
|
||||
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
|
||||
""" """
|
||||
|
@ -65,8 +68,9 @@ class RoleView(MasterView):
|
|||
query = super().get_query(session=session)
|
||||
return query.order_by(model.Role.name)
|
||||
|
||||
def configure_grid(self, g): # pylint: disable=empty-docstring
|
||||
def configure_grid(self, grid): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
g = grid
|
||||
super().configure_grid(g)
|
||||
|
||||
# name
|
||||
|
@ -75,8 +79,9 @@ class RoleView(MasterView):
|
|||
# notes
|
||||
g.set_renderer("notes", self.grid_render_notes)
|
||||
|
||||
def is_editable(self, role): # pylint: disable=empty-docstring
|
||||
def is_editable(self, obj): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
role = obj
|
||||
session = self.app.get_session(role)
|
||||
auth = self.app.get_auth_handler()
|
||||
|
||||
|
@ -93,8 +98,9 @@ class RoleView(MasterView):
|
|||
|
||||
return True
|
||||
|
||||
def is_deletable(self, role): # pylint: disable=empty-docstring
|
||||
def is_deletable(self, obj): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
role = obj
|
||||
session = self.app.get_session(role)
|
||||
auth = self.app.get_auth_handler()
|
||||
|
||||
|
@ -108,8 +114,9 @@ class RoleView(MasterView):
|
|||
|
||||
return True
|
||||
|
||||
def configure_form(self, f): # pylint: disable=empty-docstring
|
||||
def configure_form(self, form): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
f = form
|
||||
super().configure_form(f)
|
||||
role = f.model_instance
|
||||
|
||||
|
@ -145,12 +152,9 @@ class RoleView(MasterView):
|
|||
:returns: Fully configured :class:`~wuttaweb.grids.base.Grid`
|
||||
instance.
|
||||
"""
|
||||
model = self.app.model
|
||||
route_prefix = self.get_route_prefix()
|
||||
|
||||
grid = self.make_grid(
|
||||
key=f"{route_prefix}.view.users",
|
||||
model_class=model.User,
|
||||
return make_users_grid(
|
||||
self.request,
|
||||
route_prefix=self.get_route_prefix(),
|
||||
data=role.users,
|
||||
columns=[
|
||||
"username",
|
||||
|
@ -159,24 +163,6 @@ class RoleView(MasterView):
|
|||
],
|
||||
)
|
||||
|
||||
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
|
||||
|
@ -317,7 +303,7 @@ class RoleView(MasterView):
|
|||
)
|
||||
|
||||
|
||||
class PermissionView(MasterView):
|
||||
class PermissionView(MasterView): # pylint: disable=abstract-method
|
||||
"""
|
||||
Master view for permissions.
|
||||
|
||||
|
@ -346,7 +332,7 @@ class PermissionView(MasterView):
|
|||
"permission",
|
||||
]
|
||||
|
||||
def get_query(self, **kwargs): # pylint: disable=empty-docstring
|
||||
def get_query(self, **kwargs): # pylint: disable=empty-docstring,arguments-differ
|
||||
""" """
|
||||
query = super().get_query(**kwargs)
|
||||
model = self.app.model
|
||||
|
@ -356,8 +342,9 @@ class PermissionView(MasterView):
|
|||
|
||||
return query
|
||||
|
||||
def configure_grid(self, g): # pylint: disable=empty-docstring
|
||||
def configure_grid(self, grid): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
g = grid
|
||||
super().configure_grid(g)
|
||||
model = self.app.model
|
||||
|
||||
|
@ -369,25 +356,28 @@ class PermissionView(MasterView):
|
|||
# permission
|
||||
g.set_link("permission")
|
||||
|
||||
def configure_form(self, f): # pylint: disable=empty-docstring
|
||||
def configure_form(self, form): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
f = form
|
||||
super().configure_form(f)
|
||||
|
||||
# role
|
||||
f.set_node("role", RoleRef(self.request))
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
def defaults(config, **kwargs): # pylint: disable=missing-function-docstring
|
||||
base = globals()
|
||||
|
||||
RoleView = kwargs.get("RoleView", base["RoleView"]) # pylint: disable=invalid-name
|
||||
RoleView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name
|
||||
"RoleView", base["RoleView"]
|
||||
)
|
||||
RoleView.defaults(config)
|
||||
|
||||
PermissionView = kwargs.get( # pylint: disable=invalid-name
|
||||
PermissionView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name
|
||||
"PermissionView", base["PermissionView"]
|
||||
)
|
||||
PermissionView.defaults(config)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
def includeme(config): # pylint: disable=missing-function-docstring
|
||||
defaults(config)
|
||||
|
|
|
@ -35,7 +35,7 @@ from wuttaweb.views import MasterView
|
|||
from wuttaweb.util import get_libver, get_liburl
|
||||
|
||||
|
||||
class AppInfoView(MasterView):
|
||||
class AppInfoView(MasterView): # pylint: disable=abstract-method
|
||||
"""
|
||||
Master view for the core app info, to show/edit config etc.
|
||||
|
||||
|
@ -93,8 +93,9 @@ class AppInfoView(MasterView):
|
|||
|
||||
return data
|
||||
|
||||
def configure_grid(self, g): # pylint: disable=empty-docstring
|
||||
def configure_grid(self, grid): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
g = grid
|
||||
super().configure_grid(g)
|
||||
|
||||
g.sort_multiple = False
|
||||
|
@ -173,7 +174,9 @@ class AppInfoView(MasterView):
|
|||
|
||||
return simple_settings
|
||||
|
||||
def configure_get_context(self, **kwargs): # pylint: disable=empty-docstring
|
||||
def configure_get_context( # pylint: disable=empty-docstring,arguments-differ
|
||||
self, **kwargs
|
||||
):
|
||||
""" """
|
||||
context = super().configure_get_context(**kwargs)
|
||||
|
||||
|
@ -220,7 +223,7 @@ class AppInfoView(MasterView):
|
|||
return context
|
||||
|
||||
|
||||
class SettingView(MasterView):
|
||||
class SettingView(MasterView): # pylint: disable=abstract-method
|
||||
"""
|
||||
Master view for the "raw" settings table.
|
||||
|
||||
|
@ -242,15 +245,17 @@ class SettingView(MasterView):
|
|||
sort_defaults = "name"
|
||||
|
||||
# TODO: master should handle this (per model key)
|
||||
def configure_grid(self, g): # pylint: disable=empty-docstring
|
||||
def configure_grid(self, grid): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
g = grid
|
||||
super().configure_grid(g)
|
||||
|
||||
# name
|
||||
g.set_link("name")
|
||||
|
||||
def configure_form(self, f): # pylint: disable=empty-docstring
|
||||
def configure_form(self, form): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
f = form
|
||||
super().configure_form(f)
|
||||
|
||||
# name
|
||||
|
@ -275,19 +280,19 @@ class SettingView(MasterView):
|
|||
node.raise_invalid("Setting name must be unique")
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
def defaults(config, **kwargs): # pylint: disable=missing-function-docstring
|
||||
base = globals()
|
||||
|
||||
AppInfoView = kwargs.get( # pylint: disable=invalid-name
|
||||
AppInfoView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name
|
||||
"AppInfoView", base["AppInfoView"]
|
||||
)
|
||||
AppInfoView.defaults(config)
|
||||
|
||||
SettingView = kwargs.get( # pylint: disable=invalid-name
|
||||
SettingView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name
|
||||
"SettingView", base["SettingView"]
|
||||
)
|
||||
SettingView.defaults(config)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
def includeme(config): # pylint: disable=missing-function-docstring
|
||||
defaults(config)
|
||||
|
|
|
@ -41,7 +41,7 @@ from wuttaweb.progress import get_progress_session
|
|||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UpgradeView(MasterView):
|
||||
class UpgradeView(MasterView): # pylint: disable=abstract-method
|
||||
"""
|
||||
Master view for upgrades.
|
||||
|
||||
|
@ -72,8 +72,9 @@ class UpgradeView(MasterView):
|
|||
|
||||
sort_defaults = ("created", "desc")
|
||||
|
||||
def configure_grid(self, g): # pylint: disable=empty-docstring
|
||||
def configure_grid(self, grid): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
g = grid
|
||||
super().configure_grid(g)
|
||||
model = self.app.model
|
||||
enum = self.app.enum
|
||||
|
@ -121,8 +122,9 @@ class UpgradeView(MasterView):
|
|||
return "has-background-warning"
|
||||
return None
|
||||
|
||||
def configure_form(self, f): # pylint: disable=empty-docstring
|
||||
def configure_form(self, form): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
f = form
|
||||
super().configure_form(f)
|
||||
enum = self.app.enum
|
||||
upgrade = f.model_instance
|
||||
|
@ -204,11 +206,12 @@ class UpgradeView(MasterView):
|
|||
"stderr_file", self.get_upgrade_filepath(upgrade, "stderr.log")
|
||||
)
|
||||
|
||||
def delete_instance(self, upgrade):
|
||||
def delete_instance(self, obj):
|
||||
"""
|
||||
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)
|
||||
|
@ -227,8 +230,9 @@ class UpgradeView(MasterView):
|
|||
|
||||
return upgrade
|
||||
|
||||
def download_path(self, upgrade, filename): # pylint: disable=empty-docstring
|
||||
def download_path(self, obj, filename): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
upgrade = obj
|
||||
if filename:
|
||||
return self.get_upgrade_filepath(upgrade, filename)
|
||||
return None
|
||||
|
@ -245,7 +249,7 @@ class UpgradeView(MasterView):
|
|||
path = os.path.join(path, filename)
|
||||
return path
|
||||
|
||||
def execute_instance(self, upgrade, user, progress=None):
|
||||
def execute_instance(self, obj, user, progress=None):
|
||||
"""
|
||||
This method runs the actual upgrade.
|
||||
|
||||
|
@ -258,6 +262,7 @@ class UpgradeView(MasterView):
|
|||
The upgrade itself is marked as "executed" with status of
|
||||
either ``SUCCESS`` or ``FAILURE``.
|
||||
"""
|
||||
upgrade = obj
|
||||
enum = self.app.enum
|
||||
|
||||
# locate file paths
|
||||
|
@ -377,14 +382,14 @@ class UpgradeView(MasterView):
|
|||
)
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
def defaults(config, **kwargs): # pylint: disable=missing-function-docstring
|
||||
base = globals()
|
||||
|
||||
UpgradeView = kwargs.get( # pylint: disable=invalid-name
|
||||
UpgradeView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name
|
||||
"UpgradeView", base["UpgradeView"]
|
||||
)
|
||||
UpgradeView.defaults(config)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
def includeme(config): # pylint: disable=missing-function-docstring
|
||||
defaults(config)
|
||||
|
|
|
@ -30,7 +30,7 @@ from wuttaweb.forms import widgets
|
|||
from wuttaweb.forms.schema import PersonRef, RoleRefs
|
||||
|
||||
|
||||
class UserView(MasterView):
|
||||
class UserView(MasterView): # pylint: disable=abstract-method
|
||||
"""
|
||||
Master view for users.
|
||||
|
||||
|
@ -82,8 +82,9 @@ class UserView(MasterView):
|
|||
|
||||
return query
|
||||
|
||||
def configure_grid(self, g): # pylint: disable=empty-docstring
|
||||
def configure_grid(self, grid): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
g = grid
|
||||
super().configure_grid(g)
|
||||
model = self.app.model
|
||||
|
||||
|
@ -106,8 +107,9 @@ class UserView(MasterView):
|
|||
return "has-background-warning"
|
||||
return None
|
||||
|
||||
def is_editable(self, user): # pylint: disable=empty-docstring
|
||||
def is_editable(self, obj): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
user = obj
|
||||
|
||||
# only root can edit certain users
|
||||
if user.prevent_edit and not self.request.is_root:
|
||||
|
@ -115,8 +117,9 @@ class UserView(MasterView):
|
|||
|
||||
return True
|
||||
|
||||
def configure_form(self, f): # pylint: disable=empty-docstring
|
||||
def configure_form(self, form): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
f = form
|
||||
super().configure_form(f)
|
||||
user = f.model_instance
|
||||
|
||||
|
@ -233,7 +236,7 @@ class UserView(MasterView):
|
|||
session = self.Session()
|
||||
auth = self.app.get_auth_handler()
|
||||
|
||||
old_roles = set([role.uuid for role in user.roles])
|
||||
old_roles = {role.uuid for role in user.roles}
|
||||
new_roles = data["roles"]
|
||||
|
||||
admin = auth.get_role_administrator(session)
|
||||
|
@ -417,12 +420,14 @@ class UserView(MasterView):
|
|||
)
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
def defaults(config, **kwargs): # pylint: disable=missing-function-docstring
|
||||
base = globals()
|
||||
|
||||
UserView = kwargs.get("UserView", base["UserView"]) # pylint: disable=invalid-name
|
||||
UserView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name
|
||||
"UserView", base["UserView"]
|
||||
)
|
||||
UserView.defaults(config)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
def includeme(config): # pylint: disable=missing-function-docstring
|
||||
defaults(config)
|
||||
|
|
|
@ -363,7 +363,7 @@ class TestForm(TestCase):
|
|||
|
||||
# basic
|
||||
form = self.make_form(schema=schema)
|
||||
self.assertFalse(hasattr(form, "deform_form"))
|
||||
self.assertIsNone(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.assertFalse(hasattr(form, "validated"))
|
||||
self.assertIsNone(form.validated)
|
||||
|
||||
# will not validate unless request is POST
|
||||
self.request.POST = {"foo": "blarg", "bar": "baz"}
|
||||
|
|
|
@ -497,7 +497,7 @@ class TestGrid(WebTestCase):
|
|||
|
||||
# settings are loaded, applied, saved
|
||||
self.assertEqual(grid.sort_defaults, [])
|
||||
self.assertFalse(hasattr(grid, "active_sorters"))
|
||||
self.assertIsNone(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.assertFalse(hasattr(grid, "active_sorters"))
|
||||
self.assertIsNone(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.assertFalse(hasattr(grid, "active_sorters"))
|
||||
self.assertIsNone(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.assertFalse(hasattr(grid, "active_sorters"))
|
||||
self.assertIsNone(grid.active_sorters)
|
||||
grid.load_settings()
|
||||
self.assertEqual(grid.active_sorters, [{"key": "name", "dir": "desc"}])
|
||||
|
||||
|
|
|
@ -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.assertFalse(hasattr(filtr, "verb"))
|
||||
self.assertIsNone(filtr.verb)
|
||||
filtr = self.make_filter(model.Setting.name, verb="foo")
|
||||
self.assertEqual(filtr.verb, "foo")
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -16,6 +16,7 @@ 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
|
||||
|
||||
|
||||
|
@ -665,6 +666,52 @@ class TestMakeJsonSafe(TestCase):
|
|||
)
|
||||
|
||||
|
||||
class TestRenderVueFinalize(TestCase):
|
||||
|
||||
def basic(self):
|
||||
html = mod.render_vue_finalize("wutta-grid", "WuttaGrid")
|
||||
self.assertIn("<script>", html)
|
||||
self.assertIn("Vue.component('wutta-grid', WuttaGrid)", html)
|
||||
|
||||
|
||||
class TestMakeUsersGrid(WebTestCase):
|
||||
|
||||
def test_make_users_grid(self):
|
||||
self.pyramid_config.add_route("users.view", "/users/{uuid}/view")
|
||||
self.pyramid_config.add_route("users.edit", "/users/{uuid}/edit")
|
||||
model = self.app.model
|
||||
person = model.Person(full_name="John Doe")
|
||||
self.session.add(person)
|
||||
user = model.User(username="john", person=person)
|
||||
self.session.add(user)
|
||||
self.session.commit()
|
||||
|
||||
# basic (no actions because not prvileged)
|
||||
grid = mod.make_users_grid(self.request, key="blah.users", data=person.users)
|
||||
self.assertIsInstance(grid, Grid)
|
||||
self.assertFalse(grid.linked_columns)
|
||||
self.assertFalse(grid.actions)
|
||||
|
||||
# key may be derived from route_prefix
|
||||
grid = mod.make_users_grid(self.request, route_prefix="foo")
|
||||
self.assertIsInstance(grid, Grid)
|
||||
self.assertEqual(grid.key, "foo.view.users")
|
||||
|
||||
# view + edit actions (because root)
|
||||
with patch.object(self.request, "is_root", new=True):
|
||||
grid = mod.make_users_grid(
|
||||
self.request, key="blah.users", data=person.users
|
||||
)
|
||||
self.assertIsInstance(grid, Grid)
|
||||
self.assertIn("username", grid.linked_columns)
|
||||
self.assertEqual(len(grid.actions), 2)
|
||||
self.assertEqual(grid.actions[0].key, "view")
|
||||
self.assertEqual(grid.actions[1].key, "edit")
|
||||
|
||||
# render grid to ensure coverage for link urls
|
||||
grid.render_vue_template()
|
||||
|
||||
|
||||
class TestGetAvailableThemes(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
@ -624,7 +624,7 @@ class TestMasterView(WebTestCase):
|
|||
view = self.make_view()
|
||||
|
||||
# empty by default
|
||||
self.assertFalse(hasattr(mod.MasterView, "model_class"))
|
||||
self.assertIsNone(mod.MasterView.model_class)
|
||||
data = view.get_grid_data(session=self.session)
|
||||
self.assertEqual(data, [])
|
||||
|
||||
|
@ -1371,6 +1371,25 @@ class TestMasterView(WebTestCase):
|
|||
self.session.commit()
|
||||
self.assertEqual(self.session.query(model.Setting).count(), 7)
|
||||
|
||||
def test_do_thread_body(self):
|
||||
view = self.make_view()
|
||||
|
||||
# nb. so far this is just proving coverage, in case caller
|
||||
# does not specify an error handler
|
||||
|
||||
def func():
|
||||
raise RuntimeError
|
||||
|
||||
# with error handler
|
||||
onerror = MagicMock()
|
||||
view.do_thread_body(func, (), {}, onerror)
|
||||
onerror.assert_called_once_with()
|
||||
|
||||
# without error handler
|
||||
onerror.reset_mock()
|
||||
view.do_thread_body(func, (), {})
|
||||
onerror.assert_not_called()
|
||||
|
||||
def test_delete_bulk_thread(self):
|
||||
self.pyramid_config.add_route("settings", "/settings/")
|
||||
model = self.app.model
|
||||
|
@ -1656,6 +1675,11 @@ class TestMasterView(WebTestCase):
|
|||
count = self.session.query(model.Setting).count()
|
||||
self.assertEqual(count, 0)
|
||||
|
||||
def test_configure_get_simple_settings(self):
|
||||
view = self.make_view()
|
||||
settings = view.configure_get_simple_settings()
|
||||
self.assertEqual(settings, [])
|
||||
|
||||
def test_configure_gather_settings(self):
|
||||
view = self.make_view()
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue