Compare commits
6 commits
3f502fdc73
...
9b5b7ad9a7
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b5b7ad9a7 | |||
| 6fd4e15ca2 | |||
| 90784cecb2 | |||
| fa2a90c9cc | |||
| cbfa7f652f | |||
| a17d7da74c |
7 changed files with 142 additions and 86 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# wuttaweb -- Web App for Wutta Framework
|
# wuttaweb -- Web App for Wutta Framework
|
||||||
# Copyright © 2024-2025 Lance Edgar
|
# Copyright © 2024-2026 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Wutta Framework.
|
# This file is part of Wutta Framework.
|
||||||
#
|
#
|
||||||
|
|
@ -30,6 +30,12 @@ import logging
|
||||||
import warnings
|
import warnings
|
||||||
from collections import namedtuple, OrderedDict
|
from collections import namedtuple, OrderedDict
|
||||||
|
|
||||||
|
try:
|
||||||
|
from enum import EnumType
|
||||||
|
except ImportError: # pragma: no cover
|
||||||
|
# nb. python < 3.11
|
||||||
|
from enum import EnumMeta as EnumType
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import orm
|
||||||
|
|
||||||
|
|
@ -763,7 +769,7 @@ class Grid: # pylint: disable=too-many-instance-attributes,too-many-public-meth
|
||||||
|
|
||||||
:param key: Name of column.
|
:param key: Name of column.
|
||||||
|
|
||||||
:param enum: Instance of :class:`python:enum.Enum`.
|
:param enum: Instance of :class:`python:enum.Enum`, or a dict.
|
||||||
"""
|
"""
|
||||||
self.enums[key] = enum
|
self.enums[key] = enum
|
||||||
self.set_renderer(key, self.render_enum, enum=enum)
|
self.set_renderer(key, self.render_enum, enum=enum)
|
||||||
|
|
@ -2050,7 +2056,7 @@ class Grid: # pylint: disable=too-many-instance-attributes,too-many-public-meth
|
||||||
See also :meth:`set_enum()`.
|
See also :meth:`set_enum()`.
|
||||||
|
|
||||||
:param enum: Enum class for the field. This should be an
|
:param enum: Enum class for the field. This should be an
|
||||||
instance of :class:`~python:enum.Enum`.
|
instance of :class:`~python:enum.Enum` or else a dict.
|
||||||
|
|
||||||
To use this feature for your grid::
|
To use this feature for your grid::
|
||||||
|
|
||||||
|
|
@ -2061,12 +2067,26 @@ class Grid: # pylint: disable=too-many-instance-attributes,too-many-public-meth
|
||||||
TWO = 2
|
TWO = 2
|
||||||
THREE = 3
|
THREE = 3
|
||||||
|
|
||||||
grid.set_enum('my_enum_field', MyEnum)
|
grid.set_enum("my_enum_field", MyEnum)
|
||||||
|
|
||||||
|
Or, perhaps more common::
|
||||||
|
|
||||||
|
myenum = {
|
||||||
|
1: "ONE",
|
||||||
|
2: "TWO",
|
||||||
|
3: "THREE",
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.set_enum("my_enum_field", myenum)
|
||||||
"""
|
"""
|
||||||
if enum:
|
if enum:
|
||||||
raw_value = obj[key]
|
|
||||||
if raw_value:
|
if isinstance(enum, EnumType):
|
||||||
return raw_value.value
|
if raw_value := obj[key]:
|
||||||
|
return raw_value.value
|
||||||
|
|
||||||
|
if isinstance(enum, dict):
|
||||||
|
return enum.get(value, value)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,86 +7,88 @@
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="tool_panel_execution()">
|
<%def name="tool_panel_execution()">
|
||||||
<wutta-tool-panel heading="Execution">
|
% if master.executable:
|
||||||
% if batch.executed:
|
<wutta-tool-panel heading="Execution">
|
||||||
<b-notification :closable="false">
|
% if batch.executed:
|
||||||
<p class="block">
|
<b-notification :closable="false">
|
||||||
Batch was executed<br />
|
|
||||||
${app.render_time_ago(batch.executed)}<br />
|
|
||||||
by ${batch.executed_by}
|
|
||||||
</p>
|
|
||||||
</b-notification>
|
|
||||||
% elif why_not_execute:
|
|
||||||
<b-notification type="is-warning" :closable="false">
|
|
||||||
<p class="block">
|
|
||||||
Batch cannot be executed:
|
|
||||||
</p>
|
|
||||||
<p class="block">
|
|
||||||
${why_not_execute}
|
|
||||||
</p>
|
|
||||||
</b-notification>
|
|
||||||
% else:
|
|
||||||
% if master.has_perm('execute'):
|
|
||||||
<b-notification type="is-success" :closable="false">
|
|
||||||
<p class="block">
|
<p class="block">
|
||||||
Batch can be executed
|
Batch was executed<br />
|
||||||
|
${app.render_time_ago(batch.executed)}<br />
|
||||||
|
by ${batch.executed_by}
|
||||||
</p>
|
</p>
|
||||||
<b-button type="is-primary"
|
|
||||||
@click="executeInit()"
|
|
||||||
icon-pack="fas"
|
|
||||||
icon-left="arrow-circle-right">
|
|
||||||
Execute Batch
|
|
||||||
</b-button>
|
|
||||||
|
|
||||||
<b-modal has-modal-card
|
|
||||||
:active.sync="executeShowDialog">
|
|
||||||
<div class="modal-card">
|
|
||||||
|
|
||||||
<header class="modal-card-head">
|
|
||||||
<p class="modal-card-title">Execute ${model_title}</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
## TODO: forcing black text b/c of b-notification
|
|
||||||
## wrapping button, which has white text
|
|
||||||
<section class="modal-card-body has-text-black">
|
|
||||||
<p class="block has-text-weight-bold">
|
|
||||||
What will happen when this batch is executed?
|
|
||||||
</p>
|
|
||||||
<div class="content">
|
|
||||||
${execution_described|n}
|
|
||||||
</div>
|
|
||||||
${h.form(master.get_action_url('execute', batch), ref='executeForm')}
|
|
||||||
${h.csrf_token(request)}
|
|
||||||
${h.end_form()}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<footer class="modal-card-foot">
|
|
||||||
<b-button @click="executeShowDialog = false">
|
|
||||||
Cancel
|
|
||||||
</b-button>
|
|
||||||
<b-button type="is-primary"
|
|
||||||
@click="executeSubmit()"
|
|
||||||
icon-pack="fas"
|
|
||||||
icon-left="arrow-circle-right"
|
|
||||||
:disabled="executeSubmitting">
|
|
||||||
{{ executeSubmitting ? "Working, please wait..." : "Execute Batch" }}
|
|
||||||
</b-button>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</b-modal>
|
|
||||||
</b-notification>
|
</b-notification>
|
||||||
|
% elif why_not_execute:
|
||||||
% else:
|
|
||||||
<b-notification type="is-warning" :closable="false">
|
<b-notification type="is-warning" :closable="false">
|
||||||
<p class="block">
|
<p class="block">
|
||||||
Batch may be executed,<br />
|
Batch cannot be executed:
|
||||||
but you do not have permission.
|
</p>
|
||||||
|
<p class="block">
|
||||||
|
${why_not_execute}
|
||||||
</p>
|
</p>
|
||||||
</b-notification>
|
</b-notification>
|
||||||
|
% else:
|
||||||
|
% if master.has_perm('execute'):
|
||||||
|
<b-notification type="is-success" :closable="false">
|
||||||
|
<p class="block">
|
||||||
|
Batch can be executed
|
||||||
|
</p>
|
||||||
|
<b-button type="is-primary"
|
||||||
|
@click="executeInit()"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="arrow-circle-right">
|
||||||
|
Execute Batch
|
||||||
|
</b-button>
|
||||||
|
|
||||||
|
<b-modal has-modal-card
|
||||||
|
:active.sync="executeShowDialog">
|
||||||
|
<div class="modal-card">
|
||||||
|
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<p class="modal-card-title">Execute ${model_title}</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
## TODO: forcing black text b/c of b-notification
|
||||||
|
## wrapping button, which has white text
|
||||||
|
<section class="modal-card-body has-text-black">
|
||||||
|
<p class="block has-text-weight-bold">
|
||||||
|
What will happen when this batch is executed?
|
||||||
|
</p>
|
||||||
|
<div class="content">
|
||||||
|
${execution_described|n}
|
||||||
|
</div>
|
||||||
|
${h.form(master.get_action_url('execute', batch), ref='executeForm')}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
${h.end_form()}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer class="modal-card-foot">
|
||||||
|
<b-button @click="executeShowDialog = false">
|
||||||
|
Cancel
|
||||||
|
</b-button>
|
||||||
|
<b-button type="is-primary"
|
||||||
|
@click="executeSubmit()"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="arrow-circle-right"
|
||||||
|
:disabled="executeSubmitting">
|
||||||
|
{{ executeSubmitting ? "Working, please wait..." : "Execute Batch" }}
|
||||||
|
</b-button>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</b-modal>
|
||||||
|
</b-notification>
|
||||||
|
|
||||||
|
% else:
|
||||||
|
<b-notification type="is-warning" :closable="false">
|
||||||
|
<p class="block">
|
||||||
|
Batch may be executed,<br />
|
||||||
|
but you do not have permission.
|
||||||
|
</p>
|
||||||
|
</b-notification>
|
||||||
|
% endif
|
||||||
% endif
|
% endif
|
||||||
% endif
|
</wutta-tool-panel>
|
||||||
</wutta-tool-panel>
|
% endif
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="modify_vue_vars()">
|
<%def name="modify_vue_vars()">
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# wuttaweb -- Web App for Wutta Framework
|
# wuttaweb -- Web App for Wutta Framework
|
||||||
# Copyright © 2024-2025 Lance Edgar
|
# Copyright © 2024-2026 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Wutta Framework.
|
# This file is part of Wutta Framework.
|
||||||
#
|
#
|
||||||
|
|
@ -32,7 +32,7 @@ import markdown
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import orm
|
||||||
|
|
||||||
from wuttaweb.views import MasterView
|
from wuttaweb.views import MasterView
|
||||||
from wuttaweb.forms.schema import UserRef
|
from wuttaweb.forms.schema import UserRef, WuttaDictEnum
|
||||||
from wuttaweb.forms.widgets import BatchIdWidget
|
from wuttaweb.forms.widgets import BatchIdWidget
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -147,6 +147,9 @@ class BatchMasterView(MasterView):
|
||||||
# description
|
# description
|
||||||
g.set_link("description")
|
g.set_link("description")
|
||||||
|
|
||||||
|
# status_code
|
||||||
|
g.set_enum("status_code", self.model_class.STATUS)
|
||||||
|
|
||||||
def render_batch_id( # pylint: disable=empty-docstring,unused-argument
|
def render_batch_id( # pylint: disable=empty-docstring,unused-argument
|
||||||
self, batch, key, value
|
self, batch, key, value
|
||||||
):
|
):
|
||||||
|
|
@ -191,6 +194,7 @@ class BatchMasterView(MasterView):
|
||||||
if self.creating:
|
if self.creating:
|
||||||
f.remove("status_code")
|
f.remove("status_code")
|
||||||
else:
|
else:
|
||||||
|
f.set_node("status_code", WuttaDictEnum(self.request, batch.STATUS))
|
||||||
f.set_readonly("status_code")
|
f.set_readonly("status_code")
|
||||||
|
|
||||||
# created
|
# created
|
||||||
|
|
@ -441,7 +445,7 @@ class BatchMasterView(MasterView):
|
||||||
g.set_renderer("status_code", self.render_row_status)
|
g.set_renderer("status_code", self.render_row_status)
|
||||||
|
|
||||||
# tool button - create row
|
# tool button - create row
|
||||||
if not batch.executed and self.has_perm("create_row"):
|
if self.rows_creatable and not batch.executed and self.has_perm("create_row"):
|
||||||
button = self.make_button(
|
button = self.make_button(
|
||||||
f"New {self.get_row_model_title()}",
|
f"New {self.get_row_model_title()}",
|
||||||
primary=True,
|
primary=True,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# wuttaweb -- Web App for Wutta Framework
|
# wuttaweb -- Web App for Wutta Framework
|
||||||
# Copyright © 2024-2025 Lance Edgar
|
# Copyright © 2024-2026 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Wutta Framework.
|
# This file is part of Wutta Framework.
|
||||||
#
|
#
|
||||||
|
|
@ -3514,6 +3514,11 @@ class MasterView(View): # pylint: disable=too-many-public-methods
|
||||||
if hasattr(cls, "model_title"):
|
if hasattr(cls, "model_title"):
|
||||||
return cls.model_title
|
return cls.model_title
|
||||||
|
|
||||||
|
if model_class := cls.get_model_class():
|
||||||
|
if hasattr(model_class, "__wutta_hint__"):
|
||||||
|
if model_title := model_class.__wutta_hint__.get("model_title"):
|
||||||
|
return model_title
|
||||||
|
|
||||||
return cls.get_model_name()
|
return cls.get_model_name()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -3532,6 +3537,13 @@ class MasterView(View): # pylint: disable=too-many-public-methods
|
||||||
if hasattr(cls, "model_title_plural"):
|
if hasattr(cls, "model_title_plural"):
|
||||||
return cls.model_title_plural
|
return cls.model_title_plural
|
||||||
|
|
||||||
|
if model_class := cls.get_model_class():
|
||||||
|
if hasattr(model_class, "__wutta_hint__"):
|
||||||
|
if model_title_plural := model_class.__wutta_hint__.get(
|
||||||
|
"model_title_plural"
|
||||||
|
):
|
||||||
|
return model_title_plural
|
||||||
|
|
||||||
model_title = cls.get_model_title()
|
model_title = cls.get_model_title()
|
||||||
return f"{model_title}s"
|
return f"{model_title}s"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,9 @@ class PersonView(MasterView): # pylint: disable=abstract-method
|
||||||
f.remove("full_name")
|
f.remove("full_name")
|
||||||
|
|
||||||
# users
|
# users
|
||||||
if self.viewing:
|
if self.creating or self.editing:
|
||||||
|
f.remove("users")
|
||||||
|
elif 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):
|
def make_users_grid(self, person):
|
||||||
|
|
|
||||||
|
|
@ -1602,15 +1602,30 @@ class TestGrid(WebTestCase):
|
||||||
grid = self.make_grid(columns=["foo", "bar"])
|
grid = self.make_grid(columns=["foo", "bar"])
|
||||||
obj = {"status": None}
|
obj = {"status": None}
|
||||||
|
|
||||||
# null
|
# true enum, null
|
||||||
value = grid.render_enum(obj, "status", None, enum=enum.UpgradeStatus)
|
value = grid.render_enum(obj, "status", None, enum=enum.UpgradeStatus)
|
||||||
self.assertIsNone(value)
|
self.assertIsNone(value)
|
||||||
|
|
||||||
# normal
|
# true enum, normal value
|
||||||
obj["status"] = enum.UpgradeStatus.SUCCESS
|
obj["status"] = enum.UpgradeStatus.SUCCESS
|
||||||
value = grid.render_enum(obj, "status", "SUCCESS", enum=enum.UpgradeStatus)
|
value = grid.render_enum(obj, "status", "SUCCESS", enum=enum.UpgradeStatus)
|
||||||
self.assertEqual(value, "success")
|
self.assertEqual(value, "success")
|
||||||
|
|
||||||
|
# dict enum
|
||||||
|
statuses = {
|
||||||
|
enum.UpgradeStatus.SUCCESS.name: "success",
|
||||||
|
enum.UpgradeStatus.FAILURE.name: "failure",
|
||||||
|
}
|
||||||
|
|
||||||
|
# dict enum, null
|
||||||
|
value = grid.render_enum(obj, "status", None, enum=statuses)
|
||||||
|
self.assertIsNone(value)
|
||||||
|
|
||||||
|
# true enum, normal value
|
||||||
|
obj["status"] = enum.UpgradeStatus.SUCCESS.value
|
||||||
|
value = grid.render_enum(obj, "status", "SUCCESS", enum=statuses)
|
||||||
|
self.assertEqual(value, "success")
|
||||||
|
|
||||||
def test_render_percent(self):
|
def test_render_percent(self):
|
||||||
grid = self.make_grid(columns=["foo", "bar"])
|
grid = self.make_grid(columns=["foo", "bar"])
|
||||||
obj = MagicMock()
|
obj = MagicMock()
|
||||||
|
|
|
||||||
|
|
@ -442,6 +442,7 @@ class TestBatchMasterView(WebTestCase):
|
||||||
mod.BatchMasterView,
|
mod.BatchMasterView,
|
||||||
model_class=MockBatch,
|
model_class=MockBatch,
|
||||||
route_prefix="mock_batches",
|
route_prefix="mock_batches",
|
||||||
|
rows_creatable=True,
|
||||||
create=True,
|
create=True,
|
||||||
):
|
):
|
||||||
with patch.object(
|
with patch.object(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue