From 3e1131c4e48d63f409d076be2f0dc3e9a6490397 Mon Sep 17 00:00:00 2001 From: Max Erenberg Date: Mon, 4 Oct 2021 20:04:05 -0400 Subject: [PATCH] no-resizing (#21) 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: https://git.csclub.uwaterloo.ca/public/pyceo/pulls/21 Co-authored-by: Max Erenberg Co-committed-by: Max Erenberg --- .drone/data.ldif | 2 +- .drone/uwldap_data.ldif | 195 ------------------ ceo/tui/CeoFrame.py | 69 ++----- ceo/tui/ConfirmView.py | 6 +- ceo/tui/ErrorView.py | 1 - ceo/tui/Model.py | 58 +----- ceo/tui/ResultView.py | 1 - ceo/tui/TransactionView.py | 60 ++---- ceo/tui/databases/CreateDatabaseView.py | 5 +- .../databases/ResetDatabasePasswordView.py | 5 +- ceo/tui/groups/AddGroupView.py | 6 +- ceo/tui/groups/AddMemberToGroupView.py | 7 +- ceo/tui/groups/GetGroupView.py | 6 +- ceo/tui/groups/RemoveMemberFromGroupView.py | 6 +- ceo/tui/members/AddUserView.py | 10 +- ceo/tui/members/ChangeLoginShellView.py | 6 +- ceo/tui/members/GetUserView.py | 5 +- ceo/tui/members/RenewUserView.py | 7 +- ceo/tui/members/ResetPasswordView.py | 5 +- ceo/tui/members/SetForwardingAddressesView.py | 6 +- ceo/tui/positions/GetPositionsView.py | 29 +-- ceo/tui/positions/SetPositionsView.py | 9 +- ceo/tui/start.py | 18 +- ceo/utils.py | 4 +- 24 files changed, 141 insertions(+), 385 deletions(-) diff --git a/.drone/data.ldif b/.drone/data.ldif index 48873b1..2863b81 100644 --- a/.drone/data.ldif +++ b/.drone/data.ldif @@ -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 diff --git a/.drone/uwldap_data.ldif b/.drone/uwldap_data.ldif index 89d2d66..b1045ac 100644 --- a/.drone/uwldap_data.ldif +++ b/.drone/uwldap_data.ldif @@ -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 diff --git a/ceo/tui/CeoFrame.py b/ceo/tui/CeoFrame.py index 5115e08..3a26dc8 100644 --- a/ceo/tui/CeoFrame.py +++ b/ceo/tui/CeoFrame.py @@ -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 diff --git a/ceo/tui/ConfirmView.py b/ceo/tui/ConfirmView.py index 9c01584..2914519 100644 --- a/ceo/tui/ConfirmView.py +++ b/ceo/tui/ConfirmView.py @@ -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) diff --git a/ceo/tui/ErrorView.py b/ceo/tui/ErrorView.py index 4fff146..2615ed5 100644 --- a/ceo/tui/ErrorView.py +++ b/ceo/tui/ErrorView.py @@ -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): diff --git a/ceo/tui/Model.py b/ceo/tui/Model.py index 989a5bb..a6ea571 100644 --- a/ceo/tui/Model.py +++ b/ceo/tui/Model.py @@ -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] = '' diff --git a/ceo/tui/ResultView.py b/ceo/tui/ResultView.py index f2b64a0..f08890b 100644 --- a/ceo/tui/ResultView.py +++ b/ceo/tui/ResultView.py @@ -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, ) diff --git a/ceo/tui/TransactionView.py b/ceo/tui/TransactionView.py index b2413c7..0cd2f61 100644 --- a/ceo/tui/TransactionView.py +++ b/ceo/tui/TransactionView.py @@ -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() diff --git a/ceo/tui/databases/CreateDatabaseView.py b/ceo/tui/databases/CreateDatabaseView.py index b8ff17f..f2e0ac0 100644 --- a/ceo/tui/databases/CreateDatabaseView.py +++ b/ceo/tui/databases/CreateDatabaseView.py @@ -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 diff --git a/ceo/tui/databases/ResetDatabasePasswordView.py b/ceo/tui/databases/ResetDatabasePasswordView.py index 8e5075c..6e31db0 100644 --- a/ceo/tui/databases/ResetDatabasePasswordView.py +++ b/ceo/tui/databases/ResetDatabasePasswordView.py @@ -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 diff --git a/ceo/tui/groups/AddGroupView.py b/ceo/tui/groups/AddGroupView.py index 4a56f13..de10ace 100644 --- a/ceo/tui/groups/AddGroupView.py +++ b/ceo/tui/groups/AddGroupView.py @@ -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 diff --git a/ceo/tui/groups/AddMemberToGroupView.py b/ceo/tui/groups/AddMemberToGroupView.py index 2a0ba0f..1e38197 100644 --- a/ceo/tui/groups/AddMemberToGroupView.py +++ b/ceo/tui/groups/AddMemberToGroupView.py @@ -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 diff --git a/ceo/tui/groups/GetGroupView.py b/ceo/tui/groups/GetGroupView.py index f7e0af4..f7501af 100644 --- a/ceo/tui/groups/GetGroupView.py +++ b/ceo/tui/groups/GetGroupView.py @@ -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}') diff --git a/ceo/tui/groups/RemoveMemberFromGroupView.py b/ceo/tui/groups/RemoveMemberFromGroupView.py index 3704948..1c7c2f7 100644 --- a/ceo/tui/groups/RemoveMemberFromGroupView.py +++ b/ceo/tui/groups/RemoveMemberFromGroupView.py @@ -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 diff --git a/ceo/tui/members/AddUserView.py b/ceo/tui/members/AddUserView.py index 63878b2..10e81ea 100644 --- a/ceo/tui/members/AddUserView.py +++ b/ceo/tui/members/AddUserView.py @@ -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 diff --git a/ceo/tui/members/ChangeLoginShellView.py b/ceo/tui/members/ChangeLoginShellView.py index 00f3187..44e242a 100644 --- a/ceo/tui/members/ChangeLoginShellView.py +++ b/ceo/tui/members/ChangeLoginShellView.py @@ -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 diff --git a/ceo/tui/members/GetUserView.py b/ceo/tui/members/GetUserView.py index cabbfe5..3ae8bd1 100644 --- a/ceo/tui/members/GetUserView.py +++ b/ceo/tui/members/GetUserView.py @@ -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) diff --git a/ceo/tui/members/RenewUserView.py b/ceo/tui/members/RenewUserView.py index 86e722a..fdd8d8c 100644 --- a/ceo/tui/members/RenewUserView.py +++ b/ceo/tui/members/RenewUserView.py @@ -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) diff --git a/ceo/tui/members/ResetPasswordView.py b/ceo/tui/members/ResetPasswordView.py index b922799..19e060b 100644 --- a/ceo/tui/members/ResetPasswordView.py +++ b/ceo/tui/members/ResetPasswordView.py @@ -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 diff --git a/ceo/tui/members/SetForwardingAddressesView.py b/ceo/tui/members/SetForwardingAddressesView.py index aff8c85..ae92b14 100644 --- a/ceo/tui/members/SetForwardingAddressesView.py +++ b/ceo/tui/members/SetForwardingAddressesView.py @@ -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 diff --git a/ceo/tui/positions/GetPositionsView.py b/ceo/tui/positions/GetPositionsView.py index 600ff1c..5d5383d 100644 --- a/ceo/tui/positions/GetPositionsView.py +++ b/ceo/tui/positions/GetPositionsView.py @@ -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 diff --git a/ceo/tui/positions/SetPositionsView.py b/ceo/tui/positions/SetPositionsView.py index bf23cbb..3de62d6 100644 --- a/ceo/tui/positions/SetPositionsView.py +++ b/ceo/tui/positions/SetPositionsView.py @@ -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} diff --git a/ceo/tui/start.py b/ceo/tui/start.py index 1bc086b..a5aaea5 100644 --- a/ceo/tui/start.py +++ b/ceo/tui/start.py @@ -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) diff --git a/ceo/utils.py b/ceo/utils.py index bb47669..cec156e 100644 --- a/ceo/utils.py +++ b/ceo/utils.py @@ -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()