From 397f1021955edc7ec6cac1ff07641b05726c7694 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 17 Feb 2026 00:01:54 -0800 Subject: [PATCH] Backend doc updated ws --- src/documents/apps.py | 2 ++ src/documents/plugins/helpers.py | 22 +++++++++++++++++ src/documents/signals/handlers.py | 23 ++++++++++++++++++ src/paperless/consumers.py | 7 ++++++ src/paperless/tests/test_websockets.py | 33 ++++++++++++++++++++++++++ 5 files changed, 87 insertions(+) diff --git a/src/documents/apps.py b/src/documents/apps.py index d8200edac..c14f56ee4 100644 --- a/src/documents/apps.py +++ b/src/documents/apps.py @@ -15,6 +15,7 @@ class DocumentsConfig(AppConfig): from documents.signals.handlers import add_to_index from documents.signals.handlers import run_workflows_added from documents.signals.handlers import run_workflows_updated + from documents.signals.handlers import send_websocket_document_updated from documents.signals.handlers import set_correspondent from documents.signals.handlers import set_document_type from documents.signals.handlers import set_storage_path @@ -29,6 +30,7 @@ class DocumentsConfig(AppConfig): document_consumption_finished.connect(run_workflows_added) document_consumption_finished.connect(add_or_update_document_in_llm_index) document_updated.connect(run_workflows_updated) + document_updated.connect(send_websocket_document_updated) import documents.schema # noqa: F401 diff --git a/src/documents/plugins/helpers.py b/src/documents/plugins/helpers.py index 3315ec60e..1676ec6a7 100644 --- a/src/documents/plugins/helpers.py +++ b/src/documents/plugins/helpers.py @@ -100,3 +100,25 @@ class DocumentsStatusManager(BaseStatusManager): } self.send(payload) + + def send_document_updated( + self, + *, + document_id: int, + modified: str | None = None, + owner_id: int | None = None, + users_can_view: list[int] | None = None, + groups_can_view: list[int] | None = None, + ) -> None: + payload = { + "type": "document_updated", + "data": { + "document_id": document_id, + "modified": modified, + "owner_id": owner_id, + "users_can_view": users_can_view or [], + "groups_can_view": groups_can_view or [], + }, + } + + self.send(payload) diff --git a/src/documents/signals/handlers.py b/src/documents/signals/handlers.py index 41fb03d9e..f03fbb4d0 100644 --- a/src/documents/signals/handlers.py +++ b/src/documents/signals/handlers.py @@ -45,6 +45,7 @@ from documents.models import WorkflowAction from documents.models import WorkflowRun from documents.models import WorkflowTrigger from documents.permissions import get_objects_for_user_owner_aware +from documents.plugins.helpers import DocumentsStatusManager from documents.templating.utils import convert_format_str_to_template_format from documents.workflows.actions import build_workflow_action_context from documents.workflows.actions import execute_email_action @@ -753,6 +754,28 @@ def run_workflows_updated( ) +def send_websocket_document_updated( + sender, + document: Document, + **kwargs, +) -> None: + # At this point, workflows may already have applied additional changes. + document.refresh_from_db() + + from documents.data_models import DocumentMetadataOverrides + + doc_overrides = DocumentMetadataOverrides.from_document(document) + + with DocumentsStatusManager() as status_mgr: + status_mgr.send_document_updated( + document_id=document.id, + modified=document.modified.isoformat() if document.modified else None, + owner_id=doc_overrides.owner_id, + users_can_view=doc_overrides.view_users, + groups_can_view=doc_overrides.view_groups, + ) + + def run_workflows( trigger_type: WorkflowTrigger.WorkflowTriggerType, document: Document | ConsumableDocument, diff --git a/src/paperless/consumers.py b/src/paperless/consumers.py index 40f9a006f..d34f4973d 100644 --- a/src/paperless/consumers.py +++ b/src/paperless/consumers.py @@ -52,3 +52,10 @@ class StatusConsumer(WebsocketConsumer): self.close() else: self.send(json.dumps(event)) + + def document_updated(self, event) -> None: + if not self._authenticated(): + self.close() + else: + if self._can_view(event["data"]): + self.send(json.dumps(event)) diff --git a/src/paperless/tests/test_websockets.py b/src/paperless/tests/test_websockets.py index eef7d00f3..bdcbd7a8f 100644 --- a/src/paperless/tests/test_websockets.py +++ b/src/paperless/tests/test_websockets.py @@ -158,6 +158,39 @@ class TestWebSockets(TestCase): await communicator.disconnect() + @mock.patch("paperless.consumers.StatusConsumer._can_view") + @mock.patch("paperless.consumers.StatusConsumer._authenticated") + async def test_receive_document_updated(self, _authenticated, _can_view) -> None: + _authenticated.return_value = True + _can_view.return_value = True + + communicator = WebsocketCommunicator(application, "/ws/status/") + connected, _ = await communicator.connect() + self.assertTrue(connected) + + message = { + "type": "document_updated", + "data": { + "document_id": 10, + "modified": "2026-02-17T00:00:00Z", + "owner_id": 1, + "users_can_view": [1], + "groups_can_view": [], + }, + } + + channel_layer = get_channel_layer() + await channel_layer.group_send( + "status_updates", + message, + ) + + response = await communicator.receive_json_from() + + self.assertEqual(response, message) + + await communicator.disconnect() + @mock.patch("channels.layers.InMemoryChannelLayer.group_send") def test_manager_send_progress(self, mock_group_send) -> None: with ProgressManager(task_id="test") as manager: