Merge remote-tracking branch 'origin/dev' into feature-devcontainer
This commit is contained in:
@@ -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),
|
||||
),
|
||||
]
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
155
src/documents/tests/test_api_trash.py
Normal file
155
src/documents/tests/test_api_trash.py
Normal 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])
|
||||
@@ -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",
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user