3
0
Fork 0

Compare commits

...

6 commits

7 changed files with 142 additions and 86 deletions

View file

@ -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

View file

@ -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()">

View file

@ -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,

View file

@ -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"

View file

@ -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):

View file

@ -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()

View file

@ -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(