mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-10-28 10:35:43 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dda60db9f1 | ||
|
|
5d0197205f | ||
|
|
fed9e8d783 | ||
|
|
d4457efe44 |
No files matched your search
@@ -1381,9 +1381,17 @@ def task_revoked_handler(
|
|||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
close_old_connections()
|
close_old_connections()
|
||||||
|
reason = "Task revoked before completion."
|
||||||
|
if expired:
|
||||||
|
reason = "Task expired before execution."
|
||||||
|
elif terminated:
|
||||||
|
reason = "Task terminated during execution."
|
||||||
PaperlessTask.objects.filter(task_id=task_id).update(
|
PaperlessTask.objects.filter(task_id=task_id).update(
|
||||||
status=PaperlessTask.Status.REVOKED,
|
status=PaperlessTask.Status.REVOKED,
|
||||||
date_done=timezone.now(),
|
date_done=timezone.now(),
|
||||||
|
result_data={
|
||||||
|
"reason": (reason + " Check worker logs for details."),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
except Exception: # pragma: no cover
|
except Exception: # pragma: no cover
|
||||||
logger.exception("Updating PaperlessTask on revocation failed")
|
logger.exception("Updating PaperlessTask on revocation failed")
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ from documents.models import UiSettings
|
|||||||
from documents.models import Workflow
|
from documents.models import Workflow
|
||||||
from documents.models import WorkflowAction
|
from documents.models import WorkflowAction
|
||||||
from documents.models import WorkflowTrigger
|
from documents.models import WorkflowTrigger
|
||||||
|
from documents.search import get_backend
|
||||||
from documents.signals.handlers import run_workflows
|
from documents.signals.handlers import run_workflows
|
||||||
from documents.tests.utils import ConsumeTaskMixin
|
from documents.tests.utils import ConsumeTaskMixin
|
||||||
from paperless_testing.dirs import DirectoriesMixin
|
from paperless_testing.dirs import DirectoriesMixin
|
||||||
@@ -66,6 +67,70 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
|
|||||||
self.client.force_authenticate(user=self.user)
|
self.client.force_authenticate(user=self.user)
|
||||||
cache.clear()
|
cache.clear()
|
||||||
|
|
||||||
|
def test_detail_save_indexes_final_workflow_state(self) -> None:
|
||||||
|
viewer = UserFactory()
|
||||||
|
grant_global(viewer, "view_document")
|
||||||
|
inbox = TagFactory(is_inbox_tag=True)
|
||||||
|
doc = DocumentFactory(owner=self.user, content="distinctiveword")
|
||||||
|
doc.tags.add(inbox)
|
||||||
|
trigger = WorkflowTrigger.objects.create(
|
||||||
|
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
|
||||||
|
)
|
||||||
|
action = WorkflowAction.objects.create(assign_title="Workflow title")
|
||||||
|
action.assign_view_users.add(viewer)
|
||||||
|
action.remove_view_users.add(viewer)
|
||||||
|
workflow = Workflow.objects.create(name="Update document")
|
||||||
|
workflow.triggers.add(trigger)
|
||||||
|
workflow.actions.add(action)
|
||||||
|
backend = get_backend()
|
||||||
|
backend.add_or_update(doc)
|
||||||
|
|
||||||
|
for action_type in (
|
||||||
|
WorkflowAction.WorkflowActionType.ASSIGNMENT,
|
||||||
|
WorkflowAction.WorkflowActionType.REMOVAL,
|
||||||
|
WorkflowAction.WorkflowActionType.MOVE_TO_TRASH,
|
||||||
|
):
|
||||||
|
with self.subTest(action_type=action_type):
|
||||||
|
grant = action_type == WorkflowAction.WorkflowActionType.ASSIGNMENT
|
||||||
|
action.type = action_type
|
||||||
|
action.save()
|
||||||
|
trigger.filter_has_not_tags.set([inbox] if grant else [])
|
||||||
|
trigger.filter_has_tags.set([] if grant else [inbox])
|
||||||
|
self.client.force_authenticate(self.user)
|
||||||
|
response = self.client.patch(
|
||||||
|
f"/api/documents/{doc.pk}/",
|
||||||
|
{"tags": [] if grant else [inbox.pk]},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.client.force_authenticate(viewer)
|
||||||
|
self.assertEqual(
|
||||||
|
self.client.get(f"/api/documents/{doc.pk}/").status_code,
|
||||||
|
status.HTTP_200_OK if grant else status.HTTP_404_NOT_FOUND,
|
||||||
|
)
|
||||||
|
# Check the index directly: the API's DB permission filtering
|
||||||
|
# would otherwise hide stale grants and trashed index entries.
|
||||||
|
self.assertEqual(
|
||||||
|
list(backend.search_ids("distinctiveword", user=viewer)),
|
||||||
|
[doc.pk] if grant else [],
|
||||||
|
)
|
||||||
|
if grant:
|
||||||
|
response = self.client.get(
|
||||||
|
"/api/documents/",
|
||||||
|
{"query": "distinctiveword AND title:workflow"},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(
|
||||||
|
[result["id"] for result in response.data["results"]],
|
||||||
|
[doc.pk],
|
||||||
|
)
|
||||||
|
elif action_type == WorkflowAction.WorkflowActionType.MOVE_TO_TRASH:
|
||||||
|
self.assertFalse(Document.objects.filter(pk=doc.pk).exists())
|
||||||
|
self.assertEqual(
|
||||||
|
list(backend.search_ids("distinctiveword", user=None)),
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
def testDocuments(self) -> None:
|
def testDocuments(self) -> None:
|
||||||
response = self.client.get("/api/documents/").data
|
response = self.client.get("/api/documents/").data
|
||||||
|
|
||||||
|
|||||||
@@ -429,6 +429,36 @@ class TestTaskRevokedHandler:
|
|||||||
assert task.status == PaperlessTask.Status.REVOKED
|
assert task.status == PaperlessTask.Status.REVOKED
|
||||||
assert task.date_done is not None
|
assert task.date_done is not None
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("expired", "terminated", "reason"),
|
||||||
|
[
|
||||||
|
(True, False, "Task expired before execution."),
|
||||||
|
(False, True, "Task terminated during execution."),
|
||||||
|
(False, False, "Task revoked before completion."),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_records_revocation_details(
|
||||||
|
self,
|
||||||
|
mocker,
|
||||||
|
expired,
|
||||||
|
terminated,
|
||||||
|
reason,
|
||||||
|
) -> None:
|
||||||
|
task = PaperlessTaskFactory(status=PaperlessTask.Status.PENDING)
|
||||||
|
request = mocker.MagicMock()
|
||||||
|
request.id = task.task_id
|
||||||
|
|
||||||
|
task_revoked_handler(
|
||||||
|
request=request,
|
||||||
|
expired=expired,
|
||||||
|
terminated=terminated,
|
||||||
|
)
|
||||||
|
task.refresh_from_db()
|
||||||
|
|
||||||
|
assert task.result_data == {
|
||||||
|
"reason": f"{reason} Check worker logs for details.",
|
||||||
|
}
|
||||||
|
|
||||||
def test_ignores_none_request(self) -> None:
|
def test_ignores_none_request(self) -> None:
|
||||||
"""task_revoked_handler must not raise when request is None."""
|
"""task_revoked_handler must not raise when request is None."""
|
||||||
|
|
||||||
|
|||||||
@@ -1304,13 +1304,19 @@ class DocumentViewSet(
|
|||||||
|
|
||||||
from documents.search import get_backend
|
from documents.search import get_backend
|
||||||
|
|
||||||
get_backend().add_or_update(refreshed_doc)
|
|
||||||
|
|
||||||
document_updated.send(
|
document_updated.send(
|
||||||
sender=self.__class__,
|
sender=self.__class__,
|
||||||
document=refreshed_doc,
|
document=refreshed_doc,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Index the final workflow state, including deletion.
|
||||||
|
backend = get_backend()
|
||||||
|
final_doc = Document.objects.filter(pk=root_doc.pk).first()
|
||||||
|
if final_doc is None:
|
||||||
|
backend.remove(root_doc.pk)
|
||||||
|
else:
|
||||||
|
backend.add_or_update(final_doc)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def list(self, request, *args, **kwargs):
|
def list(self, request, *args, **kwargs):
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-24 21:11+0000\n"
|
"POT-Creation-Date: 2026-10-28 02:55+0000\n"
|
||||||
"PO-Revision-Date: 2022-02-17 04:17\n"
|
"PO-Revision-Date: 2022-02-17 04:17\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: English\n"
|
"Language-Team: English\n"
|
||||||
@@ -1653,7 +1653,7 @@ msgid "workflow runs"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:483 documents/serialisers.py:840
|
#: documents/serialisers.py:483 documents/serialisers.py:840
|
||||||
#: documents/serialisers.py:2872 documents/views.py:360 documents/views.py:2724
|
#: documents/serialisers.py:2872 documents/views.py:360 documents/views.py:2730
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1694,7 +1694,7 @@ msgstr ""
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2958 documents/views.py:4774
|
#: documents/serialisers.py:2958 documents/views.py:4780
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1949,36 +1949,36 @@ msgstr ""
|
|||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:353 documents/views.py:2721
|
#: documents/views.py:353 documents/views.py:2727
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1659
|
#: documents/views.py:1665
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1670
|
#: documents/views.py:1676
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1682
|
#: documents/views.py:1688
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4790
|
#: documents/views.py:4796
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4837
|
#: documents/views.py:4843
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4903
|
#: documents/views.py:4909
|
||||||
msgid "The share link bundle is still being prepared. Please try again later."
|
msgid "The share link bundle is still being prepared. Please try again later."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4917
|
#: documents/views.py:4923
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user