mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-03-08 02:01:22 +00:00
Compare commits
3 Commits
dev
...
feature-mi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
885b1078dc | ||
|
|
db1e4ce432 | ||
|
|
0b3cdd6934 |
@@ -304,7 +304,7 @@ class PaperlessCommand(RichCommand):
|
|||||||
|
|
||||||
Progress output is directed to stderr to match the convention that
|
Progress output is directed to stderr to match the convention that
|
||||||
progress bars are transient UI feedback, not command output. This
|
progress bars are transient UI feedback, not command output. This
|
||||||
mirrors tqdm's default behavior and prevents progress bar rendering
|
mirrors the convention that progress bars are transient UI feedback and prevents progress bar rendering
|
||||||
from interfering with stdout-based assertions in tests or piped
|
from interfering with stdout-based assertions in tests or piped
|
||||||
command output.
|
command output.
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class Command(PaperlessCommand):
|
|||||||
"modified) after their initial import."
|
"modified) after their initial import."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
supports_progress_bar = True
|
||||||
supports_multiprocessing = True
|
supports_multiprocessing = True
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ from itertools import islice
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import tqdm
|
|
||||||
from allauth.mfa.models import Authenticator
|
from allauth.mfa.models import Authenticator
|
||||||
from allauth.socialaccount.models import SocialAccount
|
from allauth.socialaccount.models import SocialAccount
|
||||||
from allauth.socialaccount.models import SocialApp
|
from allauth.socialaccount.models import SocialApp
|
||||||
@@ -19,7 +18,6 @@ from django.contrib.auth.models import Permission
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core import serializers
|
from django.core import serializers
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
from django.core.management.base import CommandError
|
from django.core.management.base import CommandError
|
||||||
from django.core.serializers.json import DjangoJSONEncoder
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
@@ -38,6 +36,7 @@ if settings.AUDIT_LOG_ENABLED:
|
|||||||
|
|
||||||
from documents.file_handling import delete_empty_directories
|
from documents.file_handling import delete_empty_directories
|
||||||
from documents.file_handling import generate_filename
|
from documents.file_handling import generate_filename
|
||||||
|
from documents.management.commands.base import PaperlessCommand
|
||||||
from documents.management.commands.mixins import CryptMixin
|
from documents.management.commands.mixins import CryptMixin
|
||||||
from documents.models import Correspondent
|
from documents.models import Correspondent
|
||||||
from documents.models import CustomField
|
from documents.models import CustomField
|
||||||
@@ -81,14 +80,18 @@ def serialize_queryset_batched(
|
|||||||
yield serializers.serialize("python", chunk)
|
yield serializers.serialize("python", chunk)
|
||||||
|
|
||||||
|
|
||||||
class Command(CryptMixin, BaseCommand):
|
class Command(CryptMixin, PaperlessCommand):
|
||||||
help = (
|
help = (
|
||||||
"Decrypt and rename all files in our collection into a given target "
|
"Decrypt and rename all files in our collection into a given target "
|
||||||
"directory. And include a manifest file containing document data for "
|
"directory. And include a manifest file containing document data for "
|
||||||
"easy import."
|
"easy import."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
supports_progress_bar = True
|
||||||
|
supports_multiprocessing = False
|
||||||
|
|
||||||
def add_arguments(self, parser) -> None:
|
def add_arguments(self, parser) -> None:
|
||||||
|
super().add_arguments(parser)
|
||||||
parser.add_argument("target")
|
parser.add_argument("target")
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@@ -195,13 +198,6 @@ class Command(CryptMixin, BaseCommand):
|
|||||||
help="If set, only the database will be imported, not files",
|
help="If set, only the database will be imported, not files",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--no-progress-bar",
|
|
||||||
default=False,
|
|
||||||
action="store_true",
|
|
||||||
help="If set, the progress bar will not be shown",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--passphrase",
|
"--passphrase",
|
||||||
help="If provided, is used to encrypt sensitive data in the export",
|
help="If provided, is used to encrypt sensitive data in the export",
|
||||||
@@ -230,7 +226,6 @@ class Command(CryptMixin, BaseCommand):
|
|||||||
self.no_thumbnail: bool = options["no_thumbnail"]
|
self.no_thumbnail: bool = options["no_thumbnail"]
|
||||||
self.zip_export: bool = options["zip"]
|
self.zip_export: bool = options["zip"]
|
||||||
self.data_only: bool = options["data_only"]
|
self.data_only: bool = options["data_only"]
|
||||||
self.no_progress_bar: bool = options["no_progress_bar"]
|
|
||||||
self.passphrase: str | None = options.get("passphrase")
|
self.passphrase: str | None = options.get("passphrase")
|
||||||
self.batch_size: int = options["batch_size"]
|
self.batch_size: int = options["batch_size"]
|
||||||
|
|
||||||
@@ -347,10 +342,12 @@ class Command(CryptMixin, BaseCommand):
|
|||||||
document_manifest = manifest_dict["documents"]
|
document_manifest = manifest_dict["documents"]
|
||||||
|
|
||||||
# 3. Export files from each document
|
# 3. Export files from each document
|
||||||
for index, document_dict in tqdm.tqdm(
|
for index, document_dict in enumerate(
|
||||||
enumerate(document_manifest),
|
self.track(
|
||||||
total=len(document_manifest),
|
document_manifest,
|
||||||
disable=self.no_progress_bar,
|
description="Exporting documents...",
|
||||||
|
total=len(document_manifest),
|
||||||
|
),
|
||||||
):
|
):
|
||||||
document = document_map[document_dict["pk"]]
|
document = document_map[document_dict["pk"]]
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ def _process_and_match(work: _WorkPackage) -> _WorkResult:
|
|||||||
class Command(PaperlessCommand):
|
class Command(PaperlessCommand):
|
||||||
help = "Searches for documents where the content almost matches"
|
help = "Searches for documents where the content almost matches"
|
||||||
|
|
||||||
|
supports_progress_bar = True
|
||||||
supports_multiprocessing = True
|
supports_multiprocessing = True
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
|
|||||||
@@ -8,14 +8,12 @@ from pathlib import Path
|
|||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
from zipfile import is_zipfile
|
from zipfile import is_zipfile
|
||||||
|
|
||||||
import tqdm
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import FieldDoesNotExist
|
from django.core.exceptions import FieldDoesNotExist
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
from django.core.management.base import CommandError
|
from django.core.management.base import CommandError
|
||||||
from django.core.serializers.base import DeserializationError
|
from django.core.serializers.base import DeserializationError
|
||||||
from django.db import IntegrityError
|
from django.db import IntegrityError
|
||||||
@@ -25,6 +23,7 @@ from django.db.models.signals import post_save
|
|||||||
from filelock import FileLock
|
from filelock import FileLock
|
||||||
|
|
||||||
from documents.file_handling import create_source_path_directory
|
from documents.file_handling import create_source_path_directory
|
||||||
|
from documents.management.commands.base import PaperlessCommand
|
||||||
from documents.management.commands.mixins import CryptMixin
|
from documents.management.commands.mixins import CryptMixin
|
||||||
from documents.models import Correspondent
|
from documents.models import Correspondent
|
||||||
from documents.models import CustomField
|
from documents.models import CustomField
|
||||||
@@ -57,21 +56,18 @@ def disable_signal(sig, receiver, sender, *, weak: bool | None = None) -> Genera
|
|||||||
sig.connect(receiver=receiver, sender=sender, **kwargs)
|
sig.connect(receiver=receiver, sender=sender, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Command(CryptMixin, BaseCommand):
|
class Command(CryptMixin, PaperlessCommand):
|
||||||
help = (
|
help = (
|
||||||
"Using a manifest.json file, load the data from there, and import the "
|
"Using a manifest.json file, load the data from there, and import the "
|
||||||
"documents it refers to."
|
"documents it refers to."
|
||||||
)
|
)
|
||||||
|
|
||||||
def add_arguments(self, parser) -> None:
|
supports_progress_bar = True
|
||||||
parser.add_argument("source")
|
supports_multiprocessing = False
|
||||||
|
|
||||||
parser.add_argument(
|
def add_arguments(self, parser) -> None:
|
||||||
"--no-progress-bar",
|
super().add_arguments(parser)
|
||||||
default=False,
|
parser.add_argument("source")
|
||||||
action="store_true",
|
|
||||||
help="If set, the progress bar will not be shown",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--data-only",
|
"--data-only",
|
||||||
@@ -231,7 +227,6 @@ class Command(CryptMixin, BaseCommand):
|
|||||||
|
|
||||||
self.source = Path(options["source"]).resolve()
|
self.source = Path(options["source"]).resolve()
|
||||||
self.data_only: bool = options["data_only"]
|
self.data_only: bool = options["data_only"]
|
||||||
self.no_progress_bar: bool = options["no_progress_bar"]
|
|
||||||
self.passphrase: str | None = options.get("passphrase")
|
self.passphrase: str | None = options.get("passphrase")
|
||||||
self.version: str | None = None
|
self.version: str | None = None
|
||||||
self.salt: str | None = None
|
self.salt: str | None = None
|
||||||
@@ -365,7 +360,7 @@ class Command(CryptMixin, BaseCommand):
|
|||||||
filter(lambda r: r["model"] == "documents.document", self.manifest),
|
filter(lambda r: r["model"] == "documents.document", self.manifest),
|
||||||
)
|
)
|
||||||
|
|
||||||
for record in tqdm.tqdm(manifest_documents, disable=self.no_progress_bar):
|
for record in self.track(manifest_documents, description="Copying files..."):
|
||||||
document = Document.objects.get(pk=record["pk"])
|
document = Document.objects.get(pk=record["pk"])
|
||||||
|
|
||||||
doc_file = record[EXPORTER_FILE_NAME]
|
doc_file = record[EXPORTER_FILE_NAME]
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ from documents.tasks import index_reindex
|
|||||||
class Command(PaperlessCommand):
|
class Command(PaperlessCommand):
|
||||||
help = "Manages the document index."
|
help = "Manages the document index."
|
||||||
|
|
||||||
|
supports_progress_bar = True
|
||||||
|
supports_multiprocessing = False
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
super().add_arguments(parser)
|
super().add_arguments(parser)
|
||||||
parser.add_argument("command", choices=["reindex", "optimize"])
|
parser.add_argument("command", choices=["reindex", "optimize"])
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ from documents.tasks import llmindex_index
|
|||||||
class Command(PaperlessCommand):
|
class Command(PaperlessCommand):
|
||||||
help = "Manages the LLM-based vector index for Paperless."
|
help = "Manages the LLM-based vector index for Paperless."
|
||||||
|
|
||||||
|
supports_progress_bar = True
|
||||||
|
supports_multiprocessing = False
|
||||||
|
|
||||||
def add_arguments(self, parser: Any) -> None:
|
def add_arguments(self, parser: Any) -> None:
|
||||||
super().add_arguments(parser)
|
super().add_arguments(parser)
|
||||||
parser.add_argument("command", choices=["rebuild", "update"])
|
parser.add_argument("command", choices=["rebuild", "update"])
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ from documents.models import Document
|
|||||||
class Command(PaperlessCommand):
|
class Command(PaperlessCommand):
|
||||||
help = "Rename all documents"
|
help = "Rename all documents"
|
||||||
|
|
||||||
|
supports_progress_bar = True
|
||||||
|
supports_multiprocessing = False
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
for document in self.track(Document.objects.all(), description="Renaming..."):
|
for document in self.track(Document.objects.all(), description="Renaming..."):
|
||||||
post_save.send(Document, instance=document, created=False)
|
post_save.send(Document, instance=document, created=False)
|
||||||
|
|||||||
@@ -180,6 +180,9 @@ class Command(PaperlessCommand):
|
|||||||
"modified) after their initial import."
|
"modified) after their initial import."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
supports_progress_bar = True
|
||||||
|
supports_multiprocessing = False
|
||||||
|
|
||||||
def add_arguments(self, parser) -> None:
|
def add_arguments(self, parser) -> None:
|
||||||
super().add_arguments(parser)
|
super().add_arguments(parser)
|
||||||
parser.add_argument("-c", "--correspondent", default=False, action="store_true")
|
parser.add_argument("-c", "--correspondent", default=False, action="store_true")
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ _LEVEL_STYLE: dict[int, tuple[str, str]] = {
|
|||||||
class Command(PaperlessCommand):
|
class Command(PaperlessCommand):
|
||||||
help = "This command checks your document archive for issues."
|
help = "This command checks your document archive for issues."
|
||||||
|
|
||||||
|
supports_progress_bar = True
|
||||||
|
supports_multiprocessing = False
|
||||||
|
|
||||||
def _render_results(self, messages: SanityCheckMessages) -> None:
|
def _render_results(self, messages: SanityCheckMessages) -> None:
|
||||||
"""Render sanity check results as a Rich table."""
|
"""Render sanity check results as a Rich table."""
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ def _process_document(doc_id: int) -> None:
|
|||||||
class Command(PaperlessCommand):
|
class Command(PaperlessCommand):
|
||||||
help = "This will regenerate the thumbnails for all documents."
|
help = "This will regenerate the thumbnails for all documents."
|
||||||
|
|
||||||
|
supports_progress_bar = True
|
||||||
supports_multiprocessing = True
|
supports_multiprocessing = True
|
||||||
|
|
||||||
def add_arguments(self, parser) -> None:
|
def add_arguments(self, parser) -> None:
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
from argparse import ArgumentParser
|
|
||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
from cryptography.fernet import Fernet
|
from cryptography.fernet import Fernet
|
||||||
@@ -21,25 +20,6 @@ class CryptFields(TypedDict):
|
|||||||
fields: list[str]
|
fields: list[str]
|
||||||
|
|
||||||
|
|
||||||
class ProgressBarMixin:
|
|
||||||
"""
|
|
||||||
Many commands use a progress bar, which can be disabled
|
|
||||||
via this class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def add_argument_progress_bar_mixin(self, parser: ArgumentParser) -> None:
|
|
||||||
parser.add_argument(
|
|
||||||
"--no-progress-bar",
|
|
||||||
default=False,
|
|
||||||
action="store_true",
|
|
||||||
help="If set, the progress bar will not be shown",
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle_progress_bar_mixin(self, *args, **options) -> None:
|
|
||||||
self.no_progress_bar = options["no_progress_bar"]
|
|
||||||
self.use_progress_bar = not self.no_progress_bar
|
|
||||||
|
|
||||||
|
|
||||||
class CryptMixin:
|
class CryptMixin:
|
||||||
"""
|
"""
|
||||||
Fully based on:
|
Fully based on:
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ class Command(PaperlessCommand):
|
|||||||
|
|
||||||
help = "Prunes the audit logs of objects that no longer exist."
|
help = "Prunes the audit logs of objects that no longer exist."
|
||||||
|
|
||||||
|
supports_progress_bar = True
|
||||||
|
supports_multiprocessing = False
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
for log_entry in self.track(
|
for log_entry in self.track(
|
||||||
|
|||||||
Reference in New Issue
Block a user