fix: format all code with black
and from now on should not deviate from that...
This commit is contained in:
parent
8a09fb1a3c
commit
4d0693862d
68 changed files with 6693 additions and 5659 deletions
46
docs/conf.py
46
docs/conf.py
|
|
@ -8,41 +8,41 @@
|
|||
|
||||
from importlib.metadata import version as get_version
|
||||
|
||||
project = 'WuttaWeb'
|
||||
copyright = '2024, Lance Edgar'
|
||||
author = 'Lance Edgar'
|
||||
release = get_version('WuttaWeb')
|
||||
project = "WuttaWeb"
|
||||
copyright = "2024, Lance Edgar"
|
||||
author = "Lance Edgar"
|
||||
release = get_version("WuttaWeb")
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.viewcode',
|
||||
'sphinx.ext.todo',
|
||||
'sphinxcontrib.programoutput',
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.intersphinx",
|
||||
"sphinx.ext.viewcode",
|
||||
"sphinx.ext.todo",
|
||||
"sphinxcontrib.programoutput",
|
||||
]
|
||||
|
||||
templates_path = ['_templates']
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
templates_path = ["_templates"]
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||
|
||||
intersphinx_mapping = {
|
||||
'colander': ('https://docs.pylonsproject.org/projects/colander/en/latest/', None),
|
||||
'deform': ('https://docs.pylonsproject.org/projects/deform/en/latest/', None),
|
||||
'fanstatic': ('https://www.fanstatic.org/en/latest/', None),
|
||||
'pyramid': ('https://docs.pylonsproject.org/projects/pyramid/en/latest/', None),
|
||||
'python': ('https://docs.python.org/3/', None),
|
||||
'rattail-manual': ('https://docs.wuttaproject.org/rattail-manual/', None),
|
||||
'sqlalchemy': ('http://docs.sqlalchemy.org/en/latest/', None),
|
||||
'webhelpers2': ('https://webhelpers2.readthedocs.io/en/latest/', None),
|
||||
'wuttjamaican': ('https://docs.wuttaproject.org/wuttjamaican/', None),
|
||||
'wutta-continuum': ('https://docs.wuttaproject.org/wutta-continuum/', None),
|
||||
"colander": ("https://docs.pylonsproject.org/projects/colander/en/latest/", None),
|
||||
"deform": ("https://docs.pylonsproject.org/projects/deform/en/latest/", None),
|
||||
"fanstatic": ("https://www.fanstatic.org/en/latest/", None),
|
||||
"pyramid": ("https://docs.pylonsproject.org/projects/pyramid/en/latest/", None),
|
||||
"python": ("https://docs.python.org/3/", None),
|
||||
"rattail-manual": ("https://docs.wuttaproject.org/rattail-manual/", None),
|
||||
"sqlalchemy": ("http://docs.sqlalchemy.org/en/latest/", None),
|
||||
"webhelpers2": ("https://webhelpers2.readthedocs.io/en/latest/", None),
|
||||
"wuttjamaican": ("https://docs.wuttaproject.org/wuttjamaican/", None),
|
||||
"wutta-continuum": ("https://docs.wuttaproject.org/wutta-continuum/", None),
|
||||
}
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||
|
||||
html_theme = 'furo'
|
||||
html_static_path = ['_static']
|
||||
html_theme = "furo"
|
||||
html_static_path = ["_static"]
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@
|
|||
from importlib.metadata import version
|
||||
|
||||
|
||||
__version__ = version('wuttaweb')
|
||||
__version__ = version("wuttaweb")
|
||||
|
|
|
|||
|
|
@ -48,8 +48,9 @@ class WebAppProvider(AppProvider):
|
|||
registers some :term:`email templates <email template>` for the
|
||||
app, etc.
|
||||
"""
|
||||
email_modules = ['wuttaweb.emails']
|
||||
email_templates = ['wuttaweb:email-templates']
|
||||
|
||||
email_modules = ["wuttaweb.emails"]
|
||||
email_templates = ["wuttaweb:email-templates"]
|
||||
|
||||
def get_web_handler(self, **kwargs):
|
||||
"""
|
||||
|
|
@ -64,9 +65,11 @@ class WebAppProvider(AppProvider):
|
|||
|
||||
:returns: Instance of :class:`~wuttaweb.handler.WebHandler`.
|
||||
"""
|
||||
if 'web_handler' not in self.__dict__:
|
||||
spec = self.config.get(f'{self.appname}.web.handler_spec',
|
||||
default='wuttaweb.handler:WebHandler')
|
||||
if "web_handler" not in self.__dict__:
|
||||
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
|
||||
|
||||
|
|
@ -96,23 +99,25 @@ def make_wutta_config(settings, config_maker=None, **kwargs):
|
|||
|
||||
If this config file path cannot be discovered, an error is raised.
|
||||
"""
|
||||
wutta_config = settings.get('wutta_config')
|
||||
wutta_config = settings.get("wutta_config")
|
||||
if not wutta_config:
|
||||
|
||||
# validate config file path
|
||||
path = settings.get('wutta.config')
|
||||
path = settings.get("wutta.config")
|
||||
if not path or not os.path.exists(path):
|
||||
raise ValueError("Please set 'wutta.config' in [app:main] "
|
||||
"section of config to the path of your "
|
||||
"config file. Lame, but necessary.")
|
||||
raise ValueError(
|
||||
"Please set 'wutta.config' in [app:main] "
|
||||
"section of config to the path of your "
|
||||
"config file. Lame, but necessary."
|
||||
)
|
||||
|
||||
# make config, add to settings
|
||||
config_maker = config_maker or make_config
|
||||
wutta_config = config_maker(path, **kwargs)
|
||||
settings['wutta_config'] = wutta_config
|
||||
settings["wutta_config"] = wutta_config
|
||||
|
||||
# configure database sessions
|
||||
if hasattr(wutta_config, 'appdb_engine'):
|
||||
if hasattr(wutta_config, "appdb_engine"):
|
||||
wuttaweb.db.Session.configure(bind=wutta_config.appdb_engine)
|
||||
|
||||
return wutta_config
|
||||
|
|
@ -128,10 +133,11 @@ def make_pyramid_config(settings):
|
|||
:returns: Instance of
|
||||
:class:`pyramid:pyramid.config.Configurator`.
|
||||
"""
|
||||
settings.setdefault('fanstatic.versioning', 'true')
|
||||
settings.setdefault('mako.directories', ['wuttaweb:templates'])
|
||||
settings.setdefault('pyramid_deform.template_search_path',
|
||||
'wuttaweb:templates/deform')
|
||||
settings.setdefault("fanstatic.versioning", "true")
|
||||
settings.setdefault("mako.directories", ["wuttaweb:templates"])
|
||||
settings.setdefault(
|
||||
"pyramid_deform.template_search_path", "wuttaweb:templates/deform"
|
||||
)
|
||||
|
||||
# update settings per current theme
|
||||
establish_theme(settings)
|
||||
|
|
@ -142,21 +148,21 @@ def make_pyramid_config(settings):
|
|||
pyramid_config.set_security_policy(WuttaSecurityPolicy())
|
||||
|
||||
# require CSRF token for POST
|
||||
pyramid_config.set_default_csrf_options(require_csrf=True,
|
||||
token='_csrf',
|
||||
header='X-CSRF-TOKEN')
|
||||
pyramid_config.set_default_csrf_options(
|
||||
require_csrf=True, token="_csrf", header="X-CSRF-TOKEN"
|
||||
)
|
||||
|
||||
pyramid_config.include('pyramid_beaker')
|
||||
pyramid_config.include('pyramid_deform')
|
||||
pyramid_config.include('pyramid_fanstatic')
|
||||
pyramid_config.include('pyramid_mako')
|
||||
pyramid_config.include('pyramid_tm')
|
||||
pyramid_config.include("pyramid_beaker")
|
||||
pyramid_config.include("pyramid_deform")
|
||||
pyramid_config.include("pyramid_fanstatic")
|
||||
pyramid_config.include("pyramid_mako")
|
||||
pyramid_config.include("pyramid_tm")
|
||||
|
||||
# add some permissions magic
|
||||
pyramid_config.add_directive('add_wutta_permission_group',
|
||||
'wuttaweb.auth.add_permission_group')
|
||||
pyramid_config.add_directive('add_wutta_permission',
|
||||
'wuttaweb.auth.add_permission')
|
||||
pyramid_config.add_directive(
|
||||
"add_wutta_permission_group", "wuttaweb.auth.add_permission_group"
|
||||
)
|
||||
pyramid_config.add_directive("add_wutta_permission", "wuttaweb.auth.add_permission")
|
||||
|
||||
return pyramid_config
|
||||
|
||||
|
|
@ -179,9 +185,9 @@ def main(global_config, **settings):
|
|||
wutta_config = make_wutta_config(settings)
|
||||
pyramid_config = make_pyramid_config(settings)
|
||||
|
||||
pyramid_config.include('wuttaweb.static')
|
||||
pyramid_config.include('wuttaweb.subscribers')
|
||||
pyramid_config.include('wuttaweb.views')
|
||||
pyramid_config.include("wuttaweb.static")
|
||||
pyramid_config.include("wuttaweb.subscribers")
|
||||
pyramid_config.include("wuttaweb.views")
|
||||
|
||||
return pyramid_config.make_wsgi_app()
|
||||
|
||||
|
|
@ -226,10 +232,10 @@ def make_wsgi_app(main_app=None, config=None):
|
|||
app = config.get_app()
|
||||
|
||||
# extract pyramid settings
|
||||
settings = config.get_dict('app:main')
|
||||
settings = config.get_dict("app:main")
|
||||
|
||||
# keep same config object
|
||||
settings['wutta_config'] = config
|
||||
settings["wutta_config"] = config
|
||||
|
||||
# determine the app factory
|
||||
if isinstance(main_app, str):
|
||||
|
|
@ -270,15 +276,15 @@ def establish_theme(settings):
|
|||
will update ``settings['mako.directories']`` such that the theme's
|
||||
template path is listed first.
|
||||
"""
|
||||
config = settings['wutta_config']
|
||||
config = settings["wutta_config"]
|
||||
|
||||
theme = get_effective_theme(config)
|
||||
settings['wuttaweb.theme'] = theme
|
||||
settings["wuttaweb.theme"] = theme
|
||||
|
||||
directories = settings['mako.directories']
|
||||
directories = settings["mako.directories"]
|
||||
if isinstance(directories, str):
|
||||
directories = config.parse_list(directories)
|
||||
|
||||
path = get_theme_template_path(config)
|
||||
directories.insert(0, path)
|
||||
settings['mako.directories'] = directories
|
||||
settings["mako.directories"] = directories
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ class WuttaSecurityPolicy:
|
|||
self.db_session = db_session or Session()
|
||||
|
||||
def load_identity(self, request):
|
||||
config = request.registry.settings['wutta_config']
|
||||
config = request.registry.settings["wutta_config"]
|
||||
app = config.get_app()
|
||||
model = app.model
|
||||
|
||||
|
|
@ -141,10 +141,10 @@ class WuttaSecurityPolicy:
|
|||
def permits(self, request, context, permission):
|
||||
|
||||
# nb. root user can do anything
|
||||
if getattr(request, 'is_root', False):
|
||||
if getattr(request, "is_root", False):
|
||||
return True
|
||||
|
||||
config = request.registry.settings['wutta_config']
|
||||
config = request.registry.settings["wutta_config"]
|
||||
app = config.get_app()
|
||||
auth = app.get_auth_handler()
|
||||
user = self.identity(request)
|
||||
|
|
@ -183,14 +183,16 @@ def add_permission_group(pyramid_config, groupkey, label=None, overwrite=True):
|
|||
|
||||
See also :func:`add_permission()`.
|
||||
"""
|
||||
config = pyramid_config.get_settings()['wutta_config']
|
||||
config = pyramid_config.get_settings()["wutta_config"]
|
||||
app = config.get_app()
|
||||
|
||||
def action():
|
||||
perms = pyramid_config.get_settings().get('wutta_permissions', {})
|
||||
perms = pyramid_config.get_settings().get("wutta_permissions", {})
|
||||
if overwrite or groupkey not in perms:
|
||||
group = perms.setdefault(groupkey, {'key': groupkey})
|
||||
group['label'] = label or app.make_title(groupkey)
|
||||
pyramid_config.add_settings({'wutta_permissions': perms})
|
||||
group = perms.setdefault(groupkey, {"key": groupkey})
|
||||
group["label"] = label or app.make_title(groupkey)
|
||||
pyramid_config.add_settings({"wutta_permissions": perms})
|
||||
|
||||
pyramid_config.action(None, action)
|
||||
|
||||
|
||||
|
|
@ -229,13 +231,15 @@ def add_permission(pyramid_config, groupkey, key, label=None):
|
|||
|
||||
See also :func:`add_permission_group()`.
|
||||
"""
|
||||
|
||||
def action():
|
||||
config = pyramid_config.get_settings()['wutta_config']
|
||||
config = pyramid_config.get_settings()["wutta_config"]
|
||||
app = config.get_app()
|
||||
perms = pyramid_config.get_settings().get('wutta_permissions', {})
|
||||
group = perms.setdefault(groupkey, {'key': groupkey})
|
||||
group.setdefault('label', app.make_title(groupkey))
|
||||
perm = group.setdefault('perms', {}).setdefault(key, {'key': key})
|
||||
perm['label'] = label or app.make_title(key)
|
||||
pyramid_config.add_settings({'wutta_permissions': perms})
|
||||
perms = pyramid_config.get_settings().get("wutta_permissions", {})
|
||||
group = perms.setdefault(groupkey, {"key": groupkey})
|
||||
group.setdefault("label", app.make_title(groupkey))
|
||||
perm = group.setdefault("perms", {}).setdefault(key, {"key": key})
|
||||
perm["label"] = label or app.make_title(key)
|
||||
pyramid_config.add_settings({"wutta_permissions": perms})
|
||||
|
||||
pyramid_config.action(None, action)
|
||||
|
|
|
|||
|
|
@ -36,11 +36,11 @@ from wuttjamaican.cli import wutta_typer
|
|||
|
||||
@wutta_typer.command()
|
||||
def webapp(
|
||||
ctx: typer.Context,
|
||||
auto_reload: Annotated[
|
||||
bool,
|
||||
typer.Option('--reload', '-r',
|
||||
help="Auto-reload web app when files change.")] = False,
|
||||
ctx: typer.Context,
|
||||
auto_reload: Annotated[
|
||||
bool,
|
||||
typer.Option("--reload", "-r", help="Auto-reload web app when files change."),
|
||||
] = False,
|
||||
):
|
||||
"""
|
||||
Run the configured web app
|
||||
|
|
@ -52,34 +52,38 @@ def webapp(
|
|||
sys.stderr.write("no config files found!\n")
|
||||
sys.exit(1)
|
||||
|
||||
runner = config.get(f'{config.appname}.web.app.runner', default='pserve')
|
||||
if runner == 'pserve':
|
||||
runner = config.get(f"{config.appname}.web.app.runner", default="pserve")
|
||||
if runner == "pserve":
|
||||
|
||||
# run pserve
|
||||
argv = ['pserve', f'file+ini:{config.files_read[0]}']
|
||||
if ctx.params['auto_reload']:
|
||||
argv.append('--reload')
|
||||
argv = ["pserve", f"file+ini:{config.files_read[0]}"]
|
||||
if ctx.params["auto_reload"]:
|
||||
argv.append("--reload")
|
||||
pserve.main(argv=argv)
|
||||
|
||||
elif runner == 'uvicorn':
|
||||
elif runner == "uvicorn":
|
||||
|
||||
import uvicorn
|
||||
|
||||
# need service details from config
|
||||
spec = config.require(f'{config.appname}.web.app.spec')
|
||||
spec = config.require(f"{config.appname}.web.app.spec")
|
||||
kw = {
|
||||
'host': config.get(f'{config.appname}.web.app.host', default='127.0.0.1'),
|
||||
'port': config.get_int(f'{config.appname}.web.app.port', default=8000),
|
||||
'reload': ctx.params['auto_reload'],
|
||||
'reload_dirs': config.get_list(f'{config.appname}.web.app.reload_dirs'),
|
||||
'factory': config.get_bool(f'{config.appname}.web.app.factory', default=False),
|
||||
'interface': config.get(f'{config.appname}.web.app.interface', default='auto'),
|
||||
'root_path': config.get(f'{config.appname}.web.app.root_path', default=''),
|
||||
"host": config.get(f"{config.appname}.web.app.host", default="127.0.0.1"),
|
||||
"port": config.get_int(f"{config.appname}.web.app.port", default=8000),
|
||||
"reload": ctx.params["auto_reload"],
|
||||
"reload_dirs": config.get_list(f"{config.appname}.web.app.reload_dirs"),
|
||||
"factory": config.get_bool(
|
||||
f"{config.appname}.web.app.factory", default=False
|
||||
),
|
||||
"interface": config.get(
|
||||
f"{config.appname}.web.app.interface", default="auto"
|
||||
),
|
||||
"root_path": config.get(f"{config.appname}.web.app.root_path", default=""),
|
||||
}
|
||||
|
||||
# also must inject our config files to env, since there is no
|
||||
# other way to specify when running via uvicorn
|
||||
os.environ['WUTTA_CONFIG_FILES'] = os.pathsep.join(config.files_read)
|
||||
os.environ["WUTTA_CONFIG_FILES"] = os.pathsep.join(config.files_read)
|
||||
|
||||
# run uvicorn
|
||||
uvicorn.run(spec, **kw)
|
||||
|
|
|
|||
|
|
@ -35,9 +35,12 @@ class WuttaWebConfigExtension(WuttaConfigExtension):
|
|||
only relevant if Wutta-Continuum is installed and enabled. For
|
||||
more info see :doc:`wutta-continuum:index`.
|
||||
"""
|
||||
key = 'wuttaweb'
|
||||
|
||||
key = "wuttaweb"
|
||||
|
||||
def configure(self, config):
|
||||
""" """
|
||||
config.setdefault('wutta_continuum.wutta_plugin_spec',
|
||||
'wuttaweb.db.continuum:WuttaWebContinuumPlugin')
|
||||
config.setdefault(
|
||||
"wutta_continuum.wutta_plugin_spec",
|
||||
"wuttaweb.db.continuum:WuttaWebContinuumPlugin",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ from pyramid.threadlocal import get_current_request
|
|||
|
||||
try:
|
||||
from wutta_continuum.conf import WuttaContinuumPlugin
|
||||
except ImportError: # pragma: no cover
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
else:
|
||||
|
||||
|
|
|
|||
|
|
@ -31,18 +31,19 @@ class feedback(EmailSetting):
|
|||
"""
|
||||
Sent when user submits feedback via the web app.
|
||||
"""
|
||||
|
||||
default_subject = "User Feedback"
|
||||
|
||||
def sample_data(self):
|
||||
""" """
|
||||
model = self.app.model
|
||||
person = model.Person(full_name="Barney Rubble")
|
||||
user = model.User(username='barney', person=person)
|
||||
user = model.User(username="barney", person=person)
|
||||
return {
|
||||
'user': user,
|
||||
'user_name': str(person),
|
||||
'user_url': '#',
|
||||
'referrer': 'http://example.com/',
|
||||
'client_ip': '127.0.0.1',
|
||||
'message': "This app is cool but needs a new feature.\n\nAllow me to describe...",
|
||||
"user": user,
|
||||
"user_name": str(person),
|
||||
"user_url": "#",
|
||||
"referrer": "http://example.com/",
|
||||
"client_ip": "127.0.0.1",
|
||||
"message": "This app is cool but needs a new feature.\n\nAllow me to describe...",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -268,35 +268,35 @@ class Form:
|
|||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
request,
|
||||
fields=None,
|
||||
schema=None,
|
||||
model_class=None,
|
||||
model_instance=None,
|
||||
nodes={},
|
||||
widgets={},
|
||||
validators={},
|
||||
defaults={},
|
||||
readonly=False,
|
||||
readonly_fields=[],
|
||||
required_fields={},
|
||||
labels={},
|
||||
action_method='post',
|
||||
action_url=None,
|
||||
reset_url=None,
|
||||
cancel_url=None,
|
||||
cancel_url_fallback=None,
|
||||
vue_tagname='wutta-form',
|
||||
align_buttons_right=False,
|
||||
auto_disable_submit=True,
|
||||
button_label_submit="Save",
|
||||
button_icon_submit='save',
|
||||
button_type_submit='is-primary',
|
||||
show_button_reset=False,
|
||||
show_button_cancel=True,
|
||||
button_label_cancel="Cancel",
|
||||
auto_disable_cancel=True,
|
||||
self,
|
||||
request,
|
||||
fields=None,
|
||||
schema=None,
|
||||
model_class=None,
|
||||
model_instance=None,
|
||||
nodes={},
|
||||
widgets={},
|
||||
validators={},
|
||||
defaults={},
|
||||
readonly=False,
|
||||
readonly_fields=[],
|
||||
required_fields={},
|
||||
labels={},
|
||||
action_method="post",
|
||||
action_url=None,
|
||||
reset_url=None,
|
||||
cancel_url=None,
|
||||
cancel_url_fallback=None,
|
||||
vue_tagname="wutta-form",
|
||||
align_buttons_right=False,
|
||||
auto_disable_submit=True,
|
||||
button_label_submit="Save",
|
||||
button_icon_submit="save",
|
||||
button_type_submit="is-primary",
|
||||
show_button_reset=False,
|
||||
show_button_cancel=True,
|
||||
button_label_cancel="Cancel",
|
||||
auto_disable_cancel=True,
|
||||
):
|
||||
self.request = request
|
||||
self.schema = schema
|
||||
|
|
@ -367,8 +367,8 @@ class Form:
|
|||
|
||||
This is a generated value based on :attr:`vue_tagname`.
|
||||
"""
|
||||
words = self.vue_tagname.split('-')
|
||||
return ''.join([word.capitalize() for word in words])
|
||||
words = self.vue_tagname.split("-")
|
||||
return "".join([word.capitalize() for word in words])
|
||||
|
||||
def get_cancel_url(self):
|
||||
"""
|
||||
|
|
@ -392,8 +392,8 @@ class Form:
|
|||
|
||||
# nb. use fake default to avoid normal default logic;
|
||||
# that way if we get something it's a real referrer
|
||||
url = self.request.get_referrer(default='NOPE')
|
||||
if url and url != 'NOPE':
|
||||
url = self.request.get_referrer(default="NOPE")
|
||||
if url and url != "NOPE":
|
||||
return url
|
||||
|
||||
# use fallback URL if set
|
||||
|
|
@ -471,8 +471,8 @@ class Form:
|
|||
# assume nodeinfo is a complete node
|
||||
node = nodeinfo
|
||||
|
||||
else: # assume nodeinfo is a schema type
|
||||
kwargs.setdefault('name', key)
|
||||
else: # assume nodeinfo is a schema type
|
||||
kwargs.setdefault("name", key)
|
||||
node = ObjectNode(nodeinfo, **kwargs)
|
||||
|
||||
self.nodes[key] = node
|
||||
|
|
@ -534,7 +534,7 @@ class Form:
|
|||
"""
|
||||
from wuttaweb.forms import widgets
|
||||
|
||||
if widget_type == 'notes':
|
||||
if widget_type == "notes":
|
||||
return widgets.NotesWidget(**kwargs)
|
||||
|
||||
def set_default_widgets(self):
|
||||
|
|
@ -565,7 +565,7 @@ class Form:
|
|||
|
||||
attr = getattr(self.model_class, key, None)
|
||||
if attr:
|
||||
prop = getattr(attr, 'prop', None)
|
||||
prop = getattr(attr, "prop", None)
|
||||
if prop and isinstance(prop, orm.ColumnProperty):
|
||||
column = prop.columns[0]
|
||||
if isinstance(column.type, sa.Date):
|
||||
|
|
@ -596,8 +596,10 @@ class Form:
|
|||
raise ValueError("grid must have a key!")
|
||||
|
||||
if grid.key in self.grid_vue_context:
|
||||
log.warning("grid data with key '%s' already registered, "
|
||||
"but will be replaced", grid.key)
|
||||
log.warning(
|
||||
"grid data with key '%s' already registered, " "but will be replaced",
|
||||
grid.key,
|
||||
)
|
||||
|
||||
self.grid_vue_context[grid.key] = grid.get_vue_context()
|
||||
|
||||
|
|
@ -748,7 +750,7 @@ class Form:
|
|||
|
||||
Otherwise ``None`` is returned.
|
||||
"""
|
||||
if hasattr(self, 'fields') and self.fields:
|
||||
if hasattr(self, "fields") and self.fields:
|
||||
return self.fields
|
||||
|
||||
if self.schema:
|
||||
|
|
@ -769,8 +771,9 @@ class Form:
|
|||
fields. If not set, the form's :attr:`model_class` is
|
||||
assumed.
|
||||
"""
|
||||
return get_model_fields(self.config,
|
||||
model_class=model_class or self.model_class)
|
||||
return get_model_fields(
|
||||
self.config, model_class=model_class or self.model_class
|
||||
)
|
||||
|
||||
def get_schema(self):
|
||||
"""
|
||||
|
|
@ -802,8 +805,7 @@ class Form:
|
|||
includes.append(key)
|
||||
|
||||
# make initial schema with ColanderAlchemy magic
|
||||
schema = SQLAlchemySchemaNode(self.model_class,
|
||||
includes=includes)
|
||||
schema = SQLAlchemySchemaNode(self.model_class, includes=includes)
|
||||
|
||||
# fill in the blanks if anything got missed
|
||||
for key in fields:
|
||||
|
|
@ -824,9 +826,7 @@ class Form:
|
|||
if not node:
|
||||
|
||||
# otherwise make simple string node
|
||||
node = colander.SchemaNode(
|
||||
colander.String(),
|
||||
name=key)
|
||||
node = colander.SchemaNode(colander.String(), name=key)
|
||||
|
||||
schema.add(node)
|
||||
|
||||
|
|
@ -844,7 +844,7 @@ class Form:
|
|||
if key is None:
|
||||
# nb. this one is form-wide
|
||||
schema.validator = validator
|
||||
elif key in schema: # field-level
|
||||
elif key in schema: # field-level
|
||||
schema[key].validator = validator
|
||||
|
||||
# apply default value overrides
|
||||
|
|
@ -867,7 +867,7 @@ class Form:
|
|||
Return the :class:`deform:deform.Form` instance for the form,
|
||||
generating it automatically if necessary.
|
||||
"""
|
||||
if not hasattr(self, 'deform_form'):
|
||||
if not hasattr(self, "deform_form"):
|
||||
model = self.app.model
|
||||
schema = self.get_schema()
|
||||
kwargs = {}
|
||||
|
|
@ -897,9 +897,9 @@ class Form:
|
|||
# this is what we are trying currently...
|
||||
|
||||
if isinstance(schema, SQLAlchemySchemaNode):
|
||||
kwargs['appstruct'] = schema.dictify(self.model_instance)
|
||||
kwargs["appstruct"] = schema.dictify(self.model_instance)
|
||||
else:
|
||||
kwargs['appstruct'] = self.model_instance
|
||||
kwargs["appstruct"] = self.model_instance
|
||||
|
||||
# create the Deform instance
|
||||
# nb. must give a reference back to wutta form; this is
|
||||
|
|
@ -926,10 +926,7 @@ class Form:
|
|||
"""
|
||||
return HTML.tag(self.vue_tagname, **kwargs)
|
||||
|
||||
def render_vue_template(
|
||||
self,
|
||||
template='/forms/vue_template.mako',
|
||||
**context):
|
||||
def render_vue_template(self, template="/forms/vue_template.mako", **context):
|
||||
"""
|
||||
Render the Vue template block for the form.
|
||||
|
||||
|
|
@ -962,29 +959,29 @@ class Form:
|
|||
:param template: Path to Mako template which is used to render
|
||||
the output.
|
||||
"""
|
||||
context['form'] = self
|
||||
context['dform'] = self.get_deform()
|
||||
context.setdefault('request', self.request)
|
||||
context['model_data'] = self.get_vue_model_data()
|
||||
context["form"] = self
|
||||
context["dform"] = self.get_deform()
|
||||
context.setdefault("request", self.request)
|
||||
context["model_data"] = self.get_vue_model_data()
|
||||
|
||||
# set form method, enctype
|
||||
context.setdefault('form_attrs', {})
|
||||
context['form_attrs'].setdefault('method', self.action_method)
|
||||
if self.action_method == 'post':
|
||||
context['form_attrs'].setdefault('enctype', 'multipart/form-data')
|
||||
context.setdefault("form_attrs", {})
|
||||
context["form_attrs"].setdefault("method", self.action_method)
|
||||
if self.action_method == "post":
|
||||
context["form_attrs"].setdefault("enctype", "multipart/form-data")
|
||||
|
||||
# auto disable button on submit
|
||||
if self.auto_disable_submit:
|
||||
context['form_attrs']['@submit'] = 'formSubmitting = true'
|
||||
context["form_attrs"]["@submit"] = "formSubmitting = true"
|
||||
|
||||
output = render(template, context)
|
||||
return HTML.literal(output)
|
||||
|
||||
def render_vue_field(
|
||||
self,
|
||||
fieldname,
|
||||
readonly=None,
|
||||
**kwargs,
|
||||
self,
|
||||
fieldname,
|
||||
readonly=None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Render the given field completely, i.e. ``<b-field>`` wrapper
|
||||
|
|
@ -1026,7 +1023,7 @@ class Form:
|
|||
field = dform[fieldname]
|
||||
kw = {}
|
||||
if readonly:
|
||||
kw['readonly'] = True
|
||||
kw["readonly"] = True
|
||||
html = field.serialize(**kw)
|
||||
|
||||
else:
|
||||
|
|
@ -1034,20 +1031,20 @@ class Form:
|
|||
# TODO: need to abstract this somehow
|
||||
if self.model_instance:
|
||||
value = self.model_instance[fieldname]
|
||||
html = str(value) if value is not None else ''
|
||||
html = str(value) if value is not None else ""
|
||||
else:
|
||||
html = ''
|
||||
html = ""
|
||||
|
||||
# mark all that as safe
|
||||
html = HTML.literal(html or ' ')
|
||||
html = HTML.literal(html or " ")
|
||||
|
||||
# render field label
|
||||
label = self.get_label(fieldname)
|
||||
|
||||
# b-field attrs
|
||||
attrs = {
|
||||
':horizontal': 'true',
|
||||
'label': label,
|
||||
":horizontal": "true",
|
||||
"label": label,
|
||||
}
|
||||
|
||||
# next we will build array of messages to display..some
|
||||
|
|
@ -1059,22 +1056,21 @@ class Form:
|
|||
# show errors if present
|
||||
errors = self.get_field_errors(fieldname)
|
||||
if errors:
|
||||
field_type = 'is-danger'
|
||||
field_type = "is-danger"
|
||||
messages.extend(errors)
|
||||
|
||||
# ..okay now we can declare the field messages and type
|
||||
if field_type:
|
||||
attrs['type'] = field_type
|
||||
attrs["type"] = field_type
|
||||
if messages:
|
||||
cls = 'is-size-7'
|
||||
if field_type == 'is-danger':
|
||||
cls += ' has-text-danger'
|
||||
messages = [HTML.tag('p', c=[msg], class_=cls)
|
||||
for msg in messages]
|
||||
slot = HTML.tag('slot', name='messages', c=messages)
|
||||
html = HTML.tag('div', c=[html, slot])
|
||||
cls = "is-size-7"
|
||||
if field_type == "is-danger":
|
||||
cls += " has-text-danger"
|
||||
messages = [HTML.tag("p", c=[msg], class_=cls) for msg in messages]
|
||||
slot = HTML.tag("slot", name="messages", c=messages)
|
||||
html = HTML.tag("div", c=[html, slot])
|
||||
|
||||
return HTML.tag('b-field', c=[html], **attrs)
|
||||
return HTML.tag("b-field", c=[html], **attrs)
|
||||
|
||||
def render_vue_finalize(self):
|
||||
"""
|
||||
|
|
@ -1094,11 +1090,10 @@ class Form:
|
|||
"""
|
||||
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 HTML.tag(
|
||||
"script",
|
||||
c=["\n", HTML.literal(set_data), "\n", HTML.literal(make_component), "\n"],
|
||||
)
|
||||
|
||||
def get_vue_model_data(self):
|
||||
"""
|
||||
|
|
@ -1191,10 +1186,10 @@ class Form:
|
|||
|
||||
:returns: Data dict, or ``False``.
|
||||
"""
|
||||
if hasattr(self, 'validated'):
|
||||
if hasattr(self, "validated"):
|
||||
del self.validated
|
||||
|
||||
if self.request.method != 'POST':
|
||||
if self.request.method != "POST":
|
||||
return False
|
||||
|
||||
# remove all readonly fields from deform / schema
|
||||
|
|
|
|||
|
|
@ -55,8 +55,8 @@ class WuttaDateTime(colander.DateTime):
|
|||
return colander.null
|
||||
|
||||
formats = [
|
||||
'%Y-%m-%dT%H:%M:%S',
|
||||
'%Y-%m-%dT%I:%M %p',
|
||||
"%Y-%m-%dT%H:%M:%S",
|
||||
"%Y-%m-%dT%I:%M %p",
|
||||
]
|
||||
|
||||
for fmt in formats:
|
||||
|
|
@ -98,7 +98,7 @@ class ObjectNode(colander.SchemaNode):
|
|||
If the node's type does not have a ``dictify()`` method, this
|
||||
will just convert the object to a string and return that.
|
||||
"""
|
||||
if hasattr(self.typ, 'dictify'):
|
||||
if hasattr(self.typ, "dictify"):
|
||||
return self.typ.dictify(obj)
|
||||
|
||||
# TODO: this is better than raising an error, as it previously
|
||||
|
|
@ -122,7 +122,7 @@ class ObjectNode(colander.SchemaNode):
|
|||
If the node's type does not have an ``objectify()`` method,
|
||||
this will raise ``NotImplementeError``.
|
||||
"""
|
||||
if hasattr(self.typ, 'objectify'):
|
||||
if hasattr(self.typ, "objectify"):
|
||||
return self.typ.objectify(value)
|
||||
|
||||
class_name = self.typ.__class__.__name__
|
||||
|
|
@ -148,9 +148,10 @@ class WuttaEnum(colander.Enum):
|
|||
def widget_maker(self, **kwargs):
|
||||
""" """
|
||||
|
||||
if 'values' not in kwargs:
|
||||
kwargs['values'] = [(getattr(e, self.attr), getattr(e, self.attr))
|
||||
for e in self.enum_cls]
|
||||
if "values" not in kwargs:
|
||||
kwargs["values"] = [
|
||||
(getattr(e, self.attr), getattr(e, self.attr)) for e in self.enum_cls
|
||||
]
|
||||
|
||||
return widgets.SelectWidget(**kwargs)
|
||||
|
||||
|
|
@ -180,8 +181,8 @@ class WuttaDictEnum(colander.String):
|
|||
|
||||
def widget_maker(self, **kwargs):
|
||||
""" """
|
||||
if 'values' not in kwargs:
|
||||
kwargs['values'] = [(k, v) for k, v in self.enum_dct.items()]
|
||||
if "values" not in kwargs:
|
||||
kwargs["values"] = [(k, v) for k, v in self.enum_dct.items()]
|
||||
|
||||
return widgets.SelectWidget(**kwargs)
|
||||
|
||||
|
|
@ -201,7 +202,7 @@ class WuttaMoney(colander.Money):
|
|||
"""
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
self.scale = kwargs.pop('scale', None)
|
||||
self.scale = kwargs.pop("scale", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.request = request
|
||||
self.config = self.request.wutta_config
|
||||
|
|
@ -210,7 +211,7 @@ class WuttaMoney(colander.Money):
|
|||
def widget_maker(self, **kwargs):
|
||||
""" """
|
||||
if self.scale:
|
||||
kwargs.setdefault('scale', self.scale)
|
||||
kwargs.setdefault("scale", self.scale)
|
||||
return widgets.WuttaMoneyInputWidget(self.request, **kwargs)
|
||||
|
||||
|
||||
|
|
@ -284,17 +285,17 @@ class ObjectRef(colander.SchemaType):
|
|||
Note that in the latter, ``value`` must be a string.
|
||||
"""
|
||||
|
||||
default_empty_option = ('', "(none)")
|
||||
default_empty_option = ("", "(none)")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
request,
|
||||
empty_option=None,
|
||||
*args,
|
||||
**kwargs,
|
||||
self,
|
||||
request,
|
||||
empty_option=None,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
# nb. allow session injection for tests
|
||||
self.session = kwargs.pop('session', Session())
|
||||
self.session = kwargs.pop("session", Session())
|
||||
super().__init__(*args, **kwargs)
|
||||
self.request = request
|
||||
self.config = self.request.wutta_config
|
||||
|
|
@ -307,7 +308,7 @@ class ObjectRef(colander.SchemaType):
|
|||
elif isinstance(empty_option, tuple) and len(empty_option) == 2:
|
||||
self.empty_option = empty_option
|
||||
else:
|
||||
self.empty_option = ('', str(empty_option))
|
||||
self.empty_option = ("", str(empty_option))
|
||||
else:
|
||||
self.empty_option = None
|
||||
|
||||
|
|
@ -427,17 +428,16 @@ class ObjectRef(colander.SchemaType):
|
|||
:class:`~wuttaweb.forms.widgets.ObjectRefWidget`.
|
||||
"""
|
||||
|
||||
if 'values' not in kwargs:
|
||||
if "values" not in kwargs:
|
||||
query = self.get_query()
|
||||
objects = query.all()
|
||||
values = [(self.serialize_object(obj), str(obj))
|
||||
for obj in objects]
|
||||
values = [(self.serialize_object(obj), str(obj)) for obj in objects]
|
||||
if self.empty_option:
|
||||
values.insert(0, self.empty_option)
|
||||
kwargs['values'] = values
|
||||
kwargs["values"] = values
|
||||
|
||||
if 'url' not in kwargs:
|
||||
kwargs['url'] = self.get_object_url
|
||||
if "url" not in kwargs:
|
||||
kwargs["url"] = self.get_object_url
|
||||
|
||||
return widgets.ObjectRefWidget(self.request, **kwargs)
|
||||
|
||||
|
|
@ -475,7 +475,7 @@ class PersonRef(ObjectRef):
|
|||
|
||||
def get_object_url(self, person):
|
||||
""" """
|
||||
return self.request.route_url('people.view', uuid=person.uuid)
|
||||
return self.request.route_url("people.view", uuid=person.uuid)
|
||||
|
||||
|
||||
class RoleRef(ObjectRef):
|
||||
|
|
@ -499,7 +499,7 @@ class RoleRef(ObjectRef):
|
|||
|
||||
def get_object_url(self, role):
|
||||
""" """
|
||||
return self.request.route_url('roles.view', uuid=role.uuid)
|
||||
return self.request.route_url("roles.view", uuid=role.uuid)
|
||||
|
||||
|
||||
class UserRef(ObjectRef):
|
||||
|
|
@ -523,7 +523,7 @@ class UserRef(ObjectRef):
|
|||
|
||||
def get_object_url(self, user):
|
||||
""" """
|
||||
return self.request.route_url('users.view', uuid=user.uuid)
|
||||
return self.request.route_url("users.view", uuid=user.uuid)
|
||||
|
||||
|
||||
class RoleRefs(WuttaSet):
|
||||
|
|
@ -544,9 +544,9 @@ class RoleRefs(WuttaSet):
|
|||
:returns: Instance of
|
||||
:class:`~wuttaweb.forms.widgets.RoleRefsWidget`.
|
||||
"""
|
||||
session = kwargs.setdefault('session', Session())
|
||||
session = kwargs.setdefault("session", Session())
|
||||
|
||||
if 'values' not in kwargs:
|
||||
if "values" not in kwargs:
|
||||
model = self.app.model
|
||||
auth = self.app.get_auth_handler()
|
||||
|
||||
|
|
@ -562,12 +562,14 @@ class RoleRefs(WuttaSet):
|
|||
avoid.add(auth.get_role_administrator(session).uuid)
|
||||
|
||||
# everything else can be (un)assigned for users
|
||||
roles = session.query(model.Role)\
|
||||
.filter(~model.Role.uuid.in_(avoid))\
|
||||
.order_by(model.Role.name)\
|
||||
.all()
|
||||
roles = (
|
||||
session.query(model.Role)
|
||||
.filter(~model.Role.uuid.in_(avoid))
|
||||
.order_by(model.Role.name)
|
||||
.all()
|
||||
)
|
||||
values = [(role.uuid.hex, role.name) for role in roles]
|
||||
kwargs['values'] = values
|
||||
kwargs["values"] = values
|
||||
|
||||
return widgets.RoleRefsWidget(self.request, **kwargs)
|
||||
|
||||
|
|
@ -598,15 +600,15 @@ class Permissions(WuttaSet):
|
|||
:returns: Instance of
|
||||
:class:`~wuttaweb.forms.widgets.PermissionsWidget`.
|
||||
"""
|
||||
kwargs.setdefault('session', Session())
|
||||
kwargs.setdefault('permissions', self.permissions)
|
||||
kwargs.setdefault("session", Session())
|
||||
kwargs.setdefault("permissions", self.permissions)
|
||||
|
||||
if 'values' not in kwargs:
|
||||
if "values" not in kwargs:
|
||||
values = []
|
||||
for gkey, group in self.permissions.items():
|
||||
for pkey, perm in group['perms'].items():
|
||||
values.append((pkey, perm['label']))
|
||||
kwargs['values'] = values
|
||||
for pkey, perm in group["perms"].items():
|
||||
values.append((pkey, perm["label"]))
|
||||
kwargs["values"] = values
|
||||
|
||||
return widgets.PermissionsWidget(self.request, **kwargs)
|
||||
|
||||
|
|
@ -631,7 +633,7 @@ class FileDownload(colander.String):
|
|||
"""
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
self.url = kwargs.pop('url', None)
|
||||
self.url = kwargs.pop("url", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.request = request
|
||||
self.config = self.request.wutta_config
|
||||
|
|
@ -639,7 +641,7 @@ class FileDownload(colander.String):
|
|||
|
||||
def widget_maker(self, **kwargs):
|
||||
""" """
|
||||
kwargs.setdefault('url', self.url)
|
||||
kwargs.setdefault("url", self.url)
|
||||
return widgets.FileDownloadWidget(self.request, **kwargs)
|
||||
|
||||
|
||||
|
|
@ -653,16 +655,15 @@ class EmailRecipients(colander.String):
|
|||
if appstruct is colander.null:
|
||||
return colander.null
|
||||
|
||||
return '\n'.join(parse_list(appstruct))
|
||||
return "\n".join(parse_list(appstruct))
|
||||
|
||||
def deserialize(self, node, cstruct):
|
||||
""" """
|
||||
if cstruct is colander.null:
|
||||
return colander.null
|
||||
|
||||
values = [value for value in parse_list(cstruct)
|
||||
if value]
|
||||
return ', '.join(values)
|
||||
values = [value for value in parse_list(cstruct) if value]
|
||||
return ", ".join(values)
|
||||
|
||||
def widget_maker(self, **kwargs):
|
||||
"""
|
||||
|
|
@ -675,4 +676,4 @@ class EmailRecipients(colander.String):
|
|||
|
||||
|
||||
# nb. colanderalchemy schema overrides
|
||||
sa.DateTime.__colanderalchemy_config__ = {'typ': WuttaDateTime}
|
||||
sa.DateTime.__colanderalchemy_config__ = {"typ": WuttaDateTime}
|
||||
|
|
|
|||
|
|
@ -47,10 +47,19 @@ import os
|
|||
|
||||
import colander
|
||||
import humanize
|
||||
from deform.widget import (Widget, TextInputWidget, TextAreaWidget,
|
||||
PasswordWidget, CheckedPasswordWidget,
|
||||
CheckboxWidget, SelectWidget, CheckboxChoiceWidget,
|
||||
DateInputWidget, DateTimeInputWidget, MoneyInputWidget)
|
||||
from deform.widget import (
|
||||
Widget,
|
||||
TextInputWidget,
|
||||
TextAreaWidget,
|
||||
PasswordWidget,
|
||||
CheckedPasswordWidget,
|
||||
CheckboxWidget,
|
||||
SelectWidget,
|
||||
CheckboxChoiceWidget,
|
||||
DateInputWidget,
|
||||
DateTimeInputWidget,
|
||||
MoneyInputWidget,
|
||||
)
|
||||
from webhelpers2.html import HTML
|
||||
|
||||
from wuttjamaican.conf import parse_list
|
||||
|
|
@ -93,7 +102,8 @@ class ObjectRefWidget(SelectWidget):
|
|||
when the :class:`~wuttaweb.forms.schema.ObjectRef` type
|
||||
instance (associated with the node) is serialized.
|
||||
"""
|
||||
readonly_template = 'readonly/objectref'
|
||||
|
||||
readonly_template = "readonly/objectref"
|
||||
|
||||
def __init__(self, request, url=None, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
|
@ -105,10 +115,14 @@ class ObjectRefWidget(SelectWidget):
|
|||
values = super().get_template_values(field, cstruct, kw)
|
||||
|
||||
# add url, only if rendering readonly
|
||||
readonly = kw.get('readonly', self.readonly)
|
||||
readonly = kw.get("readonly", self.readonly)
|
||||
if readonly:
|
||||
if 'url' not in values and self.url and getattr(field.schema, 'model_instance', None):
|
||||
values['url'] = self.url(field.schema.model_instance)
|
||||
if (
|
||||
"url" not in values
|
||||
and self.url
|
||||
and getattr(field.schema, "model_instance", None)
|
||||
):
|
||||
values["url"] = self.url(field.schema.model_instance)
|
||||
|
||||
return values
|
||||
|
||||
|
|
@ -128,7 +142,8 @@ class NotesWidget(TextAreaWidget):
|
|||
* ``textarea``
|
||||
* ``readonly/notes``
|
||||
"""
|
||||
readonly_template = 'readonly/notes'
|
||||
|
||||
readonly_template = "readonly/notes"
|
||||
|
||||
|
||||
class WuttaCheckboxChoiceWidget(CheckboxChoiceWidget):
|
||||
|
|
@ -165,7 +180,8 @@ class WuttaCheckedPasswordWidget(PasswordWidget):
|
|||
|
||||
* ``wutta_checked_password``
|
||||
"""
|
||||
template = 'wutta_checked_password'
|
||||
|
||||
template = "wutta_checked_password"
|
||||
|
||||
|
||||
class WuttaDateWidget(DateInputWidget):
|
||||
|
|
@ -197,7 +213,7 @@ class WuttaDateWidget(DateInputWidget):
|
|||
|
||||
def serialize(self, field, cstruct, **kw):
|
||||
""" """
|
||||
readonly = kw.get('readonly', self.readonly)
|
||||
readonly = kw.get("readonly", self.readonly)
|
||||
if readonly and cstruct:
|
||||
dt = datetime.datetime.fromisoformat(cstruct)
|
||||
return self.app.render_date(dt)
|
||||
|
|
@ -234,7 +250,7 @@ class WuttaDateTimeWidget(DateTimeInputWidget):
|
|||
|
||||
def serialize(self, field, cstruct, **kw):
|
||||
""" """
|
||||
readonly = kw.get('readonly', self.readonly)
|
||||
readonly = kw.get("readonly", self.readonly)
|
||||
if readonly and cstruct:
|
||||
dt = datetime.datetime.fromisoformat(cstruct)
|
||||
return self.app.render_datetime(dt)
|
||||
|
|
@ -264,7 +280,7 @@ class WuttaMoneyInputWidget(MoneyInputWidget):
|
|||
"""
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
self.scale = kwargs.pop('scale', 2)
|
||||
self.scale = kwargs.pop("scale", 2)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.request = request
|
||||
self.config = self.request.wutta_config
|
||||
|
|
@ -272,13 +288,13 @@ class WuttaMoneyInputWidget(MoneyInputWidget):
|
|||
|
||||
def serialize(self, field, cstruct, **kw):
|
||||
""" """
|
||||
readonly = kw.get('readonly', self.readonly)
|
||||
readonly = kw.get("readonly", self.readonly)
|
||||
if readonly:
|
||||
if cstruct in (colander.null, None):
|
||||
return HTML.tag('span')
|
||||
return HTML.tag("span")
|
||||
cstruct = decimal.Decimal(cstruct)
|
||||
text = self.app.render_currency(cstruct, scale=self.scale)
|
||||
return HTML.tag('span', c=[text])
|
||||
return HTML.tag("span", c=[text])
|
||||
|
||||
return super().serialize(field, cstruct, **kw)
|
||||
|
||||
|
|
@ -301,10 +317,11 @@ class FileDownloadWidget(Widget):
|
|||
:param url: Optional URL for hyperlink. If not specified, file
|
||||
name/size is shown with no hyperlink.
|
||||
"""
|
||||
readonly_template = 'readonly/filedownload'
|
||||
|
||||
readonly_template = "readonly/filedownload"
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
self.url = kwargs.pop('url', None)
|
||||
self.url = kwargs.pop("url", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.request = request
|
||||
self.config = self.request.wutta_config
|
||||
|
|
@ -313,21 +330,21 @@ class FileDownloadWidget(Widget):
|
|||
def serialize(self, field, cstruct, **kw):
|
||||
""" """
|
||||
# nb. readonly is the only way this rolls
|
||||
kw['readonly'] = True
|
||||
kw["readonly"] = True
|
||||
template = self.readonly_template
|
||||
|
||||
path = cstruct or None
|
||||
if path:
|
||||
kw.setdefault('filename', os.path.basename(path))
|
||||
kw.setdefault('filesize', self.readable_size(path))
|
||||
kw.setdefault("filename", os.path.basename(path))
|
||||
kw.setdefault("filesize", self.readable_size(path))
|
||||
if self.url:
|
||||
kw.setdefault('url', self.url)
|
||||
kw.setdefault("url", self.url)
|
||||
|
||||
else:
|
||||
kw.setdefault('filename', None)
|
||||
kw.setdefault('filesize', None)
|
||||
kw.setdefault("filename", None)
|
||||
kw.setdefault("filesize", None)
|
||||
|
||||
kw.setdefault('url', None)
|
||||
kw.setdefault("url", None)
|
||||
values = self.get_template_values(field, cstruct, kw)
|
||||
return field.renderer(template, **values)
|
||||
|
||||
|
|
@ -371,7 +388,7 @@ class GridWidget(Widget):
|
|||
:meth:`~wuttaweb.grids.base.Grid.render_table_element()` on
|
||||
the ``grid`` to serialize.
|
||||
"""
|
||||
readonly = kw.get('readonly', self.readonly)
|
||||
readonly = kw.get("readonly", self.readonly)
|
||||
if not readonly:
|
||||
raise NotImplementedError("edit not allowed for this widget")
|
||||
|
||||
|
|
@ -387,14 +404,15 @@ class RoleRefsWidget(WuttaCheckboxChoiceWidget):
|
|||
|
||||
This is a subclass of :class:`WuttaCheckboxChoiceWidget`.
|
||||
"""
|
||||
readonly_template = 'readonly/rolerefs'
|
||||
|
||||
readonly_template = "readonly/rolerefs"
|
||||
|
||||
def serialize(self, field, cstruct, **kw):
|
||||
""" """
|
||||
model = self.app.model
|
||||
|
||||
# special logic when field is editable
|
||||
readonly = kw.get('readonly', self.readonly)
|
||||
readonly = kw.get("readonly", self.readonly)
|
||||
if not readonly:
|
||||
|
||||
# but does not apply if current user is root
|
||||
|
|
@ -404,12 +422,11 @@ class RoleRefsWidget(WuttaCheckboxChoiceWidget):
|
|||
|
||||
# prune admin role from values list; it should not be
|
||||
# one of the options since current user is not admin
|
||||
values = kw.get('values', self.values)
|
||||
values = [val for val in values
|
||||
if val[0] != admin.uuid]
|
||||
kw['values'] = values
|
||||
values = kw.get("values", self.values)
|
||||
values = [val for val in values if val[0] != admin.uuid]
|
||||
kw["values"] = values
|
||||
|
||||
else: # readonly
|
||||
else: # readonly
|
||||
|
||||
# roles
|
||||
roles = []
|
||||
|
|
@ -418,11 +435,11 @@ class RoleRefsWidget(WuttaCheckboxChoiceWidget):
|
|||
role = self.session.get(model.Role, uuid)
|
||||
if role:
|
||||
roles.append(role)
|
||||
kw['roles'] = roles
|
||||
kw["roles"] = roles
|
||||
|
||||
# url
|
||||
url = lambda role: self.request.route_url('roles.view', uuid=role.uuid)
|
||||
kw['url'] = url
|
||||
url = lambda role: self.request.route_url("roles.view", uuid=role.uuid)
|
||||
kw["url"] = url
|
||||
|
||||
# default logic from here
|
||||
return super().serialize(field, cstruct, **kw)
|
||||
|
|
@ -440,19 +457,20 @@ class PermissionsWidget(WuttaCheckboxChoiceWidget):
|
|||
* ``permissions``
|
||||
* ``readonly/permissions``
|
||||
"""
|
||||
template = 'permissions'
|
||||
readonly_template = 'readonly/permissions'
|
||||
|
||||
template = "permissions"
|
||||
readonly_template = "readonly/permissions"
|
||||
|
||||
def serialize(self, field, cstruct, **kw):
|
||||
""" """
|
||||
kw.setdefault('permissions', self.permissions)
|
||||
kw.setdefault("permissions", self.permissions)
|
||||
|
||||
if 'values' not in kw:
|
||||
if "values" not in kw:
|
||||
values = []
|
||||
for gkey, group in self.permissions.items():
|
||||
for pkey, perm in group['perms'].items():
|
||||
values.append((pkey, perm['label']))
|
||||
kw['values'] = values
|
||||
for pkey, perm in group["perms"].items():
|
||||
values.append((pkey, perm["label"]))
|
||||
kw["values"] = values
|
||||
|
||||
return super().serialize(field, cstruct, **kw)
|
||||
|
||||
|
|
@ -472,13 +490,14 @@ class EmailRecipientsWidget(TextAreaWidget):
|
|||
See also the :class:`~wuttaweb.forms.schema.EmailRecipients`
|
||||
schema type, which uses this widget.
|
||||
"""
|
||||
readonly_template = 'readonly/email_recips'
|
||||
|
||||
readonly_template = "readonly/email_recips"
|
||||
|
||||
def serialize(self, field, cstruct, **kw):
|
||||
""" """
|
||||
readonly = kw.get('readonly', self.readonly)
|
||||
readonly = kw.get("readonly", self.readonly)
|
||||
if readonly:
|
||||
kw['recips'] = parse_list(cstruct or '')
|
||||
kw["recips"] = parse_list(cstruct or "")
|
||||
|
||||
return super().serialize(field, cstruct, **kw)
|
||||
|
||||
|
|
@ -487,9 +506,8 @@ class EmailRecipientsWidget(TextAreaWidget):
|
|||
if pstruct is colander.null:
|
||||
return colander.null
|
||||
|
||||
values = [value for value in parse_list(pstruct)
|
||||
if value]
|
||||
return ', '.join(values)
|
||||
values = [value for value in parse_list(pstruct) if value]
|
||||
return ", ".join(values)
|
||||
|
||||
|
||||
class BatchIdWidget(Widget):
|
||||
|
|
@ -508,4 +526,4 @@ class BatchIdWidget(Widget):
|
|||
return colander.null
|
||||
|
||||
batch_id = int(cstruct)
|
||||
return f'{batch_id:08d}'
|
||||
return f"{batch_id:08d}"
|
||||
|
|
|
|||
|
|
@ -48,13 +48,14 @@ from wuttaweb.grids.filters import default_sqlalchemy_filters, VerbNotSupported
|
|||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
SortInfo = namedtuple('SortInfo', ['sortkey', 'sortdir'])
|
||||
SortInfo = namedtuple("SortInfo", ["sortkey", "sortdir"])
|
||||
SortInfo.__doc__ = """
|
||||
Named tuple to track sorting info.
|
||||
|
||||
Elements of :attr:`~Grid.sort_defaults` will be of this type.
|
||||
"""
|
||||
|
||||
|
||||
class Grid:
|
||||
"""
|
||||
Base class for all :term:`grids <grid>`.
|
||||
|
|
@ -374,37 +375,37 @@ class Grid:
|
|||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
request,
|
||||
vue_tagname='wutta-grid',
|
||||
model_class=None,
|
||||
key=None,
|
||||
columns=None,
|
||||
data=None,
|
||||
labels={},
|
||||
renderers={},
|
||||
enums={},
|
||||
checkable=False,
|
||||
row_class=None,
|
||||
actions=[],
|
||||
linked_columns=[],
|
||||
hidden_columns=[],
|
||||
sortable=False,
|
||||
sort_multiple=True,
|
||||
sort_on_backend=True,
|
||||
sorters=None,
|
||||
sort_defaults=None,
|
||||
paginated=False,
|
||||
paginate_on_backend=True,
|
||||
pagesize_options=None,
|
||||
pagesize=None,
|
||||
page=1,
|
||||
searchable_columns=None,
|
||||
filterable=False,
|
||||
filters=None,
|
||||
filter_defaults=None,
|
||||
joiners=None,
|
||||
tools=None,
|
||||
self,
|
||||
request,
|
||||
vue_tagname="wutta-grid",
|
||||
model_class=None,
|
||||
key=None,
|
||||
columns=None,
|
||||
data=None,
|
||||
labels={},
|
||||
renderers={},
|
||||
enums={},
|
||||
checkable=False,
|
||||
row_class=None,
|
||||
actions=[],
|
||||
linked_columns=[],
|
||||
hidden_columns=[],
|
||||
sortable=False,
|
||||
sort_multiple=True,
|
||||
sort_on_backend=True,
|
||||
sorters=None,
|
||||
sort_defaults=None,
|
||||
paginated=False,
|
||||
paginate_on_backend=True,
|
||||
pagesize_options=None,
|
||||
pagesize=None,
|
||||
page=1,
|
||||
searchable_columns=None,
|
||||
filterable=False,
|
||||
filters=None,
|
||||
filter_defaults=None,
|
||||
joiners=None,
|
||||
tools=None,
|
||||
):
|
||||
self.request = request
|
||||
self.vue_tagname = vue_tagname
|
||||
|
|
@ -434,7 +435,9 @@ class Grid:
|
|||
self.sortable = sortable
|
||||
self.sort_multiple = sort_multiple
|
||||
if self.sort_multiple and self.request.use_oruga:
|
||||
log.warning("grid.sort_multiple is not implemented for Oruga-based templates")
|
||||
log.warning(
|
||||
"grid.sort_multiple is not implemented for Oruga-based templates"
|
||||
)
|
||||
self.sort_multiple = False
|
||||
self.sort_on_backend = sort_on_backend
|
||||
if sorters is not None:
|
||||
|
|
@ -482,7 +485,7 @@ class Grid:
|
|||
|
||||
Otherwise ``None`` is returned.
|
||||
"""
|
||||
if hasattr(self, 'columns') and self.columns:
|
||||
if hasattr(self, "columns") and self.columns:
|
||||
return self.columns
|
||||
|
||||
columns = self.get_model_columns()
|
||||
|
|
@ -500,8 +503,9 @@ class Grid:
|
|||
fields. If not set, the grid's :attr:`model_class` is
|
||||
assumed.
|
||||
"""
|
||||
return get_model_fields(self.config,
|
||||
model_class=model_class or self.model_class)
|
||||
return get_model_fields(
|
||||
self.config, model_class=model_class or self.model_class
|
||||
)
|
||||
|
||||
@property
|
||||
def vue_component(self):
|
||||
|
|
@ -510,8 +514,8 @@ class Grid:
|
|||
|
||||
This is a generated value based on :attr:`vue_tagname`.
|
||||
"""
|
||||
words = self.vue_tagname.split('-')
|
||||
return ''.join([word.capitalize() for word in words])
|
||||
words = self.vue_tagname.split("-")
|
||||
return "".join([word.capitalize() for word in words])
|
||||
|
||||
def set_columns(self, columns):
|
||||
"""
|
||||
|
|
@ -576,7 +580,7 @@ class Grid:
|
|||
if hidden:
|
||||
if key not in self.hidden_columns:
|
||||
self.hidden_columns.append(key)
|
||||
else: # un-hide
|
||||
else: # un-hide
|
||||
if self.hidden_columns and key in self.hidden_columns:
|
||||
self.hidden_columns.remove(key)
|
||||
|
||||
|
|
@ -678,13 +682,13 @@ class Grid:
|
|||
Renderer overrides are tracked via :attr:`renderers`.
|
||||
"""
|
||||
builtins = {
|
||||
'batch_id': self.render_batch_id,
|
||||
'boolean': self.render_boolean,
|
||||
'currency': self.render_currency,
|
||||
'date': self.render_date,
|
||||
'datetime': self.render_datetime,
|
||||
'quantity': self.render_quantity,
|
||||
'percent': self.render_percent,
|
||||
"batch_id": self.render_batch_id,
|
||||
"boolean": self.render_boolean,
|
||||
"currency": self.render_currency,
|
||||
"date": self.render_date,
|
||||
"datetime": self.render_datetime,
|
||||
"quantity": self.render_quantity,
|
||||
"percent": self.render_percent,
|
||||
}
|
||||
|
||||
if renderer in builtins:
|
||||
|
|
@ -722,7 +726,7 @@ class Grid:
|
|||
|
||||
attr = getattr(self.model_class, key, None)
|
||||
if attr:
|
||||
prop = getattr(attr, 'prop', None)
|
||||
prop = getattr(attr, "prop", None)
|
||||
if prop and isinstance(prop, orm.ColumnProperty):
|
||||
column = prop.columns[0]
|
||||
if isinstance(column.type, sa.Date):
|
||||
|
|
@ -780,7 +784,7 @@ class Grid:
|
|||
if link:
|
||||
if key not in self.linked_columns:
|
||||
self.linked_columns.append(key)
|
||||
else: # unlink
|
||||
else: # unlink
|
||||
if self.linked_columns and key in self.linked_columns:
|
||||
self.linked_columns.remove(key)
|
||||
|
||||
|
|
@ -946,8 +950,11 @@ class Grid:
|
|||
if key in sorters:
|
||||
continue
|
||||
prop = getattr(self.model_class, key, None)
|
||||
if (prop and hasattr(prop, 'property')
|
||||
and isinstance(prop.property, orm.ColumnProperty)):
|
||||
if (
|
||||
prop
|
||||
and hasattr(prop, "property")
|
||||
and isinstance(prop.property, orm.ColumnProperty)
|
||||
):
|
||||
sorters[prop.key] = self.make_sorter(prop)
|
||||
|
||||
return sorters
|
||||
|
|
@ -1039,7 +1046,9 @@ class Grid:
|
|||
# query is sorted with order_by()
|
||||
if isinstance(data, orm.Query):
|
||||
if not model_property:
|
||||
raise TypeError(f"grid sorter for '{key}' does not map to a model property")
|
||||
raise TypeError(
|
||||
f"grid sorter for '{key}' does not map to a model property"
|
||||
)
|
||||
query = data
|
||||
return query.order_by(getattr(model_property, direction)())
|
||||
|
||||
|
|
@ -1052,16 +1061,16 @@ class Grid:
|
|||
# TODO: may need this for String etc. as well?
|
||||
if isinstance(model_property.type, sa.Text):
|
||||
if foldcase:
|
||||
kfunc = lambda obj: (obj[key] or '').lower()
|
||||
kfunc = lambda obj: (obj[key] or "").lower()
|
||||
else:
|
||||
kfunc = lambda obj: obj[key] or ''
|
||||
kfunc = lambda obj: obj[key] or ""
|
||||
if not kfunc:
|
||||
# nb. sorting with this can raise error if data
|
||||
# contains varying types, e.g. str and None
|
||||
kfunc = lambda obj: obj[key]
|
||||
|
||||
# then sort the data and return
|
||||
return sorted(data, key=kfunc, reverse=direction == 'desc')
|
||||
return sorted(data, key=kfunc, reverse=direction == "desc")
|
||||
|
||||
# TODO: this should be improved; is needed in tailbone for
|
||||
# multi-column sorting with sqlalchemy queries
|
||||
|
|
@ -1168,13 +1177,15 @@ class Grid:
|
|||
sort_defaults = []
|
||||
if len(args) == 1:
|
||||
if isinstance(args[0], str):
|
||||
sort_defaults = [SortInfo(args[0], 'asc')]
|
||||
sort_defaults = [SortInfo(args[0], "asc")]
|
||||
elif isinstance(args[0], tuple) and len(args[0]) == 2:
|
||||
sort_defaults = [SortInfo(*args[0])]
|
||||
elif isinstance(args[0], list):
|
||||
sort_defaults = [SortInfo(*tup) for tup in args[0]]
|
||||
else:
|
||||
raise ValueError("for just one positional arg, must pass string, 2-tuple or list")
|
||||
raise ValueError(
|
||||
"for just one positional arg, must pass string, 2-tuple or list"
|
||||
)
|
||||
elif len(args) == 2:
|
||||
sort_defaults = [SortInfo(*args)]
|
||||
else:
|
||||
|
|
@ -1182,9 +1193,12 @@ class Grid:
|
|||
|
||||
# prune if multi-column requested but not supported
|
||||
if len(sort_defaults) > 1 and not self.sort_multiple:
|
||||
log.warning("multi-column sorting is not enabled for the instance; "
|
||||
"list will be pruned to first element for '%s' grid: %s",
|
||||
self.key, sort_defaults)
|
||||
log.warning(
|
||||
"multi-column sorting is not enabled for the instance; "
|
||||
"list will be pruned to first element for '%s' grid: %s",
|
||||
self.key,
|
||||
sort_defaults,
|
||||
)
|
||||
sort_defaults = [sort_defaults[0]]
|
||||
|
||||
self.sort_defaults = sort_defaults
|
||||
|
|
@ -1270,8 +1284,7 @@ class Grid:
|
|||
continue
|
||||
|
||||
# do not create filter for UUID field
|
||||
if (len(prop.columns) == 1
|
||||
and isinstance(prop.columns[0].type, UUID)):
|
||||
if len(prop.columns) == 1 and isinstance(prop.columns[0].type, UUID):
|
||||
continue
|
||||
|
||||
attr = getattr(self.model_class, prop.key)
|
||||
|
|
@ -1294,12 +1307,12 @@ class Grid:
|
|||
:returns: A :class:`~wuttaweb.grids.filters.GridFilter`
|
||||
instance.
|
||||
"""
|
||||
key = kwargs.pop('key', None)
|
||||
key = kwargs.pop("key", None)
|
||||
|
||||
# model_property is required
|
||||
model_property = None
|
||||
if kwargs.get('model_property'):
|
||||
model_property = kwargs['model_property']
|
||||
if kwargs.get("model_property"):
|
||||
model_property = kwargs["model_property"]
|
||||
elif isinstance(columninfo, str):
|
||||
key = columninfo
|
||||
if self.model_class:
|
||||
|
|
@ -1310,7 +1323,7 @@ class Grid:
|
|||
model_property = columninfo
|
||||
|
||||
# optional factory override
|
||||
factory = kwargs.pop('factory', None)
|
||||
factory = kwargs.pop("factory", None)
|
||||
if not factory:
|
||||
typ = model_property.type
|
||||
factory = default_sqlalchemy_filters.get(type(typ))
|
||||
|
|
@ -1318,7 +1331,7 @@ class Grid:
|
|||
factory = default_sqlalchemy_filters[None]
|
||||
|
||||
# make filter
|
||||
kwargs['model_property'] = model_property
|
||||
kwargs["model_property"] = model_property
|
||||
return factory(self.request, key or model_property.key, **kwargs)
|
||||
|
||||
def set_filter(self, key, filterinfo=None, **kwargs):
|
||||
|
|
@ -1349,8 +1362,8 @@ class Grid:
|
|||
# filtr = filterinfo
|
||||
raise NotImplementedError
|
||||
else:
|
||||
kwargs['key'] = key
|
||||
kwargs.setdefault('label', self.get_label(key))
|
||||
kwargs["key"] = key
|
||||
kwargs.setdefault("label", self.get_label(key))
|
||||
filtr = self.make_filter(filterinfo or key, **kwargs)
|
||||
|
||||
self.filters[key] = filtr
|
||||
|
|
@ -1384,7 +1397,7 @@ class Grid:
|
|||
|
||||
Filter defaults are tracked via :attr:`filter_defaults`.
|
||||
"""
|
||||
filter_defaults = dict(getattr(self, 'filter_defaults', {}))
|
||||
filter_defaults = dict(getattr(self, "filter_defaults", {}))
|
||||
|
||||
for key, values in defaults.items():
|
||||
filtr = filter_defaults.setdefault(key, {})
|
||||
|
|
@ -1411,10 +1424,9 @@ class Grid:
|
|||
This method is intended for use in the constructor. Code can
|
||||
instead access :attr:`pagesize_options` directly.
|
||||
"""
|
||||
options = self.config.get_list('wuttaweb.grids.default_pagesize_options')
|
||||
options = self.config.get_list("wuttaweb.grids.default_pagesize_options")
|
||||
if options:
|
||||
options = [int(size) for size in options
|
||||
if size.isdigit()]
|
||||
options = [int(size) for size in options if size.isdigit()]
|
||||
if options:
|
||||
return options
|
||||
|
||||
|
|
@ -1434,7 +1446,7 @@ class Grid:
|
|||
This method is intended for use in the constructor. Code can
|
||||
instead access :attr:`pagesize` directly.
|
||||
"""
|
||||
size = self.config.get_int('wuttaweb.grids.default_pagesize')
|
||||
size = self.config.get_int("wuttaweb.grids.default_pagesize")
|
||||
if size:
|
||||
return size
|
||||
|
||||
|
|
@ -1485,51 +1497,54 @@ class Grid:
|
|||
if self.filterable:
|
||||
for filtr in self.filters.values():
|
||||
defaults = self.filter_defaults.get(filtr.key, {})
|
||||
settings[f'filter.{filtr.key}.active'] = defaults.get('active',
|
||||
filtr.default_active)
|
||||
settings[f'filter.{filtr.key}.verb'] = defaults.get('verb',
|
||||
filtr.get_default_verb())
|
||||
settings[f'filter.{filtr.key}.value'] = defaults.get('value',
|
||||
filtr.default_value)
|
||||
settings[f"filter.{filtr.key}.active"] = defaults.get(
|
||||
"active", filtr.default_active
|
||||
)
|
||||
settings[f"filter.{filtr.key}.verb"] = defaults.get(
|
||||
"verb", filtr.get_default_verb()
|
||||
)
|
||||
settings[f"filter.{filtr.key}.value"] = defaults.get(
|
||||
"value", filtr.default_value
|
||||
)
|
||||
if self.sortable:
|
||||
if self.sort_defaults:
|
||||
# nb. as of writing neither Buefy nor Oruga support a
|
||||
# multi-column *default* sort; so just use first sorter
|
||||
sortinfo = self.sort_defaults[0]
|
||||
settings['sorters.length'] = 1
|
||||
settings['sorters.1.key'] = sortinfo.sortkey
|
||||
settings['sorters.1.dir'] = sortinfo.sortdir
|
||||
settings["sorters.length"] = 1
|
||||
settings["sorters.1.key"] = sortinfo.sortkey
|
||||
settings["sorters.1.dir"] = sortinfo.sortdir
|
||||
else:
|
||||
settings['sorters.length'] = 0
|
||||
settings["sorters.length"] = 0
|
||||
if self.paginated and self.paginate_on_backend:
|
||||
settings['pagesize'] = self.pagesize
|
||||
settings['page'] = self.page
|
||||
settings["pagesize"] = self.pagesize
|
||||
settings["page"] = self.page
|
||||
|
||||
# update settings dict based on what we find in the request
|
||||
# and/or user session. always prioritize the former.
|
||||
|
||||
# nb. do not read settings if user wants a reset
|
||||
if self.request.GET.get('reset-view'):
|
||||
if self.request.GET.get("reset-view"):
|
||||
# at this point we only have default settings, and we want
|
||||
# to keep those *and* persist them for next time, below
|
||||
pass
|
||||
|
||||
elif self.request_has_settings('filter'):
|
||||
self.update_filter_settings(settings, src='request')
|
||||
if self.request_has_settings('sort'):
|
||||
self.update_sort_settings(settings, src='request')
|
||||
elif self.request_has_settings("filter"):
|
||||
self.update_filter_settings(settings, src="request")
|
||||
if self.request_has_settings("sort"):
|
||||
self.update_sort_settings(settings, src="request")
|
||||
else:
|
||||
self.update_sort_settings(settings, src='session')
|
||||
self.update_sort_settings(settings, src="session")
|
||||
self.update_page_settings(settings)
|
||||
|
||||
elif self.request_has_settings('sort'):
|
||||
self.update_filter_settings(settings, src='session')
|
||||
self.update_sort_settings(settings, src='request')
|
||||
elif self.request_has_settings("sort"):
|
||||
self.update_filter_settings(settings, src="session")
|
||||
self.update_sort_settings(settings, src="request")
|
||||
self.update_page_settings(settings)
|
||||
|
||||
elif self.request_has_settings('page'):
|
||||
self.update_filter_settings(settings, src='session')
|
||||
self.update_sort_settings(settings, src='session')
|
||||
elif self.request_has_settings("page"):
|
||||
self.update_filter_settings(settings, src="session")
|
||||
self.update_sort_settings(settings, src="session")
|
||||
self.update_page_settings(settings)
|
||||
|
||||
else:
|
||||
|
|
@ -1537,32 +1552,36 @@ class Grid:
|
|||
persist = False
|
||||
|
||||
# but still should load whatever is in user session
|
||||
self.update_filter_settings(settings, src='session')
|
||||
self.update_sort_settings(settings, src='session')
|
||||
self.update_filter_settings(settings, src="session")
|
||||
self.update_sort_settings(settings, src="session")
|
||||
self.update_page_settings(settings)
|
||||
|
||||
# maybe save settings in user session, for next time
|
||||
if persist:
|
||||
self.persist_settings(settings, dest='session')
|
||||
self.persist_settings(settings, dest="session")
|
||||
|
||||
# update ourself to reflect settings dict..
|
||||
|
||||
# filtering
|
||||
if self.filterable:
|
||||
for filtr in self.filters.values():
|
||||
filtr.active = settings[f'filter.{filtr.key}.active']
|
||||
filtr.verb = settings[f'filter.{filtr.key}.verb'] or filtr.get_default_verb()
|
||||
filtr.value = settings[f'filter.{filtr.key}.value']
|
||||
filtr.active = settings[f"filter.{filtr.key}.active"]
|
||||
filtr.verb = (
|
||||
settings[f"filter.{filtr.key}.verb"] or filtr.get_default_verb()
|
||||
)
|
||||
filtr.value = settings[f"filter.{filtr.key}.value"]
|
||||
|
||||
# sorting
|
||||
if self.sortable:
|
||||
# nb. doing this for frontend sorting also
|
||||
self.active_sorters = []
|
||||
for i in range(1, settings['sorters.length'] + 1):
|
||||
self.active_sorters.append({
|
||||
'key': settings[f'sorters.{i}.key'],
|
||||
'dir': settings[f'sorters.{i}.dir'],
|
||||
})
|
||||
for i in range(1, settings["sorters.length"] + 1):
|
||||
self.active_sorters.append(
|
||||
{
|
||||
"key": settings[f"sorters.{i}.key"],
|
||||
"dir": settings[f"sorters.{i}.dir"],
|
||||
}
|
||||
)
|
||||
# TODO: i thought this was needed, but now idk?
|
||||
# # nb. when showing full index page (i.e. not partial)
|
||||
# # this implies we must set the default sorter for Vue
|
||||
|
|
@ -1572,35 +1591,36 @@ class Grid:
|
|||
|
||||
# paging
|
||||
if self.paginated and self.paginate_on_backend:
|
||||
self.pagesize = settings['pagesize']
|
||||
self.page = settings['page']
|
||||
self.pagesize = settings["pagesize"]
|
||||
self.page = settings["page"]
|
||||
|
||||
def request_has_settings(self, typ):
|
||||
""" """
|
||||
|
||||
if typ == 'filter' and self.filterable:
|
||||
if typ == "filter" and self.filterable:
|
||||
for filtr in self.filters.values():
|
||||
if filtr.key in self.request.GET:
|
||||
return True
|
||||
if 'filter' in self.request.GET: # user may be applying empty filters
|
||||
if "filter" in self.request.GET: # user may be applying empty filters
|
||||
return True
|
||||
|
||||
elif typ == 'sort' and self.sortable and self.sort_on_backend:
|
||||
if 'sort1key' in self.request.GET:
|
||||
elif typ == "sort" and self.sortable and self.sort_on_backend:
|
||||
if "sort1key" in self.request.GET:
|
||||
return True
|
||||
|
||||
elif typ == 'page' and self.paginated and self.paginate_on_backend:
|
||||
for key in ['pagesize', 'page']:
|
||||
elif typ == "page" and self.paginated and self.paginate_on_backend:
|
||||
for key in ["pagesize", "page"]:
|
||||
if key in self.request.GET:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get_setting(self, settings, key, src='session', default=None,
|
||||
normalize=lambda v: v):
|
||||
def get_setting(
|
||||
self, settings, key, src="session", default=None, normalize=lambda v: v
|
||||
):
|
||||
""" """
|
||||
|
||||
if src == 'request':
|
||||
if src == "request":
|
||||
value = self.request.GET.get(key)
|
||||
if value is not None:
|
||||
try:
|
||||
|
|
@ -1608,8 +1628,8 @@ class Grid:
|
|||
except ValueError:
|
||||
pass
|
||||
|
||||
elif src == 'session':
|
||||
value = self.request.session.get(f'grid.{self.key}.{key}')
|
||||
elif src == "session":
|
||||
value = self.request.session.get(f"grid.{self.key}.{key}")
|
||||
if value is not None:
|
||||
return normalize(value)
|
||||
|
||||
|
|
@ -1627,52 +1647,62 @@ class Grid:
|
|||
return
|
||||
|
||||
for filtr in self.filters.values():
|
||||
prefix = f'filter.{filtr.key}'
|
||||
prefix = f"filter.{filtr.key}"
|
||||
|
||||
if src == 'request':
|
||||
if src == "request":
|
||||
# consider filter active if query string contains a value for it
|
||||
settings[f'{prefix}.active'] = filtr.key in self.request.GET
|
||||
settings[f'{prefix}.verb'] = self.get_setting(
|
||||
settings, f'{filtr.key}.verb', src='request', default='')
|
||||
settings[f'{prefix}.value'] = self.get_setting(
|
||||
settings, filtr.key, src='request', default='')
|
||||
settings[f"{prefix}.active"] = filtr.key in self.request.GET
|
||||
settings[f"{prefix}.verb"] = self.get_setting(
|
||||
settings, f"{filtr.key}.verb", src="request", default=""
|
||||
)
|
||||
settings[f"{prefix}.value"] = self.get_setting(
|
||||
settings, filtr.key, src="request", default=""
|
||||
)
|
||||
|
||||
elif src == 'session':
|
||||
settings[f'{prefix}.active'] = self.get_setting(
|
||||
settings, f'{prefix}.active', src='session',
|
||||
normalize=lambda v: str(v).lower() == 'true', default=False)
|
||||
settings[f'{prefix}.verb'] = self.get_setting(
|
||||
settings, f'{prefix}.verb', src='session', default='')
|
||||
settings[f'{prefix}.value'] = self.get_setting(
|
||||
settings, f'{prefix}.value', src='session', default='')
|
||||
elif src == "session":
|
||||
settings[f"{prefix}.active"] = self.get_setting(
|
||||
settings,
|
||||
f"{prefix}.active",
|
||||
src="session",
|
||||
normalize=lambda v: str(v).lower() == "true",
|
||||
default=False,
|
||||
)
|
||||
settings[f"{prefix}.verb"] = self.get_setting(
|
||||
settings, f"{prefix}.verb", src="session", default=""
|
||||
)
|
||||
settings[f"{prefix}.value"] = self.get_setting(
|
||||
settings, f"{prefix}.value", src="session", default=""
|
||||
)
|
||||
|
||||
def update_sort_settings(self, settings, src=None):
|
||||
""" """
|
||||
if not (self.sortable and self.sort_on_backend):
|
||||
return
|
||||
|
||||
if src == 'request':
|
||||
if src == "request":
|
||||
i = 1
|
||||
while True:
|
||||
skey = f'sort{i}key'
|
||||
skey = f"sort{i}key"
|
||||
if skey in self.request.GET:
|
||||
settings[f'sorters.{i}.key'] = self.get_setting(settings, skey,
|
||||
src='request')
|
||||
settings[f'sorters.{i}.dir'] = self.get_setting(settings, f'sort{i}dir',
|
||||
src='request',
|
||||
default='asc')
|
||||
settings[f"sorters.{i}.key"] = self.get_setting(
|
||||
settings, skey, src="request"
|
||||
)
|
||||
settings[f"sorters.{i}.dir"] = self.get_setting(
|
||||
settings, f"sort{i}dir", src="request", default="asc"
|
||||
)
|
||||
else:
|
||||
break
|
||||
i += 1
|
||||
settings['sorters.length'] = i - 1
|
||||
settings["sorters.length"] = i - 1
|
||||
|
||||
elif src == 'session':
|
||||
settings['sorters.length'] = self.get_setting(settings, 'sorters.length',
|
||||
src='session', normalize=int)
|
||||
for i in range(1, settings['sorters.length'] + 1):
|
||||
for key in ('key', 'dir'):
|
||||
skey = f'sorters.{i}.{key}'
|
||||
settings[skey] = self.get_setting(settings, skey, src='session')
|
||||
elif src == "session":
|
||||
settings["sorters.length"] = self.get_setting(
|
||||
settings, "sorters.length", src="session", normalize=int
|
||||
)
|
||||
for i in range(1, settings["sorters.length"] + 1):
|
||||
for key in ("key", "dir"):
|
||||
skey = f"sorters.{i}.{key}"
|
||||
settings[skey] = self.get_setting(settings, skey, src="session")
|
||||
|
||||
def update_page_settings(self, settings):
|
||||
""" """
|
||||
|
|
@ -1682,34 +1712,34 @@ class Grid:
|
|||
# update the settings dict from request and/or user session
|
||||
|
||||
# pagesize
|
||||
pagesize = self.request.GET.get('pagesize')
|
||||
pagesize = self.request.GET.get("pagesize")
|
||||
if pagesize is not None:
|
||||
if pagesize.isdigit():
|
||||
settings['pagesize'] = int(pagesize)
|
||||
settings["pagesize"] = int(pagesize)
|
||||
else:
|
||||
pagesize = self.request.session.get(f'grid.{self.key}.pagesize')
|
||||
pagesize = self.request.session.get(f"grid.{self.key}.pagesize")
|
||||
if pagesize is not None:
|
||||
settings['pagesize'] = pagesize
|
||||
settings["pagesize"] = pagesize
|
||||
|
||||
# page
|
||||
page = self.request.GET.get('page')
|
||||
page = self.request.GET.get("page")
|
||||
if page is not None:
|
||||
if page.isdigit():
|
||||
settings['page'] = int(page)
|
||||
settings["page"] = int(page)
|
||||
else:
|
||||
page = self.request.session.get(f'grid.{self.key}.page')
|
||||
page = self.request.session.get(f"grid.{self.key}.page")
|
||||
if page is not None:
|
||||
settings['page'] = int(page)
|
||||
settings["page"] = int(page)
|
||||
|
||||
def persist_settings(self, settings, dest=None):
|
||||
""" """
|
||||
if dest not in ('session',):
|
||||
if dest not in ("session",):
|
||||
raise ValueError(f"invalid dest identifier: {dest}")
|
||||
|
||||
# func to save a setting value to user session
|
||||
def persist(key, value=lambda k: settings.get(k)):
|
||||
assert dest == 'session'
|
||||
skey = f'grid.{self.key}.{key}'
|
||||
assert dest == "session"
|
||||
skey = f"grid.{self.key}.{key}"
|
||||
self.request.session[skey] = value(key)
|
||||
|
||||
# filter settings
|
||||
|
|
@ -1717,10 +1747,12 @@ class Grid:
|
|||
|
||||
# always save all filters, with status
|
||||
for filtr in self.filters.values():
|
||||
persist(f'filter.{filtr.key}.active',
|
||||
value=lambda k: 'true' if settings.get(k) else 'false')
|
||||
persist(f'filter.{filtr.key}.verb')
|
||||
persist(f'filter.{filtr.key}.value')
|
||||
persist(
|
||||
f"filter.{filtr.key}.active",
|
||||
value=lambda k: "true" if settings.get(k) else "false",
|
||||
)
|
||||
persist(f"filter.{filtr.key}.verb")
|
||||
persist(f"filter.{filtr.key}.value")
|
||||
|
||||
# sort settings
|
||||
if self.sortable and self.sort_on_backend:
|
||||
|
|
@ -1729,26 +1761,26 @@ class Grid:
|
|||
# because number of sort settings will vary, so we delete
|
||||
# all and then write all
|
||||
|
||||
if dest == 'session':
|
||||
if dest == "session":
|
||||
# remove sort settings from user session
|
||||
prefix = f'grid.{self.key}.sorters.'
|
||||
prefix = f"grid.{self.key}.sorters."
|
||||
for key in list(self.request.session):
|
||||
if key.startswith(prefix):
|
||||
del self.request.session[key]
|
||||
|
||||
# now save sort settings to dest
|
||||
if 'sorters.length' in settings:
|
||||
persist('sorters.length')
|
||||
for i in range(1, settings['sorters.length'] + 1):
|
||||
persist(f'sorters.{i}.key')
|
||||
persist(f'sorters.{i}.dir')
|
||||
if "sorters.length" in settings:
|
||||
persist("sorters.length")
|
||||
for i in range(1, settings["sorters.length"] + 1):
|
||||
persist(f"sorters.{i}.key")
|
||||
persist(f"sorters.{i}.dir")
|
||||
|
||||
# pagination settings
|
||||
if self.paginated and self.paginate_on_backend:
|
||||
|
||||
# save to dest
|
||||
persist('pagesize')
|
||||
persist('page')
|
||||
persist("pagesize")
|
||||
persist("page")
|
||||
|
||||
##############################
|
||||
# data methods
|
||||
|
|
@ -1794,8 +1826,7 @@ class Grid:
|
|||
This inspects each :class:`~wuttaweb.grids.filters.GridFilter`
|
||||
in :attr:`filters` and only returns the ones marked active.
|
||||
"""
|
||||
return [filtr for filtr in self.filters.values()
|
||||
if filtr.active]
|
||||
return [filtr for filtr in self.filters.values() if filtr.active]
|
||||
|
||||
def filter_data(self, data, filters=None):
|
||||
"""
|
||||
|
|
@ -1848,8 +1879,8 @@ class Grid:
|
|||
sorters = reversed(sorters)
|
||||
|
||||
for sorter in sorters:
|
||||
sortkey = sorter['key']
|
||||
sortdir = sorter['dir']
|
||||
sortkey = sorter["key"]
|
||||
sortdir = sorter["dir"]
|
||||
|
||||
# cannot sort unless we have a sorter callable
|
||||
sortfunc = self.sorters.get(sortkey)
|
||||
|
|
@ -1876,20 +1907,18 @@ class Grid:
|
|||
This method is called by :meth:`get_visible_data()`.
|
||||
"""
|
||||
if isinstance(data, orm.Query):
|
||||
pager = SqlalchemyOrmPage(data,
|
||||
items_per_page=self.pagesize,
|
||||
page=self.page)
|
||||
pager = SqlalchemyOrmPage(
|
||||
data, items_per_page=self.pagesize, page=self.page
|
||||
)
|
||||
|
||||
else:
|
||||
pager = paginate.Page(data,
|
||||
items_per_page=self.pagesize,
|
||||
page=self.page)
|
||||
pager = paginate.Page(data, items_per_page=self.pagesize, page=self.page)
|
||||
|
||||
# pager may have detected that our current page is outside the
|
||||
# valid range. if so we should update ourself to match
|
||||
if pager.page != self.page:
|
||||
self.page = pager.page
|
||||
key = f'grid.{self.key}.page'
|
||||
key = f"grid.{self.key}.page"
|
||||
if key in self.request.session:
|
||||
self.request.session[key] = self.page
|
||||
|
||||
|
|
@ -1914,7 +1943,7 @@ class Grid:
|
|||
return ""
|
||||
|
||||
batch_id = int(value)
|
||||
return f'{batch_id:08d}'
|
||||
return f"{batch_id:08d}"
|
||||
|
||||
def render_boolean(self, obj, key, value):
|
||||
"""
|
||||
|
|
@ -2034,10 +2063,8 @@ class Grid:
|
|||
return self.app.render_quantity(value)
|
||||
|
||||
def render_table_element(
|
||||
self,
|
||||
form=None,
|
||||
template='/grids/table_element.mako',
|
||||
**context):
|
||||
self, form=None, template="/grids/table_element.mako", **context
|
||||
):
|
||||
"""
|
||||
Render a simple Vue table element for the grid.
|
||||
|
||||
|
|
@ -2100,10 +2127,7 @@ class Grid:
|
|||
"""
|
||||
return HTML.tag(self.vue_tagname, **kwargs)
|
||||
|
||||
def render_vue_template(
|
||||
self,
|
||||
template='/grids/vue_template.mako',
|
||||
**context):
|
||||
def render_vue_template(self, template="/grids/vue_template.mako", **context):
|
||||
"""
|
||||
Render the Vue template block for the grid.
|
||||
|
||||
|
|
@ -2141,8 +2165,8 @@ class Grid:
|
|||
:param template: Path to Mako template which is used to render
|
||||
the output.
|
||||
"""
|
||||
context['grid'] = self
|
||||
context.setdefault('request', self.request)
|
||||
context["grid"] = self
|
||||
context.setdefault("request", self.request)
|
||||
output = render(template, context)
|
||||
return HTML.literal(output)
|
||||
|
||||
|
|
@ -2164,11 +2188,10 @@ class Grid:
|
|||
"""
|
||||
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 HTML.tag(
|
||||
"script",
|
||||
c=["\n", HTML.literal(set_data), "\n", HTML.literal(make_component), "\n"],
|
||||
)
|
||||
|
||||
def get_vue_columns(self):
|
||||
"""
|
||||
|
|
@ -2195,13 +2218,15 @@ class Grid:
|
|||
|
||||
columns = []
|
||||
for name in self.columns:
|
||||
columns.append({
|
||||
'field': name,
|
||||
'label': self.get_label(name),
|
||||
'hidden': self.is_hidden(name),
|
||||
'sortable': self.is_sortable(name),
|
||||
'searchable': self.is_searchable(name),
|
||||
})
|
||||
columns.append(
|
||||
{
|
||||
"field": name,
|
||||
"label": self.get_label(name),
|
||||
"hidden": self.is_hidden(name),
|
||||
"sortable": self.is_sortable(name),
|
||||
"searchable": self.is_searchable(name),
|
||||
}
|
||||
)
|
||||
return columns
|
||||
|
||||
def get_vue_active_sorters(self):
|
||||
|
|
@ -2223,8 +2248,7 @@ class Grid:
|
|||
"""
|
||||
sorters = []
|
||||
for sorter in self.active_sorters:
|
||||
sorters.append({'field': sorter['key'],
|
||||
'order': sorter['dir']})
|
||||
sorters.append({"field": sorter["key"], "order": sorter["dir"]})
|
||||
return sorters
|
||||
|
||||
def get_vue_first_sorter(self):
|
||||
|
|
@ -2247,10 +2271,10 @@ class Grid:
|
|||
:returns: The first sorter in format ``[sortkey, sortdir]``,
|
||||
or ``None``.
|
||||
"""
|
||||
if hasattr(self, 'active_sorters'):
|
||||
if hasattr(self, "active_sorters"):
|
||||
if self.active_sorters:
|
||||
sorter = self.active_sorters[0]
|
||||
return [sorter['key'], sorter['dir']]
|
||||
return [sorter["key"], sorter["dir"]]
|
||||
|
||||
elif self.sort_defaults:
|
||||
sorter = self.sort_defaults[0]
|
||||
|
|
@ -2272,20 +2296,22 @@ class Grid:
|
|||
choices = list(filtr.choices)
|
||||
choice_labels = dict(filtr.choices)
|
||||
|
||||
filters.append({
|
||||
'key': filtr.key,
|
||||
'data_type': filtr.data_type,
|
||||
'active': filtr.active,
|
||||
'visible': filtr.active,
|
||||
'verbs': filtr.get_verbs(),
|
||||
'verb_labels': filtr.get_verb_labels(),
|
||||
'valueless_verbs': filtr.get_valueless_verbs(),
|
||||
'verb': filtr.verb,
|
||||
'choices': choices,
|
||||
'choice_labels': choice_labels,
|
||||
'value': filtr.value,
|
||||
'label': filtr.label,
|
||||
})
|
||||
filters.append(
|
||||
{
|
||||
"key": filtr.key,
|
||||
"data_type": filtr.data_type,
|
||||
"active": filtr.active,
|
||||
"visible": filtr.active,
|
||||
"verbs": filtr.get_verbs(),
|
||||
"verb_labels": filtr.get_verb_labels(),
|
||||
"valueless_verbs": filtr.get_valueless_verbs(),
|
||||
"verb": filtr.verb,
|
||||
"choices": choices,
|
||||
"choice_labels": choice_labels,
|
||||
"value": filtr.value,
|
||||
"label": filtr.label,
|
||||
}
|
||||
)
|
||||
return filters
|
||||
|
||||
def object_to_dict(self, obj):
|
||||
|
|
@ -2294,7 +2320,7 @@ class Grid:
|
|||
dct = dict(obj)
|
||||
except TypeError:
|
||||
dct = dict(obj.__dict__)
|
||||
dct.pop('_sa_instance_state', None)
|
||||
dct.pop("_sa_instance_state", None)
|
||||
return dct
|
||||
|
||||
def get_vue_context(self):
|
||||
|
|
@ -2345,7 +2371,7 @@ class Grid:
|
|||
|
||||
# add action urls to each record
|
||||
for action in self.actions:
|
||||
key = f'_action_url_{action.key}'
|
||||
key = f"_action_url_{action.key}"
|
||||
if key not in record:
|
||||
url = action.get_url(original_record, i)
|
||||
if url:
|
||||
|
|
@ -2355,21 +2381,24 @@ class Grid:
|
|||
css_class = self.get_row_class(original_record, record, i)
|
||||
if css_class:
|
||||
# nb. use *string* zero-based index, for js compat
|
||||
row_classes[str(i-1)] = css_class
|
||||
row_classes[str(i - 1)] = css_class
|
||||
|
||||
data.append(record)
|
||||
|
||||
return {
|
||||
'data': data,
|
||||
'row_classes': row_classes,
|
||||
"data": data,
|
||||
"row_classes": row_classes,
|
||||
}
|
||||
|
||||
def get_vue_data(self):
|
||||
""" """
|
||||
warnings.warn("grid.get_vue_data() is deprecated; "
|
||||
"please use grid.get_vue_context() instead",
|
||||
DeprecationWarning, stacklevel=2)
|
||||
return self.get_vue_context()['data']
|
||||
warnings.warn(
|
||||
"grid.get_vue_data() is deprecated; "
|
||||
"please use grid.get_vue_context() instead",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.get_vue_context()["data"]
|
||||
|
||||
def get_row_class(self, obj, data, i):
|
||||
"""
|
||||
|
|
@ -2402,12 +2431,12 @@ class Grid:
|
|||
"""
|
||||
pager = self.pager
|
||||
return {
|
||||
'item_count': pager.item_count,
|
||||
'items_per_page': pager.items_per_page,
|
||||
'page': pager.page,
|
||||
'page_count': pager.page_count,
|
||||
'first_item': pager.first_item,
|
||||
'last_item': pager.last_item,
|
||||
"item_count": pager.item_count,
|
||||
"items_per_page": pager.items_per_page,
|
||||
"page": pager.page,
|
||||
"page_count": pager.page_count,
|
||||
"first_item": pager.first_item,
|
||||
"last_item": pager.last_item,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -2487,15 +2516,15 @@ class GridAction:
|
|||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
request,
|
||||
key,
|
||||
label=None,
|
||||
url=None,
|
||||
target=None,
|
||||
click_handler=None,
|
||||
icon=None,
|
||||
link_class=None,
|
||||
self,
|
||||
request,
|
||||
key,
|
||||
label=None,
|
||||
url=None,
|
||||
target=None,
|
||||
click_handler=None,
|
||||
icon=None,
|
||||
link_class=None,
|
||||
):
|
||||
self.request = request
|
||||
self.config = self.request.wutta_config
|
||||
|
|
@ -2506,7 +2535,7 @@ class GridAction:
|
|||
self.click_handler = click_handler
|
||||
self.label = label or self.app.make_title(key)
|
||||
self.icon = icon or key
|
||||
self.link_class = link_class or ''
|
||||
self.link_class = link_class or ""
|
||||
|
||||
def render_icon_and_label(self):
|
||||
"""
|
||||
|
|
@ -2519,7 +2548,7 @@ class GridAction:
|
|||
self.render_icon(),
|
||||
self.render_label(),
|
||||
]
|
||||
return HTML.literal(' ').join(html)
|
||||
return HTML.literal(" ").join(html)
|
||||
|
||||
def render_icon(self):
|
||||
"""
|
||||
|
|
@ -2535,9 +2564,9 @@ class GridAction:
|
|||
See also :meth:`render_icon_and_label()`.
|
||||
"""
|
||||
if self.request.use_oruga:
|
||||
return HTML.tag('o-icon', icon=self.icon)
|
||||
return HTML.tag("o-icon", icon=self.icon)
|
||||
|
||||
return HTML.tag('i', class_=f'fas fa-{self.icon}')
|
||||
return HTML.tag("i", class_=f"fas fa-{self.icon}")
|
||||
|
||||
def render_label(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -27,9 +27,10 @@ Grid Filters
|
|||
import datetime
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
|
||||
try:
|
||||
from enum import EnumType
|
||||
except ImportError: # pragma: no cover
|
||||
except ImportError: # pragma: no cover
|
||||
# nb. python <= 3.10
|
||||
from enum import EnumMeta as EnumType
|
||||
|
||||
|
|
@ -137,47 +138,48 @@ class GridFilter:
|
|||
|
||||
See also :attr:`default_verb`.
|
||||
"""
|
||||
data_type = 'string'
|
||||
default_verbs = ['equal', 'not_equal']
|
||||
|
||||
data_type = "string"
|
||||
default_verbs = ["equal", "not_equal"]
|
||||
|
||||
default_verb_labels = {
|
||||
'is_any': "is any",
|
||||
'equal': "equal to",
|
||||
'not_equal': "not equal to",
|
||||
'greater_than': "greater than",
|
||||
'greater_equal': "greater than or equal to",
|
||||
'less_than': "less than",
|
||||
'less_equal': "less than or equal to",
|
||||
"is_any": "is any",
|
||||
"equal": "equal to",
|
||||
"not_equal": "not equal to",
|
||||
"greater_than": "greater than",
|
||||
"greater_equal": "greater than or equal to",
|
||||
"less_than": "less than",
|
||||
"less_equal": "less than or equal to",
|
||||
# 'between': "between",
|
||||
'is_true': "is true",
|
||||
'is_false': "is false",
|
||||
'is_false_null': "is false or null",
|
||||
'is_null': "is null",
|
||||
'is_not_null': "is not null",
|
||||
'contains': "contains",
|
||||
'does_not_contain': "does not contain",
|
||||
"is_true": "is true",
|
||||
"is_false": "is false",
|
||||
"is_false_null": "is false or null",
|
||||
"is_null": "is null",
|
||||
"is_not_null": "is not null",
|
||||
"contains": "contains",
|
||||
"does_not_contain": "does not contain",
|
||||
}
|
||||
|
||||
valueless_verbs = [
|
||||
'is_any',
|
||||
'is_true',
|
||||
'is_false',
|
||||
'is_false_null',
|
||||
'is_null',
|
||||
'is_not_null',
|
||||
"is_any",
|
||||
"is_true",
|
||||
"is_false",
|
||||
"is_false_null",
|
||||
"is_null",
|
||||
"is_not_null",
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
request,
|
||||
key,
|
||||
label=None,
|
||||
verbs=None,
|
||||
choices={},
|
||||
default_active=False,
|
||||
default_verb=None,
|
||||
default_value=None,
|
||||
**kwargs,
|
||||
self,
|
||||
request,
|
||||
key,
|
||||
label=None,
|
||||
verbs=None,
|
||||
choices={},
|
||||
default_active=False,
|
||||
default_verb=None,
|
||||
default_value=None,
|
||||
**kwargs,
|
||||
):
|
||||
self.request = request
|
||||
self.key = key
|
||||
|
|
@ -205,12 +207,14 @@ class GridFilter:
|
|||
self.__dict__.update(kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
verb = getattr(self, 'verb', None)
|
||||
return (f"{self.__class__.__name__}("
|
||||
f"key='{self.key}', "
|
||||
f"active={self.active}, "
|
||||
f"verb={repr(verb)}, "
|
||||
f"value={repr(self.value)})")
|
||||
verb = getattr(self, "verb", None)
|
||||
return (
|
||||
f"{self.__class__.__name__}("
|
||||
f"key='{self.key}', "
|
||||
f"active={self.active}, "
|
||||
f"verb={repr(verb)}, "
|
||||
f"value={repr(self.value)})"
|
||||
)
|
||||
|
||||
def get_verbs(self):
|
||||
"""
|
||||
|
|
@ -218,7 +222,7 @@ class GridFilter:
|
|||
"""
|
||||
verbs = None
|
||||
|
||||
if hasattr(self, 'verbs'):
|
||||
if hasattr(self, "verbs"):
|
||||
verbs = self.verbs
|
||||
|
||||
else:
|
||||
|
|
@ -229,13 +233,13 @@ class GridFilter:
|
|||
verbs = list(verbs)
|
||||
|
||||
if self.nullable:
|
||||
if 'is_null' not in verbs:
|
||||
verbs.append('is_null')
|
||||
if 'is_not_null' not in verbs:
|
||||
verbs.append('is_not_null')
|
||||
if "is_null" not in verbs:
|
||||
verbs.append("is_null")
|
||||
if "is_not_null" not in verbs:
|
||||
verbs.append("is_not_null")
|
||||
|
||||
if 'is_any' not in verbs:
|
||||
verbs.append('is_any')
|
||||
if "is_any" not in verbs:
|
||||
verbs.append("is_any")
|
||||
|
||||
return verbs
|
||||
|
||||
|
|
@ -260,10 +264,10 @@ class GridFilter:
|
|||
"""
|
||||
verb = None
|
||||
|
||||
if hasattr(self, 'default_verb'):
|
||||
if hasattr(self, "default_verb"):
|
||||
verb = self.default_verb
|
||||
|
||||
elif hasattr(self, 'verb'):
|
||||
elif hasattr(self, "verb"):
|
||||
verb = self.verb
|
||||
|
||||
if not verb:
|
||||
|
|
@ -291,10 +295,10 @@ class GridFilter:
|
|||
"""
|
||||
if choices:
|
||||
self.choices = self.normalize_choices(choices)
|
||||
self.data_type = 'choice'
|
||||
self.data_type = "choice"
|
||||
else:
|
||||
self.choices = {}
|
||||
self.data_type = 'string'
|
||||
self.data_type = "string"
|
||||
|
||||
def normalize_choices(self, choices):
|
||||
"""
|
||||
|
|
@ -320,22 +324,18 @@ class GridFilter:
|
|||
normalized = choices
|
||||
|
||||
if isinstance(choices, EnumType):
|
||||
normalized = OrderedDict([
|
||||
(member.name, member.value)
|
||||
for member in choices])
|
||||
normalized = OrderedDict(
|
||||
[(member.name, member.value) for member in choices]
|
||||
)
|
||||
|
||||
elif isinstance(choices, OrderedDict):
|
||||
normalized = choices
|
||||
|
||||
elif isinstance(choices, dict):
|
||||
normalized = OrderedDict([
|
||||
(key, choices[key])
|
||||
for key in sorted(choices)])
|
||||
normalized = OrderedDict([(key, choices[key]) for key in sorted(choices)])
|
||||
|
||||
elif isinstance(choices, list):
|
||||
normalized = OrderedDict([
|
||||
(key, key)
|
||||
for key in choices])
|
||||
normalized = OrderedDict([(key, key) for key in choices])
|
||||
|
||||
return normalized
|
||||
|
||||
|
|
@ -358,8 +358,11 @@ class GridFilter:
|
|||
verb = self.verb
|
||||
if not verb:
|
||||
verb = self.get_default_verb()
|
||||
log.warn("missing verb for '%s' filter, will use default verb: %s",
|
||||
self.key, verb)
|
||||
log.warn(
|
||||
"missing verb for '%s' filter, will use default verb: %s",
|
||||
self.key,
|
||||
verb,
|
||||
)
|
||||
|
||||
# only attempt for known verbs
|
||||
if verb not in self.get_verbs():
|
||||
|
|
@ -370,7 +373,7 @@ class GridFilter:
|
|||
value = self.value
|
||||
|
||||
# locate filter method
|
||||
func = getattr(self, f'filter_{verb}', None)
|
||||
func = getattr(self, f"filter_{verb}", None)
|
||||
if not func:
|
||||
raise VerbNotSupported(verb)
|
||||
|
||||
|
|
@ -403,7 +406,7 @@ class AlchemyFilter(GridFilter):
|
|||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
nullable = kwargs.pop('nullable', None)
|
||||
nullable = kwargs.pop("nullable", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.nullable = nullable
|
||||
|
|
@ -441,10 +444,12 @@ class AlchemyFilter(GridFilter):
|
|||
|
||||
# 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,
|
||||
self.model_property != value,
|
||||
))
|
||||
return query.filter(
|
||||
sa.or_(
|
||||
self.model_property == None,
|
||||
self.model_property != value,
|
||||
)
|
||||
)
|
||||
|
||||
def filter_greater_than(self, query, value):
|
||||
"""
|
||||
|
|
@ -502,8 +507,8 @@ class StringAlchemyFilter(AlchemyFilter):
|
|||
|
||||
Subclass of :class:`AlchemyFilter`.
|
||||
"""
|
||||
default_verbs = ['contains', 'does_not_contain',
|
||||
'equal', 'not_equal']
|
||||
|
||||
default_verbs = ["contains", "does_not_contain", "equal", "not_equal"]
|
||||
|
||||
def coerce_value(self, value):
|
||||
""" """
|
||||
|
|
@ -522,8 +527,8 @@ class StringAlchemyFilter(AlchemyFilter):
|
|||
|
||||
criteria = []
|
||||
for val in value.split():
|
||||
val = val.replace('_', r'\_')
|
||||
val = f'%{val}%'
|
||||
val = val.replace("_", r"\_")
|
||||
val = f"%{val}%"
|
||||
criteria.append(self.model_property.ilike(val))
|
||||
|
||||
return query.filter(sa.and_(*criteria))
|
||||
|
|
@ -538,15 +543,13 @@ class StringAlchemyFilter(AlchemyFilter):
|
|||
|
||||
criteria = []
|
||||
for val in value.split():
|
||||
val = val.replace('_', r'\_')
|
||||
val = f'%{val}%'
|
||||
val = val.replace("_", r"\_")
|
||||
val = f"%{val}%"
|
||||
criteria.append(~self.model_property.ilike(val))
|
||||
|
||||
# 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, sa.and_(*criteria)))
|
||||
|
||||
|
||||
class NumericAlchemyFilter(AlchemyFilter):
|
||||
|
|
@ -555,9 +558,15 @@ class NumericAlchemyFilter(AlchemyFilter):
|
|||
|
||||
Subclass of :class:`AlchemyFilter`.
|
||||
"""
|
||||
default_verbs = ['equal', 'not_equal',
|
||||
'greater_than', 'greater_equal',
|
||||
'less_than', 'less_equal']
|
||||
|
||||
default_verbs = [
|
||||
"equal",
|
||||
"not_equal",
|
||||
"greater_than",
|
||||
"greater_equal",
|
||||
"less_than",
|
||||
"less_equal",
|
||||
]
|
||||
|
||||
|
||||
class IntegerAlchemyFilter(NumericAlchemyFilter):
|
||||
|
|
@ -582,26 +591,27 @@ class BooleanAlchemyFilter(AlchemyFilter):
|
|||
|
||||
Subclass of :class:`AlchemyFilter`.
|
||||
"""
|
||||
default_verbs = ['is_true', 'is_false']
|
||||
|
||||
default_verbs = ["is_true", "is_false"]
|
||||
|
||||
def get_verbs(self):
|
||||
""" """
|
||||
|
||||
# get basic verbs from caller, or default list
|
||||
verbs = getattr(self, 'verbs', self.default_verbs)
|
||||
verbs = getattr(self, "verbs", self.default_verbs)
|
||||
if callable(verbs):
|
||||
verbs = verbs()
|
||||
verbs = list(verbs)
|
||||
|
||||
# add some more if column is nullable
|
||||
if self.nullable:
|
||||
for verb in ('is_false_null', 'is_null', 'is_not_null'):
|
||||
for verb in ("is_false_null", "is_null", "is_not_null"):
|
||||
if verb not in verbs:
|
||||
verbs.append(verb)
|
||||
|
||||
# add wildcard
|
||||
if 'is_any' not in verbs:
|
||||
verbs.append('is_any')
|
||||
if "is_any" not in verbs:
|
||||
verbs.append("is_any")
|
||||
|
||||
return verbs
|
||||
|
||||
|
|
@ -629,8 +639,9 @@ class BooleanAlchemyFilter(AlchemyFilter):
|
|||
Filter data with "is false or null" condition. The value is
|
||||
ignored.
|
||||
"""
|
||||
return query.filter(sa.or_(self.model_property == False,
|
||||
self.model_property == None))
|
||||
return query.filter(
|
||||
sa.or_(self.model_property == False, self.model_property == None)
|
||||
)
|
||||
|
||||
|
||||
class DateAlchemyFilter(AlchemyFilter):
|
||||
|
|
@ -640,24 +651,25 @@ class DateAlchemyFilter(AlchemyFilter):
|
|||
|
||||
Subclass of :class:`AlchemyFilter`.
|
||||
"""
|
||||
data_type = 'date'
|
||||
|
||||
data_type = "date"
|
||||
default_verbs = [
|
||||
'equal',
|
||||
'not_equal',
|
||||
'greater_than',
|
||||
'greater_equal',
|
||||
'less_than',
|
||||
'less_equal',
|
||||
"equal",
|
||||
"not_equal",
|
||||
"greater_than",
|
||||
"greater_equal",
|
||||
"less_than",
|
||||
"less_equal",
|
||||
# 'between',
|
||||
]
|
||||
|
||||
default_verb_labels = {
|
||||
'equal': "on",
|
||||
'not_equal': "not on",
|
||||
'greater_than': "after",
|
||||
'greater_equal': "on or after",
|
||||
'less_than': "before",
|
||||
'less_equal': "on or before",
|
||||
"equal": "on",
|
||||
"not_equal": "not on",
|
||||
"greater_than": "after",
|
||||
"greater_equal": "on or after",
|
||||
"less_than": "before",
|
||||
"less_equal": "on or before",
|
||||
# 'between': "between",
|
||||
}
|
||||
|
||||
|
|
@ -668,7 +680,7 @@ class DateAlchemyFilter(AlchemyFilter):
|
|||
return value
|
||||
|
||||
try:
|
||||
dt = datetime.datetime.strptime(value, '%Y-%m-%d')
|
||||
dt = datetime.datetime.strptime(value, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
log.warning("invalid date value: %s", value)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -49,8 +49,8 @@ class WebHandler(GenericHandler):
|
|||
:param resource: :class:`fanstatic:fanstatic.Resource`
|
||||
instance representing an image file or other resource.
|
||||
"""
|
||||
needed = request.environ['fanstatic.needed']
|
||||
url = needed.library_url(resource.library) + '/'
|
||||
needed = request.environ["fanstatic.needed"]
|
||||
url = needed.library_url(resource.library) + "/"
|
||||
if request.script_name:
|
||||
url = request.script_name + url
|
||||
return url + resource.relpath
|
||||
|
|
@ -67,7 +67,7 @@ class WebHandler(GenericHandler):
|
|||
[wuttaweb]
|
||||
favicon_url = http://example.com/favicon.ico
|
||||
"""
|
||||
url = self.config.get('wuttaweb.favicon_url')
|
||||
url = self.config.get("wuttaweb.favicon_url")
|
||||
if url:
|
||||
return url
|
||||
return self.get_fanstatic_url(request, static.favicon)
|
||||
|
|
@ -85,7 +85,7 @@ class WebHandler(GenericHandler):
|
|||
[wuttaweb]
|
||||
header_logo_url = http://example.com/logo.png
|
||||
"""
|
||||
url = self.config.get('wuttaweb.header_logo_url')
|
||||
url = self.config.get("wuttaweb.header_logo_url")
|
||||
if url:
|
||||
return url
|
||||
return self.get_favicon_url(request)
|
||||
|
|
@ -102,7 +102,7 @@ class WebHandler(GenericHandler):
|
|||
[wuttaweb]
|
||||
logo_url = http://example.com/logo.png
|
||||
"""
|
||||
url = self.config.get('wuttaweb.logo_url')
|
||||
url = self.config.get("wuttaweb.logo_url")
|
||||
if url:
|
||||
return url
|
||||
return self.get_fanstatic_url(request, static.logo)
|
||||
|
|
@ -120,16 +120,20 @@ class WebHandler(GenericHandler):
|
|||
|
||||
:returns: Instance of :class:`~wuttaweb.menus.MenuHandler`.
|
||||
"""
|
||||
spec = self.config.get(f'{self.appname}.web.menus.handler.spec')
|
||||
spec = self.config.get(f"{self.appname}.web.menus.handler.spec")
|
||||
if not spec:
|
||||
spec = self.config.get(f'{self.appname}.web.menus.handler_spec')
|
||||
spec = self.config.get(f"{self.appname}.web.menus.handler_spec")
|
||||
if spec:
|
||||
warnings.warn(f"setting '{self.appname}.web.menus.handler_spec' is deprecated; "
|
||||
f"please use '{self.appname}.web.menus.handler.spec' instead",
|
||||
DeprecationWarning)
|
||||
warnings.warn(
|
||||
f"setting '{self.appname}.web.menus.handler_spec' is deprecated; "
|
||||
f"please use '{self.appname}.web.menus.handler.spec' instead",
|
||||
DeprecationWarning,
|
||||
)
|
||||
else:
|
||||
spec = self.config.get(f'{self.appname}.web.menus.handler.default_spec',
|
||||
default='wuttaweb.menus:MenuHandler')
|
||||
spec = self.config.get(
|
||||
f"{self.appname}.web.menus.handler.default_spec",
|
||||
default="wuttaweb.menus:MenuHandler",
|
||||
)
|
||||
factory = self.app.load_object(spec)
|
||||
return factory(self.config)
|
||||
|
||||
|
|
@ -175,13 +179,15 @@ class WebHandler(GenericHandler):
|
|||
handlers.extend(default)
|
||||
|
||||
# configured default, if applicable
|
||||
default = self.config.get(f'{self.config.appname}.web.menus.handler.default_spec')
|
||||
default = self.config.get(
|
||||
f"{self.config.appname}.web.menus.handler.default_spec"
|
||||
)
|
||||
if default and default not in handlers:
|
||||
handlers.append(default)
|
||||
|
||||
# registered via entry points
|
||||
registered = []
|
||||
for Handler in load_entry_points(f'{self.appname}.web.menus').values():
|
||||
for Handler in load_entry_points(f"{self.appname}.web.menus").values():
|
||||
spec = Handler.get_spec()
|
||||
if spec not in handlers:
|
||||
registered.append(spec)
|
||||
|
|
|
|||
|
|
@ -121,13 +121,13 @@ class MenuHandler(GenericHandler):
|
|||
dicts as described in :class:`MenuHandler`.
|
||||
"""
|
||||
return {
|
||||
'title': "People",
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
"title": "People",
|
||||
"type": "menu",
|
||||
"items": [
|
||||
{
|
||||
'title': "All People",
|
||||
'route': 'people',
|
||||
'perm': 'people.list',
|
||||
"title": "All People",
|
||||
"route": "people",
|
||||
"perm": "people.list",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -150,59 +150,63 @@ class MenuHandler(GenericHandler):
|
|||
"""
|
||||
items = []
|
||||
|
||||
if kwargs.get('include_people'):
|
||||
items.extend([
|
||||
{
|
||||
'title': "All People",
|
||||
'route': 'people',
|
||||
'perm': 'people.list',
|
||||
},
|
||||
])
|
||||
if kwargs.get("include_people"):
|
||||
items.extend(
|
||||
[
|
||||
{
|
||||
"title": "All People",
|
||||
"route": "people",
|
||||
"perm": "people.list",
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
items.extend([
|
||||
{
|
||||
'title': "Users",
|
||||
'route': 'users',
|
||||
'perm': 'users.list',
|
||||
},
|
||||
{
|
||||
'title': "Roles",
|
||||
'route': 'roles',
|
||||
'perm': 'roles.list',
|
||||
},
|
||||
{
|
||||
'title': "Permissions",
|
||||
'route': 'permissions',
|
||||
'perm': 'permissions.list',
|
||||
},
|
||||
{'type': 'sep'},
|
||||
{
|
||||
'title': "Email Settings",
|
||||
'route': 'email_settings',
|
||||
'perm': 'email_settings.list',
|
||||
},
|
||||
{'type': 'sep'},
|
||||
{
|
||||
'title': "App Info",
|
||||
'route': 'appinfo',
|
||||
'perm': 'appinfo.list',
|
||||
},
|
||||
{
|
||||
'title': "Raw Settings",
|
||||
'route': 'settings',
|
||||
'perm': 'settings.list',
|
||||
},
|
||||
{
|
||||
'title': "Upgrades",
|
||||
'route': 'upgrades',
|
||||
'perm': 'upgrades.list',
|
||||
},
|
||||
])
|
||||
items.extend(
|
||||
[
|
||||
{
|
||||
"title": "Users",
|
||||
"route": "users",
|
||||
"perm": "users.list",
|
||||
},
|
||||
{
|
||||
"title": "Roles",
|
||||
"route": "roles",
|
||||
"perm": "roles.list",
|
||||
},
|
||||
{
|
||||
"title": "Permissions",
|
||||
"route": "permissions",
|
||||
"perm": "permissions.list",
|
||||
},
|
||||
{"type": "sep"},
|
||||
{
|
||||
"title": "Email Settings",
|
||||
"route": "email_settings",
|
||||
"perm": "email_settings.list",
|
||||
},
|
||||
{"type": "sep"},
|
||||
{
|
||||
"title": "App Info",
|
||||
"route": "appinfo",
|
||||
"perm": "appinfo.list",
|
||||
},
|
||||
{
|
||||
"title": "Raw Settings",
|
||||
"route": "settings",
|
||||
"perm": "settings.list",
|
||||
},
|
||||
{
|
||||
"title": "Upgrades",
|
||||
"route": "upgrades",
|
||||
"perm": "upgrades.list",
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
return {
|
||||
'title': kwargs.get('title', "Admin"),
|
||||
'type': 'menu',
|
||||
'items': items,
|
||||
"title": kwargs.get("title", "Admin"),
|
||||
"type": "menu",
|
||||
"items": items,
|
||||
}
|
||||
|
||||
##############################
|
||||
|
|
@ -227,64 +231,68 @@ class MenuHandler(GenericHandler):
|
|||
final_menus = []
|
||||
for topitem in raw_menus:
|
||||
|
||||
if topitem['allowed']:
|
||||
if topitem["allowed"]:
|
||||
|
||||
if topitem.get('type') == 'link':
|
||||
if topitem.get("type") == "link":
|
||||
final_menus.append(self._make_menu_entry(request, topitem))
|
||||
|
||||
else: # assuming 'menu' type
|
||||
else: # assuming 'menu' type
|
||||
|
||||
menu_items = []
|
||||
for item in topitem['items']:
|
||||
if not item['allowed']:
|
||||
for item in topitem["items"]:
|
||||
if not item["allowed"]:
|
||||
continue
|
||||
|
||||
# nested submenu
|
||||
if item.get('type') == 'menu':
|
||||
if item.get("type") == "menu":
|
||||
submenu_items = []
|
||||
for subitem in item['items']:
|
||||
if subitem['allowed']:
|
||||
submenu_items.append(self._make_menu_entry(request, subitem))
|
||||
menu_items.append({
|
||||
'type': 'submenu',
|
||||
'title': item['title'],
|
||||
'items': submenu_items,
|
||||
'is_menu': True,
|
||||
'is_sep': False,
|
||||
})
|
||||
for subitem in item["items"]:
|
||||
if subitem["allowed"]:
|
||||
submenu_items.append(
|
||||
self._make_menu_entry(request, subitem)
|
||||
)
|
||||
menu_items.append(
|
||||
{
|
||||
"type": "submenu",
|
||||
"title": item["title"],
|
||||
"items": submenu_items,
|
||||
"is_menu": True,
|
||||
"is_sep": False,
|
||||
}
|
||||
)
|
||||
|
||||
elif item.get('type') == 'sep':
|
||||
elif item.get("type") == "sep":
|
||||
# we only want to add a sep, *if* we already have some
|
||||
# menu items (i.e. there is something to separate)
|
||||
# *and* the last menu item is not a sep (avoid doubles)
|
||||
if menu_items and not menu_items[-1]['is_sep']:
|
||||
if menu_items and not menu_items[-1]["is_sep"]:
|
||||
menu_items.append(self._make_menu_entry(request, item))
|
||||
|
||||
else: # standard menu item
|
||||
else: # standard menu item
|
||||
menu_items.append(self._make_menu_entry(request, item))
|
||||
|
||||
# remove final separator if present
|
||||
if menu_items and menu_items[-1]['is_sep']:
|
||||
if menu_items and menu_items[-1]["is_sep"]:
|
||||
menu_items.pop()
|
||||
|
||||
# only add if we wound up with something
|
||||
assert menu_items
|
||||
if menu_items:
|
||||
group = {
|
||||
'type': 'menu',
|
||||
'key': topitem.get('key'),
|
||||
'title': topitem['title'],
|
||||
'items': menu_items,
|
||||
'is_menu': True,
|
||||
'is_link': False,
|
||||
"type": "menu",
|
||||
"key": topitem.get("key"),
|
||||
"title": topitem["title"],
|
||||
"items": menu_items,
|
||||
"is_menu": True,
|
||||
"is_link": False,
|
||||
}
|
||||
|
||||
# topitem w/ no key likely means it did not come
|
||||
# from config but rather explicit definition in
|
||||
# code. so we are free to "invent" a (safe) key
|
||||
# for it, since that is only for editing config
|
||||
if not group['key']:
|
||||
group['key'] = self._make_menu_key(topitem['title'])
|
||||
if not group["key"]:
|
||||
group["key"] = self._make_menu_key(topitem["title"])
|
||||
|
||||
final_menus.append(group)
|
||||
|
||||
|
|
@ -305,7 +313,7 @@ class MenuHandler(GenericHandler):
|
|||
Logic to determine if a given menu item is "allowed" for
|
||||
current user.
|
||||
"""
|
||||
perm = item.get('perm')
|
||||
perm = item.get("perm")
|
||||
if perm:
|
||||
return request.has_perm(perm)
|
||||
return True
|
||||
|
|
@ -317,30 +325,30 @@ class MenuHandler(GenericHandler):
|
|||
"""
|
||||
for topitem in menus:
|
||||
|
||||
if topitem.get('type', 'menu') == 'link':
|
||||
topitem['allowed'] = True
|
||||
if topitem.get("type", "menu") == "link":
|
||||
topitem["allowed"] = True
|
||||
|
||||
elif topitem.get('type', 'menu') == 'menu':
|
||||
topitem['allowed'] = False
|
||||
elif topitem.get("type", "menu") == "menu":
|
||||
topitem["allowed"] = False
|
||||
|
||||
for item in topitem['items']:
|
||||
for item in topitem["items"]:
|
||||
|
||||
if item.get('type') == 'menu':
|
||||
for subitem in item['items']:
|
||||
subitem['allowed'] = self._is_allowed(request, subitem)
|
||||
if item.get("type") == "menu":
|
||||
for subitem in item["items"]:
|
||||
subitem["allowed"] = self._is_allowed(request, subitem)
|
||||
|
||||
item['allowed'] = False
|
||||
for subitem in item['items']:
|
||||
if subitem['allowed'] and subitem.get('type') != 'sep':
|
||||
item['allowed'] = True
|
||||
item["allowed"] = False
|
||||
for subitem in item["items"]:
|
||||
if subitem["allowed"] and subitem.get("type") != "sep":
|
||||
item["allowed"] = True
|
||||
break
|
||||
|
||||
else:
|
||||
item['allowed'] = self._is_allowed(request, item)
|
||||
item["allowed"] = self._is_allowed(request, item)
|
||||
|
||||
for item in topitem['items']:
|
||||
if item['allowed'] and item.get('type') != 'sep':
|
||||
topitem['allowed'] = True
|
||||
for item in topitem["items"]:
|
||||
if item["allowed"] and item.get("type") != "sep":
|
||||
topitem["allowed"] = True
|
||||
break
|
||||
|
||||
def _make_menu_entry(self, request, item):
|
||||
|
|
@ -349,39 +357,39 @@ class MenuHandler(GenericHandler):
|
|||
object, for use in constructing final menu.
|
||||
"""
|
||||
# separator
|
||||
if item.get('type') == 'sep':
|
||||
if item.get("type") == "sep":
|
||||
return {
|
||||
'type': 'sep',
|
||||
'is_menu': False,
|
||||
'is_sep': True,
|
||||
"type": "sep",
|
||||
"is_menu": False,
|
||||
"is_sep": True,
|
||||
}
|
||||
|
||||
# standard menu item
|
||||
entry = {
|
||||
'type': 'item',
|
||||
'title': item['title'],
|
||||
'perm': item.get('perm'),
|
||||
'target': item.get('target'),
|
||||
'is_link': True,
|
||||
'is_menu': False,
|
||||
'is_sep': False,
|
||||
"type": "item",
|
||||
"title": item["title"],
|
||||
"perm": item.get("perm"),
|
||||
"target": item.get("target"),
|
||||
"is_link": True,
|
||||
"is_menu": False,
|
||||
"is_sep": False,
|
||||
}
|
||||
if item.get('route'):
|
||||
entry['route'] = item['route']
|
||||
if item.get("route"):
|
||||
entry["route"] = item["route"]
|
||||
try:
|
||||
entry['url'] = request.route_url(entry['route'])
|
||||
except KeyError: # happens if no such route
|
||||
entry["url"] = request.route_url(entry["route"])
|
||||
except KeyError: # happens if no such route
|
||||
log.warning("invalid route name for menu entry: %s", entry)
|
||||
entry['url'] = entry['route']
|
||||
entry['key'] = entry['route']
|
||||
entry["url"] = entry["route"]
|
||||
entry["key"] = entry["route"]
|
||||
else:
|
||||
if item.get('url'):
|
||||
entry['url'] = item['url']
|
||||
entry['key'] = self._make_menu_key(entry['title'])
|
||||
if item.get("url"):
|
||||
entry["url"] = item["url"]
|
||||
entry["key"] = self._make_menu_key(entry["title"])
|
||||
return entry
|
||||
|
||||
def _make_menu_key(self, value):
|
||||
"""
|
||||
Generate a normalized menu key for the given value.
|
||||
"""
|
||||
return re.sub(r'\W', '', value.lower())
|
||||
return re.sub(r"\W", "", value.lower())
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ def get_basic_session(request, **kwargs):
|
|||
"""
|
||||
Create/get a "basic" Beaker session object.
|
||||
"""
|
||||
kwargs['use_cookies'] = False
|
||||
kwargs["use_cookies"] = False
|
||||
return BeakerSession(request, **kwargs)
|
||||
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ def get_progress_session(request, key, **kwargs):
|
|||
"""
|
||||
Create/get a Beaker session object, to be used for progress.
|
||||
"""
|
||||
kwargs['id'] = f'{request.session.id}.progress.{key}'
|
||||
kwargs["id"] = f"{request.session.id}.progress.{key}"
|
||||
return get_basic_session(request, **kwargs)
|
||||
|
||||
|
||||
|
|
@ -91,7 +91,9 @@ class SessionProgress(ProgressBase):
|
|||
:attr:`success_url`.
|
||||
"""
|
||||
|
||||
def __init__(self, request, key, success_msg=None, success_url=None, error_url=None):
|
||||
def __init__(
|
||||
self, request, key, success_msg=None, success_url=None, error_url=None
|
||||
):
|
||||
self.request = request
|
||||
self.config = self.request.wutta_config
|
||||
self.app = self.config.get_app()
|
||||
|
|
@ -104,24 +106,24 @@ class SessionProgress(ProgressBase):
|
|||
|
||||
def __call__(self, message, maximum):
|
||||
self.clear()
|
||||
self.session['message'] = message
|
||||
self.session['maximum'] = maximum
|
||||
self.session['maximum_display'] = f'{maximum:,d}'
|
||||
self.session['value'] = 0
|
||||
self.session["message"] = message
|
||||
self.session["maximum"] = maximum
|
||||
self.session["maximum_display"] = f"{maximum:,d}"
|
||||
self.session["value"] = 0
|
||||
self.session.save()
|
||||
return self
|
||||
|
||||
def clear(self):
|
||||
""" """
|
||||
self.session.clear()
|
||||
self.session['complete'] = False
|
||||
self.session['error'] = False
|
||||
self.session["complete"] = False
|
||||
self.session["error"] = False
|
||||
self.session.save()
|
||||
|
||||
def update(self, value):
|
||||
""" """
|
||||
self.session.load()
|
||||
self.session['value'] = value
|
||||
self.session["value"] = value
|
||||
self.session.save()
|
||||
|
||||
def handle_error(self, error, error_url=None):
|
||||
|
|
@ -139,9 +141,9 @@ class SessionProgress(ProgressBase):
|
|||
:attr:`error_url` is used.
|
||||
"""
|
||||
self.session.load()
|
||||
self.session['error'] = True
|
||||
self.session['error_msg'] = self.app.render_error(error)
|
||||
self.session['error_url'] = error_url or self.error_url
|
||||
self.session["error"] = True
|
||||
self.session["error_msg"] = self.app.render_error(error)
|
||||
self.session["error_url"] = error_url or self.error_url
|
||||
self.session.save()
|
||||
|
||||
def handle_success(self, success_msg=None, success_url=None):
|
||||
|
|
@ -162,7 +164,7 @@ class SessionProgress(ProgressBase):
|
|||
:attr:`success_url` is used.
|
||||
"""
|
||||
self.session.load()
|
||||
self.session['complete'] = True
|
||||
self.session['success_msg'] = success_msg or self.success_msg
|
||||
self.session['success_url'] = success_url or self.success_url
|
||||
self.session["complete"] = True
|
||||
self.session["success_msg"] = success_msg or self.success_msg
|
||||
self.session["success_url"] = success_url or self.success_url
|
||||
self.session.save()
|
||||
|
|
|
|||
|
|
@ -62,13 +62,13 @@ from fanstatic import Library, Resource
|
|||
|
||||
|
||||
# fanstatic img library
|
||||
img = Library('wuttaweb_img', 'img')
|
||||
favicon = Resource(img, 'favicon.ico')
|
||||
img = Library("wuttaweb_img", "img")
|
||||
favicon = Resource(img, "favicon.ico")
|
||||
# nb. mock out the renderers here, to appease fanstatic
|
||||
logo = Resource(img, 'logo.png', renderer=True)
|
||||
testing = Resource(img, 'testing.png', renderer=True)
|
||||
logo = Resource(img, "logo.png", renderer=True)
|
||||
testing = Resource(img, "testing.png", renderer=True)
|
||||
|
||||
|
||||
# TODO: should consider deprecating this?
|
||||
def includeme(config):
|
||||
config.add_static_view('wuttaweb', 'wuttaweb:static')
|
||||
config.add_static_view("wuttaweb", "wuttaweb:static")
|
||||
|
|
|
|||
|
|
@ -120,32 +120,35 @@ def new_request(event):
|
|||
</script>
|
||||
"""
|
||||
request = event.request
|
||||
config = request.registry.settings['wutta_config']
|
||||
config = request.registry.settings["wutta_config"]
|
||||
app = config.get_app()
|
||||
|
||||
request.wutta_config = config
|
||||
|
||||
def get_referrer(default=None):
|
||||
if request.params.get('referrer'):
|
||||
return request.params['referrer']
|
||||
if request.session.get('referrer'):
|
||||
return request.session.pop('referrer')
|
||||
referrer = getattr(request, 'referrer', None)
|
||||
if (not referrer or referrer == request.current_route_url()
|
||||
or not referrer.startswith(request.host_url)):
|
||||
referrer = default or request.route_url('home')
|
||||
if request.params.get("referrer"):
|
||||
return request.params["referrer"]
|
||||
if request.session.get("referrer"):
|
||||
return request.session.pop("referrer")
|
||||
referrer = getattr(request, "referrer", None)
|
||||
if (
|
||||
not referrer
|
||||
or referrer == request.current_route_url()
|
||||
or not referrer.startswith(request.host_url)
|
||||
):
|
||||
referrer = default or request.route_url("home")
|
||||
return referrer
|
||||
|
||||
request.get_referrer = get_referrer
|
||||
|
||||
def use_oruga(request):
|
||||
spec = config.get('wuttaweb.oruga_detector.spec')
|
||||
spec = config.get("wuttaweb.oruga_detector.spec")
|
||||
if spec:
|
||||
func = app.load_object(spec)
|
||||
return func(request)
|
||||
|
||||
theme = request.registry.settings.get('wuttaweb.theme')
|
||||
if theme == 'butterfly':
|
||||
theme = request.registry.settings.get("wuttaweb.theme")
|
||||
if theme == "butterfly":
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
@ -156,16 +159,18 @@ 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'):
|
||||
if not hasattr(request, "_wuttaweb_registered_components"):
|
||||
request._wuttaweb_registered_components = OrderedDict()
|
||||
|
||||
if tagname in request._wuttaweb_registered_components:
|
||||
log.warning("component with tagname '%s' already registered "
|
||||
"with class '%s' but we are replacing that "
|
||||
"with class '%s'",
|
||||
tagname,
|
||||
request._wuttaweb_registered_components[tagname],
|
||||
classname)
|
||||
log.warning(
|
||||
"component with tagname '%s' already registered "
|
||||
"with class '%s' but we are replacing that "
|
||||
"with class '%s'",
|
||||
tagname,
|
||||
request._wuttaweb_registered_components[tagname],
|
||||
classname,
|
||||
)
|
||||
|
||||
request._wuttaweb_registered_components[tagname] = classname
|
||||
|
||||
|
|
@ -188,9 +193,9 @@ def default_user_getter(request, db_session=None):
|
|||
|
||||
|
||||
def new_request_set_user(
|
||||
event,
|
||||
user_getter=default_user_getter,
|
||||
db_session=None,
|
||||
event,
|
||||
user_getter=default_user_getter,
|
||||
db_session=None,
|
||||
):
|
||||
"""
|
||||
Event hook called when processing a new :term:`request`, for sake
|
||||
|
|
@ -258,32 +263,35 @@ def new_request_set_user(
|
|||
|
||||
"""
|
||||
request = event.request
|
||||
config = request.registry.settings['wutta_config']
|
||||
config = request.registry.settings["wutta_config"]
|
||||
app = config.get_app()
|
||||
auth = app.get_auth_handler()
|
||||
|
||||
# request.user
|
||||
if db_session:
|
||||
user_getter = functools.partial(user_getter, db_session=db_session)
|
||||
request.set_property(user_getter, name='user', reify=True)
|
||||
request.set_property(user_getter, name="user", reify=True)
|
||||
|
||||
# request.is_admin
|
||||
def is_admin(request):
|
||||
return auth.user_is_admin(request.user)
|
||||
|
||||
request.set_property(is_admin, reify=True)
|
||||
|
||||
# request.is_root
|
||||
def is_root(request):
|
||||
if request.is_admin:
|
||||
if request.session.get('is_root', False):
|
||||
if request.session.get("is_root", False):
|
||||
return True
|
||||
return False
|
||||
|
||||
request.set_property(is_root, reify=True)
|
||||
|
||||
# request.user_permissions
|
||||
def user_permissions(request):
|
||||
session = db_session or Session()
|
||||
return auth.get_permissions(session, request.user)
|
||||
|
||||
request.set_property(user_permissions, reify=True)
|
||||
|
||||
# request.has_perm()
|
||||
|
|
@ -293,6 +301,7 @@ def new_request_set_user(
|
|||
if name in request.user_permissions:
|
||||
return True
|
||||
return False
|
||||
|
||||
request.has_perm = has_perm
|
||||
|
||||
# request.has_any_perm()
|
||||
|
|
@ -301,6 +310,7 @@ def new_request_set_user(
|
|||
if request.has_perm(name):
|
||||
return True
|
||||
return False
|
||||
|
||||
request.has_any_perm = has_any_perm
|
||||
|
||||
|
||||
|
|
@ -371,35 +381,36 @@ def before_render(event):
|
|||
allowed to change theme. Only set/relevant if
|
||||
``expose_theme_picker`` is true (see above).
|
||||
"""
|
||||
request = event.get('request') or threadlocal.get_current_request()
|
||||
request = event.get("request") or threadlocal.get_current_request()
|
||||
config = request.wutta_config
|
||||
app = config.get_app()
|
||||
web = app.get_web_handler()
|
||||
|
||||
context = event
|
||||
context['config'] = config
|
||||
context['app'] = app
|
||||
context['web'] = web
|
||||
context['h'] = helpers
|
||||
context['url'] = request.route_url
|
||||
context['json'] = json
|
||||
context['b'] = 'o' if request.use_oruga else 'b' # for buefy
|
||||
context["config"] = config
|
||||
context["app"] = app
|
||||
context["web"] = web
|
||||
context["h"] = helpers
|
||||
context["url"] = request.route_url
|
||||
context["json"] = json
|
||||
context["b"] = "o" if request.use_oruga else "b" # for buefy
|
||||
|
||||
# TODO: this should be avoided somehow, for non-traditional web
|
||||
# apps, esp. "API" web apps. (in the meantime can configure the
|
||||
# app to use NullMenuHandler which avoids most of the overhead.)
|
||||
menus = web.get_menu_handler()
|
||||
context['menus'] = menus.do_make_menus(request)
|
||||
context["menus"] = menus.do_make_menus(request)
|
||||
|
||||
# theme
|
||||
context['theme'] = request.registry.settings.get('wuttaweb.theme', 'default')
|
||||
context['expose_theme_picker'] = config.get_bool('wuttaweb.themes.expose_picker',
|
||||
default=False)
|
||||
if context['expose_theme_picker']:
|
||||
context['available_themes'] = get_available_themes(config)
|
||||
context["theme"] = request.registry.settings.get("wuttaweb.theme", "default")
|
||||
context["expose_theme_picker"] = config.get_bool(
|
||||
"wuttaweb.themes.expose_picker", default=False
|
||||
)
|
||||
if context["expose_theme_picker"]:
|
||||
context["available_themes"] = get_available_themes(config)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
config.add_subscriber(new_request, 'pyramid.events.NewRequest')
|
||||
config.add_subscriber(new_request_set_user, 'pyramid.events.NewRequest')
|
||||
config.add_subscriber(before_render, 'pyramid.events.BeforeRender')
|
||||
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")
|
||||
|
|
|
|||
|
|
@ -45,22 +45,28 @@ class WebTestCase(DataTestCase):
|
|||
def setup_web(self):
|
||||
self.setup_db()
|
||||
self.request = self.make_request()
|
||||
self.pyramid_config = testing.setUp(request=self.request, settings={
|
||||
'wutta_config': self.config,
|
||||
'mako.directories': ['wuttaweb:templates'],
|
||||
'pyramid_deform.template_search_path': 'wuttaweb:templates/deform',
|
||||
})
|
||||
self.pyramid_config = testing.setUp(
|
||||
request=self.request,
|
||||
settings={
|
||||
"wutta_config": self.config,
|
||||
"mako.directories": ["wuttaweb:templates"],
|
||||
"pyramid_deform.template_search_path": "wuttaweb:templates/deform",
|
||||
},
|
||||
)
|
||||
|
||||
# init web
|
||||
self.pyramid_config.include('pyramid_deform')
|
||||
self.pyramid_config.include('pyramid_mako')
|
||||
self.pyramid_config.add_directive('add_wutta_permission_group',
|
||||
'wuttaweb.auth.add_permission_group')
|
||||
self.pyramid_config.add_directive('add_wutta_permission',
|
||||
'wuttaweb.auth.add_permission')
|
||||
self.pyramid_config.add_subscriber('wuttaweb.subscribers.before_render',
|
||||
'pyramid.events.BeforeRender')
|
||||
self.pyramid_config.include('wuttaweb.static')
|
||||
self.pyramid_config.include("pyramid_deform")
|
||||
self.pyramid_config.include("pyramid_mako")
|
||||
self.pyramid_config.add_directive(
|
||||
"add_wutta_permission_group", "wuttaweb.auth.add_permission_group"
|
||||
)
|
||||
self.pyramid_config.add_directive(
|
||||
"add_wutta_permission", "wuttaweb.auth.add_permission"
|
||||
)
|
||||
self.pyramid_config.add_subscriber(
|
||||
"wuttaweb.subscribers.before_render", "pyramid.events.BeforeRender"
|
||||
)
|
||||
self.pyramid_config.include("wuttaweb.static")
|
||||
|
||||
# nb. mock out fanstatic env..good enough for now to avoid errors..
|
||||
needed = fanstatic.init_needed()
|
||||
|
|
@ -69,9 +75,13 @@ class WebTestCase(DataTestCase):
|
|||
# setup new request w/ anonymous user
|
||||
event = MagicMock(request=self.request)
|
||||
subscribers.new_request(event)
|
||||
def user_getter(request, **kwargs): pass
|
||||
subscribers.new_request_set_user(event, db_session=self.session,
|
||||
user_getter=user_getter)
|
||||
|
||||
def user_getter(request, **kwargs):
|
||||
pass
|
||||
|
||||
subscribers.new_request_set_user(
|
||||
event, db_session=self.session, user_getter=user_getter
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
self.teardown_web()
|
||||
|
|
|
|||
|
|
@ -69,8 +69,9 @@ class FieldList(list):
|
|||
i = self.index(field)
|
||||
self.insert(i, newfield)
|
||||
else:
|
||||
log.warning("field '%s' not found, will append new field: %s",
|
||||
field, newfield)
|
||||
log.warning(
|
||||
"field '%s' not found, will append new field: %s", field, newfield
|
||||
)
|
||||
self.append(newfield)
|
||||
|
||||
def insert_after(self, field, newfield):
|
||||
|
|
@ -86,8 +87,9 @@ class FieldList(list):
|
|||
i = self.index(field)
|
||||
self.insert(i + 1, newfield)
|
||||
else:
|
||||
log.warning("field '%s' not found, will append new field: %s",
|
||||
field, newfield)
|
||||
log.warning(
|
||||
"field '%s' not found, will append new field: %s", field, newfield
|
||||
)
|
||||
self.append(newfield)
|
||||
|
||||
def set_sequence(self, fields):
|
||||
|
|
@ -132,18 +134,19 @@ def get_form_data(request):
|
|||
# there is a better way? see also
|
||||
# https://docs.pylonsproject.org/projects/pyramid/en/latest/api/request.html#pyramid.request.Request.is_xhr
|
||||
if not request.POST and (
|
||||
getattr(request, 'is_xhr', False)
|
||||
or getattr(request, 'content_type', None) == 'application/json'):
|
||||
getattr(request, "is_xhr", False)
|
||||
or getattr(request, "content_type", None) == "application/json"
|
||||
):
|
||||
return request.json_body
|
||||
return request.POST
|
||||
|
||||
|
||||
def get_libver(
|
||||
request,
|
||||
key,
|
||||
configured_only=False,
|
||||
default_only=False,
|
||||
prefix='wuttaweb',
|
||||
request,
|
||||
key,
|
||||
configured_only=False,
|
||||
default_only=False,
|
||||
prefix="wuttaweb",
|
||||
):
|
||||
"""
|
||||
Return the appropriate version string for the web resource library
|
||||
|
|
@ -194,88 +197,94 @@ def get_libver(
|
|||
if not default_only:
|
||||
|
||||
# nb. new/preferred setting
|
||||
version = config.get(f'wuttaweb.libver.{key}')
|
||||
version = config.get(f"wuttaweb.libver.{key}")
|
||||
if version:
|
||||
return version
|
||||
|
||||
# fallback to caller-specified prefix
|
||||
if prefix != 'wuttaweb':
|
||||
version = config.get(f'{prefix}.libver.{key}')
|
||||
if prefix != "wuttaweb":
|
||||
version = config.get(f"{prefix}.libver.{key}")
|
||||
if version:
|
||||
warnings.warn(f"config for {prefix}.libver.{key} is deprecated; "
|
||||
f"please set wuttaweb.libver.{key} instead",
|
||||
DeprecationWarning)
|
||||
warnings.warn(
|
||||
f"config for {prefix}.libver.{key} is deprecated; "
|
||||
f"please set wuttaweb.libver.{key} instead",
|
||||
DeprecationWarning,
|
||||
)
|
||||
return version
|
||||
|
||||
if key == 'buefy':
|
||||
if key == "buefy":
|
||||
if not default_only:
|
||||
# nb. old/legacy setting
|
||||
version = config.get(f'{prefix}.buefy_version')
|
||||
version = config.get(f"{prefix}.buefy_version")
|
||||
if version:
|
||||
warnings.warn(f"config for {prefix}.buefy_version is deprecated; "
|
||||
"please set wuttaweb.libver.buefy instead",
|
||||
DeprecationWarning)
|
||||
warnings.warn(
|
||||
f"config for {prefix}.buefy_version is deprecated; "
|
||||
"please set wuttaweb.libver.buefy instead",
|
||||
DeprecationWarning,
|
||||
)
|
||||
return version
|
||||
if not configured_only:
|
||||
return '0.9.25'
|
||||
return "0.9.25"
|
||||
|
||||
elif key == 'buefy.css':
|
||||
elif key == "buefy.css":
|
||||
# nb. this always returns something
|
||||
return get_libver(request, 'buefy',
|
||||
default_only=default_only,
|
||||
configured_only=configured_only)
|
||||
return get_libver(
|
||||
request, "buefy", default_only=default_only, configured_only=configured_only
|
||||
)
|
||||
|
||||
elif key == 'vue':
|
||||
elif key == "vue":
|
||||
if not default_only:
|
||||
# nb. old/legacy setting
|
||||
version = config.get(f'{prefix}.vue_version')
|
||||
version = config.get(f"{prefix}.vue_version")
|
||||
if version:
|
||||
warnings.warn(f"config for {prefix}.vue_version is deprecated; "
|
||||
"please set wuttaweb.libver.vue instead",
|
||||
DeprecationWarning)
|
||||
warnings.warn(
|
||||
f"config for {prefix}.vue_version is deprecated; "
|
||||
"please set wuttaweb.libver.vue instead",
|
||||
DeprecationWarning,
|
||||
)
|
||||
return version
|
||||
if not configured_only:
|
||||
return '2.6.14'
|
||||
return "2.6.14"
|
||||
|
||||
elif key == 'vue_resource':
|
||||
elif key == "vue_resource":
|
||||
if not configured_only:
|
||||
return '1.5.3'
|
||||
return "1.5.3"
|
||||
|
||||
elif key == 'fontawesome':
|
||||
elif key == "fontawesome":
|
||||
if not configured_only:
|
||||
return '5.3.1'
|
||||
return "5.3.1"
|
||||
|
||||
elif key == 'bb_vue':
|
||||
elif key == "bb_vue":
|
||||
if not configured_only:
|
||||
return '3.5.18'
|
||||
return "3.5.18"
|
||||
|
||||
elif key == 'bb_oruga':
|
||||
elif key == "bb_oruga":
|
||||
if not configured_only:
|
||||
return '0.11.4'
|
||||
return "0.11.4"
|
||||
|
||||
elif key in ('bb_oruga_bulma', 'bb_oruga_bulma_css'):
|
||||
elif key in ("bb_oruga_bulma", "bb_oruga_bulma_css"):
|
||||
if not configured_only:
|
||||
return '0.7.3'
|
||||
return "0.7.3"
|
||||
|
||||
elif key == 'bb_fontawesome_svg_core':
|
||||
elif key == "bb_fontawesome_svg_core":
|
||||
if not configured_only:
|
||||
return '7.0.0'
|
||||
return "7.0.0"
|
||||
|
||||
elif key == 'bb_free_solid_svg_icons':
|
||||
elif key == "bb_free_solid_svg_icons":
|
||||
if not configured_only:
|
||||
return '7.0.0'
|
||||
return "7.0.0"
|
||||
|
||||
elif key == 'bb_vue_fontawesome':
|
||||
elif key == "bb_vue_fontawesome":
|
||||
if not configured_only:
|
||||
return '3.1.1'
|
||||
return "3.1.1"
|
||||
|
||||
|
||||
def get_liburl(
|
||||
request,
|
||||
key,
|
||||
configured_only=False,
|
||||
default_only=False,
|
||||
prefix='wuttaweb',
|
||||
request,
|
||||
key,
|
||||
configured_only=False,
|
||||
default_only=False,
|
||||
prefix="wuttaweb",
|
||||
):
|
||||
"""
|
||||
Return the appropriate URL for the web resource library identified
|
||||
|
|
@ -346,100 +355,106 @@ def get_liburl(
|
|||
if not default_only:
|
||||
|
||||
# nb. new/preferred setting
|
||||
url = config.get(f'wuttaweb.liburl.{key}')
|
||||
url = config.get(f"wuttaweb.liburl.{key}")
|
||||
if url:
|
||||
return url
|
||||
|
||||
# fallback to caller-specified prefix
|
||||
url = config.get(f'{prefix}.liburl.{key}')
|
||||
url = config.get(f"{prefix}.liburl.{key}")
|
||||
if url:
|
||||
warnings.warn(f"config for {prefix}.liburl.{key} is deprecated; "
|
||||
f"please set wuttaweb.liburl.{key} instead",
|
||||
DeprecationWarning)
|
||||
warnings.warn(
|
||||
f"config for {prefix}.liburl.{key} is deprecated; "
|
||||
f"please set wuttaweb.liburl.{key} instead",
|
||||
DeprecationWarning,
|
||||
)
|
||||
return url
|
||||
|
||||
if configured_only:
|
||||
return
|
||||
|
||||
version = get_libver(request, key, prefix=prefix,
|
||||
configured_only=False,
|
||||
default_only=default_only)
|
||||
version = get_libver(
|
||||
request, key, prefix=prefix, configured_only=False, default_only=default_only
|
||||
)
|
||||
|
||||
# load fanstatic libcache if configured
|
||||
static = config.get('wuttaweb.static_libcache.module')
|
||||
static = config.get("wuttaweb.static_libcache.module")
|
||||
if not static:
|
||||
static = config.get(f'{prefix}.static_libcache.module')
|
||||
static = config.get(f"{prefix}.static_libcache.module")
|
||||
if static:
|
||||
warnings.warn(f"config for {prefix}.static_libcache.module is deprecated; "
|
||||
"please set wuttaweb.static_libcache.module instead",
|
||||
DeprecationWarning)
|
||||
warnings.warn(
|
||||
f"config for {prefix}.static_libcache.module is deprecated; "
|
||||
"please set wuttaweb.static_libcache.module instead",
|
||||
DeprecationWarning,
|
||||
)
|
||||
if static:
|
||||
static = importlib.import_module(static)
|
||||
needed = request.environ['fanstatic.needed']
|
||||
liburl = needed.library_url(static.libcache) + '/'
|
||||
needed = request.environ["fanstatic.needed"]
|
||||
liburl = needed.library_url(static.libcache) + "/"
|
||||
# nb. add custom url prefix if needed, e.g. /wutta
|
||||
if request.script_name:
|
||||
liburl = request.script_name + liburl
|
||||
|
||||
if key == 'buefy':
|
||||
if static and hasattr(static, 'buefy_js'):
|
||||
if key == "buefy":
|
||||
if static and hasattr(static, "buefy_js"):
|
||||
return liburl + static.buefy_js.relpath
|
||||
return f'https://unpkg.com/buefy@{version}/dist/buefy.min.js'
|
||||
return f"https://unpkg.com/buefy@{version}/dist/buefy.min.js"
|
||||
|
||||
elif key == 'buefy.css':
|
||||
if static and hasattr(static, 'buefy_css'):
|
||||
elif key == "buefy.css":
|
||||
if static and hasattr(static, "buefy_css"):
|
||||
return liburl + static.buefy_css.relpath
|
||||
return f'https://unpkg.com/buefy@{version}/dist/buefy.min.css'
|
||||
return f"https://unpkg.com/buefy@{version}/dist/buefy.min.css"
|
||||
|
||||
elif key == 'vue':
|
||||
if static and hasattr(static, 'vue_js'):
|
||||
elif key == "vue":
|
||||
if static and hasattr(static, "vue_js"):
|
||||
return liburl + static.vue_js.relpath
|
||||
return f'https://unpkg.com/vue@{version}/dist/vue.min.js'
|
||||
return f"https://unpkg.com/vue@{version}/dist/vue.min.js"
|
||||
|
||||
elif key == 'vue_resource':
|
||||
if static and hasattr(static, 'vue_resource_js'):
|
||||
elif key == "vue_resource":
|
||||
if static and hasattr(static, "vue_resource_js"):
|
||||
return liburl + static.vue_resource_js.relpath
|
||||
return f'https://cdn.jsdelivr.net/npm/vue-resource@{version}'
|
||||
return f"https://cdn.jsdelivr.net/npm/vue-resource@{version}"
|
||||
|
||||
elif key == 'fontawesome':
|
||||
if static and hasattr(static, 'fontawesome_js'):
|
||||
elif key == "fontawesome":
|
||||
if static and hasattr(static, "fontawesome_js"):
|
||||
return liburl + static.fontawesome_js.relpath
|
||||
return f'https://use.fontawesome.com/releases/v{version}/js/all.js'
|
||||
return f"https://use.fontawesome.com/releases/v{version}/js/all.js"
|
||||
|
||||
elif key == 'bb_vue':
|
||||
if static and hasattr(static, 'bb_vue_js'):
|
||||
elif key == "bb_vue":
|
||||
if static and hasattr(static, "bb_vue_js"):
|
||||
return liburl + static.bb_vue_js.relpath
|
||||
return f'https://unpkg.com/vue@{version}/dist/vue.esm-browser.prod.js'
|
||||
return f"https://unpkg.com/vue@{version}/dist/vue.esm-browser.prod.js"
|
||||
|
||||
elif key == 'bb_oruga':
|
||||
if static and hasattr(static, 'bb_oruga_js'):
|
||||
elif key == "bb_oruga":
|
||||
if static and hasattr(static, "bb_oruga_js"):
|
||||
return liburl + static.bb_oruga_js.relpath
|
||||
return f'https://unpkg.com/@oruga-ui/oruga-next@{version}/dist/oruga.mjs'
|
||||
return f"https://unpkg.com/@oruga-ui/oruga-next@{version}/dist/oruga.mjs"
|
||||
|
||||
elif key == 'bb_oruga_bulma':
|
||||
if static and hasattr(static, 'bb_oruga_bulma_js'):
|
||||
elif key == "bb_oruga_bulma":
|
||||
if static and hasattr(static, "bb_oruga_bulma_js"):
|
||||
return liburl + static.bb_oruga_bulma_js.relpath
|
||||
return f'https://unpkg.com/@oruga-ui/theme-bulma@{version}/dist/bulma.js'
|
||||
return f"https://unpkg.com/@oruga-ui/theme-bulma@{version}/dist/bulma.js"
|
||||
|
||||
elif key == 'bb_oruga_bulma_css':
|
||||
if static and hasattr(static, 'bb_oruga_bulma_css'):
|
||||
elif key == "bb_oruga_bulma_css":
|
||||
if static and hasattr(static, "bb_oruga_bulma_css"):
|
||||
return liburl + static.bb_oruga_bulma_css.relpath
|
||||
return f'https://unpkg.com/@oruga-ui/theme-bulma@{version}/dist/bulma.css'
|
||||
return f"https://unpkg.com/@oruga-ui/theme-bulma@{version}/dist/bulma.css"
|
||||
|
||||
elif key == 'bb_fontawesome_svg_core':
|
||||
if static and hasattr(static, 'bb_fontawesome_svg_core_js'):
|
||||
elif key == "bb_fontawesome_svg_core":
|
||||
if static and hasattr(static, "bb_fontawesome_svg_core_js"):
|
||||
return liburl + static.bb_fontawesome_svg_core_js.relpath
|
||||
return f'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-svg-core@{version}/+esm'
|
||||
return f"https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-svg-core@{version}/+esm"
|
||||
|
||||
elif key == 'bb_free_solid_svg_icons':
|
||||
if static and hasattr(static, 'bb_free_solid_svg_icons_js'):
|
||||
elif key == "bb_free_solid_svg_icons":
|
||||
if static and hasattr(static, "bb_free_solid_svg_icons_js"):
|
||||
return liburl + static.bb_free_solid_svg_icons_js.relpath
|
||||
return f'https://cdn.jsdelivr.net/npm/@fortawesome/free-solid-svg-icons@{version}/+esm'
|
||||
return f"https://cdn.jsdelivr.net/npm/@fortawesome/free-solid-svg-icons@{version}/+esm"
|
||||
|
||||
elif key == 'bb_vue_fontawesome':
|
||||
if static and hasattr(static, 'bb_vue_fontawesome_js'):
|
||||
elif key == "bb_vue_fontawesome":
|
||||
if static and hasattr(static, "bb_vue_fontawesome_js"):
|
||||
return liburl + static.bb_vue_fontawesome_js.relpath
|
||||
return f'https://cdn.jsdelivr.net/npm/@fortawesome/vue-fontawesome@{version}/+esm'
|
||||
return (
|
||||
f"https://cdn.jsdelivr.net/npm/@fortawesome/vue-fontawesome@{version}/+esm"
|
||||
)
|
||||
|
||||
|
||||
def get_csrf_token(request):
|
||||
|
|
@ -455,7 +470,7 @@ def get_csrf_token(request):
|
|||
return token
|
||||
|
||||
|
||||
def render_csrf_token(request, name='_csrf'):
|
||||
def render_csrf_token(request, name="_csrf"):
|
||||
"""
|
||||
Convenience function, returns CSRF hidden input inside hidden div,
|
||||
e.g.:
|
||||
|
|
@ -480,7 +495,9 @@ def render_csrf_token(request, name='_csrf'):
|
|||
See also :func:`get_csrf_token()`.
|
||||
"""
|
||||
token = get_csrf_token(request)
|
||||
return HTML.tag('div', tags.hidden(name, value=token, id=None), style='display:none;')
|
||||
return HTML.tag(
|
||||
"div", tags.hidden(name, value=token, id=None), style="display:none;"
|
||||
)
|
||||
|
||||
|
||||
def get_model_fields(config, model_class, include_fk=False):
|
||||
|
|
@ -511,13 +528,16 @@ def get_model_fields(config, model_class, include_fk=False):
|
|||
if include_fk:
|
||||
fields = [prop.key for prop in mapper.iterate_properties]
|
||||
else:
|
||||
fields = [prop.key for prop in mapper.iterate_properties
|
||||
if not prop_is_fk(mapper, prop)]
|
||||
fields = [
|
||||
prop.key
|
||||
for prop in mapper.iterate_properties
|
||||
if not prop_is_fk(mapper, prop)
|
||||
]
|
||||
|
||||
# nb. we never want the continuum 'versions' prop
|
||||
app = config.get_app()
|
||||
if app.continuum_is_enabled() and 'versions' in fields:
|
||||
fields.remove('versions')
|
||||
if app.continuum_is_enabled() and "versions" in fields:
|
||||
fields.remove("versions")
|
||||
|
||||
return fields
|
||||
|
||||
|
|
@ -599,6 +619,7 @@ def make_json_safe(value, key=None, warn=True):
|
|||
# theme functions
|
||||
##############################
|
||||
|
||||
|
||||
def get_available_themes(config):
|
||||
"""
|
||||
Returns the official list of theme names which are available for
|
||||
|
|
@ -621,16 +642,17 @@ def get_available_themes(config):
|
|||
:param config: App :term:`config object`.
|
||||
"""
|
||||
# get available list from config, if it has one
|
||||
available = config.get_list('wuttaweb.themes.keys',
|
||||
default=['default', 'butterfly'])
|
||||
available = config.get_list(
|
||||
"wuttaweb.themes.keys", default=["default", "butterfly"]
|
||||
)
|
||||
|
||||
# sort the list by name
|
||||
available.sort()
|
||||
|
||||
# make default theme the first option
|
||||
if 'default' in available:
|
||||
available.remove('default')
|
||||
available.insert(0, 'default')
|
||||
if "default" in available:
|
||||
available.remove("default")
|
||||
available.insert(0, "default")
|
||||
|
||||
return available
|
||||
|
||||
|
|
@ -663,7 +685,7 @@ def get_effective_theme(config, theme=None, session=None):
|
|||
|
||||
if not theme:
|
||||
with app.short_session(session=session) as s:
|
||||
theme = app.get_setting(s, 'wuttaweb.theme') or 'default'
|
||||
theme = app.get_setting(s, "wuttaweb.theme") or "default"
|
||||
|
||||
# confirm requested theme is available
|
||||
available = get_available_themes(config)
|
||||
|
|
@ -701,8 +723,9 @@ def get_theme_template_path(config, theme=None, session=None):
|
|||
:returns: Path on disk to theme template folder.
|
||||
"""
|
||||
theme = get_effective_theme(config, theme=theme, session=session)
|
||||
theme_path = config.get(f'wuttaweb.theme.{theme}',
|
||||
default=f'wuttaweb:templates/themes/{theme}')
|
||||
theme_path = config.get(
|
||||
f"wuttaweb.theme.{theme}", default=f"wuttaweb:templates/themes/{theme}"
|
||||
)
|
||||
return resource_path(theme_path)
|
||||
|
||||
|
||||
|
|
@ -731,7 +754,7 @@ 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("/menu.mako")
|
||||
lookup = renderer.lookup
|
||||
|
||||
# overwrite first entry in lookup's directory list
|
||||
|
|
@ -742,7 +765,7 @@ def set_app_theme(request, theme, session=None):
|
|||
|
||||
# persist current theme in db settings
|
||||
with app.short_session(session=session) as s:
|
||||
app.save_setting(s, 'wuttaweb.theme', theme)
|
||||
app.save_setting(s, "wuttaweb.theme", theme)
|
||||
|
||||
# and cache in live app settings
|
||||
request.registry.settings['wuttaweb.theme'] = theme
|
||||
request.registry.settings["wuttaweb.theme"] = theme
|
||||
|
|
|
|||
|
|
@ -35,4 +35,4 @@ from .master import MasterView
|
|||
|
||||
|
||||
def includeme(config):
|
||||
config.include('wuttaweb.views.essential')
|
||||
config.include("wuttaweb.views.essential")
|
||||
|
|
|
|||
|
|
@ -54,28 +54,32 @@ class AuthView(View):
|
|||
# nb. redirect to /setup if no users exist
|
||||
user = session.query(model.User).first()
|
||||
if not user:
|
||||
return self.redirect(self.request.route_url('setup'))
|
||||
return self.redirect(self.request.route_url("setup"))
|
||||
|
||||
referrer = self.request.get_referrer()
|
||||
|
||||
# redirect if already logged in
|
||||
if self.request.user:
|
||||
self.request.session.flash(f"{self.request.user} is already logged in", 'error')
|
||||
self.request.session.flash(
|
||||
f"{self.request.user} is already logged in", "error"
|
||||
)
|
||||
return self.redirect(referrer)
|
||||
|
||||
form = self.make_form(schema=self.login_make_schema(),
|
||||
align_buttons_right=True,
|
||||
show_button_cancel=False,
|
||||
show_button_reset=True,
|
||||
button_label_submit="Login",
|
||||
button_icon_submit='user')
|
||||
form = self.make_form(
|
||||
schema=self.login_make_schema(),
|
||||
align_buttons_right=True,
|
||||
show_button_cancel=False,
|
||||
show_button_reset=True,
|
||||
button_label_submit="Login",
|
||||
button_icon_submit="user",
|
||||
)
|
||||
|
||||
# validate basic form data (sanity check)
|
||||
data = form.validate()
|
||||
if data:
|
||||
|
||||
# truly validate user credentials
|
||||
user = auth.authenticate_user(session, data['username'], data['password'])
|
||||
user = auth.authenticate_user(session, data["username"], data["password"])
|
||||
if user:
|
||||
|
||||
# okay now they're truly logged in
|
||||
|
|
@ -83,11 +87,11 @@ class AuthView(View):
|
|||
return self.redirect(referrer, headers=headers)
|
||||
|
||||
else:
|
||||
self.request.session.flash("Invalid user credentials", 'error')
|
||||
self.request.session.flash("Invalid user credentials", "error")
|
||||
|
||||
return {
|
||||
'index_title': self.app.get_title(),
|
||||
'form': form,
|
||||
"index_title": self.app.get_title(),
|
||||
"form": form,
|
||||
# TODO
|
||||
# 'referrer': referrer,
|
||||
}
|
||||
|
|
@ -99,19 +103,29 @@ class AuthView(View):
|
|||
# specify the ref attribute. this is needed for autofocus and
|
||||
# keydown behavior for login form.
|
||||
|
||||
schema.add(colander.SchemaNode(
|
||||
colander.String(),
|
||||
name='username',
|
||||
widget=widgets.TextInputWidget(attributes={
|
||||
'ref': 'username',
|
||||
})))
|
||||
schema.add(
|
||||
colander.SchemaNode(
|
||||
colander.String(),
|
||||
name="username",
|
||||
widget=widgets.TextInputWidget(
|
||||
attributes={
|
||||
"ref": "username",
|
||||
}
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
schema.add(colander.SchemaNode(
|
||||
colander.String(),
|
||||
name='password',
|
||||
widget=widgets.PasswordWidget(attributes={
|
||||
'ref': 'password',
|
||||
})))
|
||||
schema.add(
|
||||
colander.SchemaNode(
|
||||
colander.String(),
|
||||
name="password",
|
||||
widget=widgets.PasswordWidget(
|
||||
attributes={
|
||||
"ref": "password",
|
||||
}
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
return schema
|
||||
|
||||
|
|
@ -138,7 +152,7 @@ class AuthView(View):
|
|||
# otherwise redirect to referrer, with 'login' page as fallback
|
||||
# TODO: should call request.get_referrer()
|
||||
# referrer = self.request.get_referrer(default=self.request.route_url('login'))
|
||||
referrer = self.request.route_url('login')
|
||||
referrer = self.request.route_url("login")
|
||||
return self.redirect(referrer, headers=headers)
|
||||
|
||||
def change_password(self):
|
||||
|
|
@ -155,46 +169,55 @@ class AuthView(View):
|
|||
* template: ``/auth/change_password.mako``
|
||||
"""
|
||||
if not self.request.user:
|
||||
return self.redirect(self.request.route_url('home'))
|
||||
return self.redirect(self.request.route_url("home"))
|
||||
|
||||
if self.request.user.prevent_edit:
|
||||
raise self.forbidden()
|
||||
|
||||
form = self.make_form(schema=self.change_password_make_schema(),
|
||||
show_button_cancel=False,
|
||||
show_button_reset=True)
|
||||
form = self.make_form(
|
||||
schema=self.change_password_make_schema(),
|
||||
show_button_cancel=False,
|
||||
show_button_reset=True,
|
||||
)
|
||||
|
||||
data = form.validate()
|
||||
if data:
|
||||
auth = self.app.get_auth_handler()
|
||||
auth.set_user_password(self.request.user, data['new_password'])
|
||||
auth.set_user_password(self.request.user, data["new_password"])
|
||||
self.request.session.flash("Your password has been changed.")
|
||||
# TODO: should use request.get_referrer() instead
|
||||
referrer = self.request.route_url('home')
|
||||
referrer = self.request.route_url("home")
|
||||
return self.redirect(referrer)
|
||||
|
||||
return {'index_title': str(self.request.user),
|
||||
'form': form}
|
||||
return {"index_title": str(self.request.user), "form": form}
|
||||
|
||||
def change_password_make_schema(self):
|
||||
""" """
|
||||
schema = colander.Schema()
|
||||
|
||||
schema.add(colander.SchemaNode(
|
||||
colander.String(),
|
||||
name='current_password',
|
||||
widget=widgets.PasswordWidget(),
|
||||
validator=self.change_password_validate_current_password))
|
||||
schema.add(
|
||||
colander.SchemaNode(
|
||||
colander.String(),
|
||||
name="current_password",
|
||||
widget=widgets.PasswordWidget(),
|
||||
validator=self.change_password_validate_current_password,
|
||||
)
|
||||
)
|
||||
|
||||
# nb. must use different widget for Vue 3 + Oruga
|
||||
widget = (widgets.WuttaCheckedPasswordWidget()
|
||||
if self.request.use_oruga
|
||||
else widgets.CheckedPasswordWidget())
|
||||
schema.add(colander.SchemaNode(
|
||||
colander.String(),
|
||||
name='new_password',
|
||||
widget=widget,
|
||||
validator=self.change_password_validate_new_password))
|
||||
widget = (
|
||||
widgets.WuttaCheckedPasswordWidget()
|
||||
if self.request.use_oruga
|
||||
else widgets.CheckedPasswordWidget()
|
||||
)
|
||||
schema.add(
|
||||
colander.SchemaNode(
|
||||
colander.String(),
|
||||
name="new_password",
|
||||
widget=widget,
|
||||
validator=self.change_password_validate_new_password,
|
||||
)
|
||||
)
|
||||
|
||||
return schema
|
||||
|
||||
|
|
@ -220,14 +243,16 @@ class AuthView(View):
|
|||
|
||||
See also :meth:`stop_root()`.
|
||||
"""
|
||||
if self.request.method != 'POST':
|
||||
if self.request.method != "POST":
|
||||
raise self.forbidden()
|
||||
|
||||
if not self.request.is_admin:
|
||||
raise self.forbidden()
|
||||
|
||||
self.request.session['is_root'] = True
|
||||
self.request.session.flash("You have been elevated to 'root' and now have full system access")
|
||||
self.request.session["is_root"] = True
|
||||
self.request.session.flash(
|
||||
"You have been elevated to 'root' and now have full system access"
|
||||
)
|
||||
|
||||
url = self.request.get_referrer()
|
||||
return self.redirect(url)
|
||||
|
|
@ -240,13 +265,13 @@ class AuthView(View):
|
|||
|
||||
See also :meth:`become_root()`.
|
||||
"""
|
||||
if self.request.method != 'POST':
|
||||
if self.request.method != "POST":
|
||||
raise self.forbidden()
|
||||
|
||||
if not self.request.is_admin:
|
||||
raise self.forbidden()
|
||||
|
||||
self.request.session['is_root'] = False
|
||||
self.request.session["is_root"] = False
|
||||
self.request.session.flash("Your normal system access has been restored")
|
||||
|
||||
url = self.request.get_referrer()
|
||||
|
|
@ -260,39 +285,37 @@ class AuthView(View):
|
|||
def _auth_defaults(cls, config):
|
||||
|
||||
# login
|
||||
config.add_route('login', '/login')
|
||||
config.add_view(cls, attr='login',
|
||||
route_name='login',
|
||||
renderer='/auth/login.mako')
|
||||
config.add_route("login", "/login")
|
||||
config.add_view(
|
||||
cls, attr="login", route_name="login", renderer="/auth/login.mako"
|
||||
)
|
||||
|
||||
# logout
|
||||
config.add_route('logout', '/logout')
|
||||
config.add_view(cls, attr='logout',
|
||||
route_name='logout')
|
||||
config.add_route("logout", "/logout")
|
||||
config.add_view(cls, attr="logout", route_name="logout")
|
||||
|
||||
# change password
|
||||
config.add_route('change_password', '/change-password')
|
||||
config.add_view(cls, attr='change_password',
|
||||
route_name='change_password',
|
||||
renderer='/auth/change_password.mako')
|
||||
config.add_route("change_password", "/change-password")
|
||||
config.add_view(
|
||||
cls,
|
||||
attr="change_password",
|
||||
route_name="change_password",
|
||||
renderer="/auth/change_password.mako",
|
||||
)
|
||||
|
||||
# become root
|
||||
config.add_route('become_root', '/root/yes',
|
||||
request_method='POST')
|
||||
config.add_view(cls, attr='become_root',
|
||||
route_name='become_root')
|
||||
config.add_route("become_root", "/root/yes", request_method="POST")
|
||||
config.add_view(cls, attr="become_root", route_name="become_root")
|
||||
|
||||
# stop root
|
||||
config.add_route('stop_root', '/root/no',
|
||||
request_method='POST')
|
||||
config.add_view(cls, attr='stop_root',
|
||||
route_name='stop_root')
|
||||
config.add_route("stop_root", "/root/no", request_method="POST")
|
||||
config.add_view(cls, attr="stop_root", route_name="stop_root")
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
base = globals()
|
||||
|
||||
AuthView = kwargs.get('AuthView', base['AuthView'])
|
||||
AuthView = kwargs.get("AuthView", base["AuthView"])
|
||||
AuthView.defaults(config)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -169,4 +169,4 @@ class View:
|
|||
|
||||
:returns: A :term:`response` with JSON content type.
|
||||
"""
|
||||
return render_to_response('json', context, request=self.request)
|
||||
return render_to_response("json", context, request=self.request)
|
||||
|
|
|
|||
|
|
@ -52,18 +52,18 @@ class BatchMasterView(MasterView):
|
|||
"""
|
||||
|
||||
labels = {
|
||||
'id': "Batch ID",
|
||||
'status_code': "Status",
|
||||
"id": "Batch ID",
|
||||
"status_code": "Status",
|
||||
}
|
||||
|
||||
sort_defaults = ('id', 'desc')
|
||||
sort_defaults = ("id", "desc")
|
||||
|
||||
has_rows = True
|
||||
rows_title = "Batch Rows"
|
||||
rows_sort_defaults = 'sequence'
|
||||
rows_sort_defaults = "sequence"
|
||||
|
||||
row_labels = {
|
||||
'status_code': "Status",
|
||||
"status_code": "Status",
|
||||
}
|
||||
|
||||
def __init__(self, request, context=None):
|
||||
|
|
@ -89,7 +89,7 @@ class BatchMasterView(MasterView):
|
|||
batches.
|
||||
"""
|
||||
templates = super().get_fallback_templates(template)
|
||||
templates.insert(0, f'/batch/{template}.mako')
|
||||
templates.insert(0, f"/batch/{template}.mako")
|
||||
return templates
|
||||
|
||||
def render_to_response(self, template, context):
|
||||
|
|
@ -105,16 +105,19 @@ class BatchMasterView(MasterView):
|
|||
* ``why_not_execute`` - text of reason (if any) not to execute batch
|
||||
* ``execution_described`` - HTML (rendered from markdown) describing batch execution
|
||||
"""
|
||||
if template == 'view':
|
||||
batch = context['instance']
|
||||
context['batch'] = batch
|
||||
context['batch_handler'] = self.batch_handler
|
||||
context['why_not_execute'] = self.batch_handler.why_not_execute(batch)
|
||||
if template == "view":
|
||||
batch = context["instance"]
|
||||
context["batch"] = batch
|
||||
context["batch_handler"] = self.batch_handler
|
||||
context["why_not_execute"] = self.batch_handler.why_not_execute(batch)
|
||||
|
||||
description = (self.batch_handler.describe_execution(batch)
|
||||
or "Handler does not say! Your guess is as good as mine.")
|
||||
context['execution_described'] = markdown.markdown(
|
||||
description, extensions=['fenced_code', 'codehilite'])
|
||||
description = (
|
||||
self.batch_handler.describe_execution(batch)
|
||||
or "Handler does not say! Your guess is as good as mine."
|
||||
)
|
||||
context["execution_described"] = markdown.markdown(
|
||||
description, extensions=["fenced_code", "codehilite"]
|
||||
)
|
||||
|
||||
return super().render_to_response(template, context)
|
||||
|
||||
|
|
@ -125,24 +128,27 @@ class BatchMasterView(MasterView):
|
|||
|
||||
# created_by
|
||||
CreatedBy = orm.aliased(model.User)
|
||||
g.set_joiner('created_by',
|
||||
lambda q: q.join(CreatedBy,
|
||||
CreatedBy.uuid == self.model_class.created_by_uuid))
|
||||
g.set_sorter('created_by', CreatedBy.username)
|
||||
g.set_joiner(
|
||||
"created_by",
|
||||
lambda q: q.join(
|
||||
CreatedBy, CreatedBy.uuid == self.model_class.created_by_uuid
|
||||
),
|
||||
)
|
||||
g.set_sorter("created_by", CreatedBy.username)
|
||||
# g.set_filter('created_by', CreatedBy.username, label="Created By Username")
|
||||
|
||||
# id
|
||||
g.set_renderer('id', self.render_batch_id)
|
||||
g.set_link('id')
|
||||
g.set_renderer("id", self.render_batch_id)
|
||||
g.set_link("id")
|
||||
|
||||
# description
|
||||
g.set_link('description')
|
||||
g.set_link("description")
|
||||
|
||||
def render_batch_id(self, batch, key, value):
|
||||
""" """
|
||||
if value:
|
||||
batch_id = int(value)
|
||||
return f'{batch_id:08d}'
|
||||
return f"{batch_id:08d}"
|
||||
|
||||
def get_instance_title(self, batch):
|
||||
""" """
|
||||
|
|
@ -157,55 +163,55 @@ class BatchMasterView(MasterView):
|
|||
|
||||
# id
|
||||
if self.creating:
|
||||
f.remove('id')
|
||||
f.remove("id")
|
||||
else:
|
||||
f.set_readonly('id')
|
||||
f.set_widget('id', BatchIdWidget())
|
||||
f.set_readonly("id")
|
||||
f.set_widget("id", BatchIdWidget())
|
||||
|
||||
# notes
|
||||
f.set_widget('notes', 'notes')
|
||||
f.set_widget("notes", "notes")
|
||||
|
||||
# rows
|
||||
f.remove('rows')
|
||||
f.remove("rows")
|
||||
if self.creating:
|
||||
f.remove('row_count')
|
||||
f.remove("row_count")
|
||||
else:
|
||||
f.set_readonly('row_count')
|
||||
f.set_readonly("row_count")
|
||||
|
||||
# status
|
||||
f.remove('status_text')
|
||||
f.remove("status_text")
|
||||
if self.creating:
|
||||
f.remove('status_code')
|
||||
f.remove("status_code")
|
||||
else:
|
||||
f.set_readonly('status_code')
|
||||
f.set_readonly("status_code")
|
||||
|
||||
# created
|
||||
if self.creating:
|
||||
f.remove('created')
|
||||
f.remove("created")
|
||||
else:
|
||||
f.set_readonly('created')
|
||||
f.set_readonly("created")
|
||||
|
||||
# created_by
|
||||
f.remove('created_by_uuid')
|
||||
f.remove("created_by_uuid")
|
||||
if self.creating:
|
||||
f.remove('created_by')
|
||||
f.remove("created_by")
|
||||
else:
|
||||
f.set_node('created_by', UserRef(self.request))
|
||||
f.set_readonly('created_by')
|
||||
f.set_node("created_by", UserRef(self.request))
|
||||
f.set_readonly("created_by")
|
||||
|
||||
# executed
|
||||
if self.creating or not batch.executed:
|
||||
f.remove('executed')
|
||||
f.remove("executed")
|
||||
else:
|
||||
f.set_readonly('executed')
|
||||
f.set_readonly("executed")
|
||||
|
||||
# executed_by
|
||||
f.remove('executed_by_uuid')
|
||||
f.remove("executed_by_uuid")
|
||||
if self.creating or not batch.executed:
|
||||
f.remove('executed_by')
|
||||
f.remove("executed_by")
|
||||
else:
|
||||
f.set_node('executed_by', UserRef(self.request))
|
||||
f.set_readonly('executed_by')
|
||||
f.set_node("executed_by", UserRef(self.request))
|
||||
f.set_readonly("executed_by")
|
||||
|
||||
def objectify(self, form, **kwargs):
|
||||
"""
|
||||
|
|
@ -226,12 +232,16 @@ 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 = 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
|
||||
kw["created_by"] = self.request.user
|
||||
|
||||
# plus caller can override anything
|
||||
kw.update(kwargs)
|
||||
|
|
@ -252,15 +262,19 @@ class BatchMasterView(MasterView):
|
|||
"""
|
||||
# just view batch if should not populate
|
||||
if not self.batch_handler.should_populate(batch):
|
||||
return self.redirect(self.get_action_url('view', batch))
|
||||
return self.redirect(self.get_action_url("view", batch))
|
||||
|
||||
# setup thread to populate batch
|
||||
route_prefix = self.get_route_prefix()
|
||||
key = f'{route_prefix}.populate'
|
||||
progress = self.make_progress(key, success_url=self.get_action_url('view', batch))
|
||||
thread = threading.Thread(target=self.populate_thread,
|
||||
args=(batch.uuid,),
|
||||
kwargs=dict(progress=progress))
|
||||
key = f"{route_prefix}.populate"
|
||||
progress = self.make_progress(
|
||||
key, success_url=self.get_action_url("view", batch)
|
||||
)
|
||||
thread = threading.Thread(
|
||||
target=self.populate_thread,
|
||||
args=(batch.uuid,),
|
||||
kwargs=dict(progress=progress),
|
||||
)
|
||||
|
||||
# start thread and show progress page
|
||||
thread.start()
|
||||
|
|
@ -316,9 +330,12 @@ class BatchMasterView(MasterView):
|
|||
|
||||
except Exception as error:
|
||||
session.rollback()
|
||||
log.warning("failed to populate %s: %s",
|
||||
self.get_model_title(), batch,
|
||||
exc_info=True)
|
||||
log.warning(
|
||||
"failed to populate %s: %s",
|
||||
self.get_model_title(),
|
||||
batch,
|
||||
exc_info=True,
|
||||
)
|
||||
if progress:
|
||||
progress.handle_error(error)
|
||||
|
||||
|
|
@ -351,9 +368,9 @@ class BatchMasterView(MasterView):
|
|||
self.batch_handler.do_execute(batch, self.request.user)
|
||||
except Exception as error:
|
||||
log.warning("failed to execute batch: %s", batch, exc_info=True)
|
||||
self.request.session.flash(f"Execution failed!: {error}", 'error')
|
||||
self.request.session.flash(f"Execution failed!: {error}", "error")
|
||||
|
||||
return self.redirect(self.get_action_url('view', batch))
|
||||
return self.redirect(self.get_action_url("view", batch))
|
||||
|
||||
##############################
|
||||
# row methods
|
||||
|
|
@ -362,7 +379,7 @@ class BatchMasterView(MasterView):
|
|||
@classmethod
|
||||
def get_row_model_class(cls):
|
||||
""" """
|
||||
if hasattr(cls, 'row_model_class'):
|
||||
if hasattr(cls, "row_model_class"):
|
||||
return cls.row_model_class
|
||||
|
||||
Batch = cls.get_model_class()
|
||||
|
|
@ -375,17 +392,16 @@ class BatchMasterView(MasterView):
|
|||
data.
|
||||
"""
|
||||
BatchRow = self.get_row_model_class()
|
||||
query = self.Session.query(BatchRow)\
|
||||
.filter(BatchRow.batch == batch)
|
||||
query = self.Session.query(BatchRow).filter(BatchRow.batch == batch)
|
||||
return query
|
||||
|
||||
def configure_row_grid(self, g):
|
||||
""" """
|
||||
super().configure_row_grid(g)
|
||||
|
||||
g.set_label('sequence', "Seq.", column_only=True)
|
||||
g.set_label("sequence", "Seq.", column_only=True)
|
||||
|
||||
g.set_renderer('status_code', self.render_row_status)
|
||||
g.set_renderer("status_code", self.render_row_status)
|
||||
|
||||
def render_row_status(self, row, key, value):
|
||||
""" """
|
||||
|
|
@ -409,12 +425,17 @@ class BatchMasterView(MasterView):
|
|||
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}")
|
||||
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}"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -58,15 +58,15 @@ class CommonView(View):
|
|||
# nb. redirect to /setup if no users exist
|
||||
user = session.query(model.User).first()
|
||||
if not user:
|
||||
return self.redirect(self.request.route_url('setup'))
|
||||
return self.redirect(self.request.route_url("setup"))
|
||||
|
||||
# maybe auto-redirect anons to login
|
||||
if not self.request.user:
|
||||
if self.config.get_bool('wuttaweb.home_redirect_to_login'):
|
||||
return self.redirect(self.request.route_url('login'))
|
||||
if self.config.get_bool("wuttaweb.home_redirect_to_login"):
|
||||
return self.redirect(self.request.route_url("login"))
|
||||
|
||||
return {
|
||||
'index_title': self.app.get_title(),
|
||||
"index_title": self.app.get_title(),
|
||||
}
|
||||
|
||||
def forbidden_view(self):
|
||||
|
|
@ -75,7 +75,7 @@ class CommonView(View):
|
|||
|
||||
Template: ``/forbidden.mako``
|
||||
"""
|
||||
return {'index_title': self.app.get_title()}
|
||||
return {"index_title": self.app.get_title()}
|
||||
|
||||
def notfound_view(self):
|
||||
"""
|
||||
|
|
@ -83,7 +83,7 @@ class CommonView(View):
|
|||
|
||||
Template: ``/notfound.mako``
|
||||
"""
|
||||
return {'index_title': self.app.get_title()}
|
||||
return {"index_title": self.app.get_title()}
|
||||
|
||||
def feedback(self):
|
||||
""" """
|
||||
|
|
@ -96,46 +96,45 @@ class CommonView(View):
|
|||
if not form.validate():
|
||||
# TODO: native Form class should better expose error(s)
|
||||
dform = form.get_deform()
|
||||
return {'error': str(dform.error)}
|
||||
return {"error": str(dform.error)}
|
||||
|
||||
# build email template context
|
||||
context = dict(form.validated)
|
||||
if context['user_uuid']:
|
||||
context['user'] = session.get(model.User, context['user_uuid'])
|
||||
context['user_url'] = self.request.route_url('users.view', uuid=context['user_uuid'])
|
||||
context['client_ip'] = self.request.client_addr
|
||||
if context["user_uuid"]:
|
||||
context["user"] = session.get(model.User, context["user_uuid"])
|
||||
context["user_url"] = self.request.route_url(
|
||||
"users.view", uuid=context["user_uuid"]
|
||||
)
|
||||
context["client_ip"] = self.request.client_addr
|
||||
|
||||
# send email
|
||||
try:
|
||||
self.feedback_send(context)
|
||||
except Exception as error:
|
||||
log.warning("failed to send feedback email", exc_info=True)
|
||||
return {'error': str(error) or error.__class__.__name__}
|
||||
return {"error": str(error) or error.__class__.__name__}
|
||||
|
||||
return {'ok': True}
|
||||
return {"ok": True}
|
||||
|
||||
def feedback_make_schema(self):
|
||||
""" """
|
||||
schema = colander.Schema()
|
||||
|
||||
schema.add(colander.SchemaNode(colander.String(),
|
||||
name='referrer'))
|
||||
schema.add(colander.SchemaNode(colander.String(), name="referrer"))
|
||||
|
||||
schema.add(colander.SchemaNode(colander.String(),
|
||||
name='user_uuid',
|
||||
missing=None))
|
||||
schema.add(
|
||||
colander.SchemaNode(colander.String(), name="user_uuid", missing=None)
|
||||
)
|
||||
|
||||
schema.add(colander.SchemaNode(colander.String(),
|
||||
name='user_name'))
|
||||
schema.add(colander.SchemaNode(colander.String(), name="user_name"))
|
||||
|
||||
schema.add(colander.SchemaNode(colander.String(),
|
||||
name='message'))
|
||||
schema.add(colander.SchemaNode(colander.String(), name="message"))
|
||||
|
||||
return schema
|
||||
|
||||
def feedback_send(self, context):
|
||||
""" """
|
||||
self.app.send_email('feedback', context)
|
||||
self.app.send_email("feedback", context)
|
||||
|
||||
def setup(self, session=None):
|
||||
"""
|
||||
|
|
@ -162,87 +161,99 @@ class CommonView(View):
|
|||
# nb. this view only available until first user is created
|
||||
user = session.query(model.User).first()
|
||||
if user:
|
||||
return self.redirect(self.request.route_url('home'))
|
||||
return self.redirect(self.request.route_url("home"))
|
||||
|
||||
form = self.make_form(fields=['username', 'password', 'first_name', 'last_name'],
|
||||
show_button_cancel=False,
|
||||
show_button_reset=True)
|
||||
form.set_widget('password', widgets.CheckedPasswordWidget())
|
||||
form.set_required('first_name', False)
|
||||
form.set_required('last_name', False)
|
||||
form = self.make_form(
|
||||
fields=["username", "password", "first_name", "last_name"],
|
||||
show_button_cancel=False,
|
||||
show_button_reset=True,
|
||||
)
|
||||
form.set_widget("password", widgets.CheckedPasswordWidget())
|
||||
form.set_required("first_name", False)
|
||||
form.set_required("last_name", False)
|
||||
|
||||
if form.validate():
|
||||
auth = self.app.get_auth_handler()
|
||||
data = form.validated
|
||||
|
||||
# make user
|
||||
user = auth.make_user(session=session, username=data['username'])
|
||||
auth.set_user_password(user, data['password'])
|
||||
user = auth.make_user(session=session, username=data["username"])
|
||||
auth.set_user_password(user, data["password"])
|
||||
|
||||
# assign admin role
|
||||
admin = auth.get_role_administrator(session)
|
||||
user.roles.append(admin)
|
||||
admin.notes = ("users in this role may \"become root\".\n\n"
|
||||
"it's recommended not to grant other perms to this role.")
|
||||
admin.notes = (
|
||||
'users in this role may "become root".\n\n'
|
||||
"it's recommended not to grant other perms to this role."
|
||||
)
|
||||
|
||||
# initialize built-in roles
|
||||
authed = auth.get_role_authenticated(session)
|
||||
authed.notes = ("this role represents any user who *is* logged in.\n\n"
|
||||
"you may grant any perms you like to it.")
|
||||
authed.notes = (
|
||||
"this role represents any user who *is* logged in.\n\n"
|
||||
"you may grant any perms you like to it."
|
||||
)
|
||||
anon = auth.get_role_anonymous(session)
|
||||
anon.notes = ("this role represents any user who is *not* logged in.\n\n"
|
||||
"you may grant any perms you like to it.")
|
||||
anon.notes = (
|
||||
"this role represents any user who is *not* logged in.\n\n"
|
||||
"you may grant any perms you like to it."
|
||||
)
|
||||
|
||||
# also make "Site Admin" role
|
||||
site_admin_perms = [
|
||||
'appinfo.list',
|
||||
'appinfo.configure',
|
||||
'people.list',
|
||||
'people.create',
|
||||
'people.view',
|
||||
'people.edit',
|
||||
'people.delete',
|
||||
'roles.list',
|
||||
'roles.create',
|
||||
'roles.view',
|
||||
'roles.edit',
|
||||
'roles.edit_builtin',
|
||||
'roles.delete',
|
||||
'settings.list',
|
||||
'settings.create',
|
||||
'settings.view',
|
||||
'settings.edit',
|
||||
'settings.delete',
|
||||
'settings.delete_bulk',
|
||||
'upgrades.list',
|
||||
'upgrades.create',
|
||||
'upgrades.view',
|
||||
'upgrades.edit',
|
||||
'upgrades.delete',
|
||||
'upgrades.execute',
|
||||
'upgrades.download',
|
||||
'upgrades.configure',
|
||||
'users.list',
|
||||
'users.create',
|
||||
'users.view',
|
||||
'users.edit',
|
||||
'users.delete',
|
||||
"appinfo.list",
|
||||
"appinfo.configure",
|
||||
"people.list",
|
||||
"people.create",
|
||||
"people.view",
|
||||
"people.edit",
|
||||
"people.delete",
|
||||
"roles.list",
|
||||
"roles.create",
|
||||
"roles.view",
|
||||
"roles.edit",
|
||||
"roles.edit_builtin",
|
||||
"roles.delete",
|
||||
"settings.list",
|
||||
"settings.create",
|
||||
"settings.view",
|
||||
"settings.edit",
|
||||
"settings.delete",
|
||||
"settings.delete_bulk",
|
||||
"upgrades.list",
|
||||
"upgrades.create",
|
||||
"upgrades.view",
|
||||
"upgrades.edit",
|
||||
"upgrades.delete",
|
||||
"upgrades.execute",
|
||||
"upgrades.download",
|
||||
"upgrades.configure",
|
||||
"users.list",
|
||||
"users.create",
|
||||
"users.view",
|
||||
"users.edit",
|
||||
"users.delete",
|
||||
]
|
||||
admin2 = model.Role(name="Site Admin")
|
||||
admin2.notes = ("this is the \"daily driver\" admin role.\n\n"
|
||||
"you may grant any perms you like to it.")
|
||||
admin2.notes = (
|
||||
'this is the "daily driver" admin role.\n\n'
|
||||
"you may grant any perms you like to it."
|
||||
)
|
||||
session.add(admin2)
|
||||
user.roles.append(admin2)
|
||||
for perm in site_admin_perms:
|
||||
auth.grant_permission(admin2, perm)
|
||||
|
||||
# maybe make person
|
||||
if data['first_name'] or data['last_name']:
|
||||
first = data['first_name']
|
||||
last = data['last_name']
|
||||
person = model.Person(first_name=first,
|
||||
last_name=last,
|
||||
full_name=(f"{first} {last}").strip())
|
||||
if data["first_name"] or data["last_name"]:
|
||||
first = data["first_name"]
|
||||
last = data["last_name"]
|
||||
person = model.Person(
|
||||
first_name=first,
|
||||
last_name=last,
|
||||
full_name=(f"{first} {last}").strip(),
|
||||
)
|
||||
session.add(person)
|
||||
user.person = person
|
||||
|
||||
|
|
@ -250,11 +261,11 @@ class CommonView(View):
|
|||
|
||||
# send user to /login
|
||||
self.request.session.flash("Account created! Please login below.")
|
||||
return self.redirect(self.request.route_url('login'))
|
||||
return self.redirect(self.request.route_url("login"))
|
||||
|
||||
return {
|
||||
'index_title': self.app.get_title(),
|
||||
'form': form,
|
||||
"index_title": self.app.get_title(),
|
||||
"form": form,
|
||||
}
|
||||
|
||||
def setup_enhance_admin_user(self, user):
|
||||
|
|
@ -273,14 +284,14 @@ class CommonView(View):
|
|||
This view will set the global app theme, then redirect back to
|
||||
the referring page.
|
||||
"""
|
||||
theme = self.request.params.get('theme')
|
||||
theme = self.request.params.get("theme")
|
||||
if theme:
|
||||
try:
|
||||
set_app_theme(self.request, theme, session=Session())
|
||||
except Exception as error:
|
||||
error = self.app.render_error(error)
|
||||
self.request.session.flash(f"Failed to set theme: {error}", 'error')
|
||||
referrer = self.request.params.get('referrer') or self.request.get_referrer()
|
||||
self.request.session.flash(f"Failed to set theme: {error}", "error")
|
||||
referrer = self.request.params.get("referrer") or self.request.get_referrer()
|
||||
return self.redirect(referrer)
|
||||
|
||||
@classmethod
|
||||
|
|
@ -290,51 +301,52 @@ class CommonView(View):
|
|||
@classmethod
|
||||
def _defaults(cls, config):
|
||||
|
||||
config.add_wutta_permission_group('common', "(common)", overwrite=False)
|
||||
config.add_wutta_permission_group("common", "(common)", overwrite=False)
|
||||
|
||||
# home page
|
||||
config.add_route('home', '/')
|
||||
config.add_view(cls, attr='home',
|
||||
route_name='home',
|
||||
renderer='/home.mako')
|
||||
config.add_route("home", "/")
|
||||
config.add_view(cls, attr="home", route_name="home", renderer="/home.mako")
|
||||
|
||||
# forbidden
|
||||
config.add_forbidden_view(cls, attr='forbidden_view',
|
||||
renderer='/forbidden.mako')
|
||||
config.add_forbidden_view(
|
||||
cls, attr="forbidden_view", renderer="/forbidden.mako"
|
||||
)
|
||||
|
||||
# notfound
|
||||
# nb. also, auto-correct URLs which require trailing slash
|
||||
config.add_notfound_view(cls, attr='notfound_view',
|
||||
append_slash=True,
|
||||
renderer='/notfound.mako')
|
||||
config.add_notfound_view(
|
||||
cls, attr="notfound_view", append_slash=True, renderer="/notfound.mako"
|
||||
)
|
||||
|
||||
# feedback
|
||||
config.add_route('feedback', '/feedback',
|
||||
request_method='POST')
|
||||
config.add_view(cls, attr='feedback',
|
||||
route_name='feedback',
|
||||
permission='common.feedback',
|
||||
renderer='json')
|
||||
config.add_wutta_permission('common', 'common.feedback',
|
||||
"Send user feedback about the app")
|
||||
config.add_route("feedback", "/feedback", request_method="POST")
|
||||
config.add_view(
|
||||
cls,
|
||||
attr="feedback",
|
||||
route_name="feedback",
|
||||
permission="common.feedback",
|
||||
renderer="json",
|
||||
)
|
||||
config.add_wutta_permission(
|
||||
"common", "common.feedback", "Send user feedback about the app"
|
||||
)
|
||||
|
||||
# setup
|
||||
config.add_route('setup', '/setup')
|
||||
config.add_view(cls, attr='setup',
|
||||
route_name='setup',
|
||||
renderer='/setup.mako')
|
||||
config.add_route("setup", "/setup")
|
||||
config.add_view(cls, attr="setup", route_name="setup", renderer="/setup.mako")
|
||||
|
||||
# change theme
|
||||
config.add_route('change_theme', '/change-theme', request_method='POST')
|
||||
config.add_view(cls, attr='change_theme', route_name='change_theme')
|
||||
config.add_wutta_permission('common', 'common.change_theme',
|
||||
"Change global theme")
|
||||
config.add_route("change_theme", "/change-theme", request_method="POST")
|
||||
config.add_view(cls, attr="change_theme", route_name="change_theme")
|
||||
config.add_wutta_permission(
|
||||
"common", "common.change_theme", "Change global theme"
|
||||
)
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
base = globals()
|
||||
|
||||
CommonView = kwargs.get('CommonView', base['CommonView'])
|
||||
CommonView = kwargs.get("CommonView", base["CommonView"])
|
||||
CommonView.defaults(config)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -34,10 +34,11 @@ class EmailSettingView(MasterView):
|
|||
"""
|
||||
Master view for :term:`email settings <email setting>`.
|
||||
"""
|
||||
model_name = 'email_setting'
|
||||
model_key = 'key'
|
||||
|
||||
model_name = "email_setting"
|
||||
model_key = "key"
|
||||
model_title = "Email Setting"
|
||||
url_prefix = '/email/settings'
|
||||
url_prefix = "/email/settings"
|
||||
filterable = False
|
||||
sortable = True
|
||||
sort_on_backend = False
|
||||
|
|
@ -46,31 +47,31 @@ class EmailSettingView(MasterView):
|
|||
deletable = False
|
||||
|
||||
labels = {
|
||||
'key': "Email Key",
|
||||
'replyto': "Reply-To",
|
||||
"key": "Email Key",
|
||||
"replyto": "Reply-To",
|
||||
}
|
||||
|
||||
grid_columns = [
|
||||
'key',
|
||||
'subject',
|
||||
'to',
|
||||
'enabled',
|
||||
"key",
|
||||
"subject",
|
||||
"to",
|
||||
"enabled",
|
||||
]
|
||||
|
||||
# TODO: why does this not work?
|
||||
sort_defaults = 'key'
|
||||
sort_defaults = "key"
|
||||
|
||||
form_fields = [
|
||||
'key',
|
||||
'description',
|
||||
'subject',
|
||||
'sender',
|
||||
'replyto',
|
||||
'to',
|
||||
'cc',
|
||||
'bcc',
|
||||
'notes',
|
||||
'enabled',
|
||||
"key",
|
||||
"description",
|
||||
"subject",
|
||||
"sender",
|
||||
"replyto",
|
||||
"to",
|
||||
"cc",
|
||||
"bcc",
|
||||
"notes",
|
||||
"enabled",
|
||||
]
|
||||
|
||||
def __init__(self, request, context=None):
|
||||
|
|
@ -92,16 +93,18 @@ class EmailSettingView(MasterView):
|
|||
""" """
|
||||
key = setting.__name__
|
||||
return {
|
||||
'key': key,
|
||||
'description': setting.__doc__,
|
||||
'subject': self.email_handler.get_auto_subject(key, rendered=False, setting=setting),
|
||||
'sender': self.email_handler.get_auto_sender(key),
|
||||
'replyto': self.email_handler.get_auto_replyto(key) or colander.null,
|
||||
'to': self.email_handler.get_auto_to(key),
|
||||
'cc': self.email_handler.get_auto_cc(key),
|
||||
'bcc': self.email_handler.get_auto_bcc(key),
|
||||
'notes': self.email_handler.get_notes(key) or colander.null,
|
||||
'enabled': self.email_handler.is_enabled(key),
|
||||
"key": key,
|
||||
"description": setting.__doc__,
|
||||
"subject": self.email_handler.get_auto_subject(
|
||||
key, rendered=False, setting=setting
|
||||
),
|
||||
"sender": self.email_handler.get_auto_sender(key),
|
||||
"replyto": self.email_handler.get_auto_replyto(key) or colander.null,
|
||||
"to": self.email_handler.get_auto_to(key),
|
||||
"cc": self.email_handler.get_auto_cc(key),
|
||||
"bcc": self.email_handler.get_auto_bcc(key),
|
||||
"notes": self.email_handler.get_notes(key) or colander.null,
|
||||
"enabled": self.email_handler.is_enabled(key),
|
||||
}
|
||||
|
||||
def configure_grid(self, g):
|
||||
|
|
@ -109,15 +112,15 @@ class EmailSettingView(MasterView):
|
|||
super().configure_grid(g)
|
||||
|
||||
# key
|
||||
g.set_searchable('key')
|
||||
g.set_link('key')
|
||||
g.set_searchable("key")
|
||||
g.set_link("key")
|
||||
|
||||
# subject
|
||||
g.set_searchable('subject')
|
||||
g.set_link('subject')
|
||||
g.set_searchable("subject")
|
||||
g.set_link("subject")
|
||||
|
||||
# to
|
||||
g.set_renderer('to', self.render_to_short)
|
||||
g.set_renderer("to", self.render_to_short)
|
||||
|
||||
def render_to_short(self, setting, field, value):
|
||||
""" """
|
||||
|
|
@ -126,14 +129,14 @@ class EmailSettingView(MasterView):
|
|||
return
|
||||
|
||||
if len(recips) < 3:
|
||||
return ', '.join(recips)
|
||||
return ", ".join(recips)
|
||||
|
||||
recips = ', '.join(recips[:2])
|
||||
recips = ", ".join(recips[:2])
|
||||
return f"{recips}, ..."
|
||||
|
||||
def get_instance(self):
|
||||
""" """
|
||||
key = self.request.matchdict['key']
|
||||
key = self.request.matchdict["key"]
|
||||
setting = self.email_handler.get_email_setting(key, instance=False)
|
||||
if setting:
|
||||
return self.normalize_setting(setting)
|
||||
|
|
@ -142,99 +145,105 @@ class EmailSettingView(MasterView):
|
|||
|
||||
def get_instance_title(self, setting):
|
||||
""" """
|
||||
return setting['subject']
|
||||
return setting["subject"]
|
||||
|
||||
def configure_form(self, f):
|
||||
""" """
|
||||
super().configure_form(f)
|
||||
|
||||
# description
|
||||
f.set_readonly('description')
|
||||
f.set_readonly("description")
|
||||
|
||||
# replyto
|
||||
f.set_required('replyto', False)
|
||||
f.set_required("replyto", False)
|
||||
|
||||
# to
|
||||
f.set_node('to', EmailRecipients())
|
||||
f.set_node("to", EmailRecipients())
|
||||
|
||||
# cc
|
||||
f.set_node('cc', EmailRecipients())
|
||||
f.set_node("cc", EmailRecipients())
|
||||
|
||||
# bcc
|
||||
f.set_node('bcc', EmailRecipients())
|
||||
f.set_node("bcc", EmailRecipients())
|
||||
|
||||
# notes
|
||||
f.set_widget('notes', 'notes')
|
||||
f.set_required('notes', False)
|
||||
f.set_widget("notes", "notes")
|
||||
f.set_required("notes", False)
|
||||
|
||||
# enabled
|
||||
f.set_node('enabled', colander.Boolean())
|
||||
f.set_node("enabled", colander.Boolean())
|
||||
|
||||
def persist(self, setting):
|
||||
""" """
|
||||
session = self.Session()
|
||||
key = self.request.matchdict['key']
|
||||
key = self.request.matchdict["key"]
|
||||
|
||||
def save(name, value):
|
||||
self.app.save_setting(session, f'{self.config.appname}.email.{key}.{name}', value)
|
||||
self.app.save_setting(
|
||||
session, f"{self.config.appname}.email.{key}.{name}", value
|
||||
)
|
||||
|
||||
def delete(name):
|
||||
self.app.delete_setting(session, f'{self.config.appname}.email.{key}.{name}')
|
||||
self.app.delete_setting(
|
||||
session, f"{self.config.appname}.email.{key}.{name}"
|
||||
)
|
||||
|
||||
# subject
|
||||
if setting['subject']:
|
||||
save('subject', setting['subject'])
|
||||
if setting["subject"]:
|
||||
save("subject", setting["subject"])
|
||||
else:
|
||||
delete('subject')
|
||||
delete("subject")
|
||||
|
||||
# sender
|
||||
if setting['sender']:
|
||||
save('sender', setting['sender'])
|
||||
if setting["sender"]:
|
||||
save("sender", setting["sender"])
|
||||
else:
|
||||
delete('sender')
|
||||
delete("sender")
|
||||
|
||||
# replyto
|
||||
if setting['replyto']:
|
||||
save('replyto', setting['replyto'])
|
||||
if setting["replyto"]:
|
||||
save("replyto", setting["replyto"])
|
||||
else:
|
||||
delete('replyto')
|
||||
delete("replyto")
|
||||
|
||||
# to
|
||||
if setting['to']:
|
||||
save('to', setting['to'])
|
||||
if setting["to"]:
|
||||
save("to", setting["to"])
|
||||
else:
|
||||
delete('to')
|
||||
delete("to")
|
||||
|
||||
# cc
|
||||
if setting['cc']:
|
||||
save('cc', setting['cc'])
|
||||
if setting["cc"]:
|
||||
save("cc", setting["cc"])
|
||||
else:
|
||||
delete('cc')
|
||||
delete("cc")
|
||||
|
||||
# bcc
|
||||
if setting['bcc']:
|
||||
save('bcc', setting['bcc'])
|
||||
if setting["bcc"]:
|
||||
save("bcc", setting["bcc"])
|
||||
else:
|
||||
delete('bcc')
|
||||
delete("bcc")
|
||||
|
||||
# notes
|
||||
if setting['notes']:
|
||||
save('notes', setting['notes'])
|
||||
if setting["notes"]:
|
||||
save("notes", setting["notes"])
|
||||
else:
|
||||
delete('notes')
|
||||
delete("notes")
|
||||
|
||||
# enabled
|
||||
save('enabled', 'true' if setting['enabled'] else 'false')
|
||||
save("enabled", "true" if setting["enabled"] else "false")
|
||||
|
||||
def render_to_response(self, template, context):
|
||||
""" """
|
||||
if self.viewing:
|
||||
setting = context['instance']
|
||||
context['setting'] = setting
|
||||
context['has_html_template'] = self.email_handler.get_auto_body_template(
|
||||
setting['key'], 'html')
|
||||
context['has_txt_template'] = self.email_handler.get_auto_body_template(
|
||||
setting['key'], 'txt')
|
||||
setting = context["instance"]
|
||||
context["setting"] = setting
|
||||
context["has_html_template"] = self.email_handler.get_auto_body_template(
|
||||
setting["key"], "html"
|
||||
)
|
||||
context["has_txt_template"] = self.email_handler.get_auto_body_template(
|
||||
setting["key"], "txt"
|
||||
)
|
||||
|
||||
return super().render_to_response(template, context)
|
||||
|
||||
|
|
@ -245,16 +254,16 @@ class EmailSettingView(MasterView):
|
|||
This will render the email template according to the "mode"
|
||||
requested - i.e. HTML or TXT.
|
||||
"""
|
||||
key = self.request.matchdict['key']
|
||||
key = self.request.matchdict["key"]
|
||||
setting = self.email_handler.get_email_setting(key)
|
||||
context = setting.sample_data()
|
||||
mode = self.request.params.get('mode', 'html')
|
||||
mode = self.request.params.get("mode", "html")
|
||||
|
||||
if mode == 'txt':
|
||||
if mode == "txt":
|
||||
body = self.email_handler.get_auto_txt_body(key, context)
|
||||
self.request.response.content_type = 'text/plain'
|
||||
self.request.response.content_type = "text/plain"
|
||||
|
||||
else: # html
|
||||
else: # html
|
||||
body = self.email_handler.get_auto_html_body(key, context)
|
||||
|
||||
self.request.response.text = body
|
||||
|
|
@ -275,22 +284,24 @@ class EmailSettingView(MasterView):
|
|||
instance_url_prefix = cls.get_instance_url_prefix()
|
||||
|
||||
# fix permission group
|
||||
config.add_wutta_permission_group(permission_prefix,
|
||||
model_title_plural,
|
||||
overwrite=False)
|
||||
config.add_wutta_permission_group(
|
||||
permission_prefix, model_title_plural, overwrite=False
|
||||
)
|
||||
|
||||
# preview
|
||||
config.add_route(f'{route_prefix}.preview',
|
||||
f'{instance_url_prefix}/preview')
|
||||
config.add_view(cls, attr='preview',
|
||||
route_name=f'{route_prefix}.preview',
|
||||
permission=f'{permission_prefix}.view')
|
||||
config.add_route(f"{route_prefix}.preview", f"{instance_url_prefix}/preview")
|
||||
config.add_view(
|
||||
cls,
|
||||
attr="preview",
|
||||
route_name=f"{route_prefix}.preview",
|
||||
permission=f"{permission_prefix}.view",
|
||||
)
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
base = globals()
|
||||
|
||||
EmailSettingView = kwargs.get('EmailSettingView', base['EmailSettingView'])
|
||||
EmailSettingView = kwargs.get("EmailSettingView", base["EmailSettingView"])
|
||||
EmailSettingView.defaults(config)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -44,15 +44,15 @@ That will in turn include the following modules:
|
|||
def defaults(config, **kwargs):
|
||||
mod = lambda spec: kwargs.get(spec, spec)
|
||||
|
||||
config.include(mod('wuttaweb.views.common'))
|
||||
config.include(mod('wuttaweb.views.auth'))
|
||||
config.include(mod('wuttaweb.views.email'))
|
||||
config.include(mod('wuttaweb.views.settings'))
|
||||
config.include(mod('wuttaweb.views.progress'))
|
||||
config.include(mod('wuttaweb.views.people'))
|
||||
config.include(mod('wuttaweb.views.roles'))
|
||||
config.include(mod('wuttaweb.views.users'))
|
||||
config.include(mod('wuttaweb.views.upgrades'))
|
||||
config.include(mod("wuttaweb.views.common"))
|
||||
config.include(mod("wuttaweb.views.auth"))
|
||||
config.include(mod("wuttaweb.views.email"))
|
||||
config.include(mod("wuttaweb.views.settings"))
|
||||
config.include(mod("wuttaweb.views.progress"))
|
||||
config.include(mod("wuttaweb.views.people"))
|
||||
config.include(mod("wuttaweb.views.roles"))
|
||||
config.include(mod("wuttaweb.views.users"))
|
||||
config.include(mod("wuttaweb.views.upgrades"))
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -44,29 +44,30 @@ class PersonView(MasterView):
|
|||
* ``/people/XXX/edit``
|
||||
* ``/people/XXX/delete``
|
||||
"""
|
||||
|
||||
model_class = Person
|
||||
model_title_plural = "People"
|
||||
route_prefix = 'people'
|
||||
sort_defaults = 'full_name'
|
||||
route_prefix = "people"
|
||||
sort_defaults = "full_name"
|
||||
has_autocomplete = True
|
||||
|
||||
grid_columns = [
|
||||
'full_name',
|
||||
'first_name',
|
||||
'middle_name',
|
||||
'last_name',
|
||||
"full_name",
|
||||
"first_name",
|
||||
"middle_name",
|
||||
"last_name",
|
||||
]
|
||||
|
||||
filter_defaults = {
|
||||
'full_name': {'active': True},
|
||||
"full_name": {"active": True},
|
||||
}
|
||||
|
||||
form_fields = [
|
||||
'full_name',
|
||||
'first_name',
|
||||
'middle_name',
|
||||
'last_name',
|
||||
'users',
|
||||
"full_name",
|
||||
"first_name",
|
||||
"middle_name",
|
||||
"last_name",
|
||||
"users",
|
||||
]
|
||||
|
||||
def configure_grid(self, g):
|
||||
|
|
@ -74,13 +75,13 @@ class PersonView(MasterView):
|
|||
super().configure_grid(g)
|
||||
|
||||
# full_name
|
||||
g.set_link('full_name')
|
||||
g.set_link("full_name")
|
||||
|
||||
# first_name
|
||||
g.set_link('first_name')
|
||||
g.set_link("first_name")
|
||||
|
||||
# last_name
|
||||
g.set_link('last_name')
|
||||
g.set_link("last_name")
|
||||
|
||||
def configure_form(self, f):
|
||||
""" """
|
||||
|
|
@ -89,11 +90,11 @@ class PersonView(MasterView):
|
|||
|
||||
# full_name
|
||||
if self.creating or self.editing:
|
||||
f.remove('full_name')
|
||||
f.remove("full_name")
|
||||
|
||||
# users
|
||||
if self.viewing:
|
||||
f.set_grid('users', self.make_users_grid(person))
|
||||
f.set_grid("users", self.make_users_grid(person))
|
||||
|
||||
def make_users_grid(self, person):
|
||||
"""
|
||||
|
|
@ -107,22 +108,24 @@ class PersonView(MasterView):
|
|||
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',
|
||||
'active',
|
||||
])
|
||||
grid = self.make_grid(
|
||||
key=f"{route_prefix}.view.users",
|
||||
model_class=model.User,
|
||||
data=person.users,
|
||||
columns=[
|
||||
"username",
|
||||
"active",
|
||||
],
|
||||
)
|
||||
|
||||
if self.request.has_perm('users.view'):
|
||||
url = lambda user, i: self.request.route_url('users.view', uuid=user.uuid)
|
||||
grid.add_action('view', icon='eye', url=url)
|
||||
grid.set_link('username')
|
||||
if self.request.has_perm("users.view"):
|
||||
url = lambda user, i: self.request.route_url("users.view", uuid=user.uuid)
|
||||
grid.add_action("view", icon="eye", url=url)
|
||||
grid.set_link("username")
|
||||
|
||||
if self.request.has_perm('users.edit'):
|
||||
url = lambda user, i: self.request.route_url('users.edit', uuid=user.uuid)
|
||||
grid.add_action('edit', url=url)
|
||||
if self.request.has_perm("users.edit"):
|
||||
url = lambda user, i: self.request.route_url("users.edit", uuid=user.uuid)
|
||||
grid.add_action("edit", url=url)
|
||||
|
||||
return grid
|
||||
|
||||
|
|
@ -131,8 +134,7 @@ class PersonView(MasterView):
|
|||
person = super().objectify(form)
|
||||
|
||||
# full_name
|
||||
person.full_name = self.app.make_full_name(person.first_name,
|
||||
person.last_name)
|
||||
person.full_name = self.app.make_full_name(person.first_name, person.last_name)
|
||||
|
||||
return person
|
||||
|
||||
|
|
@ -141,24 +143,22 @@ class PersonView(MasterView):
|
|||
model = self.app.model
|
||||
session = self.Session()
|
||||
query = session.query(model.Person)
|
||||
criteria = [model.Person.full_name.ilike(f'%{word}%')
|
||||
for word in term.split()]
|
||||
query = query.filter(sa.and_(*criteria))\
|
||||
.order_by(model.Person.full_name)
|
||||
criteria = [model.Person.full_name.ilike(f"%{word}%") for word in term.split()]
|
||||
query = query.filter(sa.and_(*criteria)).order_by(model.Person.full_name)
|
||||
return query
|
||||
|
||||
def view_profile(self, session=None):
|
||||
""" """
|
||||
person = self.get_instance(session=session)
|
||||
context = {
|
||||
'person': person,
|
||||
'instance': person,
|
||||
"person": person,
|
||||
"instance": person,
|
||||
}
|
||||
return self.render_to_response('view_profile', context)
|
||||
return self.render_to_response("view_profile", context)
|
||||
|
||||
def make_user(self):
|
||||
""" """
|
||||
self.request.session.flash("TODO: this feature is not yet supported", 'error')
|
||||
self.request.session.flash("TODO: this feature is not yet supported", "error")
|
||||
return self.redirect(self.request.get_referrer())
|
||||
|
||||
@classmethod
|
||||
|
|
@ -166,7 +166,7 @@ class PersonView(MasterView):
|
|||
""" """
|
||||
|
||||
# nb. Person may come from custom model
|
||||
wutta_config = config.registry.settings['wutta_config']
|
||||
wutta_config = config.registry.settings["wutta_config"]
|
||||
app = wutta_config.get_app()
|
||||
cls.model_class = app.model.Person
|
||||
|
||||
|
|
@ -181,26 +181,36 @@ class PersonView(MasterView):
|
|||
permission_prefix = cls.get_permission_prefix()
|
||||
|
||||
# view profile
|
||||
config.add_route(f'{route_prefix}.view_profile',
|
||||
f'{instance_url_prefix}/profile',
|
||||
request_method='GET')
|
||||
config.add_view(cls, attr='view_profile',
|
||||
route_name=f'{route_prefix}.view_profile',
|
||||
permission=f'{permission_prefix}.view_profile')
|
||||
config.add_route(
|
||||
f"{route_prefix}.view_profile",
|
||||
f"{instance_url_prefix}/profile",
|
||||
request_method="GET",
|
||||
)
|
||||
config.add_view(
|
||||
cls,
|
||||
attr="view_profile",
|
||||
route_name=f"{route_prefix}.view_profile",
|
||||
permission=f"{permission_prefix}.view_profile",
|
||||
)
|
||||
|
||||
# make user for person
|
||||
config.add_route(f'{route_prefix}.make_user',
|
||||
f'{url_prefix}/make-user',
|
||||
request_method='POST')
|
||||
config.add_view(cls, attr='make_user',
|
||||
route_name=f'{route_prefix}.make_user',
|
||||
permission='users.create')
|
||||
config.add_route(
|
||||
f"{route_prefix}.make_user",
|
||||
f"{url_prefix}/make-user",
|
||||
request_method="POST",
|
||||
)
|
||||
config.add_view(
|
||||
cls,
|
||||
attr="make_user",
|
||||
route_name=f"{route_prefix}.make_user",
|
||||
permission="users.create",
|
||||
)
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
base = globals()
|
||||
|
||||
PersonView = kwargs.get('PersonView', base['PersonView'])
|
||||
PersonView = kwargs.get("PersonView", base["PersonView"])
|
||||
PersonView.defaults(config)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -38,24 +38,24 @@ def progress(request):
|
|||
session storage. See also
|
||||
:class:`~wuttaweb.progress.SessionProgress`.
|
||||
"""
|
||||
key = request.matchdict['key']
|
||||
key = request.matchdict["key"]
|
||||
session = get_progress_session(request, key)
|
||||
|
||||
# session has 'complete' flag set when operation is over
|
||||
if session.get('complete'):
|
||||
if session.get("complete"):
|
||||
|
||||
# set a flash msg for user if one is defined. this is the
|
||||
# time to do it since user is about to get redirected.
|
||||
msg = session.get('success_msg')
|
||||
msg = session.get("success_msg")
|
||||
if msg:
|
||||
request.session.flash(msg)
|
||||
|
||||
elif session.get('error'): # uh-oh
|
||||
elif session.get("error"): # uh-oh
|
||||
|
||||
# set an error flash msg for user. this is the time to do it
|
||||
# since user is about to get redirected.
|
||||
msg = session.get('error_msg', "An unspecified error occurred.")
|
||||
request.session.flash(msg, 'error')
|
||||
msg = session.get("error_msg", "An unspecified error occurred.")
|
||||
request.session.flash(msg, "error")
|
||||
|
||||
# nb. we return the session as-is; since it is dict-like (and only
|
||||
# contains relevant progress data) it can be used directly for the
|
||||
|
|
@ -66,9 +66,9 @@ def progress(request):
|
|||
def defaults(config, **kwargs):
|
||||
base = globals()
|
||||
|
||||
progress = kwargs.get('progress', base['progress'])
|
||||
config.add_route('progress', '/progress/{key}')
|
||||
config.add_view(progress, route_name='progress', renderer='json')
|
||||
progress = kwargs.get("progress", base["progress"])
|
||||
config.add_route("progress", "/progress/{key}")
|
||||
config.add_view(progress, route_name="progress", renderer="json")
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
|
|
|||
|
|
@ -47,24 +47,25 @@ class ReportView(MasterView):
|
|||
* ``/reports/``
|
||||
* ``/reports/XXX``
|
||||
"""
|
||||
|
||||
model_title = "Report"
|
||||
model_key = 'report_key'
|
||||
model_key = "report_key"
|
||||
filterable = False
|
||||
sort_on_backend = False
|
||||
creatable = False
|
||||
editable = False
|
||||
deletable = False
|
||||
route_prefix = 'reports'
|
||||
template_prefix = '/reports'
|
||||
route_prefix = "reports"
|
||||
template_prefix = "/reports"
|
||||
|
||||
grid_columns = [
|
||||
'report_title',
|
||||
'help_text',
|
||||
'report_key',
|
||||
"report_title",
|
||||
"help_text",
|
||||
"report_key",
|
||||
]
|
||||
|
||||
form_fields = [
|
||||
'help_text',
|
||||
"help_text",
|
||||
]
|
||||
|
||||
def __init__(self, request, context=None):
|
||||
|
|
@ -81,9 +82,9 @@ class ReportView(MasterView):
|
|||
def normalize_report(self, report):
|
||||
""" """
|
||||
return {
|
||||
'report_key': report.report_key,
|
||||
'report_title': report.report_title,
|
||||
'help_text': report.__doc__,
|
||||
"report_key": report.report_key,
|
||||
"report_title": report.report_title,
|
||||
"help_text": report.__doc__,
|
||||
}
|
||||
|
||||
def configure_grid(self, g):
|
||||
|
|
@ -91,18 +92,18 @@ class ReportView(MasterView):
|
|||
super().configure_grid(g)
|
||||
|
||||
# report_key
|
||||
g.set_link('report_key')
|
||||
g.set_link("report_key")
|
||||
|
||||
# report_title
|
||||
g.set_link('report_title')
|
||||
g.set_searchable('report_title')
|
||||
g.set_link("report_title")
|
||||
g.set_searchable("report_title")
|
||||
|
||||
# help_text
|
||||
g.set_searchable('help_text')
|
||||
g.set_searchable("help_text")
|
||||
|
||||
def get_instance(self):
|
||||
""" """
|
||||
key = self.request.matchdict['report_key']
|
||||
key = self.request.matchdict["report_key"]
|
||||
report = self.report_handler.get_report(key)
|
||||
if report:
|
||||
return self.normalize_report(report)
|
||||
|
|
@ -111,7 +112,7 @@ class ReportView(MasterView):
|
|||
|
||||
def get_instance_title(self, report):
|
||||
""" """
|
||||
return report['report_title']
|
||||
return report["report_title"]
|
||||
|
||||
def view(self):
|
||||
"""
|
||||
|
|
@ -119,41 +120,43 @@ class ReportView(MasterView):
|
|||
means showing them a form with report params, so they can run
|
||||
it.
|
||||
"""
|
||||
key = self.request.matchdict['report_key']
|
||||
key = self.request.matchdict["report_key"]
|
||||
report = self.report_handler.get_report(key)
|
||||
normal = self.normalize_report(report)
|
||||
|
||||
report_url = self.get_action_url('view', normal)
|
||||
form = self.make_model_form(normal,
|
||||
action_method='get',
|
||||
action_url=report_url,
|
||||
cancel_url=self.get_index_url(),
|
||||
show_button_reset=True,
|
||||
reset_url=report_url,
|
||||
button_label_submit="Run Report",
|
||||
button_icon_submit='arrow-circle-right')
|
||||
report_url = self.get_action_url("view", normal)
|
||||
form = self.make_model_form(
|
||||
normal,
|
||||
action_method="get",
|
||||
action_url=report_url,
|
||||
cancel_url=self.get_index_url(),
|
||||
show_button_reset=True,
|
||||
reset_url=report_url,
|
||||
button_label_submit="Run Report",
|
||||
button_icon_submit="arrow-circle-right",
|
||||
)
|
||||
|
||||
context = {
|
||||
'instance': normal,
|
||||
'report': report,
|
||||
'form': form,
|
||||
'xref_buttons': self.get_xref_buttons(report),
|
||||
"instance": normal,
|
||||
"report": report,
|
||||
"form": form,
|
||||
"xref_buttons": self.get_xref_buttons(report),
|
||||
}
|
||||
|
||||
if self.request.GET:
|
||||
form.show_button_cancel = False
|
||||
context = self.run_report(report, context)
|
||||
|
||||
return self.render_to_response('view', context)
|
||||
return self.render_to_response("view", context)
|
||||
|
||||
def configure_form(self, f):
|
||||
""" """
|
||||
super().configure_form(f)
|
||||
key = self.request.matchdict['report_key']
|
||||
key = self.request.matchdict["report_key"]
|
||||
report = self.report_handler.get_report(key)
|
||||
|
||||
# help_text
|
||||
f.set_readonly('help_text')
|
||||
f.set_readonly("help_text")
|
||||
|
||||
# add widget fields for all report params
|
||||
schema = f.get_schema()
|
||||
|
|
@ -174,12 +177,12 @@ class ReportView(MasterView):
|
|||
|
||||
:returns: Final view template context.
|
||||
"""
|
||||
form = context['form']
|
||||
form = context["form"]
|
||||
controls = list(self.request.GET.items())
|
||||
|
||||
# TODO: must re-inject help_text value for some reason,
|
||||
# otherwise its absence screws things up. why?
|
||||
controls.append(('help_text', report.__doc__))
|
||||
controls.append(("help_text", report.__doc__))
|
||||
|
||||
dform = form.get_deform()
|
||||
try:
|
||||
|
|
@ -191,20 +194,20 @@ class ReportView(MasterView):
|
|||
data = self.report_handler.make_report_data(report, params)
|
||||
|
||||
columns = self.normalize_columns(report.get_output_columns())
|
||||
context['report_columns'] = columns
|
||||
context["report_columns"] = columns
|
||||
|
||||
format_cols = [col for col in columns if col.get('formatter')]
|
||||
format_cols = [col for col in columns if col.get("formatter")]
|
||||
if format_cols:
|
||||
for record in data['data']:
|
||||
for record in data["data"]:
|
||||
for column in format_cols:
|
||||
if column['name'] in record:
|
||||
value = record[column['name']]
|
||||
record[column['name']] = column['formatter'](value)
|
||||
if column["name"] in record:
|
||||
value = record[column["name"]]
|
||||
record[column["name"]] = column["formatter"](value)
|
||||
|
||||
params.pop('help_text')
|
||||
context['report_params'] = params
|
||||
context['report_data'] = data
|
||||
context['report_generated'] = datetime.datetime.now()
|
||||
params.pop("help_text")
|
||||
context["report_params"] = params
|
||||
context["report_data"] = data
|
||||
context["report_generated"] = datetime.datetime.now()
|
||||
return context
|
||||
|
||||
def normalize_columns(self, columns):
|
||||
|
|
@ -212,14 +215,14 @@ class ReportView(MasterView):
|
|||
normal = []
|
||||
for column in columns:
|
||||
if isinstance(column, str):
|
||||
column = {'name': column}
|
||||
column.setdefault('label', column['name'])
|
||||
column = {"name": column}
|
||||
column.setdefault("label", column["name"])
|
||||
normal.append(column)
|
||||
return normal
|
||||
|
||||
def get_download_data(self):
|
||||
""" """
|
||||
key = self.request.matchdict['report_key']
|
||||
key = self.request.matchdict["report_key"]
|
||||
report = self.report_handler.get_report(key)
|
||||
params = dict(self.request.GET)
|
||||
columns = self.normalize_columns(report.get_output_columns())
|
||||
|
|
@ -244,21 +247,22 @@ class ReportView(MasterView):
|
|||
model_title = cls.get_model_title()
|
||||
|
||||
# overwrite title for "view" perm since it also implies "run"
|
||||
config.add_wutta_permission(permission_prefix,
|
||||
f'{permission_prefix}.view',
|
||||
f"View / run {model_title}")
|
||||
|
||||
config.add_wutta_permission(
|
||||
permission_prefix, f"{permission_prefix}.view", f"View / run {model_title}"
|
||||
)
|
||||
|
||||
# separate permission to download report files
|
||||
config.add_wutta_permission(permission_prefix,
|
||||
f'{permission_prefix}.download',
|
||||
f"Download {model_title}")
|
||||
config.add_wutta_permission(
|
||||
permission_prefix,
|
||||
f"{permission_prefix}.download",
|
||||
f"Download {model_title}",
|
||||
)
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
base = globals()
|
||||
|
||||
ReportView = kwargs.get('ReportView', base['ReportView'])
|
||||
ReportView = kwargs.get("ReportView", base["ReportView"])
|
||||
ReportView.defaults(config)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -45,17 +45,18 @@ class RoleView(MasterView):
|
|||
* ``/roles/XXX/edit``
|
||||
* ``/roles/XXX/delete``
|
||||
"""
|
||||
|
||||
model_class = Role
|
||||
|
||||
grid_columns = [
|
||||
'name',
|
||||
'notes',
|
||||
"name",
|
||||
"notes",
|
||||
]
|
||||
|
||||
filter_defaults = {
|
||||
'name': {'active': True},
|
||||
"name": {"active": True},
|
||||
}
|
||||
sort_defaults = 'name'
|
||||
sort_defaults = "name"
|
||||
|
||||
# TODO: master should handle this, possibly via configure_form()
|
||||
def get_query(self, session=None):
|
||||
|
|
@ -69,10 +70,10 @@ class RoleView(MasterView):
|
|||
super().configure_grid(g)
|
||||
|
||||
# name
|
||||
g.set_link('name')
|
||||
g.set_link("name")
|
||||
|
||||
# notes
|
||||
g.set_renderer('notes', self.grid_render_notes)
|
||||
g.set_renderer("notes", self.grid_render_notes)
|
||||
|
||||
def is_editable(self, role):
|
||||
""" """
|
||||
|
|
@ -84,9 +85,11 @@ class RoleView(MasterView):
|
|||
return self.request.is_root
|
||||
|
||||
# other built-in roles require special perm
|
||||
if role in (auth.get_role_authenticated(session),
|
||||
auth.get_role_anonymous(session)):
|
||||
return self.has_perm('edit_builtin')
|
||||
if role in (
|
||||
auth.get_role_authenticated(session),
|
||||
auth.get_role_anonymous(session),
|
||||
):
|
||||
return self.has_perm("edit_builtin")
|
||||
|
||||
return True
|
||||
|
||||
|
|
@ -111,26 +114,27 @@ class RoleView(MasterView):
|
|||
role = f.model_instance
|
||||
|
||||
# never show these
|
||||
f.remove('permission_refs',
|
||||
'user_refs')
|
||||
f.remove("permission_refs", "user_refs")
|
||||
|
||||
# name
|
||||
f.set_validator('name', self.unique_name)
|
||||
f.set_validator("name", self.unique_name)
|
||||
|
||||
# notes
|
||||
f.set_widget('notes', widgets.NotesWidget())
|
||||
f.set_widget("notes", widgets.NotesWidget())
|
||||
|
||||
# users
|
||||
if not (self.creating or self.editing):
|
||||
f.append('users')
|
||||
f.set_grid('users', self.make_users_grid(role))
|
||||
f.append("users")
|
||||
f.set_grid("users", self.make_users_grid(role))
|
||||
|
||||
# permissions
|
||||
f.append('permissions')
|
||||
f.append("permissions")
|
||||
self.wutta_permissions = self.get_available_permissions()
|
||||
f.set_node('permissions', Permissions(self.request, permissions=self.wutta_permissions))
|
||||
f.set_node(
|
||||
"permissions", Permissions(self.request, permissions=self.wutta_permissions)
|
||||
)
|
||||
if not self.creating:
|
||||
f.set_default('permissions', list(role.permissions))
|
||||
f.set_default("permissions", list(role.permissions))
|
||||
|
||||
def make_users_grid(self, role):
|
||||
"""
|
||||
|
|
@ -144,24 +148,26 @@ class RoleView(MasterView):
|
|||
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',
|
||||
'person',
|
||||
'active',
|
||||
])
|
||||
grid = self.make_grid(
|
||||
key=f"{route_prefix}.view.users",
|
||||
model_class=model.User,
|
||||
data=role.users,
|
||||
columns=[
|
||||
"username",
|
||||
"person",
|
||||
"active",
|
||||
],
|
||||
)
|
||||
|
||||
if self.request.has_perm('users.view'):
|
||||
url = lambda user, i: self.request.route_url('users.view', uuid=user.uuid)
|
||||
grid.add_action('view', icon='eye', url=url)
|
||||
grid.set_link('person')
|
||||
grid.set_link('username')
|
||||
if self.request.has_perm("users.view"):
|
||||
url = lambda user, i: self.request.route_url("users.view", uuid=user.uuid)
|
||||
grid.add_action("view", icon="eye", url=url)
|
||||
grid.set_link("person")
|
||||
grid.set_link("username")
|
||||
|
||||
if self.request.has_perm('users.edit'):
|
||||
url = lambda user, i: self.request.route_url('users.edit', uuid=user.uuid)
|
||||
grid.add_action('edit', url=url)
|
||||
if self.request.has_perm("users.edit"):
|
||||
url = lambda user, i: self.request.route_url("users.edit", uuid=user.uuid)
|
||||
grid.add_action("edit", url=url)
|
||||
|
||||
return grid
|
||||
|
||||
|
|
@ -170,11 +176,10 @@ class RoleView(MasterView):
|
|||
model = self.app.model
|
||||
session = Session()
|
||||
|
||||
query = session.query(model.Role)\
|
||||
.filter(model.Role.name == value)
|
||||
query = session.query(model.Role).filter(model.Role.name == value)
|
||||
|
||||
if self.editing:
|
||||
uuid = self.request.matchdict['uuid']
|
||||
uuid = self.request.matchdict["uuid"]
|
||||
query = query.filter(model.Role.uuid != uuid)
|
||||
|
||||
if query.count():
|
||||
|
|
@ -236,7 +241,7 @@ class RoleView(MasterView):
|
|||
"""
|
||||
|
||||
# get all known permissions from settings cache
|
||||
permissions = self.request.registry.settings.get('wutta_permissions', {})
|
||||
permissions = self.request.registry.settings.get("wutta_permissions", {})
|
||||
|
||||
# when viewing, we allow all permissions to be exposed for all users
|
||||
if self.viewing:
|
||||
|
|
@ -249,15 +254,15 @@ class RoleView(MasterView):
|
|||
# non-admin user can only see permissions they're granted
|
||||
available = {}
|
||||
for gkey, group in permissions.items():
|
||||
for pkey, perm in group['perms'].items():
|
||||
for pkey, perm in group["perms"].items():
|
||||
if self.request.has_perm(pkey):
|
||||
if gkey not in available:
|
||||
available[gkey] = {
|
||||
'key': gkey,
|
||||
'label': group['label'],
|
||||
'perms': {},
|
||||
"key": gkey,
|
||||
"label": group["label"],
|
||||
"perms": {},
|
||||
}
|
||||
available[gkey]['perms'][pkey] = perm
|
||||
available[gkey]["perms"][pkey] = perm
|
||||
|
||||
return available
|
||||
|
||||
|
|
@ -273,15 +278,15 @@ class RoleView(MasterView):
|
|||
|
||||
def update_permissions(self, role, form):
|
||||
""" """
|
||||
if 'permissions' not in form.validated:
|
||||
if "permissions" not in form.validated:
|
||||
return
|
||||
|
||||
auth = self.app.get_auth_handler()
|
||||
available = self.wutta_permissions
|
||||
permissions = form.validated['permissions']
|
||||
permissions = form.validated["permissions"]
|
||||
|
||||
for gkey, group in available.items():
|
||||
for pkey, perm in group['perms'].items():
|
||||
for pkey, perm in group["perms"].items():
|
||||
if pkey in permissions:
|
||||
auth.grant_permission(role, pkey)
|
||||
else:
|
||||
|
|
@ -299,9 +304,11 @@ class RoleView(MasterView):
|
|||
model_title_plural = cls.get_model_title_plural()
|
||||
|
||||
# perm to edit built-in roles
|
||||
config.add_wutta_permission(permission_prefix,
|
||||
f'{permission_prefix}.edit_builtin',
|
||||
f"Edit the Built-in {model_title_plural}")
|
||||
config.add_wutta_permission(
|
||||
permission_prefix,
|
||||
f"{permission_prefix}.edit_builtin",
|
||||
f"Edit the Built-in {model_title_plural}",
|
||||
)
|
||||
|
||||
|
||||
class PermissionView(MasterView):
|
||||
|
|
@ -316,20 +323,21 @@ class PermissionView(MasterView):
|
|||
* ``/permissions/XXX``
|
||||
* ``/permissions/XXX/delete``
|
||||
"""
|
||||
|
||||
model_class = Permission
|
||||
creatable = False
|
||||
editable = False
|
||||
|
||||
grid_columns = [
|
||||
'role',
|
||||
'permission',
|
||||
"role",
|
||||
"permission",
|
||||
]
|
||||
|
||||
sort_defaults = 'role'
|
||||
sort_defaults = "role"
|
||||
|
||||
form_fields = [
|
||||
'role',
|
||||
'permission',
|
||||
"role",
|
||||
"permission",
|
||||
]
|
||||
|
||||
def get_query(self, **kwargs):
|
||||
|
|
@ -348,28 +356,28 @@ class PermissionView(MasterView):
|
|||
model = self.app.model
|
||||
|
||||
# role
|
||||
g.set_sorter('role', model.Role.name)
|
||||
g.set_filter('role', model.Role.name, label="Role Name")
|
||||
g.set_link('role')
|
||||
g.set_sorter("role", model.Role.name)
|
||||
g.set_filter("role", model.Role.name, label="Role Name")
|
||||
g.set_link("role")
|
||||
|
||||
# permission
|
||||
g.set_link('permission')
|
||||
g.set_link("permission")
|
||||
|
||||
def configure_form(self, f):
|
||||
""" """
|
||||
super().configure_form(f)
|
||||
|
||||
# role
|
||||
f.set_node('role', RoleRef(self.request))
|
||||
f.set_node("role", RoleRef(self.request))
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
base = globals()
|
||||
|
||||
RoleView = kwargs.get('RoleView', base['RoleView'])
|
||||
RoleView = kwargs.get("RoleView", base["RoleView"])
|
||||
RoleView.defaults(config)
|
||||
|
||||
PermissionView = kwargs.get('PermissionView', base['PermissionView'])
|
||||
PermissionView = kwargs.get("PermissionView", base["PermissionView"])
|
||||
PermissionView.defaults(config)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -48,12 +48,13 @@ class AppInfoView(MasterView):
|
|||
|
||||
See also :class:`SettingView`.
|
||||
"""
|
||||
model_name = 'AppInfo'
|
||||
|
||||
model_name = "AppInfo"
|
||||
model_title_plural = "App Info"
|
||||
route_prefix = 'appinfo'
|
||||
route_prefix = "appinfo"
|
||||
filterable = False
|
||||
sort_on_backend = False
|
||||
sort_defaults = 'name'
|
||||
sort_defaults = "name"
|
||||
paginated = False
|
||||
creatable = False
|
||||
viewable = False
|
||||
|
|
@ -62,9 +63,9 @@ class AppInfoView(MasterView):
|
|||
configurable = True
|
||||
|
||||
grid_columns = [
|
||||
'name',
|
||||
'version',
|
||||
'editable_project_location',
|
||||
"name",
|
||||
"version",
|
||||
"editable_project_location",
|
||||
]
|
||||
|
||||
# TODO: for tailbone backward compat with get_liburl() etc.
|
||||
|
|
@ -74,19 +75,19 @@ class AppInfoView(MasterView):
|
|||
""" """
|
||||
|
||||
# nb. init with empty data, only load it upon user request
|
||||
if not self.request.GET.get('partial'):
|
||||
if not self.request.GET.get("partial"):
|
||||
return []
|
||||
|
||||
# TODO: pretty sure this is not cross-platform. probably some
|
||||
# sort of pip methods belong on the app handler? or it should
|
||||
# have a pip handler for all that?
|
||||
pip = os.path.join(sys.prefix, 'bin', 'pip')
|
||||
output = subprocess.check_output([pip, 'list', '--format=json'], text=True)
|
||||
pip = os.path.join(sys.prefix, "bin", "pip")
|
||||
output = subprocess.check_output([pip, "list", "--format=json"], text=True)
|
||||
data = json.loads(output.strip())
|
||||
|
||||
# must avoid null values for sort to work right
|
||||
for pkg in data:
|
||||
pkg.setdefault('editable_project_location', '')
|
||||
pkg.setdefault("editable_project_location", "")
|
||||
|
||||
return data
|
||||
|
||||
|
|
@ -97,74 +98,76 @@ class AppInfoView(MasterView):
|
|||
g.sort_multiple = False
|
||||
|
||||
# name
|
||||
g.set_searchable('name')
|
||||
g.set_searchable("name")
|
||||
|
||||
# editable_project_location
|
||||
g.set_searchable('editable_project_location')
|
||||
g.set_searchable("editable_project_location")
|
||||
|
||||
def get_weblibs(self):
|
||||
""" """
|
||||
return OrderedDict([
|
||||
('vue', "(Vue2) Vue"),
|
||||
('vue_resource', "(Vue2) vue-resource"),
|
||||
('buefy', "(Vue2) Buefy"),
|
||||
('buefy.css', "(Vue2) Buefy CSS"),
|
||||
('fontawesome', "(Vue2) FontAwesome"),
|
||||
('bb_vue', "(Vue3) vue"),
|
||||
('bb_oruga', "(Vue3) @oruga-ui/oruga-next"),
|
||||
('bb_oruga_bulma', "(Vue3) @oruga-ui/theme-bulma (JS)"),
|
||||
('bb_oruga_bulma_css', "(Vue3) @oruga-ui/theme-bulma (CSS)"),
|
||||
('bb_fontawesome_svg_core', "(Vue3) @fortawesome/fontawesome-svg-core"),
|
||||
('bb_free_solid_svg_icons', "(Vue3) @fortawesome/free-solid-svg-icons"),
|
||||
('bb_vue_fontawesome', "(Vue3) @fortawesome/vue-fontawesome"),
|
||||
])
|
||||
return OrderedDict(
|
||||
[
|
||||
("vue", "(Vue2) Vue"),
|
||||
("vue_resource", "(Vue2) vue-resource"),
|
||||
("buefy", "(Vue2) Buefy"),
|
||||
("buefy.css", "(Vue2) Buefy CSS"),
|
||||
("fontawesome", "(Vue2) FontAwesome"),
|
||||
("bb_vue", "(Vue3) vue"),
|
||||
("bb_oruga", "(Vue3) @oruga-ui/oruga-next"),
|
||||
("bb_oruga_bulma", "(Vue3) @oruga-ui/theme-bulma (JS)"),
|
||||
("bb_oruga_bulma_css", "(Vue3) @oruga-ui/theme-bulma (CSS)"),
|
||||
("bb_fontawesome_svg_core", "(Vue3) @fortawesome/fontawesome-svg-core"),
|
||||
("bb_free_solid_svg_icons", "(Vue3) @fortawesome/free-solid-svg-icons"),
|
||||
("bb_vue_fontawesome", "(Vue3) @fortawesome/vue-fontawesome"),
|
||||
]
|
||||
)
|
||||
|
||||
def configure_get_simple_settings(self):
|
||||
""" """
|
||||
simple_settings = [
|
||||
|
||||
# basics
|
||||
{'name': f'{self.config.appname}.app_title'},
|
||||
{'name': f'{self.config.appname}.node_type'},
|
||||
{'name': f'{self.config.appname}.node_title'},
|
||||
{'name': f'{self.config.appname}.production',
|
||||
'type': bool},
|
||||
{'name': 'wuttaweb.themes.expose_picker',
|
||||
'type': bool},
|
||||
{'name': f'{self.config.appname}.web.menus.handler.spec'},
|
||||
{"name": f"{self.config.appname}.app_title"},
|
||||
{"name": f"{self.config.appname}.node_type"},
|
||||
{"name": f"{self.config.appname}.node_title"},
|
||||
{"name": f"{self.config.appname}.production", "type": bool},
|
||||
{"name": "wuttaweb.themes.expose_picker", "type": bool},
|
||||
{"name": f"{self.config.appname}.web.menus.handler.spec"},
|
||||
# nb. this is deprecated; we define so it is auto-deleted
|
||||
# when we replace with newer setting
|
||||
{'name': f'{self.config.appname}.web.menus.handler_spec'},
|
||||
|
||||
{"name": f"{self.config.appname}.web.menus.handler_spec"},
|
||||
# user/auth
|
||||
{'name': 'wuttaweb.home_redirect_to_login',
|
||||
'type': bool, 'default': False},
|
||||
|
||||
{"name": "wuttaweb.home_redirect_to_login", "type": bool, "default": False},
|
||||
# email
|
||||
{'name': f'{self.config.appname}.mail.send_emails',
|
||||
'type': bool, 'default': False},
|
||||
{'name': f'{self.config.appname}.email.default.sender'},
|
||||
{'name': f'{self.config.appname}.email.default.subject'},
|
||||
{'name': f'{self.config.appname}.email.default.to'},
|
||||
{'name': f'{self.config.appname}.email.feedback.subject'},
|
||||
{'name': f'{self.config.appname}.email.feedback.to'},
|
||||
|
||||
{
|
||||
"name": f"{self.config.appname}.mail.send_emails",
|
||||
"type": bool,
|
||||
"default": False,
|
||||
},
|
||||
{"name": f"{self.config.appname}.email.default.sender"},
|
||||
{"name": f"{self.config.appname}.email.default.subject"},
|
||||
{"name": f"{self.config.appname}.email.default.to"},
|
||||
{"name": f"{self.config.appname}.email.feedback.subject"},
|
||||
{"name": f"{self.config.appname}.email.feedback.to"},
|
||||
]
|
||||
|
||||
def getval(key):
|
||||
return self.config.get(f'wuttaweb.{key}')
|
||||
return self.config.get(f"wuttaweb.{key}")
|
||||
|
||||
weblibs = self.get_weblibs()
|
||||
for key, title in weblibs.items():
|
||||
|
||||
simple_settings.append({
|
||||
'name': f'wuttaweb.libver.{key}',
|
||||
'default': getval(f'libver.{key}'),
|
||||
})
|
||||
simple_settings.append({
|
||||
'name': f'wuttaweb.liburl.{key}',
|
||||
'default': getval(f'liburl.{key}'),
|
||||
})
|
||||
simple_settings.append(
|
||||
{
|
||||
"name": f"wuttaweb.libver.{key}",
|
||||
"default": getval(f"libver.{key}"),
|
||||
}
|
||||
)
|
||||
simple_settings.append(
|
||||
{
|
||||
"name": f"wuttaweb.liburl.{key}",
|
||||
"default": getval(f"liburl.{key}"),
|
||||
}
|
||||
)
|
||||
|
||||
return simple_settings
|
||||
|
||||
|
|
@ -175,34 +178,42 @@ class AppInfoView(MasterView):
|
|||
# add registered menu handlers
|
||||
web = self.app.get_web_handler()
|
||||
handlers = web.get_menu_handler_specs()
|
||||
handlers = [{'spec': spec} for spec in handlers]
|
||||
context['menu_handlers'] = handlers
|
||||
handlers = [{"spec": spec} for spec in handlers]
|
||||
context["menu_handlers"] = handlers
|
||||
|
||||
# add `weblibs` to context, based on config values
|
||||
weblibs = self.get_weblibs()
|
||||
for key in weblibs:
|
||||
title = weblibs[key]
|
||||
weblibs[key] = {
|
||||
'key': key,
|
||||
'title': title,
|
||||
|
||||
"key": key,
|
||||
"title": title,
|
||||
# nb. these values are exactly as configured, and are
|
||||
# used for editing the settings
|
||||
'configured_version': get_libver(self.request, key,
|
||||
prefix=self.weblib_config_prefix,
|
||||
configured_only=True),
|
||||
'configured_url': get_liburl(self.request, key,
|
||||
prefix=self.weblib_config_prefix,
|
||||
configured_only=True),
|
||||
|
||||
"configured_version": get_libver(
|
||||
self.request,
|
||||
key,
|
||||
prefix=self.weblib_config_prefix,
|
||||
configured_only=True,
|
||||
),
|
||||
"configured_url": get_liburl(
|
||||
self.request,
|
||||
key,
|
||||
prefix=self.weblib_config_prefix,
|
||||
configured_only=True,
|
||||
),
|
||||
# nb. these are for display only
|
||||
'default_version': get_libver(self.request, key,
|
||||
prefix=self.weblib_config_prefix,
|
||||
default_only=True),
|
||||
'live_url': get_liburl(self.request, key,
|
||||
prefix=self.weblib_config_prefix),
|
||||
"default_version": get_libver(
|
||||
self.request,
|
||||
key,
|
||||
prefix=self.weblib_config_prefix,
|
||||
default_only=True,
|
||||
),
|
||||
"live_url": get_liburl(
|
||||
self.request, key, prefix=self.weblib_config_prefix
|
||||
),
|
||||
}
|
||||
context['weblibs'] = list(weblibs.values())
|
||||
context["weblibs"] = list(weblibs.values())
|
||||
|
||||
return context
|
||||
|
||||
|
|
@ -219,13 +230,14 @@ class SettingView(MasterView):
|
|||
|
||||
See also :class:`AppInfoView`.
|
||||
"""
|
||||
|
||||
model_class = Setting
|
||||
model_title = "Raw Setting"
|
||||
deletable_bulk = True
|
||||
filter_defaults = {
|
||||
'name': {'active': True},
|
||||
"name": {"active": True},
|
||||
}
|
||||
sort_defaults = 'name'
|
||||
sort_defaults = "name"
|
||||
|
||||
# TODO: master should handle this (per model key)
|
||||
def configure_grid(self, g):
|
||||
|
|
@ -233,29 +245,28 @@ class SettingView(MasterView):
|
|||
super().configure_grid(g)
|
||||
|
||||
# name
|
||||
g.set_link('name')
|
||||
g.set_link("name")
|
||||
|
||||
def configure_form(self, f):
|
||||
""" """
|
||||
super().configure_form(f)
|
||||
|
||||
# name
|
||||
f.set_validator('name', self.unique_name)
|
||||
f.set_validator("name", self.unique_name)
|
||||
|
||||
# value
|
||||
# TODO: master should handle this (per column nullable)
|
||||
f.set_required('value', False)
|
||||
f.set_required("value", False)
|
||||
|
||||
def unique_name(self, node, value):
|
||||
""" """
|
||||
model = self.app.model
|
||||
session = self.Session()
|
||||
|
||||
query = session.query(model.Setting)\
|
||||
.filter(model.Setting.name == value)
|
||||
query = session.query(model.Setting).filter(model.Setting.name == value)
|
||||
|
||||
if self.editing:
|
||||
name = self.request.matchdict['name']
|
||||
name = self.request.matchdict["name"]
|
||||
query = query.filter(model.Setting.name != name)
|
||||
|
||||
if query.count():
|
||||
|
|
@ -265,10 +276,10 @@ class SettingView(MasterView):
|
|||
def defaults(config, **kwargs):
|
||||
base = globals()
|
||||
|
||||
AppInfoView = kwargs.get('AppInfoView', base['AppInfoView'])
|
||||
AppInfoView = kwargs.get("AppInfoView", base["AppInfoView"])
|
||||
AppInfoView.defaults(config)
|
||||
|
||||
SettingView = kwargs.get('SettingView', base['SettingView'])
|
||||
SettingView = kwargs.get("SettingView", base["SettingView"])
|
||||
SettingView.defaults(config)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -55,21 +55,22 @@ class UpgradeView(MasterView):
|
|||
* ``/upgrades/XXX/edit``
|
||||
* ``/upgrades/XXX/delete``
|
||||
"""
|
||||
|
||||
model_class = Upgrade
|
||||
executable = True
|
||||
execute_progress_template = '/upgrade.mako'
|
||||
execute_progress_template = "/upgrade.mako"
|
||||
downloadable = True
|
||||
configurable = True
|
||||
|
||||
grid_columns = [
|
||||
'created',
|
||||
'description',
|
||||
'status',
|
||||
'executed',
|
||||
'executed_by',
|
||||
"created",
|
||||
"description",
|
||||
"status",
|
||||
"executed",
|
||||
"executed_by",
|
||||
]
|
||||
|
||||
sort_defaults = ('created', 'desc')
|
||||
sort_defaults = ("created", "desc")
|
||||
|
||||
def configure_grid(self, g):
|
||||
""" """
|
||||
|
|
@ -78,40 +79,44 @@ class UpgradeView(MasterView):
|
|||
enum = self.app.enum
|
||||
|
||||
# description
|
||||
g.set_link('description')
|
||||
g.set_link("description")
|
||||
|
||||
# created
|
||||
g.set_renderer('created', self.grid_render_datetime)
|
||||
g.set_renderer("created", self.grid_render_datetime)
|
||||
|
||||
# created_by
|
||||
g.set_link('created_by')
|
||||
g.set_link("created_by")
|
||||
Creator = orm.aliased(model.User)
|
||||
g.set_joiner('created_by', lambda q: q.join(Creator,
|
||||
Creator.uuid == model.Upgrade.created_by_uuid))
|
||||
g.set_filter('created_by', Creator.username,
|
||||
label="Created By Username")
|
||||
g.set_joiner(
|
||||
"created_by",
|
||||
lambda q: q.join(Creator, Creator.uuid == model.Upgrade.created_by_uuid),
|
||||
)
|
||||
g.set_filter("created_by", Creator.username, label="Created By Username")
|
||||
|
||||
# status
|
||||
g.set_renderer('status', self.grid_render_enum, enum=enum.UpgradeStatus)
|
||||
g.set_renderer("status", self.grid_render_enum, enum=enum.UpgradeStatus)
|
||||
|
||||
# executed
|
||||
g.set_renderer('executed', self.grid_render_datetime)
|
||||
g.set_renderer("executed", self.grid_render_datetime)
|
||||
|
||||
# executed_by
|
||||
g.set_link('executed_by')
|
||||
g.set_link("executed_by")
|
||||
Executor = orm.aliased(model.User)
|
||||
g.set_joiner('executed_by', lambda q: q.outerjoin(Executor,
|
||||
Executor.uuid == model.Upgrade.executed_by_uuid))
|
||||
g.set_filter('executed_by', Executor.username,
|
||||
label="Executed By Username")
|
||||
g.set_joiner(
|
||||
"executed_by",
|
||||
lambda q: q.outerjoin(
|
||||
Executor, Executor.uuid == model.Upgrade.executed_by_uuid
|
||||
),
|
||||
)
|
||||
g.set_filter("executed_by", Executor.username, label="Executed By Username")
|
||||
|
||||
def grid_row_class(self, upgrade, data, i):
|
||||
""" """
|
||||
enum = self.app.enum
|
||||
if upgrade.status == enum.UpgradeStatus.EXECUTING:
|
||||
return 'has-background-warning'
|
||||
return "has-background-warning"
|
||||
if upgrade.status == enum.UpgradeStatus.FAILURE:
|
||||
return 'has-background-warning'
|
||||
return "has-background-warning"
|
||||
|
||||
def configure_form(self, f):
|
||||
""" """
|
||||
|
|
@ -120,71 +125,81 @@ class UpgradeView(MasterView):
|
|||
upgrade = f.model_instance
|
||||
|
||||
# never show these
|
||||
f.remove('created_by_uuid',
|
||||
'executing',
|
||||
'executed_by_uuid')
|
||||
f.remove("created_by_uuid", "executing", "executed_by_uuid")
|
||||
|
||||
# sequence sanity
|
||||
f.fields.set_sequence([
|
||||
'description',
|
||||
'notes',
|
||||
'status',
|
||||
'created',
|
||||
'created_by',
|
||||
'executed',
|
||||
'executed_by',
|
||||
])
|
||||
f.fields.set_sequence(
|
||||
[
|
||||
"description",
|
||||
"notes",
|
||||
"status",
|
||||
"created",
|
||||
"created_by",
|
||||
"executed",
|
||||
"executed_by",
|
||||
]
|
||||
)
|
||||
|
||||
# created
|
||||
if self.creating or self.editing:
|
||||
f.remove('created')
|
||||
f.remove("created")
|
||||
|
||||
# created_by
|
||||
if self.creating or self.editing:
|
||||
f.remove('created_by')
|
||||
f.remove("created_by")
|
||||
else:
|
||||
f.set_node('created_by', UserRef(self.request))
|
||||
f.set_node("created_by", UserRef(self.request))
|
||||
|
||||
# notes
|
||||
f.set_widget('notes', 'notes')
|
||||
f.set_widget("notes", "notes")
|
||||
|
||||
# status
|
||||
if self.creating:
|
||||
f.remove('status')
|
||||
f.remove("status")
|
||||
else:
|
||||
f.set_node('status', WuttaEnum(self.request, enum.UpgradeStatus))
|
||||
f.set_node("status", WuttaEnum(self.request, enum.UpgradeStatus))
|
||||
|
||||
# executed
|
||||
if self.creating or self.editing or not upgrade.executed:
|
||||
f.remove('executed')
|
||||
f.remove("executed")
|
||||
|
||||
# executed_by
|
||||
if self.creating or self.editing or not upgrade.executed:
|
||||
f.remove('executed_by')
|
||||
f.remove("executed_by")
|
||||
else:
|
||||
f.set_node('executed_by', UserRef(self.request))
|
||||
f.set_node("executed_by", UserRef(self.request))
|
||||
|
||||
# exit_code
|
||||
if self.creating or self.editing or not upgrade.executed:
|
||||
f.remove('exit_code')
|
||||
f.remove("exit_code")
|
||||
|
||||
# stdout / stderr
|
||||
if not (self.creating or self.editing) and upgrade.status in (
|
||||
enum.UpgradeStatus.SUCCESS, enum.UpgradeStatus.FAILURE):
|
||||
enum.UpgradeStatus.SUCCESS,
|
||||
enum.UpgradeStatus.FAILURE,
|
||||
):
|
||||
|
||||
# stdout_file
|
||||
f.append('stdout_file')
|
||||
f.set_label('stdout_file', "STDOUT")
|
||||
url = self.get_action_url('download', upgrade, _query={'filename': 'stdout.log'})
|
||||
f.set_node('stdout_file', FileDownload(self.request, url=url))
|
||||
f.set_default('stdout_file', self.get_upgrade_filepath(upgrade, 'stdout.log'))
|
||||
f.append("stdout_file")
|
||||
f.set_label("stdout_file", "STDOUT")
|
||||
url = self.get_action_url(
|
||||
"download", upgrade, _query={"filename": "stdout.log"}
|
||||
)
|
||||
f.set_node("stdout_file", FileDownload(self.request, url=url))
|
||||
f.set_default(
|
||||
"stdout_file", self.get_upgrade_filepath(upgrade, "stdout.log")
|
||||
)
|
||||
|
||||
# stderr_file
|
||||
f.append('stderr_file')
|
||||
f.set_label('stderr_file', "STDERR")
|
||||
url = self.get_action_url('download', upgrade, _query={'filename': 'stderr.log'})
|
||||
f.set_node('stderr_file', FileDownload(self.request, url=url))
|
||||
f.set_default('stderr_file', self.get_upgrade_filepath(upgrade, 'stderr.log'))
|
||||
f.append("stderr_file")
|
||||
f.set_label("stderr_file", "STDERR")
|
||||
url = self.get_action_url(
|
||||
"download", upgrade, _query={"filename": "stderr.log"}
|
||||
)
|
||||
f.set_node("stderr_file", FileDownload(self.request, url=url))
|
||||
f.set_default(
|
||||
"stderr_file", self.get_upgrade_filepath(upgrade, "stderr.log")
|
||||
)
|
||||
|
||||
def delete_instance(self, upgrade):
|
||||
"""
|
||||
|
|
@ -217,8 +232,9 @@ class UpgradeView(MasterView):
|
|||
def get_upgrade_filepath(self, upgrade, filename=None, create=True):
|
||||
""" """
|
||||
uuid = str(upgrade.uuid)
|
||||
path = self.app.get_appdir('data', 'upgrades', uuid[:2], uuid[2:],
|
||||
create=create)
|
||||
path = self.app.get_appdir(
|
||||
"data", "upgrades", uuid[:2], uuid[2:], create=create
|
||||
)
|
||||
if filename:
|
||||
path = os.path.join(path, filename)
|
||||
return path
|
||||
|
|
@ -239,9 +255,9 @@ class UpgradeView(MasterView):
|
|||
enum = self.app.enum
|
||||
|
||||
# locate file paths
|
||||
script = self.config.require(f'{self.app.appname}.upgrades.command')
|
||||
stdout_path = self.get_upgrade_filepath(upgrade, 'stdout.log')
|
||||
stderr_path = self.get_upgrade_filepath(upgrade, 'stderr.log')
|
||||
script = self.config.require(f"{self.app.appname}.upgrades.command")
|
||||
stdout_path = self.get_upgrade_filepath(upgrade, "stdout.log")
|
||||
stderr_path = self.get_upgrade_filepath(upgrade, "stderr.log")
|
||||
|
||||
# record the fact that execution has begun for this upgrade
|
||||
# nb. this is done in separate session to ensure it sticks,
|
||||
|
|
@ -253,10 +269,11 @@ class UpgradeView(MasterView):
|
|||
|
||||
# run the command
|
||||
log.debug("running upgrade command: %s", script)
|
||||
with open(stdout_path, 'wb') as stdout:
|
||||
with open(stderr_path, 'wb') as stderr:
|
||||
upgrade.exit_code = subprocess.call(script, shell=True, text=True,
|
||||
stdout=stdout, stderr=stderr)
|
||||
with open(stdout_path, "wb") as stdout:
|
||||
with open(stderr_path, "wb") as stderr:
|
||||
upgrade.exit_code = subprocess.call(
|
||||
script, shell=True, text=True, stdout=stdout, stderr=stderr
|
||||
)
|
||||
logger = log.warning if upgrade.exit_code != 0 else log.debug
|
||||
logger("upgrade command had exit code: %s", upgrade.exit_code)
|
||||
|
||||
|
|
@ -272,30 +289,30 @@ class UpgradeView(MasterView):
|
|||
""" """
|
||||
route_prefix = self.get_route_prefix()
|
||||
upgrade = self.get_instance()
|
||||
session = get_progress_session(self.request, f'{route_prefix}.execute')
|
||||
session = get_progress_session(self.request, f"{route_prefix}.execute")
|
||||
|
||||
# session has 'complete' flag set when operation is over
|
||||
if session.get('complete'):
|
||||
if session.get("complete"):
|
||||
|
||||
# set a flash msg for user if one is defined. this is the
|
||||
# time to do it since user is about to get redirected.
|
||||
msg = session.get('success_msg')
|
||||
msg = session.get("success_msg")
|
||||
if msg:
|
||||
self.request.session.flash(msg)
|
||||
|
||||
elif session.get('error'): # uh-oh
|
||||
elif session.get("error"): # uh-oh
|
||||
|
||||
# set an error flash msg for user. this is the time to do it
|
||||
# since user is about to get redirected.
|
||||
msg = session.get('error_msg', "An unspecified error occurred.")
|
||||
self.request.session.flash(msg, 'error')
|
||||
msg = session.get("error_msg", "An unspecified error occurred.")
|
||||
self.request.session.flash(msg, "error")
|
||||
|
||||
# our return value will include all from progress session
|
||||
data = dict(session)
|
||||
|
||||
# add whatever might be new from upgrade process STDOUT
|
||||
path = self.get_upgrade_filepath(upgrade, filename='stdout.log')
|
||||
offset = session.get('stdout.offset', 0)
|
||||
path = self.get_upgrade_filepath(upgrade, filename="stdout.log")
|
||||
offset = session.get("stdout.offset", 0)
|
||||
if os.path.exists(path):
|
||||
size = os.path.getsize(path) - offset
|
||||
if size > 0:
|
||||
|
|
@ -304,8 +321,8 @@ class UpgradeView(MasterView):
|
|||
f.seek(offset)
|
||||
chunk = f.read(size)
|
||||
# data['stdout'] = chunk.decode('utf8').replace('\n', '<br />')
|
||||
data['stdout'] = chunk.replace('\n', '<br />')
|
||||
session['stdout.offset'] = offset + size
|
||||
data["stdout"] = chunk.replace("\n", "<br />")
|
||||
session["stdout.offset"] = offset + size
|
||||
session.save()
|
||||
|
||||
return data
|
||||
|
|
@ -313,16 +330,13 @@ class UpgradeView(MasterView):
|
|||
def configure_get_simple_settings(self):
|
||||
""" """
|
||||
|
||||
script = self.config.get(f'{self.app.appname}.upgrades.command')
|
||||
script = self.config.get(f"{self.app.appname}.upgrades.command")
|
||||
if not script:
|
||||
pass
|
||||
|
||||
return [
|
||||
|
||||
# basics
|
||||
{'name': f'{self.app.appname}.upgrades.command',
|
||||
'default': script},
|
||||
|
||||
{"name": f"{self.app.appname}.upgrades.command", "default": script},
|
||||
]
|
||||
|
||||
@classmethod
|
||||
|
|
@ -330,7 +344,7 @@ class UpgradeView(MasterView):
|
|||
""" """
|
||||
|
||||
# nb. Upgrade may come from custom model
|
||||
wutta_config = config.registry.settings['wutta_config']
|
||||
wutta_config = config.registry.settings["wutta_config"]
|
||||
app = wutta_config.get_app()
|
||||
cls.model_class = app.model.Upgrade
|
||||
|
||||
|
|
@ -344,18 +358,23 @@ class UpgradeView(MasterView):
|
|||
instance_url_prefix = cls.get_instance_url_prefix()
|
||||
|
||||
# execution progress
|
||||
config.add_route(f'{route_prefix}.execute_progress',
|
||||
f'{instance_url_prefix}/execute/progress')
|
||||
config.add_view(cls, attr='execute_progress',
|
||||
route_name=f'{route_prefix}.execute_progress',
|
||||
permission=f'{permission_prefix}.execute',
|
||||
renderer='json')
|
||||
config.add_route(
|
||||
f"{route_prefix}.execute_progress",
|
||||
f"{instance_url_prefix}/execute/progress",
|
||||
)
|
||||
config.add_view(
|
||||
cls,
|
||||
attr="execute_progress",
|
||||
route_name=f"{route_prefix}.execute_progress",
|
||||
permission=f"{permission_prefix}.execute",
|
||||
renderer="json",
|
||||
)
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
base = globals()
|
||||
|
||||
UpgradeView = kwargs.get('UpgradeView', base['UpgradeView'])
|
||||
UpgradeView = kwargs.get("UpgradeView", base["UpgradeView"])
|
||||
UpgradeView.defaults(config)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -46,31 +46,32 @@ class UserView(MasterView):
|
|||
* ``/users/XXX/edit``
|
||||
* ``/users/XXX/delete``
|
||||
"""
|
||||
|
||||
model_class = User
|
||||
|
||||
labels = {
|
||||
'api_tokens': "API Tokens",
|
||||
"api_tokens": "API Tokens",
|
||||
}
|
||||
|
||||
grid_columns = [
|
||||
'username',
|
||||
'person',
|
||||
'active',
|
||||
"username",
|
||||
"person",
|
||||
"active",
|
||||
]
|
||||
|
||||
filter_defaults = {
|
||||
'username': {'active': True},
|
||||
'active': {'active': True, 'verb': 'is_true'},
|
||||
"username": {"active": True},
|
||||
"active": {"active": True, "verb": "is_true"},
|
||||
}
|
||||
sort_defaults = 'username'
|
||||
sort_defaults = "username"
|
||||
|
||||
form_fields = [
|
||||
'username',
|
||||
'person',
|
||||
'active',
|
||||
'prevent_edit',
|
||||
'roles',
|
||||
'api_tokens',
|
||||
"username",
|
||||
"person",
|
||||
"active",
|
||||
"prevent_edit",
|
||||
"roles",
|
||||
"api_tokens",
|
||||
]
|
||||
|
||||
def get_query(self, session=None):
|
||||
|
|
@ -89,23 +90,20 @@ class UserView(MasterView):
|
|||
model = self.app.model
|
||||
|
||||
# never show these
|
||||
g.remove('person_uuid',
|
||||
'role_refs',
|
||||
'password')
|
||||
g.remove("person_uuid", "role_refs", "password")
|
||||
|
||||
# username
|
||||
g.set_link('username')
|
||||
g.set_link("username")
|
||||
|
||||
# person
|
||||
g.set_link('person')
|
||||
g.set_sorter('person', model.Person.full_name)
|
||||
g.set_filter('person', model.Person.full_name,
|
||||
label="Person Full Name")
|
||||
g.set_link("person")
|
||||
g.set_sorter("person", model.Person.full_name)
|
||||
g.set_filter("person", model.Person.full_name, label="Person Full Name")
|
||||
|
||||
def grid_row_class(self, user, data, i):
|
||||
""" """
|
||||
if not user.active:
|
||||
return 'has-background-warning'
|
||||
return "has-background-warning"
|
||||
|
||||
def is_editable(self, user):
|
||||
""" """
|
||||
|
|
@ -122,56 +120,55 @@ class UserView(MasterView):
|
|||
user = f.model_instance
|
||||
|
||||
# username
|
||||
f.set_validator('username', self.unique_username)
|
||||
f.set_validator("username", self.unique_username)
|
||||
|
||||
# person
|
||||
if self.creating or self.editing:
|
||||
f.fields.insert_after('person', 'first_name')
|
||||
f.set_required('first_name', False)
|
||||
f.fields.insert_after('first_name', 'last_name')
|
||||
f.set_required('last_name', False)
|
||||
f.remove('person')
|
||||
f.fields.insert_after("person", "first_name")
|
||||
f.set_required("first_name", False)
|
||||
f.fields.insert_after("first_name", "last_name")
|
||||
f.set_required("last_name", False)
|
||||
f.remove("person")
|
||||
if self.editing:
|
||||
person = user.person
|
||||
if person:
|
||||
f.set_default('first_name', person.first_name)
|
||||
f.set_default('last_name', person.last_name)
|
||||
f.set_default("first_name", person.first_name)
|
||||
f.set_default("last_name", person.last_name)
|
||||
else:
|
||||
f.set_node('person', PersonRef(self.request))
|
||||
f.set_node("person", PersonRef(self.request))
|
||||
|
||||
# password
|
||||
# nb. we must avoid 'password' as field name since
|
||||
# ColanderAlchemy wants to handle the raw/hashed value
|
||||
f.remove('password')
|
||||
f.remove("password")
|
||||
# nb. no need for password field if readonly
|
||||
if self.creating or self.editing:
|
||||
# nb. use 'set_password' as field name
|
||||
f.append('set_password')
|
||||
f.set_required('set_password', False)
|
||||
f.set_widget('set_password', widgets.CheckedPasswordWidget())
|
||||
f.append("set_password")
|
||||
f.set_required("set_password", False)
|
||||
f.set_widget("set_password", widgets.CheckedPasswordWidget())
|
||||
|
||||
# roles
|
||||
f.append('roles')
|
||||
f.set_node('roles', RoleRefs(self.request))
|
||||
f.append("roles")
|
||||
f.set_node("roles", RoleRefs(self.request))
|
||||
if not self.creating:
|
||||
f.set_default('roles', [role.uuid.hex for role in user.roles])
|
||||
f.set_default("roles", [role.uuid.hex for role in user.roles])
|
||||
|
||||
# api_tokens
|
||||
if self.viewing and self.has_perm('manage_api_tokens'):
|
||||
f.set_grid('api_tokens', self.make_api_tokens_grid(user))
|
||||
if self.viewing and self.has_perm("manage_api_tokens"):
|
||||
f.set_grid("api_tokens", self.make_api_tokens_grid(user))
|
||||
else:
|
||||
f.remove('api_tokens')
|
||||
f.remove("api_tokens")
|
||||
|
||||
def unique_username(self, node, value):
|
||||
""" """
|
||||
model = self.app.model
|
||||
session = self.Session()
|
||||
|
||||
query = session.query(model.User)\
|
||||
.filter(model.User.username == value)
|
||||
query = session.query(model.User).filter(model.User.username == value)
|
||||
|
||||
if self.editing:
|
||||
uuid = self.request.matchdict['uuid']
|
||||
uuid = self.request.matchdict["uuid"]
|
||||
query = query.filter(model.User.uuid != uuid)
|
||||
|
||||
if query.count():
|
||||
|
|
@ -187,29 +184,34 @@ class UserView(MasterView):
|
|||
user = super().objectify(form)
|
||||
|
||||
# maybe update person name
|
||||
if 'first_name' in form or 'last_name' in form:
|
||||
first_name = data.get('first_name')
|
||||
last_name = data.get('last_name')
|
||||
if "first_name" in form or "last_name" in form:
|
||||
first_name = data.get("first_name")
|
||||
last_name = data.get("last_name")
|
||||
if self.creating and (first_name or last_name):
|
||||
user.person = auth.make_person(first_name=first_name, last_name=last_name)
|
||||
user.person = auth.make_person(
|
||||
first_name=first_name, last_name=last_name
|
||||
)
|
||||
elif self.editing:
|
||||
if first_name or last_name:
|
||||
if user.person:
|
||||
person = user.person
|
||||
if 'first_name' in form:
|
||||
if "first_name" in form:
|
||||
person.first_name = first_name
|
||||
if 'last_name' in form:
|
||||
if "last_name" in form:
|
||||
person.last_name = last_name
|
||||
person.full_name = self.app.make_full_name(person.first_name,
|
||||
person.last_name)
|
||||
person.full_name = self.app.make_full_name(
|
||||
person.first_name, person.last_name
|
||||
)
|
||||
else:
|
||||
user.person = auth.make_person(first_name=first_name, last_name=last_name)
|
||||
user.person = auth.make_person(
|
||||
first_name=first_name, last_name=last_name
|
||||
)
|
||||
elif user.person:
|
||||
user.person = None
|
||||
|
||||
# maybe set user password
|
||||
if 'set_password' in form and data.get('set_password'):
|
||||
auth.set_user_password(user, data['set_password'])
|
||||
if "set_password" in form and data.get("set_password"):
|
||||
auth.set_user_password(user, data["set_password"])
|
||||
|
||||
# update roles for user
|
||||
# TODO
|
||||
|
|
@ -224,7 +226,7 @@ class UserView(MasterView):
|
|||
# if not self.has_perm('edit_roles'):
|
||||
# return
|
||||
data = form.validated
|
||||
if 'roles' not in data:
|
||||
if "roles" not in data:
|
||||
return
|
||||
|
||||
model = self.app.model
|
||||
|
|
@ -232,7 +234,7 @@ class UserView(MasterView):
|
|||
auth = self.app.get_auth_handler()
|
||||
|
||||
old_roles = set([role.uuid for role in user.roles])
|
||||
new_roles = data['roles']
|
||||
new_roles = data["roles"]
|
||||
|
||||
admin = auth.get_role_administrator(session)
|
||||
ignored = {
|
||||
|
|
@ -274,33 +276,46 @@ class UserView(MasterView):
|
|||
model = self.app.model
|
||||
route_prefix = self.get_route_prefix()
|
||||
|
||||
grid = self.make_grid(key=f'{route_prefix}.view.api_tokens',
|
||||
data=[self.normalize_api_token(t) for t in user.api_tokens],
|
||||
columns=[
|
||||
'description',
|
||||
'created',
|
||||
],
|
||||
sortable=True,
|
||||
sort_on_backend=False,
|
||||
sort_defaults=[('created', 'desc')])
|
||||
grid = self.make_grid(
|
||||
key=f"{route_prefix}.view.api_tokens",
|
||||
data=[self.normalize_api_token(t) for t in user.api_tokens],
|
||||
columns=[
|
||||
"description",
|
||||
"created",
|
||||
],
|
||||
sortable=True,
|
||||
sort_on_backend=False,
|
||||
sort_defaults=[("created", "desc")],
|
||||
)
|
||||
|
||||
if self.has_perm('manage_api_tokens'):
|
||||
if self.has_perm("manage_api_tokens"):
|
||||
|
||||
# create token
|
||||
button = self.make_button("New", primary=True, icon_left='plus', **{'@click': "$emit('new-token')"})
|
||||
grid.add_tool(button, key='create')
|
||||
button = self.make_button(
|
||||
"New",
|
||||
primary=True,
|
||||
icon_left="plus",
|
||||
**{"@click": "$emit('new-token')"},
|
||||
)
|
||||
grid.add_tool(button, key="create")
|
||||
|
||||
# delete token
|
||||
grid.add_action('delete', url='#', icon='trash', link_class='has-text-danger', click_handler="$emit('delete-token', props.row)")
|
||||
grid.add_action(
|
||||
"delete",
|
||||
url="#",
|
||||
icon="trash",
|
||||
link_class="has-text-danger",
|
||||
click_handler="$emit('delete-token', props.row)",
|
||||
)
|
||||
|
||||
return grid
|
||||
|
||||
def normalize_api_token(self, token):
|
||||
""" """
|
||||
return {
|
||||
'uuid': token.uuid.hex,
|
||||
'description': token.description,
|
||||
'created': self.app.render_datetime(token.created),
|
||||
"uuid": token.uuid.hex,
|
||||
"description": token.description,
|
||||
"created": self.app.render_datetime(token.created),
|
||||
}
|
||||
|
||||
def add_api_token(self):
|
||||
|
|
@ -316,13 +331,13 @@ class UserView(MasterView):
|
|||
user = self.get_instance()
|
||||
data = self.request.json_body
|
||||
|
||||
token = auth.add_api_token(user, data['description'])
|
||||
token = auth.add_api_token(user, data["description"])
|
||||
session.flush()
|
||||
session.refresh(token)
|
||||
|
||||
result = self.normalize_api_token(token)
|
||||
result['token_string'] = token.token_string
|
||||
result['_action_url_delete'] = '#'
|
||||
result["token_string"] = token.token_string
|
||||
result["_action_url_delete"] = "#"
|
||||
return result
|
||||
|
||||
def delete_api_token(self):
|
||||
|
|
@ -339,12 +354,12 @@ class UserView(MasterView):
|
|||
user = self.get_instance()
|
||||
data = self.request.json_body
|
||||
|
||||
token = session.get(model.UserAPIToken, data['uuid'])
|
||||
token = session.get(model.UserAPIToken, data["uuid"])
|
||||
if not token:
|
||||
return {'error': "API token not found"}
|
||||
return {"error": "API token not found"}
|
||||
|
||||
if token.user is not user:
|
||||
return {'error': "API token not found"}
|
||||
return {"error": "API token not found"}
|
||||
|
||||
auth.delete_api_token(token)
|
||||
return {}
|
||||
|
|
@ -354,7 +369,7 @@ class UserView(MasterView):
|
|||
""" """
|
||||
|
||||
# nb. User may come from custom model
|
||||
wutta_config = config.registry.settings['wutta_config']
|
||||
wutta_config = config.registry.settings["wutta_config"]
|
||||
app = wutta_config.get_app()
|
||||
cls.model_class = app.model.User
|
||||
|
||||
|
|
@ -372,29 +387,41 @@ class UserView(MasterView):
|
|||
model_title = cls.get_model_title()
|
||||
|
||||
# manage API tokens
|
||||
config.add_wutta_permission(permission_prefix,
|
||||
f'{permission_prefix}.manage_api_tokens',
|
||||
f"Manage API tokens for any {model_title}")
|
||||
config.add_route(f'{route_prefix}.add_api_token',
|
||||
f'{instance_url_prefix}/add-api-token',
|
||||
request_method='POST')
|
||||
config.add_view(cls, attr='add_api_token',
|
||||
route_name=f'{route_prefix}.add_api_token',
|
||||
permission=f'{permission_prefix}.manage_api_tokens',
|
||||
renderer='json')
|
||||
config.add_route(f'{route_prefix}.delete_api_token',
|
||||
f'{instance_url_prefix}/delete-api-token',
|
||||
request_method='POST')
|
||||
config.add_view(cls, attr='delete_api_token',
|
||||
route_name=f'{route_prefix}.delete_api_token',
|
||||
permission=f'{permission_prefix}.manage_api_tokens',
|
||||
renderer='json')
|
||||
config.add_wutta_permission(
|
||||
permission_prefix,
|
||||
f"{permission_prefix}.manage_api_tokens",
|
||||
f"Manage API tokens for any {model_title}",
|
||||
)
|
||||
config.add_route(
|
||||
f"{route_prefix}.add_api_token",
|
||||
f"{instance_url_prefix}/add-api-token",
|
||||
request_method="POST",
|
||||
)
|
||||
config.add_view(
|
||||
cls,
|
||||
attr="add_api_token",
|
||||
route_name=f"{route_prefix}.add_api_token",
|
||||
permission=f"{permission_prefix}.manage_api_tokens",
|
||||
renderer="json",
|
||||
)
|
||||
config.add_route(
|
||||
f"{route_prefix}.delete_api_token",
|
||||
f"{instance_url_prefix}/delete-api-token",
|
||||
request_method="POST",
|
||||
)
|
||||
config.add_view(
|
||||
cls,
|
||||
attr="delete_api_token",
|
||||
route_name=f"{route_prefix}.delete_api_token",
|
||||
permission=f"{permission_prefix}.manage_api_tokens",
|
||||
renderer="json",
|
||||
)
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
base = globals()
|
||||
|
||||
UserView = kwargs.get('UserView', base['UserView'])
|
||||
UserView = kwargs.get("UserView", base["UserView"])
|
||||
UserView.defaults(config)
|
||||
|
||||
|
||||
|
|
|
|||
14
tasks.py
14
tasks.py
|
|
@ -15,14 +15,14 @@ def release(c, skip_tests=False):
|
|||
Release a new version of WuttJamaican
|
||||
"""
|
||||
if not skip_tests:
|
||||
c.run('pytest')
|
||||
c.run("pytest")
|
||||
|
||||
# rebuild pkg
|
||||
if os.path.exists('dist'):
|
||||
shutil.rmtree('dist')
|
||||
if os.path.exists('WuttJamaican.egg-info'):
|
||||
shutil.rmtree('WuttJamaican.egg-info')
|
||||
c.run('python -m build --sdist')
|
||||
if os.path.exists("dist"):
|
||||
shutil.rmtree("dist")
|
||||
if os.path.exists("WuttJamaican.egg-info"):
|
||||
shutil.rmtree("WuttJamaican.egg-info")
|
||||
c.run("python -m build --sdist")
|
||||
|
||||
# upload
|
||||
c.run('twine upload dist/*')
|
||||
c.run("twine upload dist/*")
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ from wuttaweb.cli import webapp as mod
|
|||
class TestWebapp(ConfigTestCase):
|
||||
|
||||
def make_context(self, **kwargs):
|
||||
params = {'auto_reload': False}
|
||||
params.update(kwargs.get('params', {}))
|
||||
params = {"auto_reload": False}
|
||||
params.update(kwargs.get("params", {}))
|
||||
ctx = MagicMock(params=params)
|
||||
ctx.parent.wutta_config = self.config
|
||||
return ctx
|
||||
|
|
@ -19,7 +19,7 @@ class TestWebapp(ConfigTestCase):
|
|||
def test_missing_config_file(self):
|
||||
# nb. our default config has no files, so can test w/ that
|
||||
ctx = self.make_context()
|
||||
with patch.object(mod, 'sys') as sys:
|
||||
with patch.object(mod, "sys") as sys:
|
||||
sys.exit.side_effect = RuntimeError
|
||||
self.assertRaises(RuntimeError, mod.webapp, ctx)
|
||||
sys.stderr.write.assert_called_once_with("no config files found!\n")
|
||||
|
|
@ -28,14 +28,17 @@ class TestWebapp(ConfigTestCase):
|
|||
def test_invalid_runner(self):
|
||||
|
||||
# make new config from file, with bad setting
|
||||
path = self.write_file('my.conf', """
|
||||
path = self.write_file(
|
||||
"my.conf",
|
||||
"""
|
||||
[wutta.web]
|
||||
app.runner = bogus
|
||||
""")
|
||||
""",
|
||||
)
|
||||
self.config = self.make_config(files=[path])
|
||||
|
||||
ctx = self.make_context()
|
||||
with patch.object(mod, 'sys') as sys:
|
||||
with patch.object(mod, "sys") as sys:
|
||||
sys.exit.side_effect = RuntimeError
|
||||
self.assertRaises(RuntimeError, mod.webapp, ctx)
|
||||
sys.stderr.write.assert_called_once_with("unknown web app runner: bogus\n")
|
||||
|
|
@ -43,64 +46,76 @@ app.runner = bogus
|
|||
|
||||
def test_pserve(self):
|
||||
|
||||
path = self.write_file('my.conf', """
|
||||
path = self.write_file(
|
||||
"my.conf",
|
||||
"""
|
||||
[wutta.web]
|
||||
app.runner = pserve
|
||||
""")
|
||||
""",
|
||||
)
|
||||
self.config = self.make_config(files=[path])
|
||||
|
||||
# normal
|
||||
with patch.object(mod, 'pserve') as pserve:
|
||||
with patch.object(mod, "pserve") as pserve:
|
||||
ctx = self.make_context()
|
||||
mod.webapp(ctx)
|
||||
pserve.main.assert_called_once_with(argv=['pserve', f'file+ini:{path}'])
|
||||
pserve.main.assert_called_once_with(argv=["pserve", f"file+ini:{path}"])
|
||||
|
||||
# with reload
|
||||
with patch.object(mod, 'pserve') as pserve:
|
||||
ctx = self.make_context(params={'auto_reload': True})
|
||||
with patch.object(mod, "pserve") as pserve:
|
||||
ctx = self.make_context(params={"auto_reload": True})
|
||||
mod.webapp(ctx)
|
||||
pserve.main.assert_called_once_with(argv=['pserve', f'file+ini:{path}', '--reload'])
|
||||
pserve.main.assert_called_once_with(
|
||||
argv=["pserve", f"file+ini:{path}", "--reload"]
|
||||
)
|
||||
|
||||
def test_uvicorn(self):
|
||||
|
||||
path = self.write_file('my.conf', """
|
||||
path = self.write_file(
|
||||
"my.conf",
|
||||
"""
|
||||
[wutta.web]
|
||||
app.runner = uvicorn
|
||||
app.spec = wuttaweb.app:make_wsgi_app
|
||||
""")
|
||||
""",
|
||||
)
|
||||
self.config = self.make_config(files=[path])
|
||||
|
||||
orig_import = __import__
|
||||
uvicorn = MagicMock()
|
||||
|
||||
def mock_import(name, *args, **kwargs):
|
||||
if name == 'uvicorn':
|
||||
if name == "uvicorn":
|
||||
return uvicorn
|
||||
return orig_import(name, *args, **kwargs)
|
||||
|
||||
# normal
|
||||
with patch('builtins.__import__', side_effect=mock_import):
|
||||
with patch("builtins.__import__", side_effect=mock_import):
|
||||
ctx = self.make_context()
|
||||
mod.webapp(ctx)
|
||||
uvicorn.run.assert_called_once_with('wuttaweb.app:make_wsgi_app',
|
||||
host='127.0.0.1',
|
||||
port=8000,
|
||||
reload=False,
|
||||
reload_dirs=None,
|
||||
factory=False,
|
||||
interface='auto',
|
||||
root_path='')
|
||||
uvicorn.run.assert_called_once_with(
|
||||
"wuttaweb.app:make_wsgi_app",
|
||||
host="127.0.0.1",
|
||||
port=8000,
|
||||
reload=False,
|
||||
reload_dirs=None,
|
||||
factory=False,
|
||||
interface="auto",
|
||||
root_path="",
|
||||
)
|
||||
|
||||
# with reload
|
||||
uvicorn.run.reset_mock()
|
||||
with patch('builtins.__import__', side_effect=mock_import):
|
||||
ctx = self.make_context(params={'auto_reload': True})
|
||||
with patch("builtins.__import__", side_effect=mock_import):
|
||||
ctx = self.make_context(params={"auto_reload": True})
|
||||
mod.webapp(ctx)
|
||||
uvicorn.run.assert_called_once_with('wuttaweb.app:make_wsgi_app',
|
||||
host='127.0.0.1',
|
||||
port=8000,
|
||||
reload=True,
|
||||
reload_dirs=None,
|
||||
factory=False,
|
||||
interface='auto',
|
||||
root_path='')
|
||||
uvicorn.run.assert_called_once_with(
|
||||
"wuttaweb.app:make_wsgi_app",
|
||||
host="127.0.0.1",
|
||||
port=8000,
|
||||
reload=True,
|
||||
reload_dirs=None,
|
||||
factory=False,
|
||||
interface="auto",
|
||||
root_path="",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from wuttaweb.testing import WebTestCase
|
|||
class TestWuttaWebContinuumPlugin(WebTestCase):
|
||||
|
||||
def setUp(self):
|
||||
if not hasattr(mod, 'WuttaWebContinuumPlugin'):
|
||||
if not hasattr(mod, "WuttaWebContinuumPlugin"):
|
||||
pytest.skip("test not relevant without sqlalchemy-continuum")
|
||||
self.setup_web()
|
||||
|
||||
|
|
@ -21,17 +21,17 @@ class TestWuttaWebContinuumPlugin(WebTestCase):
|
|||
def test_get_remote_addr(self):
|
||||
plugin = self.make_plugin()
|
||||
|
||||
with patch.object(mod, 'get_current_request', return_value=None):
|
||||
with patch.object(mod, "get_current_request", return_value=None):
|
||||
self.assertIsNone(plugin.get_remote_addr(None, self.session))
|
||||
|
||||
self.request.client_addr = '127.0.0.1'
|
||||
self.assertEqual(plugin.get_remote_addr(None, self.session), '127.0.0.1')
|
||||
self.request.client_addr = "127.0.0.1"
|
||||
self.assertEqual(plugin.get_remote_addr(None, self.session), "127.0.0.1")
|
||||
|
||||
def test_get_user_id(self):
|
||||
plugin = self.make_plugin()
|
||||
|
||||
with patch.object(mod, 'get_current_request', return_value=None):
|
||||
with patch.object(mod, "get_current_request", return_value=None):
|
||||
self.assertIsNone(plugin.get_user_id(None, self.session))
|
||||
|
||||
self.request.user = MagicMock(uuid='some-random-uuid')
|
||||
self.assertEqual(plugin.get_user_id(None, self.session), 'some-random-uuid')
|
||||
self.request.user = MagicMock(uuid="some-random-uuid")
|
||||
self.assertEqual(plugin.get_user_id(None, self.session), "some-random-uuid")
|
||||
|
|
|
|||
|
|
@ -18,17 +18,22 @@ from wuttaweb.grids import Grid
|
|||
class TestForm(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.config = WuttaConfig(defaults={
|
||||
'wutta.web.menus.handler_spec': 'tests.util:NullMenuHandler',
|
||||
})
|
||||
self.config = WuttaConfig(
|
||||
defaults={
|
||||
"wutta.web.menus.handler_spec": "tests.util:NullMenuHandler",
|
||||
}
|
||||
)
|
||||
self.app = self.config.get_app()
|
||||
self.request = testing.DummyRequest(wutta_config=self.config, use_oruga=False)
|
||||
|
||||
self.pyramid_config = testing.setUp(request=self.request, settings={
|
||||
'wutta_config': self.config,
|
||||
'mako.directories': ['wuttaweb:templates'],
|
||||
'pyramid_deform.template_search_path': 'wuttaweb:templates/deform',
|
||||
})
|
||||
self.pyramid_config = testing.setUp(
|
||||
request=self.request,
|
||||
settings={
|
||||
"wutta_config": self.config,
|
||||
"mako.directories": ["wuttaweb:templates"],
|
||||
"pyramid_deform.template_search_path": "wuttaweb:templates/deform",
|
||||
},
|
||||
)
|
||||
|
||||
event = MagicMock(request=self.request)
|
||||
subscribers.new_request(event)
|
||||
|
|
@ -40,12 +45,12 @@ class TestForm(TestCase):
|
|||
return base.Form(self.request, **kwargs)
|
||||
|
||||
def make_schema(self):
|
||||
schema = colander.Schema(children=[
|
||||
colander.SchemaNode(colander.String(),
|
||||
name='foo'),
|
||||
colander.SchemaNode(colander.String(),
|
||||
name='bar'),
|
||||
])
|
||||
schema = colander.Schema(
|
||||
children=[
|
||||
colander.SchemaNode(colander.String(), name="foo"),
|
||||
colander.SchemaNode(colander.String(), name="bar"),
|
||||
]
|
||||
)
|
||||
return schema
|
||||
|
||||
def test_init_with_none(self):
|
||||
|
|
@ -53,126 +58,126 @@ class TestForm(TestCase):
|
|||
self.assertEqual(form.fields, [])
|
||||
|
||||
def test_init_with_fields(self):
|
||||
form = self.make_form(fields=['foo', 'bar'])
|
||||
self.assertEqual(form.fields, ['foo', 'bar'])
|
||||
form = self.make_form(fields=["foo", "bar"])
|
||||
self.assertEqual(form.fields, ["foo", "bar"])
|
||||
|
||||
def test_init_with_schema(self):
|
||||
schema = self.make_schema()
|
||||
form = self.make_form(schema=schema)
|
||||
self.assertEqual(form.fields, ['foo', 'bar'])
|
||||
self.assertEqual(form.fields, ["foo", "bar"])
|
||||
|
||||
def test_vue_tagname(self):
|
||||
form = self.make_form()
|
||||
self.assertEqual(form.vue_tagname, 'wutta-form')
|
||||
self.assertEqual(form.vue_tagname, "wutta-form")
|
||||
|
||||
def test_vue_component(self):
|
||||
form = self.make_form()
|
||||
self.assertEqual(form.vue_component, 'WuttaForm')
|
||||
self.assertEqual(form.vue_component, "WuttaForm")
|
||||
|
||||
def test_contains(self):
|
||||
form = self.make_form(fields=['foo', 'bar'])
|
||||
self.assertIn('foo', form)
|
||||
self.assertNotIn('baz', form)
|
||||
form = self.make_form(fields=["foo", "bar"])
|
||||
self.assertIn("foo", form)
|
||||
self.assertNotIn("baz", form)
|
||||
|
||||
def test_iter(self):
|
||||
form = self.make_form(fields=['foo', 'bar'])
|
||||
form = self.make_form(fields=["foo", "bar"])
|
||||
|
||||
fields = list(iter(form))
|
||||
self.assertEqual(fields, ['foo', 'bar'])
|
||||
self.assertEqual(fields, ["foo", "bar"])
|
||||
|
||||
fields = []
|
||||
for field in form:
|
||||
fields.append(field)
|
||||
self.assertEqual(fields, ['foo', 'bar'])
|
||||
self.assertEqual(fields, ["foo", "bar"])
|
||||
|
||||
def test_set_fields(self):
|
||||
form = self.make_form(fields=['foo', 'bar'])
|
||||
self.assertEqual(form.fields, ['foo', 'bar'])
|
||||
form.set_fields(['baz'])
|
||||
self.assertEqual(form.fields, ['baz'])
|
||||
form = self.make_form(fields=["foo", "bar"])
|
||||
self.assertEqual(form.fields, ["foo", "bar"])
|
||||
form.set_fields(["baz"])
|
||||
self.assertEqual(form.fields, ["baz"])
|
||||
|
||||
def test_append(self):
|
||||
form = self.make_form(fields=['one', 'two'])
|
||||
self.assertEqual(form.fields, ['one', 'two'])
|
||||
form.append('one', 'two', 'three')
|
||||
self.assertEqual(form.fields, ['one', 'two', 'three'])
|
||||
form = self.make_form(fields=["one", "two"])
|
||||
self.assertEqual(form.fields, ["one", "two"])
|
||||
form.append("one", "two", "three")
|
||||
self.assertEqual(form.fields, ["one", "two", "three"])
|
||||
|
||||
def test_remove(self):
|
||||
form = self.make_form(fields=['one', 'two', 'three', 'four'])
|
||||
self.assertEqual(form.fields, ['one', 'two', 'three', 'four'])
|
||||
form.remove('two', 'three')
|
||||
self.assertEqual(form.fields, ['one', 'four'])
|
||||
form = self.make_form(fields=["one", "two", "three", "four"])
|
||||
self.assertEqual(form.fields, ["one", "two", "three", "four"])
|
||||
form.remove("two", "three")
|
||||
self.assertEqual(form.fields, ["one", "four"])
|
||||
|
||||
def test_set_node(self):
|
||||
form = self.make_form(fields=['foo', 'bar'])
|
||||
form = self.make_form(fields=["foo", "bar"])
|
||||
self.assertEqual(form.nodes, {})
|
||||
|
||||
# complete node
|
||||
node = colander.SchemaNode(colander.Bool(), name='foo')
|
||||
form.set_node('foo', node)
|
||||
self.assertIs(form.nodes['foo'], node)
|
||||
node = colander.SchemaNode(colander.Bool(), name="foo")
|
||||
form.set_node("foo", node)
|
||||
self.assertIs(form.nodes["foo"], node)
|
||||
|
||||
# type only
|
||||
typ = colander.Bool()
|
||||
form.set_node('foo', typ)
|
||||
node = form.nodes['foo']
|
||||
form.set_node("foo", typ)
|
||||
node = form.nodes["foo"]
|
||||
self.assertIsInstance(node, colander.SchemaNode)
|
||||
self.assertIsInstance(node.typ, colander.Bool)
|
||||
self.assertEqual(node.name, 'foo')
|
||||
self.assertEqual(node.name, "foo")
|
||||
|
||||
# schema is updated if already present
|
||||
schema = form.get_schema()
|
||||
self.assertIsNotNone(schema)
|
||||
typ = colander.Date()
|
||||
form.set_node('foo', typ)
|
||||
node = form.nodes['foo']
|
||||
form.set_node("foo", typ)
|
||||
node = form.nodes["foo"]
|
||||
self.assertIsInstance(node, colander.SchemaNode)
|
||||
self.assertIsInstance(node.typ, colander.Date)
|
||||
self.assertEqual(node.name, 'foo')
|
||||
self.assertEqual(node.name, "foo")
|
||||
|
||||
def test_set_widget(self):
|
||||
form = self.make_form(fields=['foo', 'bar'])
|
||||
form = self.make_form(fields=["foo", "bar"])
|
||||
self.assertEqual(form.widgets, {})
|
||||
|
||||
# basic
|
||||
widget = widgets.SelectWidget()
|
||||
form.set_widget('foo', widget)
|
||||
self.assertIs(form.widgets['foo'], widget)
|
||||
form.set_widget("foo", widget)
|
||||
self.assertIs(form.widgets["foo"], widget)
|
||||
|
||||
# schema is updated if already present
|
||||
schema = form.get_schema()
|
||||
self.assertIsNotNone(schema)
|
||||
self.assertIs(schema['foo'].widget, widget)
|
||||
self.assertIs(schema["foo"].widget, widget)
|
||||
new_widget = widgets.TextInputWidget()
|
||||
form.set_widget('foo', new_widget)
|
||||
self.assertIs(form.widgets['foo'], new_widget)
|
||||
self.assertIs(schema['foo'].widget, new_widget)
|
||||
form.set_widget("foo", new_widget)
|
||||
self.assertIs(form.widgets["foo"], new_widget)
|
||||
self.assertIs(schema["foo"].widget, new_widget)
|
||||
|
||||
# can also just specify widget pseudo-type (invalid)
|
||||
self.assertNotIn('bar', form.widgets)
|
||||
self.assertRaises(ValueError, form.set_widget, 'bar', 'ldjfadjfadj')
|
||||
self.assertNotIn("bar", form.widgets)
|
||||
self.assertRaises(ValueError, form.set_widget, "bar", "ldjfadjfadj")
|
||||
|
||||
# can also just specify widget pseudo-type (valid)
|
||||
self.assertNotIn('bar', form.widgets)
|
||||
form.set_widget('bar', 'notes')
|
||||
self.assertIsInstance(form.widgets['bar'], widgets.NotesWidget)
|
||||
self.assertNotIn("bar", form.widgets)
|
||||
form.set_widget("bar", "notes")
|
||||
self.assertIsInstance(form.widgets["bar"], widgets.NotesWidget)
|
||||
|
||||
def test_make_widget(self):
|
||||
form = self.make_form(fields=['foo', 'bar'])
|
||||
form = self.make_form(fields=["foo", "bar"])
|
||||
|
||||
# notes
|
||||
widget = form.make_widget('notes')
|
||||
widget = form.make_widget("notes")
|
||||
self.assertIsInstance(widget, widgets.NotesWidget)
|
||||
|
||||
# invalid
|
||||
widget = form.make_widget('fdajvdafjjf')
|
||||
widget = form.make_widget("fdajvdafjjf")
|
||||
self.assertIsNone(widget)
|
||||
|
||||
def test_set_default_widgets(self):
|
||||
model = self.app.model
|
||||
|
||||
# no defaults for "plain" schema
|
||||
form = self.make_form(fields=['foo', 'bar'])
|
||||
form = self.make_form(fields=["foo", "bar"])
|
||||
self.assertEqual(form.widgets, {})
|
||||
|
||||
# no defaults for "plain" mapped class
|
||||
|
|
@ -184,52 +189,56 @@ class TestForm(TestCase):
|
|||
|
||||
# widget set for datetime mapped field
|
||||
form = self.make_form(model_class=model.Upgrade)
|
||||
self.assertIn('created', form.widgets)
|
||||
self.assertIsNot(form.widgets['created'], MyWidget)
|
||||
self.assertNotIsInstance(form.widgets['created'], MyWidget)
|
||||
self.assertIn("created", form.widgets)
|
||||
self.assertIsNot(form.widgets["created"], MyWidget)
|
||||
self.assertNotIsInstance(form.widgets["created"], MyWidget)
|
||||
|
||||
# widget *not* set for datetime, if override present
|
||||
form = self.make_form(model_class=model.Upgrade,
|
||||
widgets={'created': MyWidget()})
|
||||
self.assertIn('created', form.widgets)
|
||||
self.assertIsInstance(form.widgets['created'], MyWidget)
|
||||
form = self.make_form(
|
||||
model_class=model.Upgrade, widgets={"created": MyWidget()}
|
||||
)
|
||||
self.assertIn("created", form.widgets)
|
||||
self.assertIsInstance(form.widgets["created"], MyWidget)
|
||||
|
||||
# mock up a table with all relevant column types
|
||||
class Whatever(model.Base):
|
||||
__tablename__ = 'whatever'
|
||||
__tablename__ = "whatever"
|
||||
id = sa.Column(sa.Integer(), primary_key=True)
|
||||
date = sa.Column(sa.Date())
|
||||
date_time = sa.Column(sa.DateTime())
|
||||
|
||||
# widget set for all known types
|
||||
form = self.make_form(model_class=Whatever)
|
||||
self.assertIsInstance(form.widgets['date'], widgets.WuttaDateWidget)
|
||||
self.assertIsInstance(form.widgets['date_time'], widgets.WuttaDateTimeWidget)
|
||||
self.assertIsInstance(form.widgets["date"], widgets.WuttaDateWidget)
|
||||
self.assertIsInstance(form.widgets["date_time"], widgets.WuttaDateTimeWidget)
|
||||
|
||||
def test_set_grid(self):
|
||||
form = self.make_form(fields=['foo', 'bar'])
|
||||
self.assertNotIn('foo', form.widgets)
|
||||
self.assertNotIn('foogrid', form.grid_vue_context)
|
||||
form = self.make_form(fields=["foo", "bar"])
|
||||
self.assertNotIn("foo", form.widgets)
|
||||
self.assertNotIn("foogrid", form.grid_vue_context)
|
||||
|
||||
grid = Grid(self.request, key='foogrid',
|
||||
columns=['a', 'b'],
|
||||
data=[{'a': 1, 'b': 2}, {'a': 3, 'b': 4}])
|
||||
grid = Grid(
|
||||
self.request,
|
||||
key="foogrid",
|
||||
columns=["a", "b"],
|
||||
data=[{"a": 1, "b": 2}, {"a": 3, "b": 4}],
|
||||
)
|
||||
|
||||
form.set_grid('foo', grid)
|
||||
self.assertIn('foo', form.widgets)
|
||||
self.assertIsInstance(form.widgets['foo'], widgets.GridWidget)
|
||||
self.assertIn('foogrid', form.grid_vue_context)
|
||||
form.set_grid("foo", grid)
|
||||
self.assertIn("foo", form.widgets)
|
||||
self.assertIsInstance(form.widgets["foo"], widgets.GridWidget)
|
||||
self.assertIn("foogrid", form.grid_vue_context)
|
||||
|
||||
def test_set_validator(self):
|
||||
form = self.make_form(fields=['foo', 'bar'])
|
||||
form = self.make_form(fields=["foo", "bar"])
|
||||
self.assertEqual(form.validators, {})
|
||||
|
||||
def validate1(node, value):
|
||||
pass
|
||||
|
||||
# basic
|
||||
form.set_validator('foo', validate1)
|
||||
self.assertIs(form.validators['foo'], validate1)
|
||||
form.set_validator("foo", validate1)
|
||||
self.assertIs(form.validators["foo"], validate1)
|
||||
|
||||
def validate2(node, value):
|
||||
pass
|
||||
|
|
@ -237,18 +246,18 @@ class TestForm(TestCase):
|
|||
# schema is updated if already present
|
||||
schema = form.get_schema()
|
||||
self.assertIsNotNone(schema)
|
||||
self.assertIs(schema['foo'].validator, validate1)
|
||||
form.set_validator('foo', validate2)
|
||||
self.assertIs(form.validators['foo'], validate2)
|
||||
self.assertIs(schema['foo'].validator, validate2)
|
||||
self.assertIs(schema["foo"].validator, validate1)
|
||||
form.set_validator("foo", validate2)
|
||||
self.assertIs(form.validators["foo"], validate2)
|
||||
self.assertIs(schema["foo"].validator, validate2)
|
||||
|
||||
def test_set_default(self):
|
||||
form = self.make_form(fields=['foo', 'bar'])
|
||||
form = self.make_form(fields=["foo", "bar"])
|
||||
self.assertEqual(form.defaults, {})
|
||||
|
||||
# basic
|
||||
form.set_default('foo', 42)
|
||||
self.assertEqual(form.defaults['foo'], 42)
|
||||
form.set_default("foo", 42)
|
||||
self.assertEqual(form.defaults["foo"], 42)
|
||||
|
||||
def test_get_schema(self):
|
||||
model = self.app.model
|
||||
|
|
@ -262,10 +271,10 @@ class TestForm(TestCase):
|
|||
self.assertIs(form.get_schema(), schema)
|
||||
|
||||
# schema is auto-generated if fields provided
|
||||
form = self.make_form(fields=['foo', 'bar'])
|
||||
form = self.make_form(fields=["foo", "bar"])
|
||||
schema = form.get_schema()
|
||||
self.assertEqual(len(schema.children), 2)
|
||||
self.assertEqual(schema['foo'].name, 'foo')
|
||||
self.assertEqual(schema["foo"].name, "foo")
|
||||
|
||||
# but auto-generating without fields is not supported
|
||||
form = self.make_form()
|
||||
|
|
@ -276,58 +285,62 @@ class TestForm(TestCase):
|
|||
form = self.make_form(model_class=model.Setting)
|
||||
schema = form.get_schema()
|
||||
self.assertEqual(len(schema.children), 2)
|
||||
self.assertIn('name', schema)
|
||||
self.assertIn('value', schema)
|
||||
self.assertIn("name", schema)
|
||||
self.assertIn("value", schema)
|
||||
|
||||
# but node overrides are honored when auto-generating
|
||||
form = self.make_form(model_class=model.Setting)
|
||||
value_node = colander.SchemaNode(colander.Bool(), name='value')
|
||||
form.set_node('value', value_node)
|
||||
value_node = colander.SchemaNode(colander.Bool(), name="value")
|
||||
form.set_node("value", value_node)
|
||||
schema = form.get_schema()
|
||||
self.assertIs(schema['value'], value_node)
|
||||
self.assertIs(schema["value"], value_node)
|
||||
|
||||
# schema is auto-generated if model_instance provided
|
||||
form = self.make_form(model_instance=model.Setting(name='uhoh'))
|
||||
self.assertEqual(form.fields, ['name', 'value'])
|
||||
form = self.make_form(model_instance=model.Setting(name="uhoh"))
|
||||
self.assertEqual(form.fields, ["name", "value"])
|
||||
self.assertIsNone(form.schema)
|
||||
# nb. force method to get new fields
|
||||
del form.fields
|
||||
schema = form.get_schema()
|
||||
self.assertEqual(len(schema.children), 2)
|
||||
self.assertIn('name', schema)
|
||||
self.assertIn('value', schema)
|
||||
self.assertIn("name", schema)
|
||||
self.assertIn("value", schema)
|
||||
|
||||
# ColanderAlchemy schema still has *all* requested fields
|
||||
form = self.make_form(model_instance=model.Setting(name='uhoh'),
|
||||
fields=['name', 'value', 'foo', 'bar'])
|
||||
self.assertEqual(form.fields, ['name', 'value', 'foo', 'bar'])
|
||||
form = self.make_form(
|
||||
model_instance=model.Setting(name="uhoh"),
|
||||
fields=["name", "value", "foo", "bar"],
|
||||
)
|
||||
self.assertEqual(form.fields, ["name", "value", "foo", "bar"])
|
||||
self.assertIsNone(form.schema)
|
||||
schema = form.get_schema()
|
||||
self.assertEqual(len(schema.children), 4)
|
||||
self.assertIn('name', schema)
|
||||
self.assertIn('value', schema)
|
||||
self.assertIn('foo', schema)
|
||||
self.assertIn('bar', schema)
|
||||
self.assertIn("name", schema)
|
||||
self.assertIn("value", schema)
|
||||
self.assertIn("foo", schema)
|
||||
self.assertIn("bar", schema)
|
||||
|
||||
# schema nodes are required by default
|
||||
form = self.make_form(fields=['foo', 'bar'])
|
||||
form = self.make_form(fields=["foo", "bar"])
|
||||
schema = form.get_schema()
|
||||
self.assertIs(schema['foo'].missing, colander.required)
|
||||
self.assertIs(schema['bar'].missing, colander.required)
|
||||
self.assertIs(schema["foo"].missing, colander.required)
|
||||
self.assertIs(schema["bar"].missing, colander.required)
|
||||
|
||||
# but fields can be marked *not* required
|
||||
form = self.make_form(fields=['foo', 'bar'])
|
||||
form.set_required('bar', False)
|
||||
form = self.make_form(fields=["foo", "bar"])
|
||||
form.set_required("bar", False)
|
||||
schema = form.get_schema()
|
||||
self.assertIs(schema['foo'].missing, colander.required)
|
||||
self.assertIs(schema['bar'].missing, colander.null)
|
||||
self.assertIs(schema["foo"].missing, colander.required)
|
||||
self.assertIs(schema["bar"].missing, colander.null)
|
||||
|
||||
# validator overrides are honored
|
||||
def validate(node, value): pass
|
||||
def validate(node, value):
|
||||
pass
|
||||
|
||||
form = self.make_form(model_class=model.Setting)
|
||||
form.set_validator('name', validate)
|
||||
form.set_validator("name", validate)
|
||||
schema = form.get_schema()
|
||||
self.assertIs(schema['name'].validator, validate)
|
||||
self.assertIs(schema["name"].validator, validate)
|
||||
|
||||
# validator can be set for whole form
|
||||
form = self.make_form(model_class=model.Setting)
|
||||
|
|
@ -340,9 +353,9 @@ class TestForm(TestCase):
|
|||
|
||||
# default value overrides are honored
|
||||
form = self.make_form(model_class=model.Setting)
|
||||
form.set_default('name', 'foo')
|
||||
form.set_default("name", "foo")
|
||||
schema = form.get_schema()
|
||||
self.assertEqual(schema['name'].default, 'foo')
|
||||
self.assertEqual(schema["name"].default, "foo")
|
||||
|
||||
def test_get_deform(self):
|
||||
model = self.app.model
|
||||
|
|
@ -350,139 +363,142 @@ class TestForm(TestCase):
|
|||
|
||||
# basic
|
||||
form = self.make_form(schema=schema)
|
||||
self.assertFalse(hasattr(form, 'deform_form'))
|
||||
self.assertFalse(hasattr(form, "deform_form"))
|
||||
dform = form.get_deform()
|
||||
self.assertIsInstance(dform, deform.Form)
|
||||
self.assertIs(form.deform_form, dform)
|
||||
|
||||
# with model instance as dict
|
||||
myobj = {'foo': 'one', 'bar': 'two'}
|
||||
myobj = {"foo": "one", "bar": "two"}
|
||||
form = self.make_form(schema=schema, model_instance=myobj)
|
||||
dform = form.get_deform()
|
||||
self.assertEqual(dform.cstruct, myobj)
|
||||
|
||||
# with sqlalchemy model instance
|
||||
myobj = model.Setting(name='foo', value='bar')
|
||||
myobj = model.Setting(name="foo", value="bar")
|
||||
form = self.make_form(model_instance=myobj)
|
||||
dform = form.get_deform()
|
||||
self.assertEqual(dform.cstruct, {'name': 'foo', 'value': 'bar'})
|
||||
self.assertEqual(dform.cstruct, {"name": "foo", "value": "bar"})
|
||||
|
||||
# sqlalchemy instance with null value
|
||||
myobj = model.Setting(name='foo', value=None)
|
||||
myobj = model.Setting(name="foo", value=None)
|
||||
form = self.make_form(model_instance=myobj)
|
||||
dform = form.get_deform()
|
||||
self.assertEqual(dform.cstruct, {'name': 'foo', 'value': colander.null})
|
||||
self.assertEqual(dform.cstruct, {"name": "foo", "value": colander.null})
|
||||
|
||||
def test_get_cancel_url(self):
|
||||
|
||||
# is referrer by default
|
||||
form = self.make_form()
|
||||
self.request.get_referrer = MagicMock(return_value='/cancel-default')
|
||||
self.assertEqual(form.get_cancel_url(), '/cancel-default')
|
||||
self.request.get_referrer = MagicMock(return_value="/cancel-default")
|
||||
self.assertEqual(form.get_cancel_url(), "/cancel-default")
|
||||
del self.request.get_referrer
|
||||
|
||||
# or can be static URL
|
||||
form = self.make_form(cancel_url='/cancel-static')
|
||||
self.assertEqual(form.get_cancel_url(), '/cancel-static')
|
||||
form = self.make_form(cancel_url="/cancel-static")
|
||||
self.assertEqual(form.get_cancel_url(), "/cancel-static")
|
||||
|
||||
# or can be fallback URL (nb. 'NOPE' indicates no referrer)
|
||||
form = self.make_form(cancel_url_fallback='/cancel-fallback')
|
||||
self.request.get_referrer = MagicMock(return_value='NOPE')
|
||||
self.assertEqual(form.get_cancel_url(), '/cancel-fallback')
|
||||
form = self.make_form(cancel_url_fallback="/cancel-fallback")
|
||||
self.request.get_referrer = MagicMock(return_value="NOPE")
|
||||
self.assertEqual(form.get_cancel_url(), "/cancel-fallback")
|
||||
del self.request.get_referrer
|
||||
|
||||
# or can be referrer fallback, i.e. home page
|
||||
form = self.make_form()
|
||||
|
||||
def get_referrer(default=None):
|
||||
if default == 'NOPE':
|
||||
return 'NOPE'
|
||||
return '/home-page'
|
||||
if default == "NOPE":
|
||||
return "NOPE"
|
||||
return "/home-page"
|
||||
|
||||
self.request.get_referrer = get_referrer
|
||||
self.assertEqual(form.get_cancel_url(), '/home-page')
|
||||
self.assertEqual(form.get_cancel_url(), "/home-page")
|
||||
del self.request.get_referrer
|
||||
|
||||
def test_get_label(self):
|
||||
form = self.make_form(fields=['foo', 'bar'])
|
||||
self.assertEqual(form.get_label('foo'), "Foo")
|
||||
form.set_label('foo', "Baz")
|
||||
self.assertEqual(form.get_label('foo'), "Baz")
|
||||
form = self.make_form(fields=["foo", "bar"])
|
||||
self.assertEqual(form.get_label("foo"), "Foo")
|
||||
form.set_label("foo", "Baz")
|
||||
self.assertEqual(form.get_label("foo"), "Baz")
|
||||
|
||||
def test_set_label(self):
|
||||
form = self.make_form(fields=['foo', 'bar'])
|
||||
self.assertEqual(form.get_label('foo'), "Foo")
|
||||
form.set_label('foo', "Baz")
|
||||
self.assertEqual(form.get_label('foo'), "Baz")
|
||||
form = self.make_form(fields=["foo", "bar"])
|
||||
self.assertEqual(form.get_label("foo"), "Foo")
|
||||
form.set_label("foo", "Baz")
|
||||
self.assertEqual(form.get_label("foo"), "Baz")
|
||||
|
||||
# schema should be updated when setting label
|
||||
schema = self.make_schema()
|
||||
form = self.make_form(schema=schema)
|
||||
form.set_label('foo', "Woohoo")
|
||||
self.assertEqual(form.get_label('foo'), "Woohoo")
|
||||
self.assertEqual(schema['foo'].title, "Woohoo")
|
||||
form.set_label("foo", "Woohoo")
|
||||
self.assertEqual(form.get_label("foo"), "Woohoo")
|
||||
self.assertEqual(schema["foo"].title, "Woohoo")
|
||||
|
||||
def test_readonly_fields(self):
|
||||
form = self.make_form(fields=['foo', 'bar'])
|
||||
form = self.make_form(fields=["foo", "bar"])
|
||||
self.assertEqual(form.readonly_fields, set())
|
||||
self.assertFalse(form.is_readonly('foo'))
|
||||
self.assertFalse(form.is_readonly("foo"))
|
||||
|
||||
form.set_readonly('foo')
|
||||
self.assertEqual(form.readonly_fields, {'foo'})
|
||||
self.assertTrue(form.is_readonly('foo'))
|
||||
self.assertFalse(form.is_readonly('bar'))
|
||||
form.set_readonly("foo")
|
||||
self.assertEqual(form.readonly_fields, {"foo"})
|
||||
self.assertTrue(form.is_readonly("foo"))
|
||||
self.assertFalse(form.is_readonly("bar"))
|
||||
|
||||
form.set_readonly('bar')
|
||||
self.assertEqual(form.readonly_fields, {'foo', 'bar'})
|
||||
self.assertTrue(form.is_readonly('foo'))
|
||||
self.assertTrue(form.is_readonly('bar'))
|
||||
form.set_readonly("bar")
|
||||
self.assertEqual(form.readonly_fields, {"foo", "bar"})
|
||||
self.assertTrue(form.is_readonly("foo"))
|
||||
self.assertTrue(form.is_readonly("bar"))
|
||||
|
||||
form.set_readonly('foo', False)
|
||||
self.assertEqual(form.readonly_fields, {'bar'})
|
||||
self.assertFalse(form.is_readonly('foo'))
|
||||
self.assertTrue(form.is_readonly('bar'))
|
||||
form.set_readonly("foo", False)
|
||||
self.assertEqual(form.readonly_fields, {"bar"})
|
||||
self.assertFalse(form.is_readonly("foo"))
|
||||
self.assertTrue(form.is_readonly("bar"))
|
||||
|
||||
def test_required_fields(self):
|
||||
form = self.make_form(fields=['foo', 'bar'])
|
||||
form = self.make_form(fields=["foo", "bar"])
|
||||
self.assertEqual(form.required_fields, {})
|
||||
self.assertIsNone(form.is_required('foo'))
|
||||
self.assertIsNone(form.is_required("foo"))
|
||||
|
||||
form.set_required('foo')
|
||||
self.assertEqual(form.required_fields, {'foo': True})
|
||||
self.assertTrue(form.is_required('foo'))
|
||||
self.assertIsNone(form.is_required('bar'))
|
||||
form.set_required("foo")
|
||||
self.assertEqual(form.required_fields, {"foo": True})
|
||||
self.assertTrue(form.is_required("foo"))
|
||||
self.assertIsNone(form.is_required("bar"))
|
||||
|
||||
form.set_required('bar')
|
||||
self.assertEqual(form.required_fields, {'foo': True, 'bar': True})
|
||||
self.assertTrue(form.is_required('foo'))
|
||||
self.assertTrue(form.is_required('bar'))
|
||||
form.set_required("bar")
|
||||
self.assertEqual(form.required_fields, {"foo": True, "bar": True})
|
||||
self.assertTrue(form.is_required("foo"))
|
||||
self.assertTrue(form.is_required("bar"))
|
||||
|
||||
form.set_required('foo', False)
|
||||
self.assertEqual(form.required_fields, {'foo': False, 'bar': True})
|
||||
self.assertFalse(form.is_required('foo'))
|
||||
self.assertTrue(form.is_required('bar'))
|
||||
form.set_required("foo", False)
|
||||
self.assertEqual(form.required_fields, {"foo": False, "bar": True})
|
||||
self.assertFalse(form.is_required("foo"))
|
||||
self.assertTrue(form.is_required("bar"))
|
||||
|
||||
def test_render_vue_tag(self):
|
||||
schema = self.make_schema()
|
||||
form = self.make_form(schema=schema)
|
||||
html = form.render_vue_tag()
|
||||
self.assertEqual(html, '<wutta-form></wutta-form>')
|
||||
self.assertEqual(html, "<wutta-form></wutta-form>")
|
||||
|
||||
def test_render_vue_template(self):
|
||||
self.pyramid_config.include('pyramid_mako')
|
||||
self.pyramid_config.add_subscriber('wuttaweb.subscribers.before_render',
|
||||
'pyramid.events.BeforeRender')
|
||||
self.pyramid_config.include("pyramid_mako")
|
||||
self.pyramid_config.add_subscriber(
|
||||
"wuttaweb.subscribers.before_render", "pyramid.events.BeforeRender"
|
||||
)
|
||||
|
||||
# form button is disabled on @submit by default
|
||||
schema = self.make_schema()
|
||||
form = self.make_form(schema=schema, cancel_url='/')
|
||||
form = self.make_form(schema=schema, cancel_url="/")
|
||||
html = form.render_vue_template()
|
||||
self.assertIn('<script type="text/x-template" id="wutta-form-template">', html)
|
||||
self.assertIn('@submit', html)
|
||||
self.assertIn("@submit", html)
|
||||
|
||||
# but not if form is configured otherwise
|
||||
form = self.make_form(schema=schema, auto_disable_submit=False, cancel_url='/')
|
||||
form = self.make_form(schema=schema, auto_disable_submit=False, cancel_url="/")
|
||||
html = form.render_vue_template()
|
||||
self.assertIn('<script type="text/x-template" id="wutta-form-template">', html)
|
||||
self.assertNotIn('@submit', html)
|
||||
self.assertNotIn("@submit", html)
|
||||
|
||||
def test_add_grid_vue_context(self):
|
||||
form = self.make_form()
|
||||
|
|
@ -492,76 +508,84 @@ class TestForm(TestCase):
|
|||
self.assertRaises(ValueError, form.add_grid_vue_context, grid)
|
||||
|
||||
# otherwise it works
|
||||
grid = Grid(self.request, key='foo')
|
||||
grid = Grid(self.request, key="foo")
|
||||
self.assertEqual(len(form.grid_vue_context), 0)
|
||||
form.add_grid_vue_context(grid)
|
||||
self.assertEqual(len(form.grid_vue_context), 1)
|
||||
self.assertIn('foo', form.grid_vue_context)
|
||||
self.assertEqual(form.grid_vue_context['foo'], {
|
||||
'data': [],
|
||||
'row_classes': {},
|
||||
})
|
||||
self.assertIn("foo", form.grid_vue_context)
|
||||
self.assertEqual(
|
||||
form.grid_vue_context["foo"],
|
||||
{
|
||||
"data": [],
|
||||
"row_classes": {},
|
||||
},
|
||||
)
|
||||
|
||||
# calling again with same key will replace data
|
||||
records = [{'foo': 1}, {'foo': 2}]
|
||||
grid = Grid(self.request, key='foo', columns=['foo'], data=records)
|
||||
records = [{"foo": 1}, {"foo": 2}]
|
||||
grid = Grid(self.request, key="foo", columns=["foo"], data=records)
|
||||
form.add_grid_vue_context(grid)
|
||||
self.assertEqual(len(form.grid_vue_context), 1)
|
||||
self.assertIn('foo', form.grid_vue_context)
|
||||
self.assertEqual(form.grid_vue_context['foo'], {
|
||||
'data': records,
|
||||
'row_classes': {},
|
||||
})
|
||||
self.assertIn("foo", form.grid_vue_context)
|
||||
self.assertEqual(
|
||||
form.grid_vue_context["foo"],
|
||||
{
|
||||
"data": records,
|
||||
"row_classes": {},
|
||||
},
|
||||
)
|
||||
|
||||
def test_render_vue_finalize(self):
|
||||
form = self.make_form()
|
||||
html = form.render_vue_finalize()
|
||||
self.assertIn('<script>', html)
|
||||
self.assertIn("<script>", html)
|
||||
self.assertIn("Vue.component('wutta-form', WuttaForm)", html)
|
||||
|
||||
def test_render_vue_field(self):
|
||||
self.pyramid_config.include('pyramid_deform')
|
||||
self.pyramid_config.include("pyramid_deform")
|
||||
schema = self.make_schema()
|
||||
form = self.make_form(schema=schema)
|
||||
dform = form.get_deform()
|
||||
|
||||
# typical
|
||||
html = form.render_vue_field('foo')
|
||||
html = form.render_vue_field("foo")
|
||||
self.assertIn('<b-field :horizontal="true" label="Foo">', html)
|
||||
self.assertIn('<b-input name="foo"', html)
|
||||
# nb. no error message
|
||||
self.assertNotIn('message', html)
|
||||
self.assertNotIn("message", html)
|
||||
|
||||
# readonly
|
||||
html = form.render_vue_field('foo', readonly=True)
|
||||
html = form.render_vue_field("foo", readonly=True)
|
||||
self.assertIn('<b-field :horizontal="true" label="Foo">', html)
|
||||
self.assertNotIn('<b-input name="foo"', html)
|
||||
# nb. no error message
|
||||
self.assertNotIn('message', html)
|
||||
self.assertNotIn("message", html)
|
||||
|
||||
# with error message
|
||||
with patch.object(form, 'get_field_errors', return_value=['something is wrong']):
|
||||
html = form.render_vue_field('foo')
|
||||
self.assertIn('something is wrong', html)
|
||||
with patch.object(
|
||||
form, "get_field_errors", return_value=["something is wrong"]
|
||||
):
|
||||
html = form.render_vue_field("foo")
|
||||
self.assertIn("something is wrong", html)
|
||||
|
||||
# add another field, but not to deform, so it should still
|
||||
# display but with no widget
|
||||
form.fields.append('zanzibar')
|
||||
html = form.render_vue_field('zanzibar')
|
||||
form.fields.append("zanzibar")
|
||||
html = form.render_vue_field("zanzibar")
|
||||
self.assertIn('<b-field :horizontal="true" label="Zanzibar">', html)
|
||||
self.assertNotIn('<b-input', html)
|
||||
self.assertNotIn("<b-input", html)
|
||||
# nb. no error message
|
||||
self.assertNotIn('message', html)
|
||||
self.assertNotIn("message", html)
|
||||
|
||||
# try that once more but with a model record instance
|
||||
with patch.object(form, 'model_instance', new={'zanzibar': 'omgwtfbbq'}):
|
||||
html = form.render_vue_field('zanzibar')
|
||||
self.assertIn('<b-field', html)
|
||||
with patch.object(form, "model_instance", new={"zanzibar": "omgwtfbbq"}):
|
||||
html = form.render_vue_field("zanzibar")
|
||||
self.assertIn("<b-field", html)
|
||||
self.assertIn('label="Zanzibar"', html)
|
||||
self.assertNotIn('<b-input', html)
|
||||
self.assertIn('>omgwtfbbq<', html)
|
||||
self.assertNotIn("<b-input", html)
|
||||
self.assertIn(">omgwtfbbq<", html)
|
||||
# nb. no error message
|
||||
self.assertNotIn('message', html)
|
||||
self.assertNotIn("message", html)
|
||||
|
||||
def test_get_vue_field_value(self):
|
||||
schema = self.make_schema()
|
||||
|
|
@ -569,20 +593,20 @@ class TestForm(TestCase):
|
|||
|
||||
# TODO: yikes what a hack (?)
|
||||
dform = form.get_deform()
|
||||
dform.set_appstruct({'foo': 'one', 'bar': 'two'})
|
||||
dform.set_appstruct({"foo": "one", "bar": "two"})
|
||||
|
||||
# null for missing field
|
||||
value = form.get_vue_field_value('doesnotexist')
|
||||
value = form.get_vue_field_value("doesnotexist")
|
||||
self.assertIsNone(value)
|
||||
|
||||
# normal value is returned
|
||||
value = form.get_vue_field_value('foo')
|
||||
self.assertEqual(value, 'one')
|
||||
value = form.get_vue_field_value("foo")
|
||||
self.assertEqual(value, "one")
|
||||
|
||||
# but not if we remove field from deform
|
||||
# TODO: what is the use case here again?
|
||||
dform.children.remove(dform['foo'])
|
||||
value = form.get_vue_field_value('foo')
|
||||
dform.children.remove(dform["foo"])
|
||||
value = form.get_vue_field_value("foo")
|
||||
self.assertIsNone(value)
|
||||
|
||||
def test_get_vue_model_data(self):
|
||||
|
|
@ -594,19 +618,22 @@ class TestForm(TestCase):
|
|||
self.assertEqual(len(data), 2)
|
||||
|
||||
# still just 2 fields even if we request more
|
||||
form.set_fields(['foo', 'bar', 'baz'])
|
||||
form.set_fields(["foo", "bar", "baz"])
|
||||
data = form.get_vue_model_data()
|
||||
self.assertEqual(len(data), 2)
|
||||
|
||||
# confirm bool values make it thru as-is
|
||||
schema.add(colander.SchemaNode(colander.Bool(), name='baz'))
|
||||
form = self.make_form(schema=schema, model_instance={
|
||||
'foo': 'one',
|
||||
'bar': 'two',
|
||||
'baz': True,
|
||||
})
|
||||
schema.add(colander.SchemaNode(colander.Bool(), name="baz"))
|
||||
form = self.make_form(
|
||||
schema=schema,
|
||||
model_instance={
|
||||
"foo": "one",
|
||||
"bar": "two",
|
||||
"baz": True,
|
||||
},
|
||||
)
|
||||
data = form.get_vue_model_data()
|
||||
self.assertEqual(list(data.values()), ['one', 'two', True])
|
||||
self.assertEqual(list(data.values()), ["one", "two", True])
|
||||
|
||||
def test_has_global_errors(self):
|
||||
|
||||
|
|
@ -617,8 +644,8 @@ class TestForm(TestCase):
|
|||
schema.validator = fail
|
||||
form = self.make_form(schema=schema)
|
||||
self.assertFalse(form.has_global_errors())
|
||||
self.request.method = 'POST'
|
||||
self.request.POST = {'foo': 'one', 'bar': 'two'}
|
||||
self.request.method = "POST"
|
||||
self.request.POST = {"foo": "one", "bar": "two"}
|
||||
self.assertFalse(form.validate())
|
||||
self.assertTrue(form.has_global_errors())
|
||||
|
||||
|
|
@ -631,8 +658,8 @@ class TestForm(TestCase):
|
|||
schema.validator = fail
|
||||
form = self.make_form(schema=schema)
|
||||
self.assertEqual(form.get_global_errors(), [])
|
||||
self.request.method = 'POST'
|
||||
self.request.POST = {'foo': 'one', 'bar': 'two'}
|
||||
self.request.method = "POST"
|
||||
self.request.POST = {"foo": "one", "bar": "two"}
|
||||
self.assertFalse(form.validate())
|
||||
self.assertTrue(form.get_global_errors(), ["things are bad!"])
|
||||
|
||||
|
|
@ -641,55 +668,55 @@ class TestForm(TestCase):
|
|||
|
||||
# simple 'Required' validation failure
|
||||
form = self.make_form(schema=schema)
|
||||
self.request.method = 'POST'
|
||||
self.request.POST = {'foo': 'one'}
|
||||
self.request.method = "POST"
|
||||
self.request.POST = {"foo": "one"}
|
||||
self.assertFalse(form.validate())
|
||||
errors = form.get_field_errors('bar')
|
||||
self.assertEqual(errors, ['Required'])
|
||||
errors = form.get_field_errors("bar")
|
||||
self.assertEqual(errors, ["Required"])
|
||||
|
||||
# no errors
|
||||
form = self.make_form(schema=schema)
|
||||
self.request.POST = {'foo': 'one', 'bar': 'two'}
|
||||
self.request.POST = {"foo": "one", "bar": "two"}
|
||||
self.assertTrue(form.validate())
|
||||
errors = form.get_field_errors('bar')
|
||||
errors = form.get_field_errors("bar")
|
||||
self.assertEqual(errors, [])
|
||||
|
||||
def test_validate(self):
|
||||
schema = self.make_schema()
|
||||
form = self.make_form(schema=schema)
|
||||
self.assertFalse(hasattr(form, 'validated'))
|
||||
self.assertFalse(hasattr(form, "validated"))
|
||||
|
||||
# will not validate unless request is POST
|
||||
self.request.POST = {'foo': 'blarg', 'bar': 'baz'}
|
||||
self.request.method = 'GET'
|
||||
self.request.POST = {"foo": "blarg", "bar": "baz"}
|
||||
self.request.method = "GET"
|
||||
self.assertFalse(form.validate())
|
||||
self.request.method = 'POST'
|
||||
self.request.method = "POST"
|
||||
data = form.validate()
|
||||
self.assertEqual(data, {'foo': 'blarg', 'bar': 'baz'})
|
||||
self.assertEqual(data, {"foo": "blarg", "bar": "baz"})
|
||||
|
||||
# validating a second time updates form.validated
|
||||
self.request.POST = {'foo': 'BLARG', 'bar': 'BAZ'}
|
||||
self.request.POST = {"foo": "BLARG", "bar": "BAZ"}
|
||||
data = form.validate()
|
||||
self.assertEqual(data, {'foo': 'BLARG', 'bar': 'BAZ'})
|
||||
self.assertEqual(data, {"foo": "BLARG", "bar": "BAZ"})
|
||||
self.assertIs(form.validated, data)
|
||||
|
||||
# bad data does not validate
|
||||
self.request.POST = {'foo': 42, 'bar': None}
|
||||
self.request.POST = {"foo": 42, "bar": None}
|
||||
self.assertFalse(form.validate())
|
||||
dform = form.get_deform()
|
||||
self.assertEqual(len(dform.error.children), 2)
|
||||
self.assertEqual(dform['foo'].errormsg, "Pstruct is not a string")
|
||||
self.assertEqual(dform["foo"].errormsg, "Pstruct is not a string")
|
||||
|
||||
# when a form has readonly fields, validating it will *remove*
|
||||
# those fields from deform/schema as well as final data dict
|
||||
schema = self.make_schema()
|
||||
form = self.make_form(schema=schema)
|
||||
form.set_readonly('foo')
|
||||
self.request.POST = {'foo': 'one', 'bar': 'two'}
|
||||
form.set_readonly("foo")
|
||||
self.request.POST = {"foo": "one", "bar": "two"}
|
||||
data = form.validate()
|
||||
self.assertEqual(data, {'bar': 'two'})
|
||||
self.assertEqual(data, {"bar": "two"})
|
||||
dform = form.get_deform()
|
||||
self.assertNotIn('foo', schema)
|
||||
self.assertNotIn('foo', dform)
|
||||
self.assertIn('bar', schema)
|
||||
self.assertIn('bar', dform)
|
||||
self.assertNotIn("foo", schema)
|
||||
self.assertNotIn("foo", dform)
|
||||
self.assertIn("bar", schema)
|
||||
self.assertIn("bar", dform)
|
||||
|
|
|
|||
|
|
@ -25,17 +25,17 @@ class TestWuttaDateTime(TestCase):
|
|||
result = typ.deserialize(node, colander.null)
|
||||
self.assertIs(result, colander.null)
|
||||
|
||||
result = typ.deserialize(node, '2024-12-11T10:33 PM')
|
||||
result = typ.deserialize(node, "2024-12-11T10:33 PM")
|
||||
self.assertIsInstance(result, datetime.datetime)
|
||||
self.assertEqual(result, datetime.datetime(2024, 12, 11, 22, 33))
|
||||
self.assertIsNone(result.tzinfo)
|
||||
|
||||
result = typ.deserialize(node, '2024-12-11T22:33:00')
|
||||
result = typ.deserialize(node, "2024-12-11T22:33:00")
|
||||
self.assertIsInstance(result, datetime.datetime)
|
||||
self.assertEqual(result, datetime.datetime(2024, 12, 11, 22, 33))
|
||||
self.assertIsNone(result.tzinfo)
|
||||
|
||||
self.assertRaises(colander.Invalid, typ.deserialize, node, 'bogus')
|
||||
self.assertRaises(colander.Invalid, typ.deserialize, node, "bogus")
|
||||
|
||||
|
||||
class TestObjectNode(DataTestCase):
|
||||
|
|
@ -84,20 +84,24 @@ class TestWuttaEnum(WebTestCase):
|
|||
MOCK_STATUS_ONE = 1
|
||||
MOCK_STATUS_TWO = 2
|
||||
MOCK_STATUS = {
|
||||
MOCK_STATUS_ONE: 'one',
|
||||
MOCK_STATUS_TWO: 'two',
|
||||
MOCK_STATUS_ONE: "one",
|
||||
MOCK_STATUS_TWO: "two",
|
||||
}
|
||||
|
||||
|
||||
class TestWuttaDictEnum(WebTestCase):
|
||||
|
||||
def test_widget_maker(self):
|
||||
typ = mod.WuttaDictEnum(self.request, MOCK_STATUS)
|
||||
widget = typ.widget_maker()
|
||||
self.assertIsInstance(widget, widgets.SelectWidget)
|
||||
self.assertEqual(widget.values, [
|
||||
(1, 'one'),
|
||||
(2, 'two'),
|
||||
])
|
||||
self.assertEqual(
|
||||
widget.values,
|
||||
[
|
||||
(1, "one"),
|
||||
(2, "two"),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
class TestWuttaMoney(WebTestCase):
|
||||
|
|
@ -132,13 +136,13 @@ class TestWuttaQuantity(WebTestCase):
|
|||
|
||||
# quantity
|
||||
result = typ.serialize(node, 42)
|
||||
self.assertEqual(result, '42')
|
||||
self.assertEqual(result, "42")
|
||||
result = typ.serialize(node, 42.00)
|
||||
self.assertEqual(result, '42')
|
||||
result = typ.serialize(node, decimal.Decimal('42.00'))
|
||||
self.assertEqual(result, '42')
|
||||
self.assertEqual(result, "42")
|
||||
result = typ.serialize(node, decimal.Decimal("42.00"))
|
||||
self.assertEqual(result, "42")
|
||||
result = typ.serialize(node, 42.13)
|
||||
self.assertEqual(result, '42.13')
|
||||
self.assertEqual(result, "42.13")
|
||||
|
||||
|
||||
class TestObjectRef(DataTestCase):
|
||||
|
|
@ -155,19 +159,19 @@ class TestObjectRef(DataTestCase):
|
|||
|
||||
# passing true yields default empty option
|
||||
typ = mod.ObjectRef(self.request, empty_option=True)
|
||||
self.assertEqual(typ.empty_option, ('', "(none)"))
|
||||
self.assertEqual(typ.empty_option, ("", "(none)"))
|
||||
|
||||
# can set explicitly
|
||||
typ = mod.ObjectRef(self.request, empty_option=('foo', 'bar'))
|
||||
self.assertEqual(typ.empty_option, ('foo', 'bar'))
|
||||
typ = mod.ObjectRef(self.request, empty_option=("foo", "bar"))
|
||||
self.assertEqual(typ.empty_option, ("foo", "bar"))
|
||||
|
||||
# can set just a label
|
||||
typ = mod.ObjectRef(self.request, empty_option="(empty)")
|
||||
self.assertEqual(typ.empty_option, ('', "(empty)"))
|
||||
self.assertEqual(typ.empty_option, ("", "(empty)"))
|
||||
|
||||
def test_model_class(self):
|
||||
typ = mod.ObjectRef(self.request)
|
||||
self.assertRaises(NotImplementedError, getattr, typ, 'model_class')
|
||||
self.assertRaises(NotImplementedError, getattr, typ, "model_class")
|
||||
|
||||
def test_serialize(self):
|
||||
model = self.app.model
|
||||
|
|
@ -188,9 +192,9 @@ class TestObjectRef(DataTestCase):
|
|||
self.assertEqual(value, person.uuid.hex)
|
||||
|
||||
# null w/ empty option
|
||||
typ = mod.ObjectRef(self.request, empty_option=('bad', 'BAD'))
|
||||
typ = mod.ObjectRef(self.request, empty_option=("bad", "BAD"))
|
||||
value = typ.serialize(node, colander.null)
|
||||
self.assertEqual(value, 'bad')
|
||||
self.assertEqual(value, "bad")
|
||||
|
||||
def test_deserialize(self):
|
||||
model = self.app.model
|
||||
|
|
@ -206,8 +210,8 @@ class TestObjectRef(DataTestCase):
|
|||
self.session.add(person)
|
||||
self.session.commit()
|
||||
self.assertIsNotNone(person.uuid)
|
||||
with patch.object(mod.ObjectRef, 'model_class', new=model.Person):
|
||||
with patch.object(mod, 'Session', return_value=self.session):
|
||||
with patch.object(mod.ObjectRef, "model_class", new=model.Person):
|
||||
with patch.object(mod, "Session", return_value=self.session):
|
||||
typ = mod.ObjectRef(self.request)
|
||||
value = typ.deserialize(node, person.uuid)
|
||||
self.assertIs(value, person)
|
||||
|
|
@ -234,14 +238,14 @@ class TestObjectRef(DataTestCase):
|
|||
value = typ.objectify(None)
|
||||
self.assertIsNone(value)
|
||||
|
||||
with patch.object(mod, 'Session', return_value=self.session):
|
||||
with patch.object(mod, "Session", return_value=self.session):
|
||||
|
||||
# model instance
|
||||
person = model.Person(full_name="Betty Boop")
|
||||
self.session.add(person)
|
||||
self.session.commit()
|
||||
self.assertIsNotNone(person.uuid)
|
||||
with patch.object(mod.ObjectRef, 'model_class', new=model.Person):
|
||||
with patch.object(mod.ObjectRef, "model_class", new=model.Person):
|
||||
|
||||
# can specify as uuid
|
||||
typ = mod.ObjectRef(self.request)
|
||||
|
|
@ -254,22 +258,22 @@ class TestObjectRef(DataTestCase):
|
|||
self.assertIs(value, person)
|
||||
|
||||
# error if not found
|
||||
with patch.object(mod.ObjectRef, 'model_class', new=model.Person):
|
||||
with patch.object(mod.ObjectRef, "model_class", new=model.Person):
|
||||
typ = mod.ObjectRef(self.request)
|
||||
self.assertRaises(ValueError, typ.objectify, 'WRONG-UUID')
|
||||
self.assertRaises(ValueError, typ.objectify, "WRONG-UUID")
|
||||
|
||||
def test_get_query(self):
|
||||
model = self.app.model
|
||||
with patch.object(mod.ObjectRef, 'model_class', new=model.Person):
|
||||
with patch.object(mod, 'Session', return_value=self.session):
|
||||
with patch.object(mod.ObjectRef, "model_class", new=model.Person):
|
||||
with patch.object(mod, "Session", return_value=self.session):
|
||||
typ = mod.ObjectRef(self.request)
|
||||
query = typ.get_query()
|
||||
self.assertIsInstance(query, orm.Query)
|
||||
|
||||
def test_sort_query(self):
|
||||
model = self.app.model
|
||||
with patch.object(mod.ObjectRef, 'model_class', new=model.Person):
|
||||
with patch.object(mod, 'Session', return_value=self.session):
|
||||
with patch.object(mod.ObjectRef, "model_class", new=model.Person):
|
||||
with patch.object(mod, "Session", return_value=self.session):
|
||||
typ = mod.ObjectRef(self.request)
|
||||
query = typ.get_query()
|
||||
sorted_query = typ.sort_query(query)
|
||||
|
|
@ -282,16 +286,16 @@ class TestObjectRef(DataTestCase):
|
|||
self.session.commit()
|
||||
|
||||
# basic
|
||||
with patch.object(mod.ObjectRef, 'model_class', new=model.Person):
|
||||
with patch.object(mod, 'Session', return_value=self.session):
|
||||
with patch.object(mod.ObjectRef, "model_class", new=model.Person):
|
||||
with patch.object(mod, "Session", return_value=self.session):
|
||||
typ = mod.ObjectRef(self.request)
|
||||
widget = typ.widget_maker()
|
||||
self.assertEqual(len(widget.values), 1)
|
||||
self.assertEqual(widget.values[0][1], "Betty Boop")
|
||||
|
||||
# empty option
|
||||
with patch.object(mod.ObjectRef, 'model_class', new=model.Person):
|
||||
with patch.object(mod, 'Session', return_value=self.session):
|
||||
with patch.object(mod.ObjectRef, "model_class", new=model.Person):
|
||||
with patch.object(mod, "Session", return_value=self.session):
|
||||
typ = mod.ObjectRef(self.request, empty_option=True)
|
||||
widget = typ.widget_maker()
|
||||
self.assertEqual(len(widget.values), 2)
|
||||
|
|
@ -302,7 +306,7 @@ class TestObjectRef(DataTestCase):
|
|||
class TestPersonRef(WebTestCase):
|
||||
|
||||
def test_sort_query(self):
|
||||
with patch.object(mod, 'Session', return_value=self.session):
|
||||
with patch.object(mod, "Session", return_value=self.session):
|
||||
typ = mod.PersonRef(self.request)
|
||||
query = typ.get_query()
|
||||
self.assertIsInstance(query, orm.Query)
|
||||
|
|
@ -311,9 +315,9 @@ class TestPersonRef(WebTestCase):
|
|||
self.assertIsNot(sorted_query, query)
|
||||
|
||||
def test_get_object_url(self):
|
||||
self.pyramid_config.add_route('people.view', '/people/{uuid}')
|
||||
self.pyramid_config.add_route("people.view", "/people/{uuid}")
|
||||
model = self.app.model
|
||||
with patch.object(mod, 'Session', return_value=self.session):
|
||||
with patch.object(mod, "Session", return_value=self.session):
|
||||
typ = mod.PersonRef(self.request)
|
||||
|
||||
person = model.Person(full_name="Barney Rubble")
|
||||
|
|
@ -322,13 +326,13 @@ class TestPersonRef(WebTestCase):
|
|||
|
||||
url = typ.get_object_url(person)
|
||||
self.assertIsNotNone(url)
|
||||
self.assertIn(f'/people/{person.uuid}', url)
|
||||
self.assertIn(f"/people/{person.uuid}", url)
|
||||
|
||||
|
||||
class TestRoleRef(WebTestCase):
|
||||
|
||||
def test_sort_query(self):
|
||||
with patch.object(mod, 'Session', return_value=self.session):
|
||||
with patch.object(mod, "Session", return_value=self.session):
|
||||
typ = mod.RoleRef(self.request)
|
||||
query = typ.get_query()
|
||||
self.assertIsInstance(query, orm.Query)
|
||||
|
|
@ -337,24 +341,24 @@ class TestRoleRef(WebTestCase):
|
|||
self.assertIsNot(sorted_query, query)
|
||||
|
||||
def test_get_object_url(self):
|
||||
self.pyramid_config.add_route('roles.view', '/roles/{uuid}')
|
||||
self.pyramid_config.add_route("roles.view", "/roles/{uuid}")
|
||||
model = self.app.model
|
||||
with patch.object(mod, 'Session', return_value=self.session):
|
||||
with patch.object(mod, "Session", return_value=self.session):
|
||||
typ = mod.RoleRef(self.request)
|
||||
|
||||
role = model.Role(name='Manager')
|
||||
role = model.Role(name="Manager")
|
||||
self.session.add(role)
|
||||
self.session.commit()
|
||||
|
||||
url = typ.get_object_url(role)
|
||||
self.assertIsNotNone(url)
|
||||
self.assertIn(f'/roles/{role.uuid}', url)
|
||||
self.assertIn(f"/roles/{role.uuid}", url)
|
||||
|
||||
|
||||
class TestUserRef(WebTestCase):
|
||||
|
||||
def test_sort_query(self):
|
||||
with patch.object(mod, 'Session', return_value=self.session):
|
||||
with patch.object(mod, "Session", return_value=self.session):
|
||||
typ = mod.UserRef(self.request)
|
||||
query = typ.get_query()
|
||||
self.assertIsInstance(query, orm.Query)
|
||||
|
|
@ -363,18 +367,18 @@ class TestUserRef(WebTestCase):
|
|||
self.assertIsNot(sorted_query, query)
|
||||
|
||||
def test_get_object_url(self):
|
||||
self.pyramid_config.add_route('users.view', '/users/{uuid}')
|
||||
self.pyramid_config.add_route("users.view", "/users/{uuid}")
|
||||
model = self.app.model
|
||||
with patch.object(mod, 'Session', return_value=self.session):
|
||||
with patch.object(mod, "Session", return_value=self.session):
|
||||
typ = mod.UserRef(self.request)
|
||||
|
||||
user = model.User(username='barney')
|
||||
user = model.User(username="barney")
|
||||
self.session.add(user)
|
||||
self.session.commit()
|
||||
|
||||
url = typ.get_object_url(user)
|
||||
self.assertIsNotNone(url)
|
||||
self.assertIn(f'/users/{user.uuid}', url)
|
||||
self.assertIn(f"/users/{user.uuid}", url)
|
||||
|
||||
|
||||
class TestRoleRefs(DataTestCase):
|
||||
|
|
@ -393,7 +397,7 @@ class TestRoleRefs(DataTestCase):
|
|||
self.session.add(blokes)
|
||||
self.session.commit()
|
||||
|
||||
with patch.object(mod, 'Session', return_value=self.session):
|
||||
with patch.object(mod, "Session", return_value=self.session):
|
||||
|
||||
# with root access, default values include: admin, blokes
|
||||
self.request.is_root = True
|
||||
|
|
@ -427,11 +431,11 @@ class TestPermissions(DataTestCase):
|
|||
|
||||
# supported permissions are morphed to values
|
||||
permissions = {
|
||||
'widgets': {
|
||||
'label': "Widgets",
|
||||
'perms': {
|
||||
'widgets.polish': {
|
||||
'label': "Polish the widgets",
|
||||
"widgets": {
|
||||
"label": "Widgets",
|
||||
"perms": {
|
||||
"widgets.polish": {
|
||||
"label": "Polish the widgets",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -439,7 +443,7 @@ class TestPermissions(DataTestCase):
|
|||
typ = mod.Permissions(self.request, permissions)
|
||||
widget = typ.widget_maker()
|
||||
self.assertEqual(len(widget.values), 1)
|
||||
self.assertEqual(widget.values[0], ('widgets.polish', "Polish the widgets"))
|
||||
self.assertEqual(widget.values[0], ("widgets.polish", "Polish the widgets"))
|
||||
|
||||
|
||||
class TestFileDownload(DataTestCase):
|
||||
|
|
@ -451,10 +455,10 @@ class TestFileDownload(DataTestCase):
|
|||
def test_widget_maker(self):
|
||||
|
||||
# sanity / coverage check
|
||||
typ = mod.FileDownload(self.request, url='/foo')
|
||||
typ = mod.FileDownload(self.request, url="/foo")
|
||||
widget = typ.widget_maker()
|
||||
self.assertIsInstance(widget, widgets.FileDownloadWidget)
|
||||
self.assertEqual(widget.url, '/foo')
|
||||
self.assertEqual(widget.url, "/foo")
|
||||
|
||||
|
||||
class TestEmailRecipients(TestCase):
|
||||
|
|
@ -464,14 +468,14 @@ class TestEmailRecipients(TestCase):
|
|||
node = colander.SchemaNode(typ)
|
||||
|
||||
recips = [
|
||||
'alice@example.com',
|
||||
'bob@example.com',
|
||||
"alice@example.com",
|
||||
"bob@example.com",
|
||||
]
|
||||
recips_str = ', '.join(recips)
|
||||
recips_str = ", ".join(recips)
|
||||
|
||||
# values
|
||||
result = typ.serialize(node, recips_str)
|
||||
self.assertEqual(result, '\n'.join(recips))
|
||||
self.assertEqual(result, "\n".join(recips))
|
||||
|
||||
# null
|
||||
result = typ.serialize(node, colander.null)
|
||||
|
|
@ -482,10 +486,10 @@ class TestEmailRecipients(TestCase):
|
|||
node = colander.SchemaNode(typ)
|
||||
|
||||
recips = [
|
||||
'alice@example.com',
|
||||
'bob@example.com',
|
||||
"alice@example.com",
|
||||
"bob@example.com",
|
||||
]
|
||||
recips_str = ', '.join(recips)
|
||||
recips_str = ", ".join(recips)
|
||||
|
||||
# values
|
||||
result = typ.deserialize(node, recips_str)
|
||||
|
|
|
|||
|
|
@ -11,8 +11,14 @@ from pyramid import testing
|
|||
from wuttaweb import grids
|
||||
from wuttaweb.forms import widgets as mod
|
||||
from wuttaweb.forms import schema
|
||||
from wuttaweb.forms.schema import (FileDownload, PersonRef, RoleRefs, Permissions,
|
||||
WuttaDateTime, EmailRecipients)
|
||||
from wuttaweb.forms.schema import (
|
||||
FileDownload,
|
||||
PersonRef,
|
||||
RoleRefs,
|
||||
Permissions,
|
||||
WuttaDateTime,
|
||||
EmailRecipients,
|
||||
)
|
||||
from wuttaweb.testing import WebTestCase
|
||||
|
||||
|
||||
|
|
@ -21,7 +27,7 @@ class TestObjectRefWidget(WebTestCase):
|
|||
def make_field(self, node, **kwargs):
|
||||
# TODO: not sure why default renderer is in use even though
|
||||
# pyramid_deform was included in setup? but this works..
|
||||
kwargs.setdefault('renderer', deform.Form.default_renderer)
|
||||
kwargs.setdefault("renderer", deform.Form.default_renderer)
|
||||
return deform.Field(node, **kwargs)
|
||||
|
||||
def make_widget(self, **kwargs):
|
||||
|
|
@ -33,14 +39,14 @@ class TestObjectRefWidget(WebTestCase):
|
|||
self.session.add(person)
|
||||
self.session.commit()
|
||||
|
||||
with patch.object(schema, 'Session', return_value=self.session):
|
||||
with patch.object(schema, "Session", return_value=self.session):
|
||||
|
||||
# standard (editable)
|
||||
node = colander.SchemaNode(PersonRef(self.request))
|
||||
widget = self.make_widget()
|
||||
field = self.make_field(node)
|
||||
html = widget.serialize(field, person.uuid)
|
||||
self.assertIn('<b-select ', html)
|
||||
self.assertIn("<b-select ", html)
|
||||
|
||||
# readonly
|
||||
node = colander.SchemaNode(PersonRef(self.request))
|
||||
|
|
@ -48,17 +54,17 @@ class TestObjectRefWidget(WebTestCase):
|
|||
widget = self.make_widget()
|
||||
field = self.make_field(node)
|
||||
html = widget.serialize(field, person.uuid, readonly=True)
|
||||
self.assertIn('Betty Boop', html)
|
||||
self.assertNotIn('<a', html)
|
||||
self.assertIn("Betty Boop", html)
|
||||
self.assertNotIn("<a", html)
|
||||
|
||||
# with hyperlink
|
||||
node = colander.SchemaNode(PersonRef(self.request))
|
||||
node.model_instance = person
|
||||
widget = self.make_widget(url=lambda p: '/foo')
|
||||
widget = self.make_widget(url=lambda p: "/foo")
|
||||
field = self.make_field(node)
|
||||
html = widget.serialize(field, person.uuid, readonly=True)
|
||||
self.assertIn('Betty Boop', html)
|
||||
self.assertIn('<a', html)
|
||||
self.assertIn("Betty Boop", html)
|
||||
self.assertIn("<a", html)
|
||||
self.assertIn('href="/foo"', html)
|
||||
|
||||
def test_get_template_values(self):
|
||||
|
|
@ -67,24 +73,25 @@ class TestObjectRefWidget(WebTestCase):
|
|||
self.session.add(person)
|
||||
self.session.commit()
|
||||
|
||||
with patch.object(schema, 'Session', return_value=self.session):
|
||||
with patch.object(schema, "Session", return_value=self.session):
|
||||
|
||||
# standard
|
||||
node = colander.SchemaNode(PersonRef(self.request))
|
||||
widget = self.make_widget()
|
||||
field = self.make_field(node)
|
||||
values = widget.get_template_values(field, person.uuid, {})
|
||||
self.assertIn('cstruct', values)
|
||||
self.assertNotIn('url', values)
|
||||
self.assertIn("cstruct", values)
|
||||
self.assertNotIn("url", values)
|
||||
|
||||
# readonly w/ empty option
|
||||
node = colander.SchemaNode(PersonRef(self.request,
|
||||
empty_option=('_empty_', '(empty)')))
|
||||
widget = self.make_widget(readonly=True, url=lambda obj: '/foo')
|
||||
node = colander.SchemaNode(
|
||||
PersonRef(self.request, empty_option=("_empty_", "(empty)"))
|
||||
)
|
||||
widget = self.make_widget(readonly=True, url=lambda obj: "/foo")
|
||||
field = self.make_field(node)
|
||||
values = widget.get_template_values(field, '_empty_', {})
|
||||
self.assertIn('cstruct', values)
|
||||
self.assertNotIn('url', values)
|
||||
values = widget.get_template_values(field, "_empty_", {})
|
||||
self.assertIn("cstruct", values)
|
||||
self.assertNotIn("url", values)
|
||||
|
||||
|
||||
class TestWuttaDateWidget(WebTestCase):
|
||||
|
|
@ -92,7 +99,7 @@ class TestWuttaDateWidget(WebTestCase):
|
|||
def make_field(self, node, **kwargs):
|
||||
# TODO: not sure why default renderer is in use even though
|
||||
# pyramid_deform was included in setup? but this works..
|
||||
kwargs.setdefault('renderer', deform.Form.default_renderer)
|
||||
kwargs.setdefault("renderer", deform.Form.default_renderer)
|
||||
return deform.Field(node, **kwargs)
|
||||
|
||||
def make_widget(self, **kwargs):
|
||||
|
|
@ -108,11 +115,11 @@ class TestWuttaDateWidget(WebTestCase):
|
|||
|
||||
# editable widget has normal picker html
|
||||
result = widget.serialize(field, str(dt))
|
||||
self.assertIn('<wutta-datepicker', result)
|
||||
self.assertIn("<wutta-datepicker", result)
|
||||
|
||||
# readonly is rendered per app convention
|
||||
result = widget.serialize(field, str(dt), readonly=True)
|
||||
self.assertEqual(result, '2025-01-15')
|
||||
self.assertEqual(result, "2025-01-15")
|
||||
|
||||
# now try again with datetime
|
||||
widget = self.make_widget()
|
||||
|
|
@ -120,11 +127,11 @@ class TestWuttaDateWidget(WebTestCase):
|
|||
|
||||
# editable widget has normal picker html
|
||||
result = widget.serialize(field, str(dt))
|
||||
self.assertIn('<wutta-datepicker', result)
|
||||
self.assertIn("<wutta-datepicker", result)
|
||||
|
||||
# readonly is rendered per app convention
|
||||
result = widget.serialize(field, str(dt), readonly=True)
|
||||
self.assertEqual(result, '2025-01-15')
|
||||
self.assertEqual(result, "2025-01-15")
|
||||
|
||||
|
||||
class TestWuttaDateTimeWidget(WebTestCase):
|
||||
|
|
@ -132,7 +139,7 @@ class TestWuttaDateTimeWidget(WebTestCase):
|
|||
def make_field(self, node, **kwargs):
|
||||
# TODO: not sure why default renderer is in use even though
|
||||
# pyramid_deform was included in setup? but this works..
|
||||
kwargs.setdefault('renderer', deform.Form.default_renderer)
|
||||
kwargs.setdefault("renderer", deform.Form.default_renderer)
|
||||
return deform.Field(node, **kwargs)
|
||||
|
||||
def make_widget(self, **kwargs):
|
||||
|
|
@ -146,11 +153,11 @@ class TestWuttaDateTimeWidget(WebTestCase):
|
|||
|
||||
# editable widget has normal picker html
|
||||
result = widget.serialize(field, str(dt))
|
||||
self.assertIn('<wutta-datepicker', result)
|
||||
self.assertIn("<wutta-datepicker", result)
|
||||
|
||||
# readonly is rendered per app convention
|
||||
result = widget.serialize(field, str(dt), readonly=True)
|
||||
self.assertEqual(result, '2024-12-12 13:49+0000')
|
||||
self.assertEqual(result, "2024-12-12 13:49+0000")
|
||||
|
||||
|
||||
class TestWuttaMoneyInputWidget(WebTestCase):
|
||||
|
|
@ -158,7 +165,7 @@ class TestWuttaMoneyInputWidget(WebTestCase):
|
|||
def make_field(self, node, **kwargs):
|
||||
# TODO: not sure why default renderer is in use even though
|
||||
# pyramid_deform was included in setup? but this works..
|
||||
kwargs.setdefault('renderer', deform.Form.default_renderer)
|
||||
kwargs.setdefault("renderer", deform.Form.default_renderer)
|
||||
return deform.Field(node, **kwargs)
|
||||
|
||||
def make_widget(self, **kwargs):
|
||||
|
|
@ -168,19 +175,19 @@ class TestWuttaMoneyInputWidget(WebTestCase):
|
|||
node = colander.SchemaNode(schema.WuttaMoney(self.request))
|
||||
field = self.make_field(node)
|
||||
widget = self.make_widget()
|
||||
amount = decimal.Decimal('12.34')
|
||||
amount = decimal.Decimal("12.34")
|
||||
|
||||
# editable widget has normal text input
|
||||
result = widget.serialize(field, str(amount))
|
||||
self.assertIn('<b-input', result)
|
||||
self.assertIn("<b-input", result)
|
||||
|
||||
# readonly is rendered per app convention
|
||||
result = widget.serialize(field, str(amount), readonly=True)
|
||||
self.assertEqual(result, '<span>$12.34</span>')
|
||||
self.assertEqual(result, "<span>$12.34</span>")
|
||||
|
||||
# readonly w/ null value
|
||||
result = widget.serialize(field, None, readonly=True)
|
||||
self.assertEqual(result, '<span></span>')
|
||||
self.assertEqual(result, "<span></span>")
|
||||
|
||||
|
||||
class TestFileDownloadWidget(WebTestCase):
|
||||
|
|
@ -188,7 +195,7 @@ class TestFileDownloadWidget(WebTestCase):
|
|||
def make_field(self, node, **kwargs):
|
||||
# TODO: not sure why default renderer is in use even though
|
||||
# pyramid_deform was included in setup? but this works..
|
||||
kwargs.setdefault('renderer', deform.Form.default_renderer)
|
||||
kwargs.setdefault("renderer", deform.Form.default_renderer)
|
||||
return deform.Field(node, **kwargs)
|
||||
|
||||
def test_serialize(self):
|
||||
|
|
@ -201,31 +208,31 @@ class TestFileDownloadWidget(WebTestCase):
|
|||
|
||||
# null value
|
||||
html = widget.serialize(field, None, readonly=True)
|
||||
self.assertNotIn('<a ', html)
|
||||
self.assertIn('<span>', html)
|
||||
self.assertNotIn("<a ", html)
|
||||
self.assertIn("<span>", html)
|
||||
|
||||
# path to nonexistent file
|
||||
html = widget.serialize(field, '/this/path/does/not/exist', readonly=True)
|
||||
self.assertNotIn('<a ', html)
|
||||
self.assertIn('<span>', html)
|
||||
html = widget.serialize(field, "/this/path/does/not/exist", readonly=True)
|
||||
self.assertNotIn("<a ", html)
|
||||
self.assertIn("<span>", html)
|
||||
|
||||
# path to actual file
|
||||
datfile = self.write_file('data.txt', "hello\n" * 1000)
|
||||
datfile = self.write_file("data.txt", "hello\n" * 1000)
|
||||
html = widget.serialize(field, datfile, readonly=True)
|
||||
self.assertNotIn('<a ', html)
|
||||
self.assertIn('<span>', html)
|
||||
self.assertIn('data.txt', html)
|
||||
self.assertIn('kB)', html)
|
||||
self.assertNotIn("<a ", html)
|
||||
self.assertIn("<span>", html)
|
||||
self.assertIn("data.txt", html)
|
||||
self.assertIn("kB)", html)
|
||||
|
||||
# path to file, w/ url
|
||||
node = colander.SchemaNode(FileDownload(self.request, url='/download/blarg'))
|
||||
node = colander.SchemaNode(FileDownload(self.request, url="/download/blarg"))
|
||||
field = self.make_field(node)
|
||||
widget = field.widget
|
||||
html = widget.serialize(field, datfile, readonly=True)
|
||||
self.assertNotIn('<span>', html)
|
||||
self.assertNotIn("<span>", html)
|
||||
self.assertIn('<a href="/download/blarg">', html)
|
||||
self.assertIn('data.txt', html)
|
||||
self.assertIn('kB)', html)
|
||||
self.assertIn("data.txt", html)
|
||||
self.assertIn("kB)", html)
|
||||
|
||||
# nb. same readonly output even if we ask for editable
|
||||
html2 = widget.serialize(field, datfile, readonly=False)
|
||||
|
|
@ -237,13 +244,15 @@ class TestGridWidget(WebTestCase):
|
|||
def make_field(self, node, **kwargs):
|
||||
# TODO: not sure why default renderer is in use even though
|
||||
# pyramid_deform was included in setup? but this works..
|
||||
kwargs.setdefault('renderer', deform.Form.default_renderer)
|
||||
kwargs.setdefault("renderer", deform.Form.default_renderer)
|
||||
return deform.Field(node, **kwargs)
|
||||
|
||||
def test_serialize(self):
|
||||
grid = grids.Grid(self.request,
|
||||
columns=['foo', 'bar'],
|
||||
data=[{'foo': 1, 'bar': 2}, {'foo': 3, 'bar': 4}])
|
||||
grid = grids.Grid(
|
||||
self.request,
|
||||
columns=["foo", "bar"],
|
||||
data=[{"foo": 1, "bar": 2}, {"foo": 3, "bar": 4}],
|
||||
)
|
||||
|
||||
node = colander.SchemaNode(colander.String())
|
||||
widget = mod.GridWidget(self.request, grid)
|
||||
|
|
@ -251,7 +260,7 @@ class TestGridWidget(WebTestCase):
|
|||
|
||||
# readonly works okay
|
||||
html = widget.serialize(field, None, readonly=True)
|
||||
self.assertIn('<b-table ', html)
|
||||
self.assertIn("<b-table ", html)
|
||||
|
||||
# but otherwise, error
|
||||
self.assertRaises(NotImplementedError, widget.serialize, field, None)
|
||||
|
|
@ -262,11 +271,11 @@ class TestRoleRefsWidget(WebTestCase):
|
|||
def make_field(self, node, **kwargs):
|
||||
# TODO: not sure why default renderer is in use even though
|
||||
# pyramid_deform was included in setup? but this works..
|
||||
kwargs.setdefault('renderer', deform.Form.default_renderer)
|
||||
kwargs.setdefault("renderer", deform.Form.default_renderer)
|
||||
return deform.Field(node, **kwargs)
|
||||
|
||||
def test_serialize(self):
|
||||
self.pyramid_config.add_route('roles.view', '/roles/{uuid}')
|
||||
self.pyramid_config.add_route("roles.view", "/roles/{uuid}")
|
||||
model = self.app.model
|
||||
auth = self.app.get_auth_handler()
|
||||
admin = auth.get_role_administrator(self.session)
|
||||
|
|
@ -275,7 +284,7 @@ class TestRoleRefsWidget(WebTestCase):
|
|||
self.session.commit()
|
||||
|
||||
# nb. we let the field construct the widget via our type
|
||||
with patch.object(schema, 'Session', return_value=self.session):
|
||||
with patch.object(schema, "Session", return_value=self.session):
|
||||
node = colander.SchemaNode(RoleRefs(self.request))
|
||||
field = self.make_field(node)
|
||||
widget = field.widget
|
||||
|
|
@ -305,16 +314,16 @@ class TestPermissionsWidget(WebTestCase):
|
|||
def make_field(self, node, **kwargs):
|
||||
# TODO: not sure why default renderer is in use even though
|
||||
# pyramid_deform was included in setup? but this works..
|
||||
kwargs.setdefault('renderer', deform.Form.default_renderer)
|
||||
kwargs.setdefault("renderer", deform.Form.default_renderer)
|
||||
return deform.Field(node, **kwargs)
|
||||
|
||||
def test_serialize(self):
|
||||
permissions = {
|
||||
'widgets': {
|
||||
'label': "Widgets",
|
||||
'perms': {
|
||||
'widgets.polish': {
|
||||
'label': "Polish the widgets",
|
||||
"widgets": {
|
||||
"label": "Widgets",
|
||||
"perms": {
|
||||
"widgets.polish": {
|
||||
"label": "Polish the widgets",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -330,7 +339,7 @@ class TestPermissionsWidget(WebTestCase):
|
|||
self.assertNotIn("Polish the widgets", html)
|
||||
|
||||
# readonly output includes the perm if set
|
||||
html = widget.serialize(field, {'widgets.polish'}, readonly=True)
|
||||
html = widget.serialize(field, {"widgets.polish"}, readonly=True)
|
||||
self.assertIn("Polish the widgets", html)
|
||||
|
||||
# editable output always includes the perm
|
||||
|
|
@ -343,7 +352,7 @@ class TestEmailRecipientsWidget(WebTestCase):
|
|||
def make_field(self, node, **kwargs):
|
||||
# TODO: not sure why default renderer is in use even though
|
||||
# pyramid_deform was included in setup? but this works..
|
||||
kwargs.setdefault('renderer', deform.Form.default_renderer)
|
||||
kwargs.setdefault("renderer", deform.Form.default_renderer)
|
||||
return deform.Field(node, **kwargs)
|
||||
|
||||
def test_serialize(self):
|
||||
|
|
@ -352,19 +361,19 @@ class TestEmailRecipientsWidget(WebTestCase):
|
|||
widget = mod.EmailRecipientsWidget()
|
||||
|
||||
recips = [
|
||||
'alice@example.com',
|
||||
'bob@example.com',
|
||||
"alice@example.com",
|
||||
"bob@example.com",
|
||||
]
|
||||
recips_str = ', '.join(recips)
|
||||
recips_str = ", ".join(recips)
|
||||
|
||||
# readonly
|
||||
result = widget.serialize(field, recips_str, readonly=True)
|
||||
self.assertIn('<ul>', result)
|
||||
self.assertIn('<li>alice@example.com</li>', result)
|
||||
self.assertIn("<ul>", result)
|
||||
self.assertIn("<li>alice@example.com</li>", result)
|
||||
|
||||
# editable
|
||||
result = widget.serialize(field, recips_str)
|
||||
self.assertIn('<b-input', result)
|
||||
self.assertIn("<b-input", result)
|
||||
self.assertIn('type="textarea"', result)
|
||||
|
||||
def test_deserialize(self):
|
||||
|
|
@ -373,10 +382,10 @@ class TestEmailRecipientsWidget(WebTestCase):
|
|||
widget = mod.EmailRecipientsWidget()
|
||||
|
||||
recips = [
|
||||
'alice@example.com',
|
||||
'bob@example.com',
|
||||
"alice@example.com",
|
||||
"bob@example.com",
|
||||
]
|
||||
recips_str = ', '.join(recips)
|
||||
recips_str = ", ".join(recips)
|
||||
|
||||
# values
|
||||
result = widget.deserialize(field, recips_str)
|
||||
|
|
@ -392,7 +401,7 @@ class TestBatchIdWidget(WebTestCase):
|
|||
def make_field(self, node, **kwargs):
|
||||
# TODO: not sure why default renderer is in use even though
|
||||
# pyramid_deform was included in setup? but this works..
|
||||
kwargs.setdefault('renderer', deform.Form.default_renderer)
|
||||
kwargs.setdefault("renderer", deform.Form.default_renderer)
|
||||
return deform.Field(node, **kwargs)
|
||||
|
||||
def test_serialize(self):
|
||||
|
|
@ -404,4 +413,4 @@ class TestBatchIdWidget(WebTestCase):
|
|||
self.assertIs(result, colander.null)
|
||||
|
||||
result = widget.serialize(field, 42)
|
||||
self.assertEqual(result, '00000042')
|
||||
self.assertEqual(result, "00000042")
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -21,24 +21,24 @@ class TestGridFilter(WebTestCase):
|
|||
|
||||
model = self.app.model
|
||||
self.sample_data = [
|
||||
{'name': 'foo1', 'value': 'ONE'},
|
||||
{'name': 'foo2', 'value': 'two'},
|
||||
{'name': 'foo3', 'value': 'ggg'},
|
||||
{'name': 'foo4', 'value': 'ggg'},
|
||||
{'name': 'foo5', 'value': 'ggg'},
|
||||
{'name': 'foo6', 'value': 'six'},
|
||||
{'name': 'foo7', 'value': 'seven'},
|
||||
{'name': 'foo8', 'value': 'eight'},
|
||||
{'name': 'foo9', 'value': 'nine'},
|
||||
{"name": "foo1", "value": "ONE"},
|
||||
{"name": "foo2", "value": "two"},
|
||||
{"name": "foo3", "value": "ggg"},
|
||||
{"name": "foo4", "value": "ggg"},
|
||||
{"name": "foo5", "value": "ggg"},
|
||||
{"name": "foo6", "value": "six"},
|
||||
{"name": "foo7", "value": "seven"},
|
||||
{"name": "foo8", "value": "eight"},
|
||||
{"name": "foo9", "value": "nine"},
|
||||
]
|
||||
for setting in self.sample_data:
|
||||
self.app.save_setting(self.session, setting['name'], setting['value'])
|
||||
self.app.save_setting(self.session, setting["name"], setting["value"])
|
||||
self.session.commit()
|
||||
self.sample_query = self.session.query(model.Setting)
|
||||
|
||||
def make_filter(self, model_property, **kwargs):
|
||||
factory = kwargs.pop('factory', mod.GridFilter)
|
||||
kwargs['model_property'] = model_property
|
||||
factory = kwargs.pop("factory", mod.GridFilter)
|
||||
kwargs["model_property"] = model_property
|
||||
return factory(self.request, model_property.key, **kwargs)
|
||||
|
||||
def test_constructor(self):
|
||||
|
|
@ -46,158 +46,193 @@ class TestGridFilter(WebTestCase):
|
|||
|
||||
# verbs is not set by default, but can be set
|
||||
filtr = self.make_filter(model.Setting.name)
|
||||
self.assertFalse(hasattr(filtr, 'verbs'))
|
||||
filtr = self.make_filter(model.Setting.name, verbs=['foo', 'bar'])
|
||||
self.assertEqual(filtr.verbs, ['foo', 'bar'])
|
||||
self.assertFalse(hasattr(filtr, "verbs"))
|
||||
filtr = self.make_filter(model.Setting.name, verbs=["foo", "bar"])
|
||||
self.assertEqual(filtr.verbs, ["foo", "bar"])
|
||||
|
||||
# verb is not set by default, but can be set
|
||||
filtr = self.make_filter(model.Setting.name)
|
||||
self.assertFalse(hasattr(filtr, 'verb'))
|
||||
filtr = self.make_filter(model.Setting.name, verb='foo')
|
||||
self.assertEqual(filtr.verb, 'foo')
|
||||
self.assertFalse(hasattr(filtr, "verb"))
|
||||
filtr = self.make_filter(model.Setting.name, verb="foo")
|
||||
self.assertEqual(filtr.verb, "foo")
|
||||
|
||||
# default verb is not set by default, but can be set
|
||||
filtr = self.make_filter(model.Setting.name)
|
||||
self.assertFalse(hasattr(filtr, 'default_verb'))
|
||||
filtr = self.make_filter(model.Setting.name, default_verb='foo')
|
||||
self.assertEqual(filtr.default_verb, 'foo')
|
||||
self.assertFalse(hasattr(filtr, "default_verb"))
|
||||
filtr = self.make_filter(model.Setting.name, default_verb="foo")
|
||||
self.assertEqual(filtr.default_verb, "foo")
|
||||
|
||||
def test_repr(self):
|
||||
model = self.app.model
|
||||
filtr = self.make_filter(model.Setting.name, factory=mod.GridFilter)
|
||||
self.assertEqual(repr(filtr), "GridFilter(key='name', active=False, verb=None, value=None)")
|
||||
self.assertEqual(
|
||||
repr(filtr), "GridFilter(key='name', active=False, verb=None, value=None)"
|
||||
)
|
||||
|
||||
def test_get_verbs(self):
|
||||
model = self.app.model
|
||||
filtr = self.make_filter(model.Setting.name, factory=mod.AlchemyFilter)
|
||||
self.assertFalse(hasattr(filtr, 'verbs'))
|
||||
self.assertEqual(filtr.default_verbs, ['equal', 'not_equal'])
|
||||
self.assertFalse(hasattr(filtr, "verbs"))
|
||||
self.assertEqual(filtr.default_verbs, ["equal", "not_equal"])
|
||||
|
||||
# by default, returns default verbs (plus 'is_any')
|
||||
self.assertEqual(filtr.get_verbs(), ['equal', 'not_equal', 'is_any'])
|
||||
self.assertEqual(filtr.get_verbs(), ["equal", "not_equal", "is_any"])
|
||||
|
||||
# default verbs can be a callable
|
||||
filtr.default_verbs = lambda: ['foo', 'bar']
|
||||
self.assertEqual(filtr.get_verbs(), ['foo', 'bar', 'is_any'])
|
||||
filtr.default_verbs = lambda: ["foo", "bar"]
|
||||
self.assertEqual(filtr.get_verbs(), ["foo", "bar", "is_any"])
|
||||
|
||||
# uses filtr.verbs if set
|
||||
filtr.verbs = ['is_true', 'is_false']
|
||||
self.assertEqual(filtr.get_verbs(), ['is_true', 'is_false', 'is_any'])
|
||||
filtr.verbs = ["is_true", "is_false"]
|
||||
self.assertEqual(filtr.get_verbs(), ["is_true", "is_false", "is_any"])
|
||||
|
||||
# may add is/null verbs
|
||||
filtr = self.make_filter(model.Setting.name, factory=mod.AlchemyFilter,
|
||||
nullable=True)
|
||||
self.assertEqual(filtr.get_verbs(), ['equal', 'not_equal',
|
||||
'is_null', 'is_not_null',
|
||||
'is_any'])
|
||||
filtr = self.make_filter(
|
||||
model.Setting.name, factory=mod.AlchemyFilter, nullable=True
|
||||
)
|
||||
self.assertEqual(
|
||||
filtr.get_verbs(),
|
||||
["equal", "not_equal", "is_null", "is_not_null", "is_any"],
|
||||
)
|
||||
|
||||
# filtr.verbs can be a callable
|
||||
filtr.nullable = False
|
||||
filtr.verbs = lambda: ['baz', 'blarg']
|
||||
self.assertEqual(filtr.get_verbs(), ['baz', 'blarg', 'is_any'])
|
||||
filtr.verbs = lambda: ["baz", "blarg"]
|
||||
self.assertEqual(filtr.get_verbs(), ["baz", "blarg", "is_any"])
|
||||
|
||||
def test_get_default_verb(self):
|
||||
model = self.app.model
|
||||
filtr = self.make_filter(model.Setting.name, factory=mod.AlchemyFilter)
|
||||
self.assertFalse(hasattr(filtr, 'verbs'))
|
||||
self.assertEqual(filtr.default_verbs, ['equal', 'not_equal'])
|
||||
self.assertEqual(filtr.get_verbs(), ['equal', 'not_equal', 'is_any'])
|
||||
self.assertFalse(hasattr(filtr, "verbs"))
|
||||
self.assertEqual(filtr.default_verbs, ["equal", "not_equal"])
|
||||
self.assertEqual(filtr.get_verbs(), ["equal", "not_equal", "is_any"])
|
||||
|
||||
# returns first verb by default
|
||||
self.assertEqual(filtr.get_default_verb(), 'equal')
|
||||
self.assertEqual(filtr.get_default_verb(), "equal")
|
||||
|
||||
# returns filtr.verb if set
|
||||
filtr.verb = 'foo'
|
||||
self.assertEqual(filtr.get_default_verb(), 'foo')
|
||||
filtr.verb = "foo"
|
||||
self.assertEqual(filtr.get_default_verb(), "foo")
|
||||
|
||||
# returns filtr.default_verb if set
|
||||
# (nb. this overrides filtr.verb since the point of this
|
||||
# method is to return the *default* verb)
|
||||
filtr.default_verb = 'bar'
|
||||
self.assertEqual(filtr.get_default_verb(), 'bar')
|
||||
filtr.default_verb = "bar"
|
||||
self.assertEqual(filtr.get_default_verb(), "bar")
|
||||
|
||||
def test_get_verb_labels(self):
|
||||
model = self.app.model
|
||||
filtr = self.make_filter(model.Setting.name, factory=mod.AlchemyFilter)
|
||||
self.assertFalse(hasattr(filtr, 'verbs'))
|
||||
self.assertEqual(filtr.get_verbs(), ['equal', 'not_equal', 'is_any'])
|
||||
self.assertFalse(hasattr(filtr, "verbs"))
|
||||
self.assertEqual(filtr.get_verbs(), ["equal", "not_equal", "is_any"])
|
||||
|
||||
labels = filtr.get_verb_labels()
|
||||
self.assertIsInstance(labels, dict)
|
||||
self.assertEqual(labels['equal'], "equal to")
|
||||
self.assertEqual(labels['not_equal'], "not equal to")
|
||||
self.assertEqual(labels['is_any'], "is any")
|
||||
self.assertEqual(labels["equal"], "equal to")
|
||||
self.assertEqual(labels["not_equal"], "not equal to")
|
||||
self.assertEqual(labels["is_any"], "is any")
|
||||
|
||||
def test_get_valueless_verbs(self):
|
||||
model = self.app.model
|
||||
filtr = self.make_filter(model.Setting.name, factory=mod.AlchemyFilter)
|
||||
self.assertFalse(hasattr(filtr, 'verbs'))
|
||||
self.assertEqual(filtr.get_verbs(), ['equal', 'not_equal', 'is_any'])
|
||||
self.assertFalse(hasattr(filtr, "verbs"))
|
||||
self.assertEqual(filtr.get_verbs(), ["equal", "not_equal", "is_any"])
|
||||
|
||||
verbs = filtr.get_valueless_verbs()
|
||||
self.assertIsInstance(verbs, list)
|
||||
self.assertIn('is_any', verbs)
|
||||
self.assertIn("is_any", verbs)
|
||||
|
||||
def test_set_choices(self):
|
||||
model = self.app.model
|
||||
|
||||
filtr = self.make_filter(model.Setting.name, factory=mod.AlchemyFilter)
|
||||
self.assertEqual(filtr.choices, {})
|
||||
self.assertEqual(filtr.data_type, 'string')
|
||||
self.assertEqual(filtr.data_type, "string")
|
||||
|
||||
class MockEnum(Enum):
|
||||
FOO = 'foo'
|
||||
BAR = 'bar'
|
||||
FOO = "foo"
|
||||
BAR = "bar"
|
||||
|
||||
filtr.set_choices(MockEnum)
|
||||
self.assertEqual(filtr.choices, OrderedDict([
|
||||
('FOO', 'foo'),
|
||||
('BAR', 'bar'),
|
||||
]))
|
||||
self.assertEqual(filtr.data_type, 'choice')
|
||||
self.assertEqual(
|
||||
filtr.choices,
|
||||
OrderedDict(
|
||||
[
|
||||
("FOO", "foo"),
|
||||
("BAR", "bar"),
|
||||
]
|
||||
),
|
||||
)
|
||||
self.assertEqual(filtr.data_type, "choice")
|
||||
|
||||
filtr.set_choices(None)
|
||||
self.assertEqual(filtr.choices, {})
|
||||
self.assertEqual(filtr.data_type, 'string')
|
||||
self.assertEqual(filtr.data_type, "string")
|
||||
|
||||
def test_normalize_choices(self):
|
||||
model = self.app.model
|
||||
filtr = self.make_filter(model.Setting.name, factory=mod.AlchemyFilter)
|
||||
|
||||
class MockEnum(Enum):
|
||||
FOO = 'foo'
|
||||
BAR = 'bar'
|
||||
FOO = "foo"
|
||||
BAR = "bar"
|
||||
|
||||
choices = filtr.normalize_choices(MockEnum)
|
||||
self.assertEqual(choices, OrderedDict([
|
||||
('FOO', 'foo'),
|
||||
('BAR', 'bar'),
|
||||
]))
|
||||
self.assertEqual(
|
||||
choices,
|
||||
OrderedDict(
|
||||
[
|
||||
("FOO", "foo"),
|
||||
("BAR", "bar"),
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
choices = filtr.normalize_choices(OrderedDict([
|
||||
('first', '1'),
|
||||
('second', '2'),
|
||||
]))
|
||||
self.assertEqual(choices, OrderedDict([
|
||||
('first', '1'),
|
||||
('second', '2'),
|
||||
]))
|
||||
choices = filtr.normalize_choices(
|
||||
OrderedDict(
|
||||
[
|
||||
("first", "1"),
|
||||
("second", "2"),
|
||||
]
|
||||
)
|
||||
)
|
||||
self.assertEqual(
|
||||
choices,
|
||||
OrderedDict(
|
||||
[
|
||||
("first", "1"),
|
||||
("second", "2"),
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
choices = filtr.normalize_choices({
|
||||
'bbb': 'b',
|
||||
'aaa': 'a',
|
||||
})
|
||||
self.assertEqual(choices, OrderedDict([
|
||||
('aaa', 'a'),
|
||||
('bbb', 'b'),
|
||||
]))
|
||||
choices = filtr.normalize_choices(
|
||||
{
|
||||
"bbb": "b",
|
||||
"aaa": "a",
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
choices,
|
||||
OrderedDict(
|
||||
[
|
||||
("aaa", "a"),
|
||||
("bbb", "b"),
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
choices = filtr.normalize_choices(['one', 'two', 'three'])
|
||||
self.assertEqual(choices, OrderedDict([
|
||||
('one', 'one'),
|
||||
('two', 'two'),
|
||||
('three', 'three'),
|
||||
]))
|
||||
choices = filtr.normalize_choices(["one", "two", "three"])
|
||||
self.assertEqual(
|
||||
choices,
|
||||
OrderedDict(
|
||||
[
|
||||
("one", "one"),
|
||||
("two", "two"),
|
||||
("three", "three"),
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
def test_apply_filter(self):
|
||||
model = self.app.model
|
||||
|
|
@ -205,32 +240,46 @@ class TestGridFilter(WebTestCase):
|
|||
|
||||
# default verb used as fallback
|
||||
# self.assertEqual(filtr.default_verb, 'contains')
|
||||
filtr.default_verb = 'contains'
|
||||
filtr.default_verb = "contains"
|
||||
filtr.verb = None
|
||||
with patch.object(filtr, 'filter_contains', side_effect=lambda q, v: q) as filter_contains:
|
||||
filtered_query = filtr.apply_filter(self.sample_query, value='foo')
|
||||
filter_contains.assert_called_once_with(self.sample_query, 'foo')
|
||||
with patch.object(
|
||||
filtr, "filter_contains", side_effect=lambda q, v: q
|
||||
) as filter_contains:
|
||||
filtered_query = filtr.apply_filter(self.sample_query, value="foo")
|
||||
filter_contains.assert_called_once_with(self.sample_query, "foo")
|
||||
self.assertIsNone(filtr.verb)
|
||||
|
||||
# filter verb used as fallback
|
||||
filtr.verb = 'equal'
|
||||
with patch.object(filtr, 'filter_equal', create=True, side_effect=lambda q, v: q) as filter_equal:
|
||||
filtered_query = filtr.apply_filter(self.sample_query, value='foo')
|
||||
filter_equal.assert_called_once_with(self.sample_query, 'foo')
|
||||
filtr.verb = "equal"
|
||||
with patch.object(
|
||||
filtr, "filter_equal", create=True, side_effect=lambda q, v: q
|
||||
) as filter_equal:
|
||||
filtered_query = filtr.apply_filter(self.sample_query, value="foo")
|
||||
filter_equal.assert_called_once_with(self.sample_query, "foo")
|
||||
|
||||
# filter value used as fallback
|
||||
filtr.verb = 'contains'
|
||||
filtr.value = 'blarg'
|
||||
with patch.object(filtr, 'filter_contains', side_effect=lambda q, v: q) as filter_contains:
|
||||
filtr.verb = "contains"
|
||||
filtr.value = "blarg"
|
||||
with patch.object(
|
||||
filtr, "filter_contains", side_effect=lambda q, v: q
|
||||
) as filter_contains:
|
||||
filtered_query = filtr.apply_filter(self.sample_query)
|
||||
filter_contains.assert_called_once_with(self.sample_query, 'blarg')
|
||||
filter_contains.assert_called_once_with(self.sample_query, "blarg")
|
||||
|
||||
# error if invalid verb
|
||||
self.assertRaises(mod.VerbNotSupported, filtr.apply_filter,
|
||||
self.sample_query, verb='doesnotexist')
|
||||
filtr.verbs = ['doesnotexist']
|
||||
self.assertRaises(mod.VerbNotSupported, filtr.apply_filter,
|
||||
self.sample_query, verb='doesnotexist')
|
||||
self.assertRaises(
|
||||
mod.VerbNotSupported,
|
||||
filtr.apply_filter,
|
||||
self.sample_query,
|
||||
verb="doesnotexist",
|
||||
)
|
||||
filtr.verbs = ["doesnotexist"]
|
||||
self.assertRaises(
|
||||
mod.VerbNotSupported,
|
||||
filtr.apply_filter,
|
||||
self.sample_query,
|
||||
verb="doesnotexist",
|
||||
)
|
||||
|
||||
def test_filter_is_any(self):
|
||||
model = self.app.model
|
||||
|
|
@ -250,24 +299,24 @@ class TestAlchemyFilter(WebTestCase):
|
|||
|
||||
model = self.app.model
|
||||
self.sample_data = [
|
||||
{'name': 'foo1', 'value': 'ONE'},
|
||||
{'name': 'foo2', 'value': 'two'},
|
||||
{'name': 'foo3', 'value': 'ggg'},
|
||||
{'name': 'foo4', 'value': 'ggg'},
|
||||
{'name': 'foo5', 'value': 'ggg'},
|
||||
{'name': 'foo6', 'value': 'six'},
|
||||
{'name': 'foo7', 'value': 'seven'},
|
||||
{'name': 'foo8', 'value': 'eight'},
|
||||
{'name': 'foo9', 'value': None},
|
||||
{"name": "foo1", "value": "ONE"},
|
||||
{"name": "foo2", "value": "two"},
|
||||
{"name": "foo3", "value": "ggg"},
|
||||
{"name": "foo4", "value": "ggg"},
|
||||
{"name": "foo5", "value": "ggg"},
|
||||
{"name": "foo6", "value": "six"},
|
||||
{"name": "foo7", "value": "seven"},
|
||||
{"name": "foo8", "value": "eight"},
|
||||
{"name": "foo9", "value": None},
|
||||
]
|
||||
for setting in self.sample_data:
|
||||
self.app.save_setting(self.session, setting['name'], setting['value'])
|
||||
self.app.save_setting(self.session, setting["name"], setting["value"])
|
||||
self.session.commit()
|
||||
self.sample_query = self.session.query(model.Setting)
|
||||
|
||||
def make_filter(self, model_property, **kwargs):
|
||||
factory = kwargs.pop('factory', mod.AlchemyFilter)
|
||||
kwargs['model_property'] = model_property
|
||||
factory = kwargs.pop("factory", mod.AlchemyFilter)
|
||||
kwargs["model_property"] = model_property
|
||||
return factory(self.request, model_property.key, **kwargs)
|
||||
|
||||
def test_filter_equal(self):
|
||||
|
|
@ -280,12 +329,12 @@ class TestAlchemyFilter(WebTestCase):
|
|||
self.assertIs(filtered_query, self.sample_query)
|
||||
|
||||
# nb. by default, *is filtered* by empty string
|
||||
filtered_query = filtr.filter_equal(self.sample_query, '')
|
||||
filtered_query = filtr.filter_equal(self.sample_query, "")
|
||||
self.assertIsNot(filtered_query, self.sample_query)
|
||||
self.assertEqual(filtered_query.count(), 0)
|
||||
|
||||
# filtered by value
|
||||
filtered_query = filtr.filter_equal(self.sample_query, 'ggg')
|
||||
filtered_query = filtr.filter_equal(self.sample_query, "ggg")
|
||||
self.assertIsNot(filtered_query, self.sample_query)
|
||||
self.assertEqual(filtered_query.count(), 3)
|
||||
|
||||
|
|
@ -299,12 +348,12 @@ class TestAlchemyFilter(WebTestCase):
|
|||
self.assertIs(filtered_query, self.sample_query)
|
||||
|
||||
# nb. by default, *is filtered* by empty string
|
||||
filtered_query = filtr.filter_not_equal(self.sample_query, '')
|
||||
filtered_query = filtr.filter_not_equal(self.sample_query, "")
|
||||
self.assertIsNot(filtered_query, self.sample_query)
|
||||
self.assertEqual(filtered_query.count(), 9)
|
||||
|
||||
# filtered by value
|
||||
filtered_query = filtr.filter_not_equal(self.sample_query, 'ggg')
|
||||
filtered_query = filtr.filter_not_equal(self.sample_query, "ggg")
|
||||
self.assertIsNot(filtered_query, self.sample_query)
|
||||
self.assertEqual(filtered_query.count(), 6)
|
||||
|
||||
|
|
@ -336,24 +385,24 @@ class TestStringAlchemyFilter(WebTestCase):
|
|||
|
||||
model = self.app.model
|
||||
self.sample_data = [
|
||||
{'name': 'foo1', 'value': 'ONE'},
|
||||
{'name': 'foo2', 'value': 'two'},
|
||||
{'name': 'foo3', 'value': 'ggg'},
|
||||
{'name': 'foo4', 'value': 'ggg'},
|
||||
{'name': 'foo5', 'value': 'ggg'},
|
||||
{'name': 'foo6', 'value': 'six'},
|
||||
{'name': 'foo7', 'value': 'seven'},
|
||||
{'name': 'foo8', 'value': 'eight'},
|
||||
{'name': 'foo9', 'value': 'nine'},
|
||||
{"name": "foo1", "value": "ONE"},
|
||||
{"name": "foo2", "value": "two"},
|
||||
{"name": "foo3", "value": "ggg"},
|
||||
{"name": "foo4", "value": "ggg"},
|
||||
{"name": "foo5", "value": "ggg"},
|
||||
{"name": "foo6", "value": "six"},
|
||||
{"name": "foo7", "value": "seven"},
|
||||
{"name": "foo8", "value": "eight"},
|
||||
{"name": "foo9", "value": "nine"},
|
||||
]
|
||||
for setting in self.sample_data:
|
||||
self.app.save_setting(self.session, setting['name'], setting['value'])
|
||||
self.app.save_setting(self.session, setting["name"], setting["value"])
|
||||
self.session.commit()
|
||||
self.sample_query = self.session.query(model.Setting)
|
||||
|
||||
def make_filter(self, model_property, **kwargs):
|
||||
factory = kwargs.pop('factory', mod.StringAlchemyFilter)
|
||||
kwargs['model_property'] = model_property
|
||||
factory = kwargs.pop("factory", mod.StringAlchemyFilter)
|
||||
kwargs["model_property"] = model_property
|
||||
return factory(self.request, model_property.key, **kwargs)
|
||||
|
||||
def test_filter_contains(self):
|
||||
|
|
@ -364,11 +413,11 @@ class TestStringAlchemyFilter(WebTestCase):
|
|||
# not filtered for empty value
|
||||
filtered_query = filtr.filter_contains(self.sample_query, None)
|
||||
self.assertIs(filtered_query, self.sample_query)
|
||||
filtered_query = filtr.filter_contains(self.sample_query, '')
|
||||
filtered_query = filtr.filter_contains(self.sample_query, "")
|
||||
self.assertIs(filtered_query, self.sample_query)
|
||||
|
||||
# filtered by value
|
||||
filtered_query = filtr.filter_contains(self.sample_query, 'ggg')
|
||||
filtered_query = filtr.filter_contains(self.sample_query, "ggg")
|
||||
self.assertIsNot(filtered_query, self.sample_query)
|
||||
self.assertEqual(filtered_query.count(), 3)
|
||||
|
||||
|
|
@ -380,11 +429,11 @@ class TestStringAlchemyFilter(WebTestCase):
|
|||
# not filtered for empty value
|
||||
filtered_query = filtr.filter_does_not_contain(self.sample_query, None)
|
||||
self.assertIs(filtered_query, self.sample_query)
|
||||
filtered_query = filtr.filter_does_not_contain(self.sample_query, '')
|
||||
filtered_query = filtr.filter_does_not_contain(self.sample_query, "")
|
||||
self.assertIs(filtered_query, self.sample_query)
|
||||
|
||||
# filtered by value
|
||||
filtered_query = filtr.filter_does_not_contain(self.sample_query, 'ggg')
|
||||
filtered_query = filtr.filter_does_not_contain(self.sample_query, "ggg")
|
||||
self.assertIsNot(filtered_query, self.sample_query)
|
||||
self.assertEqual(filtered_query.count(), 6)
|
||||
|
||||
|
|
@ -392,8 +441,8 @@ class TestStringAlchemyFilter(WebTestCase):
|
|||
class TestIntegerAlchemyFilter(WebTestCase):
|
||||
|
||||
def make_filter(self, model_property, **kwargs):
|
||||
factory = kwargs.pop('factory', mod.IntegerAlchemyFilter)
|
||||
kwargs['model_property'] = model_property
|
||||
factory = kwargs.pop("factory", mod.IntegerAlchemyFilter)
|
||||
kwargs["model_property"] = model_property
|
||||
return factory(self.request, model_property.key, **kwargs)
|
||||
|
||||
def test_coerce_value(self):
|
||||
|
|
@ -402,15 +451,15 @@ class TestIntegerAlchemyFilter(WebTestCase):
|
|||
|
||||
# null
|
||||
self.assertIsNone(filtr.coerce_value(None))
|
||||
self.assertIsNone(filtr.coerce_value(''))
|
||||
self.assertIsNone(filtr.coerce_value(""))
|
||||
|
||||
# typical
|
||||
self.assertEqual(filtr.coerce_value('42'), 42)
|
||||
self.assertEqual(filtr.coerce_value('-42'), -42)
|
||||
self.assertEqual(filtr.coerce_value("42"), 42)
|
||||
self.assertEqual(filtr.coerce_value("-42"), -42)
|
||||
|
||||
# invalid
|
||||
self.assertIsNone(filtr.coerce_value('42.12'))
|
||||
self.assertIsNone(filtr.coerce_value('bogus'))
|
||||
self.assertIsNone(filtr.coerce_value("42.12"))
|
||||
self.assertIsNone(filtr.coerce_value("bogus"))
|
||||
|
||||
|
||||
class TestBooleanAlchemyFilter(WebTestCase):
|
||||
|
|
@ -420,15 +469,9 @@ class TestBooleanAlchemyFilter(WebTestCase):
|
|||
|
||||
model = self.app.model
|
||||
self.sample_data = [
|
||||
{'username': 'alice',
|
||||
'prevent_edit': False,
|
||||
'active': True},
|
||||
{'username': 'bob',
|
||||
'prevent_edit': True,
|
||||
'active': True},
|
||||
{'username': 'charlie',
|
||||
'active': False,
|
||||
'prevent_edit': None},
|
||||
{"username": "alice", "prevent_edit": False, "active": True},
|
||||
{"username": "bob", "prevent_edit": True, "active": True},
|
||||
{"username": "charlie", "active": False, "prevent_edit": None},
|
||||
]
|
||||
for user in self.sample_data:
|
||||
user = model.User(**user)
|
||||
|
|
@ -437,37 +480,46 @@ class TestBooleanAlchemyFilter(WebTestCase):
|
|||
self.sample_query = self.session.query(model.User)
|
||||
|
||||
def make_filter(self, model_property, **kwargs):
|
||||
factory = kwargs.pop('factory', mod.BooleanAlchemyFilter)
|
||||
kwargs['model_property'] = model_property
|
||||
factory = kwargs.pop("factory", mod.BooleanAlchemyFilter)
|
||||
kwargs["model_property"] = model_property
|
||||
return factory(self.request, model_property.key, **kwargs)
|
||||
|
||||
def test_get_verbs(self):
|
||||
model = self.app.model
|
||||
|
||||
# bool field, not nullable
|
||||
filtr = self.make_filter(model.User.active,
|
||||
factory=mod.BooleanAlchemyFilter,
|
||||
nullable=False)
|
||||
self.assertFalse(hasattr(filtr, 'verbs'))
|
||||
self.assertEqual(filtr.default_verbs, ['is_true', 'is_false'])
|
||||
filtr = self.make_filter(
|
||||
model.User.active, factory=mod.BooleanAlchemyFilter, nullable=False
|
||||
)
|
||||
self.assertFalse(hasattr(filtr, "verbs"))
|
||||
self.assertEqual(filtr.default_verbs, ["is_true", "is_false"])
|
||||
|
||||
# by default, returns default verbs (plus 'is_any')
|
||||
self.assertEqual(filtr.get_verbs(), ['is_true', 'is_false', 'is_any'])
|
||||
self.assertEqual(filtr.get_verbs(), ["is_true", "is_false", "is_any"])
|
||||
|
||||
# default verbs can be a callable
|
||||
filtr.default_verbs = lambda: ['foo', 'bar']
|
||||
self.assertEqual(filtr.get_verbs(), ['foo', 'bar', 'is_any'])
|
||||
filtr.default_verbs = lambda: ["foo", "bar"]
|
||||
self.assertEqual(filtr.get_verbs(), ["foo", "bar", "is_any"])
|
||||
|
||||
# bool field, *nullable*
|
||||
filtr = self.make_filter(model.User.active,
|
||||
factory=mod.BooleanAlchemyFilter,
|
||||
nullable=True)
|
||||
self.assertFalse(hasattr(filtr, 'verbs'))
|
||||
self.assertEqual(filtr.default_verbs, ['is_true', 'is_false'])
|
||||
filtr = self.make_filter(
|
||||
model.User.active, factory=mod.BooleanAlchemyFilter, nullable=True
|
||||
)
|
||||
self.assertFalse(hasattr(filtr, "verbs"))
|
||||
self.assertEqual(filtr.default_verbs, ["is_true", "is_false"])
|
||||
|
||||
# effective verbs also include is_false_null
|
||||
self.assertEqual(filtr.get_verbs(), ['is_true', 'is_false', 'is_false_null',
|
||||
'is_null', 'is_not_null', 'is_any'])
|
||||
self.assertEqual(
|
||||
filtr.get_verbs(),
|
||||
[
|
||||
"is_true",
|
||||
"is_false",
|
||||
"is_false_null",
|
||||
"is_null",
|
||||
"is_not_null",
|
||||
"is_any",
|
||||
],
|
||||
)
|
||||
|
||||
def test_coerce_value(self):
|
||||
model = self.app.model
|
||||
|
|
@ -477,11 +529,11 @@ class TestBooleanAlchemyFilter(WebTestCase):
|
|||
|
||||
self.assertTrue(filtr.coerce_value(True))
|
||||
self.assertTrue(filtr.coerce_value(1))
|
||||
self.assertTrue(filtr.coerce_value('1'))
|
||||
self.assertTrue(filtr.coerce_value("1"))
|
||||
|
||||
self.assertFalse(filtr.coerce_value(False))
|
||||
self.assertFalse(filtr.coerce_value(0))
|
||||
self.assertFalse(filtr.coerce_value(''))
|
||||
self.assertFalse(filtr.coerce_value(""))
|
||||
|
||||
def test_filter_is_true(self):
|
||||
model = self.app.model
|
||||
|
|
@ -515,7 +567,7 @@ class TestBooleanAlchemyFilter(WebTestCase):
|
|||
|
||||
|
||||
class TheLocalThing(Base):
|
||||
__tablename__ = 'the_local_thing'
|
||||
__tablename__ = "the_local_thing"
|
||||
id = sa.Column(sa.Integer(), primary_key=True, autoincrement=False)
|
||||
date = sa.Column(sa.DateTime(timezone=True), nullable=True)
|
||||
|
||||
|
|
@ -530,12 +582,12 @@ class TestDateAlchemyFilter(WebTestCase):
|
|||
model.Base.metadata.create_all(bind=self.session.bind)
|
||||
|
||||
self.sample_data = [
|
||||
{'id': 1, 'date': datetime.date(2024, 1, 1)},
|
||||
{'id': 2, 'date': datetime.date(2024, 1, 1)},
|
||||
{'id': 3, 'date': datetime.date(2024, 3, 1)},
|
||||
{'id': 4, 'date': datetime.date(2024, 3, 1)},
|
||||
{'id': 5, 'date': None},
|
||||
{'id': 6, 'date': None},
|
||||
{"id": 1, "date": datetime.date(2024, 1, 1)},
|
||||
{"id": 2, "date": datetime.date(2024, 1, 1)},
|
||||
{"id": 3, "date": datetime.date(2024, 3, 1)},
|
||||
{"id": 4, "date": datetime.date(2024, 3, 1)},
|
||||
{"id": 5, "date": None},
|
||||
{"id": 6, "date": None},
|
||||
]
|
||||
|
||||
for thing in self.sample_data:
|
||||
|
|
@ -546,8 +598,8 @@ class TestDateAlchemyFilter(WebTestCase):
|
|||
self.sample_query = self.session.query(TheLocalThing)
|
||||
|
||||
def make_filter(self, model_property, **kwargs):
|
||||
factory = kwargs.pop('factory', mod.DateAlchemyFilter)
|
||||
kwargs['model_property'] = model_property
|
||||
factory = kwargs.pop("factory", mod.DateAlchemyFilter)
|
||||
kwargs["model_property"] = model_property
|
||||
return factory(self.request, model_property.key, **kwargs)
|
||||
|
||||
def test_coerce_value(self):
|
||||
|
|
@ -562,12 +614,12 @@ class TestDateAlchemyFilter(WebTestCase):
|
|||
self.assertIs(value, result)
|
||||
|
||||
# value as string
|
||||
result = filtr.coerce_value('2024-04-01')
|
||||
result = filtr.coerce_value("2024-04-01")
|
||||
self.assertIsInstance(result, datetime.date)
|
||||
self.assertEqual(result, datetime.date(2024, 4, 1))
|
||||
|
||||
# invalid
|
||||
result = filtr.coerce_value('thisinputisbad')
|
||||
result = filtr.coerce_value("thisinputisbad")
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_greater_than(self):
|
||||
|
|
@ -582,12 +634,14 @@ class TestDateAlchemyFilter(WebTestCase):
|
|||
self.assertEqual(filtered_query.count(), 6)
|
||||
|
||||
# value as date
|
||||
filtered_query = filtr.filter_greater_than(self.sample_query, datetime.date(2024, 2, 1))
|
||||
filtered_query = filtr.filter_greater_than(
|
||||
self.sample_query, datetime.date(2024, 2, 1)
|
||||
)
|
||||
self.assertIsNot(filtered_query, self.sample_query)
|
||||
self.assertEqual(filtered_query.count(), 2)
|
||||
|
||||
# value as string
|
||||
filtered_query = filtr.filter_greater_than(self.sample_query, '2024-02-01')
|
||||
filtered_query = filtr.filter_greater_than(self.sample_query, "2024-02-01")
|
||||
self.assertIsNot(filtered_query, self.sample_query)
|
||||
self.assertEqual(filtered_query.count(), 2)
|
||||
|
||||
|
|
@ -603,17 +657,21 @@ class TestDateAlchemyFilter(WebTestCase):
|
|||
self.assertEqual(filtered_query.count(), 6)
|
||||
|
||||
# value as date (clear of boundary)
|
||||
filtered_query = filtr.filter_greater_equal(self.sample_query, datetime.date(2024, 2, 1))
|
||||
filtered_query = filtr.filter_greater_equal(
|
||||
self.sample_query, datetime.date(2024, 2, 1)
|
||||
)
|
||||
self.assertIsNot(filtered_query, self.sample_query)
|
||||
self.assertEqual(filtered_query.count(), 2)
|
||||
|
||||
# value as date (at boundary)
|
||||
filtered_query = filtr.filter_greater_equal(self.sample_query, datetime.date(2024, 3, 1))
|
||||
filtered_query = filtr.filter_greater_equal(
|
||||
self.sample_query, datetime.date(2024, 3, 1)
|
||||
)
|
||||
self.assertIsNot(filtered_query, self.sample_query)
|
||||
self.assertEqual(filtered_query.count(), 2)
|
||||
|
||||
# value as string
|
||||
filtered_query = filtr.filter_greater_equal(self.sample_query, '2024-01-01')
|
||||
filtered_query = filtr.filter_greater_equal(self.sample_query, "2024-01-01")
|
||||
self.assertIsNot(filtered_query, self.sample_query)
|
||||
self.assertEqual(filtered_query.count(), 4)
|
||||
|
||||
|
|
@ -629,12 +687,14 @@ class TestDateAlchemyFilter(WebTestCase):
|
|||
self.assertEqual(filtered_query.count(), 6)
|
||||
|
||||
# value as date
|
||||
filtered_query = filtr.filter_less_than(self.sample_query, datetime.date(2024, 2, 1))
|
||||
filtered_query = filtr.filter_less_than(
|
||||
self.sample_query, datetime.date(2024, 2, 1)
|
||||
)
|
||||
self.assertIsNot(filtered_query, self.sample_query)
|
||||
self.assertEqual(filtered_query.count(), 2)
|
||||
|
||||
# value as string
|
||||
filtered_query = filtr.filter_less_than(self.sample_query, '2024-04-01')
|
||||
filtered_query = filtr.filter_less_than(self.sample_query, "2024-04-01")
|
||||
self.assertIsNot(filtered_query, self.sample_query)
|
||||
self.assertEqual(filtered_query.count(), 4)
|
||||
|
||||
|
|
@ -650,17 +710,21 @@ class TestDateAlchemyFilter(WebTestCase):
|
|||
self.assertEqual(filtered_query.count(), 6)
|
||||
|
||||
# value as date (clear of boundary)
|
||||
filtered_query = filtr.filter_less_equal(self.sample_query, datetime.date(2024, 2, 1))
|
||||
filtered_query = filtr.filter_less_equal(
|
||||
self.sample_query, datetime.date(2024, 2, 1)
|
||||
)
|
||||
self.assertIsNot(filtered_query, self.sample_query)
|
||||
self.assertEqual(filtered_query.count(), 2)
|
||||
|
||||
# value as date (at boundary)
|
||||
filtered_query = filtr.filter_less_equal(self.sample_query, datetime.date(2024, 3, 1))
|
||||
filtered_query = filtr.filter_less_equal(
|
||||
self.sample_query, datetime.date(2024, 3, 1)
|
||||
)
|
||||
self.assertIsNot(filtered_query, self.sample_query)
|
||||
self.assertEqual(filtered_query.count(), 2)
|
||||
|
||||
# value as string
|
||||
filtered_query = filtr.filter_less_equal(self.sample_query, '2024-04-01')
|
||||
filtered_query = filtr.filter_less_equal(self.sample_query, "2024-04-01")
|
||||
self.assertIsNot(filtered_query, self.sample_query)
|
||||
self.assertEqual(filtered_query.count(), 4)
|
||||
|
||||
|
|
@ -668,5 +732,5 @@ class TestDateAlchemyFilter(WebTestCase):
|
|||
class TestVerbNotSupported(TestCase):
|
||||
|
||||
def test_basic(self):
|
||||
error = mod.VerbNotSupported('equal')
|
||||
error = mod.VerbNotSupported("equal")
|
||||
self.assertEqual(str(error), "unknown filter verb not supported: equal")
|
||||
|
|
|
|||
|
|
@ -36,34 +36,34 @@ class TestMakeWuttaConfig(FileTestCase):
|
|||
def test_basic(self):
|
||||
|
||||
# mock path to config file
|
||||
myconf = self.write_file('my.conf', '')
|
||||
settings = {'wutta.config': myconf}
|
||||
myconf = self.write_file("my.conf", "")
|
||||
settings = {"wutta.config": myconf}
|
||||
|
||||
# can make a config okay
|
||||
config = mod.make_wutta_config(settings)
|
||||
|
||||
# and that config is also stored in settings
|
||||
self.assertIn('wutta_config', settings)
|
||||
self.assertIs(settings['wutta_config'], config)
|
||||
self.assertIn("wutta_config", settings)
|
||||
self.assertIs(settings["wutta_config"], config)
|
||||
|
||||
|
||||
class TestMakePyramidConfig(DataTestCase):
|
||||
|
||||
def test_basic(self):
|
||||
with patch.object(AppHandler, 'make_session', return_value=self.session):
|
||||
settings = {'wutta_config': self.config}
|
||||
with patch.object(AppHandler, "make_session", return_value=self.session):
|
||||
settings = {"wutta_config": self.config}
|
||||
config = mod.make_pyramid_config(settings)
|
||||
self.assertIsInstance(config, Configurator)
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'default')
|
||||
self.assertEqual(settings["wuttaweb.theme"], "default")
|
||||
|
||||
|
||||
class TestMain(DataTestCase):
|
||||
|
||||
def test_basic(self):
|
||||
with patch.object(AppHandler, 'make_session', return_value=self.session):
|
||||
with patch.object(AppHandler, "make_session", return_value=self.session):
|
||||
global_config = None
|
||||
myconf = self.write_file('my.conf', '')
|
||||
settings = {'wutta.config': myconf}
|
||||
myconf = self.write_file("my.conf", "")
|
||||
settings = {"wutta.config": myconf}
|
||||
app = mod.main(global_config, **settings)
|
||||
self.assertIsInstance(app, Router)
|
||||
|
||||
|
|
@ -73,9 +73,9 @@ def mock_main(global_config, **settings):
|
|||
wutta_config = mod.make_wutta_config(settings)
|
||||
pyramid_config = mod.make_pyramid_config(settings)
|
||||
|
||||
pyramid_config.include('wuttaweb.static')
|
||||
pyramid_config.include('wuttaweb.subscribers')
|
||||
pyramid_config.include('wuttaweb.views')
|
||||
pyramid_config.include("wuttaweb.static")
|
||||
pyramid_config.include("wuttaweb.subscribers")
|
||||
pyramid_config.include("wuttaweb.views")
|
||||
|
||||
return pyramid_config.make_wsgi_app()
|
||||
|
||||
|
|
@ -83,26 +83,26 @@ def mock_main(global_config, **settings):
|
|||
class TestMakeWsgiApp(DataTestCase):
|
||||
|
||||
def test_with_callable(self):
|
||||
with patch.object(self.app, 'make_session', return_value=self.session):
|
||||
with patch.object(self.app, "make_session", return_value=self.session):
|
||||
|
||||
# specify config
|
||||
wsgi = mod.make_wsgi_app(mock_main, config=self.config)
|
||||
self.assertIsInstance(wsgi, Router)
|
||||
|
||||
# auto config
|
||||
with patch.object(mod, 'make_config', return_value=self.config):
|
||||
with patch.object(mod, "make_config", return_value=self.config):
|
||||
wsgi = mod.make_wsgi_app(mock_main)
|
||||
self.assertIsInstance(wsgi, Router)
|
||||
|
||||
def test_with_spec(self):
|
||||
|
||||
# specify config
|
||||
wsgi = mod.make_wsgi_app('tests.test_app:mock_main', config=self.config)
|
||||
wsgi = mod.make_wsgi_app("tests.test_app:mock_main", config=self.config)
|
||||
self.assertIsInstance(wsgi, Router)
|
||||
|
||||
# auto config
|
||||
with patch.object(mod, 'make_config', return_value=self.config):
|
||||
wsgi = mod.make_wsgi_app('tests.test_app:mock_main')
|
||||
with patch.object(mod, "make_config", return_value=self.config):
|
||||
wsgi = mod.make_wsgi_app("tests.test_app:mock_main")
|
||||
self.assertIsInstance(wsgi, Router)
|
||||
|
||||
def test_invalid(self):
|
||||
|
|
@ -112,27 +112,27 @@ class TestMakeWsgiApp(DataTestCase):
|
|||
class TestMakeAsgiApp(DataTestCase):
|
||||
|
||||
def test_with_callable(self):
|
||||
with patch.object(self.app, 'make_session', return_value=self.session):
|
||||
with patch.object(self.app, "make_session", return_value=self.session):
|
||||
|
||||
# specify config
|
||||
asgi = mod.make_asgi_app(mock_main, config=self.config)
|
||||
self.assertIsInstance(asgi, WsgiToAsgi)
|
||||
|
||||
# auto config
|
||||
with patch.object(mod, 'make_config', return_value=self.config):
|
||||
with patch.object(mod, "make_config", return_value=self.config):
|
||||
asgi = mod.make_asgi_app(mock_main)
|
||||
self.assertIsInstance(asgi, WsgiToAsgi)
|
||||
|
||||
def test_with_spec(self):
|
||||
with patch.object(self.app, 'make_session', return_value=self.session):
|
||||
with patch.object(self.app, "make_session", return_value=self.session):
|
||||
|
||||
# specify config
|
||||
asgi = mod.make_asgi_app('tests.test_app:mock_main', config=self.config)
|
||||
asgi = mod.make_asgi_app("tests.test_app:mock_main", config=self.config)
|
||||
self.assertIsInstance(asgi, WsgiToAsgi)
|
||||
|
||||
# auto config
|
||||
with patch.object(mod, 'make_config', return_value=self.config):
|
||||
asgi = mod.make_asgi_app('tests.test_app:mock_main')
|
||||
with patch.object(mod, "make_config", return_value=self.config):
|
||||
asgi = mod.make_asgi_app("tests.test_app:mock_main")
|
||||
self.assertIsInstance(asgi, WsgiToAsgi)
|
||||
|
||||
def test_invalid(self):
|
||||
|
|
@ -143,53 +143,65 @@ class TestEstablishTheme(DataTestCase):
|
|||
|
||||
def test_default(self):
|
||||
settings = {
|
||||
'wutta_config': self.config,
|
||||
'mako.directories': ['wuttaweb:templates'],
|
||||
"wutta_config": self.config,
|
||||
"mako.directories": ["wuttaweb:templates"],
|
||||
}
|
||||
mod.establish_theme(settings)
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'default')
|
||||
self.assertEqual(settings['mako.directories'], [
|
||||
resource_path('wuttaweb:templates/themes/default'),
|
||||
'wuttaweb:templates',
|
||||
])
|
||||
self.assertEqual(settings["wuttaweb.theme"], "default")
|
||||
self.assertEqual(
|
||||
settings["mako.directories"],
|
||||
[
|
||||
resource_path("wuttaweb:templates/themes/default"),
|
||||
"wuttaweb:templates",
|
||||
],
|
||||
)
|
||||
|
||||
def test_mako_dirs_as_string(self):
|
||||
settings = {
|
||||
'wutta_config': self.config,
|
||||
'mako.directories': 'wuttaweb:templates',
|
||||
"wutta_config": self.config,
|
||||
"mako.directories": "wuttaweb:templates",
|
||||
}
|
||||
mod.establish_theme(settings)
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'default')
|
||||
self.assertEqual(settings['mako.directories'], [
|
||||
resource_path('wuttaweb:templates/themes/default'),
|
||||
'wuttaweb:templates',
|
||||
])
|
||||
self.assertEqual(settings["wuttaweb.theme"], "default")
|
||||
self.assertEqual(
|
||||
settings["mako.directories"],
|
||||
[
|
||||
resource_path("wuttaweb:templates/themes/default"),
|
||||
"wuttaweb:templates",
|
||||
],
|
||||
)
|
||||
|
||||
def test_butterfly(self):
|
||||
settings = {
|
||||
'wutta_config': self.config,
|
||||
'mako.directories': 'wuttaweb:templates',
|
||||
"wutta_config": self.config,
|
||||
"mako.directories": "wuttaweb:templates",
|
||||
}
|
||||
self.app.save_setting(self.session, 'wuttaweb.theme', 'butterfly')
|
||||
self.app.save_setting(self.session, "wuttaweb.theme", "butterfly")
|
||||
self.session.commit()
|
||||
mod.establish_theme(settings)
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'butterfly')
|
||||
self.assertEqual(settings['mako.directories'], [
|
||||
resource_path('wuttaweb:templates/themes/butterfly'),
|
||||
'wuttaweb:templates',
|
||||
])
|
||||
self.assertEqual(settings["wuttaweb.theme"], "butterfly")
|
||||
self.assertEqual(
|
||||
settings["mako.directories"],
|
||||
[
|
||||
resource_path("wuttaweb:templates/themes/butterfly"),
|
||||
"wuttaweb:templates",
|
||||
],
|
||||
)
|
||||
|
||||
def test_custom(self):
|
||||
settings = {
|
||||
'wutta_config': self.config,
|
||||
'mako.directories': 'wuttaweb:templates',
|
||||
"wutta_config": self.config,
|
||||
"mako.directories": "wuttaweb:templates",
|
||||
}
|
||||
self.config.setdefault('wuttaweb.themes.keys', 'anotherone')
|
||||
self.app.save_setting(self.session, 'wuttaweb.theme', 'anotherone')
|
||||
self.config.setdefault("wuttaweb.themes.keys", "anotherone")
|
||||
self.app.save_setting(self.session, "wuttaweb.theme", "anotherone")
|
||||
self.session.commit()
|
||||
mod.establish_theme(settings)
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'anotherone')
|
||||
self.assertEqual(settings['mako.directories'], [
|
||||
resource_path('wuttaweb:templates/themes/anotherone'),
|
||||
'wuttaweb:templates',
|
||||
])
|
||||
self.assertEqual(settings["wuttaweb.theme"], "anotherone")
|
||||
self.assertEqual(
|
||||
settings["mako.directories"],
|
||||
[
|
||||
resource_path("wuttaweb:templates/themes/anotherone"),
|
||||
"wuttaweb:templates",
|
||||
],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -18,10 +18,11 @@ class TestLoginUser(TestCase):
|
|||
app = config.get_app()
|
||||
model = app.model
|
||||
request = testing.DummyRequest(wutta_config=config)
|
||||
user = model.User(username='barney')
|
||||
user = model.User(username="barney")
|
||||
headers = mod.login_user(request, user)
|
||||
self.assertEqual(headers, [])
|
||||
|
||||
|
||||
class TestLogoutUser(TestCase):
|
||||
|
||||
def test_basic(self):
|
||||
|
|
@ -36,20 +37,25 @@ class TestLogoutUser(TestCase):
|
|||
class TestWuttaSecurityPolicy(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.config = WuttaConfig(defaults={
|
||||
'wutta.db.default.url': 'sqlite://',
|
||||
})
|
||||
self.config = WuttaConfig(
|
||||
defaults={
|
||||
"wutta.db.default.url": "sqlite://",
|
||||
}
|
||||
)
|
||||
|
||||
self.request = testing.DummyRequest()
|
||||
self.pyramid_config = testing.setUp(request=self.request, settings={
|
||||
'wutta_config': self.config,
|
||||
})
|
||||
self.pyramid_config = testing.setUp(
|
||||
request=self.request,
|
||||
settings={
|
||||
"wutta_config": self.config,
|
||||
},
|
||||
)
|
||||
|
||||
self.app = self.config.get_app()
|
||||
model = self.app.model
|
||||
model.Base.metadata.create_all(bind=self.config.appdb_engine)
|
||||
self.session = self.app.make_session()
|
||||
self.user = model.User(username='barney')
|
||||
self.user = model.User(username="barney")
|
||||
self.session.add(self.user)
|
||||
self.session.commit()
|
||||
|
||||
|
|
@ -66,12 +72,16 @@ class TestWuttaSecurityPolicy(TestCase):
|
|||
self.assertIsNotNone(uuid)
|
||||
self.assertIsNone(self.policy.session_helper.authenticated_userid(self.request))
|
||||
self.policy.remember(self.request, uuid)
|
||||
self.assertEqual(self.policy.session_helper.authenticated_userid(self.request), uuid)
|
||||
self.assertEqual(
|
||||
self.policy.session_helper.authenticated_userid(self.request), uuid
|
||||
)
|
||||
|
||||
def test_forget(self):
|
||||
uuid = self.user.uuid
|
||||
self.policy.remember(self.request, uuid)
|
||||
self.assertEqual(self.policy.session_helper.authenticated_userid(self.request), uuid)
|
||||
self.assertEqual(
|
||||
self.policy.session_helper.authenticated_userid(self.request), uuid
|
||||
)
|
||||
self.policy.forget(self.request)
|
||||
self.assertIsNone(self.policy.session_helper.authenticated_userid(self.request))
|
||||
|
||||
|
|
@ -91,7 +101,7 @@ class TestWuttaSecurityPolicy(TestCase):
|
|||
|
||||
# invalid identity yields no user
|
||||
self.policy = self.make_policy()
|
||||
self.policy.remember(self.request, _uuid.uuid4()) # random uuid
|
||||
self.policy.remember(self.request, _uuid.uuid4()) # random uuid
|
||||
user = self.policy.identity(self.request)
|
||||
self.assertIsNone(user)
|
||||
|
||||
|
|
@ -112,59 +122,59 @@ class TestWuttaSecurityPolicy(TestCase):
|
|||
model = self.app.model
|
||||
|
||||
# anon has no perms
|
||||
self.assertFalse(self.policy.permits(self.request, None, 'foo.bar'))
|
||||
self.assertFalse(self.policy.permits(self.request, None, "foo.bar"))
|
||||
|
||||
# but we can grant it
|
||||
anons = auth.get_role_anonymous(self.session)
|
||||
self.user.roles.append(anons)
|
||||
auth.grant_permission(anons, 'foo.bar')
|
||||
auth.grant_permission(anons, "foo.bar")
|
||||
self.session.commit()
|
||||
|
||||
# and then perm check is satisfied
|
||||
self.assertTrue(self.policy.permits(self.request, None, 'foo.bar'))
|
||||
self.assertTrue(self.policy.permits(self.request, None, "foo.bar"))
|
||||
|
||||
# now, create a separate role and grant another perm
|
||||
# (but user does not yet belong to this role)
|
||||
role = model.Role(name='whatever')
|
||||
role = model.Role(name="whatever")
|
||||
self.session.add(role)
|
||||
auth.grant_permission(role, 'baz.edit')
|
||||
auth.grant_permission(role, "baz.edit")
|
||||
self.session.commit()
|
||||
|
||||
# so far then, user does not have the permission
|
||||
self.policy = self.make_policy()
|
||||
self.policy.remember(self.request, self.user.uuid)
|
||||
self.assertFalse(self.policy.permits(self.request, None, 'baz.edit'))
|
||||
self.assertFalse(self.policy.permits(self.request, None, "baz.edit"))
|
||||
|
||||
# but if we assign user to role, perm check should pass
|
||||
self.user.roles.append(role)
|
||||
self.session.commit()
|
||||
self.assertTrue(self.policy.permits(self.request, None, 'baz.edit'))
|
||||
self.assertTrue(self.policy.permits(self.request, None, "baz.edit"))
|
||||
|
||||
# now let's try another perm - we won't grant it, but will
|
||||
# confirm user is denied access unless they become root
|
||||
self.assertFalse(self.policy.permits(self.request, None, 'some-root-perm'))
|
||||
self.assertFalse(self.policy.permits(self.request, None, "some-root-perm"))
|
||||
self.request.is_root = True
|
||||
self.assertTrue(self.policy.permits(self.request, None, 'some-root-perm'))
|
||||
self.assertTrue(self.policy.permits(self.request, None, "some-root-perm"))
|
||||
|
||||
|
||||
class TestAddPermissionGroup(WebTestCase):
|
||||
|
||||
def test_basic(self):
|
||||
permissions = self.pyramid_config.get_settings().get('wutta_permissions', {})
|
||||
self.assertNotIn('widgets', permissions)
|
||||
self.pyramid_config.add_wutta_permission_group('widgets')
|
||||
permissions = self.pyramid_config.get_settings().get('wutta_permissions', {})
|
||||
self.assertIn('widgets', permissions)
|
||||
self.assertEqual(permissions['widgets']['label'], "Widgets")
|
||||
permissions = self.pyramid_config.get_settings().get("wutta_permissions", {})
|
||||
self.assertNotIn("widgets", permissions)
|
||||
self.pyramid_config.add_wutta_permission_group("widgets")
|
||||
permissions = self.pyramid_config.get_settings().get("wutta_permissions", {})
|
||||
self.assertIn("widgets", permissions)
|
||||
self.assertEqual(permissions["widgets"]["label"], "Widgets")
|
||||
|
||||
|
||||
class TestAddPermission(WebTestCase):
|
||||
|
||||
def test_basic(self):
|
||||
permissions = self.pyramid_config.get_settings().get('wutta_permissions', {})
|
||||
self.assertNotIn('widgets', permissions)
|
||||
self.pyramid_config.add_wutta_permission('widgets', 'widgets.polish')
|
||||
permissions = self.pyramid_config.get_settings().get('wutta_permissions', {})
|
||||
self.assertIn('widgets', permissions)
|
||||
self.assertEqual(permissions['widgets']['label'], "Widgets")
|
||||
self.assertIn('widgets.polish', permissions['widgets']['perms'])
|
||||
permissions = self.pyramid_config.get_settings().get("wutta_permissions", {})
|
||||
self.assertNotIn("widgets", permissions)
|
||||
self.pyramid_config.add_wutta_permission("widgets", "widgets.polish")
|
||||
permissions = self.pyramid_config.get_settings().get("wutta_permissions", {})
|
||||
self.assertIn("widgets", permissions)
|
||||
self.assertEqual(permissions["widgets"]["label"], "Widgets")
|
||||
self.assertIn("widgets.polish", permissions["widgets"]["perms"])
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ class TestAllSettings(DataTestCase):
|
|||
def test_all(self):
|
||||
for name in dir(mod):
|
||||
obj = getattr(mod, name)
|
||||
if (isinstance(obj, type)
|
||||
if (
|
||||
isinstance(obj, type)
|
||||
and obj is not EmailSetting
|
||||
and issubclass(obj, EmailSetting)):
|
||||
and issubclass(obj, EmailSetting)
|
||||
):
|
||||
self.check_setting(obj)
|
||||
|
|
|
|||
|
|
@ -12,9 +12,11 @@ from wuttaweb.testing import WebTestCase
|
|||
class MockMenuHandler(MenuHandler):
|
||||
pass
|
||||
|
||||
|
||||
class LegacyMenuHandler(MenuHandler):
|
||||
pass
|
||||
|
||||
|
||||
class AnotherMenuHandler(MenuHandler):
|
||||
pass
|
||||
|
||||
|
|
@ -29,48 +31,48 @@ class TestWebHandler(WebTestCase):
|
|||
|
||||
# default with / root path
|
||||
url = handler.get_fanstatic_url(self.request, static.logo)
|
||||
self.assertEqual(url, '/fanstatic/wuttaweb_img/logo.png')
|
||||
self.assertEqual(url, "/fanstatic/wuttaweb_img/logo.png")
|
||||
|
||||
# what about a subpath
|
||||
self.request.script_name = '/testing'
|
||||
self.request.script_name = "/testing"
|
||||
url = handler.get_fanstatic_url(self.request, static.logo)
|
||||
self.assertEqual(url, '/testing/fanstatic/wuttaweb_img/logo.png')
|
||||
self.assertEqual(url, "/testing/fanstatic/wuttaweb_img/logo.png")
|
||||
|
||||
def test_get_favicon_url(self):
|
||||
handler = self.make_handler()
|
||||
|
||||
# default
|
||||
url = handler.get_favicon_url(self.request)
|
||||
self.assertEqual(url, '/fanstatic/wuttaweb_img/favicon.ico')
|
||||
self.assertEqual(url, "/fanstatic/wuttaweb_img/favicon.ico")
|
||||
|
||||
# config override
|
||||
self.config.setdefault('wuttaweb.favicon_url', '/testing/other.ico')
|
||||
self.config.setdefault("wuttaweb.favicon_url", "/testing/other.ico")
|
||||
url = handler.get_favicon_url(self.request)
|
||||
self.assertEqual(url, '/testing/other.ico')
|
||||
self.assertEqual(url, "/testing/other.ico")
|
||||
|
||||
def test_get_header_logo_url(self):
|
||||
handler = self.make_handler()
|
||||
|
||||
# default
|
||||
url = handler.get_header_logo_url(self.request)
|
||||
self.assertEqual(url, '/fanstatic/wuttaweb_img/favicon.ico')
|
||||
self.assertEqual(url, "/fanstatic/wuttaweb_img/favicon.ico")
|
||||
|
||||
# config override
|
||||
self.config.setdefault('wuttaweb.header_logo_url', '/testing/header.png')
|
||||
self.config.setdefault("wuttaweb.header_logo_url", "/testing/header.png")
|
||||
url = handler.get_header_logo_url(self.request)
|
||||
self.assertEqual(url, '/testing/header.png')
|
||||
self.assertEqual(url, "/testing/header.png")
|
||||
|
||||
def test_get_main_logo_url(self):
|
||||
handler = self.make_handler()
|
||||
|
||||
# default
|
||||
url = handler.get_main_logo_url(self.request)
|
||||
self.assertEqual(url, '/fanstatic/wuttaweb_img/logo.png')
|
||||
self.assertEqual(url, "/fanstatic/wuttaweb_img/logo.png")
|
||||
|
||||
# config override
|
||||
self.config.setdefault('wuttaweb.logo_url', '/testing/other.png')
|
||||
self.config.setdefault("wuttaweb.logo_url", "/testing/other.png")
|
||||
url = handler.get_main_logo_url(self.request)
|
||||
self.assertEqual(url, '/testing/other.png')
|
||||
self.assertEqual(url, "/testing/other.png")
|
||||
|
||||
def test_get_menu_handler(self):
|
||||
handler = self.make_handler()
|
||||
|
|
@ -81,20 +83,23 @@ class TestWebHandler(WebTestCase):
|
|||
self.assertIs(type(menus), MenuHandler)
|
||||
|
||||
# configured default
|
||||
self.config.setdefault('wutta.web.menus.handler.default_spec',
|
||||
'tests.test_handler:MockMenuHandler')
|
||||
self.config.setdefault(
|
||||
"wutta.web.menus.handler.default_spec", "tests.test_handler:MockMenuHandler"
|
||||
)
|
||||
menus = handler.get_menu_handler()
|
||||
self.assertIsInstance(menus, MockMenuHandler)
|
||||
|
||||
# configured handler (legacy)
|
||||
self.config.setdefault('wutta.web.menus.handler_spec',
|
||||
'tests.test_handler:LegacyMenuHandler')
|
||||
self.config.setdefault(
|
||||
"wutta.web.menus.handler_spec", "tests.test_handler:LegacyMenuHandler"
|
||||
)
|
||||
menus = handler.get_menu_handler()
|
||||
self.assertIsInstance(menus, LegacyMenuHandler)
|
||||
|
||||
# configued handler (proper)
|
||||
self.config.setdefault('wutta.web.menus.handler.spec',
|
||||
'tests.test_handler:AnotherMenuHandler')
|
||||
self.config.setdefault(
|
||||
"wutta.web.menus.handler.spec", "tests.test_handler:AnotherMenuHandler"
|
||||
)
|
||||
menus = handler.get_menu_handler()
|
||||
self.assertIsInstance(menus, AnotherMenuHandler)
|
||||
|
||||
|
|
@ -103,40 +108,51 @@ class TestWebHandler(WebTestCase):
|
|||
|
||||
# at least one spec by default
|
||||
specs = handler.get_menu_handler_specs()
|
||||
self.assertIn('wuttaweb.menus:MenuHandler', specs)
|
||||
self.assertIn("wuttaweb.menus:MenuHandler", specs)
|
||||
|
||||
# caller can specify default as string
|
||||
specs = handler.get_menu_handler_specs(default='tests.test_handler:MockMenuHandler')
|
||||
self.assertIn('wuttaweb.menus:MenuHandler', specs)
|
||||
self.assertIn('tests.test_handler:MockMenuHandler', specs)
|
||||
self.assertNotIn('tests.test_handler:AnotherMenuHandler', specs)
|
||||
specs = handler.get_menu_handler_specs(
|
||||
default="tests.test_handler:MockMenuHandler"
|
||||
)
|
||||
self.assertIn("wuttaweb.menus:MenuHandler", specs)
|
||||
self.assertIn("tests.test_handler:MockMenuHandler", specs)
|
||||
self.assertNotIn("tests.test_handler:AnotherMenuHandler", specs)
|
||||
|
||||
# caller can specify default as list
|
||||
specs = handler.get_menu_handler_specs(default=[
|
||||
'tests.test_handler:MockMenuHandler',
|
||||
'tests.test_handler:AnotherMenuHandler'])
|
||||
self.assertIn('wuttaweb.menus:MenuHandler', specs)
|
||||
self.assertIn('tests.test_handler:MockMenuHandler', specs)
|
||||
self.assertIn('tests.test_handler:AnotherMenuHandler', specs)
|
||||
specs = handler.get_menu_handler_specs(
|
||||
default=[
|
||||
"tests.test_handler:MockMenuHandler",
|
||||
"tests.test_handler:AnotherMenuHandler",
|
||||
]
|
||||
)
|
||||
self.assertIn("wuttaweb.menus:MenuHandler", specs)
|
||||
self.assertIn("tests.test_handler:MockMenuHandler", specs)
|
||||
self.assertIn("tests.test_handler:AnotherMenuHandler", specs)
|
||||
|
||||
# default can be configured
|
||||
self.config.setdefault('wutta.web.menus.handler.default_spec',
|
||||
'tests.test_handler:AnotherMenuHandler')
|
||||
self.config.setdefault(
|
||||
"wutta.web.menus.handler.default_spec",
|
||||
"tests.test_handler:AnotherMenuHandler",
|
||||
)
|
||||
specs = handler.get_menu_handler_specs()
|
||||
self.assertIn('wuttaweb.menus:MenuHandler', specs)
|
||||
self.assertNotIn('tests.test_handler:MockMenuHandler', specs)
|
||||
self.assertIn('tests.test_handler:AnotherMenuHandler', specs)
|
||||
self.assertIn("wuttaweb.menus:MenuHandler", specs)
|
||||
self.assertNotIn("tests.test_handler:MockMenuHandler", specs)
|
||||
self.assertIn("tests.test_handler:AnotherMenuHandler", specs)
|
||||
|
||||
# the rest come from entry points
|
||||
with patch.object(mod, 'load_entry_points', return_value={
|
||||
'legacy': LegacyMenuHandler,
|
||||
}):
|
||||
with patch.object(
|
||||
mod,
|
||||
"load_entry_points",
|
||||
return_value={
|
||||
"legacy": LegacyMenuHandler,
|
||||
},
|
||||
):
|
||||
specs = handler.get_menu_handler_specs()
|
||||
self.assertNotIn('wuttaweb.menus:MenuHandler', specs)
|
||||
self.assertNotIn('tests.test_handler:MockMenuHandler', specs)
|
||||
self.assertIn('tests.test_handler:LegacyMenuHandler', specs)
|
||||
self.assertNotIn("wuttaweb.menus:MenuHandler", specs)
|
||||
self.assertNotIn("tests.test_handler:MockMenuHandler", specs)
|
||||
self.assertIn("tests.test_handler:LegacyMenuHandler", specs)
|
||||
# nb. this remains from previous config default
|
||||
self.assertIn('tests.test_handler:AnotherMenuHandler', specs)
|
||||
self.assertIn("tests.test_handler:AnotherMenuHandler", specs)
|
||||
|
||||
def test_make_form(self):
|
||||
handler = self.make_handler()
|
||||
|
|
|
|||
|
|
@ -18,13 +18,13 @@ class TestMenuHandler(WebTestCase):
|
|||
# no people entry by default
|
||||
menu = self.handler.make_admin_menu(self.request)
|
||||
self.assertIsInstance(menu, dict)
|
||||
routes = [item.get('route') for item in menu['items']]
|
||||
self.assertNotIn('people', routes)
|
||||
routes = [item.get("route") for item in menu["items"]]
|
||||
self.assertNotIn("people", routes)
|
||||
|
||||
# but we can request it
|
||||
menu = self.handler.make_admin_menu(self.request, include_people=True)
|
||||
routes = [item.get('route') for item in menu['items']]
|
||||
self.assertIn('people', routes)
|
||||
routes = [item.get("route") for item in menu["items"]]
|
||||
self.assertIn("people", routes)
|
||||
|
||||
def test_make_menus(self):
|
||||
menus = self.handler.make_menus(self.request)
|
||||
|
|
@ -35,20 +35,20 @@ class TestMenuHandler(WebTestCase):
|
|||
auth = self.app.get_auth_handler()
|
||||
|
||||
# user with perms
|
||||
barney = model.User(username='barney')
|
||||
barney = model.User(username="barney")
|
||||
self.session.add(barney)
|
||||
blokes = model.Role(name="Blokes")
|
||||
self.session.add(blokes)
|
||||
barney.roles.append(blokes)
|
||||
auth.grant_permission(blokes, 'appinfo.list')
|
||||
auth.grant_permission(blokes, "appinfo.list")
|
||||
self.request.user = barney
|
||||
|
||||
# perm not granted to user
|
||||
item = {'perm': 'appinfo.configure'}
|
||||
item = {"perm": "appinfo.configure"}
|
||||
self.assertFalse(self.handler._is_allowed(self.request, item))
|
||||
|
||||
# perm *is* granted to user
|
||||
item = {'perm': 'appinfo.list'}
|
||||
item = {"perm": "appinfo.list"}
|
||||
self.assertTrue(self.handler._is_allowed(self.request, item))
|
||||
|
||||
# perm not required
|
||||
|
|
@ -60,49 +60,49 @@ class TestMenuHandler(WebTestCase):
|
|||
def make_menus():
|
||||
return [
|
||||
{
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{'title': "Foo", 'url': '#'},
|
||||
{'title': "Bar", 'url': '#'},
|
||||
"type": "menu",
|
||||
"items": [
|
||||
{"title": "Foo", "url": "#"},
|
||||
{"title": "Bar", "url": "#"},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
mock_is_allowed = MagicMock()
|
||||
with patch.object(self.handler, '_is_allowed', new=mock_is_allowed):
|
||||
with patch.object(self.handler, "_is_allowed", new=mock_is_allowed):
|
||||
|
||||
# all should be allowed
|
||||
mock_is_allowed.return_value = True
|
||||
menus = make_menus()
|
||||
self.handler._mark_allowed(self.request, menus)
|
||||
menu = menus[0]
|
||||
self.assertTrue(menu['allowed'])
|
||||
foo, bar = menu['items']
|
||||
self.assertTrue(foo['allowed'])
|
||||
self.assertTrue(bar['allowed'])
|
||||
self.assertTrue(menu["allowed"])
|
||||
foo, bar = menu["items"]
|
||||
self.assertTrue(foo["allowed"])
|
||||
self.assertTrue(bar["allowed"])
|
||||
|
||||
# none should be allowed
|
||||
mock_is_allowed.return_value = False
|
||||
menus = make_menus()
|
||||
self.handler._mark_allowed(self.request, menus)
|
||||
menu = menus[0]
|
||||
self.assertFalse(menu['allowed'])
|
||||
foo, bar = menu['items']
|
||||
self.assertFalse(foo['allowed'])
|
||||
self.assertFalse(bar['allowed'])
|
||||
self.assertFalse(menu["allowed"])
|
||||
foo, bar = menu["items"]
|
||||
self.assertFalse(foo["allowed"])
|
||||
self.assertFalse(bar["allowed"])
|
||||
|
||||
def test_mark_allowed_submenu(self):
|
||||
|
||||
def make_menus():
|
||||
return [
|
||||
{
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{'title': "Foo", 'url': '#'},
|
||||
"type": "menu",
|
||||
"items": [
|
||||
{"title": "Foo", "url": "#"},
|
||||
{
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{'title': "Bar", 'url': '#'},
|
||||
"type": "menu",
|
||||
"items": [
|
||||
{"title": "Bar", "url": "#"},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
@ -110,215 +110,215 @@ class TestMenuHandler(WebTestCase):
|
|||
]
|
||||
|
||||
mock_is_allowed = MagicMock()
|
||||
with patch.object(self.handler, '_is_allowed', new=mock_is_allowed):
|
||||
with patch.object(self.handler, "_is_allowed", new=mock_is_allowed):
|
||||
|
||||
# all should be allowed
|
||||
mock_is_allowed.return_value = True
|
||||
menus = make_menus()
|
||||
self.handler._mark_allowed(self.request, menus)
|
||||
menu = menus[0]
|
||||
self.assertTrue(menu['allowed'])
|
||||
foo, submenu = menu['items']
|
||||
self.assertTrue(foo['allowed'])
|
||||
self.assertTrue(submenu['allowed'])
|
||||
subitem = submenu['items'][0]
|
||||
self.assertTrue(subitem['allowed'])
|
||||
self.assertTrue(menu["allowed"])
|
||||
foo, submenu = menu["items"]
|
||||
self.assertTrue(foo["allowed"])
|
||||
self.assertTrue(submenu["allowed"])
|
||||
subitem = submenu["items"][0]
|
||||
self.assertTrue(subitem["allowed"])
|
||||
|
||||
# none should be allowed
|
||||
mock_is_allowed.return_value = False
|
||||
menus = make_menus()
|
||||
self.handler._mark_allowed(self.request, menus)
|
||||
menu = menus[0]
|
||||
self.assertFalse(menu['allowed'])
|
||||
foo, submenu = menu['items']
|
||||
self.assertFalse(foo['allowed'])
|
||||
self.assertFalse(submenu['allowed'])
|
||||
subitem = submenu['items'][0]
|
||||
self.assertFalse(subitem['allowed'])
|
||||
self.assertFalse(menu["allowed"])
|
||||
foo, submenu = menu["items"]
|
||||
self.assertFalse(foo["allowed"])
|
||||
self.assertFalse(submenu["allowed"])
|
||||
subitem = submenu["items"][0]
|
||||
self.assertFalse(subitem["allowed"])
|
||||
|
||||
def test_make_menu_key(self):
|
||||
self.assertEqual(self.handler._make_menu_key('foo'), 'foo')
|
||||
self.assertEqual(self.handler._make_menu_key('FooBar'), 'foobar')
|
||||
self.assertEqual(self.handler._make_menu_key('Foo - $#Bar'), 'foobar')
|
||||
self.assertEqual(self.handler._make_menu_key('Foo__Bar'), 'foo__bar')
|
||||
self.assertEqual(self.handler._make_menu_key("foo"), "foo")
|
||||
self.assertEqual(self.handler._make_menu_key("FooBar"), "foobar")
|
||||
self.assertEqual(self.handler._make_menu_key("Foo - $#Bar"), "foobar")
|
||||
self.assertEqual(self.handler._make_menu_key("Foo__Bar"), "foo__bar")
|
||||
|
||||
def test_make_menu_entry_item(self):
|
||||
item = {'title': "Foo", 'url': '#'}
|
||||
item = {"title": "Foo", "url": "#"}
|
||||
entry = self.handler._make_menu_entry(self.request, item)
|
||||
self.assertEqual(entry['type'], 'item')
|
||||
self.assertEqual(entry['title'], "Foo")
|
||||
self.assertEqual(entry['url'], '#')
|
||||
self.assertTrue(entry['is_link'])
|
||||
self.assertEqual(entry["type"], "item")
|
||||
self.assertEqual(entry["title"], "Foo")
|
||||
self.assertEqual(entry["url"], "#")
|
||||
self.assertTrue(entry["is_link"])
|
||||
|
||||
def test_make_menu_entry_item_with_no_url(self):
|
||||
item = {'title': "Foo"}
|
||||
item = {"title": "Foo"}
|
||||
entry = self.handler._make_menu_entry(self.request, item)
|
||||
self.assertEqual(entry['type'], 'item')
|
||||
self.assertEqual(entry['title'], "Foo")
|
||||
self.assertNotIn('url', entry)
|
||||
self.assertEqual(entry["type"], "item")
|
||||
self.assertEqual(entry["title"], "Foo")
|
||||
self.assertNotIn("url", entry)
|
||||
# nb. still sets is_link = True; basically it's <a> with no href
|
||||
self.assertTrue(entry['is_link'])
|
||||
self.assertTrue(entry["is_link"])
|
||||
|
||||
def test_make_menu_entry_item_with_known_route(self):
|
||||
item = {'title': "Foo", 'route': 'home'}
|
||||
with patch.object(self.request, 'route_url', return_value='/something'):
|
||||
item = {"title": "Foo", "route": "home"}
|
||||
with patch.object(self.request, "route_url", return_value="/something"):
|
||||
entry = self.handler._make_menu_entry(self.request, item)
|
||||
self.assertEqual(entry['type'], 'item')
|
||||
self.assertEqual(entry['url'], '/something')
|
||||
self.assertTrue(entry['is_link'])
|
||||
self.assertEqual(entry["type"], "item")
|
||||
self.assertEqual(entry["url"], "/something")
|
||||
self.assertTrue(entry["is_link"])
|
||||
|
||||
def test_make_menu_entry_item_with_unknown_route(self):
|
||||
item = {'title': "Foo", 'route': 'home'}
|
||||
with patch.object(self.request, 'route_url', side_effect=KeyError):
|
||||
item = {"title": "Foo", "route": "home"}
|
||||
with patch.object(self.request, "route_url", side_effect=KeyError):
|
||||
entry = self.handler._make_menu_entry(self.request, item)
|
||||
self.assertEqual(entry['type'], 'item')
|
||||
self.assertEqual(entry["type"], "item")
|
||||
# nb. fake url is used, based on (bad) route name
|
||||
self.assertEqual(entry['url'], 'home')
|
||||
self.assertTrue(entry['is_link'])
|
||||
self.assertEqual(entry["url"], "home")
|
||||
self.assertTrue(entry["is_link"])
|
||||
|
||||
def test_make_menu_entry_sep(self):
|
||||
item = {'type': 'sep'}
|
||||
item = {"type": "sep"}
|
||||
entry = self.handler._make_menu_entry(self.request, item)
|
||||
self.assertEqual(entry['type'], 'sep')
|
||||
self.assertTrue(entry['is_sep'])
|
||||
self.assertFalse(entry['is_menu'])
|
||||
self.assertEqual(entry["type"], "sep")
|
||||
self.assertTrue(entry["is_sep"])
|
||||
self.assertFalse(entry["is_menu"])
|
||||
|
||||
def test_make_raw_menus(self):
|
||||
# minimal test to ensure it calls the other method
|
||||
with patch.object(self.handler, 'make_menus') as make_menus:
|
||||
self.handler._make_raw_menus(self.request, foo='bar')
|
||||
make_menus.assert_called_once_with(self.request, foo='bar')
|
||||
with patch.object(self.handler, "make_menus") as make_menus:
|
||||
self.handler._make_raw_menus(self.request, foo="bar")
|
||||
make_menus.assert_called_once_with(self.request, foo="bar")
|
||||
|
||||
def test_do_make_menus_prune_unallowed_item(self):
|
||||
test_menus = [
|
||||
{
|
||||
'title': "First Menu",
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{'title': "Foo", 'url': '#'},
|
||||
{'title': "Bar", 'url': '#'},
|
||||
"title": "First Menu",
|
||||
"type": "menu",
|
||||
"items": [
|
||||
{"title": "Foo", "url": "#"},
|
||||
{"title": "Bar", "url": "#"},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
def is_allowed(request, item):
|
||||
if item.get('title') == 'Bar':
|
||||
if item.get("title") == "Bar":
|
||||
return False
|
||||
return True
|
||||
|
||||
with patch.object(self.handler, 'make_menus', return_value=test_menus):
|
||||
with patch.object(self.handler, '_is_allowed', side_effect=is_allowed):
|
||||
with patch.object(self.handler, "make_menus", return_value=test_menus):
|
||||
with patch.object(self.handler, "_is_allowed", side_effect=is_allowed):
|
||||
menus = self.handler.do_make_menus(self.request)
|
||||
|
||||
# Foo remains but Bar is pruned
|
||||
menu = menus[0]
|
||||
self.assertEqual(len(menu['items']), 1)
|
||||
item = menu['items'][0]
|
||||
self.assertEqual(item['title'], 'Foo')
|
||||
self.assertEqual(len(menu["items"]), 1)
|
||||
item = menu["items"][0]
|
||||
self.assertEqual(item["title"], "Foo")
|
||||
|
||||
def test_do_make_menus_prune_unallowed_menu(self):
|
||||
test_menus = [
|
||||
{
|
||||
'title': "First Menu",
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{'title': "Foo", 'url': '#'},
|
||||
{'title': "Bar", 'url': '#'},
|
||||
"title": "First Menu",
|
||||
"type": "menu",
|
||||
"items": [
|
||||
{"title": "Foo", "url": "#"},
|
||||
{"title": "Bar", "url": "#"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': "Second Menu",
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{'title': "Baz", 'url': '#'},
|
||||
"title": "Second Menu",
|
||||
"type": "menu",
|
||||
"items": [
|
||||
{"title": "Baz", "url": "#"},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
def is_allowed(request, item):
|
||||
if item.get('title') == 'Baz':
|
||||
if item.get("title") == "Baz":
|
||||
return True
|
||||
return False
|
||||
|
||||
with patch.object(self.handler, 'make_menus', return_value=test_menus):
|
||||
with patch.object(self.handler, '_is_allowed', side_effect=is_allowed):
|
||||
with patch.object(self.handler, "make_menus", return_value=test_menus):
|
||||
with patch.object(self.handler, "_is_allowed", side_effect=is_allowed):
|
||||
menus = self.handler.do_make_menus(self.request)
|
||||
|
||||
# Second/Baz remains but First/Foo/Bar are pruned
|
||||
self.assertEqual(len(menus), 1)
|
||||
menu = menus[0]
|
||||
self.assertEqual(menu['title'], 'Second Menu')
|
||||
self.assertEqual(len(menu['items']), 1)
|
||||
item = menu['items'][0]
|
||||
self.assertEqual(item['title'], 'Baz')
|
||||
self.assertEqual(menu["title"], "Second Menu")
|
||||
self.assertEqual(len(menu["items"]), 1)
|
||||
item = menu["items"][0]
|
||||
self.assertEqual(item["title"], "Baz")
|
||||
|
||||
def test_do_make_menus_with_top_link(self):
|
||||
test_menus = [
|
||||
{
|
||||
'title': "First Menu",
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{'title': "Foo", 'url': '#'},
|
||||
{'title': "Bar", 'url': '#'},
|
||||
"title": "First Menu",
|
||||
"type": "menu",
|
||||
"items": [
|
||||
{"title": "Foo", "url": "#"},
|
||||
{"title": "Bar", "url": "#"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': "Second Link",
|
||||
'type': 'link',
|
||||
"title": "Second Link",
|
||||
"type": "link",
|
||||
},
|
||||
]
|
||||
|
||||
with patch.object(self.handler, 'make_menus', return_value=test_menus):
|
||||
with patch.object(self.handler, '_is_allowed', return_value=True):
|
||||
with patch.object(self.handler, "make_menus", return_value=test_menus):
|
||||
with patch.object(self.handler, "_is_allowed", return_value=True):
|
||||
menus = self.handler.do_make_menus(self.request)
|
||||
|
||||
# ensure top link remains
|
||||
self.assertEqual(len(menus), 2)
|
||||
menu = menus[1]
|
||||
self.assertEqual(menu['title'], "Second Link")
|
||||
self.assertEqual(menu["title"], "Second Link")
|
||||
|
||||
def test_do_make_menus_with_trailing_sep(self):
|
||||
test_menus = [
|
||||
{
|
||||
'title': "First Menu",
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{'title': "Foo", 'url': '#'},
|
||||
{'title': "Bar", 'url': '#'},
|
||||
{'type': 'sep'},
|
||||
"title": "First Menu",
|
||||
"type": "menu",
|
||||
"items": [
|
||||
{"title": "Foo", "url": "#"},
|
||||
{"title": "Bar", "url": "#"},
|
||||
{"type": "sep"},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
with patch.object(self.handler, 'make_menus', return_value=test_menus):
|
||||
with patch.object(self.handler, '_is_allowed', return_value=True):
|
||||
with patch.object(self.handler, "make_menus", return_value=test_menus):
|
||||
with patch.object(self.handler, "_is_allowed", return_value=True):
|
||||
menus = self.handler.do_make_menus(self.request)
|
||||
|
||||
# ensure trailing sep was pruned
|
||||
menu = menus[0]
|
||||
self.assertEqual(len(menu['items']), 2)
|
||||
foo, bar = menu['items']
|
||||
self.assertEqual(foo['title'], 'Foo')
|
||||
self.assertEqual(bar['title'], 'Bar')
|
||||
self.assertEqual(len(menu["items"]), 2)
|
||||
foo, bar = menu["items"]
|
||||
self.assertEqual(foo["title"], "Foo")
|
||||
self.assertEqual(bar["title"], "Bar")
|
||||
|
||||
def test_do_make_menus_with_submenu(self):
|
||||
test_menus = [
|
||||
{
|
||||
'title': "First Menu",
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
"title": "First Menu",
|
||||
"type": "menu",
|
||||
"items": [
|
||||
{
|
||||
'title': "First Submenu",
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{'title': "Foo", 'url': '#'},
|
||||
"title": "First Submenu",
|
||||
"type": "menu",
|
||||
"items": [
|
||||
{"title": "Foo", "url": "#"},
|
||||
],
|
||||
},
|
||||
{
|
||||
'title': "Second Submenu",
|
||||
'type': 'menu',
|
||||
'items': [
|
||||
{'title': "Bar", 'url': '#'},
|
||||
"title": "Second Submenu",
|
||||
"type": "menu",
|
||||
"items": [
|
||||
{"title": "Bar", "url": "#"},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
@ -326,20 +326,20 @@ class TestMenuHandler(WebTestCase):
|
|||
]
|
||||
|
||||
def is_allowed(request, item):
|
||||
if item.get('title') == 'Bar':
|
||||
if item.get("title") == "Bar":
|
||||
return False
|
||||
return True
|
||||
|
||||
with patch.object(self.handler, 'make_menus', return_value=test_menus):
|
||||
with patch.object(self.handler, '_is_allowed', side_effect=is_allowed):
|
||||
with patch.object(self.handler, "make_menus", return_value=test_menus):
|
||||
with patch.object(self.handler, "_is_allowed", side_effect=is_allowed):
|
||||
menus = self.handler.do_make_menus(self.request)
|
||||
|
||||
# first submenu remains, second is pruned
|
||||
menu = menus[0]
|
||||
self.assertEqual(len(menu['items']), 1)
|
||||
submenu = menu['items'][0]
|
||||
self.assertEqual(submenu['type'], 'submenu')
|
||||
self.assertEqual(submenu['title'], 'First Submenu')
|
||||
self.assertEqual(len(submenu['items']), 1)
|
||||
item = submenu['items'][0]
|
||||
self.assertEqual(item['title'], 'Foo')
|
||||
self.assertEqual(len(menu["items"]), 1)
|
||||
submenu = menu["items"][0]
|
||||
self.assertEqual(submenu["type"], "submenu")
|
||||
self.assertEqual(submenu["title"], "First Submenu")
|
||||
self.assertEqual(len(submenu["items"]), 1)
|
||||
item = submenu["items"][0]
|
||||
self.assertEqual(item["title"], "Foo")
|
||||
|
|
|
|||
|
|
@ -27,10 +27,10 @@ class TestGetProgressSession(TestCase):
|
|||
self.request = testing.DummyRequest()
|
||||
|
||||
def test_basic(self):
|
||||
self.request.session.id = 'mockid'
|
||||
session = mod.get_progress_session(self.request, 'foo')
|
||||
self.request.session.id = "mockid"
|
||||
session = mod.get_progress_session(self.request, "foo")
|
||||
self.assertIsInstance(session, BeakerSession)
|
||||
self.assertEqual(session.id, 'mockid.progress.foo')
|
||||
self.assertEqual(session.id, "mockid.progress.foo")
|
||||
|
||||
|
||||
class TestSessionProgress(ConfigTestCase):
|
||||
|
|
@ -38,16 +38,16 @@ class TestSessionProgress(ConfigTestCase):
|
|||
def setUp(self):
|
||||
self.setup_config()
|
||||
self.request = testing.DummyRequest(wutta_config=self.config)
|
||||
self.request.session.id = 'mockid'
|
||||
self.request.session.id = "mockid"
|
||||
|
||||
def test_error_url(self):
|
||||
factory = mod.SessionProgress(self.request, 'foo', success_url='/blart')
|
||||
self.assertEqual(factory.error_url, '/blart')
|
||||
factory = mod.SessionProgress(self.request, "foo", success_url="/blart")
|
||||
self.assertEqual(factory.error_url, "/blart")
|
||||
|
||||
def test_basic(self):
|
||||
|
||||
# sanity / coverage check
|
||||
factory = mod.SessionProgress(self.request, 'foo')
|
||||
factory = mod.SessionProgress(self.request, "foo")
|
||||
prog = factory("doing things", 2)
|
||||
prog.update(1)
|
||||
prog.update(2)
|
||||
|
|
@ -56,10 +56,10 @@ class TestSessionProgress(ConfigTestCase):
|
|||
def test_error(self):
|
||||
|
||||
# sanity / coverage check
|
||||
factory = mod.SessionProgress(self.request, 'foo')
|
||||
factory = mod.SessionProgress(self.request, "foo")
|
||||
prog = factory("doing things", 2)
|
||||
prog.update(1)
|
||||
try:
|
||||
raise RuntimeError('omg')
|
||||
raise RuntimeError("omg")
|
||||
except Exception as error:
|
||||
prog.handle_error(error)
|
||||
|
|
|
|||
|
|
@ -11,4 +11,4 @@ class TestIncludeMe(TestCase):
|
|||
with testing.testConfig() as pyramid_config:
|
||||
|
||||
# just ensure no error happens when included..
|
||||
pyramid_config.include('wuttaweb.static')
|
||||
pyramid_config.include("wuttaweb.static")
|
||||
|
|
|
|||
|
|
@ -19,9 +19,12 @@ class TestNewRequest(TestCase):
|
|||
def setUp(self):
|
||||
self.config = WuttaConfig()
|
||||
self.request = self.make_request()
|
||||
self.pyramid_config = testing.setUp(request=self.request, settings={
|
||||
'wutta_config': self.config,
|
||||
})
|
||||
self.pyramid_config = testing.setUp(
|
||||
request=self.request,
|
||||
settings={
|
||||
"wutta_config": self.config,
|
||||
},
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
testing.tearDown()
|
||||
|
|
@ -35,15 +38,15 @@ class TestNewRequest(TestCase):
|
|||
event = MagicMock(request=self.request)
|
||||
|
||||
# request gets a new attr
|
||||
self.assertFalse(hasattr(self.request, 'wutta_config'))
|
||||
self.assertFalse(hasattr(self.request, "wutta_config"))
|
||||
subscribers.new_request(event)
|
||||
self.assertTrue(hasattr(self.request, 'wutta_config'))
|
||||
self.assertTrue(hasattr(self.request, "wutta_config"))
|
||||
self.assertIs(self.request.wutta_config, self.config)
|
||||
|
||||
def test_use_oruga_default(self):
|
||||
|
||||
# request gets a new attr, false by default
|
||||
self.assertFalse(hasattr(self.request, 'use_oruga'))
|
||||
self.assertFalse(hasattr(self.request, "use_oruga"))
|
||||
event = MagicMock(request=self.request)
|
||||
subscribers.new_request(event)
|
||||
self.assertFalse(self.request.use_oruga)
|
||||
|
|
@ -51,17 +54,20 @@ class TestNewRequest(TestCase):
|
|||
# nb. using 'butterfly' theme should cause the 'use_oruga'
|
||||
# flag to be turned on by default
|
||||
self.request = self.make_request()
|
||||
self.request.registry.settings['wuttaweb.theme'] = 'butterfly'
|
||||
self.request.registry.settings["wuttaweb.theme"] = "butterfly"
|
||||
event = MagicMock(request=self.request)
|
||||
subscribers.new_request(event)
|
||||
self.assertTrue(self.request.use_oruga)
|
||||
|
||||
def test_use_oruga_custom(self):
|
||||
self.config.setdefault('wuttaweb.oruga_detector.spec', 'tests.test_subscribers:custom_oruga_detector')
|
||||
self.config.setdefault(
|
||||
"wuttaweb.oruga_detector.spec",
|
||||
"tests.test_subscribers:custom_oruga_detector",
|
||||
)
|
||||
event = MagicMock(request=self.request)
|
||||
|
||||
# request gets a new attr, which should be true
|
||||
self.assertFalse(hasattr(self.request, 'use_oruga'))
|
||||
self.assertFalse(hasattr(self.request, "use_oruga"))
|
||||
subscribers.new_request(event)
|
||||
self.assertTrue(self.request.use_oruga)
|
||||
|
||||
|
|
@ -70,20 +76,24 @@ 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.request.register_component("foo-example", "FooExample")
|
||||
self.assertTrue(hasattr(self.request, "_wuttaweb_registered_components"))
|
||||
self.assertEqual(len(self.request._wuttaweb_registered_components), 1)
|
||||
self.assertIn('foo-example', self.request._wuttaweb_registered_components)
|
||||
self.assertEqual(self.request._wuttaweb_registered_components['foo-example'], 'FooExample')
|
||||
self.assertIn("foo-example", self.request._wuttaweb_registered_components)
|
||||
self.assertEqual(
|
||||
self.request._wuttaweb_registered_components["foo-example"], "FooExample"
|
||||
)
|
||||
|
||||
# re-registering same name
|
||||
self.request.register_component('foo-example', 'FooExample')
|
||||
self.request.register_component("foo-example", "FooExample")
|
||||
self.assertEqual(len(self.request._wuttaweb_registered_components), 1)
|
||||
self.assertIn('foo-example', self.request._wuttaweb_registered_components)
|
||||
self.assertEqual(self.request._wuttaweb_registered_components['foo-example'], 'FooExample')
|
||||
self.assertIn("foo-example", self.request._wuttaweb_registered_components)
|
||||
self.assertEqual(
|
||||
self.request._wuttaweb_registered_components["foo-example"], "FooExample"
|
||||
)
|
||||
|
||||
def test_get_referrer(self):
|
||||
event = MagicMock(request=self.request)
|
||||
|
|
@ -91,33 +101,33 @@ class TestNewRequest(TestCase):
|
|||
def home(request):
|
||||
pass
|
||||
|
||||
self.pyramid_config.add_route('home', '/')
|
||||
self.pyramid_config.add_view(home, route_name='home')
|
||||
self.pyramid_config.add_route("home", "/")
|
||||
self.pyramid_config.add_view(home, route_name="home")
|
||||
|
||||
self.assertFalse(hasattr(self.request, 'get_referrer'))
|
||||
self.assertFalse(hasattr(self.request, "get_referrer"))
|
||||
subscribers.new_request(event)
|
||||
self.assertTrue(hasattr(self.request, 'get_referrer'))
|
||||
self.assertTrue(hasattr(self.request, "get_referrer"))
|
||||
|
||||
# default if no referrer, is home route
|
||||
url = self.request.get_referrer()
|
||||
self.assertEqual(url, self.request.route_url('home'))
|
||||
self.assertEqual(url, self.request.route_url("home"))
|
||||
|
||||
# can specify another default
|
||||
url = self.request.get_referrer(default='https://wuttaproject.org')
|
||||
self.assertEqual(url, 'https://wuttaproject.org')
|
||||
url = self.request.get_referrer(default="https://wuttaproject.org")
|
||||
self.assertEqual(url, "https://wuttaproject.org")
|
||||
|
||||
# or referrer can come from user session
|
||||
self.request.session['referrer'] = 'https://rattailproject.org'
|
||||
self.assertIn('referrer', self.request.session)
|
||||
self.request.session["referrer"] = "https://rattailproject.org"
|
||||
self.assertIn("referrer", self.request.session)
|
||||
url = self.request.get_referrer()
|
||||
self.assertEqual(url, 'https://rattailproject.org')
|
||||
self.assertEqual(url, "https://rattailproject.org")
|
||||
# nb. referrer should also have been removed from user session
|
||||
self.assertNotIn('referrer', self.request.session)
|
||||
self.assertNotIn("referrer", self.request.session)
|
||||
|
||||
# or referrer can come from request params
|
||||
self.request.params['referrer'] = 'https://kernel.org'
|
||||
self.request.params["referrer"] = "https://kernel.org"
|
||||
url = self.request.get_referrer()
|
||||
self.assertEqual(url, 'https://kernel.org')
|
||||
self.assertEqual(url, "https://kernel.org")
|
||||
|
||||
|
||||
def custom_oruga_detector(request):
|
||||
|
|
@ -127,30 +137,37 @@ def custom_oruga_detector(request):
|
|||
class TestNewRequestSetUser(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.config = WuttaConfig(defaults={
|
||||
'wutta.db.default.url': 'sqlite://',
|
||||
})
|
||||
self.config = WuttaConfig(
|
||||
defaults={
|
||||
"wutta.db.default.url": "sqlite://",
|
||||
}
|
||||
)
|
||||
|
||||
self.request = testing.DummyRequest(wutta_config=self.config)
|
||||
self.pyramid_config = testing.setUp(request=self.request, settings={
|
||||
'wutta_config': self.config,
|
||||
})
|
||||
self.pyramid_config = testing.setUp(
|
||||
request=self.request,
|
||||
settings={
|
||||
"wutta_config": self.config,
|
||||
},
|
||||
)
|
||||
|
||||
self.app = self.config.get_app()
|
||||
model = self.app.model
|
||||
model.Base.metadata.create_all(bind=self.config.appdb_engine)
|
||||
self.session = self.app.make_session()
|
||||
self.user = model.User(username='barney')
|
||||
self.user = model.User(username="barney")
|
||||
self.session.add(self.user)
|
||||
self.session.commit()
|
||||
|
||||
self.pyramid_config.set_security_policy(WuttaSecurityPolicy(db_session=self.session))
|
||||
self.pyramid_config.set_security_policy(
|
||||
WuttaSecurityPolicy(db_session=self.session)
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
testing.tearDown()
|
||||
|
||||
def test_anonymous(self):
|
||||
self.assertFalse(hasattr(self.request, 'user'))
|
||||
self.assertFalse(hasattr(self.request, "user"))
|
||||
event = MagicMock(request=self.request)
|
||||
subscribers.new_request_set_user(event)
|
||||
self.assertIsNone(self.request.user)
|
||||
|
|
@ -167,8 +184,8 @@ class TestNewRequestSetUser(TestCase):
|
|||
event = MagicMock(request=self.request)
|
||||
|
||||
# anonymous user
|
||||
self.assertFalse(hasattr(self.request, 'user'))
|
||||
self.assertFalse(hasattr(self.request, 'is_admin'))
|
||||
self.assertFalse(hasattr(self.request, "user"))
|
||||
self.assertFalse(hasattr(self.request, "is_admin"))
|
||||
subscribers.new_request_set_user(event, db_session=self.session)
|
||||
self.assertIsNone(self.request.user)
|
||||
self.assertFalse(self.request.is_admin)
|
||||
|
|
@ -198,8 +215,8 @@ class TestNewRequestSetUser(TestCase):
|
|||
event = MagicMock(request=self.request)
|
||||
|
||||
# anonymous user
|
||||
self.assertFalse(hasattr(self.request, 'user'))
|
||||
self.assertFalse(hasattr(self.request, 'is_root'))
|
||||
self.assertFalse(hasattr(self.request, "user"))
|
||||
self.assertFalse(hasattr(self.request, "is_root"))
|
||||
subscribers.new_request_set_user(event, db_session=self.session)
|
||||
self.assertIsNone(self.request.user)
|
||||
self.assertFalse(self.request.is_root)
|
||||
|
|
@ -233,7 +250,7 @@ class TestNewRequestSetUser(TestCase):
|
|||
del self.request.is_root
|
||||
|
||||
# root status flag lives in user session
|
||||
self.request.session['is_root'] = True
|
||||
self.request.session["is_root"] = True
|
||||
subscribers.new_request_set_user(event, db_session=self.session)
|
||||
self.assertTrue(self.request.is_admin)
|
||||
self.assertTrue(self.request.is_root)
|
||||
|
|
@ -244,7 +261,7 @@ class TestNewRequestSetUser(TestCase):
|
|||
event = MagicMock(request=self.request)
|
||||
|
||||
# anonymous user
|
||||
self.assertFalse(hasattr(self.request, 'user_permissions'))
|
||||
self.assertFalse(hasattr(self.request, "user_permissions"))
|
||||
subscribers.new_request_set_user(event, db_session=self.session)
|
||||
self.assertEqual(self.request.user_permissions, set())
|
||||
|
||||
|
|
@ -254,14 +271,14 @@ class TestNewRequestSetUser(TestCase):
|
|||
# add user to role with perms
|
||||
blokes = model.Role(name="Blokes")
|
||||
self.session.add(blokes)
|
||||
auth.grant_permission(blokes, 'appinfo.list')
|
||||
auth.grant_permission(blokes, "appinfo.list")
|
||||
self.user.roles.append(blokes)
|
||||
self.session.commit()
|
||||
|
||||
# authenticated user, with perms
|
||||
self.request.user = self.user
|
||||
subscribers.new_request_set_user(event, db_session=self.session)
|
||||
self.assertEqual(self.request.user_permissions, {'appinfo.list'})
|
||||
self.assertEqual(self.request.user_permissions, {"appinfo.list"})
|
||||
|
||||
def test_has_perm(self):
|
||||
model = self.app.model
|
||||
|
|
@ -269,9 +286,9 @@ class TestNewRequestSetUser(TestCase):
|
|||
event = MagicMock(request=self.request)
|
||||
|
||||
# anonymous user
|
||||
self.assertFalse(hasattr(self.request, 'has_perm'))
|
||||
self.assertFalse(hasattr(self.request, "has_perm"))
|
||||
subscribers.new_request_set_user(event, db_session=self.session)
|
||||
self.assertFalse(self.request.has_perm('appinfo.list'))
|
||||
self.assertFalse(self.request.has_perm("appinfo.list"))
|
||||
|
||||
# reset
|
||||
del self.request.user_permissions
|
||||
|
|
@ -281,14 +298,14 @@ class TestNewRequestSetUser(TestCase):
|
|||
# add user to role with perms
|
||||
blokes = model.Role(name="Blokes")
|
||||
self.session.add(blokes)
|
||||
auth.grant_permission(blokes, 'appinfo.list')
|
||||
auth.grant_permission(blokes, "appinfo.list")
|
||||
self.user.roles.append(blokes)
|
||||
self.session.commit()
|
||||
|
||||
# authenticated user, with perms
|
||||
self.request.user = self.user
|
||||
subscribers.new_request_set_user(event, db_session=self.session)
|
||||
self.assertTrue(self.request.has_perm('appinfo.list'))
|
||||
self.assertTrue(self.request.has_perm("appinfo.list"))
|
||||
|
||||
# reset
|
||||
del self.request.user_permissions
|
||||
|
|
@ -299,7 +316,7 @@ class TestNewRequestSetUser(TestCase):
|
|||
self.user.roles.remove(blokes)
|
||||
self.session.commit()
|
||||
subscribers.new_request_set_user(event, db_session=self.session)
|
||||
self.assertFalse(self.request.has_perm('appinfo.list'))
|
||||
self.assertFalse(self.request.has_perm("appinfo.list"))
|
||||
|
||||
# reset
|
||||
del self.request.user_permissions
|
||||
|
|
@ -312,9 +329,9 @@ class TestNewRequestSetUser(TestCase):
|
|||
admin = auth.get_role_administrator(self.session)
|
||||
self.user.roles.append(admin)
|
||||
self.session.commit()
|
||||
self.request.session['is_root'] = True
|
||||
self.request.session["is_root"] = True
|
||||
subscribers.new_request_set_user(event, db_session=self.session)
|
||||
self.assertTrue(self.request.has_perm('appinfo.list'))
|
||||
self.assertTrue(self.request.has_perm("appinfo.list"))
|
||||
|
||||
def test_has_any_perm(self):
|
||||
model = self.app.model
|
||||
|
|
@ -322,9 +339,9 @@ class TestNewRequestSetUser(TestCase):
|
|||
event = MagicMock(request=self.request)
|
||||
|
||||
# anonymous user
|
||||
self.assertFalse(hasattr(self.request, 'has_any_perm'))
|
||||
self.assertFalse(hasattr(self.request, "has_any_perm"))
|
||||
subscribers.new_request_set_user(event, db_session=self.session)
|
||||
self.assertFalse(self.request.has_any_perm('appinfo.list'))
|
||||
self.assertFalse(self.request.has_any_perm("appinfo.list"))
|
||||
|
||||
# reset
|
||||
del self.request.user_permissions
|
||||
|
|
@ -334,14 +351,14 @@ class TestNewRequestSetUser(TestCase):
|
|||
# add user to role with perms
|
||||
blokes = model.Role(name="Blokes")
|
||||
self.session.add(blokes)
|
||||
auth.grant_permission(blokes, 'appinfo.list')
|
||||
auth.grant_permission(blokes, "appinfo.list")
|
||||
self.user.roles.append(blokes)
|
||||
self.session.commit()
|
||||
|
||||
# authenticated user, with perms
|
||||
self.request.user = self.user
|
||||
subscribers.new_request_set_user(event, db_session=self.session)
|
||||
self.assertTrue(self.request.has_any_perm('appinfo.list', 'appinfo.view'))
|
||||
self.assertTrue(self.request.has_any_perm("appinfo.list", "appinfo.view"))
|
||||
|
||||
# reset
|
||||
del self.request.user_permissions
|
||||
|
|
@ -352,7 +369,7 @@ class TestNewRequestSetUser(TestCase):
|
|||
self.user.roles.remove(blokes)
|
||||
self.session.commit()
|
||||
subscribers.new_request_set_user(event, db_session=self.session)
|
||||
self.assertFalse(self.request.has_any_perm('appinfo.list'))
|
||||
self.assertFalse(self.request.has_any_perm("appinfo.list"))
|
||||
|
||||
# reset
|
||||
del self.request.user_permissions
|
||||
|
|
@ -365,64 +382,66 @@ class TestNewRequestSetUser(TestCase):
|
|||
admin = auth.get_role_administrator(self.session)
|
||||
self.user.roles.append(admin)
|
||||
self.session.commit()
|
||||
self.request.session['is_root'] = True
|
||||
self.request.session["is_root"] = True
|
||||
subscribers.new_request_set_user(event, db_session=self.session)
|
||||
self.assertTrue(self.request.has_any_perm('appinfo.list'))
|
||||
self.assertTrue(self.request.has_any_perm("appinfo.list"))
|
||||
|
||||
|
||||
class TestBeforeRender(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.config = WuttaConfig(defaults={
|
||||
'wutta.web.menus.handler.spec': 'tests.util:NullMenuHandler',
|
||||
})
|
||||
self.config = WuttaConfig(
|
||||
defaults={
|
||||
"wutta.web.menus.handler.spec": "tests.util:NullMenuHandler",
|
||||
}
|
||||
)
|
||||
|
||||
def make_request(self):
|
||||
request = testing.DummyRequest(use_oruga=False)
|
||||
request.registry.settings = {'wutta_config': self.config}
|
||||
request.registry.settings = {"wutta_config": self.config}
|
||||
request.wutta_config = self.config
|
||||
return request
|
||||
|
||||
def test_basic(self):
|
||||
request = self.make_request()
|
||||
event = {'request': request}
|
||||
event = {"request": request}
|
||||
|
||||
# event dict will get populated with more context
|
||||
subscribers.before_render(event)
|
||||
|
||||
self.assertIn('config', event)
|
||||
self.assertIs(event['config'], self.config)
|
||||
self.assertIn("config", event)
|
||||
self.assertIs(event["config"], self.config)
|
||||
|
||||
self.assertIn('app', event)
|
||||
self.assertIs(event['app'], self.config.get_app())
|
||||
self.assertIn("app", event)
|
||||
self.assertIs(event["app"], self.config.get_app())
|
||||
|
||||
self.assertIn('h', event)
|
||||
self.assertIs(event['h'], helpers)
|
||||
self.assertIn("h", event)
|
||||
self.assertIs(event["h"], helpers)
|
||||
|
||||
self.assertIn('url', event)
|
||||
self.assertIn("url", event)
|
||||
# TODO: not sure how to test this?
|
||||
# self.assertIs(event['url'], request.route_url)
|
||||
|
||||
self.assertIn('json', event)
|
||||
self.assertIs(event['json'], json)
|
||||
self.assertIn("json", event)
|
||||
self.assertIs(event["json"], json)
|
||||
|
||||
# current theme should be 'default' and picker not exposed
|
||||
self.assertEqual(event['theme'], 'default')
|
||||
self.assertFalse(event['expose_theme_picker'])
|
||||
self.assertNotIn('available_themes', event)
|
||||
self.assertEqual(event["theme"], "default")
|
||||
self.assertFalse(event["expose_theme_picker"])
|
||||
self.assertNotIn("available_themes", event)
|
||||
|
||||
def test_custom_theme(self):
|
||||
self.config.setdefault('wuttaweb.themes.expose_picker', 'true')
|
||||
self.config.setdefault("wuttaweb.themes.expose_picker", "true")
|
||||
request = self.make_request()
|
||||
request.registry.settings['wuttaweb.theme'] = 'butterfly'
|
||||
event = {'request': request}
|
||||
request.registry.settings["wuttaweb.theme"] = "butterfly"
|
||||
event = {"request": request}
|
||||
|
||||
# event dict will get populated with more context
|
||||
subscribers.before_render(event)
|
||||
self.assertEqual(event['theme'], 'butterfly')
|
||||
self.assertTrue(event['expose_theme_picker'])
|
||||
self.assertIn('available_themes', event)
|
||||
self.assertEqual(event['available_themes'], ['default', 'butterfly'])
|
||||
self.assertEqual(event["theme"], "butterfly")
|
||||
self.assertTrue(event["expose_theme_picker"])
|
||||
self.assertIn("available_themes", event)
|
||||
self.assertEqual(event["available_themes"], ["default", "butterfly"])
|
||||
|
||||
|
||||
class TestIncludeMe(TestCase):
|
||||
|
|
@ -431,4 +450,4 @@ class TestIncludeMe(TestCase):
|
|||
with testing.testConfig() as pyramid_config:
|
||||
|
||||
# just ensure no error happens when included..
|
||||
pyramid_config.include('wuttaweb.subscribers')
|
||||
pyramid_config.include("wuttaweb.subscribers")
|
||||
|
|
|
|||
|
|
@ -22,40 +22,40 @@ from wuttaweb.testing import WebTestCase
|
|||
class TestFieldList(TestCase):
|
||||
|
||||
def test_insert_before(self):
|
||||
fields = mod.FieldList(['f1', 'f2'])
|
||||
self.assertEqual(fields, ['f1', 'f2'])
|
||||
fields = mod.FieldList(["f1", "f2"])
|
||||
self.assertEqual(fields, ["f1", "f2"])
|
||||
|
||||
# typical
|
||||
fields.insert_before('f1', 'XXX')
|
||||
self.assertEqual(fields, ['XXX', 'f1', 'f2'])
|
||||
fields.insert_before('f2', 'YYY')
|
||||
self.assertEqual(fields, ['XXX', 'f1', 'YYY', 'f2'])
|
||||
fields.insert_before("f1", "XXX")
|
||||
self.assertEqual(fields, ["XXX", "f1", "f2"])
|
||||
fields.insert_before("f2", "YYY")
|
||||
self.assertEqual(fields, ["XXX", "f1", "YYY", "f2"])
|
||||
|
||||
# appends new field if reference field is invalid
|
||||
fields.insert_before('f3', 'ZZZ')
|
||||
self.assertEqual(fields, ['XXX', 'f1', 'YYY', 'f2', 'ZZZ'])
|
||||
fields.insert_before("f3", "ZZZ")
|
||||
self.assertEqual(fields, ["XXX", "f1", "YYY", "f2", "ZZZ"])
|
||||
|
||||
def test_insert_after(self):
|
||||
fields = mod.FieldList(['f1', 'f2'])
|
||||
self.assertEqual(fields, ['f1', 'f2'])
|
||||
fields = mod.FieldList(["f1", "f2"])
|
||||
self.assertEqual(fields, ["f1", "f2"])
|
||||
|
||||
# typical
|
||||
fields.insert_after('f1', 'XXX')
|
||||
self.assertEqual(fields, ['f1', 'XXX', 'f2'])
|
||||
fields.insert_after('XXX', 'YYY')
|
||||
self.assertEqual(fields, ['f1', 'XXX', 'YYY', 'f2'])
|
||||
fields.insert_after("f1", "XXX")
|
||||
self.assertEqual(fields, ["f1", "XXX", "f2"])
|
||||
fields.insert_after("XXX", "YYY")
|
||||
self.assertEqual(fields, ["f1", "XXX", "YYY", "f2"])
|
||||
|
||||
# appends new field if reference field is invalid
|
||||
fields.insert_after('f3', 'ZZZ')
|
||||
self.assertEqual(fields, ['f1', 'XXX', 'YYY', 'f2', 'ZZZ'])
|
||||
fields.insert_after("f3", "ZZZ")
|
||||
self.assertEqual(fields, ["f1", "XXX", "YYY", "f2", "ZZZ"])
|
||||
|
||||
def test_set_sequence(self):
|
||||
fields = mod.FieldList(['f5', 'f1', 'f3', 'f4', 'f2'])
|
||||
fields = mod.FieldList(["f5", "f1", "f3", "f4", "f2"])
|
||||
|
||||
# setting sequence will only "sort" for explicit fields.
|
||||
# other fields remain in original order, but at the end.
|
||||
fields.set_sequence(['f1', 'f2', 'f3'])
|
||||
self.assertEqual(fields, ['f1', 'f2', 'f3', 'f5', 'f4'])
|
||||
fields.set_sequence(["f1", "f2", "f3"])
|
||||
self.assertEqual(fields, ["f1", "f2", "f3", "f5", "f4"])
|
||||
|
||||
|
||||
class TestGetLibVer(TestCase):
|
||||
|
|
@ -66,169 +66,169 @@ class TestGetLibVer(TestCase):
|
|||
self.request.wutta_config = self.config
|
||||
|
||||
def test_buefy_default(self):
|
||||
version = mod.get_libver(self.request, 'buefy')
|
||||
self.assertEqual(version, '0.9.25')
|
||||
version = mod.get_libver(self.request, "buefy")
|
||||
self.assertEqual(version, "0.9.25")
|
||||
|
||||
def test_buefy_custom_old(self):
|
||||
self.config.setdefault('wuttaweb.buefy_version', '0.9.29')
|
||||
version = mod.get_libver(self.request, 'buefy')
|
||||
self.assertEqual(version, '0.9.29')
|
||||
self.config.setdefault("wuttaweb.buefy_version", "0.9.29")
|
||||
version = mod.get_libver(self.request, "buefy")
|
||||
self.assertEqual(version, "0.9.29")
|
||||
|
||||
def test_buefy_custom_old_tailbone(self):
|
||||
self.config.setdefault('tailbone.libver.buefy', '0.9.28')
|
||||
version = mod.get_libver(self.request, 'buefy', prefix='tailbone')
|
||||
self.assertEqual(version, '0.9.28')
|
||||
self.config.setdefault("tailbone.libver.buefy", "0.9.28")
|
||||
version = mod.get_libver(self.request, "buefy", prefix="tailbone")
|
||||
self.assertEqual(version, "0.9.28")
|
||||
|
||||
def test_buefy_custom_new(self):
|
||||
self.config.setdefault('wuttaweb.libver.buefy', '0.9.29')
|
||||
version = mod.get_libver(self.request, 'buefy')
|
||||
self.assertEqual(version, '0.9.29')
|
||||
self.config.setdefault("wuttaweb.libver.buefy", "0.9.29")
|
||||
version = mod.get_libver(self.request, "buefy")
|
||||
self.assertEqual(version, "0.9.29")
|
||||
|
||||
def test_buefy_configured_only(self):
|
||||
version = mod.get_libver(self.request, 'buefy', configured_only=True)
|
||||
version = mod.get_libver(self.request, "buefy", configured_only=True)
|
||||
self.assertIsNone(version)
|
||||
|
||||
def test_buefy_default_only(self):
|
||||
self.config.setdefault('wuttaweb.libver.buefy', '0.9.29')
|
||||
version = mod.get_libver(self.request, 'buefy', default_only=True)
|
||||
self.assertEqual(version, '0.9.25')
|
||||
self.config.setdefault("wuttaweb.libver.buefy", "0.9.29")
|
||||
version = mod.get_libver(self.request, "buefy", default_only=True)
|
||||
self.assertEqual(version, "0.9.25")
|
||||
|
||||
def test_buefy_css_default(self):
|
||||
version = mod.get_libver(self.request, 'buefy.css')
|
||||
self.assertEqual(version, '0.9.25')
|
||||
version = mod.get_libver(self.request, "buefy.css")
|
||||
self.assertEqual(version, "0.9.25")
|
||||
|
||||
def test_buefy_css_custom_old(self):
|
||||
# nb. this uses same setting as buefy (js)
|
||||
self.config.setdefault('wuttaweb.buefy_version', '0.9.29')
|
||||
version = mod.get_libver(self.request, 'buefy.css')
|
||||
self.assertEqual(version, '0.9.29')
|
||||
self.config.setdefault("wuttaweb.buefy_version", "0.9.29")
|
||||
version = mod.get_libver(self.request, "buefy.css")
|
||||
self.assertEqual(version, "0.9.29")
|
||||
|
||||
def test_buefy_css_custom_new(self):
|
||||
# nb. this uses same setting as buefy (js)
|
||||
self.config.setdefault('wuttaweb.libver.buefy', '0.9.29')
|
||||
version = mod.get_libver(self.request, 'buefy.css')
|
||||
self.assertEqual(version, '0.9.29')
|
||||
self.config.setdefault("wuttaweb.libver.buefy", "0.9.29")
|
||||
version = mod.get_libver(self.request, "buefy.css")
|
||||
self.assertEqual(version, "0.9.29")
|
||||
|
||||
def test_buefy_css_configured_only(self):
|
||||
version = mod.get_libver(self.request, 'buefy.css', configured_only=True)
|
||||
version = mod.get_libver(self.request, "buefy.css", configured_only=True)
|
||||
self.assertIsNone(version)
|
||||
|
||||
def test_buefy_css_default_only(self):
|
||||
self.config.setdefault('wuttaweb.libver.buefy', '0.9.29')
|
||||
version = mod.get_libver(self.request, 'buefy.css', default_only=True)
|
||||
self.assertEqual(version, '0.9.25')
|
||||
self.config.setdefault("wuttaweb.libver.buefy", "0.9.29")
|
||||
version = mod.get_libver(self.request, "buefy.css", default_only=True)
|
||||
self.assertEqual(version, "0.9.25")
|
||||
|
||||
def test_vue_default(self):
|
||||
version = mod.get_libver(self.request, 'vue')
|
||||
self.assertEqual(version, '2.6.14')
|
||||
version = mod.get_libver(self.request, "vue")
|
||||
self.assertEqual(version, "2.6.14")
|
||||
|
||||
def test_vue_custom_old(self):
|
||||
self.config.setdefault('wuttaweb.vue_version', '3.4.31')
|
||||
version = mod.get_libver(self.request, 'vue')
|
||||
self.assertEqual(version, '3.4.31')
|
||||
self.config.setdefault("wuttaweb.vue_version", "3.4.31")
|
||||
version = mod.get_libver(self.request, "vue")
|
||||
self.assertEqual(version, "3.4.31")
|
||||
|
||||
def test_vue_custom_new(self):
|
||||
self.config.setdefault('wuttaweb.libver.vue', '3.4.31')
|
||||
version = mod.get_libver(self.request, 'vue')
|
||||
self.assertEqual(version, '3.4.31')
|
||||
self.config.setdefault("wuttaweb.libver.vue", "3.4.31")
|
||||
version = mod.get_libver(self.request, "vue")
|
||||
self.assertEqual(version, "3.4.31")
|
||||
|
||||
def test_vue_configured_only(self):
|
||||
version = mod.get_libver(self.request, 'vue', configured_only=True)
|
||||
version = mod.get_libver(self.request, "vue", configured_only=True)
|
||||
self.assertIsNone(version)
|
||||
|
||||
def test_vue_default_only(self):
|
||||
self.config.setdefault('wuttaweb.libver.vue', '3.4.31')
|
||||
version = mod.get_libver(self.request, 'vue', default_only=True)
|
||||
self.assertEqual(version, '2.6.14')
|
||||
self.config.setdefault("wuttaweb.libver.vue", "3.4.31")
|
||||
version = mod.get_libver(self.request, "vue", default_only=True)
|
||||
self.assertEqual(version, "2.6.14")
|
||||
|
||||
def test_vue_resource_default(self):
|
||||
version = mod.get_libver(self.request, 'vue_resource')
|
||||
self.assertEqual(version, '1.5.3')
|
||||
version = mod.get_libver(self.request, "vue_resource")
|
||||
self.assertEqual(version, "1.5.3")
|
||||
|
||||
def test_vue_resource_custom(self):
|
||||
self.config.setdefault('wuttaweb.libver.vue_resource', '1.5.3')
|
||||
version = mod.get_libver(self.request, 'vue_resource')
|
||||
self.assertEqual(version, '1.5.3')
|
||||
self.config.setdefault("wuttaweb.libver.vue_resource", "1.5.3")
|
||||
version = mod.get_libver(self.request, "vue_resource")
|
||||
self.assertEqual(version, "1.5.3")
|
||||
|
||||
def test_fontawesome_default(self):
|
||||
version = mod.get_libver(self.request, 'fontawesome')
|
||||
self.assertEqual(version, '5.3.1')
|
||||
version = mod.get_libver(self.request, "fontawesome")
|
||||
self.assertEqual(version, "5.3.1")
|
||||
|
||||
def test_fontawesome_custom(self):
|
||||
self.config.setdefault('wuttaweb.libver.fontawesome', '5.6.3')
|
||||
version = mod.get_libver(self.request, 'fontawesome')
|
||||
self.assertEqual(version, '5.6.3')
|
||||
self.config.setdefault("wuttaweb.libver.fontawesome", "5.6.3")
|
||||
version = mod.get_libver(self.request, "fontawesome")
|
||||
self.assertEqual(version, "5.6.3")
|
||||
|
||||
def test_bb_vue_default(self):
|
||||
version = mod.get_libver(self.request, 'bb_vue')
|
||||
self.assertEqual(version, '3.5.18')
|
||||
version = mod.get_libver(self.request, "bb_vue")
|
||||
self.assertEqual(version, "3.5.18")
|
||||
|
||||
def test_bb_vue_custom(self):
|
||||
self.config.setdefault('wuttaweb.libver.bb_vue', '3.4.30')
|
||||
version = mod.get_libver(self.request, 'bb_vue')
|
||||
self.assertEqual(version, '3.4.30')
|
||||
self.config.setdefault("wuttaweb.libver.bb_vue", "3.4.30")
|
||||
version = mod.get_libver(self.request, "bb_vue")
|
||||
self.assertEqual(version, "3.4.30")
|
||||
|
||||
def test_bb_oruga_default(self):
|
||||
version = mod.get_libver(self.request, 'bb_oruga')
|
||||
self.assertEqual(version, '0.11.4')
|
||||
version = mod.get_libver(self.request, "bb_oruga")
|
||||
self.assertEqual(version, "0.11.4")
|
||||
|
||||
def test_bb_oruga_custom(self):
|
||||
self.config.setdefault('wuttaweb.libver.bb_oruga', '0.8.11')
|
||||
version = mod.get_libver(self.request, 'bb_oruga')
|
||||
self.assertEqual(version, '0.8.11')
|
||||
self.config.setdefault("wuttaweb.libver.bb_oruga", "0.8.11")
|
||||
version = mod.get_libver(self.request, "bb_oruga")
|
||||
self.assertEqual(version, "0.8.11")
|
||||
|
||||
def test_bb_oruga_bulma_default(self):
|
||||
version = mod.get_libver(self.request, 'bb_oruga_bulma')
|
||||
self.assertEqual(version, '0.7.3')
|
||||
version = mod.get_libver(self.request, 'bb_oruga_bulma_css')
|
||||
self.assertEqual(version, '0.7.3')
|
||||
version = mod.get_libver(self.request, "bb_oruga_bulma")
|
||||
self.assertEqual(version, "0.7.3")
|
||||
version = mod.get_libver(self.request, "bb_oruga_bulma_css")
|
||||
self.assertEqual(version, "0.7.3")
|
||||
|
||||
def test_bb_oruga_bulma_custom(self):
|
||||
self.config.setdefault('wuttaweb.libver.bb_oruga_bulma', '0.2.11')
|
||||
version = mod.get_libver(self.request, 'bb_oruga_bulma')
|
||||
self.assertEqual(version, '0.2.11')
|
||||
self.config.setdefault("wuttaweb.libver.bb_oruga_bulma", "0.2.11")
|
||||
version = mod.get_libver(self.request, "bb_oruga_bulma")
|
||||
self.assertEqual(version, "0.2.11")
|
||||
|
||||
def test_bb_fontawesome_svg_core_default(self):
|
||||
version = mod.get_libver(self.request, 'bb_fontawesome_svg_core')
|
||||
self.assertEqual(version, '7.0.0')
|
||||
version = mod.get_libver(self.request, "bb_fontawesome_svg_core")
|
||||
self.assertEqual(version, "7.0.0")
|
||||
|
||||
def test_bb_fontawesome_svg_core_custom(self):
|
||||
self.config.setdefault('wuttaweb.libver.bb_fontawesome_svg_core', '6.5.1')
|
||||
version = mod.get_libver(self.request, 'bb_fontawesome_svg_core')
|
||||
self.assertEqual(version, '6.5.1')
|
||||
self.config.setdefault("wuttaweb.libver.bb_fontawesome_svg_core", "6.5.1")
|
||||
version = mod.get_libver(self.request, "bb_fontawesome_svg_core")
|
||||
self.assertEqual(version, "6.5.1")
|
||||
|
||||
def test_bb_free_solid_svg_icons_default(self):
|
||||
version = mod.get_libver(self.request, 'bb_free_solid_svg_icons')
|
||||
self.assertEqual(version, '7.0.0')
|
||||
version = mod.get_libver(self.request, "bb_free_solid_svg_icons")
|
||||
self.assertEqual(version, "7.0.0")
|
||||
|
||||
def test_bb_free_solid_svg_icons_custom(self):
|
||||
self.config.setdefault('wuttaweb.libver.bb_free_solid_svg_icons', '6.5.1')
|
||||
version = mod.get_libver(self.request, 'bb_free_solid_svg_icons')
|
||||
self.assertEqual(version, '6.5.1')
|
||||
self.config.setdefault("wuttaweb.libver.bb_free_solid_svg_icons", "6.5.1")
|
||||
version = mod.get_libver(self.request, "bb_free_solid_svg_icons")
|
||||
self.assertEqual(version, "6.5.1")
|
||||
|
||||
def test_bb_vue_fontawesome_default(self):
|
||||
version = mod.get_libver(self.request, 'bb_vue_fontawesome')
|
||||
self.assertEqual(version, '3.1.1')
|
||||
version = mod.get_libver(self.request, "bb_vue_fontawesome")
|
||||
self.assertEqual(version, "3.1.1")
|
||||
|
||||
def test_bb_vue_fontawesome_custom(self):
|
||||
self.config.setdefault('wuttaweb.libver.bb_vue_fontawesome', '3.0.8')
|
||||
version = mod.get_libver(self.request, 'bb_vue_fontawesome')
|
||||
self.assertEqual(version, '3.0.8')
|
||||
self.config.setdefault("wuttaweb.libver.bb_vue_fontawesome", "3.0.8")
|
||||
version = mod.get_libver(self.request, "bb_vue_fontawesome")
|
||||
self.assertEqual(version, "3.0.8")
|
||||
|
||||
|
||||
libcache = Library('testing', 'libcache')
|
||||
vue_js = Resource(libcache, 'vue.js')
|
||||
vue_resource_js = Resource(libcache, 'vue_resource.js')
|
||||
buefy_js = Resource(libcache, 'buefy.js')
|
||||
buefy_css = Resource(libcache, 'buefy.css')
|
||||
fontawesome_js = Resource(libcache, 'fontawesome.js')
|
||||
bb_vue_js = Resource(libcache, 'bb_vue.js')
|
||||
bb_oruga_js = Resource(libcache, 'bb_oruga.js')
|
||||
bb_oruga_bulma_js = Resource(libcache, 'bb_oruga_bulma.js')
|
||||
bb_oruga_bulma_css = Resource(libcache, 'bb_oruga_bulma.css')
|
||||
bb_fontawesome_svg_core_js = Resource(libcache, 'bb_fontawesome_svg_core.js')
|
||||
bb_free_solid_svg_icons_js = Resource(libcache, 'bb_free_solid_svg_icons.js')
|
||||
bb_vue_fontawesome_js = Resource(libcache, 'bb_vue_fontawesome.js')
|
||||
libcache = Library("testing", "libcache")
|
||||
vue_js = Resource(libcache, "vue.js")
|
||||
vue_resource_js = Resource(libcache, "vue_resource.js")
|
||||
buefy_js = Resource(libcache, "buefy.js")
|
||||
buefy_css = Resource(libcache, "buefy.css")
|
||||
fontawesome_js = Resource(libcache, "fontawesome.js")
|
||||
bb_vue_js = Resource(libcache, "bb_vue.js")
|
||||
bb_oruga_js = Resource(libcache, "bb_oruga.js")
|
||||
bb_oruga_bulma_js = Resource(libcache, "bb_oruga_bulma.js")
|
||||
bb_oruga_bulma_css = Resource(libcache, "bb_oruga_bulma.css")
|
||||
bb_fontawesome_svg_core_js = Resource(libcache, "bb_fontawesome_svg_core.js")
|
||||
bb_free_solid_svg_icons_js = Resource(libcache, "bb_free_solid_svg_icons.js")
|
||||
bb_vue_fontawesome_js = Resource(libcache, "bb_vue_fontawesome.js")
|
||||
|
||||
|
||||
class TestGetLibUrl(TestCase):
|
||||
|
|
@ -242,203 +242,226 @@ class TestGetLibUrl(TestCase):
|
|||
testing.tearDown()
|
||||
|
||||
def setup_fanstatic(self, register=True):
|
||||
self.pyramid_config.include('pyramid_fanstatic')
|
||||
self.pyramid_config.include("pyramid_fanstatic")
|
||||
if register:
|
||||
self.config.setdefault('wuttaweb.static_libcache.module',
|
||||
'tests.test_util')
|
||||
self.config.setdefault("wuttaweb.static_libcache.module", "tests.test_util")
|
||||
|
||||
needed = MagicMock()
|
||||
needed.library_url = MagicMock(return_value='/fanstatic')
|
||||
self.request.environ['fanstatic.needed'] = needed
|
||||
self.request.script_name = '/wutta'
|
||||
needed.library_url = MagicMock(return_value="/fanstatic")
|
||||
self.request.environ["fanstatic.needed"] = needed
|
||||
self.request.script_name = "/wutta"
|
||||
|
||||
def test_buefy_default(self):
|
||||
url = mod.get_liburl(self.request, 'buefy')
|
||||
self.assertEqual(url, 'https://unpkg.com/buefy@0.9.25/dist/buefy.min.js')
|
||||
url = mod.get_liburl(self.request, "buefy")
|
||||
self.assertEqual(url, "https://unpkg.com/buefy@0.9.25/dist/buefy.min.js")
|
||||
|
||||
def test_buefy_custom(self):
|
||||
self.config.setdefault('wuttaweb.liburl.buefy', '/lib/buefy.js')
|
||||
url = mod.get_liburl(self.request, 'buefy')
|
||||
self.assertEqual(url, '/lib/buefy.js')
|
||||
self.config.setdefault("wuttaweb.liburl.buefy", "/lib/buefy.js")
|
||||
url = mod.get_liburl(self.request, "buefy")
|
||||
self.assertEqual(url, "/lib/buefy.js")
|
||||
|
||||
def test_buefy_custom_tailbone(self):
|
||||
self.config.setdefault('tailbone.liburl.buefy', '/tailbone/buefy.js')
|
||||
url = mod.get_liburl(self.request, 'buefy', prefix='tailbone')
|
||||
self.assertEqual(url, '/tailbone/buefy.js')
|
||||
self.config.setdefault("tailbone.liburl.buefy", "/tailbone/buefy.js")
|
||||
url = mod.get_liburl(self.request, "buefy", prefix="tailbone")
|
||||
self.assertEqual(url, "/tailbone/buefy.js")
|
||||
|
||||
def test_buefy_default_only(self):
|
||||
self.config.setdefault('wuttaweb.liburl.buefy', '/lib/buefy.js')
|
||||
url = mod.get_liburl(self.request, 'buefy', default_only=True)
|
||||
self.assertEqual(url, 'https://unpkg.com/buefy@0.9.25/dist/buefy.min.js')
|
||||
self.config.setdefault("wuttaweb.liburl.buefy", "/lib/buefy.js")
|
||||
url = mod.get_liburl(self.request, "buefy", default_only=True)
|
||||
self.assertEqual(url, "https://unpkg.com/buefy@0.9.25/dist/buefy.min.js")
|
||||
|
||||
def test_buefy_configured_only(self):
|
||||
url = mod.get_liburl(self.request, 'buefy', configured_only=True)
|
||||
url = mod.get_liburl(self.request, "buefy", configured_only=True)
|
||||
self.assertIsNone(url)
|
||||
|
||||
def test_buefy_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = mod.get_liburl(self.request, 'buefy')
|
||||
self.assertEqual(url, '/wutta/fanstatic/buefy.js')
|
||||
url = mod.get_liburl(self.request, "buefy")
|
||||
self.assertEqual(url, "/wutta/fanstatic/buefy.js")
|
||||
|
||||
def test_buefy_fanstatic_tailbone(self):
|
||||
self.setup_fanstatic(register=False)
|
||||
self.config.setdefault('tailbone.static_libcache.module', 'tests.test_util')
|
||||
url = mod.get_liburl(self.request, 'buefy', prefix='tailbone')
|
||||
self.assertEqual(url, '/wutta/fanstatic/buefy.js')
|
||||
self.config.setdefault("tailbone.static_libcache.module", "tests.test_util")
|
||||
url = mod.get_liburl(self.request, "buefy", prefix="tailbone")
|
||||
self.assertEqual(url, "/wutta/fanstatic/buefy.js")
|
||||
|
||||
def test_buefy_css_default(self):
|
||||
url = mod.get_liburl(self.request, 'buefy.css')
|
||||
self.assertEqual(url, 'https://unpkg.com/buefy@0.9.25/dist/buefy.min.css')
|
||||
url = mod.get_liburl(self.request, "buefy.css")
|
||||
self.assertEqual(url, "https://unpkg.com/buefy@0.9.25/dist/buefy.min.css")
|
||||
|
||||
def test_buefy_css_custom(self):
|
||||
self.config.setdefault('wuttaweb.liburl.buefy.css', '/lib/buefy.css')
|
||||
url = mod.get_liburl(self.request, 'buefy.css')
|
||||
self.assertEqual(url, '/lib/buefy.css')
|
||||
self.config.setdefault("wuttaweb.liburl.buefy.css", "/lib/buefy.css")
|
||||
url = mod.get_liburl(self.request, "buefy.css")
|
||||
self.assertEqual(url, "/lib/buefy.css")
|
||||
|
||||
def test_buefy_css_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = mod.get_liburl(self.request, 'buefy.css')
|
||||
self.assertEqual(url, '/wutta/fanstatic/buefy.css')
|
||||
url = mod.get_liburl(self.request, "buefy.css")
|
||||
self.assertEqual(url, "/wutta/fanstatic/buefy.css")
|
||||
|
||||
def test_vue_default(self):
|
||||
url = mod.get_liburl(self.request, 'vue')
|
||||
self.assertEqual(url, 'https://unpkg.com/vue@2.6.14/dist/vue.min.js')
|
||||
url = mod.get_liburl(self.request, "vue")
|
||||
self.assertEqual(url, "https://unpkg.com/vue@2.6.14/dist/vue.min.js")
|
||||
|
||||
def test_vue_custom(self):
|
||||
self.config.setdefault('wuttaweb.liburl.vue', '/lib/vue.js')
|
||||
url = mod.get_liburl(self.request, 'vue')
|
||||
self.assertEqual(url, '/lib/vue.js')
|
||||
self.config.setdefault("wuttaweb.liburl.vue", "/lib/vue.js")
|
||||
url = mod.get_liburl(self.request, "vue")
|
||||
self.assertEqual(url, "/lib/vue.js")
|
||||
|
||||
def test_vue_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = mod.get_liburl(self.request, 'vue')
|
||||
self.assertEqual(url, '/wutta/fanstatic/vue.js')
|
||||
url = mod.get_liburl(self.request, "vue")
|
||||
self.assertEqual(url, "/wutta/fanstatic/vue.js")
|
||||
|
||||
def test_vue_resource_default(self):
|
||||
url = mod.get_liburl(self.request, 'vue_resource')
|
||||
self.assertEqual(url, 'https://cdn.jsdelivr.net/npm/vue-resource@1.5.3')
|
||||
url = mod.get_liburl(self.request, "vue_resource")
|
||||
self.assertEqual(url, "https://cdn.jsdelivr.net/npm/vue-resource@1.5.3")
|
||||
|
||||
def test_vue_resource_custom(self):
|
||||
self.config.setdefault('wuttaweb.liburl.vue_resource', '/lib/vue-resource.js')
|
||||
url = mod.get_liburl(self.request, 'vue_resource')
|
||||
self.assertEqual(url, '/lib/vue-resource.js')
|
||||
self.config.setdefault("wuttaweb.liburl.vue_resource", "/lib/vue-resource.js")
|
||||
url = mod.get_liburl(self.request, "vue_resource")
|
||||
self.assertEqual(url, "/lib/vue-resource.js")
|
||||
|
||||
def test_vue_resource_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = mod.get_liburl(self.request, 'vue_resource')
|
||||
self.assertEqual(url, '/wutta/fanstatic/vue_resource.js')
|
||||
url = mod.get_liburl(self.request, "vue_resource")
|
||||
self.assertEqual(url, "/wutta/fanstatic/vue_resource.js")
|
||||
|
||||
def test_fontawesome_default(self):
|
||||
url = mod.get_liburl(self.request, 'fontawesome')
|
||||
self.assertEqual(url, 'https://use.fontawesome.com/releases/v5.3.1/js/all.js')
|
||||
url = mod.get_liburl(self.request, "fontawesome")
|
||||
self.assertEqual(url, "https://use.fontawesome.com/releases/v5.3.1/js/all.js")
|
||||
|
||||
def test_fontawesome_custom(self):
|
||||
self.config.setdefault('wuttaweb.liburl.fontawesome', '/lib/fontawesome.js')
|
||||
url = mod.get_liburl(self.request, 'fontawesome')
|
||||
self.assertEqual(url, '/lib/fontawesome.js')
|
||||
self.config.setdefault("wuttaweb.liburl.fontawesome", "/lib/fontawesome.js")
|
||||
url = mod.get_liburl(self.request, "fontawesome")
|
||||
self.assertEqual(url, "/lib/fontawesome.js")
|
||||
|
||||
def test_fontawesome_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = mod.get_liburl(self.request, 'fontawesome')
|
||||
self.assertEqual(url, '/wutta/fanstatic/fontawesome.js')
|
||||
url = mod.get_liburl(self.request, "fontawesome")
|
||||
self.assertEqual(url, "/wutta/fanstatic/fontawesome.js")
|
||||
|
||||
def test_bb_vue_default(self):
|
||||
url = mod.get_liburl(self.request, 'bb_vue')
|
||||
self.assertEqual(url, 'https://unpkg.com/vue@3.5.18/dist/vue.esm-browser.prod.js')
|
||||
url = mod.get_liburl(self.request, "bb_vue")
|
||||
self.assertEqual(
|
||||
url, "https://unpkg.com/vue@3.5.18/dist/vue.esm-browser.prod.js"
|
||||
)
|
||||
|
||||
def test_bb_vue_custom(self):
|
||||
self.config.setdefault('wuttaweb.liburl.bb_vue', '/lib/vue.js')
|
||||
url = mod.get_liburl(self.request, 'bb_vue')
|
||||
self.assertEqual(url, '/lib/vue.js')
|
||||
self.config.setdefault("wuttaweb.liburl.bb_vue", "/lib/vue.js")
|
||||
url = mod.get_liburl(self.request, "bb_vue")
|
||||
self.assertEqual(url, "/lib/vue.js")
|
||||
|
||||
def test_bb_vue_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = mod.get_liburl(self.request, 'bb_vue')
|
||||
self.assertEqual(url, '/wutta/fanstatic/bb_vue.js')
|
||||
url = mod.get_liburl(self.request, "bb_vue")
|
||||
self.assertEqual(url, "/wutta/fanstatic/bb_vue.js")
|
||||
|
||||
def test_bb_oruga_default(self):
|
||||
url = mod.get_liburl(self.request, 'bb_oruga')
|
||||
self.assertEqual(url, 'https://unpkg.com/@oruga-ui/oruga-next@0.11.4/dist/oruga.mjs')
|
||||
url = mod.get_liburl(self.request, "bb_oruga")
|
||||
self.assertEqual(
|
||||
url, "https://unpkg.com/@oruga-ui/oruga-next@0.11.4/dist/oruga.mjs"
|
||||
)
|
||||
|
||||
def test_bb_oruga_custom(self):
|
||||
self.config.setdefault('wuttaweb.liburl.bb_oruga', '/lib/oruga.js')
|
||||
url = mod.get_liburl(self.request, 'bb_oruga')
|
||||
self.assertEqual(url, '/lib/oruga.js')
|
||||
self.config.setdefault("wuttaweb.liburl.bb_oruga", "/lib/oruga.js")
|
||||
url = mod.get_liburl(self.request, "bb_oruga")
|
||||
self.assertEqual(url, "/lib/oruga.js")
|
||||
|
||||
def test_bb_oruga_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = mod.get_liburl(self.request, 'bb_oruga')
|
||||
self.assertEqual(url, '/wutta/fanstatic/bb_oruga.js')
|
||||
url = mod.get_liburl(self.request, "bb_oruga")
|
||||
self.assertEqual(url, "/wutta/fanstatic/bb_oruga.js")
|
||||
|
||||
def test_bb_oruga_bulma_default(self):
|
||||
url = mod.get_liburl(self.request, 'bb_oruga_bulma')
|
||||
self.assertEqual(url, 'https://unpkg.com/@oruga-ui/theme-bulma@0.7.3/dist/bulma.js')
|
||||
url = mod.get_liburl(self.request, "bb_oruga_bulma")
|
||||
self.assertEqual(
|
||||
url, "https://unpkg.com/@oruga-ui/theme-bulma@0.7.3/dist/bulma.js"
|
||||
)
|
||||
|
||||
def test_bb_oruga_bulma_custom(self):
|
||||
self.config.setdefault('wuttaweb.liburl.bb_oruga_bulma', '/lib/oruga_bulma.js')
|
||||
url = mod.get_liburl(self.request, 'bb_oruga_bulma')
|
||||
self.assertEqual(url, '/lib/oruga_bulma.js')
|
||||
self.config.setdefault("wuttaweb.liburl.bb_oruga_bulma", "/lib/oruga_bulma.js")
|
||||
url = mod.get_liburl(self.request, "bb_oruga_bulma")
|
||||
self.assertEqual(url, "/lib/oruga_bulma.js")
|
||||
|
||||
def test_bb_oruga_bulma_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = mod.get_liburl(self.request, 'bb_oruga_bulma')
|
||||
self.assertEqual(url, '/wutta/fanstatic/bb_oruga_bulma.js')
|
||||
url = mod.get_liburl(self.request, "bb_oruga_bulma")
|
||||
self.assertEqual(url, "/wutta/fanstatic/bb_oruga_bulma.js")
|
||||
|
||||
def test_bb_oruga_bulma_css_default(self):
|
||||
url = mod.get_liburl(self.request, 'bb_oruga_bulma_css')
|
||||
self.assertEqual(url, 'https://unpkg.com/@oruga-ui/theme-bulma@0.7.3/dist/bulma.css')
|
||||
url = mod.get_liburl(self.request, "bb_oruga_bulma_css")
|
||||
self.assertEqual(
|
||||
url, "https://unpkg.com/@oruga-ui/theme-bulma@0.7.3/dist/bulma.css"
|
||||
)
|
||||
|
||||
def test_bb_oruga_bulma_css_custom(self):
|
||||
self.config.setdefault('wuttaweb.liburl.bb_oruga_bulma_css', '/lib/oruga-bulma.css')
|
||||
url = mod.get_liburl(self.request, 'bb_oruga_bulma_css')
|
||||
self.assertEqual(url, '/lib/oruga-bulma.css')
|
||||
self.config.setdefault(
|
||||
"wuttaweb.liburl.bb_oruga_bulma_css", "/lib/oruga-bulma.css"
|
||||
)
|
||||
url = mod.get_liburl(self.request, "bb_oruga_bulma_css")
|
||||
self.assertEqual(url, "/lib/oruga-bulma.css")
|
||||
|
||||
def test_bb_oruga_bulma_css_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = mod.get_liburl(self.request, 'bb_oruga_bulma_css')
|
||||
self.assertEqual(url, '/wutta/fanstatic/bb_oruga_bulma.css')
|
||||
url = mod.get_liburl(self.request, "bb_oruga_bulma_css")
|
||||
self.assertEqual(url, "/wutta/fanstatic/bb_oruga_bulma.css")
|
||||
|
||||
def test_bb_fontawesome_svg_core_default(self):
|
||||
url = mod.get_liburl(self.request, 'bb_fontawesome_svg_core')
|
||||
self.assertEqual(url, 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-svg-core@7.0.0/+esm')
|
||||
url = mod.get_liburl(self.request, "bb_fontawesome_svg_core")
|
||||
self.assertEqual(
|
||||
url,
|
||||
"https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-svg-core@7.0.0/+esm",
|
||||
)
|
||||
|
||||
def test_bb_fontawesome_svg_core_custom(self):
|
||||
self.config.setdefault('wuttaweb.liburl.bb_fontawesome_svg_core', '/lib/fontawesome-svg-core.js')
|
||||
url = mod.get_liburl(self.request, 'bb_fontawesome_svg_core')
|
||||
self.assertEqual(url, '/lib/fontawesome-svg-core.js')
|
||||
self.config.setdefault(
|
||||
"wuttaweb.liburl.bb_fontawesome_svg_core", "/lib/fontawesome-svg-core.js"
|
||||
)
|
||||
url = mod.get_liburl(self.request, "bb_fontawesome_svg_core")
|
||||
self.assertEqual(url, "/lib/fontawesome-svg-core.js")
|
||||
|
||||
def test_bb_fontawesome_svg_core_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = mod.get_liburl(self.request, 'bb_fontawesome_svg_core')
|
||||
self.assertEqual(url, '/wutta/fanstatic/bb_fontawesome_svg_core.js')
|
||||
url = mod.get_liburl(self.request, "bb_fontawesome_svg_core")
|
||||
self.assertEqual(url, "/wutta/fanstatic/bb_fontawesome_svg_core.js")
|
||||
|
||||
def test_bb_free_solid_svg_icons_default(self):
|
||||
url = mod.get_liburl(self.request, 'bb_free_solid_svg_icons')
|
||||
self.assertEqual(url, 'https://cdn.jsdelivr.net/npm/@fortawesome/free-solid-svg-icons@7.0.0/+esm')
|
||||
url = mod.get_liburl(self.request, "bb_free_solid_svg_icons")
|
||||
self.assertEqual(
|
||||
url,
|
||||
"https://cdn.jsdelivr.net/npm/@fortawesome/free-solid-svg-icons@7.0.0/+esm",
|
||||
)
|
||||
|
||||
def test_bb_free_solid_svg_icons_custom(self):
|
||||
self.config.setdefault('wuttaweb.liburl.bb_free_solid_svg_icons', '/lib/free-solid-svg-icons.js')
|
||||
url = mod.get_liburl(self.request, 'bb_free_solid_svg_icons')
|
||||
self.assertEqual(url, '/lib/free-solid-svg-icons.js')
|
||||
self.config.setdefault(
|
||||
"wuttaweb.liburl.bb_free_solid_svg_icons", "/lib/free-solid-svg-icons.js"
|
||||
)
|
||||
url = mod.get_liburl(self.request, "bb_free_solid_svg_icons")
|
||||
self.assertEqual(url, "/lib/free-solid-svg-icons.js")
|
||||
|
||||
def test_bb_free_solid_svg_icons_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = mod.get_liburl(self.request, 'bb_free_solid_svg_icons')
|
||||
self.assertEqual(url, '/wutta/fanstatic/bb_free_solid_svg_icons.js')
|
||||
url = mod.get_liburl(self.request, "bb_free_solid_svg_icons")
|
||||
self.assertEqual(url, "/wutta/fanstatic/bb_free_solid_svg_icons.js")
|
||||
|
||||
def test_bb_vue_fontawesome_default(self):
|
||||
url = mod.get_liburl(self.request, 'bb_vue_fontawesome')
|
||||
self.assertEqual(url, 'https://cdn.jsdelivr.net/npm/@fortawesome/vue-fontawesome@3.1.1/+esm')
|
||||
url = mod.get_liburl(self.request, "bb_vue_fontawesome")
|
||||
self.assertEqual(
|
||||
url, "https://cdn.jsdelivr.net/npm/@fortawesome/vue-fontawesome@3.1.1/+esm"
|
||||
)
|
||||
|
||||
def test_bb_vue_fontawesome_custom(self):
|
||||
self.config.setdefault('wuttaweb.liburl.bb_vue_fontawesome', '/lib/vue-fontawesome.js')
|
||||
url = mod.get_liburl(self.request, 'bb_vue_fontawesome')
|
||||
self.assertEqual(url, '/lib/vue-fontawesome.js')
|
||||
self.config.setdefault(
|
||||
"wuttaweb.liburl.bb_vue_fontawesome", "/lib/vue-fontawesome.js"
|
||||
)
|
||||
url = mod.get_liburl(self.request, "bb_vue_fontawesome")
|
||||
self.assertEqual(url, "/lib/vue-fontawesome.js")
|
||||
|
||||
def test_bb_vue_fontawesome_fanstatic(self):
|
||||
self.setup_fanstatic()
|
||||
url = mod.get_liburl(self.request, 'bb_vue_fontawesome')
|
||||
self.assertEqual(url, '/wutta/fanstatic/bb_vue_fontawesome.js')
|
||||
url = mod.get_liburl(self.request, "bb_vue_fontawesome")
|
||||
self.assertEqual(url, "/wutta/fanstatic/bb_vue_fontawesome.js")
|
||||
|
||||
|
||||
class TestGetFormData(TestCase):
|
||||
|
|
@ -447,25 +470,25 @@ class TestGetFormData(TestCase):
|
|||
self.config = WuttaConfig()
|
||||
|
||||
def make_request(self, **kwargs):
|
||||
kwargs.setdefault('wutta_config', self.config)
|
||||
kwargs.setdefault('POST', {'foo1': 'bar'})
|
||||
kwargs.setdefault('json_body', {'foo2': 'baz'})
|
||||
kwargs.setdefault("wutta_config", self.config)
|
||||
kwargs.setdefault("POST", {"foo1": "bar"})
|
||||
kwargs.setdefault("json_body", {"foo2": "baz"})
|
||||
return testing.DummyRequest(**kwargs)
|
||||
|
||||
def test_default(self):
|
||||
request = self.make_request()
|
||||
data = mod.get_form_data(request)
|
||||
self.assertEqual(data, {'foo1': 'bar'})
|
||||
self.assertEqual(data, {"foo1": "bar"})
|
||||
|
||||
def test_is_xhr(self):
|
||||
request = self.make_request(POST=None, is_xhr=True)
|
||||
data = mod.get_form_data(request)
|
||||
self.assertEqual(data, {'foo2': 'baz'})
|
||||
self.assertEqual(data, {"foo2": "baz"})
|
||||
|
||||
def test_content_type(self):
|
||||
request = self.make_request(POST=None, content_type='application/json')
|
||||
request = self.make_request(POST=None, content_type="application/json")
|
||||
data = mod.get_form_data(request)
|
||||
self.assertEqual(data, {'foo2': 'baz'})
|
||||
self.assertEqual(data, {"foo2": "baz"})
|
||||
|
||||
|
||||
class TestGetModelFields(ConfigTestCase):
|
||||
|
|
@ -481,41 +504,47 @@ class TestGetModelFields(ConfigTestCase):
|
|||
def test_basic(self):
|
||||
model = self.app.model
|
||||
fields = mod.get_model_fields(self.config, model.Setting)
|
||||
self.assertEqual(fields, ['name', 'value'])
|
||||
self.assertEqual(fields, ["name", "value"])
|
||||
|
||||
def test_include_fk(self):
|
||||
model = self.app.model
|
||||
|
||||
# fk excluded by default
|
||||
fields = mod.get_model_fields(self.config, model.User)
|
||||
self.assertNotIn('person_uuid', fields)
|
||||
self.assertIn('person', fields)
|
||||
self.assertNotIn("person_uuid", fields)
|
||||
self.assertIn("person", fields)
|
||||
|
||||
# fk can be included
|
||||
fields = mod.get_model_fields(self.config, model.User, include_fk=True)
|
||||
self.assertIn('person_uuid', fields)
|
||||
self.assertIn('person', fields)
|
||||
self.assertIn("person_uuid", fields)
|
||||
self.assertIn("person", fields)
|
||||
|
||||
def test_avoid_versions(self):
|
||||
model = self.app.model
|
||||
|
||||
mapper = MagicMock(iterate_properties = [
|
||||
MagicMock(key='uuid'),
|
||||
MagicMock(key='full_name'),
|
||||
MagicMock(key='first_name'),
|
||||
MagicMock(key='middle_name'),
|
||||
MagicMock(key='last_name'),
|
||||
MagicMock(key='versions'),
|
||||
])
|
||||
mapper = MagicMock(
|
||||
iterate_properties=[
|
||||
MagicMock(key="uuid"),
|
||||
MagicMock(key="full_name"),
|
||||
MagicMock(key="first_name"),
|
||||
MagicMock(key="middle_name"),
|
||||
MagicMock(key="last_name"),
|
||||
MagicMock(key="versions"),
|
||||
]
|
||||
)
|
||||
|
||||
with patch.object(mod, 'sa') as sa:
|
||||
with patch.object(mod, "sa") as sa:
|
||||
sa.inspect.return_value = mapper
|
||||
|
||||
with patch.object(self.app, 'continuum_is_enabled', return_value=True):
|
||||
with patch.object(self.app, "continuum_is_enabled", return_value=True):
|
||||
fields = mod.get_model_fields(self.config, model.Person)
|
||||
# nb. no versions field
|
||||
self.assertEqual(set(fields), set(['uuid', 'full_name', 'first_name',
|
||||
'middle_name', 'last_name']))
|
||||
self.assertEqual(
|
||||
set(fields),
|
||||
set(
|
||||
["uuid", "full_name", "first_name", "middle_name", "last_name"]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class TestGetCsrfToken(TestCase):
|
||||
|
|
@ -541,7 +570,7 @@ class TestGetCsrfToken(TestCase):
|
|||
|
||||
# nb. dummy request always returns same token, so must
|
||||
# trick it into thinking it doesn't have one yet
|
||||
with patch.object(self.request.session, 'get_csrf_token', return_value=None):
|
||||
with patch.object(self.request.session, "get_csrf_token", return_value=None):
|
||||
token = mod.get_csrf_token(self.request)
|
||||
self.assertIsNotNone(token)
|
||||
|
||||
|
|
@ -577,7 +606,7 @@ class TestMakeJsonSafe(TestCase):
|
|||
model = self.app.model
|
||||
person = model.Person(full_name="Betty Boop")
|
||||
self.assertRaises(TypeError, json.dumps, person)
|
||||
value = mod.make_json_safe(person, key='person')
|
||||
value = mod.make_json_safe(person, key="person")
|
||||
self.assertEqual(value, "Betty Boop")
|
||||
|
||||
def test_uuid(self):
|
||||
|
|
@ -586,7 +615,7 @@ class TestMakeJsonSafe(TestCase):
|
|||
self.assertEqual(value, uuid.hex)
|
||||
|
||||
def test_decimal(self):
|
||||
value = decimal.Decimal('42.42')
|
||||
value = decimal.Decimal("42.42")
|
||||
self.assertNotEqual(value, 42.42)
|
||||
result = mod.make_json_safe(value)
|
||||
self.assertEqual(result, 42.42)
|
||||
|
|
@ -596,34 +625,40 @@ class TestMakeJsonSafe(TestCase):
|
|||
person = model.Person(full_name="Betty Boop")
|
||||
|
||||
data = {
|
||||
'foo': 'bar',
|
||||
'person': person,
|
||||
"foo": "bar",
|
||||
"person": person,
|
||||
}
|
||||
|
||||
self.assertRaises(TypeError, json.dumps, data)
|
||||
value = mod.make_json_safe(data)
|
||||
self.assertEqual(value, {
|
||||
'foo': 'bar',
|
||||
'person': "Betty Boop",
|
||||
})
|
||||
self.assertEqual(
|
||||
value,
|
||||
{
|
||||
"foo": "bar",
|
||||
"person": "Betty Boop",
|
||||
},
|
||||
)
|
||||
|
||||
def test_list(self):
|
||||
model = self.app.model
|
||||
person = model.Person(full_name="Betty Boop")
|
||||
|
||||
data = [
|
||||
'foo',
|
||||
'bar',
|
||||
"foo",
|
||||
"bar",
|
||||
person,
|
||||
]
|
||||
|
||||
self.assertRaises(TypeError, json.dumps, data)
|
||||
value = mod.make_json_safe(data)
|
||||
self.assertEqual(value, [
|
||||
'foo',
|
||||
'bar',
|
||||
"Betty Boop",
|
||||
])
|
||||
self.assertEqual(
|
||||
value,
|
||||
[
|
||||
"foo",
|
||||
"bar",
|
||||
"Betty Boop",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
class TestGetAvailableThemes(TestCase):
|
||||
|
|
@ -634,63 +669,65 @@ class TestGetAvailableThemes(TestCase):
|
|||
|
||||
def test_defaults(self):
|
||||
themes = mod.get_available_themes(self.config)
|
||||
self.assertEqual(themes, ['default', 'butterfly'])
|
||||
self.assertEqual(themes, ["default", "butterfly"])
|
||||
|
||||
def test_sorting(self):
|
||||
self.config.setdefault('wuttaweb.themes.keys', 'default, foo2, foo4, foo1')
|
||||
self.config.setdefault("wuttaweb.themes.keys", "default, foo2, foo4, foo1")
|
||||
themes = mod.get_available_themes(self.config)
|
||||
self.assertEqual(themes, ['default', 'foo1', 'foo2', 'foo4'])
|
||||
self.assertEqual(themes, ["default", "foo1", "foo2", "foo4"])
|
||||
|
||||
def test_default_omitted(self):
|
||||
self.config.setdefault('wuttaweb.themes.keys', 'butterfly, foo')
|
||||
self.config.setdefault("wuttaweb.themes.keys", "butterfly, foo")
|
||||
themes = mod.get_available_themes(self.config)
|
||||
self.assertEqual(themes, ['default', 'butterfly', 'foo'])
|
||||
self.assertEqual(themes, ["default", "butterfly", "foo"])
|
||||
|
||||
def test_default_notfirst(self):
|
||||
self.config.setdefault('wuttaweb.themes.keys', 'butterfly, foo, default')
|
||||
self.config.setdefault("wuttaweb.themes.keys", "butterfly, foo, default")
|
||||
themes = mod.get_available_themes(self.config)
|
||||
self.assertEqual(themes, ['default', 'butterfly', 'foo'])
|
||||
self.assertEqual(themes, ["default", "butterfly", "foo"])
|
||||
|
||||
|
||||
class TestGetEffectiveTheme(DataTestCase):
|
||||
|
||||
def test_default(self):
|
||||
theme = mod.get_effective_theme(self.config)
|
||||
self.assertEqual(theme, 'default')
|
||||
self.assertEqual(theme, "default")
|
||||
|
||||
def test_override_config(self):
|
||||
self.app.save_setting(self.session, 'wuttaweb.theme', 'butterfly')
|
||||
self.app.save_setting(self.session, "wuttaweb.theme", "butterfly")
|
||||
self.session.commit()
|
||||
theme = mod.get_effective_theme(self.config)
|
||||
self.assertEqual(theme, 'butterfly')
|
||||
self.assertEqual(theme, "butterfly")
|
||||
|
||||
def test_override_param(self):
|
||||
theme = mod.get_effective_theme(self.config, theme='butterfly')
|
||||
self.assertEqual(theme, 'butterfly')
|
||||
theme = mod.get_effective_theme(self.config, theme="butterfly")
|
||||
self.assertEqual(theme, "butterfly")
|
||||
|
||||
def test_invalid(self):
|
||||
self.assertRaises(ValueError, mod.get_effective_theme, self.config, theme='invalid')
|
||||
self.assertRaises(
|
||||
ValueError, mod.get_effective_theme, self.config, theme="invalid"
|
||||
)
|
||||
|
||||
|
||||
class TestThemeTemplatePath(DataTestCase):
|
||||
|
||||
def test_default(self):
|
||||
path = mod.get_theme_template_path(self.config, theme='default')
|
||||
path = mod.get_theme_template_path(self.config, theme="default")
|
||||
# nb. even though the path does not exist, we still want to
|
||||
# pretend like it does, hence prev call should return this:
|
||||
expected = resource_path('wuttaweb:templates/themes/default')
|
||||
expected = resource_path("wuttaweb:templates/themes/default")
|
||||
self.assertEqual(path, expected)
|
||||
|
||||
def test_default(self):
|
||||
path = mod.get_theme_template_path(self.config, theme='butterfly')
|
||||
expected = resource_path('wuttaweb:templates/themes/butterfly')
|
||||
path = mod.get_theme_template_path(self.config, theme="butterfly")
|
||||
expected = resource_path("wuttaweb:templates/themes/butterfly")
|
||||
self.assertEqual(path, expected)
|
||||
|
||||
def test_custom(self):
|
||||
self.config.setdefault('wuttaweb.themes.keys', 'default, butterfly, poser')
|
||||
self.config.setdefault('wuttaweb.theme.poser', '/tmp/poser-theme')
|
||||
path = mod.get_theme_template_path(self.config, theme='poser')
|
||||
self.assertEqual(path, '/tmp/poser-theme')
|
||||
self.config.setdefault("wuttaweb.themes.keys", "default, butterfly, poser")
|
||||
self.config.setdefault("wuttaweb.theme.poser", "/tmp/poser-theme")
|
||||
path = mod.get_theme_template_path(self.config, theme="poser")
|
||||
self.assertEqual(path, "/tmp/poser-theme")
|
||||
|
||||
|
||||
class TestSetAppTheme(WebTestCase):
|
||||
|
|
@ -699,14 +736,14 @@ class TestSetAppTheme(WebTestCase):
|
|||
|
||||
# establish default
|
||||
settings = self.request.registry.settings
|
||||
self.assertNotIn('wuttaweb.theme', settings)
|
||||
self.assertNotIn("wuttaweb.theme", settings)
|
||||
establish_theme(settings)
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'default')
|
||||
self.assertEqual(settings["wuttaweb.theme"], "default")
|
||||
|
||||
# set to butterfly
|
||||
mod.set_app_theme(self.request, 'butterfly', session=self.session)
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'butterfly')
|
||||
mod.set_app_theme(self.request, "butterfly", session=self.session)
|
||||
self.assertEqual(settings["wuttaweb.theme"], "butterfly")
|
||||
|
||||
# set back to default
|
||||
mod.set_app_theme(self.request, 'default', session=self.session)
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'default')
|
||||
mod.set_app_theme(self.request, "default", session=self.session)
|
||||
self.assertEqual(settings["wuttaweb.theme"], "default")
|
||||
|
|
|
|||
|
|
@ -15,9 +15,11 @@ class DataTestCase(FileConfigTestCase):
|
|||
|
||||
def setup_db(self):
|
||||
self.setup_files()
|
||||
self.config = WuttaConfig(defaults={
|
||||
'wutta.db.default.url': 'sqlite://',
|
||||
})
|
||||
self.config = WuttaConfig(
|
||||
defaults={
|
||||
"wutta.db.default.url": "sqlite://",
|
||||
}
|
||||
)
|
||||
self.app = self.config.get_app()
|
||||
|
||||
# init db
|
||||
|
|
@ -36,5 +38,6 @@ class NullMenuHandler(MenuHandler):
|
|||
"""
|
||||
Dummy menu handler for testing.
|
||||
"""
|
||||
|
||||
def make_menus(self, request, **kwargs):
|
||||
return []
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@ class TestIncludeMe(WebTestCase):
|
|||
|
||||
def test_basic(self):
|
||||
# just ensure no error happens when included..
|
||||
self.pyramid_config.include('wuttaweb.views')
|
||||
self.pyramid_config.include("wuttaweb.views")
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@ class TestAuthView(WebTestCase):
|
|||
|
||||
def setUp(self):
|
||||
self.setup_web()
|
||||
self.pyramid_config.include('wuttaweb.views.common')
|
||||
self.pyramid_config.include("wuttaweb.views.common")
|
||||
|
||||
def make_view(self):
|
||||
return mod.AuthView(self.request)
|
||||
|
||||
def test_includeme(self):
|
||||
self.pyramid_config.include('wuttaweb.views.auth')
|
||||
self.pyramid_config.include("wuttaweb.views.auth")
|
||||
|
||||
def test_login(self):
|
||||
model = self.app.model
|
||||
|
|
@ -31,37 +31,37 @@ class TestAuthView(WebTestCase):
|
|||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
# make a user
|
||||
barney = model.User(username='barney')
|
||||
auth.set_user_password(barney, 'testpass')
|
||||
barney = model.User(username="barney")
|
||||
auth.set_user_password(barney, "testpass")
|
||||
self.session.add(barney)
|
||||
self.session.commit()
|
||||
|
||||
# now since user exists, form will display
|
||||
context = view.login(session=self.session)
|
||||
self.assertIn('form', context)
|
||||
self.assertIn("form", context)
|
||||
|
||||
# redirect if user already logged in
|
||||
with patch.object(self.request, 'user', new=barney):
|
||||
with patch.object(self.request, "user", new=barney):
|
||||
view = self.make_view()
|
||||
response = view.login(session=self.session)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
# login fails w/ wrong password
|
||||
self.request.method = 'POST'
|
||||
self.request.POST = {'username': 'barney', 'password': 'WRONG'}
|
||||
self.request.method = "POST"
|
||||
self.request.POST = {"username": "barney", "password": "WRONG"}
|
||||
view = self.make_view()
|
||||
context = view.login(session=self.session)
|
||||
self.assertIn('form', context)
|
||||
self.assertIn("form", context)
|
||||
|
||||
# redirect if login succeeds
|
||||
self.request.method = 'POST'
|
||||
self.request.POST = {'username': 'barney', 'password': 'testpass'}
|
||||
self.request.method = "POST"
|
||||
self.request.POST = {"username": "barney", "password": "testpass"}
|
||||
view = self.make_view()
|
||||
response = view.login(session=self.session)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_logout(self):
|
||||
self.pyramid_config.add_route('login', '/login')
|
||||
self.pyramid_config.add_route("login", "/login")
|
||||
view = self.make_view()
|
||||
self.request.session.delete = MagicMock()
|
||||
response = view.logout()
|
||||
|
|
@ -71,7 +71,7 @@ class TestAuthView(WebTestCase):
|
|||
def test_change_password(self):
|
||||
model = self.app.model
|
||||
auth = self.app.get_auth_handler()
|
||||
barney = model.User(username='barney')
|
||||
barney = model.User(username="barney")
|
||||
self.session.add(barney)
|
||||
self.session.commit()
|
||||
view = self.make_view()
|
||||
|
|
@ -81,7 +81,7 @@ class TestAuthView(WebTestCase):
|
|||
self.assertIsInstance(redirect, HTTPFound)
|
||||
|
||||
# set initial password
|
||||
auth.set_user_password(barney, 'foo')
|
||||
auth.set_user_password(barney, "foo")
|
||||
self.session.commit()
|
||||
|
||||
# forbidden if prevent_edit is set for user
|
||||
|
|
@ -94,24 +94,24 @@ class TestAuthView(WebTestCase):
|
|||
|
||||
# view should now return context w/ form
|
||||
context = view.change_password()
|
||||
self.assertIn('form', context)
|
||||
self.assertIn("form", context)
|
||||
|
||||
# submit valid form, ensure password is changed
|
||||
# (nb. this also would redirect user to home page)
|
||||
self.request.method = 'POST'
|
||||
self.request.method = "POST"
|
||||
self.request.POST = {
|
||||
'current_password': 'foo',
|
||||
"current_password": "foo",
|
||||
# nb. new_password requires colander mapping structure
|
||||
'__start__': 'new_password:mapping',
|
||||
'new_password': 'bar',
|
||||
'new_password-confirm': 'bar',
|
||||
'__end__': 'new_password:mapping',
|
||||
"__start__": "new_password:mapping",
|
||||
"new_password": "bar",
|
||||
"new_password-confirm": "bar",
|
||||
"__end__": "new_password:mapping",
|
||||
}
|
||||
redirect = view.change_password()
|
||||
self.assertIsInstance(redirect, HTTPFound)
|
||||
self.session.commit()
|
||||
self.assertFalse(auth.check_user_password(barney, 'foo'))
|
||||
self.assertTrue(auth.check_user_password(barney, 'bar'))
|
||||
self.assertFalse(auth.check_user_password(barney, "foo"))
|
||||
self.assertTrue(auth.check_user_password(barney, "bar"))
|
||||
|
||||
# at this point 'foo' is the password, now let's submit some
|
||||
# invalid forms and make sure we get back a context w/ form
|
||||
|
|
@ -119,72 +119,77 @@ class TestAuthView(WebTestCase):
|
|||
# first try empty data
|
||||
self.request.POST = {}
|
||||
context = view.change_password()
|
||||
self.assertIn('form', context)
|
||||
dform = context['form'].get_deform()
|
||||
self.assertEqual(dform['current_password'].errormsg, "Required")
|
||||
self.assertEqual(dform['new_password'].errormsg, "Required")
|
||||
self.assertIn("form", context)
|
||||
dform = context["form"].get_deform()
|
||||
self.assertEqual(dform["current_password"].errormsg, "Required")
|
||||
self.assertEqual(dform["new_password"].errormsg, "Required")
|
||||
|
||||
# now try bad current password
|
||||
self.request.POST = {
|
||||
'current_password': 'blahblah',
|
||||
'__start__': 'new_password:mapping',
|
||||
'new_password': 'baz',
|
||||
'new_password-confirm': 'baz',
|
||||
'__end__': 'new_password:mapping',
|
||||
"current_password": "blahblah",
|
||||
"__start__": "new_password:mapping",
|
||||
"new_password": "baz",
|
||||
"new_password-confirm": "baz",
|
||||
"__end__": "new_password:mapping",
|
||||
}
|
||||
context = view.change_password()
|
||||
self.assertIn('form', context)
|
||||
dform = context['form'].get_deform()
|
||||
self.assertEqual(dform['current_password'].errormsg, "Current password is incorrect.")
|
||||
self.assertIn("form", context)
|
||||
dform = context["form"].get_deform()
|
||||
self.assertEqual(
|
||||
dform["current_password"].errormsg, "Current password is incorrect."
|
||||
)
|
||||
|
||||
# now try bad new password
|
||||
self.request.POST = {
|
||||
'current_password': 'bar',
|
||||
'__start__': 'new_password:mapping',
|
||||
'new_password': 'bar',
|
||||
'new_password-confirm': 'bar',
|
||||
'__end__': 'new_password:mapping',
|
||||
"current_password": "bar",
|
||||
"__start__": "new_password:mapping",
|
||||
"new_password": "bar",
|
||||
"new_password-confirm": "bar",
|
||||
"__end__": "new_password:mapping",
|
||||
}
|
||||
context = view.change_password()
|
||||
self.assertIn('form', context)
|
||||
dform = context['form'].get_deform()
|
||||
self.assertEqual(dform['new_password'].errormsg, "New password must be different from old password.")
|
||||
self.assertIn("form", context)
|
||||
dform = context["form"].get_deform()
|
||||
self.assertEqual(
|
||||
dform["new_password"].errormsg,
|
||||
"New password must be different from old password.",
|
||||
)
|
||||
|
||||
def test_become_root(self):
|
||||
view = mod.AuthView(self.request)
|
||||
|
||||
# GET not allowed
|
||||
self.request.method = 'GET'
|
||||
self.request.method = "GET"
|
||||
self.assertRaises(HTTPForbidden, view.become_root)
|
||||
|
||||
# non-admin users also not allowed
|
||||
self.request.method = 'POST'
|
||||
self.request.method = "POST"
|
||||
self.request.is_admin = False
|
||||
self.assertRaises(HTTPForbidden, view.become_root)
|
||||
|
||||
# but admin users can become root
|
||||
self.request.is_admin = True
|
||||
self.assertNotIn('is_root', self.request.session)
|
||||
self.assertNotIn("is_root", self.request.session)
|
||||
redirect = view.become_root()
|
||||
self.assertIsInstance(redirect, HTTPFound)
|
||||
self.assertTrue(self.request.session['is_root'])
|
||||
self.assertTrue(self.request.session["is_root"])
|
||||
|
||||
def test_stop_root(self):
|
||||
view = mod.AuthView(self.request)
|
||||
|
||||
# GET not allowed
|
||||
self.request.method = 'GET'
|
||||
self.request.method = "GET"
|
||||
self.assertRaises(HTTPForbidden, view.stop_root)
|
||||
|
||||
# non-admin users also not allowed
|
||||
self.request.method = 'POST'
|
||||
self.request.method = "POST"
|
||||
self.request.is_admin = False
|
||||
self.assertRaises(HTTPForbidden, view.stop_root)
|
||||
|
||||
# but admin users can stop being root
|
||||
# (nb. there is no check whether user is currently root)
|
||||
self.request.is_admin = True
|
||||
self.assertNotIn('is_root', self.request.session)
|
||||
self.assertNotIn("is_root", self.request.session)
|
||||
redirect = view.stop_root()
|
||||
self.assertIsInstance(redirect, HTTPFound)
|
||||
self.assertFalse(self.request.session['is_root'])
|
||||
self.assertFalse(self.request.session["is_root"])
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class TestView(WebTestCase):
|
|||
|
||||
def test_make_grid_action(self):
|
||||
view = self.make_view()
|
||||
action = view.make_grid_action('view')
|
||||
action = view.make_grid_action("view")
|
||||
self.assertIsInstance(action, GridAction)
|
||||
|
||||
def test_notfound(self):
|
||||
|
|
@ -46,31 +46,31 @@ class TestView(WebTestCase):
|
|||
|
||||
def test_redirect(self):
|
||||
view = self.make_view()
|
||||
error = view.redirect('/')
|
||||
error = view.redirect("/")
|
||||
self.assertIsInstance(error, HTTPFound)
|
||||
self.assertEqual(error.location, '/')
|
||||
self.assertEqual(error.location, "/")
|
||||
|
||||
def test_file_response(self):
|
||||
view = self.make_view()
|
||||
|
||||
# default uses attachment behavior
|
||||
datfile = self.write_file('dat.txt', 'hello')
|
||||
datfile = self.write_file("dat.txt", "hello")
|
||||
response = view.file_response(datfile)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.content_disposition, 'attachment; filename="dat.txt"')
|
||||
|
||||
# but can disable attachment behavior
|
||||
datfile = self.write_file('dat.txt', 'hello')
|
||||
datfile = self.write_file("dat.txt", "hello")
|
||||
response = view.file_response(datfile, attachment=False)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIsNone(response.content_disposition)
|
||||
|
||||
# path not found
|
||||
crapfile = '/does/not/exist'
|
||||
crapfile = "/does/not/exist"
|
||||
response = view.file_response(crapfile)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_json_response(self):
|
||||
view = self.make_view()
|
||||
response = view.json_response({'foo': 'bar'})
|
||||
response = view.json_response({"foo": "bar"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
|
|
|||
|
|
@ -14,14 +14,17 @@ from wuttaweb.testing import WebTestCase
|
|||
|
||||
|
||||
class MockBatch(model.BatchMixin, model.Base):
|
||||
__tablename__ = 'testing_batch_mock'
|
||||
__tablename__ = "testing_batch_mock"
|
||||
|
||||
|
||||
class MockBatchRow(model.BatchRowMixin, model.Base):
|
||||
__tablename__ = 'testing_batch_mock_row'
|
||||
__tablename__ = "testing_batch_mock_row"
|
||||
__batch_class__ = MockBatch
|
||||
|
||||
|
||||
MockBatch.__row_class__ = MockBatchRow
|
||||
|
||||
|
||||
class MockBatchHandler(BatchHandler):
|
||||
model_class = MockBatch
|
||||
|
||||
|
|
@ -43,43 +46,52 @@ class TestBatchMasterView(WebTestCase):
|
|||
def test_get_batch_handler(self):
|
||||
self.assertRaises(NotImplementedError, mod.BatchMasterView, self.request)
|
||||
|
||||
with patch.object(mod.BatchMasterView, 'get_batch_handler', return_value=42):
|
||||
with patch.object(mod.BatchMasterView, "get_batch_handler", return_value=42):
|
||||
view = mod.BatchMasterView(self.request)
|
||||
self.assertEqual(view.batch_handler, 42)
|
||||
|
||||
def test_get_fallback_templates(self):
|
||||
handler = MockBatchHandler(self.config)
|
||||
with patch.object(mod.BatchMasterView, 'get_batch_handler', return_value=handler):
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
):
|
||||
view = self.make_view()
|
||||
templates = view.get_fallback_templates('view')
|
||||
self.assertEqual(templates, [
|
||||
'/batch/view.mako',
|
||||
'/master/view.mako',
|
||||
])
|
||||
templates = view.get_fallback_templates("view")
|
||||
self.assertEqual(
|
||||
templates,
|
||||
[
|
||||
"/batch/view.mako",
|
||||
"/master/view.mako",
|
||||
],
|
||||
)
|
||||
|
||||
def test_render_to_response(self):
|
||||
model = self.app.model
|
||||
handler = MockBatchHandler(self.config)
|
||||
|
||||
user = model.User(username='barney')
|
||||
user = model.User(username="barney")
|
||||
self.session.add(user)
|
||||
batch = handler.make_batch(self.session, created_by=user)
|
||||
self.session.add(batch)
|
||||
self.session.flush()
|
||||
|
||||
with patch.object(mod.BatchMasterView, 'get_batch_handler', return_value=handler):
|
||||
with patch.object(MasterView, 'render_to_response') as render_to_response:
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
):
|
||||
with patch.object(MasterView, "render_to_response") as render_to_response:
|
||||
view = self.make_view()
|
||||
response = view.render_to_response('view', {'instance': batch})
|
||||
response = view.render_to_response("view", {"instance": batch})
|
||||
self.assertTrue(render_to_response.called)
|
||||
context = render_to_response.call_args[0][1]
|
||||
self.assertIs(context['batch'], batch)
|
||||
self.assertIs(context['batch_handler'], handler)
|
||||
self.assertIs(context["batch"], batch)
|
||||
self.assertIs(context["batch_handler"], handler)
|
||||
|
||||
def test_configure_grid(self):
|
||||
handler = MockBatchHandler(self.config)
|
||||
with patch.multiple(mod.BatchMasterView, create=True, model_class=MockBatch):
|
||||
with patch.object(mod.BatchMasterView, 'get_batch_handler', return_value=handler):
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
):
|
||||
view = mod.BatchMasterView(self.request)
|
||||
grid = view.make_model_grid()
|
||||
# nb. coverage only; tests nothing
|
||||
|
|
@ -87,19 +99,23 @@ class TestBatchMasterView(WebTestCase):
|
|||
|
||||
def test_render_batch_id(self):
|
||||
handler = MockBatchHandler(self.config)
|
||||
with patch.object(mod.BatchMasterView, 'get_batch_handler', return_value=handler):
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
):
|
||||
view = mod.BatchMasterView(self.request)
|
||||
batch = MockBatch(id=42)
|
||||
|
||||
result = view.render_batch_id(batch, 'id', 42)
|
||||
self.assertEqual(result, '00000042')
|
||||
result = view.render_batch_id(batch, "id", 42)
|
||||
self.assertEqual(result, "00000042")
|
||||
|
||||
result = view.render_batch_id(batch, 'id', None)
|
||||
result = view.render_batch_id(batch, "id", None)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_get_instance_title(self):
|
||||
handler = MockBatchHandler(self.config)
|
||||
with patch.object(mod.BatchMasterView, 'get_batch_handler', return_value=handler):
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
):
|
||||
view = mod.BatchMasterView(self.request)
|
||||
|
||||
batch = MockBatch(id=42)
|
||||
|
|
@ -113,46 +129,52 @@ class TestBatchMasterView(WebTestCase):
|
|||
def test_configure_form(self):
|
||||
handler = MockBatchHandler(self.config)
|
||||
with patch.multiple(mod.BatchMasterView, create=True, model_class=MockBatch):
|
||||
with patch.object(mod.BatchMasterView, 'get_batch_handler', return_value=handler):
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
):
|
||||
view = mod.BatchMasterView(self.request)
|
||||
|
||||
# creating
|
||||
with patch.object(view, 'creating', new=True):
|
||||
with patch.object(view, "creating", new=True):
|
||||
form = view.make_model_form(model_instance=None)
|
||||
view.configure_form(form)
|
||||
|
||||
batch = MockBatch(id=42)
|
||||
|
||||
# viewing
|
||||
with patch.object(view, 'viewing', new=True):
|
||||
with patch.object(view, "viewing", new=True):
|
||||
form = view.make_model_form(model_instance=batch)
|
||||
view.configure_form(form)
|
||||
|
||||
# editing
|
||||
with patch.object(view, 'editing', new=True):
|
||||
with patch.object(view, "editing", new=True):
|
||||
form = view.make_model_form(model_instance=batch)
|
||||
view.configure_form(form)
|
||||
|
||||
# deleting
|
||||
with patch.object(view, 'deleting', new=True):
|
||||
with patch.object(view, "deleting", new=True):
|
||||
form = view.make_model_form(model_instance=batch)
|
||||
view.configure_form(form)
|
||||
|
||||
# viewing (executed)
|
||||
batch.executed = datetime.datetime.now()
|
||||
with patch.object(view, 'viewing', new=True):
|
||||
with patch.object(view, "viewing", new=True):
|
||||
form = view.make_model_form(model_instance=batch)
|
||||
view.configure_form(form)
|
||||
|
||||
def test_objectify(self):
|
||||
handler = MockBatchHandler(self.config)
|
||||
with patch.multiple(mod.BatchMasterView, create=True, model_class=MockBatch):
|
||||
with patch.object(mod.BatchMasterView, 'get_batch_handler', return_value=handler):
|
||||
with patch.object(mod.BatchMasterView, 'Session', return_value=self.session):
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
):
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "Session", return_value=self.session
|
||||
):
|
||||
view = mod.BatchMasterView(self.request)
|
||||
|
||||
# create batch
|
||||
with patch.object(view, 'creating', new=True):
|
||||
with patch.object(view, "creating", new=True):
|
||||
form = view.make_model_form(model_instance=None)
|
||||
form.validated = {}
|
||||
batch = view.objectify(form)
|
||||
|
|
@ -160,21 +182,28 @@ class TestBatchMasterView(WebTestCase):
|
|||
self.assertTrue(batch.id > 0)
|
||||
|
||||
# edit batch
|
||||
with patch.object(view, 'editing', new=True):
|
||||
with patch.object(view.batch_handler, 'make_batch') as make_batch:
|
||||
with patch.object(view, "editing", new=True):
|
||||
with patch.object(
|
||||
view.batch_handler, "make_batch"
|
||||
) as make_batch:
|
||||
form = view.make_model_form(model_instance=batch)
|
||||
form.validated = {'description': 'foo'}
|
||||
form.validated = {"description": "foo"}
|
||||
self.assertIsNone(batch.description)
|
||||
batch = view.objectify(form)
|
||||
self.assertEqual(batch.description, 'foo')
|
||||
self.assertEqual(batch.description, "foo")
|
||||
|
||||
def test_redirect_after_create(self):
|
||||
self.pyramid_config.add_route('mock_batches.view', '/batch/mock/{uuid}')
|
||||
self.pyramid_config.add_route("mock_batches.view", "/batch/mock/{uuid}")
|
||||
handler = MockBatchHandler(self.config)
|
||||
with patch.object(mod.BatchMasterView, 'get_batch_handler', return_value=handler):
|
||||
with patch.multiple(mod.BatchMasterView, create=True,
|
||||
model_class=MockBatch,
|
||||
route_prefix='mock_batches'):
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
):
|
||||
with patch.multiple(
|
||||
mod.BatchMasterView,
|
||||
create=True,
|
||||
model_class=MockBatch,
|
||||
route_prefix="mock_batches",
|
||||
):
|
||||
view = mod.BatchMasterView(self.request)
|
||||
batch = MockBatch(id=42)
|
||||
|
||||
|
|
@ -183,12 +212,14 @@ class TestBatchMasterView(WebTestCase):
|
|||
self.assertIsInstance(result, HTTPFound)
|
||||
|
||||
# unless populating in which case thread is launched
|
||||
self.request.session.id = 'abcdefghijk'
|
||||
with patch.object(mod, 'threading') as threading:
|
||||
self.request.session.id = "abcdefghijk"
|
||||
with patch.object(mod, "threading") as threading:
|
||||
thread = MagicMock()
|
||||
threading.Thread.return_value = thread
|
||||
with patch.object(view.batch_handler, 'should_populate', return_value=True):
|
||||
with patch.object(view, 'render_progress') as render_progress:
|
||||
with patch.object(
|
||||
view.batch_handler, "should_populate", return_value=True
|
||||
):
|
||||
with patch.object(view, "render_progress") as render_progress:
|
||||
view.redirect_after_create(batch)
|
||||
self.assertTrue(threading.Thread.called)
|
||||
thread.start.assert_called_once_with()
|
||||
|
|
@ -198,14 +229,16 @@ class TestBatchMasterView(WebTestCase):
|
|||
model = self.app.model
|
||||
handler = self.make_handler()
|
||||
|
||||
user = model.User(username='barney')
|
||||
user = model.User(username="barney")
|
||||
self.session.add(user)
|
||||
|
||||
batch = handler.make_batch(self.session, created_by=user)
|
||||
self.session.add(batch)
|
||||
self.session.flush()
|
||||
|
||||
with patch.object(mod.BatchMasterView, 'get_batch_handler', return_value=handler):
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
):
|
||||
view = self.make_view()
|
||||
|
||||
self.assertEqual(self.session.query(MockBatch).count(), 1)
|
||||
|
|
@ -215,20 +248,24 @@ class TestBatchMasterView(WebTestCase):
|
|||
def test_populate_thread(self):
|
||||
model = self.app.model
|
||||
handler = MockBatchHandler(self.config)
|
||||
with patch.object(mod.BatchMasterView, 'get_batch_handler', return_value=handler):
|
||||
with patch.multiple(mod.BatchMasterView, create=True, model_class=MockBatch):
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
):
|
||||
with patch.multiple(
|
||||
mod.BatchMasterView, create=True, model_class=MockBatch
|
||||
):
|
||||
view = mod.BatchMasterView(self.request)
|
||||
user = model.User(username='barney')
|
||||
user = model.User(username="barney")
|
||||
self.session.add(user)
|
||||
batch = MockBatch(id=42, created_by=user)
|
||||
self.session.add(batch)
|
||||
self.session.commit()
|
||||
|
||||
# nb. use our session within thread method
|
||||
with patch.object(self.app, 'make_session', return_value=self.session):
|
||||
with patch.object(self.app, "make_session", return_value=self.session):
|
||||
|
||||
# nb. prevent closing our session
|
||||
with patch.object(self.session, 'close') as close:
|
||||
with patch.object(self.session, "close") as close:
|
||||
|
||||
# without progress
|
||||
view.populate_thread(batch.uuid)
|
||||
|
|
@ -236,24 +273,34 @@ class TestBatchMasterView(WebTestCase):
|
|||
close.reset_mock()
|
||||
|
||||
# with progress
|
||||
self.request.session.id = 'abcdefghijk'
|
||||
view.populate_thread(batch.uuid,
|
||||
progress=SessionProgress(self.request,
|
||||
'populate_mock_batch'))
|
||||
self.request.session.id = "abcdefghijk"
|
||||
view.populate_thread(
|
||||
batch.uuid,
|
||||
progress=SessionProgress(
|
||||
self.request, "populate_mock_batch"
|
||||
),
|
||||
)
|
||||
close.assert_called_once_with()
|
||||
close.reset_mock()
|
||||
|
||||
# failure to populate, without progress
|
||||
with patch.object(view.batch_handler, 'do_populate', side_effect=RuntimeError):
|
||||
with patch.object(
|
||||
view.batch_handler, "do_populate", side_effect=RuntimeError
|
||||
):
|
||||
view.populate_thread(batch.uuid)
|
||||
close.assert_called_once_with()
|
||||
close.reset_mock()
|
||||
|
||||
# failure to populate, with progress
|
||||
with patch.object(view.batch_handler, 'do_populate', side_effect=RuntimeError):
|
||||
view.populate_thread(batch.uuid,
|
||||
progress=SessionProgress(self.request,
|
||||
'populate_mock_batch'))
|
||||
with patch.object(
|
||||
view.batch_handler, "do_populate", side_effect=RuntimeError
|
||||
):
|
||||
view.populate_thread(
|
||||
batch.uuid,
|
||||
progress=SessionProgress(
|
||||
self.request, "populate_mock_batch"
|
||||
),
|
||||
)
|
||||
close.assert_called_once_with()
|
||||
close.reset_mock()
|
||||
|
||||
|
|
@ -261,53 +308,64 @@ class TestBatchMasterView(WebTestCase):
|
|||
self.session.delete(batch)
|
||||
self.session.commit()
|
||||
# nb. should give up waiting after 1 second
|
||||
self.assertRaises(RuntimeError, view.populate_thread, batch.uuid)
|
||||
self.assertRaises(
|
||||
RuntimeError, view.populate_thread, batch.uuid
|
||||
)
|
||||
|
||||
def test_execute(self):
|
||||
self.pyramid_config.add_route('mock_batches.view', '/batch/mock/{uuid}')
|
||||
self.pyramid_config.add_route("mock_batches.view", "/batch/mock/{uuid}")
|
||||
model = self.app.model
|
||||
handler = MockBatchHandler(self.config)
|
||||
|
||||
user = model.User(username='barney')
|
||||
user = model.User(username="barney")
|
||||
self.session.add(user)
|
||||
batch = handler.make_batch(self.session, created_by=user)
|
||||
self.session.add(batch)
|
||||
self.session.commit()
|
||||
|
||||
with patch.multiple(mod.BatchMasterView, create=True,
|
||||
model_class=MockBatch,
|
||||
route_prefix='mock_batches',
|
||||
get_batch_handler=MagicMock(return_value=handler),
|
||||
get_instance=MagicMock(return_value=batch)):
|
||||
with patch.multiple(
|
||||
mod.BatchMasterView,
|
||||
create=True,
|
||||
model_class=MockBatch,
|
||||
route_prefix="mock_batches",
|
||||
get_batch_handler=MagicMock(return_value=handler),
|
||||
get_instance=MagicMock(return_value=batch),
|
||||
):
|
||||
view = self.make_view()
|
||||
|
||||
# batch executes okay
|
||||
response = view.execute()
|
||||
self.assertEqual(response.status_code, 302) # redirect to "view batch"
|
||||
self.assertFalse(self.request.session.peek_flash('error'))
|
||||
self.assertEqual(response.status_code, 302) # redirect to "view batch"
|
||||
self.assertFalse(self.request.session.peek_flash("error"))
|
||||
|
||||
# but cannot be executed again
|
||||
response = view.execute()
|
||||
self.assertEqual(response.status_code, 302) # redirect to "view batch"
|
||||
self.assertEqual(response.status_code, 302) # redirect to "view batch"
|
||||
# nb. flash has error this time
|
||||
self.assertTrue(self.request.session.peek_flash('error'))
|
||||
self.assertTrue(self.request.session.peek_flash("error"))
|
||||
|
||||
def test_get_row_model_class(self):
|
||||
handler = MockBatchHandler(self.config)
|
||||
with patch.object(mod.BatchMasterView, 'get_batch_handler', return_value=handler):
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
):
|
||||
view = self.make_view()
|
||||
|
||||
self.assertRaises(AttributeError, view.get_row_model_class)
|
||||
|
||||
# row class determined from batch class
|
||||
with patch.object(mod.BatchMasterView, 'model_class', new=MockBatch, create=True):
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "model_class", new=MockBatch, create=True
|
||||
):
|
||||
cls = view.get_row_model_class()
|
||||
self.assertIs(cls, MockBatchRow)
|
||||
|
||||
self.assertRaises(AttributeError, view.get_row_model_class)
|
||||
|
||||
# view may specify row class
|
||||
with patch.object(mod.BatchMasterView, 'row_model_class', new=MockBatchRow, create=True):
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "row_model_class", new=MockBatchRow, create=True
|
||||
):
|
||||
cls = view.get_row_model_class()
|
||||
self.assertIs(cls, MockBatchRow)
|
||||
|
||||
|
|
@ -315,7 +373,7 @@ class TestBatchMasterView(WebTestCase):
|
|||
handler = MockBatchHandler(self.config)
|
||||
model = self.app.model
|
||||
|
||||
user = model.User(username='barney')
|
||||
user = model.User(username="barney")
|
||||
self.session.add(user)
|
||||
|
||||
batch = handler.make_batch(self.session, created_by=user)
|
||||
|
|
@ -324,16 +382,18 @@ class TestBatchMasterView(WebTestCase):
|
|||
handler.add_row(batch, row)
|
||||
self.session.flush()
|
||||
|
||||
with patch.object(mod.BatchMasterView, 'get_batch_handler', return_value=handler):
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
):
|
||||
|
||||
view = self.make_view()
|
||||
self.assertRaises(AttributeError, view.get_row_grid_data, batch)
|
||||
|
||||
Session = MagicMock(return_value=self.session)
|
||||
Session.query.side_effect = lambda m: self.session.query(m)
|
||||
with patch.multiple(mod.BatchMasterView, create=True,
|
||||
Session=Session,
|
||||
model_class=MockBatch):
|
||||
with patch.multiple(
|
||||
mod.BatchMasterView, create=True, Session=Session, model_class=MockBatch
|
||||
):
|
||||
|
||||
view = self.make_view()
|
||||
data = view.get_row_grid_data(batch)
|
||||
|
|
@ -344,7 +404,7 @@ class TestBatchMasterView(WebTestCase):
|
|||
handler = MockBatchHandler(self.config)
|
||||
model = self.app.model
|
||||
|
||||
user = model.User(username='barney')
|
||||
user = model.User(username="barney")
|
||||
self.session.add(user)
|
||||
|
||||
batch = handler.make_batch(self.session, created_by=user)
|
||||
|
|
@ -353,27 +413,31 @@ class TestBatchMasterView(WebTestCase):
|
|||
handler.add_row(batch, row)
|
||||
self.session.flush()
|
||||
|
||||
with patch.object(mod.BatchMasterView, 'get_batch_handler', return_value=handler):
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "get_batch_handler", return_value=handler
|
||||
):
|
||||
|
||||
Session = MagicMock(return_value=self.session)
|
||||
Session.query.side_effect = lambda m: self.session.query(m)
|
||||
with patch.multiple(mod.BatchMasterView, create=True,
|
||||
Session=Session,
|
||||
model_class=MockBatch):
|
||||
with patch.multiple(
|
||||
mod.BatchMasterView, create=True, Session=Session, model_class=MockBatch
|
||||
):
|
||||
|
||||
with patch.object(self.request, 'matchdict', new={'uuid': batch.uuid}):
|
||||
with patch.object(self.request, "matchdict", new={"uuid": batch.uuid}):
|
||||
view = self.make_view()
|
||||
grid = view.make_row_model_grid(batch)
|
||||
self.assertIn('sequence', grid.labels)
|
||||
self.assertEqual(grid.labels['sequence'], "Seq.")
|
||||
self.assertIn("sequence", grid.labels)
|
||||
self.assertEqual(grid.labels["sequence"], "Seq.")
|
||||
|
||||
def test_render_row_status(self):
|
||||
with patch.object(mod.BatchMasterView, 'get_batch_handler', return_value=None):
|
||||
with patch.object(mod.BatchMasterView, "get_batch_handler", return_value=None):
|
||||
view = self.make_view()
|
||||
row = MagicMock(foo=1, STATUS={1: 'bar'})
|
||||
self.assertEqual(view.render_row_status(row, 'foo', 1), 'bar')
|
||||
row = MagicMock(foo=1, STATUS={1: "bar"})
|
||||
self.assertEqual(view.render_row_status(row, "foo", 1), "bar")
|
||||
|
||||
def test_defaults(self):
|
||||
# nb. coverage only
|
||||
with patch.object(mod.BatchMasterView, 'model_class', new=MockBatch, create=True):
|
||||
with patch.object(
|
||||
mod.BatchMasterView, "model_class", new=MockBatch, create=True
|
||||
):
|
||||
mod.BatchMasterView.defaults(self.pyramid_config)
|
||||
|
|
|
|||
|
|
@ -15,21 +15,21 @@ class TestCommonView(WebTestCase):
|
|||
return mod.CommonView(self.request)
|
||||
|
||||
def test_includeme(self):
|
||||
self.pyramid_config.include('wuttaweb.views.common')
|
||||
self.pyramid_config.include("wuttaweb.views.common")
|
||||
|
||||
def test_forbidden_view(self):
|
||||
view = self.make_view()
|
||||
context = view.forbidden_view()
|
||||
self.assertEqual(context['index_title'], self.app.get_title())
|
||||
self.assertEqual(context["index_title"], self.app.get_title())
|
||||
|
||||
def test_notfound_view(self):
|
||||
view = self.make_view()
|
||||
context = view.notfound_view()
|
||||
self.assertEqual(context['index_title'], self.app.get_title())
|
||||
self.assertEqual(context["index_title"], self.app.get_title())
|
||||
|
||||
def test_home(self):
|
||||
self.pyramid_config.add_route('setup', '/setup')
|
||||
self.pyramid_config.add_route('login', '/login')
|
||||
self.pyramid_config.add_route("setup", "/setup")
|
||||
self.pyramid_config.add_route("login", "/login")
|
||||
model = self.app.model
|
||||
view = self.make_view()
|
||||
|
||||
|
|
@ -38,50 +38,50 @@ class TestCommonView(WebTestCase):
|
|||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
# so add a user
|
||||
user = model.User(username='foo')
|
||||
user = model.User(username="foo")
|
||||
self.session.add(user)
|
||||
self.session.commit()
|
||||
|
||||
# now we see the home page
|
||||
context = view.home(session=self.session)
|
||||
self.assertEqual(context['index_title'], self.app.get_title())
|
||||
self.assertEqual(context["index_title"], self.app.get_title())
|
||||
|
||||
# but if configured, anons will be redirected to login
|
||||
self.config.setdefault('wuttaweb.home_redirect_to_login', 'true')
|
||||
self.config.setdefault("wuttaweb.home_redirect_to_login", "true")
|
||||
response = view.home(session=self.session)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
# now only an auth'ed user can see home page
|
||||
self.request.user = user
|
||||
context = view.home(session=self.session)
|
||||
self.assertEqual(context['index_title'], self.app.get_title())
|
||||
self.assertEqual(context["index_title"], self.app.get_title())
|
||||
|
||||
def test_feedback_make_schema(self):
|
||||
view = self.make_view()
|
||||
schema = view.feedback_make_schema()
|
||||
self.assertIsInstance(schema, colander.Schema)
|
||||
self.assertIn('message', schema)
|
||||
self.assertIn("message", schema)
|
||||
|
||||
def test_feedback(self):
|
||||
self.pyramid_config.add_route('users.view', '/users/{uuid}')
|
||||
self.pyramid_config.add_route("users.view", "/users/{uuid}")
|
||||
model = self.app.model
|
||||
user = model.User(username='barney')
|
||||
user = model.User(username="barney")
|
||||
self.session.add(user)
|
||||
self.session.commit()
|
||||
|
||||
view = self.make_view()
|
||||
with patch.object(view, 'feedback_send') as feedback_send:
|
||||
with patch.object(view, "feedback_send") as feedback_send:
|
||||
|
||||
# basic send, no user
|
||||
self.request.client_addr = '127.0.0.1'
|
||||
self.request.method = 'POST'
|
||||
self.request.client_addr = "127.0.0.1"
|
||||
self.request.method = "POST"
|
||||
self.request.POST = {
|
||||
'referrer': '/foo',
|
||||
'user_name': "Barney Rubble",
|
||||
'message': "hello world",
|
||||
"referrer": "/foo",
|
||||
"user_name": "Barney Rubble",
|
||||
"message": "hello world",
|
||||
}
|
||||
context = view.feedback()
|
||||
self.assertEqual(context, {'ok': True})
|
||||
self.assertEqual(context, {"ok": True})
|
||||
feedback_send.assert_called_once()
|
||||
|
||||
# reset
|
||||
|
|
@ -89,10 +89,10 @@ class TestCommonView(WebTestCase):
|
|||
|
||||
# basic send, with user
|
||||
self.request.user = user
|
||||
self.request.POST['user_uuid'] = str(user.uuid)
|
||||
with patch.object(mod, 'Session', return_value=self.session):
|
||||
self.request.POST["user_uuid"] = str(user.uuid)
|
||||
with patch.object(mod, "Session", return_value=self.session):
|
||||
context = view.feedback()
|
||||
self.assertEqual(context, {'ok': True})
|
||||
self.assertEqual(context, {"ok": True})
|
||||
feedback_send.assert_called_once()
|
||||
|
||||
# reset
|
||||
|
|
@ -100,37 +100,35 @@ class TestCommonView(WebTestCase):
|
|||
feedback_send.reset_mock()
|
||||
|
||||
# invalid form data
|
||||
self.request.POST = {'message': 'hello world'}
|
||||
self.request.POST = {"message": "hello world"}
|
||||
context = view.feedback()
|
||||
self.assertEqual(list(context), ['error'])
|
||||
self.assertIn('Required', context['error'])
|
||||
self.assertEqual(list(context), ["error"])
|
||||
self.assertIn("Required", context["error"])
|
||||
feedback_send.assert_not_called()
|
||||
|
||||
# error on send
|
||||
self.request.POST = {
|
||||
'referrer': '/foo',
|
||||
'user_name': "Barney Rubble",
|
||||
'message': "hello world",
|
||||
"referrer": "/foo",
|
||||
"user_name": "Barney Rubble",
|
||||
"message": "hello world",
|
||||
}
|
||||
feedback_send.side_effect = RuntimeError
|
||||
context = view.feedback()
|
||||
feedback_send.assert_called_once()
|
||||
self.assertEqual(list(context), ['error'])
|
||||
self.assertIn('RuntimeError', context['error'])
|
||||
self.assertEqual(list(context), ["error"])
|
||||
self.assertIn("RuntimeError", context["error"])
|
||||
|
||||
def test_feedback_send(self):
|
||||
view = self.make_view()
|
||||
with patch.object(self.app, 'send_email') as send_email:
|
||||
view.feedback_send({'user_name': "Barney",
|
||||
'message': "hello world"})
|
||||
send_email.assert_called_once_with('feedback', {
|
||||
'user_name': "Barney",
|
||||
'message': "hello world"
|
||||
})
|
||||
with patch.object(self.app, "send_email") as send_email:
|
||||
view.feedback_send({"user_name": "Barney", "message": "hello world"})
|
||||
send_email.assert_called_once_with(
|
||||
"feedback", {"user_name": "Barney", "message": "hello world"}
|
||||
)
|
||||
|
||||
def test_setup(self):
|
||||
self.pyramid_config.add_route('home', '/')
|
||||
self.pyramid_config.add_route('login', '/login')
|
||||
self.pyramid_config.add_route("home", "/")
|
||||
self.pyramid_config.add_route("login", "/login")
|
||||
model = self.app.model
|
||||
auth = self.app.get_auth_handler()
|
||||
view = self.make_view()
|
||||
|
|
@ -138,10 +136,10 @@ class TestCommonView(WebTestCase):
|
|||
# at first, can see the setup page
|
||||
self.assertEqual(self.session.query(model.User).count(), 0)
|
||||
context = view.setup(session=self.session)
|
||||
self.assertEqual(context['index_title'], self.app.get_title())
|
||||
self.assertEqual(context["index_title"], self.app.get_title())
|
||||
|
||||
# so add a user
|
||||
user = model.User(username='foo')
|
||||
user = model.User(username="foo")
|
||||
self.session.add(user)
|
||||
self.session.commit()
|
||||
|
||||
|
|
@ -155,25 +153,25 @@ class TestCommonView(WebTestCase):
|
|||
|
||||
# so we can see the setup page again
|
||||
context = view.setup(session=self.session)
|
||||
self.assertEqual(context['index_title'], self.app.get_title())
|
||||
self.assertEqual(context["index_title"], self.app.get_title())
|
||||
|
||||
# and finally, post data to create admin user
|
||||
self.request.method = 'POST'
|
||||
self.request.method = "POST"
|
||||
self.request.POST = {
|
||||
'username': 'barney',
|
||||
'__start__': 'password:mapping',
|
||||
'password': 'testpass',
|
||||
'password-confirm': 'testpass',
|
||||
'__end__': 'password:mapping',
|
||||
'first_name': "Barney",
|
||||
'last_name': "Rubble",
|
||||
"username": "barney",
|
||||
"__start__": "password:mapping",
|
||||
"password": "testpass",
|
||||
"password-confirm": "testpass",
|
||||
"__end__": "password:mapping",
|
||||
"first_name": "Barney",
|
||||
"last_name": "Rubble",
|
||||
}
|
||||
response = view.setup(session=self.session)
|
||||
# nb. redirects on success
|
||||
self.assertEqual(response.status_code, 302)
|
||||
barney = self.session.query(model.User).one()
|
||||
self.assertEqual(barney.username, 'barney')
|
||||
self.assertTrue(auth.check_user_password(barney, 'testpass'))
|
||||
self.assertEqual(barney.username, "barney")
|
||||
self.assertTrue(auth.check_user_password(barney, "testpass"))
|
||||
admin = auth.get_role_administrator(self.session)
|
||||
self.assertIn(admin, barney.roles)
|
||||
self.assertIsNotNone(barney.person)
|
||||
|
|
@ -183,30 +181,30 @@ class TestCommonView(WebTestCase):
|
|||
self.assertEqual(person.full_name, "Barney Rubble")
|
||||
|
||||
def test_change_theme(self):
|
||||
self.pyramid_config.add_route('home', '/')
|
||||
self.pyramid_config.add_route("home", "/")
|
||||
settings = self.request.registry.settings
|
||||
establish_theme(settings)
|
||||
view = self.make_view()
|
||||
|
||||
# theme is not changed if not provided by caller
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'default')
|
||||
with patch.object(mod, 'set_app_theme') as set_app_theme:
|
||||
self.assertEqual(settings["wuttaweb.theme"], "default")
|
||||
with patch.object(mod, "set_app_theme") as set_app_theme:
|
||||
view.change_theme()
|
||||
set_app_theme.assert_not_called()
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'default')
|
||||
self.assertEqual(settings["wuttaweb.theme"], "default")
|
||||
|
||||
# but theme will change if provided
|
||||
with patch.object(self.request, 'params', new={'theme': 'butterfly'}):
|
||||
with patch.object(mod, 'Session', return_value=self.session):
|
||||
with patch.object(self.request, "params", new={"theme": "butterfly"}):
|
||||
with patch.object(mod, "Session", return_value=self.session):
|
||||
view.change_theme()
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'butterfly')
|
||||
self.assertEqual(settings["wuttaweb.theme"], "butterfly")
|
||||
|
||||
# flash error if invalid theme is provided
|
||||
self.assertFalse(self.request.session.peek_flash('error'))
|
||||
with patch.object(self.request, 'params', new={'theme': 'anotherone'}):
|
||||
with patch.object(mod, 'Session', return_value=self.session):
|
||||
self.assertFalse(self.request.session.peek_flash("error"))
|
||||
with patch.object(self.request, "params", new={"theme": "anotherone"}):
|
||||
with patch.object(mod, "Session", return_value=self.session):
|
||||
view.change_theme()
|
||||
self.assertEqual(settings['wuttaweb.theme'], 'butterfly')
|
||||
self.assertTrue(self.request.session.peek_flash('error'))
|
||||
messages = self.request.session.pop_flash('error')
|
||||
self.assertIn('Failed to set theme', messages[0])
|
||||
self.assertEqual(settings["wuttaweb.theme"], "butterfly")
|
||||
self.assertTrue(self.request.session.peek_flash("error"))
|
||||
messages = self.request.session.pop_flash("error")
|
||||
self.assertIn("Failed to set theme", messages[0])
|
||||
|
|
|
|||
|
|
@ -18,194 +18,232 @@ class TestEmailSettingViews(WebTestCase):
|
|||
return mod.EmailSettingView(self.request)
|
||||
|
||||
def test_includeme(self):
|
||||
self.pyramid_config.include('wuttaweb.views.email')
|
||||
self.pyramid_config.include("wuttaweb.views.email")
|
||||
|
||||
def test_get_grid_data(self):
|
||||
self.config.setdefault('wutta.email.default.sender', 'test@example.com')
|
||||
self.config.setdefault("wutta.email.default.sender", "test@example.com")
|
||||
view = self.make_view()
|
||||
data = view.get_grid_data()
|
||||
self.assertIsInstance(data, list)
|
||||
self.assertTrue(data) # 1+ items
|
||||
self.assertTrue(data) # 1+ items
|
||||
setting = data[0]
|
||||
self.assertIn('key', setting)
|
||||
self.assertIn('subject', setting)
|
||||
self.assertIn('sender', setting)
|
||||
self.assertIn('to', setting)
|
||||
self.assertIn('cc', setting)
|
||||
self.assertIn('notes', setting)
|
||||
self.assertIn("key", setting)
|
||||
self.assertIn("subject", setting)
|
||||
self.assertIn("sender", setting)
|
||||
self.assertIn("to", setting)
|
||||
self.assertIn("cc", setting)
|
||||
self.assertIn("notes", setting)
|
||||
|
||||
def test_configure_grid(self):
|
||||
self.config.setdefault('wutta.email.default.sender', 'test@example.com')
|
||||
self.config.setdefault("wutta.email.default.sender", "test@example.com")
|
||||
view = self.make_view()
|
||||
grid = view.make_model_grid()
|
||||
self.assertIn('key', grid.searchable_columns)
|
||||
self.assertIn('subject', grid.searchable_columns)
|
||||
self.assertIn("key", grid.searchable_columns)
|
||||
self.assertIn("subject", grid.searchable_columns)
|
||||
|
||||
def test_render_to_short(self):
|
||||
view = self.make_view()
|
||||
setting = EmailSetting(self.config)
|
||||
|
||||
# more than 2 recips
|
||||
result = view.render_to_short(setting, 'to', [
|
||||
'alice@example.com',
|
||||
'bob@example.com',
|
||||
'charlie@example.com',
|
||||
'diana@example.com',
|
||||
])
|
||||
self.assertEqual(result, 'alice@example.com, bob@example.com, ...')
|
||||
result = view.render_to_short(
|
||||
setting,
|
||||
"to",
|
||||
[
|
||||
"alice@example.com",
|
||||
"bob@example.com",
|
||||
"charlie@example.com",
|
||||
"diana@example.com",
|
||||
],
|
||||
)
|
||||
self.assertEqual(result, "alice@example.com, bob@example.com, ...")
|
||||
|
||||
# just 2 recips
|
||||
result = view.render_to_short(setting, 'to', [
|
||||
'alice@example.com',
|
||||
'bob@example.com',
|
||||
])
|
||||
self.assertEqual(result, 'alice@example.com, bob@example.com')
|
||||
result = view.render_to_short(
|
||||
setting,
|
||||
"to",
|
||||
[
|
||||
"alice@example.com",
|
||||
"bob@example.com",
|
||||
],
|
||||
)
|
||||
self.assertEqual(result, "alice@example.com, bob@example.com")
|
||||
|
||||
# just 1 recip
|
||||
result = view.render_to_short(setting, 'to', ['alice@example.com'])
|
||||
self.assertEqual(result, 'alice@example.com')
|
||||
result = view.render_to_short(setting, "to", ["alice@example.com"])
|
||||
self.assertEqual(result, "alice@example.com")
|
||||
|
||||
# no recips
|
||||
result = view.render_to_short(setting, 'to', [])
|
||||
result = view.render_to_short(setting, "to", [])
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_get_instance(self):
|
||||
self.config.setdefault('wutta.email.default.sender', 'test@example.com')
|
||||
self.config.setdefault("wutta.email.default.sender", "test@example.com")
|
||||
view = self.make_view()
|
||||
|
||||
# normal
|
||||
with patch.object(self.request, 'matchdict', new={'key': 'feedback'}):
|
||||
with patch.object(self.request, "matchdict", new={"key": "feedback"}):
|
||||
setting = view.get_instance()
|
||||
self.assertIsInstance(setting, dict)
|
||||
self.assertIn('key', setting)
|
||||
self.assertIn('sender', setting)
|
||||
self.assertIn('subject', setting)
|
||||
self.assertIn('to', setting)
|
||||
self.assertIn('cc', setting)
|
||||
self.assertIn('notes', setting)
|
||||
self.assertIn('enabled', setting)
|
||||
self.assertIn("key", setting)
|
||||
self.assertIn("sender", setting)
|
||||
self.assertIn("subject", setting)
|
||||
self.assertIn("to", setting)
|
||||
self.assertIn("cc", setting)
|
||||
self.assertIn("notes", setting)
|
||||
self.assertIn("enabled", setting)
|
||||
|
||||
# not found
|
||||
with patch.object(self.request, 'matchdict', new={'key': 'this-should_notEXIST'}):
|
||||
with patch.object(
|
||||
self.request, "matchdict", new={"key": "this-should_notEXIST"}
|
||||
):
|
||||
self.assertRaises(HTTPNotFound, view.get_instance)
|
||||
|
||||
def test_get_instance_title(self):
|
||||
view = self.make_view()
|
||||
result = view.get_instance_title({'subject': 'whatever'})
|
||||
self.assertEqual(result, 'whatever')
|
||||
result = view.get_instance_title({"subject": "whatever"})
|
||||
self.assertEqual(result, "whatever")
|
||||
|
||||
def test_configure_form(self):
|
||||
self.config.setdefault('wutta.email.default.sender', 'test@example.com')
|
||||
self.config.setdefault("wutta.email.default.sender", "test@example.com")
|
||||
view = self.make_view()
|
||||
|
||||
with patch.object(self.request, 'matchdict', new={'key': 'feedback'}):
|
||||
with patch.object(self.request, "matchdict", new={"key": "feedback"}):
|
||||
setting = view.get_instance()
|
||||
form = view.make_model_form(setting)
|
||||
self.assertIn('description', form.readonly_fields)
|
||||
self.assertFalse(form.required_fields['replyto'])
|
||||
self.assertIn("description", form.readonly_fields)
|
||||
self.assertFalse(form.required_fields["replyto"])
|
||||
|
||||
def test_persist(self):
|
||||
model = self.app.model
|
||||
self.config.setdefault('wutta.email.default.sender', 'test@example.com')
|
||||
self.config.setdefault("wutta.email.default.sender", "test@example.com")
|
||||
view = self.make_view()
|
||||
|
||||
# start w/ no settings in db
|
||||
self.assertEqual(self.session.query(model.Setting).count(), 0)
|
||||
|
||||
# "edit" settings for feedback email
|
||||
with patch.object(self.request, 'matchdict', new={'key': 'feedback'}):
|
||||
with patch.object(self.request, "matchdict", new={"key": "feedback"}):
|
||||
setting = view.get_instance()
|
||||
setting['subject'] = 'Testing Feedback'
|
||||
setting['sender'] = 'feedback@example.com'
|
||||
setting['replyto'] = 'feedback4@example.com'
|
||||
setting['to'] = 'feedback@example.com'
|
||||
setting['cc'] = 'feedback2@example.com'
|
||||
setting['bcc'] = 'feedback3@example.com'
|
||||
setting['notes'] = "did this work?"
|
||||
setting['enabled'] = True
|
||||
setting["subject"] = "Testing Feedback"
|
||||
setting["sender"] = "feedback@example.com"
|
||||
setting["replyto"] = "feedback4@example.com"
|
||||
setting["to"] = "feedback@example.com"
|
||||
setting["cc"] = "feedback2@example.com"
|
||||
setting["bcc"] = "feedback3@example.com"
|
||||
setting["notes"] = "did this work?"
|
||||
setting["enabled"] = True
|
||||
|
||||
# persist email settings
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
with patch.object(view, "Session", return_value=self.session):
|
||||
view.persist(setting)
|
||||
self.session.commit()
|
||||
|
||||
# check settings in db
|
||||
self.assertEqual(self.session.query(model.Setting).count(), 8)
|
||||
self.assertEqual(self.app.get_setting(self.session, 'wutta.email.feedback.subject'),
|
||||
"Testing Feedback")
|
||||
self.assertEqual(self.app.get_setting(self.session, 'wutta.email.feedback.sender'),
|
||||
'feedback@example.com')
|
||||
self.assertEqual(self.app.get_setting(self.session, 'wutta.email.feedback.replyto'),
|
||||
'feedback4@example.com')
|
||||
self.assertEqual(self.app.get_setting(self.session, 'wutta.email.feedback.to'),
|
||||
'feedback@example.com')
|
||||
self.assertEqual(self.app.get_setting(self.session, 'wutta.email.feedback.cc'),
|
||||
'feedback2@example.com')
|
||||
self.assertEqual(self.app.get_setting(self.session, 'wutta.email.feedback.bcc'),
|
||||
'feedback3@example.com')
|
||||
self.assertEqual(self.app.get_setting(self.session, 'wutta.email.feedback.notes'),
|
||||
"did this work?")
|
||||
self.assertEqual(self.app.get_setting(self.session, 'wutta.email.feedback.enabled'),
|
||||
'true')
|
||||
self.assertEqual(
|
||||
self.app.get_setting(self.session, "wutta.email.feedback.subject"),
|
||||
"Testing Feedback",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.app.get_setting(self.session, "wutta.email.feedback.sender"),
|
||||
"feedback@example.com",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.app.get_setting(self.session, "wutta.email.feedback.replyto"),
|
||||
"feedback4@example.com",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.app.get_setting(self.session, "wutta.email.feedback.to"),
|
||||
"feedback@example.com",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.app.get_setting(self.session, "wutta.email.feedback.cc"),
|
||||
"feedback2@example.com",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.app.get_setting(self.session, "wutta.email.feedback.bcc"),
|
||||
"feedback3@example.com",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.app.get_setting(self.session, "wutta.email.feedback.notes"),
|
||||
"did this work?",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.app.get_setting(self.session, "wutta.email.feedback.enabled"), "true"
|
||||
)
|
||||
|
||||
# "edit" settings for feedback email
|
||||
with patch.object(self.request, 'matchdict', new={'key': 'feedback'}):
|
||||
with patch.object(self.request, "matchdict", new={"key": "feedback"}):
|
||||
setting = view.get_instance()
|
||||
setting['subject'] = None
|
||||
setting['sender'] = None
|
||||
setting['replyto'] = None
|
||||
setting['to'] = None
|
||||
setting['cc'] = None
|
||||
setting['bcc'] = None
|
||||
setting['notes'] = None
|
||||
setting['enabled'] = False
|
||||
setting["subject"] = None
|
||||
setting["sender"] = None
|
||||
setting["replyto"] = None
|
||||
setting["to"] = None
|
||||
setting["cc"] = None
|
||||
setting["bcc"] = None
|
||||
setting["notes"] = None
|
||||
setting["enabled"] = False
|
||||
|
||||
# persist email settings
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
with patch.object(view, "Session", return_value=self.session):
|
||||
view.persist(setting)
|
||||
self.session.commit()
|
||||
|
||||
# check settings in db
|
||||
self.assertEqual(self.session.query(model.Setting).count(), 1)
|
||||
self.assertIsNone(self.app.get_setting(self.session, 'wutta.email.feedback.subject'))
|
||||
self.assertIsNone(self.app.get_setting(self.session, 'wutta.email.feedback.sender'))
|
||||
self.assertIsNone(self.app.get_setting(self.session, 'wutta.email.feedback.replyto'))
|
||||
self.assertIsNone(self.app.get_setting(self.session, 'wutta.email.feedback.to'))
|
||||
self.assertIsNone(self.app.get_setting(self.session, 'wutta.email.feedback.cc'))
|
||||
self.assertIsNone(self.app.get_setting(self.session, 'wutta.email.feedback.bcc'))
|
||||
self.assertIsNone(self.app.get_setting(self.session, 'wutta.email.feedback.notes'))
|
||||
self.assertEqual(self.app.get_setting(self.session, 'wutta.email.feedback.enabled'),
|
||||
'false')
|
||||
self.assertIsNone(
|
||||
self.app.get_setting(self.session, "wutta.email.feedback.subject")
|
||||
)
|
||||
self.assertIsNone(
|
||||
self.app.get_setting(self.session, "wutta.email.feedback.sender")
|
||||
)
|
||||
self.assertIsNone(
|
||||
self.app.get_setting(self.session, "wutta.email.feedback.replyto")
|
||||
)
|
||||
self.assertIsNone(self.app.get_setting(self.session, "wutta.email.feedback.to"))
|
||||
self.assertIsNone(self.app.get_setting(self.session, "wutta.email.feedback.cc"))
|
||||
self.assertIsNone(
|
||||
self.app.get_setting(self.session, "wutta.email.feedback.bcc")
|
||||
)
|
||||
self.assertIsNone(
|
||||
self.app.get_setting(self.session, "wutta.email.feedback.notes")
|
||||
)
|
||||
self.assertEqual(
|
||||
self.app.get_setting(self.session, "wutta.email.feedback.enabled"), "false"
|
||||
)
|
||||
|
||||
def test_render_to_response(self):
|
||||
self.config.setdefault('wutta.email.default.sender', 'test@example.com')
|
||||
self.pyramid_config.add_route('home', '/')
|
||||
self.pyramid_config.add_route('login', '/auth/login')
|
||||
self.pyramid_config.add_route('email_settings', '/email/settings')
|
||||
self.pyramid_config.add_route('email_settings.preview', '/email/settings/{key}/preview')
|
||||
self.config.setdefault("wutta.email.default.sender", "test@example.com")
|
||||
self.pyramid_config.add_route("home", "/")
|
||||
self.pyramid_config.add_route("login", "/auth/login")
|
||||
self.pyramid_config.add_route("email_settings", "/email/settings")
|
||||
self.pyramid_config.add_route(
|
||||
"email_settings.preview", "/email/settings/{key}/preview"
|
||||
)
|
||||
view = self.make_view()
|
||||
|
||||
# nb. this gives coverage, but tests nothing..
|
||||
with patch.object(self.request, 'matchdict', new={'key': 'feedback'}):
|
||||
with patch.object(self.request, "matchdict", new={"key": "feedback"}):
|
||||
setting = view.get_instance()
|
||||
with patch.object(view, 'viewing', new=True):
|
||||
context = {'instance': setting}
|
||||
response = view.render_to_response('view', context)
|
||||
with patch.object(view, "viewing", new=True):
|
||||
context = {"instance": setting}
|
||||
response = view.render_to_response("view", context)
|
||||
self.assertIsInstance(response, Response)
|
||||
|
||||
def test_preview(self):
|
||||
self.config.setdefault('wutta.email.default.sender', 'test@example.com')
|
||||
self.config.setdefault("wutta.email.default.sender", "test@example.com")
|
||||
view = self.make_view()
|
||||
|
||||
# nb. this gives coverage, but tests nothing..
|
||||
with patch.object(self.request, 'matchdict', new={'key': 'feedback'}):
|
||||
with patch.object(self.request, "matchdict", new={"key": "feedback"}):
|
||||
|
||||
# html
|
||||
with patch.object(self.request, 'params', new={'mode': 'html'}):
|
||||
with patch.object(self.request, "params", new={"mode": "html"}):
|
||||
response = view.preview()
|
||||
self.assertEqual(response.content_type, 'text/html')
|
||||
self.assertEqual(response.content_type, "text/html")
|
||||
|
||||
# txt
|
||||
with patch.object(self.request, 'params', new={'mode': 'txt'}):
|
||||
with patch.object(self.request, "params", new={"mode": "txt"}):
|
||||
response = view.preview()
|
||||
self.assertEqual(response.content_type, 'text/plain')
|
||||
self.assertEqual(response.content_type, "text/plain")
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@ from wuttaweb.testing import WebTestCase
|
|||
class TestEssentialViews(WebTestCase):
|
||||
|
||||
def test_includeme(self):
|
||||
self.pyramid_config.include('wuttaweb.views.essential')
|
||||
self.pyramid_config.include("wuttaweb.views.essential")
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -18,7 +18,7 @@ class TestPersonView(WebTestCase):
|
|||
return people.PersonView(self.request)
|
||||
|
||||
def test_includeme(self):
|
||||
self.pyramid_config.include('wuttaweb.views.people')
|
||||
self.pyramid_config.include("wuttaweb.views.people")
|
||||
|
||||
def test_get_query(self):
|
||||
view = self.make_view()
|
||||
|
|
@ -31,7 +31,7 @@ class TestPersonView(WebTestCase):
|
|||
grid = view.make_grid(model_class=model.Setting)
|
||||
self.assertEqual(grid.linked_columns, [])
|
||||
view.configure_grid(grid)
|
||||
self.assertIn('full_name', grid.linked_columns)
|
||||
self.assertIn("full_name", grid.linked_columns)
|
||||
|
||||
def test_configure_form(self):
|
||||
model = self.app.model
|
||||
|
|
@ -39,19 +39,19 @@ class TestPersonView(WebTestCase):
|
|||
|
||||
# full_name
|
||||
form = view.make_form(model_class=model.Person)
|
||||
self.assertIn('full_name', form)
|
||||
with patch.object(view, 'creating', new=True):
|
||||
self.assertIn("full_name", form)
|
||||
with patch.object(view, "creating", new=True):
|
||||
view.configure_form(form)
|
||||
self.assertNotIn('full_name', form)
|
||||
self.assertNotIn("full_name", form)
|
||||
|
||||
# users
|
||||
person = model.Person()
|
||||
form = view.make_form(model_instance=person)
|
||||
self.assertNotIn('users', form.widgets)
|
||||
with patch.object(view, 'viewing', new=True):
|
||||
self.assertNotIn("users", form.widgets)
|
||||
with patch.object(view, "viewing", new=True):
|
||||
view.configure_form(form)
|
||||
self.assertIn('users', form.widgets)
|
||||
self.assertIsInstance(form.widgets['users'], GridWidget)
|
||||
self.assertIn("users", form.widgets)
|
||||
self.assertIsInstance(form.widgets["users"], GridWidget)
|
||||
|
||||
def test_make_users_grid(self):
|
||||
model = self.app.model
|
||||
|
|
@ -65,13 +65,13 @@ class TestPersonView(WebTestCase):
|
|||
self.assertFalse(grid.actions)
|
||||
|
||||
# view + edit actions
|
||||
with patch.object(self.request, 'is_root', new=True):
|
||||
with patch.object(self.request, "is_root", new=True):
|
||||
grid = view.make_users_grid(person)
|
||||
self.assertIsInstance(grid, Grid)
|
||||
self.assertIn('username', grid.linked_columns)
|
||||
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')
|
||||
self.assertEqual(grid.actions[0].key, "view")
|
||||
self.assertEqual(grid.actions[1].key, "edit")
|
||||
|
||||
def test_objectify(self):
|
||||
model = self.app.model
|
||||
|
|
@ -79,15 +79,15 @@ class TestPersonView(WebTestCase):
|
|||
|
||||
# creating
|
||||
form = view.make_model_form()
|
||||
form.validated = {'first_name': 'Barney', 'last_name': 'Rubble'}
|
||||
form.validated = {"first_name": "Barney", "last_name": "Rubble"}
|
||||
person = view.objectify(form)
|
||||
self.assertEqual(person.full_name, 'Barney Rubble')
|
||||
self.assertEqual(person.full_name, "Barney Rubble")
|
||||
|
||||
# editing
|
||||
form = view.make_model_form(model_instance=person)
|
||||
form.validated = {'first_name': 'Betty', 'last_name': 'Rubble'}
|
||||
form.validated = {"first_name": "Betty", "last_name": "Rubble"}
|
||||
person2 = view.objectify(form)
|
||||
self.assertEqual(person2.full_name, 'Betty Rubble')
|
||||
self.assertEqual(person2.full_name, "Betty Rubble")
|
||||
self.assertIs(person2, person)
|
||||
|
||||
def test_autocomplete_query(self):
|
||||
|
|
@ -100,24 +100,24 @@ class TestPersonView(WebTestCase):
|
|||
self.session.commit()
|
||||
|
||||
view = self.make_view()
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
with patch.object(view, "Session", return_value=self.session):
|
||||
|
||||
# both people match
|
||||
query = view.autocomplete_query('george')
|
||||
query = view.autocomplete_query("george")
|
||||
self.assertEqual(query.count(), 2)
|
||||
|
||||
# just 1 match
|
||||
query = view.autocomplete_query('jones')
|
||||
query = view.autocomplete_query("jones")
|
||||
self.assertEqual(query.count(), 1)
|
||||
|
||||
# no matches
|
||||
query = view.autocomplete_query('sally')
|
||||
query = view.autocomplete_query("sally")
|
||||
self.assertEqual(query.count(), 0)
|
||||
|
||||
def test_view_profile(self):
|
||||
self.pyramid_config.include('wuttaweb.views.common')
|
||||
self.pyramid_config.include('wuttaweb.views.auth')
|
||||
self.pyramid_config.add_route('people', '/people/')
|
||||
self.pyramid_config.include("wuttaweb.views.common")
|
||||
self.pyramid_config.include("wuttaweb.views.auth")
|
||||
self.pyramid_config.add_route("people", "/people/")
|
||||
|
||||
model = self.app.model
|
||||
person = model.Person(full_name="Barney Rubble")
|
||||
|
|
@ -126,12 +126,12 @@ class TestPersonView(WebTestCase):
|
|||
|
||||
# sanity check
|
||||
view = self.make_view()
|
||||
self.request.matchdict = {'uuid': person.uuid}
|
||||
self.request.matchdict = {"uuid": person.uuid}
|
||||
response = view.view_profile(session=self.session)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_make_user(self):
|
||||
self.pyramid_config.include('wuttaweb.views.common')
|
||||
self.pyramid_config.include("wuttaweb.views.common")
|
||||
|
||||
model = self.app.model
|
||||
person = model.Person(full_name="Barney Rubble")
|
||||
|
|
@ -140,7 +140,7 @@ class TestPersonView(WebTestCase):
|
|||
|
||||
# sanity check
|
||||
view = self.make_view()
|
||||
self.request.matchdict = {'uuid': person.uuid}
|
||||
self.request.matchdict = {"uuid": person.uuid}
|
||||
response = view.make_user()
|
||||
# nb. this always redirects for now
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ from wuttaweb.testing import WebTestCase
|
|||
class TestProgressView(WebTestCase):
|
||||
|
||||
def test_includeme(self):
|
||||
self.pyramid_config.include('wuttaweb.views.progress')
|
||||
self.pyramid_config.include("wuttaweb.views.progress")
|
||||
|
||||
def test_basic(self):
|
||||
self.request.session.id = 'mockid'
|
||||
self.request.matchdict = {'key': 'foo'}
|
||||
self.request.session.id = "mockid"
|
||||
self.request.matchdict = {"key": "foo"}
|
||||
|
||||
# first call with no setup, will create the progress session
|
||||
# but it should be "empty" - except not really since beaker
|
||||
|
|
@ -23,40 +23,40 @@ class TestProgressView(WebTestCase):
|
|||
self.assertIsInstance(context, dict)
|
||||
|
||||
# now let's establish a progress session of our own
|
||||
progsess = get_progress_session(self.request, 'bar')
|
||||
progsess['maximum'] = 2
|
||||
progsess['value'] = 1
|
||||
progsess = get_progress_session(self.request, "bar")
|
||||
progsess["maximum"] = 2
|
||||
progsess["value"] = 1
|
||||
progsess.save()
|
||||
|
||||
# then call view, check results
|
||||
self.request.matchdict = {'key': 'bar'}
|
||||
self.request.matchdict = {"key": "bar"}
|
||||
context = mod.progress(self.request)
|
||||
self.assertEqual(context['maximum'], 2)
|
||||
self.assertEqual(context['value'], 1)
|
||||
self.assertNotIn('complete', context)
|
||||
self.assertEqual(context["maximum"], 2)
|
||||
self.assertEqual(context["value"], 1)
|
||||
self.assertNotIn("complete", context)
|
||||
|
||||
# now mark it as complete, check results
|
||||
progsess['complete'] = True
|
||||
progsess['success_msg'] = "yay!"
|
||||
progsess["complete"] = True
|
||||
progsess["success_msg"] = "yay!"
|
||||
progsess.save()
|
||||
context = mod.progress(self.request)
|
||||
self.assertTrue(context['complete'])
|
||||
self.assertEqual(context['success_msg'], "yay!")
|
||||
self.assertTrue(context["complete"])
|
||||
self.assertEqual(context["success_msg"], "yay!")
|
||||
|
||||
# now do that all again, with error
|
||||
progsess = get_progress_session(self.request, 'baz')
|
||||
progsess['maximum'] = 2
|
||||
progsess['value'] = 1
|
||||
progsess = get_progress_session(self.request, "baz")
|
||||
progsess["maximum"] = 2
|
||||
progsess["value"] = 1
|
||||
progsess.save()
|
||||
self.request.matchdict = {'key': 'baz'}
|
||||
self.request.matchdict = {"key": "baz"}
|
||||
context = mod.progress(self.request)
|
||||
self.assertEqual(context['maximum'], 2)
|
||||
self.assertEqual(context['value'], 1)
|
||||
self.assertNotIn('complete', context)
|
||||
self.assertNotIn('error', context)
|
||||
progsess['error'] = True
|
||||
progsess['error_msg'] = "omg!"
|
||||
self.assertEqual(context["maximum"], 2)
|
||||
self.assertEqual(context["value"], 1)
|
||||
self.assertNotIn("complete", context)
|
||||
self.assertNotIn("error", context)
|
||||
progsess["error"] = True
|
||||
progsess["error_msg"] = "omg!"
|
||||
progsess.save()
|
||||
context = mod.progress(self.request)
|
||||
self.assertTrue(context['error'])
|
||||
self.assertEqual(context['error_msg'], "omg!")
|
||||
self.assertTrue(context["error"])
|
||||
self.assertEqual(context["error_msg"], "omg!")
|
||||
|
|
|
|||
|
|
@ -16,28 +16,29 @@ class SomeRandomReport(Report):
|
|||
"""
|
||||
This report shows something random.
|
||||
"""
|
||||
report_key = 'testing_some_random'
|
||||
|
||||
report_key = "testing_some_random"
|
||||
report_title = "Random Test Report"
|
||||
|
||||
def add_params(self, schema):
|
||||
|
||||
schema.add(colander.SchemaNode(
|
||||
colander.String(),
|
||||
name='foo',
|
||||
missing=colander.null))
|
||||
schema.add(
|
||||
colander.SchemaNode(colander.String(), name="foo", missing=colander.null)
|
||||
)
|
||||
|
||||
schema.add(colander.SchemaNode(
|
||||
colander.Date(),
|
||||
name='start_date',
|
||||
missing=colander.null))
|
||||
schema.add(
|
||||
colander.SchemaNode(
|
||||
colander.Date(), name="start_date", missing=colander.null
|
||||
)
|
||||
)
|
||||
|
||||
def get_output_columns(self):
|
||||
return ['foo']
|
||||
return ["foo"]
|
||||
|
||||
def make_data(self, params, **kwargs):
|
||||
return {
|
||||
'output_title': "Testing Output",
|
||||
'data': [{'foo': 'bar'}],
|
||||
"output_title": "Testing Output",
|
||||
"data": [{"foo": "bar"}],
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -47,68 +48,77 @@ class TestReportViews(WebTestCase):
|
|||
return mod.ReportView(self.request)
|
||||
|
||||
def test_includeme(self):
|
||||
self.pyramid_config.include('wuttaweb.views.reports')
|
||||
self.pyramid_config.include("wuttaweb.views.reports")
|
||||
|
||||
def test_get_grid_data(self):
|
||||
view = self.make_view()
|
||||
providers = dict(self.app.providers)
|
||||
providers['wuttatest'] = MagicMock(report_modules=['tests.views.test_reports'])
|
||||
with patch.object(self.app, 'providers', new=providers):
|
||||
providers["wuttatest"] = MagicMock(report_modules=["tests.views.test_reports"])
|
||||
with patch.object(self.app, "providers", new=providers):
|
||||
|
||||
data = view.get_grid_data()
|
||||
self.assertIsInstance(data, list)
|
||||
self.assertTrue(data) # 1+ reports
|
||||
self.assertTrue(data) # 1+ reports
|
||||
|
||||
def test_normalize_report(self):
|
||||
view = self.make_view()
|
||||
report = SomeRandomReport(self.config)
|
||||
normal = view.normalize_report(report)
|
||||
help_text = normal.pop('help_text').strip()
|
||||
help_text = normal.pop("help_text").strip()
|
||||
self.assertEqual(help_text, "This report shows something random.")
|
||||
self.assertEqual(normal, {
|
||||
'report_key': 'testing_some_random',
|
||||
'report_title': "Random Test Report",
|
||||
})
|
||||
self.assertEqual(
|
||||
normal,
|
||||
{
|
||||
"report_key": "testing_some_random",
|
||||
"report_title": "Random Test Report",
|
||||
},
|
||||
)
|
||||
|
||||
def test_configure_grid(self):
|
||||
view = self.make_view()
|
||||
grid = view.make_model_grid()
|
||||
self.assertIn('report_title', grid.searchable_columns)
|
||||
self.assertIn('help_text', grid.searchable_columns)
|
||||
self.assertIn("report_title", grid.searchable_columns)
|
||||
self.assertIn("help_text", grid.searchable_columns)
|
||||
|
||||
def test_get_instance(self):
|
||||
view = self.make_view()
|
||||
providers = {
|
||||
'wuttatest': MagicMock(report_modules=['tests.views.test_reports']),
|
||||
"wuttatest": MagicMock(report_modules=["tests.views.test_reports"]),
|
||||
}
|
||||
with patch.object(self.app, 'providers', new=providers):
|
||||
with patch.object(self.app, "providers", new=providers):
|
||||
|
||||
# normal
|
||||
with patch.object(self.request, 'matchdict', new={'report_key': 'testing_some_random'}):
|
||||
with patch.object(
|
||||
self.request, "matchdict", new={"report_key": "testing_some_random"}
|
||||
):
|
||||
report = view.get_instance()
|
||||
self.assertIsInstance(report, dict)
|
||||
self.assertEqual(report['report_key'], 'testing_some_random')
|
||||
self.assertEqual(report['report_title'], "Random Test Report")
|
||||
self.assertEqual(report["report_key"], "testing_some_random")
|
||||
self.assertEqual(report["report_title"], "Random Test Report")
|
||||
|
||||
# not found
|
||||
with patch.object(self.request, 'matchdict', new={'report_key': 'this-should_notEXIST'}):
|
||||
with patch.object(
|
||||
self.request, "matchdict", new={"report_key": "this-should_notEXIST"}
|
||||
):
|
||||
self.assertRaises(HTTPNotFound, view.get_instance)
|
||||
|
||||
def test_get_instance_title(self):
|
||||
view = self.make_view()
|
||||
result = view.get_instance_title({'report_title': 'whatever'})
|
||||
self.assertEqual(result, 'whatever')
|
||||
result = view.get_instance_title({"report_title": "whatever"})
|
||||
self.assertEqual(result, "whatever")
|
||||
|
||||
def test_view(self):
|
||||
self.pyramid_config.add_route('home', '/')
|
||||
self.pyramid_config.add_route('login', '/auth/login')
|
||||
self.pyramid_config.add_route('reports', '/reports/')
|
||||
self.pyramid_config.add_route('reports.view', '/reports/{report_key}')
|
||||
self.pyramid_config.add_route("home", "/")
|
||||
self.pyramid_config.add_route("login", "/auth/login")
|
||||
self.pyramid_config.add_route("reports", "/reports/")
|
||||
self.pyramid_config.add_route("reports.view", "/reports/{report_key}")
|
||||
view = self.make_view()
|
||||
providers = dict(self.app.providers)
|
||||
providers['wuttatest'] = MagicMock(report_modules=['tests.views.test_reports'])
|
||||
with patch.object(self.app, 'providers', new=providers):
|
||||
with patch.object(self.request, 'matchdict', new={'report_key': 'testing_some_random'}):
|
||||
providers["wuttatest"] = MagicMock(report_modules=["tests.views.test_reports"])
|
||||
with patch.object(self.app, "providers", new=providers):
|
||||
with patch.object(
|
||||
self.request, "matchdict", new={"report_key": "testing_some_random"}
|
||||
):
|
||||
|
||||
# initial view
|
||||
response = view.view()
|
||||
|
|
@ -118,11 +128,15 @@ class TestReportViews(WebTestCase):
|
|||
self.assertNotIn("Testing Output", response.text)
|
||||
|
||||
# run the report
|
||||
with patch.object(self.request, 'GET', new={
|
||||
'__start__': 'start_date:mapping',
|
||||
'date': '2025-01-11',
|
||||
'__end__': 'start_date',
|
||||
}):
|
||||
with patch.object(
|
||||
self.request,
|
||||
"GET",
|
||||
new={
|
||||
"__start__": "start_date:mapping",
|
||||
"date": "2025-01-11",
|
||||
"__end__": "start_date",
|
||||
},
|
||||
):
|
||||
response = view.view()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# nb. there's a button in there somewhere, *and* an output title
|
||||
|
|
@ -132,105 +146,142 @@ class TestReportViews(WebTestCase):
|
|||
def test_configure_form(self):
|
||||
view = self.make_view()
|
||||
providers = dict(self.app.providers)
|
||||
providers['wuttatest'] = MagicMock(report_modules=['tests.views.test_reports'])
|
||||
with patch.object(self.app, 'providers', new=providers):
|
||||
providers["wuttatest"] = MagicMock(report_modules=["tests.views.test_reports"])
|
||||
with patch.object(self.app, "providers", new=providers):
|
||||
|
||||
with patch.object(self.request, 'matchdict', new={'report_key': 'testing_some_random'}):
|
||||
with patch.object(
|
||||
self.request, "matchdict", new={"report_key": "testing_some_random"}
|
||||
):
|
||||
report = view.get_instance()
|
||||
form = view.make_model_form(report)
|
||||
self.assertIn('help_text', form.readonly_fields)
|
||||
self.assertIn('foo', form)
|
||||
self.assertIn("help_text", form.readonly_fields)
|
||||
self.assertIn("foo", form)
|
||||
|
||||
def test_normalize_columns(self):
|
||||
view = self.make_view()
|
||||
|
||||
columns = view.normalize_columns(['foo'])
|
||||
self.assertEqual(columns, [
|
||||
{'name': 'foo', 'label': 'foo'},
|
||||
])
|
||||
columns = view.normalize_columns(["foo"])
|
||||
self.assertEqual(
|
||||
columns,
|
||||
[
|
||||
{"name": "foo", "label": "foo"},
|
||||
],
|
||||
)
|
||||
|
||||
columns = view.normalize_columns([{'name': 'foo'}])
|
||||
self.assertEqual(columns, [
|
||||
{'name': 'foo', 'label': 'foo'},
|
||||
])
|
||||
columns = view.normalize_columns([{"name": "foo"}])
|
||||
self.assertEqual(
|
||||
columns,
|
||||
[
|
||||
{"name": "foo", "label": "foo"},
|
||||
],
|
||||
)
|
||||
|
||||
columns = view.normalize_columns([{'name': 'foo', 'label': "FOO"}])
|
||||
self.assertEqual(columns, [
|
||||
{'name': 'foo', 'label': 'FOO'},
|
||||
])
|
||||
columns = view.normalize_columns([{"name": "foo", "label": "FOO"}])
|
||||
self.assertEqual(
|
||||
columns,
|
||||
[
|
||||
{"name": "foo", "label": "FOO"},
|
||||
],
|
||||
)
|
||||
|
||||
columns = view.normalize_columns([{'name': 'foo', 'label': "FOO", 'numeric': True}])
|
||||
self.assertEqual(columns, [
|
||||
{'name': 'foo', 'label': 'FOO', 'numeric': True},
|
||||
])
|
||||
columns = view.normalize_columns(
|
||||
[{"name": "foo", "label": "FOO", "numeric": True}]
|
||||
)
|
||||
self.assertEqual(
|
||||
columns,
|
||||
[
|
||||
{"name": "foo", "label": "FOO", "numeric": True},
|
||||
],
|
||||
)
|
||||
|
||||
def test_run_report(self):
|
||||
view = self.make_view()
|
||||
providers = dict(self.app.providers)
|
||||
providers['wuttatest'] = MagicMock(report_modules=['tests.views.test_reports'])
|
||||
with patch.object(self.app, 'providers', new=providers):
|
||||
providers["wuttatest"] = MagicMock(report_modules=["tests.views.test_reports"])
|
||||
with patch.object(self.app, "providers", new=providers):
|
||||
|
||||
with patch.object(self.request, 'matchdict', new={'report_key': 'testing_some_random'}):
|
||||
report = view.report_handler.get_report('testing_some_random')
|
||||
with patch.object(
|
||||
self.request, "matchdict", new={"report_key": "testing_some_random"}
|
||||
):
|
||||
report = view.report_handler.get_report("testing_some_random")
|
||||
normal = view.normalize_report(report)
|
||||
form = view.make_model_form(normal)
|
||||
|
||||
# typical
|
||||
context = view.run_report(report, {'form': form})
|
||||
self.assertEqual(sorted(context['report_params']), ['foo', 'start_date'])
|
||||
self.assertEqual(context['report_data'], {
|
||||
'output_title': "Testing Output",
|
||||
'data': [{'foo': 'bar'}],
|
||||
})
|
||||
self.assertIn('report_generated', context)
|
||||
context = view.run_report(report, {"form": form})
|
||||
self.assertEqual(
|
||||
sorted(context["report_params"]), ["foo", "start_date"]
|
||||
)
|
||||
self.assertEqual(
|
||||
context["report_data"],
|
||||
{
|
||||
"output_title": "Testing Output",
|
||||
"data": [{"foo": "bar"}],
|
||||
},
|
||||
)
|
||||
self.assertIn("report_generated", context)
|
||||
|
||||
# invalid params
|
||||
with patch.object(self.request, 'GET', new={'start_date': 'NOT_GOOD'}):
|
||||
context = view.run_report(report, {'form': form})
|
||||
self.assertNotIn('report_params', context)
|
||||
self.assertNotIn('report_data', context)
|
||||
self.assertNotIn('report_generated', context)
|
||||
with patch.object(self.request, "GET", new={"start_date": "NOT_GOOD"}):
|
||||
context = view.run_report(report, {"form": form})
|
||||
self.assertNotIn("report_params", context)
|
||||
self.assertNotIn("report_data", context)
|
||||
self.assertNotIn("report_generated", context)
|
||||
|
||||
# custom formatter
|
||||
with patch.object(report, 'get_output_columns') as get_output_columns:
|
||||
with patch.object(report, "get_output_columns") as get_output_columns:
|
||||
get_output_columns.return_value = [
|
||||
'foo',
|
||||
{'name': 'start_date',
|
||||
'formatter': lambda val: "FORMATTED VALUE"},
|
||||
"foo",
|
||||
{
|
||||
"name": "start_date",
|
||||
"formatter": lambda val: "FORMATTED VALUE",
|
||||
},
|
||||
]
|
||||
|
||||
with patch.object(report, 'make_data') as make_data:
|
||||
with patch.object(report, "make_data") as make_data:
|
||||
make_data.return_value = [
|
||||
{'foo': 'bar', 'start_date': datetime.date(2025, 1, 11)},
|
||||
{"foo": "bar", "start_date": datetime.date(2025, 1, 11)},
|
||||
]
|
||||
|
||||
context = view.run_report(report, {'form': form})
|
||||
context = view.run_report(report, {"form": form})
|
||||
get_output_columns.assert_called_once_with()
|
||||
self.assertEqual(len(context['report_columns']), 2)
|
||||
self.assertEqual(context['report_columns'][0]['name'], 'foo')
|
||||
self.assertEqual(context['report_columns'][1]['name'], 'start_date')
|
||||
self.assertEqual(context['report_data'], {
|
||||
'output_title': "Random Test Report",
|
||||
'data': [{'foo': 'bar', 'start_date': 'FORMATTED VALUE'}],
|
||||
})
|
||||
self.assertEqual(len(context["report_columns"]), 2)
|
||||
self.assertEqual(context["report_columns"][0]["name"], "foo")
|
||||
self.assertEqual(
|
||||
context["report_columns"][1]["name"], "start_date"
|
||||
)
|
||||
self.assertEqual(
|
||||
context["report_data"],
|
||||
{
|
||||
"output_title": "Random Test Report",
|
||||
"data": [
|
||||
{"foo": "bar", "start_date": "FORMATTED VALUE"}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
def test_download_data(self):
|
||||
view = self.make_view()
|
||||
providers = dict(self.app.providers)
|
||||
providers['wuttatest'] = MagicMock(report_modules=['tests.views.test_reports'])
|
||||
with patch.object(self.app, 'providers', new=providers):
|
||||
with patch.object(self.request, 'matchdict', new={'report_key': 'testing_some_random'}):
|
||||
providers["wuttatest"] = MagicMock(report_modules=["tests.views.test_reports"])
|
||||
with patch.object(self.app, "providers", new=providers):
|
||||
with patch.object(
|
||||
self.request, "matchdict", new={"report_key": "testing_some_random"}
|
||||
):
|
||||
|
||||
params, columns, data = view.get_download_data()
|
||||
self.assertEqual(params, {})
|
||||
self.assertEqual(columns, [{'name': 'foo', 'label': 'foo'}])
|
||||
self.assertEqual(data, {
|
||||
'output_title': "Testing Output",
|
||||
'data': [{'foo': 'bar'}],
|
||||
})
|
||||
self.assertEqual(columns, [{"name": "foo", "label": "foo"}])
|
||||
self.assertEqual(
|
||||
data,
|
||||
{
|
||||
"output_title": "Testing Output",
|
||||
"data": [{"foo": "bar"}],
|
||||
},
|
||||
)
|
||||
|
||||
def test_download_path(self):
|
||||
view = self.make_view()
|
||||
data = {'output_title': "My Report"}
|
||||
path = view.get_download_path(data, 'csv')
|
||||
self.assertTrue(path.endswith('My Report.csv'))
|
||||
data = {"output_title": "My Report"}
|
||||
path = view.get_download_path(data, "csv")
|
||||
self.assertTrue(path.endswith("My Report.csv"))
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class TestRoleView(WebTestCase):
|
|||
return mod.RoleView(self.request)
|
||||
|
||||
def test_includeme(self):
|
||||
self.pyramid_config.include('wuttaweb.views.roles')
|
||||
self.pyramid_config.include("wuttaweb.views.roles")
|
||||
|
||||
def test_get_query(self):
|
||||
view = self.make_view()
|
||||
|
|
@ -29,9 +29,9 @@ class TestRoleView(WebTestCase):
|
|||
model = self.app.model
|
||||
view = self.make_view()
|
||||
grid = view.make_grid(model_class=model.Role)
|
||||
self.assertFalse(grid.is_linked('name'))
|
||||
self.assertFalse(grid.is_linked("name"))
|
||||
view.configure_grid(grid)
|
||||
self.assertTrue(grid.is_linked('name'))
|
||||
self.assertTrue(grid.is_linked("name"))
|
||||
|
||||
def test_is_editable(self):
|
||||
model = self.app.model
|
||||
|
|
@ -56,10 +56,10 @@ class TestRoleView(WebTestCase):
|
|||
# reset
|
||||
del self.request.user_permissions
|
||||
|
||||
barney = model.User(username='barney')
|
||||
barney = model.User(username="barney")
|
||||
self.session.add(barney)
|
||||
barney.roles.append(blokes)
|
||||
auth.grant_permission(blokes, 'roles.edit_builtin')
|
||||
auth.grant_permission(blokes, "roles.edit_builtin")
|
||||
self.session.commit()
|
||||
|
||||
# user with perms can edit *some* built-in
|
||||
|
|
@ -90,9 +90,9 @@ class TestRoleView(WebTestCase):
|
|||
role = model.Role(name="Foo")
|
||||
view = self.make_view()
|
||||
form = view.make_form(model_instance=role)
|
||||
self.assertNotIn('name', form.validators)
|
||||
self.assertNotIn("name", form.validators)
|
||||
view.configure_form(form)
|
||||
self.assertIsNotNone(form.validators['name'])
|
||||
self.assertIsNotNone(form.validators["name"])
|
||||
|
||||
def test_make_users_grid(self):
|
||||
model = self.app.model
|
||||
|
|
@ -106,48 +106,48 @@ class TestRoleView(WebTestCase):
|
|||
self.assertFalse(grid.actions)
|
||||
|
||||
# view + edit actions
|
||||
with patch.object(self.request, 'is_root', new=True):
|
||||
with patch.object(self.request, "is_root", new=True):
|
||||
grid = view.make_users_grid(role)
|
||||
self.assertIsInstance(grid, Grid)
|
||||
self.assertIn('person', grid.linked_columns)
|
||||
self.assertIn('username', grid.linked_columns)
|
||||
self.assertIn("person", grid.linked_columns)
|
||||
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')
|
||||
self.assertEqual(grid.actions[0].key, "view")
|
||||
self.assertEqual(grid.actions[1].key, "edit")
|
||||
|
||||
def test_unique_name(self):
|
||||
model = self.app.model
|
||||
view = self.make_view()
|
||||
|
||||
role = model.Role(name='Foo')
|
||||
role = model.Role(name="Foo")
|
||||
self.session.add(role)
|
||||
self.session.commit()
|
||||
|
||||
with patch.object(mod, 'Session', return_value=self.session):
|
||||
with patch.object(mod, "Session", return_value=self.session):
|
||||
|
||||
# invalid if same name in data
|
||||
node = colander.SchemaNode(colander.String(), name='name')
|
||||
self.assertRaises(colander.Invalid, view.unique_name, node, 'Foo')
|
||||
node = colander.SchemaNode(colander.String(), name="name")
|
||||
self.assertRaises(colander.Invalid, view.unique_name, node, "Foo")
|
||||
|
||||
# but not if name belongs to current role
|
||||
view.editing = True
|
||||
self.request.matchdict = {'uuid': role.uuid}
|
||||
node = colander.SchemaNode(colander.String(), name='name')
|
||||
self.assertIsNone(view.unique_name(node, 'Foo'))
|
||||
self.request.matchdict = {"uuid": role.uuid}
|
||||
node = colander.SchemaNode(colander.String(), name="name")
|
||||
self.assertIsNone(view.unique_name(node, "Foo"))
|
||||
|
||||
def get_permissions(self):
|
||||
return {
|
||||
'widgets': {
|
||||
'label': "Widgets",
|
||||
'perms': {
|
||||
'widgets.list': {
|
||||
'label': "List widgets",
|
||||
"widgets": {
|
||||
"label": "Widgets",
|
||||
"perms": {
|
||||
"widgets.list": {
|
||||
"label": "List widgets",
|
||||
},
|
||||
'widgets.polish': {
|
||||
'label': "Polish the widgets",
|
||||
"widgets.polish": {
|
||||
"label": "Polish the widgets",
|
||||
},
|
||||
'widgets.view': {
|
||||
'label': "View widget",
|
||||
"widgets.view": {
|
||||
"label": "View widget",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -157,55 +157,59 @@ class TestRoleView(WebTestCase):
|
|||
model = self.app.model
|
||||
auth = self.app.get_auth_handler()
|
||||
blokes = model.Role(name="Blokes")
|
||||
auth.grant_permission(blokes, 'widgets.list')
|
||||
auth.grant_permission(blokes, "widgets.list")
|
||||
self.session.add(blokes)
|
||||
barney = model.User(username='barney')
|
||||
barney = model.User(username="barney")
|
||||
barney.roles.append(blokes)
|
||||
self.session.add(barney)
|
||||
self.session.commit()
|
||||
view = self.make_view()
|
||||
all_perms = self.get_permissions()
|
||||
self.request.registry.settings['wutta_permissions'] = all_perms
|
||||
self.request.registry.settings["wutta_permissions"] = all_perms
|
||||
|
||||
def has_perm(perm):
|
||||
if perm == 'widgets.list':
|
||||
if perm == "widgets.list":
|
||||
return True
|
||||
return False
|
||||
|
||||
with patch.object(self.request, 'has_perm', new=has_perm, create=True):
|
||||
with patch.object(self.request, "has_perm", new=has_perm, create=True):
|
||||
|
||||
# sanity check; current request has 1 perm
|
||||
self.assertTrue(self.request.has_perm('widgets.list'))
|
||||
self.assertFalse(self.request.has_perm('widgets.polish'))
|
||||
self.assertFalse(self.request.has_perm('widgets.view'))
|
||||
self.assertTrue(self.request.has_perm("widgets.list"))
|
||||
self.assertFalse(self.request.has_perm("widgets.polish"))
|
||||
self.assertFalse(self.request.has_perm("widgets.view"))
|
||||
|
||||
# when editing, user sees only the 1 perm
|
||||
with patch.object(view, 'editing', new=True):
|
||||
with patch.object(view, "editing", new=True):
|
||||
perms = view.get_available_permissions()
|
||||
self.assertEqual(list(perms), ['widgets'])
|
||||
self.assertEqual(list(perms['widgets']['perms']), ['widgets.list'])
|
||||
self.assertEqual(list(perms), ["widgets"])
|
||||
self.assertEqual(list(perms["widgets"]["perms"]), ["widgets.list"])
|
||||
|
||||
# but when viewing, same user sees all perms
|
||||
with patch.object(view, 'viewing', new=True):
|
||||
with patch.object(view, "viewing", new=True):
|
||||
perms = view.get_available_permissions()
|
||||
self.assertEqual(list(perms), ['widgets'])
|
||||
self.assertEqual(list(perms['widgets']['perms']),
|
||||
['widgets.list', 'widgets.polish', 'widgets.view'])
|
||||
self.assertEqual(list(perms), ["widgets"])
|
||||
self.assertEqual(
|
||||
list(perms["widgets"]["perms"]),
|
||||
["widgets.list", "widgets.polish", "widgets.view"],
|
||||
)
|
||||
|
||||
# also, when admin user is editing, sees all perms
|
||||
self.request.is_admin = True
|
||||
with patch.object(view, 'editing', new=True):
|
||||
with patch.object(view, "editing", new=True):
|
||||
perms = view.get_available_permissions()
|
||||
self.assertEqual(list(perms), ['widgets'])
|
||||
self.assertEqual(list(perms['widgets']['perms']),
|
||||
['widgets.list', 'widgets.polish', 'widgets.view'])
|
||||
self.assertEqual(list(perms), ["widgets"])
|
||||
self.assertEqual(
|
||||
list(perms["widgets"]["perms"]),
|
||||
["widgets.list", "widgets.polish", "widgets.view"],
|
||||
)
|
||||
|
||||
def test_objectify(self):
|
||||
model = self.app.model
|
||||
auth = self.app.get_auth_handler()
|
||||
blokes = model.Role(name="Blokes")
|
||||
self.session.add(blokes)
|
||||
barney = model.User(username='barney')
|
||||
barney = model.User(username="barney")
|
||||
barney.roles.append(blokes)
|
||||
self.session.add(barney)
|
||||
self.session.commit()
|
||||
|
|
@ -213,56 +217,62 @@ class TestRoleView(WebTestCase):
|
|||
permissions = self.get_permissions()
|
||||
|
||||
# sanity check, role has just 1 perm
|
||||
auth.grant_permission(blokes, 'widgets.list')
|
||||
auth.grant_permission(blokes, "widgets.list")
|
||||
self.session.commit()
|
||||
self.assertEqual(blokes.permissions, ['widgets.list'])
|
||||
self.assertEqual(blokes.permissions, ["widgets.list"])
|
||||
|
||||
# form can update role perms
|
||||
view.editing = True
|
||||
self.request.matchdict = {'uuid': blokes.uuid}
|
||||
with patch.object(view, 'get_available_permissions', return_value=permissions):
|
||||
self.request.matchdict = {"uuid": blokes.uuid}
|
||||
with patch.object(view, "get_available_permissions", return_value=permissions):
|
||||
form = view.make_model_form(model_instance=blokes)
|
||||
form.validated = {'name': 'Blokes',
|
||||
'permissions': {'widgets.list', 'widgets.polish', 'widgets.view'}}
|
||||
form.validated = {
|
||||
"name": "Blokes",
|
||||
"permissions": {"widgets.list", "widgets.polish", "widgets.view"},
|
||||
}
|
||||
role = view.objectify(form)
|
||||
self.session.commit()
|
||||
self.assertIs(role, blokes)
|
||||
self.assertEqual(blokes.permissions, ['widgets.list', 'widgets.polish', 'widgets.view'])
|
||||
self.assertEqual(
|
||||
blokes.permissions, ["widgets.list", "widgets.polish", "widgets.view"]
|
||||
)
|
||||
|
||||
def test_update_permissions(self):
|
||||
model = self.app.model
|
||||
auth = self.app.get_auth_handler()
|
||||
blokes = model.Role(name="Blokes")
|
||||
auth.grant_permission(blokes, 'widgets.list')
|
||||
auth.grant_permission(blokes, "widgets.list")
|
||||
self.session.add(blokes)
|
||||
barney = model.User(username='barney')
|
||||
barney = model.User(username="barney")
|
||||
barney.roles.append(blokes)
|
||||
self.session.add(barney)
|
||||
self.session.commit()
|
||||
view = self.make_view()
|
||||
permissions = self.get_permissions()
|
||||
|
||||
with patch.object(view, 'get_available_permissions', return_value=permissions):
|
||||
with patch.object(view, "get_available_permissions", return_value=permissions):
|
||||
|
||||
# no error if data is missing perms
|
||||
form = view.make_model_form(model_instance=blokes)
|
||||
form.validated = {'name': 'BloX'}
|
||||
form.validated = {"name": "BloX"}
|
||||
role = view.objectify(form)
|
||||
self.session.commit()
|
||||
self.assertIs(role, blokes)
|
||||
self.assertEqual(blokes.name, 'BloX')
|
||||
self.assertEqual(blokes.name, "BloX")
|
||||
|
||||
# sanity check, role has just 1 perm
|
||||
self.assertEqual(blokes.permissions, ['widgets.list'])
|
||||
self.assertEqual(blokes.permissions, ["widgets.list"])
|
||||
|
||||
# role perms are updated
|
||||
form = view.make_model_form(model_instance=blokes)
|
||||
form.validated = {'name': 'Blokes',
|
||||
'permissions': {'widgets.polish', 'widgets.view'}}
|
||||
form.validated = {
|
||||
"name": "Blokes",
|
||||
"permissions": {"widgets.polish", "widgets.view"},
|
||||
}
|
||||
role = view.objectify(form)
|
||||
self.session.commit()
|
||||
self.assertIs(role, blokes)
|
||||
self.assertEqual(blokes.permissions, ['widgets.polish', 'widgets.view'])
|
||||
self.assertEqual(blokes.permissions, ["widgets.polish", "widgets.view"])
|
||||
|
||||
|
||||
class TestPermissionView(WebTestCase):
|
||||
|
|
@ -279,17 +289,17 @@ class TestPermissionView(WebTestCase):
|
|||
model = self.app.model
|
||||
view = self.make_view()
|
||||
grid = view.make_grid(model_class=model.Permission)
|
||||
self.assertFalse(grid.is_linked('role'))
|
||||
self.assertFalse(grid.is_linked("role"))
|
||||
view.configure_grid(grid)
|
||||
self.assertTrue(grid.is_linked('role'))
|
||||
self.assertTrue(grid.is_linked("role"))
|
||||
|
||||
def test_configure_form(self):
|
||||
model = self.app.model
|
||||
role = model.Role(name="Foo")
|
||||
perm = model.Permission(role=role, permission='whatever')
|
||||
perm = model.Permission(role=role, permission="whatever")
|
||||
view = self.make_view()
|
||||
form = view.make_form(model_instance=perm)
|
||||
self.assertIsNone(form.schema)
|
||||
view.configure_form(form)
|
||||
schema = form.get_schema()
|
||||
self.assertIsInstance(schema['role'].typ, RoleRef)
|
||||
self.assertIsInstance(schema["role"].typ, RoleRef)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class TestAppInfoView(WebTestCase):
|
|||
|
||||
def setUp(self):
|
||||
self.setup_web()
|
||||
self.pyramid_config.include('wuttaweb.views.essential')
|
||||
self.pyramid_config.include("wuttaweb.views.essential")
|
||||
|
||||
def make_view(self):
|
||||
return mod.AppInfoView(self.request)
|
||||
|
|
@ -26,7 +26,7 @@ class TestAppInfoView(WebTestCase):
|
|||
self.assertEqual(data, [])
|
||||
|
||||
# 'partial' request returns data
|
||||
self.request.GET = {'partial': '1'}
|
||||
self.request.GET = {"partial": "1"}
|
||||
data = view.get_grid_data()
|
||||
self.assertIsInstance(data, list)
|
||||
self.assertTrue(data)
|
||||
|
|
@ -61,7 +61,7 @@ class TestSettingView(WebTestCase):
|
|||
self.assertEqual(len(data), 0)
|
||||
|
||||
# unless we save some settings
|
||||
self.app.save_setting(self.session, 'foo', 'bar')
|
||||
self.app.save_setting(self.session, "foo", "bar")
|
||||
self.session.commit()
|
||||
query = view.get_grid_data(session=self.session)
|
||||
data = query.all()
|
||||
|
|
@ -71,34 +71,34 @@ class TestSettingView(WebTestCase):
|
|||
model = self.app.model
|
||||
view = self.make_view()
|
||||
grid = view.make_grid(model_class=model.Setting)
|
||||
self.assertFalse(grid.is_linked('name'))
|
||||
self.assertFalse(grid.is_linked("name"))
|
||||
view.configure_grid(grid)
|
||||
self.assertTrue(grid.is_linked('name'))
|
||||
self.assertTrue(grid.is_linked("name"))
|
||||
|
||||
def test_configure_form(self):
|
||||
view = self.make_view()
|
||||
form = view.make_form(fields=view.get_form_fields())
|
||||
self.assertNotIn('value', form.required_fields)
|
||||
self.assertNotIn("value", form.required_fields)
|
||||
view.configure_form(form)
|
||||
self.assertIn('value', form.required_fields)
|
||||
self.assertFalse(form.required_fields['value'])
|
||||
self.assertIn("value", form.required_fields)
|
||||
self.assertFalse(form.required_fields["value"])
|
||||
|
||||
def test_unique_name(self):
|
||||
model = self.app.model
|
||||
view = self.make_view()
|
||||
|
||||
setting = model.Setting(name='foo')
|
||||
setting = model.Setting(name="foo")
|
||||
self.session.add(setting)
|
||||
self.session.commit()
|
||||
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
with patch.object(view, "Session", return_value=self.session):
|
||||
|
||||
# invalid if same name in data
|
||||
node = colander.SchemaNode(colander.String(), name='name')
|
||||
self.assertRaises(colander.Invalid, view.unique_name, node, 'foo')
|
||||
node = colander.SchemaNode(colander.String(), name="name")
|
||||
self.assertRaises(colander.Invalid, view.unique_name, node, "foo")
|
||||
|
||||
# but not if name belongs to current setting
|
||||
view.editing = True
|
||||
self.request.matchdict = {'name': 'foo'}
|
||||
node = colander.SchemaNode(colander.String(), name='name')
|
||||
self.assertIsNone(view.unique_name(node, 'foo'))
|
||||
self.request.matchdict = {"name": "foo"}
|
||||
node = colander.SchemaNode(colander.String(), name="name")
|
||||
self.assertIsNone(view.unique_name(node, "foo"))
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class TestUpgradeView(WebTestCase):
|
|||
return mod.UpgradeView(self.request)
|
||||
|
||||
def test_includeme(self):
|
||||
self.pyramid_config.include('wuttaweb.views.upgrades')
|
||||
self.pyramid_config.include("wuttaweb.views.upgrades")
|
||||
|
||||
def test_configure_grid(self):
|
||||
model = self.app.model
|
||||
|
|
@ -37,74 +37,79 @@ class TestUpgradeView(WebTestCase):
|
|||
self.assertIsNone(view.grid_row_class(upgrade, data, 1))
|
||||
|
||||
upgrade.status = enum.UpgradeStatus.EXECUTING
|
||||
self.assertEqual(view.grid_row_class(upgrade, data, 1), 'has-background-warning')
|
||||
self.assertEqual(
|
||||
view.grid_row_class(upgrade, data, 1), "has-background-warning"
|
||||
)
|
||||
|
||||
upgrade.status = enum.UpgradeStatus.SUCCESS
|
||||
self.assertIsNone(view.grid_row_class(upgrade, data, 1))
|
||||
|
||||
upgrade.status = enum.UpgradeStatus.FAILURE
|
||||
self.assertEqual(view.grid_row_class(upgrade, data, 1), 'has-background-warning')
|
||||
self.assertEqual(
|
||||
view.grid_row_class(upgrade, data, 1), "has-background-warning"
|
||||
)
|
||||
|
||||
def test_configure_form(self):
|
||||
self.pyramid_config.add_route('upgrades.download', '/upgrades/{uuid}/download')
|
||||
self.pyramid_config.add_route("upgrades.download", "/upgrades/{uuid}/download")
|
||||
model = self.app.model
|
||||
enum = self.app.enum
|
||||
user = model.User(username='barney')
|
||||
user = model.User(username="barney")
|
||||
self.session.add(user)
|
||||
upgrade = model.Upgrade(description='test', created_by=user,
|
||||
status=enum.UpgradeStatus.PENDING)
|
||||
upgrade = model.Upgrade(
|
||||
description="test", created_by=user, status=enum.UpgradeStatus.PENDING
|
||||
)
|
||||
self.session.add(upgrade)
|
||||
self.session.commit()
|
||||
view = self.make_view()
|
||||
|
||||
# some fields exist when viewing
|
||||
with patch.object(view, 'viewing', new=True):
|
||||
with patch.object(view, "viewing", new=True):
|
||||
form = view.make_form(model_class=model.Upgrade, model_instance=upgrade)
|
||||
self.assertIn('created', form)
|
||||
self.assertIn("created", form)
|
||||
view.configure_form(form)
|
||||
self.assertIn('created', form)
|
||||
self.assertIn("created", form)
|
||||
|
||||
# but then are removed when creating
|
||||
with patch.object(view, 'creating', new=True):
|
||||
with patch.object(view, "creating", new=True):
|
||||
form = view.make_form(model_class=model.Upgrade)
|
||||
self.assertIn('created', form)
|
||||
self.assertIn("created", form)
|
||||
view.configure_form(form)
|
||||
self.assertNotIn('created', form)
|
||||
self.assertNotIn("created", form)
|
||||
|
||||
# test executed, stdout/stderr when viewing
|
||||
with patch.object(view, 'viewing', new=True):
|
||||
with patch.object(view, "viewing", new=True):
|
||||
|
||||
# executed is *not* shown by default
|
||||
form = view.make_form(model_class=model.Upgrade, model_instance=upgrade)
|
||||
self.assertIn('executed', form)
|
||||
self.assertIn("executed", form)
|
||||
view.configure_form(form)
|
||||
self.assertNotIn('executed', form)
|
||||
self.assertNotIn('stdout_file', form)
|
||||
self.assertNotIn('stderr_file', form)
|
||||
self.assertNotIn("executed", form)
|
||||
self.assertNotIn("stdout_file", form)
|
||||
self.assertNotIn("stderr_file", form)
|
||||
|
||||
# but it *is* shown if upgrade is executed
|
||||
upgrade.executed = datetime.datetime.now()
|
||||
upgrade.status = enum.UpgradeStatus.SUCCESS
|
||||
form = view.make_form(model_class=model.Upgrade, model_instance=upgrade)
|
||||
self.assertIn('executed', form)
|
||||
self.assertIn("executed", form)
|
||||
view.configure_form(form)
|
||||
self.assertIn('executed', form)
|
||||
self.assertIn('stdout_file', form)
|
||||
self.assertIn('stderr_file', form)
|
||||
self.assertIn("executed", form)
|
||||
self.assertIn("stdout_file", form)
|
||||
self.assertIn("stderr_file", form)
|
||||
|
||||
def test_objectify(self):
|
||||
model = self.app.model
|
||||
enum = self.app.enum
|
||||
user = model.User(username='barney')
|
||||
user = model.User(username="barney")
|
||||
self.session.add(user)
|
||||
self.session.commit()
|
||||
view = self.make_view()
|
||||
|
||||
# user and status are auto-set when creating
|
||||
self.request.user = user
|
||||
self.request.method = 'POST'
|
||||
self.request.POST = {'description': "new one"}
|
||||
with patch.object(view, 'creating', new=True):
|
||||
self.request.method = "POST"
|
||||
self.request.POST = {"description": "new one"}
|
||||
with patch.object(view, "creating", new=True):
|
||||
form = view.make_model_form()
|
||||
self.assertTrue(form.validate())
|
||||
upgrade = view.objectify(form)
|
||||
|
|
@ -116,13 +121,14 @@ class TestUpgradeView(WebTestCase):
|
|||
model = self.app.model
|
||||
enum = self.app.enum
|
||||
|
||||
appdir = self.mkdir('app')
|
||||
self.config.setdefault('wutta.appdir', appdir)
|
||||
appdir = self.mkdir("app")
|
||||
self.config.setdefault("wutta.appdir", appdir)
|
||||
self.assertEqual(self.app.get_appdir(), appdir)
|
||||
|
||||
user = model.User(username='barney')
|
||||
upgrade = model.Upgrade(description='test', created_by=user,
|
||||
status=enum.UpgradeStatus.PENDING)
|
||||
user = model.User(username="barney")
|
||||
upgrade = model.Upgrade(
|
||||
description="test", created_by=user, status=enum.UpgradeStatus.PENDING
|
||||
)
|
||||
self.session.add(upgrade)
|
||||
self.session.commit()
|
||||
|
||||
|
|
@ -134,21 +140,24 @@ class TestUpgradeView(WebTestCase):
|
|||
self.assertIsNone(path)
|
||||
|
||||
# with filename
|
||||
path = view.download_path(upgrade, 'foo.txt')
|
||||
self.assertEqual(path, os.path.join(appdir, 'data', 'upgrades',
|
||||
uuid[:2], uuid[2:], 'foo.txt'))
|
||||
path = view.download_path(upgrade, "foo.txt")
|
||||
self.assertEqual(
|
||||
path,
|
||||
os.path.join(appdir, "data", "upgrades", uuid[:2], uuid[2:], "foo.txt"),
|
||||
)
|
||||
|
||||
def test_get_upgrade_filepath(self):
|
||||
model = self.app.model
|
||||
enum = self.app.enum
|
||||
|
||||
appdir = self.mkdir('app')
|
||||
self.config.setdefault('wutta.appdir', appdir)
|
||||
appdir = self.mkdir("app")
|
||||
self.config.setdefault("wutta.appdir", appdir)
|
||||
self.assertEqual(self.app.get_appdir(), appdir)
|
||||
|
||||
user = model.User(username='barney')
|
||||
upgrade = model.Upgrade(description='test', created_by=user,
|
||||
status=enum.UpgradeStatus.PENDING)
|
||||
user = model.User(username="barney")
|
||||
upgrade = model.Upgrade(
|
||||
description="test", created_by=user, status=enum.UpgradeStatus.PENDING
|
||||
)
|
||||
self.session.add(upgrade)
|
||||
self.session.commit()
|
||||
|
||||
|
|
@ -157,25 +166,29 @@ class TestUpgradeView(WebTestCase):
|
|||
|
||||
# no filename
|
||||
path = view.get_upgrade_filepath(upgrade)
|
||||
self.assertEqual(path, os.path.join(appdir, 'data', 'upgrades',
|
||||
uuid[:2], uuid[2:]))
|
||||
self.assertEqual(
|
||||
path, os.path.join(appdir, "data", "upgrades", uuid[:2], uuid[2:])
|
||||
)
|
||||
|
||||
# with filename
|
||||
path = view.get_upgrade_filepath(upgrade, 'foo.txt')
|
||||
self.assertEqual(path, os.path.join(appdir, 'data', 'upgrades',
|
||||
uuid[:2], uuid[2:], 'foo.txt'))
|
||||
path = view.get_upgrade_filepath(upgrade, "foo.txt")
|
||||
self.assertEqual(
|
||||
path,
|
||||
os.path.join(appdir, "data", "upgrades", uuid[:2], uuid[2:], "foo.txt"),
|
||||
)
|
||||
|
||||
def test_delete_instance(self):
|
||||
model = self.app.model
|
||||
enum = self.app.enum
|
||||
|
||||
appdir = self.mkdir('app')
|
||||
self.config.setdefault('wutta.appdir', appdir)
|
||||
appdir = self.mkdir("app")
|
||||
self.config.setdefault("wutta.appdir", appdir)
|
||||
self.assertEqual(self.app.get_appdir(), appdir)
|
||||
|
||||
user = model.User(username='barney')
|
||||
upgrade = model.Upgrade(description='test', created_by=user,
|
||||
status=enum.UpgradeStatus.PENDING)
|
||||
user = model.User(username="barney")
|
||||
upgrade = model.Upgrade(
|
||||
description="test", created_by=user, status=enum.UpgradeStatus.PENDING
|
||||
)
|
||||
self.session.add(upgrade)
|
||||
self.session.commit()
|
||||
|
||||
|
|
@ -183,19 +196,19 @@ class TestUpgradeView(WebTestCase):
|
|||
|
||||
# mock stdout/stderr files
|
||||
upgrade_dir = view.get_upgrade_filepath(upgrade)
|
||||
stdout = view.get_upgrade_filepath(upgrade, 'stdout.log')
|
||||
with open(stdout, 'w') as f:
|
||||
f.write('stdout')
|
||||
stderr = view.get_upgrade_filepath(upgrade, 'stderr.log')
|
||||
with open(stderr, 'w') as f:
|
||||
f.write('stderr')
|
||||
stdout = view.get_upgrade_filepath(upgrade, "stdout.log")
|
||||
with open(stdout, "w") as f:
|
||||
f.write("stdout")
|
||||
stderr = view.get_upgrade_filepath(upgrade, "stderr.log")
|
||||
with open(stderr, "w") as f:
|
||||
f.write("stderr")
|
||||
|
||||
# both upgrade and files are deleted
|
||||
self.assertTrue(os.path.exists(upgrade_dir))
|
||||
self.assertTrue(os.path.exists(stdout))
|
||||
self.assertTrue(os.path.exists(stderr))
|
||||
self.assertEqual(self.session.query(model.Upgrade).count(), 1)
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
with patch.object(view, "Session", return_value=self.session):
|
||||
view.delete_instance(upgrade)
|
||||
self.assertFalse(os.path.exists(upgrade_dir))
|
||||
self.assertFalse(os.path.exists(stdout))
|
||||
|
|
@ -206,13 +219,14 @@ class TestUpgradeView(WebTestCase):
|
|||
model = self.app.model
|
||||
enum = self.app.enum
|
||||
|
||||
appdir = self.mkdir('app')
|
||||
self.config.setdefault('wutta.appdir', appdir)
|
||||
appdir = self.mkdir("app")
|
||||
self.config.setdefault("wutta.appdir", appdir)
|
||||
self.assertEqual(self.app.get_appdir(), appdir)
|
||||
|
||||
user = model.User(username='barney')
|
||||
upgrade = model.Upgrade(description='test', created_by=user,
|
||||
status=enum.UpgradeStatus.PENDING)
|
||||
user = model.User(username="barney")
|
||||
upgrade = model.Upgrade(
|
||||
description="test", created_by=user, status=enum.UpgradeStatus.PENDING
|
||||
)
|
||||
self.session.add(upgrade)
|
||||
self.session.commit()
|
||||
|
||||
|
|
@ -224,139 +238,153 @@ class TestUpgradeView(WebTestCase):
|
|||
self.assertRaises(ConfigurationError, view.execute_instance, upgrade, user)
|
||||
|
||||
# script w/ success
|
||||
goodpy = self.write_file('good.py', """
|
||||
goodpy = self.write_file(
|
||||
"good.py",
|
||||
"""
|
||||
import sys
|
||||
sys.stdout.write('hello from good.py')
|
||||
sys.exit(0)
|
||||
""")
|
||||
self.app.save_setting(self.session, 'wutta.upgrades.command', f'{python} {goodpy}')
|
||||
""",
|
||||
)
|
||||
self.app.save_setting(
|
||||
self.session, "wutta.upgrades.command", f"{python} {goodpy}"
|
||||
)
|
||||
self.assertIsNone(upgrade.executed)
|
||||
self.assertIsNone(upgrade.executed_by)
|
||||
self.assertEqual(upgrade.status, enum.UpgradeStatus.PENDING)
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
with patch.object(self.config, 'usedb', new=True):
|
||||
with patch.object(view, "Session", return_value=self.session):
|
||||
with patch.object(self.config, "usedb", new=True):
|
||||
view.execute_instance(upgrade, user)
|
||||
self.assertIsNotNone(upgrade.executed)
|
||||
self.assertIs(upgrade.executed_by, user)
|
||||
self.assertEqual(upgrade.status, enum.UpgradeStatus.SUCCESS)
|
||||
with open(view.get_upgrade_filepath(upgrade, 'stdout.log')) as f:
|
||||
self.assertEqual(f.read(), 'hello from good.py')
|
||||
with open(view.get_upgrade_filepath(upgrade, 'stderr.log')) as f:
|
||||
self.assertEqual(f.read(), '')
|
||||
with open(view.get_upgrade_filepath(upgrade, "stdout.log")) as f:
|
||||
self.assertEqual(f.read(), "hello from good.py")
|
||||
with open(view.get_upgrade_filepath(upgrade, "stderr.log")) as f:
|
||||
self.assertEqual(f.read(), "")
|
||||
|
||||
# need a new record for next test
|
||||
upgrade = model.Upgrade(description='test', created_by=user,
|
||||
status=enum.UpgradeStatus.PENDING)
|
||||
upgrade = model.Upgrade(
|
||||
description="test", created_by=user, status=enum.UpgradeStatus.PENDING
|
||||
)
|
||||
self.session.add(upgrade)
|
||||
self.session.commit()
|
||||
|
||||
# script w/ failure
|
||||
badpy = self.write_file('bad.py', """
|
||||
badpy = self.write_file(
|
||||
"bad.py",
|
||||
"""
|
||||
import sys
|
||||
sys.stderr.write('hello from bad.py')
|
||||
sys.exit(42)
|
||||
""")
|
||||
self.app.save_setting(self.session, 'wutta.upgrades.command', f'{python} {badpy}')
|
||||
""",
|
||||
)
|
||||
self.app.save_setting(
|
||||
self.session, "wutta.upgrades.command", f"{python} {badpy}"
|
||||
)
|
||||
self.assertIsNone(upgrade.executed)
|
||||
self.assertIsNone(upgrade.executed_by)
|
||||
self.assertEqual(upgrade.status, enum.UpgradeStatus.PENDING)
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
with patch.object(self.config, 'usedb', new=True):
|
||||
with patch.object(view, "Session", return_value=self.session):
|
||||
with patch.object(self.config, "usedb", new=True):
|
||||
view.execute_instance(upgrade, user)
|
||||
self.assertIsNotNone(upgrade.executed)
|
||||
self.assertIs(upgrade.executed_by, user)
|
||||
self.assertEqual(upgrade.status, enum.UpgradeStatus.FAILURE)
|
||||
with open(view.get_upgrade_filepath(upgrade, 'stdout.log')) as f:
|
||||
self.assertEqual(f.read(), '')
|
||||
with open(view.get_upgrade_filepath(upgrade, 'stderr.log')) as f:
|
||||
self.assertEqual(f.read(), 'hello from bad.py')
|
||||
with open(view.get_upgrade_filepath(upgrade, "stdout.log")) as f:
|
||||
self.assertEqual(f.read(), "")
|
||||
with open(view.get_upgrade_filepath(upgrade, "stderr.log")) as f:
|
||||
self.assertEqual(f.read(), "hello from bad.py")
|
||||
|
||||
def test_execute_progress(self):
|
||||
model = self.app.model
|
||||
enum = self.app.enum
|
||||
view = self.make_view()
|
||||
|
||||
user = model.User(username='barney')
|
||||
user = model.User(username="barney")
|
||||
self.session.add(user)
|
||||
upgrade = model.Upgrade(description='test', created_by=user,
|
||||
status=enum.UpgradeStatus.PENDING)
|
||||
upgrade = model.Upgrade(
|
||||
description="test", created_by=user, status=enum.UpgradeStatus.PENDING
|
||||
)
|
||||
self.session.add(upgrade)
|
||||
self.session.commit()
|
||||
|
||||
stdout = self.write_file('stdout.log', 'hello 001\n')
|
||||
stdout = self.write_file("stdout.log", "hello 001\n")
|
||||
|
||||
self.request.matchdict = {'uuid': upgrade.uuid}
|
||||
with patch.multiple(mod.UpgradeView,
|
||||
Session=MagicMock(return_value=self.session),
|
||||
get_upgrade_filepath=MagicMock(return_value=stdout)):
|
||||
self.request.matchdict = {"uuid": upgrade.uuid}
|
||||
with patch.multiple(
|
||||
mod.UpgradeView,
|
||||
Session=MagicMock(return_value=self.session),
|
||||
get_upgrade_filepath=MagicMock(return_value=stdout),
|
||||
):
|
||||
|
||||
# nb. this is used to identify progress tracker
|
||||
self.request.session.id = 'mockid#1'
|
||||
self.request.session.id = "mockid#1"
|
||||
|
||||
# first call should get the full contents
|
||||
context = view.execute_progress()
|
||||
self.assertFalse(context.get('complete'))
|
||||
self.assertFalse(context.get('error'))
|
||||
self.assertFalse(context.get("complete"))
|
||||
self.assertFalse(context.get("error"))
|
||||
# nb. newline is converted to <br>
|
||||
self.assertEqual(context['stdout'], 'hello 001<br />')
|
||||
self.assertEqual(context["stdout"], "hello 001<br />")
|
||||
|
||||
# next call should get any new contents
|
||||
with open(stdout, 'a') as f:
|
||||
f.write('hello 002\n')
|
||||
with open(stdout, "a") as f:
|
||||
f.write("hello 002\n")
|
||||
context = view.execute_progress()
|
||||
self.assertFalse(context.get('complete'))
|
||||
self.assertFalse(context.get('error'))
|
||||
self.assertEqual(context['stdout'], 'hello 002<br />')
|
||||
self.assertFalse(context.get("complete"))
|
||||
self.assertFalse(context.get("error"))
|
||||
self.assertEqual(context["stdout"], "hello 002<br />")
|
||||
|
||||
# nb. switch to a different progress tracker
|
||||
self.request.session.id = 'mockid#2'
|
||||
self.request.session.id = "mockid#2"
|
||||
|
||||
# first call should get the full contents
|
||||
context = view.execute_progress()
|
||||
self.assertFalse(context.get('complete'))
|
||||
self.assertFalse(context.get('error'))
|
||||
self.assertEqual(context['stdout'], 'hello 001<br />hello 002<br />')
|
||||
self.assertFalse(context.get("complete"))
|
||||
self.assertFalse(context.get("error"))
|
||||
self.assertEqual(context["stdout"], "hello 001<br />hello 002<br />")
|
||||
|
||||
# mark progress complete
|
||||
session = get_progress_session(self.request, 'upgrades.execute')
|
||||
session = get_progress_session(self.request, "upgrades.execute")
|
||||
session.load()
|
||||
session['complete'] = True
|
||||
session['success_msg'] = 'yay!'
|
||||
session["complete"] = True
|
||||
session["success_msg"] = "yay!"
|
||||
session.save()
|
||||
|
||||
# next call should reflect that
|
||||
self.assertEqual(self.request.session.pop_flash(), [])
|
||||
context = view.execute_progress()
|
||||
self.assertTrue(context.get('complete'))
|
||||
self.assertFalse(context.get('error'))
|
||||
self.assertTrue(context.get("complete"))
|
||||
self.assertFalse(context.get("error"))
|
||||
# nb. this is missing b/c we already got all contents
|
||||
self.assertNotIn('stdout', context)
|
||||
self.assertEqual(self.request.session.pop_flash(), ['yay!'])
|
||||
self.assertNotIn("stdout", context)
|
||||
self.assertEqual(self.request.session.pop_flash(), ["yay!"])
|
||||
|
||||
# nb. switch to a different progress tracker
|
||||
self.request.session.id = 'mockid#3'
|
||||
self.request.session.id = "mockid#3"
|
||||
|
||||
# first call should get the full contents
|
||||
context = view.execute_progress()
|
||||
self.assertFalse(context.get('complete'))
|
||||
self.assertFalse(context.get('error'))
|
||||
self.assertEqual(context['stdout'], 'hello 001<br />hello 002<br />')
|
||||
self.assertFalse(context.get("complete"))
|
||||
self.assertFalse(context.get("error"))
|
||||
self.assertEqual(context["stdout"], "hello 001<br />hello 002<br />")
|
||||
|
||||
# mark progress error
|
||||
session = get_progress_session(self.request, 'upgrades.execute')
|
||||
session = get_progress_session(self.request, "upgrades.execute")
|
||||
session.load()
|
||||
session['error'] = True
|
||||
session['error_msg'] = 'omg!'
|
||||
session["error"] = True
|
||||
session["error_msg"] = "omg!"
|
||||
session.save()
|
||||
|
||||
# next call should reflect that
|
||||
self.assertEqual(self.request.session.pop_flash('error'), [])
|
||||
self.assertEqual(self.request.session.pop_flash("error"), [])
|
||||
context = view.execute_progress()
|
||||
self.assertFalse(context.get('complete'))
|
||||
self.assertTrue(context.get('error'))
|
||||
self.assertFalse(context.get("complete"))
|
||||
self.assertTrue(context.get("error"))
|
||||
# nb. this is missing b/c we already got all contents
|
||||
self.assertNotIn('stdout', context)
|
||||
self.assertEqual(self.request.session.pop_flash('error'), ['omg!'])
|
||||
self.assertNotIn("stdout", context)
|
||||
self.assertEqual(self.request.session.pop_flash("error"), ["omg!"])
|
||||
|
||||
def test_configure_get_simple_settings(self):
|
||||
# sanity/coverage check
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class TestUserView(WebTestCase):
|
|||
return mod.UserView(self.request)
|
||||
|
||||
def test_includeme(self):
|
||||
self.pyramid_config.include('wuttaweb.views.users')
|
||||
self.pyramid_config.include("wuttaweb.views.users")
|
||||
|
||||
def test_get_query(self):
|
||||
view = self.make_view()
|
||||
|
|
@ -28,35 +28,35 @@ class TestUserView(WebTestCase):
|
|||
model = self.app.model
|
||||
view = self.make_view()
|
||||
grid = view.make_grid(model_class=model.User)
|
||||
self.assertFalse(grid.is_linked('person'))
|
||||
self.assertFalse(grid.is_linked("person"))
|
||||
view.configure_grid(grid)
|
||||
self.assertTrue(grid.is_linked('person'))
|
||||
self.assertTrue(grid.is_linked("person"))
|
||||
|
||||
def test_grid_row_class(self):
|
||||
model = self.app.model
|
||||
user = model.User(username='barney', active=True)
|
||||
user = model.User(username="barney", active=True)
|
||||
data = dict(user)
|
||||
view = self.make_view()
|
||||
|
||||
self.assertIsNone(view.grid_row_class(user, data, 1))
|
||||
|
||||
user.active = False
|
||||
self.assertEqual(view.grid_row_class(user, data, 1), 'has-background-warning')
|
||||
self.assertEqual(view.grid_row_class(user, data, 1), "has-background-warning")
|
||||
|
||||
def test_is_editable(self):
|
||||
model = self.app.model
|
||||
view = self.make_view()
|
||||
|
||||
# active user is editable
|
||||
user = model.User(username='barney', active=True)
|
||||
user = model.User(username="barney", active=True)
|
||||
self.assertTrue(view.is_editable(user))
|
||||
|
||||
# inactive also editable
|
||||
user = model.User(username='barney', active=False)
|
||||
user = model.User(username="barney", active=False)
|
||||
self.assertTrue(view.is_editable(user))
|
||||
|
||||
# but not if prevent_edit flag is set
|
||||
user = model.User(username='barney', prevent_edit=True)
|
||||
user = model.User(username="barney", prevent_edit=True)
|
||||
self.assertFalse(view.is_editable(user))
|
||||
|
||||
# unless request user is root
|
||||
|
|
@ -65,95 +65,97 @@ class TestUserView(WebTestCase):
|
|||
|
||||
def test_configure_form(self):
|
||||
model = self.app.model
|
||||
person = model.Person(first_name='Barney', last_name='Rubble', full_name='Barney Rubble')
|
||||
barney = model.User(username='barney', person=person)
|
||||
person = model.Person(
|
||||
first_name="Barney", last_name="Rubble", full_name="Barney Rubble"
|
||||
)
|
||||
barney = model.User(username="barney", person=person)
|
||||
self.session.add(barney)
|
||||
self.session.commit()
|
||||
view = self.make_view()
|
||||
|
||||
# person replaced with first/last name when creating or editing
|
||||
with patch.object(view, 'viewing', new=True):
|
||||
with patch.object(view, "viewing", new=True):
|
||||
form = view.make_form(model_instance=barney)
|
||||
self.assertIn('person', form)
|
||||
self.assertNotIn('first_name', form)
|
||||
self.assertNotIn('last_name', form)
|
||||
self.assertIn("person", form)
|
||||
self.assertNotIn("first_name", form)
|
||||
self.assertNotIn("last_name", form)
|
||||
view.configure_form(form)
|
||||
self.assertIn('person', form)
|
||||
self.assertNotIn('first_name', form)
|
||||
self.assertNotIn('last_name', form)
|
||||
with patch.object(view, 'creating', new=True):
|
||||
self.assertIn("person", form)
|
||||
self.assertNotIn("first_name", form)
|
||||
self.assertNotIn("last_name", form)
|
||||
with patch.object(view, "creating", new=True):
|
||||
form = view.make_form(model_instance=barney)
|
||||
self.assertIn('person', form)
|
||||
self.assertNotIn('first_name', form)
|
||||
self.assertNotIn('last_name', form)
|
||||
self.assertIn("person", form)
|
||||
self.assertNotIn("first_name", form)
|
||||
self.assertNotIn("last_name", form)
|
||||
view.configure_form(form)
|
||||
self.assertNotIn('person', form)
|
||||
self.assertIn('first_name', form)
|
||||
self.assertIn('last_name', form)
|
||||
with patch.object(view, 'editing', new=True):
|
||||
self.assertNotIn("person", form)
|
||||
self.assertIn("first_name", form)
|
||||
self.assertIn("last_name", form)
|
||||
with patch.object(view, "editing", new=True):
|
||||
form = view.make_form(model_instance=barney)
|
||||
self.assertIn('person', form)
|
||||
self.assertNotIn('first_name', form)
|
||||
self.assertNotIn('last_name', form)
|
||||
self.assertIn("person", form)
|
||||
self.assertNotIn("first_name", form)
|
||||
self.assertNotIn("last_name", form)
|
||||
view.configure_form(form)
|
||||
self.assertNotIn('person', form)
|
||||
self.assertIn('first_name', form)
|
||||
self.assertIn('last_name', form)
|
||||
self.assertNotIn("person", form)
|
||||
self.assertIn("first_name", form)
|
||||
self.assertIn("last_name", form)
|
||||
|
||||
# first/last name have default values when editing
|
||||
with patch.object(view, 'editing', new=True):
|
||||
with patch.object(view, "editing", new=True):
|
||||
form = view.make_form(model_instance=barney)
|
||||
self.assertNotIn('first_name', form.defaults)
|
||||
self.assertNotIn('last_name', form.defaults)
|
||||
self.assertNotIn("first_name", form.defaults)
|
||||
self.assertNotIn("last_name", form.defaults)
|
||||
view.configure_form(form)
|
||||
self.assertIn('first_name', form.defaults)
|
||||
self.assertEqual(form.defaults['first_name'], 'Barney')
|
||||
self.assertIn('last_name', form.defaults)
|
||||
self.assertEqual(form.defaults['last_name'], 'Rubble')
|
||||
self.assertIn("first_name", form.defaults)
|
||||
self.assertEqual(form.defaults["first_name"], "Barney")
|
||||
self.assertIn("last_name", form.defaults)
|
||||
self.assertEqual(form.defaults["last_name"], "Rubble")
|
||||
|
||||
# password removed (always, for now)
|
||||
with patch.object(view, 'viewing', new=True):
|
||||
with patch.object(view, "viewing", new=True):
|
||||
form = view.make_form(model_instance=barney)
|
||||
self.assertIn('password', form)
|
||||
self.assertIn("password", form)
|
||||
view.configure_form(form)
|
||||
self.assertNotIn('password', form)
|
||||
with patch.object(view, 'editing', new=True):
|
||||
self.assertNotIn("password", form)
|
||||
with patch.object(view, "editing", new=True):
|
||||
form = view.make_form(model_instance=barney)
|
||||
self.assertIn('password', form)
|
||||
self.assertIn("password", form)
|
||||
view.configure_form(form)
|
||||
self.assertNotIn('password', form)
|
||||
self.assertNotIn("password", form)
|
||||
|
||||
# api tokens grid shown only if current user has perm
|
||||
with patch.object(view, 'viewing', new=True):
|
||||
with patch.object(view, "viewing", new=True):
|
||||
form = view.make_form(model_instance=barney)
|
||||
self.assertIn('api_tokens', form)
|
||||
self.assertIn("api_tokens", form)
|
||||
view.configure_form(form)
|
||||
self.assertNotIn('api_tokens', form)
|
||||
with patch.object(self.request, 'is_root', new=True):
|
||||
self.assertNotIn("api_tokens", form)
|
||||
with patch.object(self.request, "is_root", new=True):
|
||||
form = view.make_form(model_instance=barney)
|
||||
self.assertIn('api_tokens', form)
|
||||
self.assertIn("api_tokens", form)
|
||||
view.configure_form(form)
|
||||
self.assertIn('api_tokens', form)
|
||||
self.assertIn("api_tokens", form)
|
||||
|
||||
def test_unique_username(self):
|
||||
model = self.app.model
|
||||
view = self.make_view()
|
||||
|
||||
user = model.User(username='foo')
|
||||
user = model.User(username="foo")
|
||||
self.session.add(user)
|
||||
self.session.commit()
|
||||
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
with patch.object(view, "Session", return_value=self.session):
|
||||
|
||||
# invalid if same username in data
|
||||
node = colander.SchemaNode(colander.String(), name='username')
|
||||
self.assertRaises(colander.Invalid, view.unique_username, node, 'foo')
|
||||
node = colander.SchemaNode(colander.String(), name="username")
|
||||
self.assertRaises(colander.Invalid, view.unique_username, node, "foo")
|
||||
|
||||
# but not if username belongs to current user
|
||||
view.editing = True
|
||||
self.request.matchdict = {'uuid': user.uuid}
|
||||
node = colander.SchemaNode(colander.String(), name='username')
|
||||
self.assertIsNone(view.unique_username(node, 'foo'))
|
||||
self.request.matchdict = {"uuid": user.uuid}
|
||||
node = colander.SchemaNode(colander.String(), name="username")
|
||||
self.assertIsNone(view.unique_username(node, "foo"))
|
||||
|
||||
def test_objectify(self):
|
||||
model = self.app.model
|
||||
|
|
@ -164,14 +166,14 @@ class TestUserView(WebTestCase):
|
|||
self.session.add(blokes)
|
||||
others = model.Role(name="Others")
|
||||
self.session.add(others)
|
||||
barney = model.User(username='barney')
|
||||
auth.set_user_password(barney, 'testpass')
|
||||
barney = model.User(username="barney")
|
||||
auth.set_user_password(barney, "testpass")
|
||||
barney.roles.append(blokes)
|
||||
self.session.add(barney)
|
||||
self.session.commit()
|
||||
|
||||
with patch.object(self.request, 'matchdict', new={'uuid': barney.uuid}):
|
||||
with patch.object(view, 'editing', new=True):
|
||||
with patch.object(self.request, "matchdict", new={"uuid": barney.uuid}):
|
||||
with patch.object(view, "editing", new=True):
|
||||
|
||||
# sanity check, user is just in 'blokes' role
|
||||
self.session.refresh(barney)
|
||||
|
|
@ -179,18 +181,18 @@ class TestUserView(WebTestCase):
|
|||
self.assertEqual(barney.roles[0].name, "Blokes")
|
||||
|
||||
# form can update user password
|
||||
self.assertTrue(auth.check_user_password(barney, 'testpass'))
|
||||
self.assertTrue(auth.check_user_password(barney, "testpass"))
|
||||
form = view.make_model_form(model_instance=barney)
|
||||
form.validated = {'username': 'barney', 'set_password': 'testpass2'}
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
form.validated = {"username": "barney", "set_password": "testpass2"}
|
||||
with patch.object(view, "Session", return_value=self.session):
|
||||
user = view.objectify(form)
|
||||
self.assertIs(user, barney)
|
||||
self.assertTrue(auth.check_user_password(barney, 'testpass2'))
|
||||
self.assertTrue(auth.check_user_password(barney, "testpass2"))
|
||||
|
||||
# form can update user roles
|
||||
form = view.make_model_form(model_instance=barney)
|
||||
form.validated = {'username': 'barney', 'roles': {others.uuid}}
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
form.validated = {"username": "barney", "roles": {others.uuid}}
|
||||
with patch.object(view, "Session", return_value=self.session):
|
||||
user = view.objectify(form)
|
||||
self.assertIs(user, barney)
|
||||
self.assertEqual(len(user.roles), 1)
|
||||
|
|
@ -199,19 +201,27 @@ class TestUserView(WebTestCase):
|
|||
# person is auto-created
|
||||
self.assertIsNone(barney.person)
|
||||
form = view.make_model_form(model_instance=barney)
|
||||
form.validated = {'username': 'barney', 'first_name': 'Barney', 'last_name': 'Rubble'}
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
form.validated = {
|
||||
"username": "barney",
|
||||
"first_name": "Barney",
|
||||
"last_name": "Rubble",
|
||||
}
|
||||
with patch.object(view, "Session", return_value=self.session):
|
||||
user = view.objectify(form)
|
||||
self.assertIsNotNone(barney.person)
|
||||
self.assertEqual(barney.person.first_name, 'Barney')
|
||||
self.assertEqual(barney.person.last_name, 'Rubble')
|
||||
self.assertEqual(barney.person.full_name, 'Barney Rubble')
|
||||
self.assertEqual(barney.person.first_name, "Barney")
|
||||
self.assertEqual(barney.person.last_name, "Rubble")
|
||||
self.assertEqual(barney.person.full_name, "Barney Rubble")
|
||||
|
||||
# person is auto-removed
|
||||
self.assertIsNotNone(barney.person)
|
||||
form = view.make_model_form(model_instance=barney)
|
||||
form.validated = {'username': 'barney', 'first_name': '', 'last_name': ''}
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
form.validated = {
|
||||
"username": "barney",
|
||||
"first_name": "",
|
||||
"last_name": "",
|
||||
}
|
||||
with patch.object(view, "Session", return_value=self.session):
|
||||
user = view.objectify(form)
|
||||
self.assertIsNone(barney.person)
|
||||
|
||||
|
|
@ -219,37 +229,45 @@ class TestUserView(WebTestCase):
|
|||
barney.person = self.session.query(model.Person).one()
|
||||
|
||||
# person name is updated
|
||||
self.assertEqual(barney.person.first_name, 'Barney')
|
||||
self.assertEqual(barney.person.last_name, 'Rubble')
|
||||
self.assertEqual(barney.person.full_name, 'Barney Rubble')
|
||||
self.assertEqual(barney.person.first_name, "Barney")
|
||||
self.assertEqual(barney.person.last_name, "Rubble")
|
||||
self.assertEqual(barney.person.full_name, "Barney Rubble")
|
||||
form = view.make_model_form(model_instance=barney)
|
||||
form.validated = {'username': 'barney', 'first_name': 'Fred', 'last_name': 'Flintstone'}
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
form.validated = {
|
||||
"username": "barney",
|
||||
"first_name": "Fred",
|
||||
"last_name": "Flintstone",
|
||||
}
|
||||
with patch.object(view, "Session", return_value=self.session):
|
||||
user = view.objectify(form)
|
||||
self.assertIsNotNone(barney.person)
|
||||
self.assertEqual(barney.person.first_name, 'Fred')
|
||||
self.assertEqual(barney.person.last_name, 'Flintstone')
|
||||
self.assertEqual(barney.person.full_name, 'Fred Flintstone')
|
||||
self.assertEqual(barney.person.first_name, "Fred")
|
||||
self.assertEqual(barney.person.last_name, "Flintstone")
|
||||
self.assertEqual(barney.person.full_name, "Fred Flintstone")
|
||||
|
||||
with patch.object(view, 'creating', new=True):
|
||||
with patch.object(view, "creating", new=True):
|
||||
|
||||
# person is auto-created when making new user
|
||||
form = view.make_model_form()
|
||||
form.validated = {'username': 'betty', 'first_name': 'Betty', 'last_name': 'Boop'}
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
form.validated = {
|
||||
"username": "betty",
|
||||
"first_name": "Betty",
|
||||
"last_name": "Boop",
|
||||
}
|
||||
with patch.object(view, "Session", return_value=self.session):
|
||||
user = view.objectify(form)
|
||||
self.assertIsNotNone(user.person)
|
||||
self.assertEqual(user.person.first_name, 'Betty')
|
||||
self.assertEqual(user.person.last_name, 'Boop')
|
||||
self.assertEqual(user.person.full_name, 'Betty Boop')
|
||||
self.assertEqual(user.person.first_name, "Betty")
|
||||
self.assertEqual(user.person.last_name, "Boop")
|
||||
self.assertEqual(user.person.full_name, "Betty Boop")
|
||||
|
||||
# nb. keep ref to last user
|
||||
last_user = user
|
||||
|
||||
# person is *not* auto-created if no name provided
|
||||
form = view.make_model_form()
|
||||
form.validated = {'username': 'betty', 'first_name': '', 'last_name': ''}
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
form.validated = {"username": "betty", "first_name": "", "last_name": ""}
|
||||
with patch.object(view, "Session", return_value=self.session):
|
||||
user = view.objectify(form)
|
||||
self.assertIsNone(user.person)
|
||||
self.assertIsNot(user, last_user)
|
||||
|
|
@ -264,20 +282,20 @@ class TestUserView(WebTestCase):
|
|||
self.session.add(blokes)
|
||||
others = model.Role(name="Others")
|
||||
self.session.add(others)
|
||||
barney = model.User(username='barney')
|
||||
barney = model.User(username="barney")
|
||||
barney.roles.append(blokes)
|
||||
self.session.add(barney)
|
||||
self.session.commit()
|
||||
view = self.make_view()
|
||||
view.editing = True
|
||||
self.request.matchdict = {'uuid': barney.uuid}
|
||||
self.request.matchdict = {"uuid": barney.uuid}
|
||||
|
||||
# no error if data is missing roles
|
||||
form = view.make_model_form(model_instance=barney)
|
||||
form.validated = {'username': 'barneyx'}
|
||||
form.validated = {"username": "barneyx"}
|
||||
user = view.objectify(form)
|
||||
self.assertIs(user, barney)
|
||||
self.assertEqual(barney.username, 'barneyx')
|
||||
self.assertEqual(barney.username, "barneyx")
|
||||
|
||||
# sanity check, user is just in 'blokes' role
|
||||
self.session.refresh(barney)
|
||||
|
|
@ -289,9 +307,11 @@ class TestUserView(WebTestCase):
|
|||
# - authed / anon roles are not added
|
||||
# - admin role not added if current user is not root
|
||||
form = view.make_model_form(model_instance=barney)
|
||||
form.validated = {'username': 'barney',
|
||||
'roles': {admin.uuid, authed.uuid, anon.uuid, others.uuid}}
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
form.validated = {
|
||||
"username": "barney",
|
||||
"roles": {admin.uuid, authed.uuid, anon.uuid, others.uuid},
|
||||
}
|
||||
with patch.object(view, "Session", return_value=self.session):
|
||||
user = view.objectify(form)
|
||||
self.assertIs(user, barney)
|
||||
self.assertEqual(len(user.roles), 1)
|
||||
|
|
@ -302,9 +322,11 @@ class TestUserView(WebTestCase):
|
|||
# - admin role is added if current user is root
|
||||
self.request.is_root = True
|
||||
form = view.make_model_form(model_instance=barney)
|
||||
form.validated = {'username': 'barney',
|
||||
'roles': {admin.uuid, blokes.uuid, others.uuid}}
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
form.validated = {
|
||||
"username": "barney",
|
||||
"roles": {admin.uuid, blokes.uuid, others.uuid},
|
||||
}
|
||||
with patch.object(view, "Session", return_value=self.session):
|
||||
user = view.objectify(form)
|
||||
self.assertIs(user, barney)
|
||||
self.assertEqual(len(user.roles), 3)
|
||||
|
|
@ -314,9 +336,8 @@ class TestUserView(WebTestCase):
|
|||
# admin role not removed if current user is not root
|
||||
self.request.is_root = False
|
||||
form = view.make_model_form(model_instance=barney)
|
||||
form.validated = {'username': 'barney',
|
||||
'roles': {blokes.uuid, others.uuid}}
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
form.validated = {"username": "barney", "roles": {blokes.uuid, others.uuid}}
|
||||
with patch.object(view, "Session", return_value=self.session):
|
||||
user = view.objectify(form)
|
||||
self.assertIs(user, barney)
|
||||
self.assertEqual(len(user.roles), 3)
|
||||
|
|
@ -324,9 +345,8 @@ class TestUserView(WebTestCase):
|
|||
# admin role is removed if current user is root
|
||||
self.request.is_root = True
|
||||
form = view.make_model_form(model_instance=barney)
|
||||
form.validated = {'username': 'barney',
|
||||
'roles': {blokes.uuid, others.uuid}}
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
form.validated = {"username": "barney", "roles": {blokes.uuid, others.uuid}}
|
||||
with patch.object(view, "Session", return_value=self.session):
|
||||
user = view.objectify(form)
|
||||
self.assertIs(user, barney)
|
||||
self.assertEqual(len(user.roles), 2)
|
||||
|
|
@ -336,27 +356,27 @@ class TestUserView(WebTestCase):
|
|||
auth = self.app.get_auth_handler()
|
||||
view = self.make_view()
|
||||
|
||||
user = model.User(username='foo')
|
||||
user = model.User(username="foo")
|
||||
self.session.add(user)
|
||||
token = auth.add_api_token(user, 'test token')
|
||||
token = auth.add_api_token(user, "test token")
|
||||
self.session.commit()
|
||||
|
||||
normal = view.normalize_api_token(token)
|
||||
self.assertIn('uuid', normal)
|
||||
self.assertEqual(normal['uuid'], token.uuid.hex)
|
||||
self.assertIn('description', normal)
|
||||
self.assertEqual(normal['description'], 'test token')
|
||||
self.assertIn('created', normal)
|
||||
self.assertIn("uuid", normal)
|
||||
self.assertEqual(normal["uuid"], token.uuid.hex)
|
||||
self.assertIn("description", normal)
|
||||
self.assertEqual(normal["description"], "test token")
|
||||
self.assertIn("created", normal)
|
||||
|
||||
def test_make_api_tokens_grid(self):
|
||||
model = self.app.model
|
||||
auth = self.app.get_auth_handler()
|
||||
view = self.make_view()
|
||||
|
||||
user = model.User(username='foo')
|
||||
user = model.User(username="foo")
|
||||
self.session.add(user)
|
||||
token1 = auth.add_api_token(user, 'test1')
|
||||
token2 = auth.add_api_token(user, 'test2')
|
||||
token1 = auth.add_api_token(user, "test1")
|
||||
token2 = auth.add_api_token(user, "test2")
|
||||
self.session.commit()
|
||||
|
||||
# grid should have 2 records but no tools/actions
|
||||
|
|
@ -367,52 +387,60 @@ class TestUserView(WebTestCase):
|
|||
self.assertEqual(len(grid.actions), 0)
|
||||
|
||||
# create + delete allowed
|
||||
with patch.object(self.request, 'is_root', new=True):
|
||||
with patch.object(self.request, "is_root", new=True):
|
||||
grid = view.make_api_tokens_grid(user)
|
||||
self.assertEqual(len(grid.tools), 1)
|
||||
self.assertIn('create', grid.tools)
|
||||
self.assertIn("create", grid.tools)
|
||||
self.assertEqual(len(grid.actions), 1)
|
||||
self.assertEqual(grid.actions[0].key, 'delete')
|
||||
self.assertEqual(grid.actions[0].key, "delete")
|
||||
|
||||
def test_add_api_token(self):
|
||||
model = self.app.model
|
||||
view = self.make_view()
|
||||
|
||||
user = model.User(username='foo')
|
||||
user = model.User(username="foo")
|
||||
self.session.add(user)
|
||||
self.session.commit()
|
||||
self.session.refresh(user)
|
||||
self.assertEqual(len(user.api_tokens), 0)
|
||||
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
with patch.object(self.request, 'matchdict', new={'uuid': user.uuid}):
|
||||
with patch.object(self.request, 'json_body', create=True,
|
||||
new={'description': 'testing'}):
|
||||
with patch.object(view, "Session", return_value=self.session):
|
||||
with patch.object(self.request, "matchdict", new={"uuid": user.uuid}):
|
||||
with patch.object(
|
||||
self.request,
|
||||
"json_body",
|
||||
create=True,
|
||||
new={"description": "testing"},
|
||||
):
|
||||
result = view.add_api_token()
|
||||
self.assertEqual(len(user.api_tokens), 1)
|
||||
token = user.api_tokens[0]
|
||||
self.assertEqual(result['token_string'], token.token_string)
|
||||
self.assertEqual(result['description'], 'testing')
|
||||
self.assertEqual(result["token_string"], token.token_string)
|
||||
self.assertEqual(result["description"], "testing")
|
||||
|
||||
def test_delete_api_token(self):
|
||||
model = self.app.model
|
||||
auth = self.app.get_auth_handler()
|
||||
view = self.make_view()
|
||||
|
||||
user = model.User(username='foo')
|
||||
user = model.User(username="foo")
|
||||
self.session.add(user)
|
||||
token1 = auth.add_api_token(user, 'test1')
|
||||
token2 = auth.add_api_token(user, 'test2')
|
||||
token1 = auth.add_api_token(user, "test1")
|
||||
token2 = auth.add_api_token(user, "test2")
|
||||
self.session.commit()
|
||||
self.session.refresh(user)
|
||||
self.assertEqual(len(user.api_tokens), 2)
|
||||
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
with patch.object(self.request, 'matchdict', new={'uuid': user.uuid}):
|
||||
with patch.object(view, "Session", return_value=self.session):
|
||||
with patch.object(self.request, "matchdict", new={"uuid": user.uuid}):
|
||||
|
||||
# normal behavior
|
||||
with patch.object(self.request, 'json_body', create=True,
|
||||
new={'uuid': token1.uuid.hex}):
|
||||
with patch.object(
|
||||
self.request,
|
||||
"json_body",
|
||||
create=True,
|
||||
new={"uuid": token1.uuid.hex},
|
||||
):
|
||||
result = view.delete_api_token()
|
||||
self.assertEqual(result, {})
|
||||
self.session.refresh(user)
|
||||
|
|
@ -421,17 +449,25 @@ class TestUserView(WebTestCase):
|
|||
self.assertIs(token, token2)
|
||||
|
||||
# token for wrong user
|
||||
user2 = model.User(username='bar')
|
||||
user2 = model.User(username="bar")
|
||||
self.session.add(user2)
|
||||
token3 = auth.add_api_token(user2, 'test3')
|
||||
token3 = auth.add_api_token(user2, "test3")
|
||||
self.session.commit()
|
||||
with patch.object(self.request, 'json_body', create=True,
|
||||
new={'uuid': token3.uuid.hex}):
|
||||
with patch.object(
|
||||
self.request,
|
||||
"json_body",
|
||||
create=True,
|
||||
new={"uuid": token3.uuid.hex},
|
||||
):
|
||||
result = view.delete_api_token()
|
||||
self.assertEqual(result, {'error': "API token not found"})
|
||||
self.assertEqual(result, {"error": "API token not found"})
|
||||
|
||||
# token not found
|
||||
with patch.object(self.request, 'json_body', create=True,
|
||||
new={'uuid': self.app.make_true_uuid().hex}):
|
||||
with patch.object(
|
||||
self.request,
|
||||
"json_body",
|
||||
create=True,
|
||||
new={"uuid": self.app.make_true_uuid().hex},
|
||||
):
|
||||
result = view.delete_api_token()
|
||||
self.assertEqual(result, {'error': "API token not found"})
|
||||
self.assertEqual(result, {"error": "API token not found"})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue