Merge remote-tracking branch 'origin/dev' into feature-devcontainer

This commit is contained in:
Philipp
2024-06-19 21:53:47 +00:00
98 changed files with 15083 additions and 6693 deletions

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.2.11 on 2024-04-23 07:56
from django.db import migrations
from django.db import models
class Migration(migrations.Migration):
dependencies = [
("documents", "1048_alter_savedviewfilterrule_rule_type"),
]
operations = [
migrations.AddField(
model_name="document",
name="deleted_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="document",
name="restored_at",
field=models.DateTimeField(blank=True, null=True),
),
]

View File

@@ -23,6 +23,8 @@ from multiselectfield import MultiSelectField
if settings.AUDIT_LOG_ENABLED:
from auditlog.registry import auditlog
from django_softdelete.models import SoftDeleteModel
from documents.data_models import DocumentSource
from documents.parsers import get_default_file_extension
@@ -130,7 +132,7 @@ class StoragePath(MatchingModel):
verbose_name_plural = _("storage paths")
class Document(ModelWithOwner):
class Document(SoftDeleteModel, ModelWithOwner):
STORAGE_TYPE_UNENCRYPTED = "unencrypted"
STORAGE_TYPE_GPG = "gpg"
STORAGE_TYPES = (

View File

@@ -786,6 +786,7 @@ class DocumentSerializer(
"created_date",
"modified",
"added",
"deleted_at",
"archive_serial_number",
"original_file_name",
"archived_file_name",
@@ -1863,3 +1864,26 @@ class WorkflowSerializer(serializers.ModelSerializer):
self.prune_triggers_and_actions()
return instance
class TrashSerializer(SerializerWithPerms):
documents = serializers.ListField(
required=False,
label="Documents",
write_only=True,
child=serializers.IntegerField(),
)
action = serializers.ChoiceField(
choices=["restore", "empty"],
label="Action",
write_only=True,
)
def validate_documents(self, documents):
count = Document.deleted_objects.filter(id__in=documents).count()
if not count == len(documents):
raise serializers.ValidationError(
"Some documents in the list have not yet been deleted.",
)
return documents

View File

@@ -301,10 +301,10 @@ def set_storage_path(
document.save(update_fields=("storage_path",))
@receiver(models.signals.post_delete, sender=Document)
def cleanup_document_deletion(sender, instance, using, **kwargs):
# see empty_trash in documents/tasks.py for signal handling
def cleanup_document_deletion(sender, instance, **kwargs):
with FileLock(settings.MEDIA_LOCK):
if settings.TRASH_DIR:
if settings.EMPTY_TRASH_DIR:
# Find a non-conflicting filename in case a document with the same
# name was moved to trash earlier
counter = 0
@@ -313,7 +313,7 @@ def cleanup_document_deletion(sender, instance, using, **kwargs):
while True:
new_file_path = os.path.join(
settings.TRASH_DIR,
settings.EMPTY_TRASH_DIR,
old_filebase + (f"_{counter:02}" if counter else "") + old_fileext,
)

View File

@@ -2,6 +2,7 @@ import hashlib
import logging
import shutil
import uuid
from datetime import timedelta
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Optional
@@ -10,8 +11,10 @@ import tqdm
from celery import Task
from celery import shared_task
from django.conf import settings
from django.db import models
from django.db import transaction
from django.db.models.signals import post_save
from django.utils import timezone
from filelock import FileLock
from whoosh.writing import AsyncWriter
@@ -41,6 +44,7 @@ from documents.plugins.base import StopConsumeTaskError
from documents.plugins.helpers import ProgressStatusOptions
from documents.sanity_checker import SanityCheckFailedException
from documents.signals import document_updated
from documents.signals.handlers import cleanup_document_deletion
if settings.AUDIT_LOG_ENABLED:
import json
@@ -292,3 +296,29 @@ def update_document_archive_file(document_id):
)
finally:
parser.cleanup()
@shared_task
def empty_trash(doc_ids=None):
documents = (
Document.deleted_objects.filter(id__in=doc_ids)
if doc_ids is not None
else Document.deleted_objects.filter(
deleted_at__lt=timezone.localtime(timezone.now())
- timedelta(
days=settings.EMPTY_TRASH_DELAY,
),
)
)
try:
# Temporarily connect the cleanup handler
models.signals.post_delete.connect(cleanup_document_deletion, sender=Document)
documents.delete() # this is effectively a hard delete
except Exception as e: # pragma: no cover
logger.exception(f"Error while emptying trash: {e}")
finally:
models.signals.post_delete.disconnect(
cleanup_document_deletion,
sender=Document,
)

View File

@@ -0,0 +1,155 @@
from django.contrib.auth.models import User
from django.core.cache import cache
from rest_framework import status
from rest_framework.test import APITestCase
from documents.models import Document
class TestTrashAPI(APITestCase):
def setUp(self):
super().setUp()
self.user = User.objects.create_superuser(username="temp_admin")
self.client.force_authenticate(user=self.user)
cache.clear()
def test_api_trash(self):
"""
GIVEN:
- Existing document
WHEN:
- API request to delete document
- API request to restore document
- API request to empty trash
THEN:
- Document is moved to trash
- Document is restored from trash
- Trash is emptied
"""
document = Document.objects.create(
title="Title",
content="content",
checksum="checksum",
mime_type="application/pdf",
)
self.client.force_login(user=self.user)
self.client.delete(f"/api/documents/{document.pk}/")
self.assertEqual(Document.objects.count(), 0)
self.assertEqual(Document.global_objects.count(), 1)
self.assertEqual(Document.deleted_objects.count(), 1)
resp = self.client.get("/api/trash/")
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertEqual(resp.data["count"], 1)
resp = self.client.post(
"/api/trash/",
{"action": "restore", "documents": [document.pk]},
)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertEqual(Document.objects.count(), 1)
resp = self.client.get("/api/trash/")
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertEqual(resp.data["count"], 0)
self.client.delete(f"/api/documents/{document.pk}/")
resp = self.client.post(
"/api/trash/",
{"action": "empty", "documents": [document.pk]},
)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertEqual(Document.global_objects.count(), 0)
def test_trash_api_empty_all(self):
"""
GIVEN:
- Existing documents in trash
WHEN:
- API request to empty trash
THEN:
- Trash is emptied
"""
document = Document.objects.create(
title="Title",
content="content",
checksum="checksum",
mime_type="application/pdf",
)
document.delete()
document2 = Document.objects.create(
title="Title2",
content="content2",
checksum="checksum2",
mime_type="application/pdf",
)
document2.delete()
self.client.force_login(user=self.user)
resp = self.client.post(
"/api/trash/",
{"action": "empty", "documents": []},
)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertEqual(Document.global_objects.count(), 0)
def test_api_trash_insufficient_permissions(self):
"""
GIVEN:
- Existing document with owner = user2 in trash
WHEN:
- user 1 makes API request to empty document from trash
THEN:
- 403 Forbidden
"""
user1 = User.objects.create_user(username="user1")
self.client.force_authenticate(user=user1)
self.client.force_login(user=user1)
user2 = User.objects.create_user(username="user2")
document = Document.objects.create(
title="Title",
content="content",
checksum="checksum",
mime_type="application/pdf",
owner=user2,
)
document.delete()
resp = self.client.post(
"/api/trash/",
{"action": "empty", "documents": [document.pk]},
)
self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(Document.global_objects.count(), 1)
def test_api_trash_invalid_params(self):
"""
GIVEN:
- Existing documents
WHEN:
- API request to trash with invalid params
THEN:
- 400 Bad Request
"""
document = Document.objects.create(
title="Title",
content="content",
checksum="checksum",
mime_type="application/pdf",
)
self.client.force_login(user=self.user)
# document isn't in trash
resp = self.client.post(
"/api/trash/",
{"action": "restore", "documents": [document.pk]},
)
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn("have not yet been deleted", resp.data["documents"][0])

View File

@@ -40,6 +40,7 @@ class TestApiUiSettings(DirectoriesMixin, APITestCase):
"app_title": None,
"app_logo": None,
"auditlog_enabled": True,
"trash_delay": 30,
"update_checking": {
"backend_setting": "default",
},

View File

@@ -10,6 +10,7 @@ from django.utils import timezone
from documents.models import Correspondent
from documents.models import Document
from documents.tasks import empty_trash
class TestDocument(TestCase):
@@ -43,10 +44,39 @@ class TestDocument(TestCase):
with mock.patch("documents.signals.handlers.os.unlink") as mock_unlink:
document.delete()
empty_trash([document.pk])
mock_unlink.assert_any_call(file_path)
mock_unlink.assert_any_call(thumb_path)
self.assertEqual(mock_unlink.call_count, 2)
def test_document_soft_delete(self):
document = Document.objects.create(
correspondent=Correspondent.objects.create(name="Test0"),
title="Title",
content="content",
checksum="checksum",
mime_type="application/pdf",
)
file_path = document.source_path
thumb_path = document.thumbnail_path
Path(file_path).touch()
Path(thumb_path).touch()
with mock.patch("documents.signals.handlers.os.unlink") as mock_unlink:
document.delete()
self.assertEqual(mock_unlink.call_count, 0)
self.assertEqual(Document.objects.count(), 0)
document.restore(strict=False)
self.assertEqual(Document.objects.count(), 1)
document.delete()
empty_trash([document.pk])
self.assertEqual(mock_unlink.call_count, 2)
def test_file_name(self):
doc = Document(
mime_type="application/pdf",

View File

@@ -19,6 +19,7 @@ from documents.models import Correspondent
from documents.models import Document
from documents.models import DocumentType
from documents.models import StoragePath
from documents.tasks import empty_trash
from documents.tests.utils import DirectoriesMixin
from documents.tests.utils import FileSystemAssertsMixin
@@ -169,6 +170,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
# Ensure that filename is properly generated
document.filename = generate_filename(document)
document.save()
self.assertEqual(document.filename, "none/none.pdf")
create_source_path_directory(document.source_path)
@@ -176,6 +178,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
# Ensure file deletion after delete
document.delete()
empty_trash([document.pk])
self.assertIsNotFile(
os.path.join(settings.ORIGINALS_DIR, "none", "none.pdf"),
)
@@ -183,9 +186,9 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
@override_settings(
FILENAME_FORMAT="{correspondent}/{correspondent}",
TRASH_DIR=tempfile.mkdtemp(),
EMPTY_TRASH_DIR=tempfile.mkdtemp(),
)
def test_document_delete_trash(self):
def test_document_delete_trash_dir(self):
document = Document()
document.mime_type = "application/pdf"
document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
@@ -193,20 +196,22 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
# Ensure that filename is properly generated
document.filename = generate_filename(document)
document.save()
self.assertEqual(document.filename, "none/none.pdf")
create_source_path_directory(document.source_path)
Path(document.source_path).touch()
# Ensure file was moved to trash after delete
self.assertIsNotFile(os.path.join(settings.TRASH_DIR, "none", "none.pdf"))
self.assertIsNotFile(os.path.join(settings.EMPTY_TRASH_DIR, "none", "none.pdf"))
document.delete()
empty_trash([document.pk])
self.assertIsNotFile(
os.path.join(settings.ORIGINALS_DIR, "none", "none.pdf"),
)
self.assertIsNotDir(os.path.join(settings.ORIGINALS_DIR, "none"))
self.assertIsFile(os.path.join(settings.TRASH_DIR, "none.pdf"))
self.assertIsNotFile(os.path.join(settings.TRASH_DIR, "none_01.pdf"))
self.assertIsFile(os.path.join(settings.EMPTY_TRASH_DIR, "none.pdf"))
self.assertIsNotFile(os.path.join(settings.EMPTY_TRASH_DIR, "none_01.pdf"))
# Create an identical document and ensure it is trashed under a new name
document = Document()
@@ -214,10 +219,12 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
document.save()
document.filename = generate_filename(document)
document.save()
create_source_path_directory(document.source_path)
Path(document.source_path).touch()
document.delete()
self.assertIsFile(os.path.join(settings.TRASH_DIR, "none_01.pdf"))
empty_trash([document.pk])
self.assertIsFile(os.path.join(settings.EMPTY_TRASH_DIR, "none_01.pdf"))
@override_settings(FILENAME_FORMAT="{correspondent}/{correspondent}")
def test_document_delete_nofile(self):
@@ -227,6 +234,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
document.save()
document.delete()
empty_trash([document.pk])
@override_settings(FILENAME_FORMAT="{correspondent}/{correspondent}")
def test_directory_not_empty(self):
@@ -436,6 +444,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
# Ensure that filename is properly generated
document.filename = generate_filename(document)
document.save()
self.assertEqual(document.filename, "none/none/none.pdf")
create_source_path_directory(document.source_path)
Path(document.source_path).touch()
@@ -444,6 +453,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
self.assertIsDir(os.path.join(settings.ORIGINALS_DIR, "none/none"))
document.delete()
empty_trash([document.pk])
self.assertIsNotFile(
os.path.join(settings.ORIGINALS_DIR, "none/none/none.pdf"),
@@ -550,6 +560,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
self.assertEqual(document2.filename, "qwe_01.pdf")
document.delete()
empty_trash([document.pk])
self.assertIsNotFile(document.source_path)
@@ -819,6 +830,7 @@ class TestFileHandlingWithArchive(DirectoriesMixin, FileSystemAssertsMixin, Test
self.assertIsFile(doc.archive_path)
doc.delete()
empty_trash([doc.pk])
self.assertIsNotFile(original)
self.assertIsNotFile(archive)
@@ -854,6 +866,7 @@ class TestFileHandlingWithArchive(DirectoriesMixin, FileSystemAssertsMixin, Test
self.assertIsFile(doc2.source_path)
doc2.delete()
empty_trash([doc2.pk])
self.assertIsFile(doc1.source_path)
self.assertIsFile(doc1.archive_path)

View File

@@ -1,4 +1,5 @@
import os
from datetime import timedelta
from unittest import mock
from django.conf import settings
@@ -150,3 +151,36 @@ class TestBulkUpdate(DirectoriesMixin, TestCase):
)
tasks.bulk_update_documents([doc1.pk])
class TestEmptyTrashTask(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
"""
GIVEN:
- Existing document in trash
WHEN:
- Empty trash task is called without doc_ids
THEN:
- Document is only deleted if it has been in trash for more than delay (default 30 days)
"""
def test_empty_trash(self):
doc = Document.objects.create(
title="test",
content="my document",
checksum="wow",
added=timezone.now(),
created=timezone.now(),
modified=timezone.now(),
)
doc.delete()
self.assertEqual(Document.global_objects.count(), 1)
self.assertEqual(Document.objects.count(), 0)
tasks.empty_trash()
self.assertEqual(Document.global_objects.count(), 1)
doc.deleted_at = timezone.now() - timedelta(days=31)
doc.save()
tasks.empty_trash()
self.assertEqual(Document.global_objects.count(), 0)

View File

@@ -142,12 +142,14 @@ from documents.serialisers import StoragePathSerializer
from documents.serialisers import TagSerializer
from documents.serialisers import TagSerializerVersion1
from documents.serialisers import TasksViewSerializer
from documents.serialisers import TrashSerializer
from documents.serialisers import UiSettingsViewSerializer
from documents.serialisers import WorkflowActionSerializer
from documents.serialisers import WorkflowSerializer
from documents.serialisers import WorkflowTriggerSerializer
from documents.signals import document_updated
from documents.tasks import consume_file
from documents.tasks import empty_trash
from paperless import version
from paperless.celery import app as celery_app
from paperless.config import GeneralConfig
@@ -1557,6 +1559,8 @@ class UiSettingsView(GenericAPIView):
"backend_setting": settings.ENABLE_UPDATE_CHECK,
}
ui_settings["trash_delay"] = settings.EMPTY_TRASH_DELAY
general_config = GeneralConfig()
ui_settings["app_title"] = settings.APP_TITLE
@@ -2050,3 +2054,41 @@ class SystemStatusView(PassUserMixin):
},
},
)
class TrashView(ListModelMixin, PassUserMixin):
permission_classes = (IsAuthenticated,)
serializer_class = TrashSerializer
filter_backends = (ObjectOwnedOrGrantedPermissionsFilter,)
pagination_class = StandardPagination
model = Document
queryset = Document.deleted_objects.all()
def get(self, request, format=None):
self.serializer_class = DocumentSerializer
return self.list(request, format)
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
doc_ids = serializer.validated_data.get("documents")
docs = (
Document.global_objects.filter(id__in=doc_ids)
if doc_ids is not None
else Document.deleted_objects.all()
)
for doc in docs:
if not has_perms_owner_aware(request.user, "delete_document", doc):
return HttpResponseForbidden("Insufficient permissions")
action = serializer.validated_data.get("action")
if action == "restore":
for doc in Document.deleted_objects.filter(id__in=doc_ids).all():
doc.restore(strict=False)
elif action == "empty":
if doc_ids is None:
doc_ids = [doc.id for doc in docs]
empty_trash(doc_ids=doc_ids)
return Response({"result": "OK", "doc_ids": doc_ids})

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-05-19 11:07-0700\n"
"PO-Revision-Date: 2024-06-03 00:26\n"
"PO-Revision-Date: 2024-06-18 12:10\n"
"Last-Translator: \n"
"Language-Team: Arabic\n"
"Language: ar_SA\n"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-05-19 11:07-0700\n"
"PO-Revision-Date: 2024-05-20 16:40\n"
"PO-Revision-Date: 2024-06-15 00:25\n"
"Last-Translator: \n"
"Language-Team: Bulgarian\n"
"Language: bg_BG\n"
@@ -68,7 +68,7 @@ msgstr "алгоритъм за съвпадение"
#: documents/models.py:72 documents/models.py:1015
msgid "is insensitive"
msgstr "без чувствителност към големината на буквите"
msgstr "без значение от големината на буквите"
#: documents/models.py:95 documents/models.py:147
msgid "correspondent"
@@ -88,7 +88,7 @@ msgstr "етикет за входяща поща"
#: documents/models.py:106
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
msgstr "Маркира този етикет като етикет за входяща кутия: Всички новоизползвани документи ще бъдат маркирани с етикети за входяща кутия."
msgstr "Маркира този етикет като етикет за входяща кутия: Всички новопостъпили документи ще бъдат маркирани с етикети за входяща кутия."
#: documents/models.py:112
msgid "tag"
@@ -100,11 +100,11 @@ msgstr "етикети"
#: documents/models.py:118 documents/models.py:167
msgid "document type"
msgstr "тип на документа"
msgstr "вид на документа"
#: documents/models.py:119
msgid "document types"
msgstr "типове документи"
msgstr "видове документи"
#: documents/models.py:124
msgid "path"
@@ -257,59 +257,59 @@ msgstr "дневници"
#: documents/models.py:398
msgid "Table"
msgstr ""
msgstr "Таблица"
#: documents/models.py:399
msgid "Small Cards"
msgstr ""
msgstr "Малки картинки"
#: documents/models.py:400
msgid "Large Cards"
msgstr ""
msgstr "Големи картинки"
#: documents/models.py:403
msgid "Title"
msgstr ""
msgstr "Име"
#: documents/models.py:404
msgid "Created"
msgstr ""
msgstr "Създаден"
#: documents/models.py:405
msgid "Added"
msgstr ""
msgstr "Добавен"
#: documents/models.py:406
msgid "Tags"
msgstr ""
msgstr "Тагове"
#: documents/models.py:407
msgid "Correspondent"
msgstr ""
msgstr "Кореспондент"
#: documents/models.py:408
msgid "Document Type"
msgstr ""
msgstr "Вид документ"
#: documents/models.py:409
msgid "Storage Path"
msgstr ""
msgstr "Директории за съхранение"
#: documents/models.py:410
msgid "Note"
msgstr ""
msgstr "Бележка"
#: documents/models.py:411
msgid "Owner"
msgstr ""
msgstr "Собственик"
#: documents/models.py:412
msgid "Shared"
msgstr ""
msgstr "Споделени"
#: documents/models.py:413
msgid "ASN"
msgstr ""
msgstr "Архивен номер"
#: documents/models.py:419
msgid "show on dashboard"
@@ -329,11 +329,11 @@ msgstr "обратно сортиране"
#: documents/models.py:434
msgid "View page size"
msgstr ""
msgstr "Големина на страница"
#: documents/models.py:442
msgid "View display mode"
msgstr ""
msgstr "Режим на изгледа"
#: documents/models.py:449
msgid "Document display fields"
@@ -697,7 +697,7 @@ msgstr "инстанции на персонализирани полета"
#: documents/models.py:954
msgid "Consumption Started"
msgstr ""
msgstr "Започната обработка"
#: documents/models.py:955
msgid "Document Added"
@@ -849,11 +849,11 @@ msgstr "прехахни всички кореспонденти"
#: documents/models.py:1193
msgid "remove these storage path(s)"
msgstr ""
msgstr "изтрий тези директории"
#: documents/models.py:1198
msgid "remove all storage paths"
msgstr ""
msgstr "изтрий всички директории"
#: documents/models.py:1205
msgid "remove these owner(s)"
@@ -1053,7 +1053,7 @@ msgstr "Вашата нова парола е зададена. Вече мож
#: documents/templates/account/signup.html:5
msgid "Paperless-ngx sign up"
msgstr ""
msgstr "Регистрация"
#: documents/templates/account/signup.html:10
#, python-format
@@ -1115,7 +1115,7 @@ msgstr "Продължи"
#: documents/templates/socialaccount/signup.html:5
msgid "Paperless-ngx social account sign up"
msgstr ""
msgstr "Влез със социална мрежа"
#: documents/templates/socialaccount/signup.html:10
#, python-format
@@ -1171,7 +1171,7 @@ msgstr "пропусни"
#: paperless/models.py:40
msgid "redo"
msgstr ""
msgstr "повтори"
#: paperless/models.py:41
msgid "force"
@@ -1195,7 +1195,7 @@ msgstr "винаги"
#: paperless/models.py:60
msgid "clean"
msgstr ""
msgstr "изтрий"
#: paperless/models.py:61
msgid "clean-final"
@@ -1243,11 +1243,11 @@ msgstr "Задава режима OCR"
#: paperless/models.py:116
msgid "Controls the generation of an archive file"
msgstr ""
msgstr "Управлява архивирането"
#: paperless/models.py:124
msgid "Sets image DPI fallback value"
msgstr ""
msgstr "Настройва DPI стойност по подразбиране за изображенията"
#: paperless/models.py:131
msgid "Controls the unpaper cleaning"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-05-19 11:07-0700\n"
"PO-Revision-Date: 2024-05-24 12:09\n"
"PO-Revision-Date: 2024-06-18 12:10\n"
"Last-Translator: \n"
"Language-Team: Catalan\n"
"Language: ca_ES\n"
@@ -1519,7 +1519,7 @@ msgstr "norma e-mail"
#: paperless_mail/models.py:61
msgid "mail rules"
msgstr "normes e-mail"
msgstr "normes correu"
#: paperless_mail/models.py:64 paperless_mail/models.py:75
msgid "Only process attachments."

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-05-19 11:07-0700\n"
"PO-Revision-Date: 2024-06-02 00:27\n"
"PO-Revision-Date: 2024-06-19 00:25\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Language: de_DE\n"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-05-19 11:07-0700\n"
"PO-Revision-Date: 2024-05-20 16:40\n"
"PO-Revision-Date: 2024-06-09 12:09\n"
"Last-Translator: \n"
"Language-Team: Finnish\n"
"Language: fi_FI\n"
@@ -47,11 +47,11 @@ msgstr "Säännöllinen lauseke (regex)"
#: documents/models.py:58 documents/models.py:951
msgid "Fuzzy word"
msgstr ""
msgstr "Sumea sana"
#: documents/models.py:59
msgid "Automatic"
msgstr ""
msgstr "Automaattinen"
#: documents/models.py:62 documents/models.py:416 documents/models.py:1267
#: paperless_mail/models.py:18 paperless_mail/models.py:96
@@ -261,11 +261,11 @@ msgstr ""
#: documents/models.py:399
msgid "Small Cards"
msgstr ""
msgstr "Pienet kortit"
#: documents/models.py:400
msgid "Large Cards"
msgstr ""
msgstr "Suuret kortit"
#: documents/models.py:403
msgid "Title"
@@ -273,15 +273,15 @@ msgstr ""
#: documents/models.py:404
msgid "Created"
msgstr ""
msgstr "Luotu"
#: documents/models.py:405
msgid "Added"
msgstr ""
msgstr "Lisätty"
#: documents/models.py:406
msgid "Tags"
msgstr ""
msgstr "Tunnisteet"
#: documents/models.py:407
msgid "Correspondent"
@@ -289,7 +289,7 @@ msgstr ""
#: documents/models.py:408
msgid "Document Type"
msgstr ""
msgstr "Asiakirjan tyyppi"
#: documents/models.py:409
msgid "Storage Path"
@@ -301,11 +301,11 @@ msgstr ""
#: documents/models.py:411
msgid "Owner"
msgstr ""
msgstr "Omistaja"
#: documents/models.py:412
msgid "Shared"
msgstr ""
msgstr "Jaettu"
#: documents/models.py:413
msgid "ASN"
@@ -313,7 +313,7 @@ msgstr ""
#: documents/models.py:419
msgid "show on dashboard"
msgstr "näytä etusivulla"
msgstr "näytä koontinäytöllä"
#: documents/models.py:422
msgid "show in sidebar"
@@ -329,7 +329,7 @@ msgstr "lajittele käänteisesti"
#: documents/models.py:434
msgid "View page size"
msgstr ""
msgstr "Näytä sivun koko"
#: documents/models.py:442
msgid "View display mode"
@@ -661,7 +661,7 @@ msgstr ""
#: documents/models.py:805
msgid "Integer"
msgstr ""
msgstr "Kokonaisluku"
#: documents/models.py:806
msgid "Float"
@@ -829,15 +829,15 @@ msgstr ""
#: documents/models.py:1162
msgid "remove all tags"
msgstr ""
msgstr "poista kaikki tunnisteet"
#: documents/models.py:1169
msgid "remove these document type(s)"
msgstr ""
msgstr "poista nämä asiakirjatyypit"
#: documents/models.py:1174
msgid "remove all document types"
msgstr ""
msgstr "poista kaikki asiakirjatyypit"
#: documents/models.py:1181
msgid "remove these correspondent(s)"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-05-19 11:07-0700\n"
"PO-Revision-Date: 2024-06-03 00:26\n"
"PO-Revision-Date: 2024-06-19 00:25\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Language: fr_FR\n"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-05-19 11:07-0700\n"
"PO-Revision-Date: 2024-05-20 16:41\n"
"PO-Revision-Date: 2024-06-03 12:10\n"
"Last-Translator: \n"
"Language-Team: Italian\n"
"Language: it_IT\n"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-05-19 11:07-0700\n"
"PO-Revision-Date: 2024-05-20 16:41\n"
"PO-Revision-Date: 2024-06-05 12:10\n"
"Last-Translator: \n"
"Language-Team: Korean\n"
"Language: ko_KR\n"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-05-19 11:07-0700\n"
"PO-Revision-Date: 2024-05-20 16:41\n"
"PO-Revision-Date: 2024-06-10 12:10\n"
"Last-Translator: \n"
"Language-Team: Polish\n"
"Language: pl_PL\n"
@@ -501,19 +501,19 @@ msgstr "jest udostępniony przeze mnie"
#: documents/models.py:503
msgid "has custom fields"
msgstr ""
msgstr "posiada pola dodatkowe"
#: documents/models.py:504
msgid "has custom field in"
msgstr ""
msgstr "ma dodatkowe pole w"
#: documents/models.py:505
msgid "does not have custom field in"
msgstr ""
msgstr "nie ma dodatkowego pola w"
#: documents/models.py:506
msgid "does not have custom field"
msgstr ""
msgstr "nie ma pola dodatkowego"
#: documents/models.py:516
msgid "rule type"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-05-19 11:07-0700\n"
"PO-Revision-Date: 2024-05-20 16:41\n"
"PO-Revision-Date: 2024-06-14 00:25\n"
"Last-Translator: \n"
"Language-Team: Portuguese\n"
"Language: pt_PT\n"
@@ -501,19 +501,19 @@ msgstr "é partilhado por mim"
#: documents/models.py:503
msgid "has custom fields"
msgstr ""
msgstr "personalizado"
#: documents/models.py:504
msgid "has custom field in"
msgstr ""
msgstr "tem um campo personalizado em"
#: documents/models.py:505
msgid "does not have custom field in"
msgstr ""
msgstr "não tem um campo personalizado"
#: documents/models.py:506
msgid "does not have custom field"
msgstr ""
msgstr "não tem um campo personalizado"
#: documents/models.py:516
msgid "rule type"
@@ -741,15 +741,15 @@ msgstr "Consumir apenas documentos que correspondam inteiramente ao nome de arqu
#: documents/models.py:1004
msgid "filter documents from this mail rule"
msgstr ""
msgstr "filtrar documentos desta regra de e-mail"
#: documents/models.py:1020
msgid "has these tag(s)"
msgstr ""
msgstr "tem esta(s) tag(s)"
#: documents/models.py:1028
msgid "has this document type"
msgstr ""
msgstr "tem esta categoria de documento"
#: documents/models.py:1036
msgid "has this correspondent"
@@ -765,11 +765,11 @@ msgstr ""
#: documents/models.py:1051
msgid "Assignment"
msgstr ""
msgstr "Atribuição"
#: documents/models.py:1055
msgid "Removal"
msgstr ""
msgstr "Remoção"
#: documents/models.py:1059
msgid "Workflow Action Type"
@@ -777,11 +777,11 @@ msgstr ""
#: documents/models.py:1065
msgid "assign title"
msgstr ""
msgstr "atribuir título"
#: documents/models.py:1070
msgid "Assign a document title, can include some placeholders, see documentation."
msgstr ""
msgstr "Atribuir um título de documento, pode incluir alguns placeholders, consulte a documentação."
#: documents/models.py:1079 paperless_mail/models.py:219
msgid "assign this tag"
@@ -797,7 +797,7 @@ msgstr "atribuir este correspondente"
#: documents/models.py:1106
msgid "assign this storage path"
msgstr ""
msgstr "atribuir este caminho de armazenamento"
#: documents/models.py:1115
msgid "assign this owner"
@@ -805,11 +805,11 @@ msgstr ""
#: documents/models.py:1122
msgid "grant view permissions to these users"
msgstr ""
msgstr "conceder permissões de visualização para estes utilizadores"
#: documents/models.py:1129
msgid "grant view permissions to these groups"
msgstr ""
msgstr "conceder permissões de visualização para estes grupos"
#: documents/models.py:1136
msgid "grant change permissions to these users"
@@ -837,7 +837,7 @@ msgstr ""
#: documents/models.py:1174
msgid "remove all document types"
msgstr ""
msgstr "remover todos os tipos de documentos"
#: documents/models.py:1181
msgid "remove these correspondent(s)"
@@ -845,7 +845,7 @@ msgstr ""
#: documents/models.py:1186
msgid "remove all correspondents"
msgstr ""
msgstr "remover todos os correspondentes"
#: documents/models.py:1193
msgid "remove these storage path(s)"
@@ -861,7 +861,7 @@ msgstr ""
#: documents/models.py:1210
msgid "remove all owners"
msgstr ""
msgstr "remover todos os donos"
#: documents/models.py:1217
msgid "remove view permissions for these users"
@@ -881,15 +881,15 @@ msgstr ""
#: documents/models.py:1243
msgid "remove all permissions"
msgstr ""
msgstr "remover todas as permissões"
#: documents/models.py:1250
msgid "remove these custom fields"
msgstr ""
msgstr "remover esses campos personalizados"
#: documents/models.py:1255
msgid "remove all custom fields"
msgstr ""
msgstr "remover todos os campos personalizados"
#: documents/models.py:1259
msgid "workflow action"
@@ -909,11 +909,11 @@ msgstr ""
#: documents/models.py:1282
msgid "actions"
msgstr ""
msgstr "ações"
#: documents/models.py:1285
msgid "enabled"
msgstr ""
msgstr "ativado"
#: documents/serialisers.py:119
#, python-format
@@ -979,15 +979,15 @@ msgstr "Esqueceu-se da sua palavra-passe?"
#: documents/templates/account/login.html:45
#: documents/templates/account/signup.html:49
msgid "or sign in via"
msgstr ""
msgstr "ou inicie sessão com"
#: documents/templates/account/password_reset.html:5
msgid "Paperless-ngx reset password request"
msgstr ""
msgstr "pedido de redefinição da password para Paperless-ngx"
#: documents/templates/account/password_reset.html:9
msgid "Enter your email address below, and we'll email instructions for setting a new one."
msgstr ""
msgstr "Insira o seu endereço de e-mail abaixo e enviaremos instruções para definir um novo."
#: documents/templates/account/password_reset.html:12
msgid "An error occurred. Please try again."
@@ -1007,7 +1007,7 @@ msgstr ""
#: documents/templates/account/password_reset_done.html:9
msgid "Check your inbox."
msgstr ""
msgstr "Verifique a sua caixa de entrada."
#: documents/templates/account/password_reset_done.html:13
msgid "We've emailed you instructions for setting your password. You should receive the email shortly!"
@@ -1019,27 +1019,27 @@ msgstr ""
#: documents/templates/account/password_reset_from_key.html:9
msgid "Set a new password."
msgstr ""
msgstr "Definir uma nova palavra passe."
#: documents/templates/account/password_reset_from_key.html:15
msgid "request a new password reset"
msgstr ""
msgstr "solicitar uma nova redefinição de senha"
#: documents/templates/account/password_reset_from_key.html:17
msgid "New Password"
msgstr ""
msgstr "Nova palavra-passe"
#: documents/templates/account/password_reset_from_key.html:18
msgid "Confirm Password"
msgstr ""
msgstr "Confirmar Palavra-passe"
#: documents/templates/account/password_reset_from_key.html:28
msgid "Change my password"
msgstr ""
msgstr "Alterar a minha palavra-passe"
#: documents/templates/account/password_reset_from_key_done.html:5
msgid "Paperless-ngx reset password complete"
msgstr ""
msgstr "Pedido de redefinição da password para Paperless-ngx"
#: documents/templates/account/password_reset_from_key_done.html:9
msgid "Password reset complete."
@@ -1087,11 +1087,11 @@ msgstr "Aqui está uma hiperligação para os documentos."
#: documents/templates/paperless-ngx/base.html:55
msgid "Share link was not found."
msgstr ""
msgstr "O link de partilha não foi encontrado."
#: documents/templates/paperless-ngx/base.html:59
msgid "Share link has expired."
msgstr ""
msgstr "O link de partilha expirou."
#: documents/templates/socialaccount/authentication_error.html:5
#: documents/templates/socialaccount/login.html:5
@@ -1110,7 +1110,7 @@ msgstr ""
#: documents/templates/socialaccount/login.html:13
msgid "Continue"
msgstr ""
msgstr "Continuar"
#: documents/templates/socialaccount/signup.html:5
msgid "Paperless-ngx social account sign up"
@@ -1146,87 +1146,87 @@ msgstr "Paperless"
#: paperless/models.py:26
msgid "pdf"
msgstr ""
msgstr "pdf"
#: paperless/models.py:27
msgid "pdfa"
msgstr ""
msgstr "pdfa"
#: paperless/models.py:28
msgid "pdfa-1"
msgstr ""
msgstr "pdfa-1"
#: paperless/models.py:29
msgid "pdfa-2"
msgstr ""
msgstr "pdfa-2"
#: paperless/models.py:30
msgid "pdfa-3"
msgstr ""
msgstr "pdfa-3"
#: paperless/models.py:39
msgid "skip"
msgstr ""
msgstr "ignorar"
#: paperless/models.py:40
msgid "redo"
msgstr ""
msgstr "refazer"
#: paperless/models.py:41
msgid "force"
msgstr ""
msgstr "forçar"
#: paperless/models.py:42
msgid "skip_noarchive"
msgstr ""
msgstr "skip_noarchive"
#: paperless/models.py:50
msgid "never"
msgstr ""
msgstr "nunca"
#: paperless/models.py:51
msgid "with_text"
msgstr ""
msgstr "with_text"
#: paperless/models.py:52
msgid "always"
msgstr ""
msgstr "sempre"
#: paperless/models.py:60
msgid "clean"
msgstr ""
msgstr "limpar"
#: paperless/models.py:61
msgid "clean-final"
msgstr ""
msgstr "clean-final"
#: paperless/models.py:62
msgid "none"
msgstr ""
msgstr "nenhum"
#: paperless/models.py:70
msgid "LeaveColorUnchanged"
msgstr ""
msgstr "LeaveColorUnchanged"
#: paperless/models.py:71
msgid "RGB"
msgstr ""
msgstr "RGB"
#: paperless/models.py:72
msgid "UseDeviceIndependentColor"
msgstr ""
msgstr "UseDeviceIndependentColor"
#: paperless/models.py:73
msgid "Gray"
msgstr ""
msgstr "Cinzento"
#: paperless/models.py:74
msgid "CMYK"
msgstr ""
msgstr "CMYK"
#: paperless/models.py:83
msgid "Sets the output PDF type"
msgstr ""
msgstr "Define o tipo de PDF de saída"
#: paperless/models.py:95
msgid "Do OCR from page 1 to this value"
@@ -1234,11 +1234,11 @@ msgstr ""
#: paperless/models.py:101
msgid "Do OCR using these languages"
msgstr ""
msgstr "Faça OCR usando esses idiomas"
#: paperless/models.py:108
msgid "Sets the OCR mode"
msgstr ""
msgstr "Define o modo OCR"
#: paperless/models.py:116
msgid "Controls the generation of an archive file"
@@ -1258,7 +1258,7 @@ msgstr ""
#: paperless/models.py:141
msgid "Enables page rotation"
msgstr ""
msgstr "Ativa a rotação da página"
#: paperless/models.py:146
msgid "Sets the threshold for rotation of pages"
@@ -1278,15 +1278,15 @@ msgstr ""
#: paperless/models.py:171
msgid "Application title"
msgstr ""
msgstr "Título da aplicação"
#: paperless/models.py:178
msgid "Application logo"
msgstr ""
msgstr "Logótipo da aplicação"
#: paperless/models.py:188
msgid "paperless application settings"
msgstr ""
msgstr "configurações do paperless"
#: paperless/settings.py:660
msgid "English (US)"
@@ -1298,7 +1298,7 @@ msgstr "Árabe"
#: paperless/settings.py:662
msgid "Afrikaans"
msgstr ""
msgstr "Africano"
#: paperless/settings.py:663
msgid "Belarusian"
@@ -1306,7 +1306,7 @@ msgstr "Bielorrusso"
#: paperless/settings.py:664
msgid "Bulgarian"
msgstr ""
msgstr "Búlgaro"
#: paperless/settings.py:665
msgid "Catalan"
@@ -1326,7 +1326,7 @@ msgstr "Deutsch"
#: paperless/settings.py:669
msgid "Greek"
msgstr ""
msgstr "Grego"
#: paperless/settings.py:670
msgid "English (GB)"
@@ -1346,7 +1346,7 @@ msgstr "Français"
#: paperless/settings.py:674
msgid "Hungarian"
msgstr ""
msgstr "Húngaro"
#: paperless/settings.py:675
msgid "Italian"
@@ -1354,7 +1354,7 @@ msgstr "Italiano"
#: paperless/settings.py:676
msgid "Japanese"
msgstr ""
msgstr "Japonês"
#: paperless/settings.py:677
msgid "Luxembourgish"
@@ -1362,7 +1362,7 @@ msgstr "Luxemburguês"
#: paperless/settings.py:678
msgid "Norwegian"
msgstr ""
msgstr "Norueguês"
#: paperless/settings.py:679
msgid "Dutch"
@@ -1410,7 +1410,7 @@ msgstr "Turco"
#: paperless/settings.py:690
msgid "Ukrainian"
msgstr ""
msgstr "Ucraniano"
#: paperless/settings.py:691
msgid "Chinese Simplified"
@@ -1566,7 +1566,7 @@ msgstr "Utilizar o nome do anexo como título"
#: paperless_mail/models.py:88
msgid "Do not assign title from rule"
msgstr ""
msgstr "Não atribuir título a partir da regra"
#: paperless_mail/models.py:91
msgid "Do not assign a correspondent"
@@ -1614,11 +1614,11 @@ msgstr "filtrar corpo"
#: paperless_mail/models.py:146
msgid "filter attachment filename inclusive"
msgstr ""
msgstr "filtrar nome do arquivo anexo"
#: paperless_mail/models.py:158
msgid "filter attachment filename exclusive"
msgstr ""
msgstr "filtrar nome do arquivo anexo"
#: paperless_mail/models.py:163
msgid "Do not consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
@@ -1666,7 +1666,7 @@ msgstr "atribuir correspondente de"
#: paperless_mail/models.py:245
msgid "Assign the rule owner to documents"
msgstr ""
msgstr "Atribuir o proprietário da regra aos documentos"
#: paperless_mail/models.py:271
msgid "uid"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-05-19 11:07-0700\n"
"PO-Revision-Date: 2024-05-20 16:41\n"
"PO-Revision-Date: 2024-06-03 12:10\n"
"Last-Translator: \n"
"Language-Team: Russian\n"
"Language: ru_RU\n"
@@ -257,15 +257,15 @@ msgstr "логи"
#: documents/models.py:398
msgid "Table"
msgstr ""
msgstr "Таблица"
#: documents/models.py:399
msgid "Small Cards"
msgstr ""
msgstr "Маленькие карточки"
#: documents/models.py:400
msgid "Large Cards"
msgstr ""
msgstr "Большие карточки"
#: documents/models.py:403
msgid "Title"
@@ -277,11 +277,11 @@ msgstr ""
#: documents/models.py:405
msgid "Added"
msgstr ""
msgstr "Добавлено"
#: documents/models.py:406
msgid "Tags"
msgstr ""
msgstr "Теги"
#: documents/models.py:407
msgid "Correspondent"
@@ -289,7 +289,7 @@ msgstr ""
#: documents/models.py:408
msgid "Document Type"
msgstr ""
msgstr "Тип документа"
#: documents/models.py:409
msgid "Storage Path"
@@ -297,11 +297,11 @@ msgstr ""
#: documents/models.py:410
msgid "Note"
msgstr ""
msgstr "Заметка"
#: documents/models.py:411
msgid "Owner"
msgstr ""
msgstr "Владелец"
#: documents/models.py:412
msgid "Shared"
@@ -861,7 +861,7 @@ msgstr ""
#: documents/models.py:1210
msgid "remove all owners"
msgstr ""
msgstr "удалить всех владельцев"
#: documents/models.py:1217
msgid "remove view permissions for these users"
@@ -881,7 +881,7 @@ msgstr ""
#: documents/models.py:1243
msgid "remove all permissions"
msgstr ""
msgstr "удалить все разрешения"
#: documents/models.py:1250
msgid "remove these custom fields"
@@ -1052,7 +1052,7 @@ msgstr "Ваш новый пароль был установлен. Теперь
#: documents/templates/account/signup.html:5
msgid "Paperless-ngx sign up"
msgstr ""
msgstr "Войти в Paperless-ngx"
#: documents/templates/account/signup.html:10
#, python-format
@@ -1066,7 +1066,7 @@ msgstr ""
#: documents/templates/account/signup.html:18
msgid "Password (again)"
msgstr ""
msgstr "Пароль (ещё раз)"
#: documents/templates/account/signup.html:36
#: documents/templates/socialaccount/signup.html:27

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-05-19 11:07-0700\n"
"PO-Revision-Date: 2024-05-28 00:24\n"
"PO-Revision-Date: 2024-06-04 12:10\n"
"Last-Translator: \n"
"Language-Team: Serbian (Latin)\n"
"Language: sr_CS\n"
@@ -47,11 +47,11 @@ msgstr "Regularni izraz"
#: documents/models.py:58 documents/models.py:951
msgid "Fuzzy word"
msgstr ""
msgstr "Nejasna reč"
#: documents/models.py:59
msgid "Automatic"
msgstr ""
msgstr "Automatski"
#: documents/models.py:62 documents/models.py:416 documents/models.py:1267
#: paperless_mail/models.py:18 paperless_mail/models.py:96
@@ -257,55 +257,55 @@ msgstr "logovi"
#: documents/models.py:398
msgid "Table"
msgstr ""
msgstr "Tabela"
#: documents/models.py:399
msgid "Small Cards"
msgstr ""
msgstr "Male kartice"
#: documents/models.py:400
msgid "Large Cards"
msgstr ""
msgstr "Velike kartice"
#: documents/models.py:403
msgid "Title"
msgstr ""
msgstr "Naslov"
#: documents/models.py:404
msgid "Created"
msgstr ""
msgstr "Kreirano"
#: documents/models.py:405
msgid "Added"
msgstr ""
msgstr "Dodato"
#: documents/models.py:406
msgid "Tags"
msgstr ""
msgstr "Oznake"
#: documents/models.py:407
msgid "Correspondent"
msgstr ""
msgstr "Korespondent"
#: documents/models.py:408
msgid "Document Type"
msgstr ""
msgstr "Tip dokumenta"
#: documents/models.py:409
msgid "Storage Path"
msgstr ""
msgstr "Putanje skladišta"
#: documents/models.py:410
msgid "Note"
msgstr ""
msgstr "Beleška"
#: documents/models.py:411
msgid "Owner"
msgstr ""
msgstr "Vlasnik"
#: documents/models.py:412
msgid "Shared"
msgstr ""
msgstr "Deljeno"
#: documents/models.py:413
msgid "ASN"
@@ -329,7 +329,7 @@ msgstr "obrnuto sortiranje"
#: documents/models.py:434
msgid "View page size"
msgstr ""
msgstr "Veličinа stranice za prikaz"
#: documents/models.py:442
msgid "View display mode"
@@ -337,7 +337,7 @@ msgstr ""
#: documents/models.py:449
msgid "Document display fields"
msgstr ""
msgstr "Polja prikaza dokumenta"
#: documents/models.py:456 documents/models.py:513
msgid "saved view"
@@ -433,7 +433,7 @@ msgstr "upit za ceo tekst"
#: documents/models.py:486
msgid "more like this"
msgstr ""
msgstr "više ovakvih"
#: documents/models.py:487
msgid "has tags in"
@@ -453,15 +453,15 @@ msgstr "putanja skladišta je"
#: documents/models.py:491
msgid "has correspondent in"
msgstr ""
msgstr "postoji korespondent"
#: documents/models.py:492
msgid "does not have correspondent in"
msgstr ""
msgstr "ne postoji korespondent"
#: documents/models.py:493
msgid "has document type in"
msgstr ""
msgstr "postoji tip dokumenta"
#: documents/models.py:494
msgid "does not have document type in"

View File

@@ -62,7 +62,7 @@ def paths_check(app_configs, **kwargs):
return (
path_check("PAPERLESS_DATA_DIR", settings.DATA_DIR)
+ path_check("PAPERLESS_TRASH_DIR", settings.TRASH_DIR)
+ path_check("PAPERLESS_EMPTY_TRASH_DIR", settings.EMPTY_TRASH_DIR)
+ path_check("PAPERLESS_MEDIA_ROOT", settings.MEDIA_ROOT)
+ path_check("PAPERLESS_CONSUMPTION_DIR", settings.CONSUMPTION_DIR)
)

View File

@@ -207,6 +207,17 @@ def _parse_beat_schedule() -> dict:
"expires": ((7.0 * 24.0) - 1.0) * 60.0 * 60.0,
},
},
{
"name": "Empty trash",
"env_key": "PAPERLESS_EMPTY_TRASH_TASK_CRON",
# Default daily at 01:00
"env_default": "0 1 * * *",
"task": "documents.tasks.empty_trash",
"options": {
# 1 hour before default schedule sends again
"expires": 23.0 * 60.0 * 60.0,
},
},
]
for task in tasks:
# Either get the environment setting or use the default
@@ -250,7 +261,11 @@ DATA_DIR = __get_path("PAPERLESS_DATA_DIR", BASE_DIR.parent / "data")
NLTK_DIR = __get_path("PAPERLESS_NLTK_DIR", "/usr/share/nltk_data")
TRASH_DIR = os.getenv("PAPERLESS_TRASH_DIR")
# Check deprecated setting first
EMPTY_TRASH_DIR = os.getenv(
"PAPERLESS_TRASH_DIR",
os.getenv("PAPERLESS_EMPTY_TRASH_DIR"),
)
# Lock file for synchronizing changes to the MEDIA directory across multiple
# threads.
@@ -1148,3 +1163,9 @@ EMAIL_SUBJECT_PREFIX: Final[str] = "[Paperless-ngx] "
if DEBUG: # pragma: no cover
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = BASE_DIR / "sent_emails"
###############################################################################
# Soft Delete
###############################################################################
EMPTY_TRASH_DELAY = max(__get_int("PAPERLESS_EMPTY_TRASH_DELAY", 30), 1)

View File

@@ -156,6 +156,7 @@ class TestCeleryScheduleParsing(TestCase):
CLASSIFIER_EXPIRE_TIME = 59.0 * 60.0
INDEX_EXPIRE_TIME = 23.0 * 60.0 * 60.0
SANITY_EXPIRE_TIME = ((7.0 * 24.0) - 1.0) * 60.0 * 60.0
EMPTY_TRASH_EXPIRE_TIME = 23.0 * 60.0 * 60.0
def test_schedule_configuration_default(self):
"""
@@ -190,6 +191,11 @@ class TestCeleryScheduleParsing(TestCase):
"schedule": crontab(minute=30, hour=0, day_of_week="sun"),
"options": {"expires": self.SANITY_EXPIRE_TIME},
},
"Empty trash": {
"task": "documents.tasks.empty_trash",
"schedule": crontab(minute=0, hour="1"),
"options": {"expires": self.EMPTY_TRASH_EXPIRE_TIME},
},
},
schedule,
)
@@ -232,6 +238,11 @@ class TestCeleryScheduleParsing(TestCase):
"schedule": crontab(minute=30, hour=0, day_of_week="sun"),
"options": {"expires": self.SANITY_EXPIRE_TIME},
},
"Empty trash": {
"task": "documents.tasks.empty_trash",
"schedule": crontab(minute=0, hour="1"),
"options": {"expires": self.EMPTY_TRASH_EXPIRE_TIME},
},
},
schedule,
)
@@ -266,6 +277,11 @@ class TestCeleryScheduleParsing(TestCase):
"schedule": crontab(minute=30, hour=0, day_of_week="sun"),
"options": {"expires": self.SANITY_EXPIRE_TIME},
},
"Empty trash": {
"task": "documents.tasks.empty_trash",
"schedule": crontab(minute=0, hour="1"),
"options": {"expires": self.EMPTY_TRASH_EXPIRE_TIME},
},
},
schedule,
)
@@ -286,6 +302,7 @@ class TestCeleryScheduleParsing(TestCase):
"PAPERLESS_TRAIN_TASK_CRON": "disable",
"PAPERLESS_SANITY_TASK_CRON": "disable",
"PAPERLESS_INDEX_TASK_CRON": "disable",
"PAPERLESS_EMPTY_TRASH_TASK_CRON": "disable",
},
):
schedule = _parse_beat_schedule()

View File

@@ -36,6 +36,7 @@ from documents.views import StoragePathViewSet
from documents.views import SystemStatusView
from documents.views import TagViewSet
from documents.views import TasksViewSet
from documents.views import TrashView
from documents.views import UiSettingsView
from documents.views import UnifiedSearchViewSet
from documents.views import WorkflowActionViewSet
@@ -159,6 +160,11 @@ urlpatterns = [
SystemStatusView.as_view(),
name="system_status",
),
re_path(
"^trash/",
TrashView.as_view(),
name="trash",
),
*api_router.urls,
],
),

View File

@@ -1,6 +1,6 @@
from typing import Final
__version__: Final[tuple[int, int, int]] = (2, 9, 0)
__version__: Final[tuple[int, int, int]] = (2, 10, 1)
# Version string like X.Y.Z
__full_version_str__: Final[str] = ".".join(map(str, __version__))
# Version string like X.Y