Positions TUI #20
|
@ -32,7 +32,6 @@ class CeoFrame(Frame):
|
|||
self._extra_on_load = on_load
|
||||
self._model = model
|
||||
self._name = name
|
||||
self._loaded = False
|
||||
self._has_dynamic_layouts = has_dynamic_layouts
|
||||
self._quit_keys = [Screen.KEY_ESCAPE]
|
||||
if escape_on_q:
|
||||
|
@ -40,13 +39,19 @@ class CeoFrame(Frame):
|
|||
# sanity check
|
||||
if save_data:
|
||||
assert name in model.viewdata
|
||||
# child classes may override this as a last resort
|
||||
self.do_not_reload = False
|
||||
|
||||
def _ceoframe_on_load(self):
|
||||
# We usually don't want _on_load() to be called multiple times
|
||||
# e.g. when switching back to a scene, or after calling reset()
|
||||
if self._loaded:
|
||||
if self.do_not_reload:
|
||||
self.do_not_reload = False
|
||||
return
|
||||
self._loaded = True
|
||||
self.do_not_reload = True
|
||||
if self._has_dynamic_layouts:
|
||||
# We arrive here after a user pressed 'Back' then 'Next'.
|
||||
# The data may have changed, so we need to redraw everything,
|
||||
# via self._extra_on_load().
|
||||
self.clear_layouts()
|
||||
if self._model.title is not None:
|
||||
self.title = self._model.title
|
||||
self._model.title = None
|
||||
|
@ -76,7 +81,7 @@ class CeoFrame(Frame):
|
|||
"""
|
||||
# We want a fresh slate once we return to the home screen, so we
|
||||
# want on_load() to be called for the scenes.
|
||||
self._loaded = False
|
||||
self.do_not_reload = False
|
||||
if self._has_dynamic_layouts:
|
||||
# We don't want layouts to accumulate.
|
||||
self.clear_layouts()
|
||||
|
@ -157,8 +162,8 @@ class CeoFrame(Frame):
|
|||
self._model.screen.force_update()
|
||||
self._model.screen.draw_next_frame()
|
||||
|
||||
def clear_flash_message(self):
|
||||
self.flash_message('')
|
||||
def clear_flash_message(self, force_update: bool = False):
|
||||
self.flash_message('', force_update)
|
||||
|
||||
def process_event(self, event):
|
||||
if not isinstance(event, KeyboardEvent):
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
from copy import deepcopy
|
||||
|
||||
from zope import component
|
||||
|
||||
from ceo_common.interfaces import IConfig
|
||||
|
||||
|
||||
class Model:
|
||||
"""A convenient place to store View data persistently."""
|
||||
|
||||
def __init__(self):
|
||||
cfg = component.getUtility(IConfig)
|
||||
|
||||
self.screen = None
|
||||
self.views = []
|
||||
self.title = None
|
||||
|
@ -12,7 +18,9 @@ class Model:
|
|||
self.result_view_name = None
|
||||
self.txn_view_name = None
|
||||
self.error_message = None
|
||||
# view-specific data, to be used when e.g. resizing the window
|
||||
# View-specific data, to be used when e.g. resizing the window.
|
||||
# For the views where save_data=True was passed to the CeoFrame
|
||||
# constructor, the keys correspond to the names of text fields.
|
||||
self._initial_viewdata = {
|
||||
'AddUser': {
|
||||
'uid': '',
|
||||
|
@ -68,7 +76,11 @@ class Model:
|
|||
'ResetDatabasePassword': {
|
||||
'uid': '',
|
||||
},
|
||||
# This one needs to be filled in dynamically
|
||||
'SetPositions': {},
|
||||
}
|
||||
for pos in cfg.get('positions_available'):
|
||||
self._initial_viewdata[pos] = ''
|
||||
self.viewdata = deepcopy(self._initial_viewdata)
|
||||
# data which is shared between multiple views
|
||||
self.is_club_rep = False
|
||||
|
|
|
@ -97,7 +97,9 @@ class TransactionView(CeoFrame):
|
|||
def enable_next_btn(self):
|
||||
self._next_btn.disabled = False
|
||||
# If we don't reset, the button isn't selectable, even though we
|
||||
# enabled it
|
||||
# enabled it.
|
||||
# We don't want to reload, though (which reset() will trigger).
|
||||
self.do_not_reload = True
|
||||
self.reset()
|
||||
# save the fact that the transaction is completed
|
||||
self._model.viewdata['Transaction']['status'] = 'completed'
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
from asciimatics.widgets import Layout, Text, Button
|
||||
from threading import Thread
|
||||
|
||||
from asciimatics.widgets import Layout, Label
|
||||
from zope import component
|
||||
|
||||
from ...utils import http_get
|
||||
|
@ -21,47 +23,57 @@ position_names = {
|
|||
|
||||
|
||||
class GetPositionsView(CeoFrame):
|
||||
def __init__(self, screen, width, height, model, readonly=True):
|
||||
def __init__(self, screen, width, height, model):
|
||||
super().__init__(
|
||||
screen, height, width, model,
|
||||
'GetPositions',
|
||||
on_load = self._on_load)
|
||||
|
||||
layout = Layout([100], fill_frame=True)
|
||||
self.add_layout(layout)
|
||||
screen, height, width, model, 'GetPositions',
|
||||
escape_on_q=True,
|
||||
on_load=self._on_load)
|
||||
|
||||
cfg = component.getUtility(IConfig)
|
||||
avail = cfg.get('positions_available')
|
||||
required = cfg.get('positions_required')
|
||||
|
||||
self._positions = []
|
||||
layout = Layout([100])
|
||||
self.add_layout(layout)
|
||||
layout.add_widget(Label(''))
|
||||
|
||||
self._main_layout = Layout([10, 1, 10], fill_frame=True)
|
||||
self.add_layout(self._main_layout)
|
||||
|
||||
self._position_widgets = {}
|
||||
for pos in avail:
|
||||
widget = Text(
|
||||
f"{'*' if pos in required else ' '}{position_names.get(pos, pos)}:",
|
||||
pos,
|
||||
readonly=readonly,
|
||||
)
|
||||
self._positions.append(widget)
|
||||
layout.add_widget(widget)
|
||||
self._position_widgets[pos] = self._add_pair(position_names[pos], '')
|
||||
|
||||
self.add_flash_message_layout()
|
||||
self._add_buttons()
|
||||
self.add_buttons(back_btn=True)
|
||||
self.fix()
|
||||
|
||||
def _add_blank_line(self):
|
||||
self._main_layout.add_widget(Label(' ', 0))
|
||||
self._main_layout.add_widget(Label(' ', 2))
|
||||
|
||||
def _add_buttons(self):
|
||||
self.add_buttons(
|
||||
next_btn_text="Change",
|
||||
next_scene='SetPositions',
|
||||
back_btn=True,
|
||||
)
|
||||
|
||||
def _add_pair(self, key: str, val: str):
|
||||
key_widget = Label(key + ':', align='>')
|
||||
value_widget = Label(val, align='<')
|
||||
self._main_layout.add_widget(key_widget, 0)
|
||||
self._main_layout.add_widget(value_widget, 2)
|
||||
return value_widget
|
||||
|
||||
def _on_load(self):
|
||||
res = http_get('/api/positions')
|
||||
if res.status_code != 200:
|
||||
return
|
||||
def target():
|
||||
self.flash_message('Looking up positions...')
|
||||
try:
|
||||
resp = http_get('/api/positions')
|
||||
if not resp.ok:
|
||||
return
|
||||
positions = resp.json()
|
||||
for pos, username in positions.items():
|
||||
self._position_widgets[pos].text = username
|
||||
finally:
|
||||
self.clear_flash_message(force_update=True)
|
||||
Thread(target=target).start()
|
||||
|
||||
positions = res.json()
|
||||
for pos in self._positions:
|
||||
pos.value = positions.get(pos.name)
|
||||
def _ceoframe_on_reset(self):
|
||||
super()._ceoframe_on_reset()
|
||||
# clear the labels
|
||||
for widget in self._position_widgets.values():
|
||||
widget.text = ''
|
||||
|
|
|
@ -1,32 +1,51 @@
|
|||
from threading import Thread
|
||||
|
||||
from asciimatics.widgets import Layout, Label, Text
|
||||
from zope import component
|
||||
|
||||
from ...utils import defer, http_post
|
||||
from . import GetPositionsView
|
||||
from ceod.transactions.members.UpdateMemberPositionsTransaction import UpdateMemberPositionsTransaction as PositionsTransactions
|
||||
|
||||
class SetPositionsView(GetPositionsView):
|
||||
def __init__(self, screen, widgets, height, model):
|
||||
super().__init__(screen, widgets, height, model, False)
|
||||
from ..CeoFrame import CeoFrame
|
||||
from .GetPositionsView import position_names
|
||||
from ceo_common.interfaces import IConfig
|
||||
from ceod.transactions.members.UpdateMemberPositionsTransaction import UpdateMemberPositionsTransaction
|
||||
|
||||
|
||||
def _add_buttons(self):
|
||||
class SetPositionsView(CeoFrame):
|
||||
def __init__(self, screen, width, height, model):
|
||||
super().__init__(
|
||||
screen, height, width, model, 'SetPositions',
|
||||
save_data=True)
|
||||
cfg = component.getUtility(IConfig)
|
||||
avail = cfg.get('positions_available')
|
||||
required = cfg.get('positions_required')
|
||||
|
||||
layout = Layout([100], fill_frame=True)
|
||||
self.add_layout(layout)
|
||||
for pos in avail:
|
||||
suffix = ' (*)' if pos in required else ''
|
||||
widget = Text(position_names[pos] + suffix, pos)
|
||||
layout.add_widget(widget)
|
||||
|
||||
layout = Layout([100])
|
||||
self.add_layout(layout)
|
||||
layout.add_widget(Label('(*) Required'))
|
||||
|
||||
self.add_flash_message_layout()
|
||||
self.add_buttons(
|
||||
next_btn_text="Update",
|
||||
next_scene='Confirm',
|
||||
on_next=self._next,
|
||||
back_btn=True,
|
||||
)
|
||||
|
||||
next_scene='Confirm', on_next=self._next)
|
||||
self.fix()
|
||||
|
||||
def _next(self):
|
||||
positions = {}
|
||||
for pos in self._positions:
|
||||
positions[pos.name] = pos.value
|
||||
self.save()
|
||||
body = {pos: username for pos, username in self.data.items() if username}
|
||||
|
||||
self._model.deferred_req = defer(http_post, f'/api/positions', json=positions)
|
||||
self._model.operations = PositionsTransactions.operations
|
||||
self._model.deferred_req = defer(http_post, f'/api/positions', json=body)
|
||||
self._model.operations = UpdateMemberPositionsTransaction.operations
|
||||
self._model.confirm_lines = [
|
||||
"The positions will be updated as follows",
|
||||
"The positions will be updated as follows:",
|
||||
'',
|
||||
*positions.items(),
|
||||
*self.data.items(),
|
||||
'',
|
||||
'Are you sure you want to continue?',
|
||||
]
|
||||
|
|
|
@ -39,8 +39,8 @@ def screen_wrapper(screen, last_scene, model):
|
|||
global views
|
||||
# unload the old views
|
||||
for name, view in views:
|
||||
if hasattr(view, '_on_ceoframe_unload'):
|
||||
view._on_ceoframe_unload()
|
||||
if hasattr(view, '_ceoframe_on_unload'):
|
||||
view._ceoframe_on_unload()
|
||||
width = min(screen.width, 90)
|
||||
height = min(screen.height, 24)
|
||||
views = [
|
||||
|
|
Loading…
Reference in New Issue