mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-03-11 03:31:23 +00:00
Compare commits
1 Commits
feature-tr
...
feature-ty
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efda9cf892 |
@@ -45,7 +45,7 @@ dependencies = [
|
||||
"drf-spectacular-sidecar~=2026.1.1",
|
||||
"drf-writable-nested~=0.7.1",
|
||||
"faiss-cpu>=1.10",
|
||||
"filelock~=3.20.3",
|
||||
"filelock~=3.24.3",
|
||||
"flower~=2.0.1",
|
||||
"gotenberg-client~=0.13.1",
|
||||
"httpx-oauth~=0.16",
|
||||
|
||||
@@ -138,14 +138,12 @@ class ConsumerPluginMixin:
|
||||
message,
|
||||
current_progress,
|
||||
max_progress,
|
||||
extra_args={
|
||||
"document_id": document_id,
|
||||
"owner_id": self.metadata.owner_id if self.metadata.owner_id else None,
|
||||
"users_can_view": (self.metadata.view_users or [])
|
||||
+ (self.metadata.change_users or []),
|
||||
"groups_can_view": (self.metadata.view_groups or [])
|
||||
+ (self.metadata.change_groups or []),
|
||||
},
|
||||
document_id=document_id,
|
||||
owner_id=self.metadata.owner_id if self.metadata.owner_id else None,
|
||||
users_can_view=(self.metadata.view_users or [])
|
||||
+ (self.metadata.change_users or []),
|
||||
groups_can_view=(self.metadata.view_groups or [])
|
||||
+ (self.metadata.change_groups or []),
|
||||
)
|
||||
|
||||
def _fail(
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import enum
|
||||
from collections.abc import Mapping
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Literal
|
||||
from typing import Self
|
||||
from typing import TypeAlias
|
||||
from typing import TypedDict
|
||||
|
||||
from asgiref.sync import async_to_sync
|
||||
from channels.layers import get_channel_layer
|
||||
@@ -16,6 +19,59 @@ class ProgressStatusOptions(enum.StrEnum):
|
||||
FAILED = "FAILED"
|
||||
|
||||
|
||||
class PermissionsData(TypedDict, total=False):
|
||||
"""Permission fields included in status messages for access control."""
|
||||
|
||||
owner_id: int | None
|
||||
users_can_view: list[int]
|
||||
groups_can_view: list[int]
|
||||
|
||||
|
||||
class ProgressUpdateData(TypedDict):
|
||||
filename: str | None
|
||||
task_id: str | None
|
||||
current_progress: int
|
||||
max_progress: int
|
||||
status: str
|
||||
message: str
|
||||
document_id: int | None
|
||||
owner_id: int | None
|
||||
users_can_view: list[int]
|
||||
groups_can_view: list[int]
|
||||
|
||||
|
||||
class StatusUpdatePayload(TypedDict):
|
||||
type: Literal["status_update"]
|
||||
data: ProgressUpdateData
|
||||
|
||||
|
||||
class DocumentsDeletedData(TypedDict):
|
||||
documents: list[int]
|
||||
|
||||
|
||||
class DocumentsDeletedPayload(TypedDict):
|
||||
type: Literal["documents_deleted"]
|
||||
data: DocumentsDeletedData
|
||||
|
||||
|
||||
class DocumentUpdatedData(TypedDict):
|
||||
document_id: int
|
||||
modified: str
|
||||
owner_id: int | None
|
||||
users_can_view: list[int]
|
||||
groups_can_view: list[int]
|
||||
|
||||
|
||||
class DocumentUpdatedPayload(TypedDict):
|
||||
type: Literal["document_updated"]
|
||||
data: DocumentUpdatedData
|
||||
|
||||
|
||||
WebsocketPayload: TypeAlias = (
|
||||
StatusUpdatePayload | DocumentsDeletedPayload | DocumentUpdatedPayload
|
||||
)
|
||||
|
||||
|
||||
class BaseStatusManager:
|
||||
"""
|
||||
Handles sending of progress information via the channel layer, with proper management
|
||||
@@ -25,11 +81,11 @@ class BaseStatusManager:
|
||||
def __init__(self) -> None:
|
||||
self._channel: RedisPubSubChannelLayer | None = None
|
||||
|
||||
def __enter__(self):
|
||||
def __enter__(self) -> Self:
|
||||
self.open()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None:
|
||||
self.close()
|
||||
|
||||
def open(self) -> None:
|
||||
@@ -48,7 +104,7 @@ class BaseStatusManager:
|
||||
async_to_sync(self._channel.flush)
|
||||
self._channel = None
|
||||
|
||||
def send(self, payload: Mapping[str, object]) -> None:
|
||||
def send(self, payload: WebsocketPayload) -> None:
|
||||
# Ensure the layer is open
|
||||
self.open()
|
||||
|
||||
@@ -72,36 +128,36 @@ class ProgressManager(BaseStatusManager):
|
||||
message: str,
|
||||
current_progress: int,
|
||||
max_progress: int,
|
||||
extra_args: dict[str, str | int | None] | None = None,
|
||||
*,
|
||||
document_id: int | None = None,
|
||||
owner_id: int | None = None,
|
||||
users_can_view: list[int] | None = None,
|
||||
groups_can_view: list[int] | None = None,
|
||||
) -> None:
|
||||
data: dict[str, object] = {
|
||||
data: ProgressUpdateData = {
|
||||
"filename": self.filename,
|
||||
"task_id": self.task_id,
|
||||
"current_progress": current_progress,
|
||||
"max_progress": max_progress,
|
||||
"status": status,
|
||||
"message": message,
|
||||
"document_id": document_id,
|
||||
"owner_id": owner_id,
|
||||
"users_can_view": users_can_view or [],
|
||||
"groups_can_view": groups_can_view or [],
|
||||
}
|
||||
if extra_args is not None:
|
||||
data.update(extra_args)
|
||||
|
||||
payload: dict[str, object] = {
|
||||
"type": "status_update",
|
||||
"data": data,
|
||||
}
|
||||
|
||||
payload: StatusUpdatePayload = {"type": "status_update", "data": data}
|
||||
self.send(payload)
|
||||
|
||||
|
||||
class DocumentsStatusManager(BaseStatusManager):
|
||||
def send_documents_deleted(self, documents: list[int]) -> None:
|
||||
payload: dict[str, object] = {
|
||||
payload: DocumentsDeletedPayload = {
|
||||
"type": "documents_deleted",
|
||||
"data": {
|
||||
"documents": documents,
|
||||
},
|
||||
}
|
||||
|
||||
self.send(payload)
|
||||
|
||||
def send_document_updated(
|
||||
@@ -113,7 +169,7 @@ class DocumentsStatusManager(BaseStatusManager):
|
||||
users_can_view: list[int] | None = None,
|
||||
groups_can_view: list[int] | None = None,
|
||||
) -> None:
|
||||
payload: dict[str, object] = {
|
||||
payload: DocumentUpdatedPayload = {
|
||||
"type": "document_updated",
|
||||
"data": {
|
||||
"document_id": document_id,
|
||||
@@ -123,5 +179,4 @@ class DocumentsStatusManager(BaseStatusManager):
|
||||
"groups_can_view": groups_can_view or [],
|
||||
},
|
||||
}
|
||||
|
||||
self.send(payload)
|
||||
|
||||
@@ -429,7 +429,11 @@ class DummyProgressManager:
|
||||
message: str,
|
||||
current_progress: int,
|
||||
max_progress: int,
|
||||
extra_args: dict[str, str | int] | None = None,
|
||||
*,
|
||||
document_id: int | None = None,
|
||||
owner_id: int | None = None,
|
||||
users_can_view: list[int] | None = None,
|
||||
groups_can_view: list[int] | None = None,
|
||||
) -> None:
|
||||
# Ensure the layer is open
|
||||
self.open()
|
||||
@@ -443,9 +447,10 @@ class DummyProgressManager:
|
||||
"max_progress": max_progress,
|
||||
"status": status,
|
||||
"message": message,
|
||||
"document_id": document_id,
|
||||
"owner_id": owner_id,
|
||||
"users_can_view": users_can_view or [],
|
||||
"groups_can_view": groups_can_view or [],
|
||||
},
|
||||
}
|
||||
if extra_args is not None:
|
||||
payload["data"].update(extra_args)
|
||||
|
||||
self.payloads.append(payload)
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Any
|
||||
|
||||
from channels.generic.websocket import AsyncWebsocketConsumer
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from documents.plugins.helpers import PermissionsData
|
||||
|
||||
|
||||
class StatusConsumer(AsyncWebsocketConsumer):
|
||||
def _authenticated(self) -> bool:
|
||||
user: Any = self.scope.get("user")
|
||||
return user is not None and user.is_authenticated
|
||||
|
||||
async def _can_view(self, data: dict[str, Any]) -> bool:
|
||||
async def _can_view(self, data: PermissionsData) -> bool:
|
||||
user: Any = self.scope.get("user")
|
||||
if user is None:
|
||||
return False
|
||||
|
||||
@@ -199,7 +199,10 @@ class TestWebSockets:
|
||||
"Test message",
|
||||
1,
|
||||
10,
|
||||
extra_args={"foo": "bar"},
|
||||
document_id=42,
|
||||
owner_id=1,
|
||||
users_can_view=[2, 3],
|
||||
groups_can_view=[4],
|
||||
)
|
||||
|
||||
assert mock_group_send.call_args[0][1] == {
|
||||
@@ -211,7 +214,10 @@ class TestWebSockets:
|
||||
"max_progress": 10,
|
||||
"status": ProgressStatusOptions.STARTED,
|
||||
"message": "Test message",
|
||||
"foo": "bar",
|
||||
"document_id": 42,
|
||||
"owner_id": 1,
|
||||
"users_can_view": [2, 3],
|
||||
"groups_can_view": [4],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
8
uv.lock
generated
8
uv.lock
generated
@@ -1251,11 +1251,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.20.3"
|
||||
version = "3.24.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/73/92/a8e2479937ff39185d20dd6a851c1a63e55849e447a55e798cc2e1f49c65/filelock-3.24.3.tar.gz", hash = "sha256:011a5644dc937c22699943ebbfc46e969cdde3e171470a6e40b9533e5a72affa", size = 37935, upload-time = "2026-02-19T00:48:20.543Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/0f/5d0c71a1aefeb08efff26272149e07ab922b64f46c63363756224bd6872e/filelock-3.24.3-py3-none-any.whl", hash = "sha256:426e9a4660391f7f8a810d71b0555bce9008b0a1cc342ab1f6947d37639e002d", size = 24331, upload-time = "2026-02-19T00:48:18.465Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2961,7 +2961,7 @@ requires-dist = [
|
||||
{ name = "drf-spectacular-sidecar", specifier = "~=2026.1.1" },
|
||||
{ name = "drf-writable-nested", specifier = "~=0.7.1" },
|
||||
{ name = "faiss-cpu", specifier = ">=1.10" },
|
||||
{ name = "filelock", specifier = "~=3.20.3" },
|
||||
{ name = "filelock", specifier = "~=3.24.3" },
|
||||
{ name = "flower", specifier = "~=2.0.1" },
|
||||
{ name = "gotenberg-client", specifier = "~=0.13.1" },
|
||||
{ name = "granian", extras = ["uvloop"], marker = "extra == 'webserver'", specifier = "~=2.7.0" },
|
||||
|
||||
Reference in New Issue
Block a user