no-resizing (#21)
continuous-integration/drone/push Build is passing Details

This PR disables resizing the TUI.
Unfortunately this is a regression from the old ceo. But trying to preserve state when destroying and creating new views in asciimatics proved to be very difficult.

Co-authored-by: Max Erenberg <>
Reviewed-on: #21
Co-authored-by: Max Erenberg <merenber@csclub.uwaterloo.ca>
Co-committed-by: Max Erenberg <merenber@csclub.uwaterloo.ca>
This commit is contained in:
Max Erenberg 2021-10-04 20:04:05 -04:00
parent 7edc01e42b
commit 3e1131c4e4
24 changed files with 141 additions and 385 deletions

View File

@ -125,7 +125,7 @@ cn: regular1
gidNumber: 20002
dn: uid=exec1,ou=People,dc=csclub,dc=internal
cn: Regular One
cn: Exec One
userPassword: {SASL}exec1@CSCLUB.INTERNAL
loginShell: /bin/bash
homeDirectory: /users/exec1

View File

@ -47,201 +47,6 @@ objectClass: top
uid: regular2
mail: regular2@uwaterloo.internal
dn: uid=exec1,ou=UWLDAP,dc=csclub,dc=internal
displayName: Exec One
givenName: Exec
sn: One
cn: Exec One
ou: MAT/Mathematics Computer Science
mailLocalAddress: exec1@uwaterloo.internal
objectClass: inetLocalMailRecipient
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
uid: exec1
mail: exec1@uwaterloo.internal
dn: uid=exec2,ou=UWLDAP,dc=csclub,dc=internal
displayName: Exec Two
givenName: Exec
sn: Two
cn: Exec Two
ou: MAT/Mathematics Computer Science
mailLocalAddress: exec2@uwaterloo.internal
objectClass: inetLocalMailRecipient
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
uid: exec2
mail: exec2@uwaterloo.internal
dn: uid=ctdalek,ou=UWLDAP,dc=csclub,dc=internal
displayName: Calum Dalek
givenName: Calum
sn: Dalek
cn: Calum Dalek
ou: MAT/Mathematics Computer Science
mailLocalAddress: ctdalek@uwaterloo.internal
objectClass: inetLocalMailRecipient
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
uid: ctdalek
mail: ctdalek@uwaterloo.internal
dn: uid=regular1,ou=UWLDAP,dc=csclub,dc=internal
displayName: Regular One
givenName: Regular
sn: One
cn: Regular One
ou: MAT/Mathematics Computer Science
mailLocalAddress: regular1@uwaterloo.internal
objectClass: inetLocalMailRecipient
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
uid: regular1
mail: regular1@uwaterloo.internal
dn: uid=regular2,ou=UWLDAP,dc=csclub,dc=internal
displayName: Regular Two
givenName: Regular
sn: Two
cn: Regular Two
ou: MAT/Mathematics Computer Science
mailLocalAddress: regular2@uwaterloo.internal
objectClass: inetLocalMailRecipient
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
uid: regular2
mail: regular2@uwaterloo.internal
dn: uid=exec1,ou=UWLDAP,dc=csclub,dc=internal
displayName: Exec One
givenName: Exec
sn: One
cn: Exec One
ou: MAT/Mathematics Computer Science
mailLocalAddress: exec1@uwaterloo.internal
objectClass: inetLocalMailRecipient
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
uid: exec1
mail: exec1@uwaterloo.internal
dn: uid=ctdalek,ou=UWLDAP,dc=csclub,dc=internal
displayName: Calum Dalek
givenName: Calum
sn: Dalek
cn: Calum Dalek
ou: MAT/Mathematics Computer Science
mailLocalAddress: ctdalek@uwaterloo.internal
objectClass: inetLocalMailRecipient
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
uid: ctdalek
mail: ctdalek@uwaterloo.internal
dn: uid=regular1,ou=UWLDAP,dc=csclub,dc=internal
displayName: Regular One
givenName: Regular
sn: One
cn: Regular One
ou: MAT/Mathematics Computer Science
mailLocalAddress: regular1@uwaterloo.internal
objectClass: inetLocalMailRecipient
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
uid: regular1
mail: regular1@uwaterloo.internal
dn: uid=regular2,ou=UWLDAP,dc=csclub,dc=internal
displayName: Regular Two
givenName: Regular
sn: Two
cn: Regular Two
ou: MAT/Mathematics Computer Science
mailLocalAddress: regular2@uwaterloo.internal
objectClass: inetLocalMailRecipient
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
uid: regular2
mail: regular2@uwaterloo.internal
dn: uid=exec1,ou=UWLDAP,dc=csclub,dc=internal
displayName: Exec One
givenName: Exec
sn: One
cn: Exec One
ou: MAT/Mathematics Computer Science
mailLocalAddress: exec1@uwaterloo.internal
objectClass: inetLocalMailRecipient
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
uid: exec1
mail: exec1@uwaterloo.internal
dn: uid=ctdalek,ou=UWLDAP,dc=csclub,dc=internal
displayName: Calum Dalek
givenName: Calum
sn: Dalek
cn: Calum Dalek
ou: MAT/Mathematics Computer Science
mailLocalAddress: ctdalek@uwaterloo.internal
objectClass: inetLocalMailRecipient
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
uid: ctdalek
mail: ctdalek@uwaterloo.internal
dn: uid=regular1,ou=UWLDAP,dc=csclub,dc=internal
displayName: Regular One
givenName: Regular
sn: One
cn: Regular One
ou: MAT/Mathematics Computer Science
mailLocalAddress: regular1@uwaterloo.internal
objectClass: inetLocalMailRecipient
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
uid: regular1
mail: regular1@uwaterloo.internal
dn: uid=regular2,ou=UWLDAP,dc=csclub,dc=internal
displayName: Regular Two
givenName: Regular
sn: Two
cn: Regular Two
ou: MAT/Mathematics Computer Science
mailLocalAddress: regular2@uwaterloo.internal
objectClass: inetLocalMailRecipient
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
uid: regular2
mail: regular2@uwaterloo.internal
dn: uid=regular3,ou=UWLDAP,dc=csclub,dc=internal
displayName: Regular Three
givenName: Regular

View File

@ -12,11 +12,9 @@ class CeoFrame(Frame):
height,
width,
model,
name, # key in model.viewdata
name,
on_load=None,
title=None,
save_data=False, # whether to save widget state for resizing
has_dynamic_layouts=False, # whether layouts are created on load
escape_on_q=False, # whether to quit when 'q' is pressed
):
super().__init__(
@ -28,70 +26,43 @@ class CeoFrame(Frame):
title=title,
on_load=self._ceoframe_on_load,
)
self._save_data = save_data
self._extra_on_load = on_load
self._custom_on_load = on_load
self._model = model
self._name = name
self._has_dynamic_layouts = has_dynamic_layouts
# If a view has a custom on_load function, all layouts should
# be created on load (*not* in the constructor)
self._has_dynamic_layouts = on_load is not None
self._quit_keys = [Screen.KEY_ESCAPE]
if escape_on_q:
self._quit_keys.append(ord('q'))
# sanity check
if save_data:
assert name in model.viewdata
# child classes may override this as a last resort
self.do_not_reload = False
self.skip_reload = False
def _ceoframe_on_load(self):
if self.do_not_reload:
self.do_not_reload = False
if self.skip_reload:
self.skip_reload = False
return
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
if self._save_data:
# restore the saved input fields' values
self.data = self._model.viewdata[self._name]
if self._extra_on_load is not None:
self._extra_on_load()
def _ceoframe_on_unload(self):
"""
This should be called just after the screen gets resized,
but before the new scenes are constructed.
The idea is to save the user's data in the input fields
so that we can restore them in the new scenes.
"""
if not self._save_data:
return
self.save()
self._model.viewdata[self._name] = self.data
if self._has_dynamic_layouts and self._model.nav_direction == 'forward':
# We arrive here after a user pressed 'Back' then 'Next',
# or after we returned to the Welcome screen.
# The data may have changed, so we need to redraw everything,
# via self._custom_on_load().
self.clear_layouts()
self._custom_on_load()
# may be overridden by child classes
def _ceoframe_on_reset(self):
"""
This needs to be called whenever we return to the home screen
This is called whenever we return to the home screen
after some kind of operation was completed.
Currently this is called from Model.reset().
This is called from Model.reset().
"""
# We want a fresh slate once we return to the home screen, so we
# want on_load() to be called for the scenes.
self.do_not_reload = False
if self._has_dynamic_layouts:
# We don't want layouts to accumulate.
self.clear_layouts()
pass
def clear_layouts(self):
# OK so this a *really* bad thing to do, since we're reaching
# into the private variables of a third-party library.
# Unfortunately asciimatics doesn't allow us to clear the layouts
# of an existing frame, and we need this to be able to re-use
# frames which create layouts dynamically.
self._layouts.clear()
def force_update(self):
@ -126,12 +97,14 @@ class CeoFrame(Frame):
layout.add_widget(Divider())
def _back():
self._model.nav_direction = 'backward'
last_scene = self._model.scene_stack.pop()
if last_scene == 'Welcome':
self._model.reset()
raise NextScene(last_scene)
def _next():
self._model.nav_direction = 'forward'
if on_next_excl is not None:
on_next_excl()
return

View File

@ -8,7 +8,6 @@ class ConfirmView(CeoFrame):
super().__init__(
screen, height, width, model, 'Confirm',
on_load=self._confirmview_on_load, title='Confirmation',
has_dynamic_layouts=True,
escape_on_q=True,
)
@ -46,6 +45,11 @@ class ConfirmView(CeoFrame):
kwargs['on_next_excl'] = self._next
self.add_buttons(**kwargs)
self.fix()
# OK so there's some weird bug somewhere which causes the buttons to be unselectable
# if we add a new user, return to the Welcome screen, then try to renew a user.
# This is a workaround for that.
self.skip_reload = True
self.reset()
def _next(self):
self.flash_message('Sending request...', force_update=True)

View File

@ -9,7 +9,6 @@ class ErrorView(CeoFrame):
super().__init__(
screen, height, width, model, 'Error',
on_load=self._errorview_on_load, title='Error',
has_dynamic_layouts=True,
)
def _errorview_on_load(self):

View File

@ -18,66 +18,12 @@ 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.
# For the views where save_data=True was passed to the CeoFrame
# constructor, the keys correspond to the names of text fields.
self.nav_direction = 'forward'
# View-specific data
self._initial_viewdata = {
'AddUser': {
'uid': '',
'cn': '',
'program': '',
'forwarding_address': '',
'num_terms': '1',
},
'RenewUser': {
'uid': '',
'num_terms': '1',
},
'Transaction': {
'op_layout': None,
'msg_layout': None,
'labels': {},
'status': 'not started',
},
'GetUser': {
'uid': '',
},
'ResetPassword': {
'uid': '',
},
'ChangeLoginShell': {
'uid': '',
'login_shell': '',
},
'SetForwardingAddresses': {
'uid': '',
'forwarding_addresses': [''],
},
'AddGroup': {
'cn': '',
'description': '',
},
'GetGroup': {
'cn': '',
},
'AddMemberToGroup': {
'cn': '',
'uid': '',
'subscribe': True,
},
'RemoveMemberFromGroup': {
'cn': '',
'uid': '',
'unsubscribe': True,
},
'CreateDatabase': {
'uid': '',
},
'ResetDatabasePassword': {
'uid': '',
},
# This one needs to be filled in dynamically
'SetPositions': {},
}
for pos in cfg.get('positions_available'):
self._initial_viewdata[pos] = ''

View File

@ -10,7 +10,6 @@ class ResultView(CeoFrame):
super().__init__(
screen, height, width, model, 'Result',
on_load=self._resultview_on_load, title='Result',
has_dynamic_layouts=True,
escape_on_q=True,
)

View File

@ -15,10 +15,9 @@ class TransactionView(CeoFrame):
super().__init__(
screen, height, width, model, 'Transaction',
on_load=self._txnview_on_load, title='Running Transaction',
has_dynamic_layouts=True,
)
# map operation names to label widgets
self._labels = model.viewdata['Transaction']['labels']
self._labels = {}
def _add_buttons(self):
layout = Layout([100])
@ -28,10 +27,6 @@ class TransactionView(CeoFrame):
layout = Layout([1, 1])
self.add_layout(layout)
self._next_btn = Button('Next', self._next)
# we don't want to disable the button if the txn completed
# and the user just resized the window
if self._model.viewdata['Transaction']['status'] != 'completed':
self._next_btn.disabled = True
layout.add_widget(self._next_btn, 1)
def _add_blank_line(self):
@ -39,47 +34,30 @@ class TransactionView(CeoFrame):
self._op_layout.add_widget(Label(''), 2)
def _txnview_on_load(self):
d = self._model.viewdata['Transaction']
if d['op_layout'] is None:
first_time = True
self._op_layout = Layout([12, 1, 10])
self.add_layout(self._op_layout)
# store the layouts so that we can re-use them when the screen
# gets resized
d['op_layout'] = self._op_layout
for _ in range(2):
self._add_blank_line()
for operation in self._model.operations:
desc = op_desc[operation]
self._op_layout.add_widget(Label(desc + '...', align='>'), 0)
desc_label = Label('', align='<')
self._op_layout.add_widget(desc_label, 2)
self._labels[operation] = desc_label
self._op_layout = Layout([12, 1, 10])
self.add_layout(self._op_layout)
# store the layouts so that we can re-use them when the screen
# gets resized
for _ in range(2):
self._add_blank_line()
# this is the where success/failure messages etc. get placed
self._msg_layout = Layout([100])
self.add_layout(self._msg_layout)
d['msg_layout'] = self._msg_layout
else:
# we arrive here when the screen has been resized
first_time = False
# restore the layouts which we saved
self._op_layout = d['op_layout']
self.add_layout(self._op_layout)
self._msg_layout = d['msg_layout']
self.add_layout(self._msg_layout)
for operation in self._model.operations:
desc = op_desc[operation]
self._op_layout.add_widget(Label(desc + '...', align='>'), 0)
desc_label = Label('', align='<')
self._op_layout.add_widget(desc_label, 2)
self._labels[operation] = desc_label
self._add_blank_line()
# this is the where success/failure messages etc. get placed
self._msg_layout = Layout([100])
self.add_layout(self._msg_layout)
# fill up the rest of the space
self.add_layout(Layout([100], fill_frame=True))
self._add_buttons()
self.fix()
# only send the API request the first time we arrive at this
# scene, not when the screen gets resized
if first_time:
Thread(target=self._do_txn).start()
Thread(target=self._do_txn).start()
def _do_txn(self):
self._model.viewdata['Transaction']['status'] = 'in progress'
resp = self._model.deferred_req()
handler = TUIStreamResponseHandler(
model=self._model,
@ -99,10 +77,8 @@ class TransactionView(CeoFrame):
# If we don't reset, the button isn't selectable, even though we
# enabled it.
# We don't want to reload, though (which reset() will trigger).
self.do_not_reload = True
self.skip_reload = True
self.reset()
# save the fact that the transaction is completed
self._model.viewdata['Transaction']['status'] = 'completed'
def _next(self):
self._model.reset()

View File

@ -8,7 +8,6 @@ class CreateDatabaseView(CeoFrame):
def __init__(self, screen, width, height, model):
super().__init__(
screen, height, width, model, 'CreateDatabase',
save_data=True,
)
layout = Layout([100], fill_frame=True)
self.add_layout(layout)
@ -19,6 +18,10 @@ class CreateDatabaseView(CeoFrame):
on_next=self._next)
self.fix()
def _ceoframe_on_reset(self):
super()._ceoframe_on_reset()
self._username.value = None
def _target(self):
username = self._username.value
db_type = self._model.db_type

View File

@ -8,7 +8,6 @@ class ResetDatabasePasswordView(CeoFrame):
def __init__(self, screen, width, height, model):
super().__init__(
screen, height, width, model, 'ResetDatabasePassword',
save_data=True,
)
layout = Layout([100], fill_frame=True)
self.add_layout(layout)
@ -19,6 +18,10 @@ class ResetDatabasePasswordView(CeoFrame):
on_next=self._next)
self.fix()
def _ceoframe_on_reset(self):
super()._ceoframe_on_reset()
self._username.value = None
def _target(self):
username = self._username.value
db_type = self._model.db_type

View File

@ -9,7 +9,6 @@ class AddGroupView(CeoFrame):
def __init__(self, screen, width, height, model):
super().__init__(
screen, height, width, model, 'AddGroup',
save_data=True,
)
layout = Layout([100], fill_frame=True)
self.add_layout(layout)
@ -23,6 +22,11 @@ class AddGroupView(CeoFrame):
next_scene='Confirm', on_next=self._next)
self.fix()
def _ceoframe_on_reset(self):
super()._ceoframe_on_reset()
self._cn.value = None
self._description.value = None
def _next(self):
cn = self._cn.value
description = self._description.value

View File

@ -9,7 +9,6 @@ class AddMemberToGroupView(CeoFrame):
def __init__(self, screen, width, height, model):
super().__init__(
screen, height, width, model, 'AddMemberToGroup',
save_data=True,
)
layout = Layout([100], fill_frame=True)
self.add_layout(layout)
@ -28,6 +27,12 @@ class AddMemberToGroupView(CeoFrame):
next_scene='Confirm', on_next=self._next)
self.fix()
def _ceoframe_on_reset(self):
super()._ceoframe_on_reset()
self._cn.value = None
self._username.value = None
self._checkbox.value = True
def _next(self):
cn = self._cn.value
uid = self._username.value

View File

@ -8,7 +8,6 @@ class GetGroupView(CeoFrame):
def __init__(self, screen, width, height, model):
super().__init__(
screen, height, width, model, 'GetGroup',
save_data=True,
)
layout = Layout([100], fill_frame=True)
self.add_layout(layout)
@ -21,9 +20,12 @@ class GetGroupView(CeoFrame):
next_scene='GetGroupResult', on_next=self._next)
self.fix()
def _ceoframe_on_reset(self):
super()._ceoframe_on_reset()
self._cn.value = None
def _next(self):
cn = self._cn.value
self._model.viewdata['GetGroup']['cn'] = cn
self.flash_message('Looking up group...', force_update=True)
try:
self._model.resp = http_get(f'/api/groups/{cn}')

View File

@ -9,7 +9,6 @@ class RemoveMemberFromGroupView(CeoFrame):
def __init__(self, screen, width, height, model):
super().__init__(
screen, height, width, model, 'RemoveMemberFromGroup',
save_data=True,
)
layout = Layout([100], fill_frame=True)
self.add_layout(layout)
@ -28,6 +27,11 @@ class RemoveMemberFromGroupView(CeoFrame):
next_scene='Confirm', on_next=self._next)
self.fix()
def _ceoframe_on_reset(self):
super()._ceoframe_on_reset()
self._cn.value = None
self._username.value = None
def _next(self):
cn = self._cn.value
uid = self._username.value

View File

@ -12,7 +12,6 @@ class AddUserView(CeoFrame):
def __init__(self, screen, width, height, model):
super().__init__(
screen, height, width, model, 'AddUser',
save_data=True,
)
self._username_changed = False
@ -33,6 +32,7 @@ class AddUserView(CeoFrame):
self._num_terms = Text(
"Number of terms:", "num_terms",
validator=lambda s: s.isdigit() and s[0] != '0')
self._num_terms.value = '1'
layout.add_widget(self._num_terms)
self.add_flash_message_layout()
@ -41,6 +41,14 @@ class AddUserView(CeoFrame):
next_scene='Confirm', on_next=self._next)
self.fix()
def _ceoframe_on_reset(self):
super()._ceoframe_on_reset()
self._username.value = None
self._full_name.value = None
self._program.value = None
self._forwarding_address.value = None
self._num_terms.value = '1'
def _on_username_change(self):
self._username_changed = True

View File

@ -10,7 +10,6 @@ class ChangeLoginShellView(CeoFrame):
def __init__(self, screen, width, height, model):
super().__init__(
screen, height, width, model, 'ChangeLoginShell',
save_data=True,
)
self._username_changed = False
@ -31,6 +30,11 @@ class ChangeLoginShellView(CeoFrame):
next_scene='Confirm', on_next=self._next)
self.fix()
def _ceoframe_on_reset(self):
super()._ceoframe_on_reset()
self._username.value = None
self._login_shell.value = None
# TODO: deduplicate this from AddUserView
def _on_username_change(self):
self._username_changed = True

View File

@ -8,7 +8,6 @@ class GetUserView(CeoFrame):
def __init__(self, screen, width, height, model):
super().__init__(
screen, height, width, model, 'GetUser',
save_data=True,
)
layout = Layout([100], fill_frame=True)
self.add_layout(layout)
@ -21,6 +20,10 @@ class GetUserView(CeoFrame):
next_scene='GetUserResult', on_next=self._next)
self.fix()
def _ceoframe_on_reset(self):
super()._ceoframe_on_reset()
self._username.value = None
def _next(self):
uid = self._username.value
self.flash_message('Looking up user...', force_update=True)

View File

@ -9,7 +9,6 @@ class RenewUserView(CeoFrame):
def __init__(self, screen, width, height, model):
super().__init__(
screen, height, width, model, 'RenewUser',
save_data=True,
)
self._model = model
@ -20,6 +19,7 @@ class RenewUserView(CeoFrame):
self._num_terms = Text(
"Number of terms:", "num_terms",
validator=lambda s: s.isdigit() and s[0] != '0')
self._num_terms.value = '1'
layout.add_widget(self._num_terms)
self.add_flash_message_layout()
@ -28,6 +28,11 @@ class RenewUserView(CeoFrame):
next_scene='Confirm', on_next=self._next)
self.fix()
def _ceoframe_on_reset(self):
super()._ceoframe_on_reset()
self._username.value = None
self._num_terms.value = '1'
def _next(self):
uid = self._username.value
self.flash_message('Looking up user...', force_update=True)

View File

@ -8,7 +8,6 @@ class ResetPasswordView(CeoFrame):
def __init__(self, screen, width, height, model):
super().__init__(
screen, height, width, model, 'ResetPassword',
save_data=True,
)
layout = Layout([100], fill_frame=True)
self.add_layout(layout)
@ -21,6 +20,10 @@ class ResetPasswordView(CeoFrame):
next_scene='Confirm', on_next=self._next)
self.fix()
def _ceoframe_on_reset(self):
super()._ceoframe_on_reset()
self._username.value = None
def _next(self):
uid = self._username.value
self._model.viewdata['ResetPassword']['uid'] = uid

View File

@ -10,7 +10,6 @@ class SetForwardingAddressesView(CeoFrame):
def __init__(self, screen, width, height, model):
super().__init__(
screen, height, width, model, 'SetForwardingAddresses',
save_data=True,
)
self._username_changed = False
@ -34,6 +33,11 @@ class SetForwardingAddressesView(CeoFrame):
next_scene='Confirm', on_next=self._next)
self.fix()
def _ceoframe_on_reset(self):
super()._ceoframe_on_reset()
self._username.value = None
self._forwarding_addresses.value = None
# TODO: deduplicate this from AddUserView
def _on_username_change(self):
self._username_changed = True

View File

@ -27,8 +27,11 @@ class GetPositionsView(CeoFrame):
super().__init__(
screen, height, width, model, 'GetPositions',
escape_on_q=True,
on_load=self._on_load)
on_load=self._on_load
)
self._position_widgets = {}
def _on_load(self):
cfg = component.getUtility(IConfig)
avail = cfg.get('positions_available')
@ -39,7 +42,6 @@ class GetPositionsView(CeoFrame):
self._main_layout = Layout([10, 1, 10], fill_frame=True)
self.add_layout(self._main_layout)
self._position_widgets = {}
for pos in avail:
self._position_widgets[pos] = self._add_pair(position_names[pos], '')
@ -47,18 +49,6 @@ class GetPositionsView(CeoFrame):
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_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):
def target():
self.flash_message('Looking up positions...')
try:
@ -72,6 +62,17 @@ class GetPositionsView(CeoFrame):
self.clear_flash_message(force_update=True)
Thread(target=target).start()
def _add_blank_line(self):
self._main_layout.add_widget(Label(' ', 0))
self._main_layout.add_widget(Label(' ', 2))
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 _ceoframe_on_reset(self):
super()._ceoframe_on_reset()
# clear the labels

View File

@ -12,16 +12,18 @@ 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)
self._widgets = []
for pos in avail:
suffix = ' (*)' if pos in required else ''
widget = Text(position_names[pos] + suffix, pos)
self._widgets.append(widget)
layout.add_widget(widget)
layout = Layout([100])
@ -34,6 +36,11 @@ class SetPositionsView(CeoFrame):
next_scene='Confirm', on_next=self._next)
self.fix()
def _ceoframe_on_reset(self):
super()._ceoframe_on_reset()
for widget in self._widgets:
widget.value = None
def _next(self):
self.save()
body = {pos: username for pos, username in self.data.items() if username}

View File

@ -1,3 +1,4 @@
import os
import sys
from asciimatics.exceptions import ResizeScreenError
@ -37,10 +38,6 @@ views = []
def screen_wrapper(screen, last_scene, model):
global views
# unload the old views
for name, view in views:
if hasattr(view, '_ceoframe_on_unload'):
view._ceoframe_on_unload()
width = min(screen.width, 90)
height = min(screen.height, 24)
views = [
@ -83,9 +80,10 @@ def screen_wrapper(screen, last_scene, model):
def main():
last_scene = None
model = Model()
while True:
try:
Screen.wrapper(screen_wrapper, arguments=[last_scene, model])
sys.exit(0)
except ResizeScreenError as e:
last_scene = e.scene
try:
Screen.wrapper(screen_wrapper, arguments=[last_scene, model])
sys.exit(0)
except ResizeScreenError:
os.system('reset')
print('Unfortunately, ceo does not currently support dynamic resizing.')
sys.exit(1)

View File

@ -146,7 +146,7 @@ def generic_handle_stream_response(
"""
if resp.status_code != 200:
handler.handle_non_200(resp)
return
return [{'status': 'error'}]
handler.begin()
idx = 0
data = []
@ -155,7 +155,7 @@ def generic_handle_stream_response(
data.append(d)
if d['status'] == 'aborted':
handler.handle_aborted(d['error'])
return
return data
elif d['status'] == 'completed':
while idx < len(operations):
handler.handle_skipped_operation()