diff --git a/securedrop_client/db.py b/securedrop_client/db.py index 72fc8e9cf..2148080f8 100644 --- a/securedrop_client/db.py +++ b/securedrop_client/db.py @@ -108,7 +108,8 @@ class Message(Base): source_id = Column(Integer, ForeignKey('sources.id'), nullable=False) source = relationship("Source", backref=backref("messages", order_by=id, - cascade="delete")) + cascade="delete"), + lazy="joined") def __init__(self, **kwargs: Any) -> None: if 'file_counter' in kwargs: @@ -174,7 +175,8 @@ class File(Base): source_id = Column(Integer, ForeignKey('sources.id'), nullable=False) source = relationship("Source", backref=backref("files", order_by=id, - cascade="delete")) + cascade="delete"), + lazy="joined") def __init__(self, **kwargs: Any) -> None: if 'file_counter' in kwargs: @@ -221,7 +223,8 @@ class Reply(Base): source_id = Column(Integer, ForeignKey('sources.id'), nullable=False) source = relationship("Source", backref=backref("replies", order_by=id, - cascade="delete")) + cascade="delete"), + lazy="joined") journalist_id = Column(Integer, ForeignKey('users.id')) journalist = relationship( @@ -291,7 +294,8 @@ class DraftReply(Base): source_id = Column(Integer, ForeignKey('sources.id'), nullable=False) source = relationship("Source", backref=backref("draftreplies", order_by=id, - cascade="delete")) + cascade="delete"), + lazy="joined") journalist_id = Column(Integer, ForeignKey('users.id')) journalist = relationship( "User", backref=backref('draftreplies', order_by=id)) diff --git a/securedrop_client/gui/main.py b/securedrop_client/gui/main.py index 67806b552..7df5d378b 100644 --- a/securedrop_client/gui/main.py +++ b/securedrop_client/gui/main.py @@ -142,6 +142,12 @@ def hide_login(self): self.login_dialog.accept() self.login_dialog = None + def refresh_current_source_conversation(self): + """ + Update the current conversation if the source collection has changed. + """ + self.main_view.on_source_changed() + def show_sources(self, sources: List[Source]): """ Update the left hand sources list in the UI with the passed in list of diff --git a/securedrop_client/gui/widgets.py b/securedrop_client/gui/widgets.py index 6d8f52981..b0111f16e 100644 --- a/securedrop_client/gui/widgets.py +++ b/securedrop_client/gui/widgets.py @@ -711,6 +711,7 @@ def on_source_changed(self): # Try to get the SourceConversationWrapper from the persistent dict, # else we create it. try: + logger.debug('Drawing source conversation for {}'.format(source.uuid)) conversation_wrapper = self.source_conversations[source.uuid] # Redraw the conversation view such that new messages, replies, files appear. @@ -2195,6 +2196,7 @@ def eventFilter(self, obj, event): def _set_file_state(self): if self.file.is_decrypted: + logger.debug('Changing file {} state to decrypted/downloaded'.format(self.uuid)) self._set_file_name() self.download_button.hide() self.no_file_name.hide() @@ -2203,6 +2205,7 @@ def _set_file_state(self): self.print_button.show() self.file_name.show() else: + logger.debug('Changing file {} state to not downloaded'.format(self.uuid)) self.download_button.setText(_('DOWNLOAD')) # Ensure correct icon depending on mouse hover state. if self.download_button.underMouse(): @@ -3002,13 +3005,16 @@ def update_conversation(self, collection: list) -> None: # by another user (a journalist using the Web UI is able to delete individual # submissions). for item_widget in current_conversation.values(): + logger.debug('Deleting item: {}'.format(item_widget.uuid)) self.current_messages.pop(item_widget.uuid) + item_widget.deleteLater() self.conversation_layout.removeWidget(item_widget) def add_file(self, file: File, index): """ Add a file from the source. """ + logger.debug('Adding file for {}'.format(file.uuid)) conversation_item = FileWidget( file.uuid, self.controller, diff --git a/securedrop_client/logic.py b/securedrop_client/logic.py index 39c0227a3..48bba32ed 100644 --- a/securedrop_client/logic.py +++ b/securedrop_client/logic.py @@ -457,6 +457,7 @@ def on_sync_success(self) -> None: self.file_missing.emit(missed_file.source.uuid, missed_file.uuid, str(missed_file)) self.update_sources() + self.gui.refresh_current_source_conversation() self.download_new_messages() self.download_new_replies() self.sync_events.emit('synced') diff --git a/tests/gui/test_main.py b/tests/gui/test_main.py index 9364b7e6b..fd33da021 100644 --- a/tests/gui/test_main.py +++ b/tests/gui/test_main.py @@ -157,6 +157,20 @@ def test_hide_login(mocker): assert w.login_dialog is None +def test_refresh_current_source_conversation(mocker): + """ + Ensure on_source_changed is called on the MainView (which + updates the current conversation) when + refresh_current_source_conversation() is called. + """ + w = Window() + w.main_view = mocker.MagicMock() + + w.refresh_current_source_conversation() + + w.main_view.on_source_changed.assert_called_once_with() + + def test_show_sources(mocker): """ Ensure the sources list is passed to the main view to be updated.