add positions get/set widgets
continuous-integration/drone/pr Build is failing Details

This commit is contained in:
Max Erenberg 2021-09-25 23:49:10 -04:00
parent 103fdc64a9
commit faa5c73145
6 changed files with 112 additions and 62 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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?',
]

View File

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