mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-10-28 03:46:06 -05:00
Merge branch 'dev' into feature/any-all-filtering
This commit is contained in:
78
src/documents/admin.py
Executable file → Normal file
78
src/documents/admin.py
Executable file → Normal file
@@ -1,10 +1,6 @@
|
||||
from django.contrib import admin
|
||||
from django.utils.html import format_html, format_html_join
|
||||
from django.utils.safestring import mark_safe
|
||||
from whoosh.writing import AsyncWriter
|
||||
|
||||
from . import index
|
||||
from .models import Correspondent, Document, DocumentType, Log, Tag, \
|
||||
from .models import Correspondent, Document, DocumentType, Tag, \
|
||||
SavedView, SavedViewFilterRule
|
||||
|
||||
|
||||
@@ -23,12 +19,12 @@ class TagAdmin(admin.ModelAdmin):
|
||||
|
||||
list_display = (
|
||||
"name",
|
||||
"colour",
|
||||
"color",
|
||||
"match",
|
||||
"matching_algorithm"
|
||||
)
|
||||
list_filter = ("colour", "matching_algorithm")
|
||||
list_editable = ("colour", "match", "matching_algorithm")
|
||||
list_filter = ("color", "matching_algorithm")
|
||||
list_editable = ("color", "match", "matching_algorithm")
|
||||
|
||||
|
||||
class DocumentTypeAdmin(admin.ModelAdmin):
|
||||
@@ -50,26 +46,31 @@ class DocumentAdmin(admin.ModelAdmin):
|
||||
"modified",
|
||||
"mime_type",
|
||||
"storage_type",
|
||||
"filename")
|
||||
"filename",
|
||||
"checksum",
|
||||
"archive_filename",
|
||||
"archive_checksum"
|
||||
)
|
||||
|
||||
list_display_links = ("title",)
|
||||
|
||||
list_display = (
|
||||
"correspondent",
|
||||
"id",
|
||||
"title",
|
||||
"tags_",
|
||||
"created",
|
||||
"mime_type",
|
||||
"filename",
|
||||
"archive_filename"
|
||||
)
|
||||
|
||||
list_filter = (
|
||||
"document_type",
|
||||
"tags",
|
||||
"correspondent"
|
||||
("mime_type"),
|
||||
("archive_serial_number", admin.EmptyFieldListFilter),
|
||||
("archive_filename", admin.EmptyFieldListFilter),
|
||||
)
|
||||
|
||||
filter_horizontal = ("tags",)
|
||||
|
||||
ordering = ["-created"]
|
||||
ordering = ["-id"]
|
||||
|
||||
date_hierarchy = "created"
|
||||
|
||||
@@ -81,56 +82,24 @@ class DocumentAdmin(admin.ModelAdmin):
|
||||
created_.short_description = "Created"
|
||||
|
||||
def delete_queryset(self, request, queryset):
|
||||
ix = index.open_index()
|
||||
with AsyncWriter(ix) as writer:
|
||||
from documents import index
|
||||
|
||||
with index.open_index_writer() as writer:
|
||||
for o in queryset:
|
||||
index.remove_document(writer, o)
|
||||
|
||||
super(DocumentAdmin, self).delete_queryset(request, queryset)
|
||||
|
||||
def delete_model(self, request, obj):
|
||||
from documents import index
|
||||
index.remove_document_from_index(obj)
|
||||
super(DocumentAdmin, self).delete_model(request, obj)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
from documents import index
|
||||
index.add_or_update_document(obj)
|
||||
super(DocumentAdmin, self).save_model(request, obj, form, change)
|
||||
|
||||
@mark_safe
|
||||
def tags_(self, obj):
|
||||
r = ""
|
||||
for tag in obj.tags.all():
|
||||
r += self._html_tag(
|
||||
"span",
|
||||
tag.name + ", "
|
||||
)
|
||||
return r
|
||||
|
||||
@staticmethod
|
||||
def _html_tag(kind, inside=None, **kwargs):
|
||||
attributes = format_html_join(' ', '{}="{}"', kwargs.items())
|
||||
|
||||
if inside is not None:
|
||||
return format_html("<{kind} {attributes}>{inside}</{kind}>",
|
||||
kind=kind, attributes=attributes, inside=inside)
|
||||
|
||||
return format_html("<{} {}/>", kind, attributes)
|
||||
|
||||
|
||||
class LogAdmin(admin.ModelAdmin):
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
list_display = ("created", "message", "level",)
|
||||
list_filter = ("level", "created",)
|
||||
|
||||
ordering = ('-created',)
|
||||
|
||||
list_display_links = ("created", "message")
|
||||
|
||||
|
||||
class RuleInline(admin.TabularInline):
|
||||
model = SavedViewFilterRule
|
||||
@@ -149,5 +118,4 @@ admin.site.register(Correspondent, CorrespondentAdmin)
|
||||
admin.site.register(Tag, TagAdmin)
|
||||
admin.site.register(DocumentType, DocumentTypeAdmin)
|
||||
admin.site.register(Document, DocumentAdmin)
|
||||
admin.site.register(Log, LogAdmin)
|
||||
admin.site.register(SavedView, SavedViewAdmin)
|
||||
|
||||
60
src/documents/bulk_download.py
Normal file
60
src/documents/bulk_download.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from zipfile import ZipFile
|
||||
|
||||
from documents.models import Document
|
||||
|
||||
|
||||
class BulkArchiveStrategy:
|
||||
|
||||
def __init__(self, zipf: ZipFile):
|
||||
self.zipf = zipf
|
||||
|
||||
def make_unique_filename(self,
|
||||
doc: Document,
|
||||
archive: bool = False,
|
||||
folder: str = ""):
|
||||
counter = 0
|
||||
while True:
|
||||
filename = folder + doc.get_public_filename(archive, counter)
|
||||
if filename in self.zipf.namelist():
|
||||
counter += 1
|
||||
else:
|
||||
return filename
|
||||
|
||||
def add_document(self, doc: Document):
|
||||
raise NotImplementedError() # pragma: no cover
|
||||
|
||||
|
||||
class OriginalsOnlyStrategy(BulkArchiveStrategy):
|
||||
|
||||
def add_document(self, doc: Document):
|
||||
self.zipf.write(doc.source_path, self.make_unique_filename(doc))
|
||||
|
||||
|
||||
class ArchiveOnlyStrategy(BulkArchiveStrategy):
|
||||
|
||||
def __init__(self, zipf):
|
||||
super(ArchiveOnlyStrategy, self).__init__(zipf)
|
||||
|
||||
def add_document(self, doc: Document):
|
||||
if doc.has_archive_version:
|
||||
self.zipf.write(doc.archive_path,
|
||||
self.make_unique_filename(doc, archive=True))
|
||||
else:
|
||||
self.zipf.write(doc.source_path,
|
||||
self.make_unique_filename(doc))
|
||||
|
||||
|
||||
class OriginalAndArchiveStrategy(BulkArchiveStrategy):
|
||||
|
||||
def add_document(self, doc: Document):
|
||||
if doc.has_archive_version:
|
||||
self.zipf.write(
|
||||
doc.archive_path, self.make_unique_filename(
|
||||
doc, archive=True, folder="archive/"
|
||||
)
|
||||
)
|
||||
|
||||
self.zipf.write(
|
||||
doc.source_path,
|
||||
self.make_unique_filename(doc, folder="originals/")
|
||||
)
|
||||
@@ -2,9 +2,7 @@ import itertools
|
||||
|
||||
from django.db.models import Q
|
||||
from django_q.tasks import async_task
|
||||
from whoosh.writing import AsyncWriter
|
||||
|
||||
from documents import index
|
||||
from documents.models import Document, Correspondent, DocumentType
|
||||
|
||||
|
||||
@@ -99,8 +97,9 @@ def modify_tags(doc_ids, add_tags, remove_tags):
|
||||
def delete(doc_ids):
|
||||
Document.objects.filter(id__in=doc_ids).delete()
|
||||
|
||||
ix = index.open_index()
|
||||
with AsyncWriter(ix) as writer:
|
||||
from documents import index
|
||||
|
||||
with index.open_index_writer() as writer:
|
||||
for id in doc_ids:
|
||||
index.remove_document_by_id(writer, id)
|
||||
|
||||
|
||||
117
src/documents/classifier.py
Executable file → Normal file
117
src/documents/classifier.py
Executable file → Normal file
@@ -3,12 +3,9 @@ import logging
|
||||
import os
|
||||
import pickle
|
||||
import re
|
||||
import shutil
|
||||
|
||||
from django.conf import settings
|
||||
from sklearn.feature_extraction.text import CountVectorizer
|
||||
from sklearn.neural_network import MLPClassifier
|
||||
from sklearn.preprocessing import MultiLabelBinarizer, LabelBinarizer
|
||||
from sklearn.utils.multiclass import type_of_target
|
||||
|
||||
from documents.models import Document, MatchingModel
|
||||
|
||||
@@ -17,7 +14,11 @@ class IncompatibleClassifierVersionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
class ClassifierModelCorruptError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
logger = logging.getLogger("paperless.classifier")
|
||||
|
||||
|
||||
def preprocess_content(content):
|
||||
@@ -26,15 +27,46 @@ def preprocess_content(content):
|
||||
return content
|
||||
|
||||
|
||||
def load_classifier():
|
||||
if not os.path.isfile(settings.MODEL_FILE):
|
||||
logger.debug(
|
||||
f"Document classification model does not exist (yet), not "
|
||||
f"performing automatic matching."
|
||||
)
|
||||
return None
|
||||
|
||||
classifier = DocumentClassifier()
|
||||
try:
|
||||
classifier.load()
|
||||
|
||||
except (ClassifierModelCorruptError,
|
||||
IncompatibleClassifierVersionError):
|
||||
# there's something wrong with the model file.
|
||||
logger.exception(
|
||||
f"Unrecoverable error while loading document "
|
||||
f"classification model, deleting model file."
|
||||
)
|
||||
os.unlink(settings.MODEL_FILE)
|
||||
classifier = None
|
||||
except OSError:
|
||||
logger.exception(
|
||||
f"IO error while loading document classification model"
|
||||
)
|
||||
classifier = None
|
||||
except Exception:
|
||||
logger.exception(
|
||||
f"Unknown error while loading document classification model"
|
||||
)
|
||||
classifier = None
|
||||
|
||||
return classifier
|
||||
|
||||
|
||||
class DocumentClassifier(object):
|
||||
|
||||
FORMAT_VERSION = 6
|
||||
|
||||
def __init__(self):
|
||||
# mtime of the model file on disk. used to prevent reloading when
|
||||
# nothing has changed.
|
||||
self.classifier_version = 0
|
||||
|
||||
# hash of the training data. used to prevent re-training when the
|
||||
# training data has not changed.
|
||||
self.data_hash = None
|
||||
@@ -45,20 +77,15 @@ class DocumentClassifier(object):
|
||||
self.correspondent_classifier = None
|
||||
self.document_type_classifier = None
|
||||
|
||||
def reload(self):
|
||||
if os.path.getmtime(settings.MODEL_FILE) > self.classifier_version:
|
||||
with open(settings.MODEL_FILE, "rb") as f:
|
||||
schema_version = pickle.load(f)
|
||||
def load(self):
|
||||
with open(settings.MODEL_FILE, "rb") as f:
|
||||
schema_version = pickle.load(f)
|
||||
|
||||
if schema_version != self.FORMAT_VERSION:
|
||||
raise IncompatibleClassifierVersionError(
|
||||
"Cannor load classifier, incompatible versions.")
|
||||
else:
|
||||
if self.classifier_version > 0:
|
||||
# Don't be confused by this check. It's simply here
|
||||
# so that we wont log anything on initial reload.
|
||||
logger.info("Classifier updated on disk, "
|
||||
"reloading classifier models")
|
||||
if schema_version != self.FORMAT_VERSION:
|
||||
raise IncompatibleClassifierVersionError(
|
||||
"Cannor load classifier, incompatible versions.")
|
||||
else:
|
||||
try:
|
||||
self.data_hash = pickle.load(f)
|
||||
self.data_vectorizer = pickle.load(f)
|
||||
self.tags_binarizer = pickle.load(f)
|
||||
@@ -66,10 +93,14 @@ class DocumentClassifier(object):
|
||||
self.tags_classifier = pickle.load(f)
|
||||
self.correspondent_classifier = pickle.load(f)
|
||||
self.document_type_classifier = pickle.load(f)
|
||||
self.classifier_version = os.path.getmtime(settings.MODEL_FILE)
|
||||
except Exception:
|
||||
raise ClassifierModelCorruptError()
|
||||
|
||||
def save_classifier(self):
|
||||
with open(settings.MODEL_FILE, "wb") as f:
|
||||
def save(self):
|
||||
target_file = settings.MODEL_FILE
|
||||
target_file_temp = settings.MODEL_FILE + ".part"
|
||||
|
||||
with open(target_file_temp, "wb") as f:
|
||||
pickle.dump(self.FORMAT_VERSION, f)
|
||||
pickle.dump(self.data_hash, f)
|
||||
pickle.dump(self.data_vectorizer, f)
|
||||
@@ -80,14 +111,19 @@ class DocumentClassifier(object):
|
||||
pickle.dump(self.correspondent_classifier, f)
|
||||
pickle.dump(self.document_type_classifier, f)
|
||||
|
||||
if os.path.isfile(target_file):
|
||||
os.unlink(target_file)
|
||||
shutil.move(target_file_temp, target_file)
|
||||
|
||||
def train(self):
|
||||
|
||||
data = list()
|
||||
labels_tags = list()
|
||||
labels_correspondent = list()
|
||||
labels_document_type = list()
|
||||
|
||||
# Step 1: Extract and preprocess training data from the database.
|
||||
logging.getLogger(__name__).debug("Gathering data from database...")
|
||||
logger.debug("Gathering data from database...")
|
||||
m = hashlib.sha1()
|
||||
for doc in Document.objects.order_by('pk').exclude(tags__is_inbox_tag=True): # NOQA: E501
|
||||
preprocessed_content = preprocess_content(doc.content)
|
||||
@@ -108,10 +144,11 @@ class DocumentClassifier(object):
|
||||
m.update(y.to_bytes(4, 'little', signed=True))
|
||||
labels_correspondent.append(y)
|
||||
|
||||
tags = [tag.pk for tag in doc.tags.filter(
|
||||
tags = sorted([tag.pk for tag in doc.tags.filter(
|
||||
matching_algorithm=MatchingModel.MATCH_AUTO
|
||||
)]
|
||||
m.update(bytearray(tags))
|
||||
)])
|
||||
for tag in tags:
|
||||
m.update(tag.to_bytes(4, 'little', signed=True))
|
||||
labels_tags.append(tags)
|
||||
|
||||
if not data:
|
||||
@@ -134,7 +171,7 @@ class DocumentClassifier(object):
|
||||
num_correspondents = len(set(labels_correspondent) | {-1}) - 1
|
||||
num_document_types = len(set(labels_document_type) | {-1}) - 1
|
||||
|
||||
logging.getLogger(__name__).debug(
|
||||
logger.debug(
|
||||
"{} documents, {} tag(s), {} correspondent(s), "
|
||||
"{} document type(s).".format(
|
||||
len(data),
|
||||
@@ -144,8 +181,12 @@ class DocumentClassifier(object):
|
||||
)
|
||||
)
|
||||
|
||||
from sklearn.feature_extraction.text import CountVectorizer
|
||||
from sklearn.neural_network import MLPClassifier
|
||||
from sklearn.preprocessing import MultiLabelBinarizer, LabelBinarizer
|
||||
|
||||
# Step 2: vectorize data
|
||||
logging.getLogger(__name__).debug("Vectorizing data...")
|
||||
logger.debug("Vectorizing data...")
|
||||
self.data_vectorizer = CountVectorizer(
|
||||
analyzer="word",
|
||||
ngram_range=(1, 2),
|
||||
@@ -155,7 +196,7 @@ class DocumentClassifier(object):
|
||||
|
||||
# Step 3: train the classifiers
|
||||
if num_tags > 0:
|
||||
logging.getLogger(__name__).debug("Training tags classifier...")
|
||||
logger.debug("Training tags classifier...")
|
||||
|
||||
if num_tags == 1:
|
||||
# Special case where only one tag has auto:
|
||||
@@ -174,12 +215,12 @@ class DocumentClassifier(object):
|
||||
self.tags_classifier.fit(data_vectorized, labels_tags_vectorized)
|
||||
else:
|
||||
self.tags_classifier = None
|
||||
logging.getLogger(__name__).debug(
|
||||
logger.debug(
|
||||
"There are no tags. Not training tags classifier."
|
||||
)
|
||||
|
||||
if num_correspondents > 0:
|
||||
logging.getLogger(__name__).debug(
|
||||
logger.debug(
|
||||
"Training correspondent classifier..."
|
||||
)
|
||||
self.correspondent_classifier = MLPClassifier(tol=0.01)
|
||||
@@ -189,13 +230,13 @@ class DocumentClassifier(object):
|
||||
)
|
||||
else:
|
||||
self.correspondent_classifier = None
|
||||
logging.getLogger(__name__).debug(
|
||||
logger.debug(
|
||||
"There are no correspondents. Not training correspondent "
|
||||
"classifier."
|
||||
)
|
||||
|
||||
if num_document_types > 0:
|
||||
logging.getLogger(__name__).debug(
|
||||
logger.debug(
|
||||
"Training document type classifier..."
|
||||
)
|
||||
self.document_type_classifier = MLPClassifier(tol=0.01)
|
||||
@@ -205,7 +246,7 @@ class DocumentClassifier(object):
|
||||
)
|
||||
else:
|
||||
self.document_type_classifier = None
|
||||
logging.getLogger(__name__).debug(
|
||||
logger.debug(
|
||||
"There are no document types. Not training document type "
|
||||
"classifier."
|
||||
)
|
||||
@@ -237,6 +278,8 @@ class DocumentClassifier(object):
|
||||
return None
|
||||
|
||||
def predict_tags(self, content):
|
||||
from sklearn.utils.multiclass import type_of_target
|
||||
|
||||
if self.tags_classifier:
|
||||
X = self.data_vectorizer.transform([preprocess_content(content)])
|
||||
y = self.tags_classifier.predict(X)
|
||||
|
||||
172
src/documents/consumer.py
Executable file → Normal file
172
src/documents/consumer.py
Executable file → Normal file
@@ -1,9 +1,12 @@
|
||||
import datetime
|
||||
import hashlib
|
||||
import os
|
||||
import uuid
|
||||
from subprocess import Popen
|
||||
|
||||
import magic
|
||||
from asgiref.sync import async_to_sync
|
||||
from channels.layers import get_channel_layer
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
@@ -11,7 +14,7 @@ from django.utils import timezone
|
||||
from filelock import FileLock
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from .classifier import DocumentClassifier, IncompatibleClassifierVersionError
|
||||
from .classifier import load_classifier
|
||||
from .file_handling import create_source_path_directory, \
|
||||
generate_unique_filename
|
||||
from .loggers import LoggingMixin
|
||||
@@ -27,8 +30,45 @@ class ConsumerError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
MESSAGE_DOCUMENT_ALREADY_EXISTS = "document_already_exists"
|
||||
MESSAGE_FILE_NOT_FOUND = "file_not_found"
|
||||
MESSAGE_PRE_CONSUME_SCRIPT_NOT_FOUND = "pre_consume_script_not_found"
|
||||
MESSAGE_PRE_CONSUME_SCRIPT_ERROR = "pre_consume_script_error"
|
||||
MESSAGE_POST_CONSUME_SCRIPT_NOT_FOUND = "post_consume_script_not_found"
|
||||
MESSAGE_POST_CONSUME_SCRIPT_ERROR = "post_consume_script_error"
|
||||
MESSAGE_NEW_FILE = "new_file"
|
||||
MESSAGE_UNSUPPORTED_TYPE = "unsupported_type"
|
||||
MESSAGE_PARSING_DOCUMENT = "parsing_document"
|
||||
MESSAGE_GENERATING_THUMBNAIL = "generating_thumbnail"
|
||||
MESSAGE_PARSE_DATE = "parse_date"
|
||||
MESSAGE_SAVE_DOCUMENT = "save_document"
|
||||
MESSAGE_FINISHED = "finished"
|
||||
|
||||
|
||||
class Consumer(LoggingMixin):
|
||||
|
||||
logging_name = "paperless.consumer"
|
||||
|
||||
def _send_progress(self, current_progress, max_progress, status,
|
||||
message=None, document_id=None):
|
||||
payload = {
|
||||
'filename': os.path.basename(self.filename) if self.filename else None, # NOQA: E501
|
||||
'task_id': self.task_id,
|
||||
'current_progress': current_progress,
|
||||
'max_progress': max_progress,
|
||||
'status': status,
|
||||
'message': message,
|
||||
'document_id': document_id
|
||||
}
|
||||
async_to_sync(self.channel_layer.group_send)("status_updates",
|
||||
{'type': 'status_update',
|
||||
'data': payload})
|
||||
|
||||
def _fail(self, message, log_message=None, exc_info=None):
|
||||
self._send_progress(100, 100, 'FAILED', message)
|
||||
self.log("error", log_message or message, exc_info=exc_info)
|
||||
raise ConsumerError(f"{self.filename}: {log_message or message}")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.path = None
|
||||
@@ -37,15 +77,16 @@ class Consumer(LoggingMixin):
|
||||
self.override_correspondent_id = None
|
||||
self.override_tag_ids = None
|
||||
self.override_document_type_id = None
|
||||
self.task_id = None
|
||||
|
||||
self.channel_layer = get_channel_layer()
|
||||
|
||||
def pre_check_file_exists(self):
|
||||
if not os.path.isfile(self.path):
|
||||
self.log(
|
||||
"error",
|
||||
"Cannot consume {}: It is not a file.".format(self.path)
|
||||
self._fail(
|
||||
MESSAGE_FILE_NOT_FOUND,
|
||||
f"Cannot consume {self.path}: File not found."
|
||||
)
|
||||
raise ConsumerError("Cannot consume {}: It is not a file".format(
|
||||
self.path))
|
||||
|
||||
def pre_check_duplicate(self):
|
||||
with open(self.path, "rb") as f:
|
||||
@@ -53,12 +94,9 @@ class Consumer(LoggingMixin):
|
||||
if Document.objects.filter(Q(checksum=checksum) | Q(archive_checksum=checksum)).exists(): # NOQA: E501
|
||||
if settings.CONSUMER_DELETE_DUPLICATES:
|
||||
os.unlink(self.path)
|
||||
self.log(
|
||||
"error",
|
||||
"Not consuming {}: It is a duplicate.".format(self.filename)
|
||||
)
|
||||
raise ConsumerError(
|
||||
"Not consuming {}: It is a duplicate.".format(self.filename)
|
||||
self._fail(
|
||||
MESSAGE_DOCUMENT_ALREADY_EXISTS,
|
||||
f"Not consuming {self.filename}: It is a duplicate."
|
||||
)
|
||||
|
||||
def pre_check_directories(self):
|
||||
@@ -72,15 +110,21 @@ class Consumer(LoggingMixin):
|
||||
return
|
||||
|
||||
if not os.path.isfile(settings.PRE_CONSUME_SCRIPT):
|
||||
raise ConsumerError(
|
||||
self._fail(
|
||||
MESSAGE_PRE_CONSUME_SCRIPT_NOT_FOUND,
|
||||
f"Configured pre-consume script "
|
||||
f"{settings.PRE_CONSUME_SCRIPT} does not exist.")
|
||||
|
||||
self.log("info",
|
||||
f"Executing pre-consume script {settings.PRE_CONSUME_SCRIPT}")
|
||||
|
||||
try:
|
||||
Popen((settings.PRE_CONSUME_SCRIPT, self.path)).wait()
|
||||
except Exception as e:
|
||||
raise ConsumerError(
|
||||
f"Error while executing pre-consume script: {e}"
|
||||
self._fail(
|
||||
MESSAGE_PRE_CONSUME_SCRIPT_ERROR,
|
||||
f"Error while executing pre-consume script: {e}",
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
def run_post_consume_script(self, document):
|
||||
@@ -88,9 +132,16 @@ class Consumer(LoggingMixin):
|
||||
return
|
||||
|
||||
if not os.path.isfile(settings.POST_CONSUME_SCRIPT):
|
||||
raise ConsumerError(
|
||||
self._fail(
|
||||
MESSAGE_POST_CONSUME_SCRIPT_NOT_FOUND,
|
||||
f"Configured post-consume script "
|
||||
f"{settings.POST_CONSUME_SCRIPT} does not exist.")
|
||||
f"{settings.POST_CONSUME_SCRIPT} does not exist."
|
||||
)
|
||||
|
||||
self.log(
|
||||
"info",
|
||||
f"Executing post-consume script {settings.POST_CONSUME_SCRIPT}"
|
||||
)
|
||||
|
||||
try:
|
||||
Popen((
|
||||
@@ -106,8 +157,10 @@ class Consumer(LoggingMixin):
|
||||
"name", flat=True)))
|
||||
)).wait()
|
||||
except Exception as e:
|
||||
raise ConsumerError(
|
||||
f"Error while executing pre-consume script: {e}"
|
||||
self._fail(
|
||||
MESSAGE_POST_CONSUME_SCRIPT_ERROR,
|
||||
f"Error while executing post-consume script: {e}",
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
def try_consume_file(self,
|
||||
@@ -116,7 +169,8 @@ class Consumer(LoggingMixin):
|
||||
override_title=None,
|
||||
override_correspondent_id=None,
|
||||
override_document_type_id=None,
|
||||
override_tag_ids=None):
|
||||
override_tag_ids=None,
|
||||
task_id=None):
|
||||
"""
|
||||
Return the document object if it was successfully created.
|
||||
"""
|
||||
@@ -127,6 +181,9 @@ class Consumer(LoggingMixin):
|
||||
self.override_correspondent_id = override_correspondent_id
|
||||
self.override_document_type_id = override_document_type_id
|
||||
self.override_tag_ids = override_tag_ids
|
||||
self.task_id = task_id or str(uuid.uuid4())
|
||||
|
||||
self._send_progress(0, 100, 'STARTING', MESSAGE_NEW_FILE)
|
||||
|
||||
# this is for grouping logging entries for this particular file
|
||||
# together.
|
||||
@@ -149,11 +206,10 @@ class Consumer(LoggingMixin):
|
||||
|
||||
parser_class = get_parser_class_for_mime_type(mime_type)
|
||||
if not parser_class:
|
||||
raise ConsumerError(
|
||||
f"Unsupported mime type {mime_type} of file {self.filename}")
|
||||
else:
|
||||
self.log("debug",
|
||||
f"Parser: {parser_class.__name__}")
|
||||
self._fail(
|
||||
MESSAGE_UNSUPPORTED_TYPE,
|
||||
f"Unsupported mime type {mime_type}"
|
||||
)
|
||||
|
||||
# Notify all listeners that we're going to do some work.
|
||||
|
||||
@@ -165,35 +221,53 @@ class Consumer(LoggingMixin):
|
||||
|
||||
self.run_pre_consume_script()
|
||||
|
||||
def progress_callback(current_progress, max_progress):
|
||||
# recalculate progress to be within 20 and 80
|
||||
p = int((current_progress / max_progress) * 50 + 20)
|
||||
self._send_progress(p, 100, "WORKING")
|
||||
|
||||
# This doesn't parse the document yet, but gives us a parser.
|
||||
|
||||
document_parser = parser_class(self.logging_group)
|
||||
document_parser = parser_class(self.logging_group, progress_callback)
|
||||
|
||||
self.log("debug", f"Parser: {type(document_parser).__name__}")
|
||||
|
||||
# However, this already created working directories which we have to
|
||||
# clean up.
|
||||
|
||||
# Parse the document. This may take some time.
|
||||
|
||||
text = None
|
||||
date = None
|
||||
thumbnail = None
|
||||
archive_path = None
|
||||
|
||||
try:
|
||||
self._send_progress(20, 100, 'WORKING', MESSAGE_PARSING_DOCUMENT)
|
||||
self.log("debug", "Parsing {}...".format(self.filename))
|
||||
document_parser.parse(self.path, mime_type, self.filename)
|
||||
|
||||
self.log("debug", f"Generating thumbnail for {self.filename}...")
|
||||
self._send_progress(70, 100, 'WORKING',
|
||||
MESSAGE_GENERATING_THUMBNAIL)
|
||||
thumbnail = document_parser.get_optimised_thumbnail(
|
||||
self.path, mime_type)
|
||||
self.path, mime_type, self.filename)
|
||||
|
||||
text = document_parser.get_text()
|
||||
date = document_parser.get_date()
|
||||
if not date:
|
||||
self._send_progress(90, 100, 'WORKING',
|
||||
MESSAGE_PARSE_DATE)
|
||||
date = parse_date(self.filename, text)
|
||||
archive_path = document_parser.get_archive_path()
|
||||
|
||||
except ParseError as e:
|
||||
document_parser.cleanup()
|
||||
self.log(
|
||||
"error",
|
||||
f"Error while consuming document {self.filename}: {e}")
|
||||
raise ConsumerError(e)
|
||||
self._fail(
|
||||
str(e),
|
||||
f"Error while consuming document {self.filename}: {e}",
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
# Prepare the document classifier.
|
||||
|
||||
@@ -201,15 +275,9 @@ class Consumer(LoggingMixin):
|
||||
# reloading the classifier multiple times, since there are multiple
|
||||
# post-consume hooks that all require the classifier.
|
||||
|
||||
try:
|
||||
classifier = DocumentClassifier()
|
||||
classifier.reload()
|
||||
except (OSError, EOFError, IncompatibleClassifierVersionError) as e:
|
||||
self.log(
|
||||
"warning",
|
||||
f"Cannot classify documents: {e}.")
|
||||
classifier = None
|
||||
classifier = load_classifier()
|
||||
|
||||
self._send_progress(95, 100, 'WORKING', MESSAGE_SAVE_DOCUMENT)
|
||||
# now that everything is done, we can start to store the document
|
||||
# in the system. This will be a transaction and reasonably fast.
|
||||
try:
|
||||
@@ -235,8 +303,7 @@ class Consumer(LoggingMixin):
|
||||
# After everything is in the database, copy the files into
|
||||
# place. If this fails, we'll also rollback the transaction.
|
||||
with FileLock(settings.MEDIA_LOCK):
|
||||
document.filename = generate_unique_filename(
|
||||
document, settings.ORIGINALS_DIR)
|
||||
document.filename = generate_unique_filename(document)
|
||||
create_source_path_directory(document.source_path)
|
||||
|
||||
self._write(document.storage_type,
|
||||
@@ -246,6 +313,10 @@ class Consumer(LoggingMixin):
|
||||
thumbnail, document.thumbnail_path)
|
||||
|
||||
if archive_path and os.path.isfile(archive_path):
|
||||
document.archive_filename = generate_unique_filename(
|
||||
document,
|
||||
archive_filename=True
|
||||
)
|
||||
create_source_path_directory(document.archive_path)
|
||||
self._write(document.storage_type,
|
||||
archive_path, document.archive_path)
|
||||
@@ -262,13 +333,22 @@ class Consumer(LoggingMixin):
|
||||
self.log("debug", "Deleting file {}".format(self.path))
|
||||
os.unlink(self.path)
|
||||
|
||||
# https://github.com/jonaswinkler/paperless-ng/discussions/1037
|
||||
shadow_file = os.path.join(
|
||||
os.path.dirname(self.path),
|
||||
"._" + os.path.basename(self.path))
|
||||
|
||||
if os.path.isfile(shadow_file):
|
||||
self.log("debug", "Deleting file {}".format(shadow_file))
|
||||
os.unlink(shadow_file)
|
||||
|
||||
except Exception as e:
|
||||
self.log(
|
||||
"error",
|
||||
self._fail(
|
||||
str(e),
|
||||
f"The following error occured while consuming "
|
||||
f"{self.filename}: {e}"
|
||||
f"{self.filename}: {e}",
|
||||
exc_info=True
|
||||
)
|
||||
raise ConsumerError(e)
|
||||
finally:
|
||||
document_parser.cleanup()
|
||||
|
||||
@@ -279,6 +359,8 @@ class Consumer(LoggingMixin):
|
||||
"Document {} consumption finished".format(document)
|
||||
)
|
||||
|
||||
self._send_progress(100, 100, 'SUCCESS', MESSAGE_FINISHED, document.id)
|
||||
|
||||
return document
|
||||
|
||||
def _store(self, text, date, mime_type):
|
||||
|
||||
@@ -8,6 +8,9 @@ from django.conf import settings
|
||||
from django.template.defaultfilters import slugify
|
||||
|
||||
|
||||
logger = logging.getLogger("paperless.filehandling")
|
||||
|
||||
|
||||
class defaultdictNoStr(defaultdict):
|
||||
|
||||
def __str__(self):
|
||||
@@ -76,12 +79,40 @@ def many_to_dictionary(field):
|
||||
return mydictionary
|
||||
|
||||
|
||||
def generate_unique_filename(doc, root):
|
||||
def generate_unique_filename(doc,
|
||||
archive_filename=False):
|
||||
"""
|
||||
Generates a unique filename for doc in settings.ORIGINALS_DIR.
|
||||
|
||||
The returned filename is guaranteed to be either the current filename
|
||||
of the document if unchanged, or a new filename that does not correspondent
|
||||
to any existing files. The function will append _01, _02, etc to the
|
||||
filename before the extension to avoid conflicts.
|
||||
|
||||
If archive_filename is True, return a unique archive filename instead.
|
||||
|
||||
"""
|
||||
if archive_filename:
|
||||
old_filename = doc.archive_filename
|
||||
root = settings.ARCHIVE_DIR
|
||||
else:
|
||||
old_filename = doc.filename
|
||||
root = settings.ORIGINALS_DIR
|
||||
|
||||
# If generating archive filenames, try to make a name that is similar to
|
||||
# the original filename first.
|
||||
|
||||
if archive_filename and doc.filename:
|
||||
new_filename = os.path.splitext(doc.filename)[0] + ".pdf"
|
||||
if new_filename == old_filename or not os.path.exists(os.path.join(root, new_filename)): # NOQA: E501
|
||||
return new_filename
|
||||
|
||||
counter = 0
|
||||
|
||||
while True:
|
||||
new_filename = generate_filename(doc, counter)
|
||||
if new_filename == doc.filename:
|
||||
new_filename = generate_filename(
|
||||
doc, counter, archive_filename=archive_filename)
|
||||
if new_filename == old_filename:
|
||||
# still the same as before.
|
||||
return new_filename
|
||||
|
||||
@@ -91,7 +122,7 @@ def generate_unique_filename(doc, root):
|
||||
return new_filename
|
||||
|
||||
|
||||
def generate_filename(doc, counter=0, append_gpg=True):
|
||||
def generate_filename(doc, counter=0, append_gpg=True, archive_filename=False):
|
||||
path = ""
|
||||
|
||||
try:
|
||||
@@ -120,6 +151,11 @@ def generate_filename(doc, counter=0, append_gpg=True):
|
||||
else:
|
||||
document_type = "none"
|
||||
|
||||
if doc.archive_serial_number:
|
||||
asn = str(doc.archive_serial_number)
|
||||
else:
|
||||
asn = "none"
|
||||
|
||||
path = settings.PAPERLESS_FILENAME_FORMAT.format(
|
||||
title=pathvalidate.sanitize_filename(
|
||||
doc.title, replacement_text="-"),
|
||||
@@ -133,6 +169,7 @@ def generate_filename(doc, counter=0, append_gpg=True):
|
||||
added_year=doc.added.year if doc.added else "none",
|
||||
added_month=f"{doc.added.month:02}" if doc.added else "none",
|
||||
added_day=f"{doc.added.day:02}" if doc.added else "none",
|
||||
asn=asn,
|
||||
tags=tags,
|
||||
tag_list=tag_list
|
||||
).strip()
|
||||
@@ -140,23 +177,21 @@ def generate_filename(doc, counter=0, append_gpg=True):
|
||||
path = path.strip(os.sep)
|
||||
|
||||
except (ValueError, KeyError, IndexError):
|
||||
logging.getLogger(__name__).warning(
|
||||
logger.warning(
|
||||
f"Invalid PAPERLESS_FILENAME_FORMAT: "
|
||||
f"{settings.PAPERLESS_FILENAME_FORMAT}, falling back to default")
|
||||
|
||||
counter_str = f"_{counter:02}" if counter else ""
|
||||
|
||||
filetype_str = ".pdf" if archive_filename else doc.file_type
|
||||
|
||||
if len(path) > 0:
|
||||
filename = f"{path}{counter_str}{doc.file_type}"
|
||||
filename = f"{path}{counter_str}{filetype_str}"
|
||||
else:
|
||||
filename = f"{doc.pk:07}{counter_str}{doc.file_type}"
|
||||
filename = f"{doc.pk:07}{counter_str}{filetype_str}"
|
||||
|
||||
# Append .gpg for encrypted files
|
||||
if append_gpg and doc.storage_type == doc.STORAGE_TYPE_GPG:
|
||||
filename += ".gpg"
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
def archive_name_from_filename(filename):
|
||||
|
||||
return os.path.splitext(filename)[0] + ".pdf"
|
||||
|
||||
13
src/documents/filters.py
Executable file → Normal file
13
src/documents/filters.py
Executable file → Normal file
@@ -1,3 +1,4 @@
|
||||
from django.db.models import Q
|
||||
from django_filters.rest_framework import BooleanFilter, FilterSet, Filter
|
||||
|
||||
from .models import Correspondent, Document, Tag, DocumentType, Log
|
||||
@@ -74,6 +75,16 @@ class InboxFilter(Filter):
|
||||
return qs
|
||||
|
||||
|
||||
class TitleContentFilter(Filter):
|
||||
|
||||
def filter(self, qs, value):
|
||||
if value:
|
||||
return qs.filter(Q(title__icontains=value) |
|
||||
Q(content__icontains=value))
|
||||
else:
|
||||
return qs
|
||||
|
||||
|
||||
class DocumentFilterSet(FilterSet):
|
||||
|
||||
is_tagged = BooleanFilter(
|
||||
@@ -91,6 +102,8 @@ class DocumentFilterSet(FilterSet):
|
||||
|
||||
is_in_inbox = InboxFilter()
|
||||
|
||||
title_content = TitleContentFilter()
|
||||
|
||||
class Meta:
|
||||
model = Document
|
||||
fields = {
|
||||
|
||||
@@ -2,75 +2,70 @@ import logging
|
||||
import os
|
||||
from contextlib import contextmanager
|
||||
|
||||
import math
|
||||
from dateutil.parser import isoparse
|
||||
from django.conf import settings
|
||||
from whoosh import highlight, classify, query
|
||||
from whoosh.fields import Schema, TEXT, NUMERIC, KEYWORD, DATETIME
|
||||
from whoosh.highlight import Formatter, get_text
|
||||
from whoosh.fields import Schema, TEXT, NUMERIC, KEYWORD, DATETIME, BOOLEAN
|
||||
from whoosh.highlight import HtmlFormatter
|
||||
from whoosh.index import create_in, exists_in, open_dir
|
||||
from whoosh.qparser import MultifieldParser
|
||||
from whoosh.qparser.dateparse import DateParserPlugin
|
||||
from whoosh.searching import ResultsPage, Searcher
|
||||
from whoosh.writing import AsyncWriter
|
||||
|
||||
from documents.models import Document
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class JsonFormatter(Formatter):
|
||||
def __init__(self):
|
||||
self.seen = {}
|
||||
|
||||
def format_token(self, text, token, replace=False):
|
||||
ttext = self._text(get_text(text, token, replace))
|
||||
return {'text': ttext, 'highlight': 'true'}
|
||||
|
||||
def format_fragment(self, fragment, replace=False):
|
||||
output = []
|
||||
index = fragment.startchar
|
||||
text = fragment.text
|
||||
amend_token = None
|
||||
for t in fragment.matches:
|
||||
if t.startchar is None:
|
||||
continue
|
||||
if t.startchar < index:
|
||||
continue
|
||||
if t.startchar > index:
|
||||
text_inbetween = text[index:t.startchar]
|
||||
if amend_token and t.startchar - index < 10:
|
||||
amend_token['text'] += text_inbetween
|
||||
else:
|
||||
output.append({'text': text_inbetween,
|
||||
'highlight': False})
|
||||
amend_token = None
|
||||
token = self.format_token(text, t, replace)
|
||||
if amend_token:
|
||||
amend_token['text'] += token['text']
|
||||
else:
|
||||
output.append(token)
|
||||
amend_token = token
|
||||
index = t.endchar
|
||||
if index < fragment.endchar:
|
||||
output.append({'text': text[index:fragment.endchar],
|
||||
'highlight': False})
|
||||
return output
|
||||
|
||||
def format(self, fragments, replace=False):
|
||||
output = []
|
||||
for fragment in fragments:
|
||||
output.append(self.format_fragment(fragment, replace=replace))
|
||||
return output
|
||||
logger = logging.getLogger("paperless.index")
|
||||
|
||||
|
||||
def get_schema():
|
||||
return Schema(
|
||||
id=NUMERIC(stored=True, unique=True, numtype=int),
|
||||
title=TEXT(stored=True),
|
||||
id=NUMERIC(
|
||||
stored=True,
|
||||
unique=True
|
||||
),
|
||||
title=TEXT(
|
||||
sortable=True
|
||||
),
|
||||
content=TEXT(),
|
||||
correspondent=TEXT(stored=True),
|
||||
tag=KEYWORD(stored=True, commas=True, scorable=True, lowercase=True),
|
||||
type=TEXT(stored=True),
|
||||
created=DATETIME(stored=True, sortable=True),
|
||||
modified=DATETIME(stored=True, sortable=True),
|
||||
added=DATETIME(stored=True, sortable=True),
|
||||
asn=NUMERIC(
|
||||
sortable=True
|
||||
),
|
||||
|
||||
correspondent=TEXT(
|
||||
sortable=True
|
||||
),
|
||||
correspondent_id=NUMERIC(),
|
||||
has_correspondent=BOOLEAN(),
|
||||
|
||||
tag=KEYWORD(
|
||||
commas=True,
|
||||
scorable=True,
|
||||
lowercase=True
|
||||
),
|
||||
tag_id=KEYWORD(
|
||||
commas=True,
|
||||
scorable=True
|
||||
),
|
||||
has_tag=BOOLEAN(),
|
||||
|
||||
type=TEXT(
|
||||
sortable=True
|
||||
),
|
||||
type_id=NUMERIC(),
|
||||
has_type=BOOLEAN(),
|
||||
|
||||
created=DATETIME(
|
||||
sortable=True
|
||||
),
|
||||
modified=DATETIME(
|
||||
sortable=True
|
||||
),
|
||||
added=DATETIME(
|
||||
sortable=True
|
||||
),
|
||||
|
||||
)
|
||||
|
||||
|
||||
@@ -78,25 +73,56 @@ def open_index(recreate=False):
|
||||
try:
|
||||
if exists_in(settings.INDEX_DIR) and not recreate:
|
||||
return open_dir(settings.INDEX_DIR, schema=get_schema())
|
||||
except Exception as e:
|
||||
logger.error(f"Error while opening the index: {e}, recreating.")
|
||||
except Exception:
|
||||
logger.exception(f"Error while opening the index, recreating.")
|
||||
|
||||
if not os.path.isdir(settings.INDEX_DIR):
|
||||
os.makedirs(settings.INDEX_DIR, exist_ok=True)
|
||||
return create_in(settings.INDEX_DIR, get_schema())
|
||||
|
||||
|
||||
@contextmanager
|
||||
def open_index_writer(optimize=False):
|
||||
writer = AsyncWriter(open_index())
|
||||
|
||||
try:
|
||||
yield writer
|
||||
except Exception as e:
|
||||
logger.exception(str(e))
|
||||
writer.cancel()
|
||||
finally:
|
||||
writer.commit(optimize=optimize)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def open_index_searcher():
|
||||
searcher = open_index().searcher()
|
||||
|
||||
try:
|
||||
yield searcher
|
||||
finally:
|
||||
searcher.close()
|
||||
|
||||
|
||||
def update_document(writer, doc):
|
||||
tags = ",".join([t.name for t in doc.tags.all()])
|
||||
tags_ids = ",".join([str(t.id) for t in doc.tags.all()])
|
||||
writer.update_document(
|
||||
id=doc.pk,
|
||||
title=doc.title,
|
||||
content=doc.content,
|
||||
correspondent=doc.correspondent.name if doc.correspondent else None,
|
||||
correspondent_id=doc.correspondent.id if doc.correspondent else None,
|
||||
has_correspondent=doc.correspondent is not None,
|
||||
tag=tags if tags else None,
|
||||
tag_id=tags_ids if tags_ids else None,
|
||||
has_tag=len(tags) > 0,
|
||||
type=doc.document_type.name if doc.document_type else None,
|
||||
type_id=doc.document_type.id if doc.document_type else None,
|
||||
has_type=doc.document_type is not None,
|
||||
created=doc.created,
|
||||
added=doc.added,
|
||||
asn=doc.archive_serial_number,
|
||||
modified=doc.modified,
|
||||
)
|
||||
|
||||
@@ -110,61 +136,162 @@ def remove_document_by_id(writer, doc_id):
|
||||
|
||||
|
||||
def add_or_update_document(document):
|
||||
ix = open_index()
|
||||
with AsyncWriter(ix) as writer:
|
||||
with open_index_writer() as writer:
|
||||
update_document(writer, document)
|
||||
|
||||
|
||||
def remove_document_from_index(document):
|
||||
ix = open_index()
|
||||
with AsyncWriter(ix) as writer:
|
||||
with open_index_writer() as writer:
|
||||
remove_document(writer, document)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def query_page(ix, page, querystring, more_like_doc_id, more_like_doc_content):
|
||||
searcher = ix.searcher()
|
||||
try:
|
||||
if querystring:
|
||||
qp = MultifieldParser(
|
||||
["content", "title", "correspondent", "tag", "type"],
|
||||
ix.schema)
|
||||
qp.add_plugin(DateParserPlugin())
|
||||
str_q = qp.parse(querystring)
|
||||
corrected = searcher.correct_query(str_q, querystring)
|
||||
else:
|
||||
str_q = None
|
||||
corrected = None
|
||||
class DelayedQuery:
|
||||
|
||||
if more_like_doc_id:
|
||||
docnum = searcher.document_number(id=more_like_doc_id)
|
||||
kts = searcher.key_terms_from_text(
|
||||
'content', more_like_doc_content, numterms=20,
|
||||
model=classify.Bo1Model, normalize=False)
|
||||
more_like_q = query.Or(
|
||||
[query.Term('content', word, boost=weight)
|
||||
for word, weight in kts])
|
||||
result_page = searcher.search_page(
|
||||
more_like_q, page, filter=str_q, mask={docnum})
|
||||
elif str_q:
|
||||
result_page = searcher.search_page(str_q, page)
|
||||
else:
|
||||
raise ValueError(
|
||||
"Either querystring or more_like_doc_id is required."
|
||||
)
|
||||
def _get_query(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
result_page.results.fragmenter = highlight.ContextFragmenter(
|
||||
def _get_query_filter(self):
|
||||
criterias = []
|
||||
for k, v in self.query_params.items():
|
||||
if k == 'correspondent__id':
|
||||
criterias.append(query.Term('correspondent_id', v))
|
||||
elif k == 'tags__id__all':
|
||||
for tag_id in v.split(","):
|
||||
criterias.append(query.Term('tag_id', tag_id))
|
||||
elif k == 'document_type__id':
|
||||
criterias.append(query.Term('type_id', v))
|
||||
elif k == 'correspondent__isnull':
|
||||
criterias.append(query.Term("has_correspondent", v == "false"))
|
||||
elif k == 'is_tagged':
|
||||
criterias.append(query.Term("has_tag", v == "true"))
|
||||
elif k == 'document_type__isnull':
|
||||
criterias.append(query.Term("has_type", v == "false"))
|
||||
elif k == 'created__date__lt':
|
||||
criterias.append(
|
||||
query.DateRange("created", start=None, end=isoparse(v)))
|
||||
elif k == 'created__date__gt':
|
||||
criterias.append(
|
||||
query.DateRange("created", start=isoparse(v), end=None))
|
||||
elif k == 'added__date__gt':
|
||||
criterias.append(
|
||||
query.DateRange("added", start=isoparse(v), end=None))
|
||||
elif k == 'added__date__lt':
|
||||
criterias.append(
|
||||
query.DateRange("added", start=None, end=isoparse(v)))
|
||||
if len(criterias) > 0:
|
||||
return query.And(criterias)
|
||||
else:
|
||||
return None
|
||||
|
||||
def _get_query_sortedby(self):
|
||||
if 'ordering' not in self.query_params:
|
||||
return None, False
|
||||
|
||||
field: str = self.query_params['ordering']
|
||||
|
||||
sort_fields_map = {
|
||||
"created": "created",
|
||||
"modified": "modified",
|
||||
"added": "added",
|
||||
"title": "title",
|
||||
"correspondent__name": "correspondent",
|
||||
"document_type__name": "type",
|
||||
"archive_serial_number": "asn"
|
||||
}
|
||||
|
||||
if field.startswith('-'):
|
||||
field = field[1:]
|
||||
reverse = True
|
||||
else:
|
||||
reverse = False
|
||||
|
||||
if field not in sort_fields_map:
|
||||
return None, False
|
||||
else:
|
||||
return sort_fields_map[field], reverse
|
||||
|
||||
def __init__(self, searcher: Searcher, query_params, page_size):
|
||||
self.searcher = searcher
|
||||
self.query_params = query_params
|
||||
self.page_size = page_size
|
||||
self.saved_results = dict()
|
||||
self.first_score = None
|
||||
|
||||
def __len__(self):
|
||||
page = self[0:1]
|
||||
return len(page)
|
||||
|
||||
def __getitem__(self, item):
|
||||
if item.start in self.saved_results:
|
||||
return self.saved_results[item.start]
|
||||
|
||||
q, mask = self._get_query()
|
||||
sortedby, reverse = self._get_query_sortedby()
|
||||
|
||||
page: ResultsPage = self.searcher.search_page(
|
||||
q,
|
||||
mask=mask,
|
||||
filter=self._get_query_filter(),
|
||||
pagenum=math.floor(item.start / self.page_size) + 1,
|
||||
pagelen=self.page_size,
|
||||
sortedby=sortedby,
|
||||
reverse=reverse
|
||||
)
|
||||
page.results.fragmenter = highlight.ContextFragmenter(
|
||||
surround=50)
|
||||
result_page.results.formatter = JsonFormatter()
|
||||
page.results.formatter = HtmlFormatter(tagname="span", between=" ... ")
|
||||
|
||||
if corrected and corrected.query != str_q:
|
||||
if (not self.first_score and
|
||||
len(page.results) > 0 and
|
||||
sortedby is None):
|
||||
self.first_score = page.results[0].score
|
||||
|
||||
page.results.top_n = list(map(
|
||||
lambda hit: (
|
||||
(hit[0] / self.first_score) if self.first_score else None,
|
||||
hit[1]
|
||||
),
|
||||
page.results.top_n
|
||||
))
|
||||
|
||||
self.saved_results[item.start] = page
|
||||
|
||||
return page
|
||||
|
||||
|
||||
class DelayedFullTextQuery(DelayedQuery):
|
||||
|
||||
def _get_query(self):
|
||||
q_str = self.query_params['query']
|
||||
qp = MultifieldParser(
|
||||
["content", "title", "correspondent", "tag", "type"],
|
||||
self.searcher.ixreader.schema)
|
||||
qp.add_plugin(DateParserPlugin())
|
||||
q = qp.parse(q_str)
|
||||
|
||||
corrected = self.searcher.correct_query(q, q_str)
|
||||
if corrected.query != q:
|
||||
corrected_query = corrected.string
|
||||
else:
|
||||
corrected_query = None
|
||||
|
||||
yield result_page, corrected_query
|
||||
finally:
|
||||
searcher.close()
|
||||
return q, None
|
||||
|
||||
|
||||
class DelayedMoreLikeThisQuery(DelayedQuery):
|
||||
|
||||
def _get_query(self):
|
||||
more_like_doc_id = int(self.query_params['more_like_id'])
|
||||
content = Document.objects.get(id=more_like_doc_id).content
|
||||
|
||||
docnum = self.searcher.document_number(id=more_like_doc_id)
|
||||
kts = self.searcher.key_terms_from_text(
|
||||
'content', content, numterms=20,
|
||||
model=classify.Bo1Model, normalize=False)
|
||||
q = query.Or(
|
||||
[query.Term('content', word, boost=weight)
|
||||
for word, weight in kts])
|
||||
mask = {docnum}
|
||||
|
||||
return q, mask
|
||||
|
||||
|
||||
def autocomplete(ix, term, limit=10):
|
||||
|
||||
@@ -4,33 +4,24 @@ import uuid
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class PaperlessHandler(logging.Handler):
|
||||
def emit(self, record):
|
||||
if settings.DISABLE_DBHANDLER:
|
||||
return
|
||||
|
||||
# We have to do the import here or Django will barf when it tries to
|
||||
# load this because the apps aren't loaded at that point
|
||||
from .models import Log
|
||||
|
||||
kwargs = {"message": record.msg, "level": record.levelno}
|
||||
|
||||
if hasattr(record, "group"):
|
||||
kwargs["group"] = record.group
|
||||
|
||||
Log.objects.create(**kwargs)
|
||||
|
||||
|
||||
class LoggingMixin:
|
||||
|
||||
logging_group = None
|
||||
|
||||
logging_name = None
|
||||
|
||||
def renew_logging_group(self):
|
||||
self.logging_group = uuid.uuid4()
|
||||
|
||||
def log(self, level, message, **kwargs):
|
||||
target = ".".join([self.__class__.__module__, self.__class__.__name__])
|
||||
logger = logging.getLogger(target)
|
||||
if self.logging_name:
|
||||
logger = logging.getLogger(self.logging_name)
|
||||
else:
|
||||
name = ".".join([
|
||||
self.__class__.__module__,
|
||||
self.__class__.__name__
|
||||
])
|
||||
logger = logging.getLogger(name)
|
||||
|
||||
getattr(logger, level)(message, extra={
|
||||
"group": self.logging_group
|
||||
|
||||
@@ -16,12 +16,12 @@ from whoosh.writing import AsyncWriter
|
||||
|
||||
from documents.models import Document
|
||||
from ... import index
|
||||
from ...file_handling import create_source_path_directory
|
||||
from ...mixins import Renderable
|
||||
from ...file_handling import create_source_path_directory, \
|
||||
generate_unique_filename
|
||||
from ...parsers import get_parser_class_for_mime_type
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger = logging.getLogger("paperless.management.archiver")
|
||||
|
||||
|
||||
def handle_document(document_id):
|
||||
@@ -31,38 +31,57 @@ def handle_document(document_id):
|
||||
|
||||
parser_class = get_parser_class_for_mime_type(mime_type)
|
||||
|
||||
if not parser_class:
|
||||
logger.error(f"No parser found for mime type {mime_type}, cannot "
|
||||
f"archive document {document} (ID: {document_id})")
|
||||
return
|
||||
|
||||
parser = parser_class(logging_group=uuid.uuid4())
|
||||
|
||||
try:
|
||||
parser.parse(document.source_path, mime_type)
|
||||
parser.parse(
|
||||
document.source_path,
|
||||
mime_type,
|
||||
document.get_public_filename())
|
||||
|
||||
thumbnail = parser.get_optimised_thumbnail(
|
||||
document.source_path,
|
||||
mime_type,
|
||||
document.get_public_filename()
|
||||
)
|
||||
|
||||
if parser.get_archive_path():
|
||||
with transaction.atomic():
|
||||
with open(parser.get_archive_path(), 'rb') as f:
|
||||
checksum = hashlib.md5(f.read()).hexdigest()
|
||||
# i'm going to save first so that in case the file move
|
||||
# I'm going to save first so that in case the file move
|
||||
# fails, the database is rolled back.
|
||||
# we also don't use save() since that triggers the filehandling
|
||||
# We also don't use save() since that triggers the filehandling
|
||||
# logic, and we don't want that yet (file not yet in place)
|
||||
document.archive_filename = generate_unique_filename(
|
||||
document, archive_filename=True)
|
||||
Document.objects.filter(pk=document.pk).update(
|
||||
archive_checksum=checksum,
|
||||
content=parser.get_text()
|
||||
content=parser.get_text(),
|
||||
archive_filename=document.archive_filename
|
||||
)
|
||||
with FileLock(settings.MEDIA_LOCK):
|
||||
create_source_path_directory(document.archive_path)
|
||||
shutil.move(parser.get_archive_path(),
|
||||
document.archive_path)
|
||||
shutil.move(thumbnail, document.thumbnail_path)
|
||||
|
||||
with AsyncWriter(index.open_index()) as writer:
|
||||
index.update_document(writer, document)
|
||||
with index.open_index_writer() as writer:
|
||||
index.update_document(writer, document)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error while parsing document {document}: {str(e)}")
|
||||
logger.exception(f"Error while parsing document {document} "
|
||||
f"(ID: {document_id})")
|
||||
finally:
|
||||
parser.cleanup()
|
||||
|
||||
|
||||
class Command(Renderable, BaseCommand):
|
||||
class Command(BaseCommand):
|
||||
|
||||
help = """
|
||||
Using the current classification model, assigns correspondents, tags
|
||||
@@ -71,10 +90,6 @@ class Command(Renderable, BaseCommand):
|
||||
modified) after their initial import.
|
||||
""".replace(" ", "")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.verbosity = 0
|
||||
BaseCommand.__init__(self, *args, **kwargs)
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"-f", "--overwrite",
|
||||
@@ -91,6 +106,12 @@ class Command(Renderable, BaseCommand):
|
||||
help="Specify the ID of a document, and this command will only "
|
||||
"run on this specific document."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-progress-bar",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="If set, the progress bar will not be shown"
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
@@ -106,7 +127,7 @@ class Command(Renderable, BaseCommand):
|
||||
document_ids = list(map(
|
||||
lambda doc: doc.id,
|
||||
filter(
|
||||
lambda d: overwrite or not d.archive_checksum,
|
||||
lambda d: overwrite or not d.has_archive_version,
|
||||
documents
|
||||
)
|
||||
))
|
||||
@@ -125,7 +146,8 @@ class Command(Renderable, BaseCommand):
|
||||
handle_document,
|
||||
document_ids
|
||||
),
|
||||
total=len(document_ids)
|
||||
total=len(document_ids),
|
||||
disable=options['no_progress_bar']
|
||||
))
|
||||
except KeyboardInterrupt:
|
||||
print("Aborting...")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from pathlib import Path, PurePath
|
||||
from threading import Thread
|
||||
from time import sleep
|
||||
|
||||
from django.conf import settings
|
||||
@@ -17,11 +18,11 @@ try:
|
||||
except ImportError:
|
||||
INotify = flags = None
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger = logging.getLogger("paperless.management.consumer")
|
||||
|
||||
|
||||
def _tags_from_path(filepath):
|
||||
"""Walk up the directory tree from filepath to CONSUMPTION_DIr
|
||||
"""Walk up the directory tree from filepath to CONSUMPTION_DIR
|
||||
and get or create Tag IDs for every directory.
|
||||
"""
|
||||
tag_ids = set()
|
||||
@@ -35,8 +36,15 @@ def _tags_from_path(filepath):
|
||||
return tag_ids
|
||||
|
||||
|
||||
def _is_ignored(filepath: str) -> bool:
|
||||
filepath_relative = PurePath(filepath).relative_to(
|
||||
settings.CONSUMPTION_DIR)
|
||||
return any(
|
||||
filepath_relative.match(p) for p in settings.CONSUMER_IGNORE_PATTERNS)
|
||||
|
||||
|
||||
def _consume(filepath):
|
||||
if os.path.isdir(filepath):
|
||||
if os.path.isdir(filepath) or _is_ignored(filepath):
|
||||
return
|
||||
|
||||
if not os.path.isfile(filepath):
|
||||
@@ -54,10 +62,10 @@ def _consume(filepath):
|
||||
if settings.CONSUMER_SUBDIRS_AS_TAGS:
|
||||
tag_ids = _tags_from_path(filepath)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Error creating tags from path: {}".format(e))
|
||||
logger.exception("Error creating tags from path")
|
||||
|
||||
try:
|
||||
logger.info(f"Adding {filepath} to the task queue.")
|
||||
async_task("documents.tasks.consume_file",
|
||||
filepath,
|
||||
override_tag_ids=tag_ids if tag_ids else None,
|
||||
@@ -66,14 +74,17 @@ def _consume(filepath):
|
||||
# Catch all so that the consumer won't crash.
|
||||
# This is also what the test case is listening for to check for
|
||||
# errors.
|
||||
logger.error(
|
||||
"Error while consuming document: {}".format(e))
|
||||
logger.exception("Error while consuming document")
|
||||
|
||||
|
||||
def _consume_wait_unmodified(file, num_tries=20, wait_time=1):
|
||||
def _consume_wait_unmodified(file):
|
||||
if _is_ignored(file):
|
||||
return
|
||||
|
||||
logger.debug(f"Waiting for file {file} to remain unmodified")
|
||||
mtime = -1
|
||||
current_try = 0
|
||||
while current_try < num_tries:
|
||||
while current_try < settings.CONSUMER_POLLING_RETRY_COUNT:
|
||||
try:
|
||||
new_mtime = os.stat(file).st_mtime
|
||||
except FileNotFoundError:
|
||||
@@ -84,7 +95,7 @@ def _consume_wait_unmodified(file, num_tries=20, wait_time=1):
|
||||
_consume(file)
|
||||
return
|
||||
mtime = new_mtime
|
||||
sleep(wait_time)
|
||||
sleep(settings.CONSUMER_POLLING_DELAY)
|
||||
current_try += 1
|
||||
|
||||
logger.error(f"Timeout while waiting on file {file} to remain unmodified.")
|
||||
@@ -93,10 +104,14 @@ def _consume_wait_unmodified(file, num_tries=20, wait_time=1):
|
||||
class Handler(FileSystemEventHandler):
|
||||
|
||||
def on_created(self, event):
|
||||
_consume_wait_unmodified(event.src_path)
|
||||
Thread(
|
||||
target=_consume_wait_unmodified, args=(event.src_path,)
|
||||
).start()
|
||||
|
||||
def on_moved(self, event):
|
||||
_consume_wait_unmodified(event.dest_path)
|
||||
Thread(
|
||||
target=_consume_wait_unmodified, args=(event.dest_path,)
|
||||
).start()
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
@@ -108,12 +123,7 @@ class Command(BaseCommand):
|
||||
# This is here primarily for the tests and is irrelevant in production.
|
||||
stop_flag = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
BaseCommand.__init__(self, *args, **kwargs)
|
||||
self.observer = None
|
||||
observer = None
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
@@ -153,7 +163,7 @@ class Command(BaseCommand):
|
||||
if options["oneshot"]:
|
||||
return
|
||||
|
||||
if settings.CONSUMER_POLLING == 0:
|
||||
if settings.CONSUMER_POLLING == 0 and INotify:
|
||||
self.handle_inotify(directory, recursive)
|
||||
else:
|
||||
self.handle_polling(directory, recursive)
|
||||
@@ -161,7 +171,7 @@ class Command(BaseCommand):
|
||||
logger.debug("Consumer exiting.")
|
||||
|
||||
def handle_polling(self, directory, recursive):
|
||||
logging.getLogger(__name__).info(
|
||||
logger.info(
|
||||
f"Polling directory for changes: {directory}")
|
||||
self.observer = PollingObserver(timeout=settings.CONSUMER_POLLING)
|
||||
self.observer.schedule(Handler(), directory, recursive=recursive)
|
||||
@@ -176,7 +186,7 @@ class Command(BaseCommand):
|
||||
self.observer.join()
|
||||
|
||||
def handle_inotify(self, directory, recursive):
|
||||
logging.getLogger(__name__).info(
|
||||
logger.info(
|
||||
f"Using inotify to watch directory for changes: {directory}")
|
||||
|
||||
inotify = INotify()
|
||||
|
||||
3
src/documents/management/commands/document_create_classifier.py
Executable file → Normal file
3
src/documents/management/commands/document_create_classifier.py
Executable file → Normal file
@@ -1,10 +1,9 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from ...mixins import Renderable
|
||||
from ...tasks import train_classifier
|
||||
|
||||
|
||||
class Command(Renderable, BaseCommand):
|
||||
class Command(BaseCommand):
|
||||
|
||||
help = """
|
||||
Trains the classifier on your data and saves the resulting models to a
|
||||
|
||||
@@ -6,20 +6,22 @@ import time
|
||||
|
||||
import tqdm
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core import serializers
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
from filelock import FileLock
|
||||
|
||||
from documents.models import Document, Correspondent, Tag, DocumentType
|
||||
from documents.models import Document, Correspondent, Tag, DocumentType, \
|
||||
SavedView, SavedViewFilterRule
|
||||
from documents.settings import EXPORTER_FILE_NAME, EXPORTER_THUMBNAIL_NAME, \
|
||||
EXPORTER_ARCHIVE_NAME
|
||||
from paperless.db import GnuPG
|
||||
from paperless_mail.models import MailAccount, MailRule
|
||||
from ...file_handling import generate_filename, delete_empty_directories
|
||||
from ...mixins import Renderable
|
||||
|
||||
|
||||
class Command(Renderable, BaseCommand):
|
||||
class Command(BaseCommand):
|
||||
|
||||
help = """
|
||||
Decrypt and rename all files in our collection into a given target
|
||||
@@ -55,6 +57,12 @@ class Command(Renderable, BaseCommand):
|
||||
"do not belong to the current export, such as files from "
|
||||
"deleted documents."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-progress-bar",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="If set, the progress bar will not be shown"
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
BaseCommand.__init__(self, *args, **kwargs)
|
||||
@@ -79,9 +87,9 @@ class Command(Renderable, BaseCommand):
|
||||
raise CommandError("That path doesn't appear to be writable")
|
||||
|
||||
with FileLock(settings.MEDIA_LOCK):
|
||||
self.dump()
|
||||
self.dump(options['no_progress_bar'])
|
||||
|
||||
def dump(self):
|
||||
def dump(self, progress_bar_disable=False):
|
||||
# 1. Take a snapshot of what files exist in the current export folder
|
||||
for root, dirs, files in os.walk(self.target):
|
||||
self.files_in_export_dir.extend(
|
||||
@@ -106,9 +114,27 @@ class Command(Renderable, BaseCommand):
|
||||
serializers.serialize("json", documents))
|
||||
manifest += document_manifest
|
||||
|
||||
manifest += json.loads(serializers.serialize(
|
||||
"json", MailAccount.objects.all()))
|
||||
|
||||
manifest += json.loads(serializers.serialize(
|
||||
"json", MailRule.objects.all()))
|
||||
|
||||
manifest += json.loads(serializers.serialize(
|
||||
"json", SavedView.objects.all()))
|
||||
|
||||
manifest += json.loads(serializers.serialize(
|
||||
"json", SavedViewFilterRule.objects.all()))
|
||||
|
||||
manifest += json.loads(serializers.serialize(
|
||||
"json", User.objects.all()))
|
||||
|
||||
# 3. Export files from each document
|
||||
for index, document_dict in tqdm.tqdm(enumerate(document_manifest),
|
||||
total=len(document_manifest)):
|
||||
for index, document_dict in tqdm.tqdm(
|
||||
enumerate(document_manifest),
|
||||
total=len(document_manifest),
|
||||
disable=progress_bar_disable
|
||||
):
|
||||
# 3.1. store files unencrypted
|
||||
document_dict["fields"]["storage_type"] = Document.STORAGE_TYPE_UNENCRYPTED # NOQA: E501
|
||||
|
||||
@@ -140,7 +166,7 @@ class Command(Renderable, BaseCommand):
|
||||
thumbnail_target = os.path.join(self.target, thumbnail_name)
|
||||
document_dict[EXPORTER_THUMBNAIL_NAME] = thumbnail_name
|
||||
|
||||
if os.path.exists(document.archive_path):
|
||||
if document.has_archive_version:
|
||||
archive_name = base_name + "-archive.pdf"
|
||||
archive_target = os.path.join(self.target, archive_name)
|
||||
document_dict[EXPORTER_ARCHIVE_NAME] = archive_name
|
||||
|
||||
@@ -15,7 +15,6 @@ from documents.models import Document
|
||||
from documents.settings import EXPORTER_FILE_NAME, EXPORTER_THUMBNAIL_NAME, \
|
||||
EXPORTER_ARCHIVE_NAME
|
||||
from ...file_handling import create_source_path_directory
|
||||
from ...mixins import Renderable
|
||||
from ...signals.handlers import update_filename_and_move_files
|
||||
|
||||
|
||||
@@ -28,7 +27,7 @@ def disable_signal(sig, receiver, sender):
|
||||
sig.connect(receiver=receiver, sender=sender)
|
||||
|
||||
|
||||
class Command(Renderable, BaseCommand):
|
||||
class Command(BaseCommand):
|
||||
|
||||
help = """
|
||||
Using a manifest.json file, load the data from there, and import the
|
||||
@@ -37,6 +36,12 @@ class Command(Renderable, BaseCommand):
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("source")
|
||||
parser.add_argument(
|
||||
"--no-progress-bar",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="If set, the progress bar will not be shown"
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
BaseCommand.__init__(self, *args, **kwargs)
|
||||
@@ -71,7 +76,7 @@ class Command(Renderable, BaseCommand):
|
||||
# Fill up the database with whatever is in the manifest
|
||||
call_command("loaddata", manifest_path)
|
||||
|
||||
self._import_files_from_manifest()
|
||||
self._import_files_from_manifest(options['no_progress_bar'])
|
||||
|
||||
print("Updating search index...")
|
||||
call_command('document_index', 'reindex')
|
||||
@@ -112,7 +117,7 @@ class Command(Renderable, BaseCommand):
|
||||
f"does not appear to be in the source directory."
|
||||
)
|
||||
|
||||
def _import_files_from_manifest(self):
|
||||
def _import_files_from_manifest(self, progress_bar_disable):
|
||||
|
||||
os.makedirs(settings.ORIGINALS_DIR, exist_ok=True)
|
||||
os.makedirs(settings.THUMBNAIL_DIR, exist_ok=True)
|
||||
@@ -124,7 +129,10 @@ class Command(Renderable, BaseCommand):
|
||||
lambda r: r["model"] == "documents.document",
|
||||
self.manifest))
|
||||
|
||||
for record in tqdm.tqdm(manifest_documents):
|
||||
for record in tqdm.tqdm(
|
||||
manifest_documents,
|
||||
disable=progress_bar_disable
|
||||
):
|
||||
|
||||
document = Document.objects.get(pk=record["pk"])
|
||||
|
||||
@@ -152,6 +160,9 @@ class Command(Renderable, BaseCommand):
|
||||
shutil.copy2(thumbnail_path, document.thumbnail_path)
|
||||
if archive_path:
|
||||
create_source_path_directory(document.archive_path)
|
||||
# TODO: this assumes that the export is valid and
|
||||
# archive_filename is present on all documents with
|
||||
# archived files
|
||||
shutil.copy2(archive_path, document.archive_path)
|
||||
|
||||
document.save()
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
from django.core.management import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from documents.mixins import Renderable
|
||||
from documents.tasks import index_reindex, index_optimize
|
||||
|
||||
|
||||
class Command(Renderable, BaseCommand):
|
||||
class Command(BaseCommand):
|
||||
|
||||
help = "Manages the document index."
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.verbosity = 0
|
||||
BaseCommand.__init__(self, *args, **kwargs)
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("command", choices=['reindex', 'optimize'])
|
||||
parser.add_argument(
|
||||
"--no-progress-bar",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="If set, the progress bar will not be shown"
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
self.verbosity = options["verbosity"]
|
||||
with transaction.atomic():
|
||||
if options['command'] == 'reindex':
|
||||
index_reindex()
|
||||
index_reindex(progress_bar_disable=options['no_progress_bar'])
|
||||
elif options['command'] == 'optimize':
|
||||
index_optimize()
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from documents.models import Log
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
help = "A quick & dirty way to see what's in the logs"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
for log in Log.objects.order_by("pk"):
|
||||
print(log)
|
||||
@@ -5,24 +5,28 @@ from django.core.management.base import BaseCommand
|
||||
from django.db.models.signals import post_save
|
||||
|
||||
from documents.models import Document
|
||||
from ...mixins import Renderable
|
||||
|
||||
|
||||
class Command(Renderable, BaseCommand):
|
||||
class Command(BaseCommand):
|
||||
|
||||
help = """
|
||||
This will rename all documents to match the latest filename format.
|
||||
""".replace(" ", "")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.verbosity = 0
|
||||
BaseCommand.__init__(self, *args, **kwargs)
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--no-progress-bar",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="If set, the progress bar will not be shown"
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
self.verbosity = options["verbosity"]
|
||||
|
||||
logging.getLogger().handlers[0].level = logging.ERROR
|
||||
|
||||
for document in tqdm.tqdm(Document.objects.all()):
|
||||
for document in tqdm.tqdm(
|
||||
Document.objects.all(),
|
||||
disable=options['no_progress_bar']
|
||||
):
|
||||
post_save.send(Document, instance=document)
|
||||
|
||||
64
src/documents/management/commands/document_retagger.py
Executable file → Normal file
64
src/documents/management/commands/document_retagger.py
Executable file → Normal file
@@ -1,15 +1,17 @@
|
||||
import logging
|
||||
|
||||
import tqdm
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from documents.classifier import DocumentClassifier, \
|
||||
IncompatibleClassifierVersionError
|
||||
from documents.classifier import load_classifier
|
||||
from documents.models import Document
|
||||
from ...mixins import Renderable
|
||||
from ...signals.handlers import set_correspondent, set_document_type, set_tags
|
||||
|
||||
|
||||
class Command(Renderable, BaseCommand):
|
||||
logger = logging.getLogger("paperless.management.retagger")
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
help = """
|
||||
Using the current classification model, assigns correspondents, tags
|
||||
@@ -18,10 +20,6 @@ class Command(Renderable, BaseCommand):
|
||||
modified) after their initial import.
|
||||
""".replace(" ", "")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.verbosity = 0
|
||||
BaseCommand.__init__(self, *args, **kwargs)
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"-c", "--correspondent",
|
||||
@@ -59,10 +57,26 @@ class Command(Renderable, BaseCommand):
|
||||
"set correspondent, document and remove correspondents, types"
|
||||
"and tags that do not match anymore due to changed rules."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-progress-bar",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="If set, the progress bar will not be shown"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--suggest",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Return the suggestion, don't change anything."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--base-url",
|
||||
help="The base URL to use to build the link to the documents."
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
self.verbosity = options["verbosity"]
|
||||
# Detect if we support color
|
||||
color = self.style.ERROR("test") != "test"
|
||||
|
||||
if options["inbox_only"]:
|
||||
queryset = Document.objects.filter(tags__is_inbox_tag=True)
|
||||
@@ -70,17 +84,12 @@ class Command(Renderable, BaseCommand):
|
||||
queryset = Document.objects.all()
|
||||
documents = queryset.distinct()
|
||||
|
||||
classifier = DocumentClassifier()
|
||||
try:
|
||||
classifier.reload()
|
||||
except (OSError, EOFError, IncompatibleClassifierVersionError) as e:
|
||||
logging.getLogger(__name__).warning(
|
||||
f"Cannot classify documents: {e}.")
|
||||
classifier = None
|
||||
classifier = load_classifier()
|
||||
|
||||
for document in documents:
|
||||
logging.getLogger(__name__).info(
|
||||
f"Processing document {document.title}")
|
||||
for document in tqdm.tqdm(
|
||||
documents,
|
||||
disable=options['no_progress_bar']
|
||||
):
|
||||
|
||||
if options['correspondent']:
|
||||
set_correspondent(
|
||||
@@ -88,18 +97,27 @@ class Command(Renderable, BaseCommand):
|
||||
document=document,
|
||||
classifier=classifier,
|
||||
replace=options['overwrite'],
|
||||
use_first=options['use_first'])
|
||||
use_first=options['use_first'],
|
||||
suggest=options['suggest'],
|
||||
base_url=options['base_url'],
|
||||
color=color)
|
||||
|
||||
if options['document_type']:
|
||||
set_document_type(sender=None,
|
||||
document=document,
|
||||
classifier=classifier,
|
||||
replace=options['overwrite'],
|
||||
use_first=options['use_first'])
|
||||
use_first=options['use_first'],
|
||||
suggest=options['suggest'],
|
||||
base_url=options['base_url'],
|
||||
color=color)
|
||||
|
||||
if options['tags']:
|
||||
set_tags(
|
||||
sender=None,
|
||||
document=document,
|
||||
classifier=classifier,
|
||||
replace=options['overwrite'])
|
||||
replace=options['overwrite'],
|
||||
suggest=options['suggest'],
|
||||
base_url=options['base_url'],
|
||||
color=color)
|
||||
|
||||
23
src/documents/management/commands/document_sanity_checker.py
Normal file
23
src/documents/management/commands/document_sanity_checker.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from documents.sanity_checker import check_sanity
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
help = """
|
||||
This command checks your document archive for issues.
|
||||
""".replace(" ", "")
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--no-progress-bar",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="If set, the progress bar will not be shown"
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
messages = check_sanity(progress=not options['no_progress_bar'])
|
||||
|
||||
messages.log_messages()
|
||||
@@ -7,7 +7,6 @@ from django import db
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from documents.models import Document
|
||||
from ...mixins import Renderable
|
||||
from ...parsers import get_parser_class_for_mime_type
|
||||
|
||||
|
||||
@@ -23,23 +22,22 @@ def _process_document(doc_in):
|
||||
|
||||
try:
|
||||
thumb = parser.get_optimised_thumbnail(
|
||||
document.source_path, document.mime_type)
|
||||
document.source_path,
|
||||
document.mime_type,
|
||||
document.get_public_filename()
|
||||
)
|
||||
|
||||
shutil.move(thumb, document.thumbnail_path)
|
||||
finally:
|
||||
parser.cleanup()
|
||||
|
||||
|
||||
class Command(Renderable, BaseCommand):
|
||||
class Command(BaseCommand):
|
||||
|
||||
help = """
|
||||
This will regenerate the thumbnails for all documents.
|
||||
""".replace(" ", "")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.verbosity = 0
|
||||
BaseCommand.__init__(self, *args, **kwargs)
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"-d", "--document",
|
||||
@@ -49,11 +47,14 @@ class Command(Renderable, BaseCommand):
|
||||
help="Specify the ID of a document, and this command will only "
|
||||
"run on this specific document."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-progress-bar",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="If set, the progress bar will not be shown"
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
self.verbosity = options["verbosity"]
|
||||
|
||||
logging.getLogger().handlers[0].level = logging.ERROR
|
||||
|
||||
if options['document']:
|
||||
@@ -70,5 +71,7 @@ class Command(Renderable, BaseCommand):
|
||||
|
||||
with multiprocessing.Pool() as pool:
|
||||
list(tqdm.tqdm(
|
||||
pool.imap_unordered(_process_document, ids), total=len(ids)
|
||||
pool.imap_unordered(_process_document, ids),
|
||||
total=len(ids),
|
||||
disable=options['no_progress_bar']
|
||||
))
|
||||
|
||||
42
src/documents/management/commands/manage_superuser.py
Normal file
42
src/documents/management/commands/manage_superuser.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
|
||||
logger = logging.getLogger("paperless.management.superuser")
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
help = """
|
||||
Creates a Django superuser based on env variables.
|
||||
""".replace(" ", "")
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
username = os.getenv('PAPERLESS_ADMIN_USER')
|
||||
if not username:
|
||||
return
|
||||
|
||||
mail = os.getenv('PAPERLESS_ADMIN_MAIL', 'root@localhost')
|
||||
password = os.getenv('PAPERLESS_ADMIN_PASSWORD')
|
||||
|
||||
# Check if user exists already, leave as is if it does
|
||||
if User.objects.filter(username=username).exists():
|
||||
user: User = User.objects.get_by_natural_key(username)
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
self.stdout.write(f"Changed password of user {username}.")
|
||||
elif password:
|
||||
# Create superuser based on env variables
|
||||
User.objects.create_superuser(username, mail, password)
|
||||
self.stdout.write(
|
||||
f'Created superuser "{username}" with provided password.')
|
||||
else:
|
||||
self.stdout.write(
|
||||
f'Did not create superuser "{username}".')
|
||||
self.stdout.write(
|
||||
'Make sure you specified "PAPERLESS_ADMIN_PASSWORD" in your '
|
||||
'"docker-compose.env" file.')
|
||||
@@ -1,18 +1,17 @@
|
||||
import logging
|
||||
import re
|
||||
|
||||
from fuzzywuzzy import fuzz
|
||||
|
||||
from documents.models import MatchingModel, Correspondent, DocumentType, Tag
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger = logging.getLogger("paperless.matching")
|
||||
|
||||
|
||||
def log_reason(matching_model, document, reason):
|
||||
class_name = type(matching_model).__name__
|
||||
logger.debug(
|
||||
f"Assigning {class_name} {matching_model.name} to document "
|
||||
f"{class_name} {matching_model.name} matched on document "
|
||||
f"{document} because {reason}")
|
||||
|
||||
|
||||
@@ -91,7 +90,7 @@ def matches(matching_model, document):
|
||||
|
||||
elif matching_model.matching_algorithm == MatchingModel.MATCH_LITERAL:
|
||||
result = bool(re.search(
|
||||
rf"\b{matching_model.match}\b",
|
||||
rf"\b{re.escape(matching_model.match)}\b",
|
||||
document_content,
|
||||
**search_kwargs
|
||||
))
|
||||
@@ -123,6 +122,8 @@ def matches(matching_model, document):
|
||||
return bool(match)
|
||||
|
||||
elif matching_model.matching_algorithm == MatchingModel.MATCH_FUZZY:
|
||||
from fuzzywuzzy import fuzz
|
||||
|
||||
match = re.sub(r'[^\w\s]', '', matching_model.match)
|
||||
text = re.sub(r'[^\w\s]', '', document_content)
|
||||
if matching_model.is_insensitive:
|
||||
@@ -160,6 +161,9 @@ def _split_match(matching_model):
|
||||
findterms = re.compile(r'"([^"]+)"|(\S+)').findall
|
||||
normspace = re.compile(r"\s+").sub
|
||||
return [
|
||||
normspace(" ", (t[0] or t[1]).strip()).replace(" ", r"\s+")
|
||||
# normspace(" ", (t[0] or t[1]).strip()).replace(" ", r"\s+")
|
||||
re.escape(
|
||||
normspace(" ", (t[0] or t[1]).strip())
|
||||
).replace(r"\ ", r"\s+")
|
||||
for t in findterms(matching_model.match)
|
||||
]
|
||||
|
||||
330
src/documents/migrations/1012_fix_archive_files.py
Normal file
330
src/documents/migrations/1012_fix_archive_files.py
Normal file
@@ -0,0 +1,330 @@
|
||||
# Generated by Django 3.1.6 on 2021-02-07 22:26
|
||||
import datetime
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
from time import sleep
|
||||
|
||||
import pathvalidate
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
from django.template.defaultfilters import slugify
|
||||
|
||||
from documents.file_handling import defaultdictNoStr, many_to_dictionary
|
||||
|
||||
|
||||
logger = logging.getLogger("paperless.migrations")
|
||||
|
||||
###############################################################################
|
||||
# This is code copied straight paperless before the change.
|
||||
###############################################################################
|
||||
|
||||
def archive_name_from_filename(filename):
|
||||
return os.path.splitext(filename)[0] + ".pdf"
|
||||
|
||||
|
||||
def archive_path_old(doc):
|
||||
if doc.filename:
|
||||
fname = archive_name_from_filename(doc.filename)
|
||||
else:
|
||||
fname = "{:07}.pdf".format(doc.pk)
|
||||
|
||||
return os.path.join(
|
||||
settings.ARCHIVE_DIR,
|
||||
fname
|
||||
)
|
||||
|
||||
|
||||
STORAGE_TYPE_GPG = "gpg"
|
||||
|
||||
|
||||
def archive_path_new(doc):
|
||||
if doc.archive_filename is not None:
|
||||
return os.path.join(
|
||||
settings.ARCHIVE_DIR,
|
||||
str(doc.archive_filename)
|
||||
)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def source_path(doc):
|
||||
if doc.filename:
|
||||
fname = str(doc.filename)
|
||||
else:
|
||||
fname = "{:07}{}".format(doc.pk, doc.file_type)
|
||||
if doc.storage_type == STORAGE_TYPE_GPG:
|
||||
fname += ".gpg" # pragma: no cover
|
||||
|
||||
return os.path.join(
|
||||
settings.ORIGINALS_DIR,
|
||||
fname
|
||||
)
|
||||
|
||||
|
||||
def generate_unique_filename(doc, archive_filename=False):
|
||||
if archive_filename:
|
||||
old_filename = doc.archive_filename
|
||||
root = settings.ARCHIVE_DIR
|
||||
else:
|
||||
old_filename = doc.filename
|
||||
root = settings.ORIGINALS_DIR
|
||||
|
||||
counter = 0
|
||||
|
||||
while True:
|
||||
new_filename = generate_filename(
|
||||
doc, counter, archive_filename=archive_filename)
|
||||
if new_filename == old_filename:
|
||||
# still the same as before.
|
||||
return new_filename
|
||||
|
||||
if os.path.exists(os.path.join(root, new_filename)):
|
||||
counter += 1
|
||||
else:
|
||||
return new_filename
|
||||
|
||||
|
||||
def generate_filename(doc, counter=0, append_gpg=True, archive_filename=False):
|
||||
path = ""
|
||||
|
||||
try:
|
||||
if settings.PAPERLESS_FILENAME_FORMAT is not None:
|
||||
tags = defaultdictNoStr(lambda: slugify(None),
|
||||
many_to_dictionary(doc.tags))
|
||||
|
||||
tag_list = pathvalidate.sanitize_filename(
|
||||
",".join(sorted(
|
||||
[tag.name for tag in doc.tags.all()]
|
||||
)),
|
||||
replacement_text="-"
|
||||
)
|
||||
|
||||
if doc.correspondent:
|
||||
correspondent = pathvalidate.sanitize_filename(
|
||||
doc.correspondent.name, replacement_text="-"
|
||||
)
|
||||
else:
|
||||
correspondent = "none"
|
||||
|
||||
if doc.document_type:
|
||||
document_type = pathvalidate.sanitize_filename(
|
||||
doc.document_type.name, replacement_text="-"
|
||||
)
|
||||
else:
|
||||
document_type = "none"
|
||||
|
||||
path = settings.PAPERLESS_FILENAME_FORMAT.format(
|
||||
title=pathvalidate.sanitize_filename(
|
||||
doc.title, replacement_text="-"),
|
||||
correspondent=correspondent,
|
||||
document_type=document_type,
|
||||
created=datetime.date.isoformat(doc.created),
|
||||
created_year=doc.created.year if doc.created else "none",
|
||||
created_month=f"{doc.created.month:02}" if doc.created else "none", # NOQA: E501
|
||||
created_day=f"{doc.created.day:02}" if doc.created else "none",
|
||||
added=datetime.date.isoformat(doc.added),
|
||||
added_year=doc.added.year if doc.added else "none",
|
||||
added_month=f"{doc.added.month:02}" if doc.added else "none",
|
||||
added_day=f"{doc.added.day:02}" if doc.added else "none",
|
||||
tags=tags,
|
||||
tag_list=tag_list
|
||||
).strip()
|
||||
|
||||
path = path.strip(os.sep)
|
||||
|
||||
except (ValueError, KeyError, IndexError):
|
||||
logger.warning(
|
||||
f"Invalid PAPERLESS_FILENAME_FORMAT: "
|
||||
f"{settings.PAPERLESS_FILENAME_FORMAT}, falling back to default")
|
||||
|
||||
counter_str = f"_{counter:02}" if counter else ""
|
||||
|
||||
filetype_str = ".pdf" if archive_filename else doc.file_type
|
||||
|
||||
if len(path) > 0:
|
||||
filename = f"{path}{counter_str}{filetype_str}"
|
||||
else:
|
||||
filename = f"{doc.pk:07}{counter_str}{filetype_str}"
|
||||
|
||||
# Append .gpg for encrypted files
|
||||
if append_gpg and doc.storage_type == STORAGE_TYPE_GPG:
|
||||
filename += ".gpg"
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
###############################################################################
|
||||
# This code performs bidirection archive file transformation.
|
||||
###############################################################################
|
||||
|
||||
|
||||
def parse_wrapper(parser, path, mime_type, file_name):
|
||||
# this is here so that I can mock this out for testing.
|
||||
parser.parse(path, mime_type, file_name)
|
||||
|
||||
|
||||
def create_archive_version(doc, retry_count=3):
|
||||
from documents.parsers import get_parser_class_for_mime_type, \
|
||||
DocumentParser, \
|
||||
ParseError
|
||||
|
||||
logger.info(
|
||||
f"Regenerating archive document for document ID:{doc.id}"
|
||||
)
|
||||
parser_class = get_parser_class_for_mime_type(doc.mime_type)
|
||||
for try_num in range(retry_count):
|
||||
parser: DocumentParser = parser_class(None, None)
|
||||
try:
|
||||
parse_wrapper(parser, source_path(doc), doc.mime_type,
|
||||
os.path.basename(doc.filename))
|
||||
doc.content = parser.get_text()
|
||||
|
||||
if parser.get_archive_path() and os.path.isfile(
|
||||
parser.get_archive_path()):
|
||||
doc.archive_filename = generate_unique_filename(
|
||||
doc, archive_filename=True)
|
||||
with open(parser.get_archive_path(), "rb") as f:
|
||||
doc.archive_checksum = hashlib.md5(f.read()).hexdigest()
|
||||
os.makedirs(os.path.dirname(archive_path_new(doc)),
|
||||
exist_ok=True)
|
||||
shutil.copy2(parser.get_archive_path(), archive_path_new(doc))
|
||||
else:
|
||||
doc.archive_checksum = None
|
||||
logger.error(
|
||||
f"Parser did not return an archive document for document "
|
||||
f"ID:{doc.id}. Removing archive document."
|
||||
)
|
||||
doc.save()
|
||||
return
|
||||
except ParseError:
|
||||
if try_num + 1 == retry_count:
|
||||
logger.exception(
|
||||
f"Unable to regenerate archive document for ID:{doc.id}. You "
|
||||
f"need to invoke the document_archiver management command "
|
||||
f"manually for that document."
|
||||
)
|
||||
doc.archive_checksum = None
|
||||
doc.save()
|
||||
return
|
||||
else:
|
||||
# This is mostly here for the tika parser in docker
|
||||
# environemnts. The servers for parsing need to come up first,
|
||||
# and the docker setup doesn't ensure that tika is running
|
||||
# before attempting migrations.
|
||||
logger.error("Parse error, will try again in 5 seconds...")
|
||||
sleep(5)
|
||||
finally:
|
||||
parser.cleanup()
|
||||
|
||||
|
||||
def move_old_to_new_locations(apps, schema_editor):
|
||||
Document = apps.get_model("documents", "Document")
|
||||
|
||||
affected_document_ids = set()
|
||||
|
||||
old_archive_path_to_id = {}
|
||||
|
||||
# check for documents that have incorrect archive versions
|
||||
for doc in Document.objects.filter(archive_checksum__isnull=False):
|
||||
old_path = archive_path_old(doc)
|
||||
|
||||
if old_path in old_archive_path_to_id:
|
||||
affected_document_ids.add(doc.id)
|
||||
affected_document_ids.add(old_archive_path_to_id[old_path])
|
||||
else:
|
||||
old_archive_path_to_id[old_path] = doc.id
|
||||
|
||||
# check that archive files of all unaffected documents are in place
|
||||
for doc in Document.objects.filter(archive_checksum__isnull=False):
|
||||
old_path = archive_path_old(doc)
|
||||
if doc.id not in affected_document_ids and not os.path.isfile(old_path):
|
||||
raise ValueError(
|
||||
f"Archived document ID:{doc.id} does not exist at: "
|
||||
f"{old_path}")
|
||||
|
||||
# check that we can regenerate affected archive versions
|
||||
for doc_id in affected_document_ids:
|
||||
from documents.parsers import get_parser_class_for_mime_type
|
||||
|
||||
doc = Document.objects.get(id=doc_id)
|
||||
parser_class = get_parser_class_for_mime_type(doc.mime_type)
|
||||
if not parser_class:
|
||||
raise ValueError(
|
||||
f"Document ID:{doc.id} has an invalid archived document, "
|
||||
f"but no parsers are available. Cannot migrate.")
|
||||
|
||||
for doc in Document.objects.filter(archive_checksum__isnull=False):
|
||||
|
||||
if doc.id in affected_document_ids:
|
||||
old_path = archive_path_old(doc)
|
||||
# remove affected archive versions
|
||||
if os.path.isfile(old_path):
|
||||
logger.debug(
|
||||
f"Removing {old_path}"
|
||||
)
|
||||
os.unlink(old_path)
|
||||
else:
|
||||
# Set archive path for unaffected files
|
||||
doc.archive_filename = archive_name_from_filename(doc.filename)
|
||||
Document.objects.filter(id=doc.id).update(
|
||||
archive_filename=doc.archive_filename
|
||||
)
|
||||
|
||||
# regenerate archive documents
|
||||
for doc_id in affected_document_ids:
|
||||
doc = Document.objects.get(id=doc_id)
|
||||
create_archive_version(doc)
|
||||
|
||||
|
||||
def move_new_to_old_locations(apps, schema_editor):
|
||||
Document = apps.get_model("documents", "Document")
|
||||
|
||||
old_archive_paths = set()
|
||||
|
||||
for doc in Document.objects.filter(archive_checksum__isnull=False):
|
||||
new_archive_path = archive_path_new(doc)
|
||||
old_archive_path = archive_path_old(doc)
|
||||
if old_archive_path in old_archive_paths:
|
||||
raise ValueError(
|
||||
f"Cannot migrate: Archive file name {old_archive_path} of "
|
||||
f"document {doc.filename} would clash with another archive "
|
||||
f"filename.")
|
||||
old_archive_paths.add(old_archive_path)
|
||||
if new_archive_path != old_archive_path and os.path.isfile(old_archive_path):
|
||||
raise ValueError(
|
||||
f"Cannot migrate: Cannot move {new_archive_path} to "
|
||||
f"{old_archive_path}: file already exists."
|
||||
)
|
||||
|
||||
for doc in Document.objects.filter(archive_checksum__isnull=False):
|
||||
new_archive_path = archive_path_new(doc)
|
||||
old_archive_path = archive_path_old(doc)
|
||||
if new_archive_path != old_archive_path:
|
||||
logger.debug(f"Moving {new_archive_path} to {old_archive_path}")
|
||||
shutil.move(new_archive_path, old_archive_path)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('documents', '1011_auto_20210101_2340'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='document',
|
||||
name='archive_filename',
|
||||
field=models.FilePathField(default=None, editable=False, help_text='Current archive filename in storage', max_length=1024, null=True, unique=True, verbose_name='archive filename'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='document',
|
||||
name='filename',
|
||||
field=models.FilePathField(default=None, editable=False, help_text='Current filename in storage', max_length=1024, null=True, unique=True, verbose_name='filename'),
|
||||
),
|
||||
migrations.RunPython(
|
||||
move_old_to_new_locations,
|
||||
move_new_to_old_locations
|
||||
),
|
||||
]
|
||||
70
src/documents/migrations/1013_migrate_tag_colour.py
Normal file
70
src/documents/migrations/1013_migrate_tag_colour.py
Normal file
@@ -0,0 +1,70 @@
|
||||
# Generated by Django 3.1.4 on 2020-12-02 21:43
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
COLOURS_OLD = {
|
||||
1: "#a6cee3",
|
||||
2: "#1f78b4",
|
||||
3: "#b2df8a",
|
||||
4: "#33a02c",
|
||||
5: "#fb9a99",
|
||||
6: "#e31a1c",
|
||||
7: "#fdbf6f",
|
||||
8: "#ff7f00",
|
||||
9: "#cab2d6",
|
||||
10: "#6a3d9a",
|
||||
11: "#b15928",
|
||||
12: "#000000",
|
||||
13: "#cccccc",
|
||||
}
|
||||
|
||||
|
||||
def forward(apps, schema_editor):
|
||||
Tag = apps.get_model('documents', 'Tag')
|
||||
|
||||
for tag in Tag.objects.all():
|
||||
colour_old_id = tag.colour_old
|
||||
rgb = COLOURS_OLD[colour_old_id]
|
||||
tag.color = rgb
|
||||
tag.save()
|
||||
|
||||
|
||||
def reverse(apps, schema_editor):
|
||||
Tag = apps.get_model('documents', 'Tag')
|
||||
|
||||
def _get_colour_id(rdb):
|
||||
for idx, rdbx in COLOURS_OLD.items():
|
||||
if rdbx == rdb:
|
||||
return idx
|
||||
# Return colour 1 if we can't match anything
|
||||
return 1
|
||||
|
||||
for tag in Tag.objects.all():
|
||||
colour_id = _get_colour_id(tag.color)
|
||||
tag.colour_old = colour_id
|
||||
tag.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('documents', '1012_fix_archive_files'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='tag',
|
||||
old_name='colour',
|
||||
new_name='colour_old',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tag',
|
||||
name='color',
|
||||
field=models.CharField(default='#a6cee3', max_length=7, verbose_name='color'),
|
||||
),
|
||||
migrations.RunPython(forward, reverse),
|
||||
migrations.RemoveField(
|
||||
model_name='tag',
|
||||
name='colour_old',
|
||||
)
|
||||
]
|
||||
18
src/documents/migrations/1014_auto_20210228_1614.py
Normal file
18
src/documents/migrations/1014_auto_20210228_1614.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.1.7 on 2021-02-28 15:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('documents', '1013_migrate_tag_colour'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='savedviewfilterrule',
|
||||
name='rule_type',
|
||||
field=models.PositiveIntegerField(choices=[(0, 'title contains'), (1, 'content contains'), (2, 'ASN is'), (3, 'correspondent is'), (4, 'document type is'), (5, 'is in inbox'), (6, 'has tag'), (7, 'has any tag'), (8, 'created before'), (9, 'created after'), (10, 'created year is'), (11, 'created month is'), (12, 'created day is'), (13, 'added before'), (14, 'added after'), (15, 'modified before'), (16, 'modified after'), (17, 'does not have tag'), (18, 'does not have ASN'), (19, 'title or content contains')], verbose_name='rule type'),
|
||||
),
|
||||
]
|
||||
29
src/documents/migrations/1015_remove_null_characters.py
Normal file
29
src/documents/migrations/1015_remove_null_characters.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 3.1.7 on 2021-04-04 18:28
|
||||
import logging
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
logger = logging.getLogger("paperless.migrations")
|
||||
|
||||
|
||||
def remove_null_characters(apps, schema_editor):
|
||||
Document = apps.get_model('documents', 'Document')
|
||||
|
||||
for doc in Document.objects.all():
|
||||
content: str = doc.content
|
||||
if '\0' in content:
|
||||
logger.info(f"Removing null characters from document {doc}...")
|
||||
doc.content = content.replace('\0', ' ')
|
||||
doc.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('documents', '1014_auto_20210228_1614'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(remove_null_characters, migrations.RunPython.noop)
|
||||
]
|
||||
23
src/documents/migrations/1016_auto_20210317_1351.py
Normal file
23
src/documents/migrations/1016_auto_20210317_1351.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.1.7 on 2021-03-17 12:51
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('documents', '1015_remove_null_characters'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='savedview',
|
||||
name='sort_field',
|
||||
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='sort field'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='savedviewfilterrule',
|
||||
name='rule_type',
|
||||
field=models.PositiveIntegerField(choices=[(0, 'title contains'), (1, 'content contains'), (2, 'ASN is'), (3, 'correspondent is'), (4, 'document type is'), (5, 'is in inbox'), (6, 'has tag'), (7, 'has any tag'), (8, 'created before'), (9, 'created after'), (10, 'created year is'), (11, 'created month is'), (12, 'created day is'), (13, 'added before'), (14, 'added after'), (15, 'modified before'), (16, 'modified after'), (17, 'does not have tag'), (18, 'does not have ASN'), (19, 'title or content contains'), (20, 'fulltext query'), (21, 'more like this')], verbose_name='rule type'),
|
||||
),
|
||||
]
|
||||
@@ -1,9 +0,0 @@
|
||||
class Renderable:
|
||||
"""
|
||||
A handy mixin to make it easier/cleaner to print output based on a
|
||||
verbosity value.
|
||||
"""
|
||||
|
||||
def _render(self, text, verbosity):
|
||||
if self.verbosity >= verbosity:
|
||||
print(text)
|
||||
69
src/documents/models.py
Executable file → Normal file
69
src/documents/models.py
Executable file → Normal file
@@ -16,7 +16,6 @@ from django.utils.timezone import is_aware
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from documents.file_handling import archive_name_from_filename
|
||||
from documents.parsers import get_default_file_extension
|
||||
|
||||
|
||||
@@ -66,10 +65,6 @@ class MatchingModel(models.Model):
|
||||
|
||||
class Correspondent(MatchingModel):
|
||||
|
||||
# This regex is probably more restrictive than it needs to be, but it's
|
||||
# better safe than sorry.
|
||||
SAFE_REGEX = re.compile(r"^[\w\- ,.']+$")
|
||||
|
||||
class Meta:
|
||||
ordering = ("name",)
|
||||
verbose_name = _("correspondent")
|
||||
@@ -78,25 +73,11 @@ class Correspondent(MatchingModel):
|
||||
|
||||
class Tag(MatchingModel):
|
||||
|
||||
COLOURS = (
|
||||
(1, "#a6cee3"),
|
||||
(2, "#1f78b4"),
|
||||
(3, "#b2df8a"),
|
||||
(4, "#33a02c"),
|
||||
(5, "#fb9a99"),
|
||||
(6, "#e31a1c"),
|
||||
(7, "#fdbf6f"),
|
||||
(8, "#ff7f00"),
|
||||
(9, "#cab2d6"),
|
||||
(10, "#6a3d9a"),
|
||||
(11, "#b15928"),
|
||||
(12, "#000000"),
|
||||
(13, "#cccccc")
|
||||
)
|
||||
|
||||
colour = models.PositiveIntegerField(
|
||||
color = models.CharField(
|
||||
_("color"),
|
||||
choices=COLOURS, default=1)
|
||||
max_length=7,
|
||||
default="#a6cee3"
|
||||
)
|
||||
|
||||
is_inbox_tag = models.BooleanField(
|
||||
_("is inbox tag"),
|
||||
@@ -208,10 +189,21 @@ class Document(models.Model):
|
||||
max_length=1024,
|
||||
editable=False,
|
||||
default=None,
|
||||
unique=True,
|
||||
null=True,
|
||||
help_text=_("Current filename in storage")
|
||||
)
|
||||
|
||||
archive_filename = models.FilePathField(
|
||||
_("archive filename"),
|
||||
max_length=1024,
|
||||
editable=False,
|
||||
default=None,
|
||||
unique=True,
|
||||
null=True,
|
||||
help_text=_("Current archive filename in storage")
|
||||
)
|
||||
|
||||
archive_serial_number = models.IntegerField(
|
||||
_("archive serial number"),
|
||||
blank=True,
|
||||
@@ -256,16 +248,18 @@ class Document(models.Model):
|
||||
return open(self.source_path, "rb")
|
||||
|
||||
@property
|
||||
def archive_path(self):
|
||||
if self.filename:
|
||||
fname = archive_name_from_filename(self.filename)
|
||||
else:
|
||||
fname = "{:07}.pdf".format(self.pk)
|
||||
def has_archive_version(self):
|
||||
return self.archive_filename is not None
|
||||
|
||||
return os.path.join(
|
||||
settings.ARCHIVE_DIR,
|
||||
fname
|
||||
)
|
||||
@property
|
||||
def archive_path(self):
|
||||
if self.has_archive_version:
|
||||
return os.path.join(
|
||||
settings.ARCHIVE_DIR,
|
||||
str(self.archive_filename)
|
||||
)
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def archive_file(self):
|
||||
@@ -361,7 +355,10 @@ class SavedView(models.Model):
|
||||
|
||||
sort_field = models.CharField(
|
||||
_("sort field"),
|
||||
max_length=128)
|
||||
max_length=128,
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
sort_reverse = models.BooleanField(
|
||||
_("sort reverse"),
|
||||
default=False)
|
||||
@@ -387,7 +384,11 @@ class SavedViewFilterRule(models.Model):
|
||||
(15, _("modified before")),
|
||||
(16, _("modified after")),
|
||||
(17, _("does not have tag")),
|
||||
(19, _("has tags in")),
|
||||
(18, _("does not have ASN")),
|
||||
(19, _("title or content contains")),
|
||||
(20, _("fulltext query")),
|
||||
(21, _("more like this")),
|
||||
(22, _("has tags in"))
|
||||
]
|
||||
|
||||
saved_view = models.ForeignKey(
|
||||
|
||||
@@ -6,7 +6,6 @@ import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
import dateparser
|
||||
import magic
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
@@ -36,7 +35,7 @@ DATE_REGEX = re.compile(
|
||||
)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger = logging.getLogger("paperless.parsing")
|
||||
|
||||
|
||||
def is_mime_type_supported(mime_type):
|
||||
@@ -144,6 +143,46 @@ def run_convert(input_file,
|
||||
raise ParseError("Convert failed at {}".format(args))
|
||||
|
||||
|
||||
def get_default_thumbnail():
|
||||
return os.path.join(os.path.dirname(__file__), "resources", "document.png")
|
||||
|
||||
|
||||
def make_thumbnail_from_pdf_gs_fallback(in_path, temp_dir, logging_group=None):
|
||||
out_path = os.path.join(temp_dir, "convert_gs.png")
|
||||
|
||||
# if convert fails, fall back to extracting
|
||||
# the first PDF page as a PNG using Ghostscript
|
||||
logger.warning(
|
||||
"Thumbnail generation with ImageMagick failed, falling back "
|
||||
"to ghostscript. Check your /etc/ImageMagick-x/policy.xml!",
|
||||
extra={'group': logging_group}
|
||||
)
|
||||
gs_out_path = os.path.join(temp_dir, "gs_out.png")
|
||||
cmd = [settings.GS_BINARY,
|
||||
"-q",
|
||||
"-sDEVICE=pngalpha",
|
||||
"-o", gs_out_path,
|
||||
in_path]
|
||||
try:
|
||||
if not subprocess.Popen(cmd).wait() == 0:
|
||||
raise ParseError("Thumbnail (gs) failed at {}".format(cmd))
|
||||
# then run convert on the output from gs
|
||||
run_convert(density=300,
|
||||
scale="500x5000>",
|
||||
alpha="remove",
|
||||
strip=True,
|
||||
trim=False,
|
||||
auto_orient=True,
|
||||
input_file=gs_out_path,
|
||||
output_file=out_path,
|
||||
logging_group=logging_group)
|
||||
|
||||
return out_path
|
||||
|
||||
except ParseError:
|
||||
return get_default_thumbnail()
|
||||
|
||||
|
||||
def make_thumbnail_from_pdf(in_path, temp_dir, logging_group=None):
|
||||
"""
|
||||
The thumbnail of a PDF is just a 500px wide image of the first page.
|
||||
@@ -162,31 +201,8 @@ def make_thumbnail_from_pdf(in_path, temp_dir, logging_group=None):
|
||||
output_file=out_path,
|
||||
logging_group=logging_group)
|
||||
except ParseError:
|
||||
# if convert fails, fall back to extracting
|
||||
# the first PDF page as a PNG using Ghostscript
|
||||
logger.warning(
|
||||
"Thumbnail generation with ImageMagick failed, falling back "
|
||||
"to ghostscript. Check your /etc/ImageMagick-x/policy.xml!",
|
||||
extra={'group': logging_group}
|
||||
)
|
||||
gs_out_path = os.path.join(temp_dir, "gs_out.png")
|
||||
cmd = [settings.GS_BINARY,
|
||||
"-q",
|
||||
"-sDEVICE=pngalpha",
|
||||
"-o", gs_out_path,
|
||||
in_path]
|
||||
if not subprocess.Popen(cmd).wait() == 0:
|
||||
raise ParseError("Thumbnail (gs) failed at {}".format(cmd))
|
||||
# then run convert on the output from gs
|
||||
run_convert(density=300,
|
||||
scale="500x5000>",
|
||||
alpha="remove",
|
||||
strip=True,
|
||||
trim=False,
|
||||
auto_orient=True,
|
||||
input_file=gs_out_path,
|
||||
output_file=out_path,
|
||||
logging_group=logging_group)
|
||||
out_path = make_thumbnail_from_pdf_gs_fallback(
|
||||
in_path, temp_dir, logging_group)
|
||||
|
||||
return out_path
|
||||
|
||||
@@ -200,6 +216,8 @@ def parse_date(filename, text):
|
||||
"""
|
||||
Call dateparser.parse with a particular date ordering
|
||||
"""
|
||||
import dateparser
|
||||
|
||||
return dateparser.parse(
|
||||
ds,
|
||||
settings={
|
||||
@@ -261,7 +279,9 @@ class DocumentParser(LoggingMixin):
|
||||
`paperless_tesseract.parsers` for inspiration.
|
||||
"""
|
||||
|
||||
def __init__(self, logging_group):
|
||||
logging_name = "paperless.parsing"
|
||||
|
||||
def __init__(self, logging_group, progress_callback=None):
|
||||
super().__init__()
|
||||
self.logging_group = logging_group
|
||||
os.makedirs(settings.SCRATCH_DIR, exist_ok=True)
|
||||
@@ -271,6 +291,11 @@ class DocumentParser(LoggingMixin):
|
||||
self.archive_path = None
|
||||
self.text = None
|
||||
self.date = None
|
||||
self.progress_callback = progress_callback
|
||||
|
||||
def progress(self, current_progress, max_progress):
|
||||
if self.progress_callback:
|
||||
self.progress_callback(current_progress, max_progress)
|
||||
|
||||
def extract_metadata(self, document_path, mime_type):
|
||||
return []
|
||||
@@ -281,14 +306,17 @@ class DocumentParser(LoggingMixin):
|
||||
def get_archive_path(self):
|
||||
return self.archive_path
|
||||
|
||||
def get_thumbnail(self, document_path, mime_type):
|
||||
def get_thumbnail(self, document_path, mime_type, file_name=None):
|
||||
"""
|
||||
Returns the path to a file we can use as a thumbnail for this document.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_optimised_thumbnail(self, document_path, mime_type):
|
||||
thumbnail = self.get_thumbnail(document_path, mime_type)
|
||||
def get_optimised_thumbnail(self,
|
||||
document_path,
|
||||
mime_type,
|
||||
file_name=None):
|
||||
thumbnail = self.get_thumbnail(document_path, mime_type, file_name)
|
||||
if settings.OPTIMIZE_THUMBNAILS:
|
||||
out_path = os.path.join(self.tempdir, "thumb_optipng.png")
|
||||
|
||||
@@ -311,5 +339,5 @@ class DocumentParser(LoggingMixin):
|
||||
return self.date
|
||||
|
||||
def cleanup(self):
|
||||
self.log("debug", "Deleting directory {}".format(self.tempdir))
|
||||
self.log("debug", f"Deleting directory {self.tempdir}")
|
||||
shutil.rmtree(self.tempdir)
|
||||
|
||||
BIN
src/documents/resources/document.png
Normal file
BIN
src/documents/resources/document.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -1,45 +1,55 @@
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from tqdm import tqdm
|
||||
|
||||
from documents.models import Document
|
||||
|
||||
|
||||
class SanityMessage:
|
||||
message = None
|
||||
class SanityCheckMessages:
|
||||
|
||||
def __init__(self):
|
||||
self._messages = []
|
||||
|
||||
def error(self, message):
|
||||
self._messages.append({"level": logging.ERROR, "message": message})
|
||||
|
||||
def warning(self, message):
|
||||
self._messages.append({"level": logging.WARNING, "message": message})
|
||||
|
||||
def info(self, message):
|
||||
self._messages.append({"level": logging.INFO, "message": message})
|
||||
|
||||
def log_messages(self):
|
||||
logger = logging.getLogger("paperless.sanity_checker")
|
||||
|
||||
if len(self._messages) == 0:
|
||||
logger.info("Sanity checker detected no issues.")
|
||||
else:
|
||||
for msg in self._messages:
|
||||
logger.log(msg['level'], msg['message'])
|
||||
|
||||
def __len__(self):
|
||||
return len(self._messages)
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self._messages[item]
|
||||
|
||||
def has_error(self):
|
||||
return any([msg['level'] == logging.ERROR for msg in self._messages])
|
||||
|
||||
def has_warning(self):
|
||||
return any([msg['level'] == logging.WARNING for msg in self._messages])
|
||||
|
||||
|
||||
class SanityWarning(SanityMessage):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return f"Warning: {self.message}"
|
||||
class SanityCheckFailedException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class SanityError(SanityMessage):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return f"ERROR: {self.message}"
|
||||
|
||||
|
||||
class SanityFailedError(Exception):
|
||||
|
||||
def __init__(self, messages):
|
||||
self.messages = messages
|
||||
|
||||
def __str__(self):
|
||||
message_string = "\n".join([str(m) for m in self.messages])
|
||||
return (
|
||||
f"The following issuse were found by the sanity checker:\n"
|
||||
f"{message_string}\n\n===============\n\n")
|
||||
|
||||
|
||||
def check_sanity():
|
||||
messages = []
|
||||
def check_sanity(progress=False):
|
||||
messages = SanityCheckMessages()
|
||||
|
||||
present_files = []
|
||||
for root, subdirs, files in os.walk(settings.MEDIA_ROOT):
|
||||
@@ -50,72 +60,81 @@ def check_sanity():
|
||||
if lockfile in present_files:
|
||||
present_files.remove(lockfile)
|
||||
|
||||
for doc in Document.objects.all():
|
||||
for doc in tqdm(Document.objects.all(), disable=not progress):
|
||||
# Check sanity of the thumbnail
|
||||
if not os.path.isfile(doc.thumbnail_path):
|
||||
messages.append(SanityError(
|
||||
f"Thumbnail of document {doc.pk} does not exist."))
|
||||
messages.error(f"Thumbnail of document {doc.pk} does not exist.")
|
||||
else:
|
||||
present_files.remove(os.path.normpath(doc.thumbnail_path))
|
||||
if os.path.normpath(doc.thumbnail_path) in present_files:
|
||||
present_files.remove(os.path.normpath(doc.thumbnail_path))
|
||||
try:
|
||||
with doc.thumbnail_file as f:
|
||||
f.read()
|
||||
except OSError as e:
|
||||
messages.append(SanityError(
|
||||
messages.error(
|
||||
f"Cannot read thumbnail file of document {doc.pk}: {e}"
|
||||
))
|
||||
)
|
||||
|
||||
# Check sanity of the original file
|
||||
# TODO: extract method
|
||||
if not os.path.isfile(doc.source_path):
|
||||
messages.append(SanityError(
|
||||
f"Original of document {doc.pk} does not exist."))
|
||||
messages.error(f"Original of document {doc.pk} does not exist.")
|
||||
else:
|
||||
present_files.remove(os.path.normpath(doc.source_path))
|
||||
if os.path.normpath(doc.source_path) in present_files:
|
||||
present_files.remove(os.path.normpath(doc.source_path))
|
||||
try:
|
||||
with doc.source_file as f:
|
||||
checksum = hashlib.md5(f.read()).hexdigest()
|
||||
except OSError as e:
|
||||
messages.append(SanityError(
|
||||
f"Cannot read original file of document {doc.pk}: {e}"))
|
||||
messages.error(
|
||||
f"Cannot read original file of document {doc.pk}: {e}")
|
||||
else:
|
||||
if not checksum == doc.checksum:
|
||||
messages.append(SanityError(
|
||||
messages.error(
|
||||
f"Checksum mismatch of document {doc.pk}. "
|
||||
f"Stored: {doc.checksum}, actual: {checksum}."
|
||||
))
|
||||
)
|
||||
|
||||
# Check sanity of the archive file.
|
||||
if doc.archive_checksum:
|
||||
if doc.archive_checksum and not doc.archive_filename:
|
||||
messages.error(
|
||||
f"Document {doc.pk} has an archive file checksum, but no "
|
||||
f"archive filename."
|
||||
)
|
||||
elif not doc.archive_checksum and doc.archive_filename:
|
||||
messages.error(
|
||||
f"Document {doc.pk} has an archive file, but its checksum is "
|
||||
f"missing."
|
||||
)
|
||||
elif doc.has_archive_version:
|
||||
if not os.path.isfile(doc.archive_path):
|
||||
messages.append(SanityError(
|
||||
messages.error(
|
||||
f"Archived version of document {doc.pk} does not exist."
|
||||
))
|
||||
)
|
||||
else:
|
||||
present_files.remove(os.path.normpath(doc.archive_path))
|
||||
if os.path.normpath(doc.archive_path) in present_files:
|
||||
present_files.remove(os.path.normpath(doc.archive_path))
|
||||
try:
|
||||
with doc.archive_file as f:
|
||||
checksum = hashlib.md5(f.read()).hexdigest()
|
||||
except OSError as e:
|
||||
messages.append(SanityError(
|
||||
messages.error(
|
||||
f"Cannot read archive file of document {doc.pk}: {e}"
|
||||
))
|
||||
)
|
||||
else:
|
||||
if not checksum == doc.archive_checksum:
|
||||
messages.append(SanityError(
|
||||
f"Checksum mismatch of archive {doc.pk}. "
|
||||
f"Stored: {doc.checksum}, actual: {checksum}."
|
||||
))
|
||||
messages.error(
|
||||
f"Checksum mismatch of archived document "
|
||||
f"{doc.pk}. "
|
||||
f"Stored: {doc.archive_checksum}, "
|
||||
f"actual: {checksum}."
|
||||
)
|
||||
|
||||
# other document checks
|
||||
if not doc.content:
|
||||
messages.append(SanityWarning(
|
||||
f"Document {doc.pk} has no content."
|
||||
))
|
||||
messages.info(f"Document {doc.pk} has no content.")
|
||||
|
||||
for extra_file in present_files:
|
||||
messages.append(SanityWarning(
|
||||
f"Orphaned file in media dir: {extra_file}"
|
||||
))
|
||||
messages.warning(f"Orphaned file in media dir: {extra_file}")
|
||||
|
||||
return messages
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import re
|
||||
|
||||
import magic
|
||||
import math
|
||||
from django.utils.text import slugify
|
||||
from rest_framework import serializers
|
||||
from rest_framework.fields import SerializerMethodField
|
||||
|
||||
from . import bulk_edit
|
||||
from .models import Correspondent, Tag, Document, Log, DocumentType, \
|
||||
SavedView, SavedViewFilterRule
|
||||
from .models import Correspondent, Tag, Document, DocumentType, \
|
||||
SavedView, SavedViewFilterRule, MatchingModel
|
||||
from .parsers import is_mime_type_supported
|
||||
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
|
||||
# https://www.django-rest-framework.org/api-guide/serializers/#example
|
||||
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
|
||||
@@ -31,16 +36,30 @@ class DynamicFieldsModelSerializer(serializers.ModelSerializer):
|
||||
self.fields.pop(field_name)
|
||||
|
||||
|
||||
class CorrespondentSerializer(serializers.ModelSerializer):
|
||||
class MatchingModelSerializer(serializers.ModelSerializer):
|
||||
|
||||
document_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
last_correspondence = serializers.DateTimeField(read_only=True)
|
||||
|
||||
def get_slug(self, obj):
|
||||
return slugify(obj.name)
|
||||
slug = SerializerMethodField()
|
||||
|
||||
def validate_match(self, match):
|
||||
if 'matching_algorithm' in self.initial_data and self.initial_data['matching_algorithm'] == MatchingModel.MATCH_REGEX: # NOQA: E501
|
||||
try:
|
||||
re.compile(match)
|
||||
except Exception as e:
|
||||
raise serializers.ValidationError(
|
||||
_("Invalid regular expression: %(error)s") %
|
||||
{'error': str(e)}
|
||||
)
|
||||
return match
|
||||
|
||||
|
||||
class CorrespondentSerializer(MatchingModelSerializer):
|
||||
|
||||
last_correspondence = serializers.DateTimeField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Correspondent
|
||||
fields = (
|
||||
@@ -55,13 +74,7 @@ class CorrespondentSerializer(serializers.ModelSerializer):
|
||||
)
|
||||
|
||||
|
||||
class DocumentTypeSerializer(serializers.ModelSerializer):
|
||||
|
||||
document_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
def get_slug(self, obj):
|
||||
return slugify(obj.name)
|
||||
slug = SerializerMethodField()
|
||||
class DocumentTypeSerializer(MatchingModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = DocumentType
|
||||
@@ -76,13 +89,40 @@ class DocumentTypeSerializer(serializers.ModelSerializer):
|
||||
)
|
||||
|
||||
|
||||
class TagSerializer(serializers.ModelSerializer):
|
||||
class ColorField(serializers.Field):
|
||||
|
||||
document_count = serializers.IntegerField(read_only=True)
|
||||
COLOURS = (
|
||||
(1, "#a6cee3"),
|
||||
(2, "#1f78b4"),
|
||||
(3, "#b2df8a"),
|
||||
(4, "#33a02c"),
|
||||
(5, "#fb9a99"),
|
||||
(6, "#e31a1c"),
|
||||
(7, "#fdbf6f"),
|
||||
(8, "#ff7f00"),
|
||||
(9, "#cab2d6"),
|
||||
(10, "#6a3d9a"),
|
||||
(11, "#b15928"),
|
||||
(12, "#000000"),
|
||||
(13, "#cccccc")
|
||||
)
|
||||
|
||||
def get_slug(self, obj):
|
||||
return slugify(obj.name)
|
||||
slug = SerializerMethodField()
|
||||
def to_internal_value(self, data):
|
||||
for id, color in self.COLOURS:
|
||||
if id == data:
|
||||
return color
|
||||
raise serializers.ValidationError()
|
||||
|
||||
def to_representation(self, value):
|
||||
for id, color in self.COLOURS:
|
||||
if color == value:
|
||||
return id
|
||||
return 1
|
||||
|
||||
|
||||
class TagSerializerVersion1(MatchingModelSerializer):
|
||||
|
||||
colour = ColorField(source='color', default="#a6cee3")
|
||||
|
||||
class Meta:
|
||||
model = Tag
|
||||
@@ -99,6 +139,45 @@ class TagSerializer(serializers.ModelSerializer):
|
||||
)
|
||||
|
||||
|
||||
class TagSerializer(MatchingModelSerializer):
|
||||
|
||||
def get_text_color(self, obj):
|
||||
try:
|
||||
h = obj.color.lstrip('#')
|
||||
rgb = tuple(int(h[i:i + 2], 16)/256 for i in (0, 2, 4))
|
||||
luminance = math.sqrt(
|
||||
0.299 * math.pow(rgb[0], 2) +
|
||||
0.587 * math.pow(rgb[1], 2) +
|
||||
0.114 * math.pow(rgb[2], 2)
|
||||
)
|
||||
return "#ffffff" if luminance < 0.53 else "#000000"
|
||||
except ValueError:
|
||||
return "#000000"
|
||||
|
||||
text_color = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Tag
|
||||
fields = (
|
||||
"id",
|
||||
"slug",
|
||||
"name",
|
||||
"color",
|
||||
"text_color",
|
||||
"match",
|
||||
"matching_algorithm",
|
||||
"is_insensitive",
|
||||
"is_inbox_tag",
|
||||
"document_count"
|
||||
)
|
||||
|
||||
def validate_color(self, color):
|
||||
regex = r"#[0-9a-fA-F]{6}"
|
||||
if not re.match(regex, color):
|
||||
raise serializers.ValidationError(_("Invalid color."))
|
||||
return color
|
||||
|
||||
|
||||
class CorrespondentField(serializers.PrimaryKeyRelatedField):
|
||||
def get_queryset(self):
|
||||
return Correspondent.objects.all()
|
||||
@@ -127,7 +206,7 @@ class DocumentSerializer(DynamicFieldsModelSerializer):
|
||||
return obj.get_public_filename()
|
||||
|
||||
def get_archived_file_name(self, obj):
|
||||
if obj.archive_checksum:
|
||||
if obj.has_archive_version:
|
||||
return obj.get_public_filename(archive=True)
|
||||
else:
|
||||
return None
|
||||
@@ -151,19 +230,6 @@ class DocumentSerializer(DynamicFieldsModelSerializer):
|
||||
)
|
||||
|
||||
|
||||
class LogSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Log
|
||||
fields = (
|
||||
"id",
|
||||
"created",
|
||||
"message",
|
||||
"group",
|
||||
"level"
|
||||
)
|
||||
|
||||
|
||||
class SavedViewFilterRuleSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
@@ -203,14 +269,34 @@ class SavedViewSerializer(serializers.ModelSerializer):
|
||||
return saved_view
|
||||
|
||||
|
||||
class BulkEditSerializer(serializers.Serializer):
|
||||
class DocumentListSerializer(serializers.Serializer):
|
||||
|
||||
documents = serializers.ListField(
|
||||
child=serializers.IntegerField(),
|
||||
required=True,
|
||||
label="Documents",
|
||||
write_only=True
|
||||
write_only=True,
|
||||
child=serializers.IntegerField()
|
||||
)
|
||||
|
||||
def _validate_document_id_list(self, documents, name="documents"):
|
||||
if not type(documents) == list:
|
||||
raise serializers.ValidationError(f"{name} must be a list")
|
||||
if not all([type(i) == int for i in documents]):
|
||||
raise serializers.ValidationError(
|
||||
f"{name} must be a list of integers")
|
||||
count = Document.objects.filter(id__in=documents).count()
|
||||
if not count == len(documents):
|
||||
raise serializers.ValidationError(
|
||||
f"Some documents in {name} don't exist or were "
|
||||
f"specified twice.")
|
||||
|
||||
def validate_documents(self, documents):
|
||||
self._validate_document_id_list(documents)
|
||||
return documents
|
||||
|
||||
|
||||
class BulkEditSerializer(DocumentListSerializer):
|
||||
|
||||
method = serializers.ChoiceField(
|
||||
choices=[
|
||||
"set_correspondent",
|
||||
@@ -226,18 +312,6 @@ class BulkEditSerializer(serializers.Serializer):
|
||||
|
||||
parameters = serializers.DictField(allow_empty=True)
|
||||
|
||||
def _validate_document_id_list(self, documents, name="documents"):
|
||||
if not type(documents) == list:
|
||||
raise serializers.ValidationError(f"{name} must be a list")
|
||||
if not all([type(i) == int for i in documents]):
|
||||
raise serializers.ValidationError(
|
||||
f"{name} must be a list of integers")
|
||||
count = Document.objects.filter(id__in=documents).count()
|
||||
if not count == len(documents):
|
||||
raise serializers.ValidationError(
|
||||
f"Some documents in {name} don't exist or were "
|
||||
f"specified twice.")
|
||||
|
||||
def _validate_tag_id_list(self, tags, name="tags"):
|
||||
if not type(tags) == list:
|
||||
raise serializers.ValidationError(f"{name} must be a list")
|
||||
@@ -249,10 +323,6 @@ class BulkEditSerializer(serializers.Serializer):
|
||||
raise serializers.ValidationError(
|
||||
f"Some tags in {name} don't exist or were specified twice.")
|
||||
|
||||
def validate_documents(self, documents):
|
||||
self._validate_document_id_list(documents)
|
||||
return documents
|
||||
|
||||
def validate_method(self, method):
|
||||
if method == "set_correspondent":
|
||||
return bulk_edit.set_correspondent
|
||||
@@ -378,7 +448,9 @@ class PostDocumentSerializer(serializers.Serializer):
|
||||
|
||||
if not is_mime_type_supported(mime_type):
|
||||
raise serializers.ValidationError(
|
||||
"This file type is not supported.")
|
||||
_("File type %(type)s not supported") %
|
||||
{'type': mime_type}
|
||||
)
|
||||
|
||||
return document.name, document_data
|
||||
|
||||
@@ -401,9 +473,24 @@ class PostDocumentSerializer(serializers.Serializer):
|
||||
return None
|
||||
|
||||
|
||||
class SelectionDataSerializer(serializers.Serializer):
|
||||
class BulkDownloadSerializer(DocumentListSerializer):
|
||||
|
||||
documents = serializers.ListField(
|
||||
required=True,
|
||||
child=serializers.IntegerField()
|
||||
content = serializers.ChoiceField(
|
||||
choices=["archive", "originals", "both"],
|
||||
default="archive"
|
||||
)
|
||||
|
||||
compression = serializers.ChoiceField(
|
||||
choices=["none", "deflated", "bzip2", "lzma"],
|
||||
default="none"
|
||||
)
|
||||
|
||||
def validate_compression(self, compression):
|
||||
import zipfile
|
||||
|
||||
return {
|
||||
"none": zipfile.ZIP_STORED,
|
||||
"deflated": zipfile.ZIP_DEFLATED,
|
||||
"bzip2": zipfile.ZIP_BZIP2,
|
||||
"lzma": zipfile.ZIP_LZMA
|
||||
}[compression]
|
||||
|
||||
284
src/documents/signals/handlers.py
Executable file → Normal file
284
src/documents/signals/handlers.py
Executable file → Normal file
@@ -1,7 +1,7 @@
|
||||
import logging
|
||||
import os
|
||||
from subprocess import Popen
|
||||
|
||||
from django.utils import termcolors
|
||||
from django.conf import settings
|
||||
from django.contrib.admin.models import ADDITION, LogEntry
|
||||
from django.contrib.auth.models import User
|
||||
@@ -9,18 +9,17 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models, DatabaseError
|
||||
from django.db.models import Q
|
||||
from django.dispatch import receiver
|
||||
from django.utils import timezone
|
||||
from django.utils import termcolors, timezone
|
||||
from filelock import FileLock
|
||||
|
||||
from .. import index, matching
|
||||
from .. import matching
|
||||
from ..file_handling import delete_empty_directories, \
|
||||
create_source_path_directory, archive_name_from_filename, \
|
||||
create_source_path_directory, \
|
||||
generate_unique_filename
|
||||
from ..models import Document, Tag
|
||||
from ..models import Document, Tag, MatchingModel
|
||||
|
||||
|
||||
def logger(message, group):
|
||||
logging.getLogger(__name__).debug(message, extra={"group": group})
|
||||
logger = logging.getLogger("paperless.handlers")
|
||||
|
||||
|
||||
def add_inbox_tags(sender, document=None, logging_group=None, **kwargs):
|
||||
@@ -34,6 +33,9 @@ def set_correspondent(sender,
|
||||
classifier=None,
|
||||
replace=False,
|
||||
use_first=True,
|
||||
suggest=False,
|
||||
base_url=None,
|
||||
color=False,
|
||||
**kwargs):
|
||||
if document.correspondent and not replace:
|
||||
return
|
||||
@@ -48,27 +50,45 @@ def set_correspondent(sender,
|
||||
selected = None
|
||||
if potential_count > 1:
|
||||
if use_first:
|
||||
logger(
|
||||
logger.debug(
|
||||
f"Detected {potential_count} potential correspondents, "
|
||||
f"so we've opted for {selected}",
|
||||
logging_group
|
||||
extra={'group': logging_group}
|
||||
)
|
||||
else:
|
||||
logger(
|
||||
logger.debug(
|
||||
f"Detected {potential_count} potential correspondents, "
|
||||
f"not assigning any correspondent",
|
||||
logging_group
|
||||
extra={'group': logging_group}
|
||||
)
|
||||
return
|
||||
|
||||
if selected or replace:
|
||||
logger(
|
||||
f"Assigning correspondent {selected} to {document}",
|
||||
logging_group
|
||||
)
|
||||
if suggest:
|
||||
if base_url:
|
||||
print(
|
||||
termcolors.colorize(str(document), fg='green')
|
||||
if color
|
||||
else str(document)
|
||||
)
|
||||
print(f"{base_url}/documents/{document.pk}")
|
||||
else:
|
||||
print(
|
||||
(
|
||||
termcolors.colorize(str(document), fg='green')
|
||||
if color
|
||||
else str(document)
|
||||
) + f" [{document.pk}]"
|
||||
)
|
||||
print(f"Suggest correspondent {selected}")
|
||||
else:
|
||||
logger.info(
|
||||
f"Assigning correspondent {selected} to {document}",
|
||||
extra={'group': logging_group}
|
||||
)
|
||||
|
||||
document.correspondent = selected
|
||||
document.save(update_fields=("correspondent",))
|
||||
document.correspondent = selected
|
||||
document.save(update_fields=("correspondent",))
|
||||
|
||||
|
||||
def set_document_type(sender,
|
||||
@@ -77,6 +97,9 @@ def set_document_type(sender,
|
||||
classifier=None,
|
||||
replace=False,
|
||||
use_first=True,
|
||||
suggest=False,
|
||||
base_url=None,
|
||||
color=False,
|
||||
**kwargs):
|
||||
if document.document_type and not replace:
|
||||
return
|
||||
@@ -92,27 +115,45 @@ def set_document_type(sender,
|
||||
|
||||
if potential_count > 1:
|
||||
if use_first:
|
||||
logger(
|
||||
logger.info(
|
||||
f"Detected {potential_count} potential document types, "
|
||||
f"so we've opted for {selected}",
|
||||
logging_group
|
||||
extra={'group': logging_group}
|
||||
)
|
||||
else:
|
||||
logger(
|
||||
logger.info(
|
||||
f"Detected {potential_count} potential document types, "
|
||||
f"not assigning any document type",
|
||||
logging_group
|
||||
extra={'group': logging_group}
|
||||
)
|
||||
return
|
||||
|
||||
if selected or replace:
|
||||
logger(
|
||||
f"Assigning document type {selected} to {document}",
|
||||
logging_group
|
||||
)
|
||||
if suggest:
|
||||
if base_url:
|
||||
print(
|
||||
termcolors.colorize(str(document), fg='green')
|
||||
if color
|
||||
else str(document)
|
||||
)
|
||||
print(f"{base_url}/documents/{document.pk}")
|
||||
else:
|
||||
print(
|
||||
(
|
||||
termcolors.colorize(str(document), fg='green')
|
||||
if color
|
||||
else str(document)
|
||||
) + f" [{document.pk}]"
|
||||
)
|
||||
print(f"Sugest document type {selected}")
|
||||
else:
|
||||
logger.info(
|
||||
f"Assigning document type {selected} to {document}",
|
||||
extra={'group': logging_group}
|
||||
)
|
||||
|
||||
document.document_type = selected
|
||||
document.save(update_fields=("document_type",))
|
||||
document.document_type = selected
|
||||
document.save(update_fields=("document_type",))
|
||||
|
||||
|
||||
def set_tags(sender,
|
||||
@@ -120,6 +161,9 @@ def set_tags(sender,
|
||||
logging_group=None,
|
||||
classifier=None,
|
||||
replace=False,
|
||||
suggest=False,
|
||||
base_url=None,
|
||||
color=False,
|
||||
**kwargs):
|
||||
|
||||
if replace:
|
||||
@@ -134,33 +178,65 @@ def set_tags(sender,
|
||||
|
||||
relevant_tags = set(matched_tags) - current_tags
|
||||
|
||||
if not relevant_tags:
|
||||
return
|
||||
if suggest:
|
||||
extra_tags = current_tags - set(matched_tags)
|
||||
extra_tags = [
|
||||
t for t in extra_tags
|
||||
if t.matching_algorithm == MatchingModel.MATCH_AUTO
|
||||
]
|
||||
if not relevant_tags and not extra_tags:
|
||||
return
|
||||
if base_url:
|
||||
print(
|
||||
termcolors.colorize(str(document), fg='green')
|
||||
if color
|
||||
else str(document)
|
||||
)
|
||||
print(f"{base_url}/documents/{document.pk}")
|
||||
else:
|
||||
print(
|
||||
(
|
||||
termcolors.colorize(str(document), fg='green')
|
||||
if color
|
||||
else str(document)
|
||||
) + f" [{document.pk}]"
|
||||
)
|
||||
if relevant_tags:
|
||||
print(
|
||||
"Suggest tags: " + ", ".join([t.name for t in relevant_tags])
|
||||
)
|
||||
if extra_tags:
|
||||
print("Extra tags: " + ", ".join([t.name for t in extra_tags]))
|
||||
else:
|
||||
if not relevant_tags:
|
||||
return
|
||||
|
||||
message = 'Tagging "{}" with "{}"'
|
||||
logger(
|
||||
message.format(document, ", ".join([t.name for t in relevant_tags])),
|
||||
logging_group
|
||||
)
|
||||
message = 'Tagging "{}" with "{}"'
|
||||
logger.info(
|
||||
message.format(
|
||||
document, ", ".join([t.name for t in relevant_tags])
|
||||
),
|
||||
extra={'group': logging_group}
|
||||
)
|
||||
|
||||
document.tags.add(*relevant_tags)
|
||||
document.tags.add(*relevant_tags)
|
||||
|
||||
|
||||
@receiver(models.signals.post_delete, sender=Document)
|
||||
def cleanup_document_deletion(sender, instance, using, **kwargs):
|
||||
with FileLock(settings.MEDIA_LOCK):
|
||||
for f in (instance.source_path,
|
||||
instance.archive_path,
|
||||
instance.thumbnail_path):
|
||||
if os.path.isfile(f):
|
||||
for filename in (instance.source_path,
|
||||
instance.archive_path,
|
||||
instance.thumbnail_path):
|
||||
if filename and os.path.isfile(filename):
|
||||
try:
|
||||
os.unlink(f)
|
||||
logging.getLogger(__name__).debug(
|
||||
f"Deleted file {f}.")
|
||||
os.unlink(filename)
|
||||
logger.debug(
|
||||
f"Deleted file {filename}.")
|
||||
except OSError as e:
|
||||
logging.getLogger(__name__).warning(
|
||||
logger.warning(
|
||||
f"While deleting document {str(instance)}, the file "
|
||||
f"{f} could not be deleted: {e}"
|
||||
f"{filename} could not be deleted: {e}"
|
||||
)
|
||||
|
||||
delete_empty_directories(
|
||||
@@ -168,27 +244,30 @@ def cleanup_document_deletion(sender, instance, using, **kwargs):
|
||||
root=settings.ORIGINALS_DIR
|
||||
)
|
||||
|
||||
delete_empty_directories(
|
||||
os.path.dirname(instance.archive_path),
|
||||
root=settings.ARCHIVE_DIR
|
||||
)
|
||||
if instance.has_archive_version:
|
||||
delete_empty_directories(
|
||||
os.path.dirname(instance.archive_path),
|
||||
root=settings.ARCHIVE_DIR
|
||||
)
|
||||
|
||||
|
||||
class CannotMoveFilesException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def validate_move(instance, old_path, new_path):
|
||||
if not os.path.isfile(old_path):
|
||||
# Can't do anything if the old file does not exist anymore.
|
||||
logging.getLogger(__name__).fatal(
|
||||
logger.fatal(
|
||||
f"Document {str(instance)}: File {old_path} has gone.")
|
||||
return False
|
||||
raise CannotMoveFilesException()
|
||||
|
||||
if os.path.isfile(new_path):
|
||||
# Can't do anything if the new file already exists. Skip updating file.
|
||||
logging.getLogger(__name__).warning(
|
||||
logger.warning(
|
||||
f"Document {str(instance)}: Cannot rename file "
|
||||
f"since target path {new_path} already exists.")
|
||||
return False
|
||||
|
||||
return True
|
||||
raise CannotMoveFilesException()
|
||||
|
||||
|
||||
@receiver(models.signals.m2m_changed, sender=Document.tags.through)
|
||||
@@ -207,56 +286,61 @@ def update_filename_and_move_files(sender, instance, **kwargs):
|
||||
return
|
||||
|
||||
with FileLock(settings.MEDIA_LOCK):
|
||||
old_filename = instance.filename
|
||||
new_filename = generate_unique_filename(
|
||||
instance, settings.ORIGINALS_DIR)
|
||||
try:
|
||||
old_filename = instance.filename
|
||||
old_source_path = instance.source_path
|
||||
|
||||
if new_filename == instance.filename:
|
||||
# Don't do anything if its the same.
|
||||
return
|
||||
instance.filename = generate_unique_filename(instance)
|
||||
move_original = old_filename != instance.filename
|
||||
|
||||
old_source_path = instance.source_path
|
||||
new_source_path = os.path.join(settings.ORIGINALS_DIR, new_filename)
|
||||
|
||||
if not validate_move(instance, old_source_path, new_source_path):
|
||||
return
|
||||
|
||||
# archive files are optional, archive checksum tells us if we have one,
|
||||
# since this is None for documents without archived files.
|
||||
if instance.archive_checksum:
|
||||
new_archive_filename = archive_name_from_filename(new_filename)
|
||||
old_archive_filename = instance.archive_filename
|
||||
old_archive_path = instance.archive_path
|
||||
new_archive_path = os.path.join(settings.ARCHIVE_DIR,
|
||||
new_archive_filename)
|
||||
|
||||
if not validate_move(instance, old_archive_path, new_archive_path):
|
||||
if instance.has_archive_version:
|
||||
|
||||
instance.archive_filename = generate_unique_filename(
|
||||
instance, archive_filename=True
|
||||
)
|
||||
|
||||
move_archive = old_archive_filename != instance.archive_filename # NOQA: E501
|
||||
else:
|
||||
move_archive = False
|
||||
|
||||
if not move_original and not move_archive:
|
||||
# Don't do anything if filenames did not change.
|
||||
return
|
||||
|
||||
create_source_path_directory(new_archive_path)
|
||||
else:
|
||||
old_archive_path = None
|
||||
new_archive_path = None
|
||||
if move_original:
|
||||
validate_move(instance, old_source_path, instance.source_path)
|
||||
create_source_path_directory(instance.source_path)
|
||||
os.rename(old_source_path, instance.source_path)
|
||||
|
||||
create_source_path_directory(new_source_path)
|
||||
|
||||
try:
|
||||
os.rename(old_source_path, new_source_path)
|
||||
if instance.archive_checksum:
|
||||
os.rename(old_archive_path, new_archive_path)
|
||||
instance.filename = new_filename
|
||||
if move_archive:
|
||||
validate_move(
|
||||
instance, old_archive_path, instance.archive_path)
|
||||
create_source_path_directory(instance.archive_path)
|
||||
os.rename(old_archive_path, instance.archive_path)
|
||||
|
||||
# Don't save() here to prevent infinite recursion.
|
||||
Document.objects.filter(pk=instance.pk).update(
|
||||
filename=new_filename)
|
||||
filename=instance.filename,
|
||||
archive_filename=instance.archive_filename,
|
||||
)
|
||||
|
||||
except OSError as e:
|
||||
instance.filename = old_filename
|
||||
# this happens when we can't move a file. If that's the case for
|
||||
# the archive file, we try our best to revert the changes.
|
||||
# no need to save the instance, the update() has not happened yet.
|
||||
except (OSError, DatabaseError, CannotMoveFilesException):
|
||||
# This happens when either:
|
||||
# - moving the files failed due to file system errors
|
||||
# - saving to the database failed due to database errors
|
||||
# In both cases, we need to revert to the original state.
|
||||
|
||||
# Try to move files to their original location.
|
||||
try:
|
||||
os.rename(new_source_path, old_source_path)
|
||||
os.rename(new_archive_path, old_archive_path)
|
||||
if move_original and os.path.isfile(instance.source_path):
|
||||
os.rename(instance.source_path, old_source_path)
|
||||
|
||||
if move_archive and os.path.isfile(instance.archive_path):
|
||||
os.rename(instance.archive_path, old_archive_path)
|
||||
|
||||
except Exception as e:
|
||||
# This is fine, since:
|
||||
# A: if we managed to move source from A to B, we will also
|
||||
@@ -267,16 +351,10 @@ def update_filename_and_move_files(sender, instance, **kwargs):
|
||||
# B: if moving the orignal file failed, nothing has changed
|
||||
# anyway.
|
||||
pass
|
||||
except DatabaseError as e:
|
||||
# this happens after moving files, so move them back into place.
|
||||
# since moving them once succeeded, it's very likely going to
|
||||
# succeed again.
|
||||
os.rename(new_source_path, old_source_path)
|
||||
if instance.archive_checksum:
|
||||
os.rename(new_archive_path, old_archive_path)
|
||||
|
||||
# restore old values on the instance
|
||||
instance.filename = old_filename
|
||||
# again, no need to save the instance, since the actual update()
|
||||
# operation failed.
|
||||
instance.archive_filename = old_archive_filename
|
||||
|
||||
# finally, remove any empty sub folders. This will do nothing if
|
||||
# something has failed above.
|
||||
@@ -284,7 +362,7 @@ def update_filename_and_move_files(sender, instance, **kwargs):
|
||||
delete_empty_directories(os.path.dirname(old_source_path),
|
||||
root=settings.ORIGINALS_DIR)
|
||||
|
||||
if old_archive_path and not os.path.isfile(old_archive_path):
|
||||
if instance.has_archive_version and not os.path.isfile(old_archive_path): # NOQA: E501
|
||||
delete_empty_directories(os.path.dirname(old_archive_path),
|
||||
root=settings.ARCHIVE_DIR)
|
||||
|
||||
@@ -305,4 +383,6 @@ def set_log_entry(sender, document=None, logging_group=None, **kwargs):
|
||||
|
||||
|
||||
def add_to_index(sender, document, **kwargs):
|
||||
from documents import index
|
||||
|
||||
index.add_or_update_document(document)
|
||||
|
||||
2
src/documents/static/bootstrap.min.css
vendored
2
src/documents/static/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -42,3 +42,58 @@ body {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
/*
|
||||
From theme_dark.scss
|
||||
$primary-dark-mode: #45973a;
|
||||
$danger-dark-mode: #b71631;
|
||||
$bg-dark-mode: #161618;
|
||||
$bg-dark-mode-accent: #21262d;
|
||||
$bg-light-dark-mode: #1c1c1f;
|
||||
$text-color-dark-mode: #abb2bf;
|
||||
$border-color-dark-mode: #47494f;
|
||||
*/
|
||||
body {
|
||||
background-color: #161618 !important;
|
||||
color: #abb2bf;
|
||||
}
|
||||
|
||||
svg.logo .text {
|
||||
fill: #abb2bf!important;
|
||||
}
|
||||
|
||||
.form-control:not(.is-invalid):not(.btn) {
|
||||
border-color: #47494f;
|
||||
}
|
||||
|
||||
.form-control:not(.btn) {
|
||||
background-color: #161618;
|
||||
color: #abb2bf;
|
||||
}
|
||||
|
||||
.form-control:not(.btn)::placeholder {
|
||||
color: #abb2bf;
|
||||
}
|
||||
|
||||
.form-control:not(.btn):focus {
|
||||
background-color: #1c1c1f !important;
|
||||
color: #8e97a9 !important;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #17541f;
|
||||
border-color: #17541f;
|
||||
}
|
||||
|
||||
.btn-primary:hover, .btn-primary:focus {
|
||||
background-color: #0f3614;
|
||||
border-color: #0c2c10;
|
||||
}
|
||||
|
||||
.btn-primary:not(:disabled):not(.disabled):active {
|
||||
background-color: #0c2c10;
|
||||
border-color: #09220d;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,12 @@ from django.db.models.signals import post_save
|
||||
from whoosh.writing import AsyncWriter
|
||||
|
||||
from documents import index, sanity_checker
|
||||
from documents.classifier import DocumentClassifier, \
|
||||
IncompatibleClassifierVersionError
|
||||
from documents.classifier import DocumentClassifier, load_classifier
|
||||
from documents.consumer import Consumer, ConsumerError
|
||||
from documents.models import Document
|
||||
from documents.sanity_checker import SanityFailedError
|
||||
from documents.models import Document, Tag, DocumentType, Correspondent
|
||||
from documents.sanity_checker import SanityCheckFailedException
|
||||
|
||||
logger = logging.getLogger("paperless.tasks")
|
||||
|
||||
|
||||
def index_optimize():
|
||||
@@ -19,40 +20,45 @@ def index_optimize():
|
||||
writer.commit(optimize=True)
|
||||
|
||||
|
||||
def index_reindex():
|
||||
def index_reindex(progress_bar_disable=False):
|
||||
documents = Document.objects.all()
|
||||
|
||||
ix = index.open_index(recreate=True)
|
||||
|
||||
with AsyncWriter(ix) as writer:
|
||||
for document in tqdm.tqdm(documents):
|
||||
for document in tqdm.tqdm(documents, disable=progress_bar_disable):
|
||||
index.update_document(writer, document)
|
||||
|
||||
|
||||
def train_classifier():
|
||||
classifier = DocumentClassifier()
|
||||
if (not Tag.objects.filter(
|
||||
matching_algorithm=Tag.MATCH_AUTO).exists() and
|
||||
not DocumentType.objects.filter(
|
||||
matching_algorithm=Tag.MATCH_AUTO).exists() and
|
||||
not Correspondent.objects.filter(
|
||||
matching_algorithm=Tag.MATCH_AUTO).exists()):
|
||||
|
||||
try:
|
||||
# load the classifier, since we might not have to train it again.
|
||||
classifier.reload()
|
||||
except (OSError, EOFError, IncompatibleClassifierVersionError):
|
||||
# This is what we're going to fix here.
|
||||
return
|
||||
|
||||
classifier = load_classifier()
|
||||
|
||||
if not classifier:
|
||||
classifier = DocumentClassifier()
|
||||
|
||||
try:
|
||||
if classifier.train():
|
||||
logging.getLogger(__name__).info(
|
||||
logger.info(
|
||||
"Saving updated classifier model to {}...".format(
|
||||
settings.MODEL_FILE)
|
||||
)
|
||||
classifier.save_classifier()
|
||||
classifier.save()
|
||||
else:
|
||||
logging.getLogger(__name__).debug(
|
||||
logger.debug(
|
||||
"Training data unchanged."
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logging.getLogger(__name__).error(
|
||||
logger.warning(
|
||||
"Classifier error: " + str(e)
|
||||
)
|
||||
|
||||
@@ -62,7 +68,8 @@ def consume_file(path,
|
||||
override_title=None,
|
||||
override_correspondent_id=None,
|
||||
override_document_type_id=None,
|
||||
override_tag_ids=None):
|
||||
override_tag_ids=None,
|
||||
task_id=None):
|
||||
|
||||
document = Consumer().try_consume_file(
|
||||
path,
|
||||
@@ -70,7 +77,9 @@ def consume_file(path,
|
||||
override_title=override_title,
|
||||
override_correspondent_id=override_correspondent_id,
|
||||
override_document_type_id=override_document_type_id,
|
||||
override_tag_ids=override_tag_ids)
|
||||
override_tag_ids=override_tag_ids,
|
||||
task_id=task_id
|
||||
)
|
||||
|
||||
if document:
|
||||
return "Success. New document id {} created".format(
|
||||
@@ -84,8 +93,15 @@ def consume_file(path,
|
||||
def sanity_check():
|
||||
messages = sanity_checker.check_sanity()
|
||||
|
||||
if len(messages) > 0:
|
||||
raise SanityFailedError(messages)
|
||||
messages.log_messages()
|
||||
|
||||
if messages.has_error():
|
||||
raise SanityCheckFailedException(
|
||||
"Sanity check failed with errors. See log.")
|
||||
elif messages.has_warning():
|
||||
return "Sanity check exited with warnings. See log."
|
||||
elif len(messages) > 0:
|
||||
return "Sanity check exited with infos. See log."
|
||||
else:
|
||||
return "No issues detected."
|
||||
|
||||
|
||||
@@ -7,14 +7,16 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Paperless-ng</title>
|
||||
<base href="/">
|
||||
<base href="{% url 'base' %}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="username" content="{{username}}">
|
||||
<meta name="full_name" content="{{full_name}}">
|
||||
<meta name="cookie_prefix" content="{{cookie_prefix}}">
|
||||
<meta name="robots" content="noindex,nofollow">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link rel="manifest" href="{% static webmanifest %}">
|
||||
<link rel="stylesheet" href="{% static styles_css %}">
|
||||
<link rel="apple-touch-icon" href="{% static apple_touch_icon %}">
|
||||
</head>
|
||||
<body>
|
||||
<app-root>{% translate "Paperless-ng is loading..." %}</app-root>
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
src/documents/tests/data/model.pickle
Normal file
BIN
src/documents/tests/data/model.pickle
Normal file
Binary file not shown.
BIN
src/documents/tests/samples/simple-noalpha.png
Normal file
BIN
src/documents/tests/samples/simple-noalpha.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.3 KiB |
BIN
src/documents/tests/samples/simple.jpg
Normal file
BIN
src/documents/tests/samples/simple.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
src/documents/tests/samples/simple.png
Normal file
BIN
src/documents/tests/samples/simple.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
1
src/documents/tests/samples/simple.txt
Normal file
1
src/documents/tests/samples/simple.txt
Normal file
@@ -0,0 +1 @@
|
||||
This is a test file.
|
||||
@@ -4,6 +4,7 @@ from django.contrib.admin.sites import AdminSite
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
from documents import index
|
||||
from documents.admin import DocumentAdmin
|
||||
from documents.models import Document
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
@@ -11,49 +12,52 @@ from documents.tests.utils import DirectoriesMixin
|
||||
|
||||
class TestDocumentAdmin(DirectoriesMixin, TestCase):
|
||||
|
||||
def get_document_from_index(self, doc):
|
||||
ix = index.open_index()
|
||||
with ix.searcher() as searcher:
|
||||
return searcher.document(id=doc.id)
|
||||
|
||||
def setUp(self) -> None:
|
||||
super(TestDocumentAdmin, self).setUp()
|
||||
self.doc_admin = DocumentAdmin(model=Document, admin_site=AdminSite())
|
||||
|
||||
@mock.patch("documents.admin.index.add_or_update_document")
|
||||
def test_save_model(self, m):
|
||||
def test_save_model(self):
|
||||
doc = Document.objects.create(title="test")
|
||||
|
||||
doc.title = "new title"
|
||||
self.doc_admin.save_model(None, doc, None, None)
|
||||
self.assertEqual(Document.objects.get(id=doc.id).title, "new title")
|
||||
m.assert_called_once()
|
||||
self.assertEqual(self.get_document_from_index(doc)['id'], doc.id)
|
||||
|
||||
def test_tags(self):
|
||||
def test_delete_model(self):
|
||||
doc = Document.objects.create(title="test")
|
||||
doc.tags.create(name="t1")
|
||||
doc.tags.create(name="t2")
|
||||
index.add_or_update_document(doc)
|
||||
self.assertIsNotNone(self.get_document_from_index(doc))
|
||||
|
||||
self.assertEqual(self.doc_admin.tags_(doc), "<span >t1, </span><span >t2, </span>")
|
||||
|
||||
def test_tags_empty(self):
|
||||
doc = Document.objects.create(title="test")
|
||||
|
||||
self.assertEqual(self.doc_admin.tags_(doc), "")
|
||||
|
||||
@mock.patch("documents.admin.index.remove_document")
|
||||
def test_delete_model(self, m):
|
||||
doc = Document.objects.create(title="test")
|
||||
self.doc_admin.delete_model(None, doc)
|
||||
self.assertRaises(Document.DoesNotExist, Document.objects.get, id=doc.id)
|
||||
m.assert_called_once()
|
||||
|
||||
@mock.patch("documents.admin.index.remove_document")
|
||||
def test_delete_queryset(self, m):
|
||||
self.assertRaises(Document.DoesNotExist, Document.objects.get, id=doc.id)
|
||||
self.assertIsNone(self.get_document_from_index(doc))
|
||||
|
||||
def test_delete_queryset(self):
|
||||
docs = []
|
||||
for i in range(42):
|
||||
Document.objects.create(title="Many documents with the same title", checksum=f"{i:02}")
|
||||
doc = Document.objects.create(title="Many documents with the same title", checksum=f"{i:02}")
|
||||
docs.append(doc)
|
||||
index.add_or_update_document(doc)
|
||||
|
||||
self.assertEqual(Document.objects.count(), 42)
|
||||
|
||||
for doc in docs:
|
||||
self.assertIsNotNone(self.get_document_from_index(doc))
|
||||
|
||||
self.doc_admin.delete_queryset(None, Document.objects.all())
|
||||
|
||||
self.assertEqual(m.call_count, 42)
|
||||
self.assertEqual(Document.objects.count(), 0)
|
||||
|
||||
for doc in docs:
|
||||
self.assertIsNone(self.get_document_from_index(doc))
|
||||
|
||||
def test_created(self):
|
||||
doc = Document.objects.create(title="test", created=timezone.datetime(2020, 4, 12))
|
||||
self.assertEqual(self.doc_admin.created_(doc), "2020-04-12")
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import datetime
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import override_settings
|
||||
from rest_framework.test import APITestCase
|
||||
from whoosh.writing import AsyncWriter
|
||||
|
||||
from documents import index, bulk_edit
|
||||
from documents.models import Document, Correspondent, DocumentType, Tag, SavedView
|
||||
from documents.models import Document, Correspondent, DocumentType, Tag, SavedView, MatchingModel
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
|
||||
|
||||
@@ -144,21 +150,19 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.content, content_thumbnail)
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="")
|
||||
def test_download_with_archive(self):
|
||||
|
||||
_, filename = tempfile.mkstemp(dir=self.dirs.originals_dir)
|
||||
|
||||
content = b"This is a test"
|
||||
content_archive = b"This is the same test but archived"
|
||||
|
||||
with open(filename, "wb") as f:
|
||||
f.write(content)
|
||||
|
||||
filename = os.path.basename(filename)
|
||||
|
||||
doc = Document.objects.create(title="none", filename=filename,
|
||||
doc = Document.objects.create(title="none", filename="my_document.pdf",
|
||||
archive_filename="archived.pdf",
|
||||
mime_type="application/pdf")
|
||||
|
||||
with open(doc.source_path, "wb") as f:
|
||||
f.write(content)
|
||||
|
||||
with open(doc.archive_path, "wb") as f:
|
||||
f.write(content_archive)
|
||||
|
||||
@@ -228,6 +232,12 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
||||
self.assertEqual(len(results), 2)
|
||||
self.assertCountEqual([results[0]['id'], results[1]['id']], [doc1.id, doc3.id])
|
||||
|
||||
response = self.client.get("/api/documents/?tags__id__in={},{}".format(tag_2.id, tag_3.id))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
results = response.data['results']
|
||||
self.assertEqual(len(results), 2)
|
||||
self.assertCountEqual([results[0]['id'], results[1]['id']], [doc2.id, doc3.id])
|
||||
|
||||
response = self.client.get("/api/documents/?tags__id__all={},{}".format(tag_2.id, tag_3.id))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
results = response.data['results']
|
||||
@@ -261,10 +271,28 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
||||
results = response.data['results']
|
||||
self.assertEqual(len(results), 0)
|
||||
|
||||
def test_search_no_query(self):
|
||||
response = self.client.get("/api/search/")
|
||||
results = response.data['results']
|
||||
def test_documents_title_content_filter(self):
|
||||
|
||||
doc1 = Document.objects.create(title="title A", content="content A", checksum="A", mime_type="application/pdf")
|
||||
doc2 = Document.objects.create(title="title B", content="content A", checksum="B", mime_type="application/pdf")
|
||||
doc3 = Document.objects.create(title="title A", content="content B", checksum="C", mime_type="application/pdf")
|
||||
doc4 = Document.objects.create(title="title B", content="content B", checksum="D", mime_type="application/pdf")
|
||||
|
||||
response = self.client.get("/api/documents/?title_content=A")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
results = response.data['results']
|
||||
self.assertEqual(len(results), 3)
|
||||
self.assertCountEqual([results[0]['id'], results[1]['id'], results[2]['id']], [doc1.id, doc2.id, doc3.id])
|
||||
|
||||
response = self.client.get("/api/documents/?title_content=B")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
results = response.data['results']
|
||||
self.assertEqual(len(results), 3)
|
||||
self.assertCountEqual([results[0]['id'], results[1]['id'], results[2]['id']], [doc2.id, doc3.id, doc4.id])
|
||||
|
||||
response = self.client.get("/api/documents/?title_content=X")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
results = response.data['results']
|
||||
self.assertEqual(len(results), 0)
|
||||
|
||||
def test_search(self):
|
||||
@@ -278,32 +306,24 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
||||
index.update_document(writer, d1)
|
||||
index.update_document(writer, d2)
|
||||
index.update_document(writer, d3)
|
||||
response = self.client.get("/api/search/?query=bank")
|
||||
response = self.client.get("/api/documents/?query=bank")
|
||||
results = response.data['results']
|
||||
self.assertEqual(response.data['count'], 3)
|
||||
self.assertEqual(response.data['page'], 1)
|
||||
self.assertEqual(response.data['page_count'], 1)
|
||||
self.assertEqual(len(results), 3)
|
||||
|
||||
response = self.client.get("/api/search/?query=september")
|
||||
response = self.client.get("/api/documents/?query=september")
|
||||
results = response.data['results']
|
||||
self.assertEqual(response.data['count'], 1)
|
||||
self.assertEqual(response.data['page'], 1)
|
||||
self.assertEqual(response.data['page_count'], 1)
|
||||
self.assertEqual(len(results), 1)
|
||||
|
||||
response = self.client.get("/api/search/?query=statement")
|
||||
response = self.client.get("/api/documents/?query=statement")
|
||||
results = response.data['results']
|
||||
self.assertEqual(response.data['count'], 2)
|
||||
self.assertEqual(response.data['page'], 1)
|
||||
self.assertEqual(response.data['page_count'], 1)
|
||||
self.assertEqual(len(results), 2)
|
||||
|
||||
response = self.client.get("/api/search/?query=sfegdfg")
|
||||
response = self.client.get("/api/documents/?query=sfegdfg")
|
||||
results = response.data['results']
|
||||
self.assertEqual(response.data['count'], 0)
|
||||
self.assertEqual(response.data['page'], 0)
|
||||
self.assertEqual(response.data['page_count'], 0)
|
||||
self.assertEqual(len(results), 0)
|
||||
|
||||
def test_search_multi_page(self):
|
||||
@@ -316,53 +336,34 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
||||
seen_ids = []
|
||||
|
||||
for i in range(1, 6):
|
||||
response = self.client.get(f"/api/search/?query=content&page={i}")
|
||||
response = self.client.get(f"/api/documents/?query=content&page={i}&page_size=10")
|
||||
results = response.data['results']
|
||||
self.assertEqual(response.data['count'], 55)
|
||||
self.assertEqual(response.data['page'], i)
|
||||
self.assertEqual(response.data['page_count'], 6)
|
||||
self.assertEqual(len(results), 10)
|
||||
|
||||
for result in results:
|
||||
self.assertNotIn(result['id'], seen_ids)
|
||||
seen_ids.append(result['id'])
|
||||
|
||||
response = self.client.get(f"/api/search/?query=content&page=6")
|
||||
response = self.client.get(f"/api/documents/?query=content&page=6&page_size=10")
|
||||
results = response.data['results']
|
||||
self.assertEqual(response.data['count'], 55)
|
||||
self.assertEqual(response.data['page'], 6)
|
||||
self.assertEqual(response.data['page_count'], 6)
|
||||
self.assertEqual(len(results), 5)
|
||||
|
||||
for result in results:
|
||||
self.assertNotIn(result['id'], seen_ids)
|
||||
seen_ids.append(result['id'])
|
||||
|
||||
response = self.client.get(f"/api/search/?query=content&page=7")
|
||||
results = response.data['results']
|
||||
self.assertEqual(response.data['count'], 55)
|
||||
self.assertEqual(response.data['page'], 6)
|
||||
self.assertEqual(response.data['page_count'], 6)
|
||||
self.assertEqual(len(results), 5)
|
||||
|
||||
def test_search_invalid_page(self):
|
||||
with AsyncWriter(index.open_index()) as writer:
|
||||
for i in range(15):
|
||||
doc = Document.objects.create(checksum=str(i), pk=i+1, title=f"Document {i+1}", content="content")
|
||||
index.update_document(writer, doc)
|
||||
|
||||
first_page = self.client.get(f"/api/search/?query=content&page=1").data
|
||||
second_page = self.client.get(f"/api/search/?query=content&page=2").data
|
||||
should_be_first_page_1 = self.client.get(f"/api/search/?query=content&page=0").data
|
||||
should_be_first_page_2 = self.client.get(f"/api/search/?query=content&page=dgfd").data
|
||||
should_be_first_page_3 = self.client.get(f"/api/search/?query=content&page=").data
|
||||
should_be_first_page_4 = self.client.get(f"/api/search/?query=content&page=-7868").data
|
||||
|
||||
self.assertDictEqual(first_page, should_be_first_page_1)
|
||||
self.assertDictEqual(first_page, should_be_first_page_2)
|
||||
self.assertDictEqual(first_page, should_be_first_page_3)
|
||||
self.assertDictEqual(first_page, should_be_first_page_4)
|
||||
self.assertNotEqual(len(first_page['results']), len(second_page['results']))
|
||||
response = self.client.get(f"/api/documents/?query=content&page=0&page_size=10")
|
||||
self.assertEqual(response.status_code, 404)
|
||||
response = self.client.get(f"/api/documents/?query=content&page=3&page_size=10")
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
@mock.patch("documents.index.autocomplete")
|
||||
def test_search_autocomplete(self, m):
|
||||
@@ -386,6 +387,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(len(response.data), 10)
|
||||
|
||||
@pytest.mark.skip(reason="Not implemented yet")
|
||||
def test_search_spelling_correction(self):
|
||||
with AsyncWriter(index.open_index()) as writer:
|
||||
for i in range(55):
|
||||
@@ -411,7 +413,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
||||
index.update_document(writer, d2)
|
||||
index.update_document(writer, d3)
|
||||
|
||||
response = self.client.get(f"/api/search/?more_like={d2.id}")
|
||||
response = self.client.get(f"/api/documents/?more_like_id={d2.id}")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@@ -421,6 +423,79 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
||||
self.assertEqual(results[0]['id'], d3.id)
|
||||
self.assertEqual(results[1]['id'], d1.id)
|
||||
|
||||
def test_search_filtering(self):
|
||||
t = Tag.objects.create(name="tag")
|
||||
t2 = Tag.objects.create(name="tag2")
|
||||
c = Correspondent.objects.create(name="correspondent")
|
||||
dt = DocumentType.objects.create(name="type")
|
||||
|
||||
d1 = Document.objects.create(checksum="1", correspondent=c, content="test")
|
||||
d2 = Document.objects.create(checksum="2", document_type=dt, content="test")
|
||||
d3 = Document.objects.create(checksum="3", content="test")
|
||||
d3.tags.add(t)
|
||||
d3.tags.add(t2)
|
||||
d4 = Document.objects.create(checksum="4", created=datetime.datetime(2020, 7, 13), content="test")
|
||||
d4.tags.add(t2)
|
||||
d5 = Document.objects.create(checksum="5", added=datetime.datetime(2020, 7, 13), content="test")
|
||||
d6 = Document.objects.create(checksum="6", content="test2")
|
||||
|
||||
with AsyncWriter(index.open_index()) as writer:
|
||||
for doc in Document.objects.all():
|
||||
index.update_document(writer, doc)
|
||||
|
||||
def search_query(q):
|
||||
r = self.client.get("/api/documents/?query=test" + q)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
return [hit['id'] for hit in r.data['results']]
|
||||
|
||||
self.assertCountEqual(search_query(""), [d1.id, d2.id, d3.id, d4.id, d5.id])
|
||||
self.assertCountEqual(search_query("&is_tagged=true"), [d3.id, d4.id])
|
||||
self.assertCountEqual(search_query("&is_tagged=false"), [d1.id, d2.id, d5.id])
|
||||
self.assertCountEqual(search_query("&correspondent__id=" + str(c.id)), [d1.id])
|
||||
self.assertCountEqual(search_query("&document_type__id=" + str(dt.id)), [d2.id])
|
||||
self.assertCountEqual(search_query("&correspondent__isnull"), [d2.id, d3.id, d4.id, d5.id])
|
||||
self.assertCountEqual(search_query("&document_type__isnull"), [d1.id, d3.id, d4.id, d5.id])
|
||||
self.assertCountEqual(search_query("&tags__id__all=" + str(t.id) + "," + str(t2.id)), [d3.id])
|
||||
self.assertCountEqual(search_query("&tags__id__all=" + str(t.id)), [d3.id])
|
||||
self.assertCountEqual(search_query("&tags__id__all=" + str(t2.id)), [d3.id, d4.id])
|
||||
|
||||
self.assertIn(d4.id, search_query("&created__date__lt=" + datetime.datetime(2020, 9, 2).strftime("%Y-%m-%d")))
|
||||
self.assertNotIn(d4.id, search_query("&created__date__gt=" + datetime.datetime(2020, 9, 2).strftime("%Y-%m-%d")))
|
||||
|
||||
self.assertNotIn(d4.id, search_query("&created__date__lt=" + datetime.datetime(2020, 1, 2).strftime("%Y-%m-%d")))
|
||||
self.assertIn(d4.id, search_query("&created__date__gt=" + datetime.datetime(2020, 1, 2).strftime("%Y-%m-%d")))
|
||||
|
||||
self.assertIn(d5.id, search_query("&added__date__lt=" + datetime.datetime(2020, 9, 2).strftime("%Y-%m-%d")))
|
||||
self.assertNotIn(d5.id, search_query("&added__date__gt=" + datetime.datetime(2020, 9, 2).strftime("%Y-%m-%d")))
|
||||
|
||||
self.assertNotIn(d5.id, search_query("&added__date__lt=" + datetime.datetime(2020, 1, 2).strftime("%Y-%m-%d")))
|
||||
self.assertIn(d5.id, search_query("&added__date__gt=" + datetime.datetime(2020, 1, 2).strftime("%Y-%m-%d")))
|
||||
|
||||
def test_search_sorting(self):
|
||||
c1 = Correspondent.objects.create(name="corres Ax")
|
||||
c2 = Correspondent.objects.create(name="corres Cx")
|
||||
c3 = Correspondent.objects.create(name="corres Bx")
|
||||
d1 = Document.objects.create(checksum="1", correspondent=c1, content="test", archive_serial_number=2, title="3")
|
||||
d2 = Document.objects.create(checksum="2", correspondent=c2, content="test", archive_serial_number=3, title="2")
|
||||
d3 = Document.objects.create(checksum="3", correspondent=c3, content="test", archive_serial_number=1, title="1")
|
||||
|
||||
with AsyncWriter(index.open_index()) as writer:
|
||||
for doc in Document.objects.all():
|
||||
index.update_document(writer, doc)
|
||||
|
||||
def search_query(q):
|
||||
r = self.client.get("/api/documents/?query=test" + q)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
return [hit['id'] for hit in r.data['results']]
|
||||
|
||||
self.assertListEqual(search_query("&ordering=archive_serial_number"), [d3.id, d1.id, d2.id])
|
||||
self.assertListEqual(search_query("&ordering=-archive_serial_number"), [d2.id, d1.id, d3.id])
|
||||
self.assertListEqual(search_query("&ordering=title"), [d3.id, d2.id, d1.id])
|
||||
self.assertListEqual(search_query("&ordering=-title"), [d1.id, d2.id, d3.id])
|
||||
self.assertListEqual(search_query("&ordering=correspondent__name"), [d1.id, d3.id, d2.id])
|
||||
self.assertListEqual(search_query("&ordering=-correspondent__name"), [d2.id, d3.id, d1.id])
|
||||
|
||||
|
||||
def test_statistics(self):
|
||||
|
||||
doc1 = Document.objects.create(title="none1", checksum="A")
|
||||
@@ -436,6 +511,13 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
||||
self.assertEqual(response.data['documents_total'], 3)
|
||||
self.assertEqual(response.data['documents_inbox'], 1)
|
||||
|
||||
def test_statistics_no_inbox_tag(self):
|
||||
Document.objects.create(title="none1", checksum="A")
|
||||
|
||||
response = self.client.get("/api/statistics/")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data['documents_inbox'], None)
|
||||
|
||||
@mock.patch("documents.views.async_task")
|
||||
def test_upload(self, m):
|
||||
|
||||
@@ -569,10 +651,13 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
||||
async_task.assert_not_called()
|
||||
|
||||
def test_get_metadata(self):
|
||||
doc = Document.objects.create(title="test", filename="file.pdf", mime_type="image/png", archive_checksum="A")
|
||||
doc = Document.objects.create(title="test", filename="file.pdf", mime_type="image/png", archive_checksum="A", archive_filename="archive.pdf")
|
||||
|
||||
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "documents", "thumbnails", "0000001.png"), doc.source_path)
|
||||
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), doc.archive_path)
|
||||
source_file = os.path.join(os.path.dirname(__file__), "samples", "documents", "thumbnails", "0000001.png")
|
||||
archive_file = os.path.join(os.path.dirname(__file__), "samples", "simple.pdf")
|
||||
|
||||
shutil.copy(source_file, doc.source_path)
|
||||
shutil.copy(archive_file, doc.archive_path)
|
||||
|
||||
response = self.client.get(f"/api/documents/{doc.pk}/metadata/")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
@@ -583,6 +668,14 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
||||
self.assertTrue(meta['has_archive_version'])
|
||||
self.assertEqual(len(meta['original_metadata']), 0)
|
||||
self.assertGreater(len(meta['archive_metadata']), 0)
|
||||
self.assertEqual(meta['media_filename'], "file.pdf")
|
||||
self.assertEqual(meta['archive_media_filename'], "archive.pdf")
|
||||
self.assertEqual(meta['original_size'], os.stat(source_file).st_size)
|
||||
self.assertEqual(meta['archive_size'], os.stat(archive_file).st_size)
|
||||
|
||||
def test_get_metadata_invalid_doc(self):
|
||||
response = self.client.get(f"/api/documents/34576/metadata/")
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_get_metadata_no_archive(self):
|
||||
doc = Document.objects.create(title="test", filename="file.pdf", mime_type="application/pdf")
|
||||
@@ -598,6 +691,46 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
||||
self.assertFalse(meta['has_archive_version'])
|
||||
self.assertGreater(len(meta['original_metadata']), 0)
|
||||
self.assertIsNone(meta['archive_metadata'])
|
||||
self.assertIsNone(meta['archive_media_filename'])
|
||||
|
||||
def test_get_metadata_missing_files(self):
|
||||
doc = Document.objects.create(title="test", filename="file.pdf", mime_type="application/pdf", archive_filename="file.pdf", archive_checksum="B", checksum="A")
|
||||
|
||||
response = self.client.get(f"/api/documents/{doc.pk}/metadata/")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
meta = response.data
|
||||
|
||||
self.assertTrue(meta['has_archive_version'])
|
||||
self.assertIsNone(meta['original_metadata'])
|
||||
self.assertIsNone(meta['original_size'])
|
||||
self.assertIsNone(meta['archive_metadata'])
|
||||
self.assertIsNone(meta['archive_size'])
|
||||
|
||||
|
||||
def test_get_empty_suggestions(self):
|
||||
doc = Document.objects.create(title="test", mime_type="application/pdf")
|
||||
|
||||
response = self.client.get(f"/api/documents/{doc.pk}/suggestions/")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data, {'correspondents': [], 'tags': [], 'document_types': []})
|
||||
|
||||
def test_get_suggestions_invalid_doc(self):
|
||||
response = self.client.get(f"/api/documents/34676/suggestions/")
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
@mock.patch("documents.views.match_correspondents")
|
||||
@mock.patch("documents.views.match_tags")
|
||||
@mock.patch("documents.views.match_document_types")
|
||||
def test_get_suggestions(self, match_document_types, match_tags, match_correspondents):
|
||||
doc = Document.objects.create(title="test", mime_type="application/pdf", content="this is an invoice!")
|
||||
match_tags.return_value = [Tag(id=56), Tag(id=123)]
|
||||
match_document_types.return_value = [DocumentType(id=23)]
|
||||
match_correspondents.return_value = [Correspondent(id=88), Correspondent(id=2)]
|
||||
|
||||
response = self.client.get(f"/api/documents/{doc.pk}/suggestions/")
|
||||
self.assertEqual(response.data, {'correspondents': [88,2], 'tags': [56,123], 'document_types': [23]})
|
||||
|
||||
def test_saved_views(self):
|
||||
u1 = User.objects.create_user("user1")
|
||||
@@ -683,6 +816,126 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
||||
v1 = SavedView.objects.get(id=v1.id)
|
||||
self.assertEqual(v1.filter_rules.count(), 0)
|
||||
|
||||
def test_get_logs(self):
|
||||
response = self.client.get("/api/logs/")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertCountEqual(response.data, ["mail", "paperless"])
|
||||
|
||||
def test_get_invalid_log(self):
|
||||
response = self.client.get("/api/logs/bogus_log/")
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
@override_settings(LOGGING_DIR="bogus_dir")
|
||||
def test_get_nonexistent_log(self):
|
||||
response = self.client.get("/api/logs/paperless/")
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_get_log(self):
|
||||
log_data = "test\ntest2\n"
|
||||
with open(os.path.join(settings.LOGGING_DIR, "paperless.log"), "w") as f:
|
||||
f.write(log_data)
|
||||
response = self.client.get("/api/logs/paperless/")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertListEqual(response.data, ["test", "test2"])
|
||||
|
||||
def test_invalid_regex_other_algorithm(self):
|
||||
for endpoint in ['correspondents', 'tags', 'document_types']:
|
||||
response = self.client.post(f"/api/{endpoint}/", {
|
||||
"name": "test",
|
||||
"matching_algorithm": MatchingModel.MATCH_ANY,
|
||||
"match": "["
|
||||
}, format='json')
|
||||
self.assertEqual(response.status_code, 201, endpoint)
|
||||
|
||||
def test_invalid_regex(self):
|
||||
for endpoint in ['correspondents', 'tags', 'document_types']:
|
||||
response = self.client.post(f"/api/{endpoint}/", {
|
||||
"name": "test",
|
||||
"matching_algorithm": MatchingModel.MATCH_REGEX,
|
||||
"match": "["
|
||||
}, format='json')
|
||||
self.assertEqual(response.status_code, 400, endpoint)
|
||||
|
||||
def test_valid_regex(self):
|
||||
for endpoint in ['correspondents', 'tags', 'document_types']:
|
||||
response = self.client.post(f"/api/{endpoint}/", {
|
||||
"name": "test",
|
||||
"matching_algorithm": MatchingModel.MATCH_REGEX,
|
||||
"match": "[0-9]"
|
||||
}, format='json')
|
||||
self.assertEqual(response.status_code, 201, endpoint)
|
||||
|
||||
def test_regex_no_algorithm(self):
|
||||
for endpoint in ['correspondents', 'tags', 'document_types']:
|
||||
response = self.client.post(f"/api/{endpoint}/", {
|
||||
"name": "test",
|
||||
"match": "[0-9]"
|
||||
}, format='json')
|
||||
self.assertEqual(response.status_code, 201, endpoint)
|
||||
|
||||
def test_tag_color_default(self):
|
||||
response = self.client.post("/api/tags/", {
|
||||
"name": "tag"
|
||||
}, format="json")
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(Tag.objects.get(id=response.data['id']).color, "#a6cee3")
|
||||
self.assertEqual(self.client.get(f"/api/tags/{response.data['id']}/", format="json").data['colour'], 1)
|
||||
|
||||
def test_tag_color(self):
|
||||
response = self.client.post("/api/tags/", {
|
||||
"name": "tag",
|
||||
"colour": 3
|
||||
}, format="json")
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(Tag.objects.get(id=response.data['id']).color, "#b2df8a")
|
||||
self.assertEqual(self.client.get(f"/api/tags/{response.data['id']}/", format="json").data['colour'], 3)
|
||||
|
||||
def test_tag_color_invalid(self):
|
||||
response = self.client.post("/api/tags/", {
|
||||
"name": "tag",
|
||||
"colour": 34
|
||||
}, format="json")
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_tag_color_custom(self):
|
||||
tag = Tag.objects.create(name="test", color="#abcdef")
|
||||
self.assertEqual(self.client.get(f"/api/tags/{tag.id}/", format="json").data['colour'], 1)
|
||||
|
||||
|
||||
class TestDocumentApiV2(DirectoriesMixin, APITestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDocumentApiV2, self).setUp()
|
||||
|
||||
self.user = User.objects.create_superuser(username="temp_admin")
|
||||
|
||||
self.client.force_login(user=self.user)
|
||||
self.client.defaults['HTTP_ACCEPT'] = 'application/json; version=2'
|
||||
|
||||
def test_tag_validate_color(self):
|
||||
self.assertEqual(self.client.post("/api/tags/", {"name": "test", "color": "#12fFaA"}, format="json").status_code, 201)
|
||||
|
||||
self.assertEqual(self.client.post("/api/tags/", {"name": "test1", "color": "abcdef"}, format="json").status_code, 400)
|
||||
self.assertEqual(self.client.post("/api/tags/", {"name": "test2", "color": "#abcdfg"}, format="json").status_code, 400)
|
||||
self.assertEqual(self.client.post("/api/tags/", {"name": "test3", "color": "#asd"}, format="json").status_code, 400)
|
||||
self.assertEqual(self.client.post("/api/tags/", {"name": "test4", "color": "#12121212"}, format="json").status_code, 400)
|
||||
|
||||
def test_tag_text_color(self):
|
||||
t = Tag.objects.create(name="tag1", color="#000000")
|
||||
self.assertEqual(self.client.get(f"/api/tags/{t.id}/", format="json").data['text_color'], "#ffffff")
|
||||
|
||||
t.color = "#ffffff"
|
||||
t.save()
|
||||
self.assertEqual(self.client.get(f"/api/tags/{t.id}/", format="json").data['text_color'], "#000000")
|
||||
|
||||
t.color = "asdf"
|
||||
t.save()
|
||||
self.assertEqual(self.client.get(f"/api/tags/{t.id}/", format="json").data['text_color'], "#000000")
|
||||
|
||||
t.color = "123"
|
||||
t.save()
|
||||
self.assertEqual(self.client.get(f"/api/tags/{t.id}/", format="json").data['text_color'], "#000000")
|
||||
|
||||
|
||||
class TestBulkEdit(DirectoriesMixin, APITestCase):
|
||||
|
||||
@@ -1037,6 +1290,113 @@ class TestBulkEdit(DirectoriesMixin, APITestCase):
|
||||
self.assertCountEqual(response.data['selected_document_types'], [{"id": self.c1.id, "document_count": 1}, {"id": self.c2.id, "document_count": 0}])
|
||||
|
||||
|
||||
class TestBulkDownload(DirectoriesMixin, APITestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestBulkDownload, self).setUp()
|
||||
|
||||
user = User.objects.create_superuser(username="temp_admin")
|
||||
self.client.force_login(user=user)
|
||||
|
||||
self.doc1 = Document.objects.create(title="unrelated", checksum="A")
|
||||
self.doc2 = Document.objects.create(title="document A", filename="docA.pdf", mime_type="application/pdf", checksum="B", created=datetime.datetime(2021, 1, 1))
|
||||
self.doc2b = Document.objects.create(title="document A", filename="docA2.pdf", mime_type="application/pdf", checksum="D", created=datetime.datetime(2021, 1, 1))
|
||||
self.doc3 = Document.objects.create(title="document B", filename="docB.jpg", mime_type="image/jpeg", checksum="C", created=datetime.datetime(2020, 3, 21), archive_filename="docB.pdf", archive_checksum="D")
|
||||
|
||||
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), self.doc2.source_path)
|
||||
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "simple.png"), self.doc2b.source_path)
|
||||
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "simple.jpg"), self.doc3.source_path)
|
||||
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "test_with_bom.pdf"), self.doc3.archive_path)
|
||||
|
||||
def test_download_originals(self):
|
||||
response = self.client.post("/api/documents/bulk_download/", json.dumps({
|
||||
"documents": [self.doc2.id, self.doc3.id],
|
||||
"content": "originals"
|
||||
}), content_type='application/json')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response['Content-Type'], 'application/zip')
|
||||
|
||||
with zipfile.ZipFile(io.BytesIO(response.content)) as zipf:
|
||||
self.assertEqual(len(zipf.filelist), 2)
|
||||
self.assertIn("2021-01-01 document A.pdf", zipf.namelist())
|
||||
self.assertIn("2020-03-21 document B.jpg", zipf.namelist())
|
||||
|
||||
with self.doc2.source_file as f:
|
||||
self.assertEqual(f.read(), zipf.read("2021-01-01 document A.pdf"))
|
||||
|
||||
with self.doc3.source_file as f:
|
||||
self.assertEqual(f.read(), zipf.read("2020-03-21 document B.jpg"))
|
||||
|
||||
def test_download_default(self):
|
||||
response = self.client.post("/api/documents/bulk_download/", json.dumps({
|
||||
"documents": [self.doc2.id, self.doc3.id]
|
||||
}), content_type='application/json')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response['Content-Type'], 'application/zip')
|
||||
|
||||
with zipfile.ZipFile(io.BytesIO(response.content)) as zipf:
|
||||
self.assertEqual(len(zipf.filelist), 2)
|
||||
self.assertIn("2021-01-01 document A.pdf", zipf.namelist())
|
||||
self.assertIn("2020-03-21 document B.pdf", zipf.namelist())
|
||||
|
||||
with self.doc2.source_file as f:
|
||||
self.assertEqual(f.read(), zipf.read("2021-01-01 document A.pdf"))
|
||||
|
||||
with self.doc3.archive_file as f:
|
||||
self.assertEqual(f.read(), zipf.read("2020-03-21 document B.pdf"))
|
||||
|
||||
def test_download_both(self):
|
||||
response = self.client.post("/api/documents/bulk_download/", json.dumps({
|
||||
"documents": [self.doc2.id, self.doc3.id],
|
||||
"content": "both"
|
||||
}), content_type='application/json')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response['Content-Type'], 'application/zip')
|
||||
|
||||
with zipfile.ZipFile(io.BytesIO(response.content)) as zipf:
|
||||
self.assertEqual(len(zipf.filelist), 3)
|
||||
self.assertIn("originals/2021-01-01 document A.pdf", zipf.namelist())
|
||||
self.assertIn("archive/2020-03-21 document B.pdf", zipf.namelist())
|
||||
self.assertIn("originals/2020-03-21 document B.jpg", zipf.namelist())
|
||||
|
||||
with self.doc2.source_file as f:
|
||||
self.assertEqual(f.read(), zipf.read("originals/2021-01-01 document A.pdf"))
|
||||
|
||||
with self.doc3.archive_file as f:
|
||||
self.assertEqual(f.read(), zipf.read("archive/2020-03-21 document B.pdf"))
|
||||
|
||||
with self.doc3.source_file as f:
|
||||
self.assertEqual(f.read(), zipf.read("originals/2020-03-21 document B.jpg"))
|
||||
|
||||
def test_filename_clashes(self):
|
||||
response = self.client.post("/api/documents/bulk_download/", json.dumps({
|
||||
"documents": [self.doc2.id, self.doc2b.id]
|
||||
}), content_type='application/json')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response['Content-Type'], 'application/zip')
|
||||
|
||||
with zipfile.ZipFile(io.BytesIO(response.content)) as zipf:
|
||||
self.assertEqual(len(zipf.filelist), 2)
|
||||
|
||||
self.assertIn("2021-01-01 document A.pdf", zipf.namelist())
|
||||
self.assertIn("2021-01-01 document A_01.pdf", zipf.namelist())
|
||||
|
||||
with self.doc2.source_file as f:
|
||||
self.assertEqual(f.read(), zipf.read("2021-01-01 document A.pdf"))
|
||||
|
||||
with self.doc2b.source_file as f:
|
||||
self.assertEqual(f.read(), zipf.read("2021-01-01 document A_01.pdf"))
|
||||
|
||||
def test_compression(self):
|
||||
response = self.client.post("/api/documents/bulk_download/", json.dumps({
|
||||
"documents": [self.doc2.id, self.doc2b.id],
|
||||
"compression": "lzma"
|
||||
}), content_type='application/json')
|
||||
|
||||
class TestApiAuth(APITestCase):
|
||||
|
||||
def test_auth_required(self):
|
||||
@@ -1057,7 +1417,20 @@ class TestApiAuth(APITestCase):
|
||||
self.assertEqual(self.client.get("/api/logs/").status_code, 401)
|
||||
self.assertEqual(self.client.get("/api/saved_views/").status_code, 401)
|
||||
|
||||
self.assertEqual(self.client.get("/api/search/").status_code, 401)
|
||||
self.assertEqual(self.client.get("/api/search/auto_complete/").status_code, 401)
|
||||
self.assertEqual(self.client.get("/api/search/autocomplete/").status_code, 401)
|
||||
self.assertEqual(self.client.get("/api/documents/bulk_edit/").status_code, 401)
|
||||
self.assertEqual(self.client.get("/api/documents/bulk_download/").status_code, 401)
|
||||
self.assertEqual(self.client.get("/api/documents/selection_data/").status_code, 401)
|
||||
|
||||
def test_api_version_no_auth(self):
|
||||
|
||||
response = self.client.get("/api/")
|
||||
self.assertNotIn("X-Api-Version", response)
|
||||
self.assertNotIn("X-Version", response)
|
||||
|
||||
def test_api_version_with_auth(self):
|
||||
user = User.objects.create_superuser(username="test")
|
||||
self.client.force_login(user)
|
||||
response = self.client.get("/api/")
|
||||
self.assertIn("X-Api-Version", response)
|
||||
self.assertIn("X-Version", response)
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import os
|
||||
import tempfile
|
||||
from time import sleep
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from django.conf import settings
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
from documents.classifier import DocumentClassifier, IncompatibleClassifierVersionError
|
||||
from documents.classifier import DocumentClassifier, IncompatibleClassifierVersionError, load_classifier
|
||||
from documents.models import Correspondent, Document, Tag, DocumentType
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
|
||||
@@ -82,37 +85,19 @@ class TestClassifier(DirectoriesMixin, TestCase):
|
||||
self.assertTrue(self.classifier.train())
|
||||
self.assertFalse(self.classifier.train())
|
||||
|
||||
self.classifier.save_classifier()
|
||||
self.classifier.save()
|
||||
|
||||
classifier2 = DocumentClassifier()
|
||||
|
||||
current_ver = DocumentClassifier.FORMAT_VERSION
|
||||
with mock.patch("documents.classifier.DocumentClassifier.FORMAT_VERSION", current_ver+1):
|
||||
# assure that we won't load old classifiers.
|
||||
self.assertRaises(IncompatibleClassifierVersionError, classifier2.reload)
|
||||
self.assertRaises(IncompatibleClassifierVersionError, classifier2.load)
|
||||
|
||||
self.classifier.save_classifier()
|
||||
self.classifier.save()
|
||||
|
||||
# assure that we can load the classifier after saving it.
|
||||
classifier2.reload()
|
||||
|
||||
def testReload(self):
|
||||
|
||||
self.generate_test_data()
|
||||
self.assertTrue(self.classifier.train())
|
||||
self.classifier.save_classifier()
|
||||
|
||||
classifier2 = DocumentClassifier()
|
||||
classifier2.reload()
|
||||
v1 = classifier2.classifier_version
|
||||
|
||||
# change the classifier after some time.
|
||||
sleep(1)
|
||||
self.classifier.save_classifier()
|
||||
|
||||
classifier2.reload()
|
||||
v2 = classifier2.classifier_version
|
||||
self.assertNotEqual(v1, v2)
|
||||
classifier2.load()
|
||||
|
||||
@override_settings(DATA_DIR=tempfile.mkdtemp())
|
||||
def testSaveClassifier(self):
|
||||
@@ -121,12 +106,21 @@ class TestClassifier(DirectoriesMixin, TestCase):
|
||||
|
||||
self.classifier.train()
|
||||
|
||||
self.classifier.save_classifier()
|
||||
self.classifier.save()
|
||||
|
||||
new_classifier = DocumentClassifier()
|
||||
new_classifier.reload()
|
||||
new_classifier.load()
|
||||
self.assertFalse(new_classifier.train())
|
||||
|
||||
@override_settings(MODEL_FILE=os.path.join(os.path.dirname(__file__), "data", "model.pickle"))
|
||||
def test_load_and_classify(self):
|
||||
self.generate_test_data()
|
||||
|
||||
new_classifier = DocumentClassifier()
|
||||
new_classifier.load()
|
||||
|
||||
self.assertCountEqual(new_classifier.predict_tags(self.doc2.content), [45, 12])
|
||||
|
||||
def test_one_correspondent_predict(self):
|
||||
c1 = Correspondent.objects.create(name="c1", matching_algorithm=Correspondent.MATCH_AUTO)
|
||||
doc1 = Document.objects.create(title="doc1", content="this is a document from c1", correspondent=c1, checksum="A")
|
||||
@@ -235,3 +229,42 @@ class TestClassifier(DirectoriesMixin, TestCase):
|
||||
self.classifier.train()
|
||||
self.assertListEqual(self.classifier.predict_tags(doc1.content), [t1.pk])
|
||||
self.assertListEqual(self.classifier.predict_tags(doc2.content), [])
|
||||
|
||||
def test_load_classifier_not_exists(self):
|
||||
self.assertFalse(os.path.exists(settings.MODEL_FILE))
|
||||
self.assertIsNone(load_classifier())
|
||||
|
||||
@mock.patch("documents.classifier.DocumentClassifier.load")
|
||||
def test_load_classifier(self, load):
|
||||
Path(settings.MODEL_FILE).touch()
|
||||
self.assertIsNotNone(load_classifier())
|
||||
load.assert_called_once()
|
||||
|
||||
@override_settings(CACHES={'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'}})
|
||||
@override_settings(MODEL_FILE=os.path.join(os.path.dirname(__file__), "data", "model.pickle"))
|
||||
@pytest.mark.skip(reason="Disabled caching due to high memory usage - need to investigate.")
|
||||
def test_load_classifier_cached(self):
|
||||
classifier = load_classifier()
|
||||
self.assertIsNotNone(classifier)
|
||||
|
||||
with mock.patch("documents.classifier.DocumentClassifier.load") as load:
|
||||
classifier2 = load_classifier()
|
||||
load.assert_not_called()
|
||||
|
||||
@mock.patch("documents.classifier.DocumentClassifier.load")
|
||||
def test_load_classifier_incompatible_version(self, load):
|
||||
Path(settings.MODEL_FILE).touch()
|
||||
self.assertTrue(os.path.exists(settings.MODEL_FILE))
|
||||
|
||||
load.side_effect = IncompatibleClassifierVersionError()
|
||||
self.assertIsNone(load_classifier())
|
||||
self.assertFalse(os.path.exists(settings.MODEL_FILE))
|
||||
|
||||
@mock.patch("documents.classifier.DocumentClassifier.load")
|
||||
def test_load_classifier_os_error(self, load):
|
||||
Path(settings.MODEL_FILE).touch()
|
||||
self.assertTrue(os.path.exists(settings.MODEL_FILE))
|
||||
|
||||
load.side_effect = OSError()
|
||||
self.assertIsNone(load_classifier())
|
||||
self.assertTrue(os.path.exists(settings.MODEL_FILE))
|
||||
|
||||
@@ -5,12 +5,14 @@ import tempfile
|
||||
from unittest import mock
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from django.conf import settings
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
from .utils import DirectoriesMixin
|
||||
from ..consumer import Consumer, ConsumerError
|
||||
from ..models import FileInfo, Tag, Correspondent, DocumentType, Document
|
||||
from ..parsers import DocumentParser, ParseError
|
||||
from ..tasks import sanity_check
|
||||
|
||||
|
||||
class TestAttributes(TestCase):
|
||||
@@ -165,25 +167,43 @@ class TestFieldPermutations(TestCase):
|
||||
|
||||
class DummyParser(DocumentParser):
|
||||
|
||||
def get_thumbnail(self, document_path, mime_type):
|
||||
def get_thumbnail(self, document_path, mime_type, file_name=None):
|
||||
# not important during tests
|
||||
raise NotImplementedError()
|
||||
|
||||
def __init__(self, logging_group, scratch_dir, archive_path):
|
||||
super(DummyParser, self).__init__(logging_group)
|
||||
super(DummyParser, self).__init__(logging_group, None)
|
||||
_, self.fake_thumb = tempfile.mkstemp(suffix=".png", dir=scratch_dir)
|
||||
self.archive_path = archive_path
|
||||
|
||||
def get_optimised_thumbnail(self, document_path, mime_type):
|
||||
def get_optimised_thumbnail(self, document_path, mime_type, file_name=None):
|
||||
return self.fake_thumb
|
||||
|
||||
def parse(self, document_path, mime_type, file_name=None):
|
||||
self.text = "The Text"
|
||||
|
||||
|
||||
class CopyParser(DocumentParser):
|
||||
|
||||
def get_thumbnail(self, document_path, mime_type, file_name=None):
|
||||
return self.fake_thumb
|
||||
|
||||
def get_optimised_thumbnail(self, document_path, mime_type, file_name=None):
|
||||
return self.fake_thumb
|
||||
|
||||
def __init__(self, logging_group, progress_callback=None):
|
||||
super(CopyParser, self).__init__(logging_group, progress_callback)
|
||||
_, self.fake_thumb = tempfile.mkstemp(suffix=".png", dir=self.tempdir)
|
||||
|
||||
def parse(self, document_path, mime_type, file_name=None):
|
||||
self.text = "The text"
|
||||
self.archive_path = os.path.join(self.tempdir, "archive.pdf")
|
||||
shutil.copy(document_path, self.archive_path)
|
||||
|
||||
|
||||
class FaultyParser(DocumentParser):
|
||||
|
||||
def get_thumbnail(self, document_path, mime_type):
|
||||
def get_thumbnail(self, document_path, mime_type, file_name=None):
|
||||
# not important during tests
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -191,7 +211,7 @@ class FaultyParser(DocumentParser):
|
||||
super(FaultyParser, self).__init__(logging_group)
|
||||
_, self.fake_thumb = tempfile.mkstemp(suffix=".png", dir=scratch_dir)
|
||||
|
||||
def get_optimised_thumbnail(self, document_path, mime_type):
|
||||
def get_optimised_thumbnail(self, document_path, mime_type, file_name=None):
|
||||
return self.fake_thumb
|
||||
|
||||
def parse(self, document_path, mime_type, file_name=None):
|
||||
@@ -203,6 +223,8 @@ def fake_magic_from_file(file, mime=False):
|
||||
if mime:
|
||||
if os.path.splitext(file)[1] == ".pdf":
|
||||
return "application/pdf"
|
||||
elif os.path.splitext(file)[1] == ".png":
|
||||
return "image/png"
|
||||
else:
|
||||
return "unknown"
|
||||
else:
|
||||
@@ -212,10 +234,24 @@ def fake_magic_from_file(file, mime=False):
|
||||
@mock.patch("documents.consumer.magic.from_file", fake_magic_from_file)
|
||||
class TestConsumer(DirectoriesMixin, TestCase):
|
||||
|
||||
def make_dummy_parser(self, logging_group):
|
||||
def _assert_first_last_send_progress(self, first_status="STARTING", last_status="SUCCESS", first_progress=0, first_progress_max=100, last_progress=100, last_progress_max=100):
|
||||
|
||||
self._send_progress.assert_called()
|
||||
|
||||
args, kwargs = self._send_progress.call_args_list[0]
|
||||
self.assertEqual(args[0], first_progress)
|
||||
self.assertEqual(args[1], first_progress_max)
|
||||
self.assertEqual(args[2], first_status)
|
||||
|
||||
args, kwargs = self._send_progress.call_args_list[len(self._send_progress.call_args_list) - 1]
|
||||
self.assertEqual(args[0], last_progress)
|
||||
self.assertEqual(args[1], last_progress_max)
|
||||
self.assertEqual(args[2], last_status)
|
||||
|
||||
def make_dummy_parser(self, logging_group, progress_callback=None):
|
||||
return DummyParser(logging_group, self.dirs.scratch_dir, self.get_test_archive_file())
|
||||
|
||||
def make_faulty_parser(self, logging_group):
|
||||
def make_faulty_parser(self, logging_group, progress_callback=None):
|
||||
return FaultyParser(logging_group, self.dirs.scratch_dir)
|
||||
|
||||
def setUp(self):
|
||||
@@ -228,7 +264,11 @@ class TestConsumer(DirectoriesMixin, TestCase):
|
||||
"mime_types": {"application/pdf": ".pdf"},
|
||||
"weight": 0
|
||||
})]
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
# this prevents websocket message reports during testing.
|
||||
patcher = mock.patch("documents.consumer.Consumer._send_progress")
|
||||
self._send_progress = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
self.consumer = Consumer()
|
||||
@@ -256,6 +296,7 @@ class TestConsumer(DirectoriesMixin, TestCase):
|
||||
self.assertIsNone(document.correspondent)
|
||||
self.assertIsNone(document.document_type)
|
||||
self.assertEqual(document.filename, "0000001.pdf")
|
||||
self.assertEqual(document.archive_filename, "0000001.pdf")
|
||||
|
||||
self.assertTrue(os.path.isfile(
|
||||
document.source_path
|
||||
@@ -274,6 +315,29 @@ class TestConsumer(DirectoriesMixin, TestCase):
|
||||
|
||||
self.assertFalse(os.path.isfile(filename))
|
||||
|
||||
self._assert_first_last_send_progress()
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT=None)
|
||||
def testDeleteMacFiles(self):
|
||||
# https://github.com/jonaswinkler/paperless-ng/discussions/1037
|
||||
|
||||
filename = self.get_test_file()
|
||||
shadow_file = os.path.join(self.dirs.scratch_dir, "._sample.pdf")
|
||||
|
||||
shutil.copy(filename, shadow_file)
|
||||
|
||||
self.assertTrue(os.path.isfile(shadow_file))
|
||||
|
||||
document = self.consumer.try_consume_file(filename)
|
||||
|
||||
self.assertTrue(os.path.isfile(
|
||||
document.source_path
|
||||
))
|
||||
|
||||
self.assertFalse(os.path.isfile(shadow_file))
|
||||
self.assertFalse(os.path.isfile(filename))
|
||||
|
||||
|
||||
def testOverrideFilename(self):
|
||||
filename = self.get_test_file()
|
||||
override_filename = "Statement for November.pdf"
|
||||
@@ -282,21 +346,26 @@ class TestConsumer(DirectoriesMixin, TestCase):
|
||||
|
||||
self.assertEqual(document.title, "Statement for November")
|
||||
|
||||
self._assert_first_last_send_progress()
|
||||
|
||||
def testOverrideTitle(self):
|
||||
document = self.consumer.try_consume_file(self.get_test_file(), override_title="Override Title")
|
||||
self.assertEqual(document.title, "Override Title")
|
||||
self._assert_first_last_send_progress()
|
||||
|
||||
def testOverrideCorrespondent(self):
|
||||
c = Correspondent.objects.create(name="test")
|
||||
|
||||
document = self.consumer.try_consume_file(self.get_test_file(), override_correspondent_id=c.pk)
|
||||
self.assertEqual(document.correspondent.id, c.id)
|
||||
self._assert_first_last_send_progress()
|
||||
|
||||
def testOverrideDocumentType(self):
|
||||
dt = DocumentType.objects.create(name="test")
|
||||
|
||||
document = self.consumer.try_consume_file(self.get_test_file(), override_document_type_id=dt.pk)
|
||||
self.assertEqual(document.document_type.id, dt.id)
|
||||
self._assert_first_last_send_progress()
|
||||
|
||||
def testOverrideTags(self):
|
||||
t1 = Tag.objects.create(name="t1")
|
||||
@@ -307,37 +376,42 @@ class TestConsumer(DirectoriesMixin, TestCase):
|
||||
self.assertIn(t1, document.tags.all())
|
||||
self.assertNotIn(t2, document.tags.all())
|
||||
self.assertIn(t3, document.tags.all())
|
||||
self._assert_first_last_send_progress()
|
||||
|
||||
def testNotAFile(self):
|
||||
try:
|
||||
self.consumer.try_consume_file("non-existing-file")
|
||||
except ConsumerError as e:
|
||||
self.assertTrue(str(e).endswith('It is not a file'))
|
||||
return
|
||||
|
||||
self.fail("Should throw exception")
|
||||
self.assertRaisesMessage(
|
||||
ConsumerError,
|
||||
"File not found",
|
||||
self.consumer.try_consume_file,
|
||||
"non-existing-file"
|
||||
)
|
||||
|
||||
self._assert_first_last_send_progress(last_status="FAILED")
|
||||
|
||||
def testDuplicates1(self):
|
||||
self.consumer.try_consume_file(self.get_test_file())
|
||||
|
||||
try:
|
||||
self.consumer.try_consume_file(self.get_test_file())
|
||||
except ConsumerError as e:
|
||||
self.assertTrue(str(e).endswith("It is a duplicate."))
|
||||
return
|
||||
self.assertRaisesMessage(
|
||||
ConsumerError,
|
||||
"It is a duplicate",
|
||||
self.consumer.try_consume_file,
|
||||
self.get_test_file()
|
||||
)
|
||||
|
||||
self.fail("Should throw exception")
|
||||
self._assert_first_last_send_progress(last_status="FAILED")
|
||||
|
||||
def testDuplicates2(self):
|
||||
self.consumer.try_consume_file(self.get_test_file())
|
||||
|
||||
try:
|
||||
self.consumer.try_consume_file(self.get_test_archive_file())
|
||||
except ConsumerError as e:
|
||||
self.assertTrue(str(e).endswith("It is a duplicate."))
|
||||
return
|
||||
self.assertRaisesMessage(
|
||||
ConsumerError,
|
||||
"It is a duplicate",
|
||||
self.consumer.try_consume_file,
|
||||
self.get_test_archive_file()
|
||||
)
|
||||
|
||||
self.fail("Should throw exception")
|
||||
self._assert_first_last_send_progress(last_status="FAILED")
|
||||
|
||||
def testDuplicates3(self):
|
||||
self.consumer.try_consume_file(self.get_test_archive_file())
|
||||
@@ -347,13 +421,15 @@ class TestConsumer(DirectoriesMixin, TestCase):
|
||||
def testNoParsers(self, m):
|
||||
m.return_value = []
|
||||
|
||||
try:
|
||||
self.consumer.try_consume_file(self.get_test_file())
|
||||
except ConsumerError as e:
|
||||
self.assertEqual("Unsupported mime type application/pdf of file sample.pdf", str(e))
|
||||
return
|
||||
self.assertRaisesMessage(
|
||||
ConsumerError,
|
||||
"sample.pdf: Unsupported mime type application/pdf",
|
||||
self.consumer.try_consume_file,
|
||||
self.get_test_file()
|
||||
)
|
||||
|
||||
self._assert_first_last_send_progress(last_status="FAILED")
|
||||
|
||||
self.fail("Should throw exception")
|
||||
|
||||
@mock.patch("documents.parsers.document_consumer_declaration.send")
|
||||
def testFaultyParser(self, m):
|
||||
@@ -363,24 +439,28 @@ class TestConsumer(DirectoriesMixin, TestCase):
|
||||
"weight": 0
|
||||
})]
|
||||
|
||||
try:
|
||||
self.consumer.try_consume_file(self.get_test_file())
|
||||
except ConsumerError as e:
|
||||
self.assertEqual(str(e), "Does not compute.")
|
||||
return
|
||||
self.assertRaisesMessage(
|
||||
ConsumerError,
|
||||
"sample.pdf: Error while consuming document sample.pdf: Does not compute.",
|
||||
self.consumer.try_consume_file,
|
||||
self.get_test_file()
|
||||
)
|
||||
|
||||
self.fail("Should throw exception.")
|
||||
self._assert_first_last_send_progress(last_status="FAILED")
|
||||
|
||||
@mock.patch("documents.consumer.Consumer._write")
|
||||
def testPostSaveError(self, m):
|
||||
filename = self.get_test_file()
|
||||
m.side_effect = OSError("NO.")
|
||||
try:
|
||||
self.consumer.try_consume_file(filename)
|
||||
except ConsumerError as e:
|
||||
self.assertEqual(str(e), "NO.")
|
||||
else:
|
||||
self.fail("Should raise exception")
|
||||
|
||||
self.assertRaisesMessage(
|
||||
ConsumerError,
|
||||
"sample.pdf: The following error occured while consuming sample.pdf: NO.",
|
||||
self.consumer.try_consume_file,
|
||||
filename
|
||||
)
|
||||
|
||||
self._assert_first_last_send_progress(last_status="FAILED")
|
||||
|
||||
# file not deleted
|
||||
self.assertTrue(os.path.isfile(filename))
|
||||
@@ -396,6 +476,9 @@ class TestConsumer(DirectoriesMixin, TestCase):
|
||||
|
||||
self.assertEqual(document.title, "new docs")
|
||||
self.assertEqual(document.filename, "none/new docs.pdf")
|
||||
self.assertEqual(document.archive_filename, "none/new docs.pdf")
|
||||
|
||||
self._assert_first_last_send_progress()
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="{correspondent}/{title}")
|
||||
@mock.patch("documents.signals.handlers.generate_unique_filename")
|
||||
@@ -408,7 +491,7 @@ class TestConsumer(DirectoriesMixin, TestCase):
|
||||
filenames.insert(0, f)
|
||||
return f
|
||||
|
||||
m.side_effect = lambda f, root: get_filename()
|
||||
m.side_effect = lambda f, archive_filename = False: get_filename()
|
||||
|
||||
filename = self.get_test_file()
|
||||
|
||||
@@ -419,8 +502,11 @@ class TestConsumer(DirectoriesMixin, TestCase):
|
||||
self.assertEqual(document.title, "new docs")
|
||||
self.assertIsNotNone(os.path.isfile(document.title))
|
||||
self.assertTrue(os.path.isfile(document.source_path))
|
||||
self.assertTrue(os.path.isfile(document.archive_path))
|
||||
|
||||
@mock.patch("documents.consumer.DocumentClassifier")
|
||||
self._assert_first_last_send_progress()
|
||||
|
||||
@mock.patch("documents.consumer.load_classifier")
|
||||
def testClassifyDocument(self, m):
|
||||
correspondent = Correspondent.objects.create(name="test")
|
||||
dtype = DocumentType.objects.create(name="test")
|
||||
@@ -439,19 +525,26 @@ class TestConsumer(DirectoriesMixin, TestCase):
|
||||
self.assertIn(t1, document.tags.all())
|
||||
self.assertNotIn(t2, document.tags.all())
|
||||
|
||||
self._assert_first_last_send_progress()
|
||||
|
||||
@override_settings(CONSUMER_DELETE_DUPLICATES=True)
|
||||
def test_delete_duplicate(self):
|
||||
dst = self.get_test_file()
|
||||
self.assertTrue(os.path.isfile(dst))
|
||||
doc = self.consumer.try_consume_file(dst)
|
||||
|
||||
self._assert_first_last_send_progress()
|
||||
|
||||
self.assertFalse(os.path.isfile(dst))
|
||||
self.assertIsNotNone(doc)
|
||||
|
||||
self._send_progress.reset_mock()
|
||||
|
||||
dst = self.get_test_file()
|
||||
self.assertTrue(os.path.isfile(dst))
|
||||
self.assertRaises(ConsumerError, self.consumer.try_consume_file, dst)
|
||||
self.assertFalse(os.path.isfile(dst))
|
||||
self._assert_first_last_send_progress(last_status="FAILED")
|
||||
|
||||
@override_settings(CONSUMER_DELETE_DUPLICATES=False)
|
||||
def test_no_delete_duplicate(self):
|
||||
@@ -467,6 +560,32 @@ class TestConsumer(DirectoriesMixin, TestCase):
|
||||
self.assertRaises(ConsumerError, self.consumer.try_consume_file, dst)
|
||||
self.assertTrue(os.path.isfile(dst))
|
||||
|
||||
self._assert_first_last_send_progress(last_status="FAILED")
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="{title}")
|
||||
@mock.patch("documents.parsers.document_consumer_declaration.send")
|
||||
def test_similar_filenames(self, m):
|
||||
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), os.path.join(settings.CONSUMPTION_DIR, "simple.pdf"))
|
||||
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "simple.png"), os.path.join(settings.CONSUMPTION_DIR, "simple.png"))
|
||||
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "simple-noalpha.png"), os.path.join(settings.CONSUMPTION_DIR, "simple.png.pdf"))
|
||||
m.return_value = [(None, {
|
||||
"parser": CopyParser,
|
||||
"mime_types": {"application/pdf": ".pdf", "image/png": ".png"},
|
||||
"weight": 0
|
||||
})]
|
||||
doc1 = self.consumer.try_consume_file(os.path.join(settings.CONSUMPTION_DIR, "simple.png"))
|
||||
doc2 = self.consumer.try_consume_file(os.path.join(settings.CONSUMPTION_DIR, "simple.pdf"))
|
||||
doc3 = self.consumer.try_consume_file(os.path.join(settings.CONSUMPTION_DIR, "simple.png.pdf"))
|
||||
|
||||
self.assertEqual(doc1.filename, "simple.png")
|
||||
self.assertEqual(doc1.archive_filename, "simple.pdf")
|
||||
self.assertEqual(doc2.filename, "simple.pdf")
|
||||
self.assertEqual(doc2.archive_filename, "simple_01.pdf")
|
||||
self.assertEqual(doc3.filename, "simple.png.pdf")
|
||||
self.assertEqual(doc3.archive_filename, "simple.png.pdf")
|
||||
|
||||
sanity_check()
|
||||
|
||||
|
||||
class PreConsumeTestCase(TestCase):
|
||||
|
||||
@@ -479,9 +598,11 @@ class PreConsumeTestCase(TestCase):
|
||||
m.assert_not_called()
|
||||
|
||||
@mock.patch("documents.consumer.Popen")
|
||||
@mock.patch("documents.consumer.Consumer._send_progress")
|
||||
@override_settings(PRE_CONSUME_SCRIPT="does-not-exist")
|
||||
def test_pre_consume_script_not_found(self, m):
|
||||
def test_pre_consume_script_not_found(self, m, m2):
|
||||
c = Consumer()
|
||||
c.filename = "somefile.pdf"
|
||||
c.path = "path-to-file"
|
||||
self.assertRaises(ConsumerError, c.run_pre_consume_script)
|
||||
|
||||
@@ -503,7 +624,6 @@ class PreConsumeTestCase(TestCase):
|
||||
self.assertEqual(command[1], "path-to-file")
|
||||
|
||||
|
||||
|
||||
class PostConsumeTestCase(TestCase):
|
||||
|
||||
@mock.patch("documents.consumer.Popen")
|
||||
@@ -519,12 +639,13 @@ class PostConsumeTestCase(TestCase):
|
||||
|
||||
m.assert_not_called()
|
||||
|
||||
|
||||
@override_settings(POST_CONSUME_SCRIPT="does-not-exist")
|
||||
def test_post_consume_script_not_found(self):
|
||||
@mock.patch("documents.consumer.Consumer._send_progress")
|
||||
def test_post_consume_script_not_found(self, m):
|
||||
doc = Document.objects.create(title="Test", mime_type="application/pdf")
|
||||
|
||||
self.assertRaises(ConsumerError, Consumer().run_post_consume_script, doc)
|
||||
c = Consumer()
|
||||
c.filename = "somefile.pdf"
|
||||
self.assertRaises(ConsumerError, c.run_post_consume_script, doc)
|
||||
|
||||
@mock.patch("documents.consumer.Popen")
|
||||
def test_post_consume_script_simple(self, m):
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import datetime
|
||||
import os
|
||||
import shutil
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
from dateutil import tz
|
||||
@@ -9,7 +8,6 @@ from django.conf import settings
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
from documents.parsers import parse_date
|
||||
from paperless_tesseract.parsers import RasterisedDocumentParser
|
||||
|
||||
|
||||
class TestDate(TestCase):
|
||||
@@ -152,4 +150,4 @@ class TestDate(TestCase):
|
||||
2018, 2, 13, 0, 0,
|
||||
tzinfo=tz.gettz(settings.TIME_ZONE)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -201,6 +201,13 @@ class TestFileHandling(DirectoriesMixin, TestCase):
|
||||
|
||||
self.assertEqual(generate_filename(d), "my_doc_type - the_doc.pdf")
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="{asn} - {title}")
|
||||
def test_asn(self):
|
||||
d1 = Document.objects.create(title="the_doc", mime_type="application/pdf", archive_serial_number=652, checksum="A")
|
||||
d2 = Document.objects.create(title="the_doc", mime_type="application/pdf", archive_serial_number=None, checksum="B")
|
||||
self.assertEqual(generate_filename(d1), "652 - the_doc.pdf")
|
||||
self.assertEqual(generate_filename(d2), "none - the_doc.pdf")
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="{tags[type]}")
|
||||
def test_tags_with_underscore(self):
|
||||
document = Document()
|
||||
@@ -439,6 +446,18 @@ class TestFileHandling(DirectoriesMixin, TestCase):
|
||||
self.assertEqual(document2.filename, "qwe.pdf")
|
||||
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="{title}")
|
||||
@mock.patch("documents.signals.handlers.Document.objects.filter")
|
||||
def test_no_update_without_change(self, m):
|
||||
doc = Document.objects.create(title="document", filename="document.pdf", archive_filename="document.pdf", checksum="A", archive_checksum="B", mime_type="application/pdf")
|
||||
Path(doc.source_path).touch()
|
||||
Path(doc.archive_path).touch()
|
||||
|
||||
doc.save()
|
||||
|
||||
m.assert_not_called()
|
||||
|
||||
|
||||
|
||||
class TestFileHandlingWithArchive(DirectoriesMixin, TestCase):
|
||||
|
||||
@@ -448,7 +467,7 @@ class TestFileHandlingWithArchive(DirectoriesMixin, TestCase):
|
||||
archive = os.path.join(settings.ARCHIVE_DIR, "0000001.pdf")
|
||||
Path(original).touch()
|
||||
Path(archive).touch()
|
||||
doc = Document.objects.create(mime_type="application/pdf", filename="0000001.pdf", checksum="A", archive_checksum="B")
|
||||
doc = Document.objects.create(mime_type="application/pdf", filename="0000001.pdf", checksum="A", archive_filename="0000001.pdf", archive_checksum="B")
|
||||
|
||||
self.assertTrue(os.path.isfile(original))
|
||||
self.assertTrue(os.path.isfile(archive))
|
||||
@@ -461,7 +480,7 @@ class TestFileHandlingWithArchive(DirectoriesMixin, TestCase):
|
||||
archive = os.path.join(settings.ARCHIVE_DIR, "0000001.pdf")
|
||||
Path(original).touch()
|
||||
Path(archive).touch()
|
||||
doc = Document.objects.create(mime_type="application/pdf", title="my_doc", filename="0000001.pdf", checksum="A", archive_checksum="B")
|
||||
doc = Document.objects.create(mime_type="application/pdf", title="my_doc", filename="0000001.pdf", checksum="A", archive_checksum="B", archive_filename="0000001.pdf")
|
||||
|
||||
self.assertFalse(os.path.isfile(original))
|
||||
self.assertFalse(os.path.isfile(archive))
|
||||
@@ -475,7 +494,7 @@ class TestFileHandlingWithArchive(DirectoriesMixin, TestCase):
|
||||
original = os.path.join(settings.ORIGINALS_DIR, "0000001.pdf")
|
||||
archive = os.path.join(settings.ARCHIVE_DIR, "0000001.pdf")
|
||||
Path(original).touch()
|
||||
doc = Document.objects.create(mime_type="application/pdf", title="my_doc", filename="0000001.pdf", checksum="A", archive_checksum="B")
|
||||
doc = Document.objects.create(mime_type="application/pdf", title="my_doc", filename="0000001.pdf", checksum="A", archive_checksum="B", archive_filename="0000001.pdf")
|
||||
|
||||
self.assertTrue(os.path.isfile(original))
|
||||
self.assertFalse(os.path.isfile(archive))
|
||||
@@ -486,14 +505,49 @@ class TestFileHandlingWithArchive(DirectoriesMixin, TestCase):
|
||||
def test_move_archive_exists(self):
|
||||
original = os.path.join(settings.ORIGINALS_DIR, "0000001.pdf")
|
||||
archive = os.path.join(settings.ARCHIVE_DIR, "0000001.pdf")
|
||||
existing_archive_file = os.path.join(settings.ARCHIVE_DIR, "none", "my_doc.pdf")
|
||||
Path(original).touch()
|
||||
Path(archive).touch()
|
||||
os.makedirs(os.path.join(settings.ARCHIVE_DIR, "none"))
|
||||
Path(os.path.join(settings.ARCHIVE_DIR, "none", "my_doc.pdf")).touch()
|
||||
doc = Document.objects.create(mime_type="application/pdf", title="my_doc", filename="0000001.pdf", checksum="A", archive_checksum="B")
|
||||
Path(existing_archive_file).touch()
|
||||
doc = Document.objects.create(mime_type="application/pdf", title="my_doc", filename="0000001.pdf", checksum="A", archive_checksum="B", archive_filename="0000001.pdf")
|
||||
|
||||
self.assertFalse(os.path.isfile(original))
|
||||
self.assertFalse(os.path.isfile(archive))
|
||||
self.assertTrue(os.path.isfile(doc.source_path))
|
||||
self.assertTrue(os.path.isfile(doc.archive_path))
|
||||
self.assertTrue(os.path.isfile(existing_archive_file))
|
||||
self.assertEqual(doc.archive_filename, "none/my_doc_01.pdf")
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="{title}")
|
||||
def test_move_original_only(self):
|
||||
original = os.path.join(settings.ORIGINALS_DIR, "document_01.pdf")
|
||||
archive = os.path.join(settings.ARCHIVE_DIR, "document.pdf")
|
||||
Path(original).touch()
|
||||
Path(archive).touch()
|
||||
|
||||
doc = Document.objects.create(mime_type="application/pdf", title="document", filename="document_01.pdf", checksum="A",
|
||||
archive_checksum="B", archive_filename="document.pdf")
|
||||
|
||||
self.assertEqual(doc.filename, "document.pdf")
|
||||
self.assertEqual(doc.archive_filename, "document.pdf")
|
||||
|
||||
self.assertTrue(os.path.isfile(doc.source_path))
|
||||
self.assertTrue(os.path.isfile(doc.archive_path))
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="{title}")
|
||||
def test_move_archive_only(self):
|
||||
original = os.path.join(settings.ORIGINALS_DIR, "document.pdf")
|
||||
archive = os.path.join(settings.ARCHIVE_DIR, "document_01.pdf")
|
||||
Path(original).touch()
|
||||
Path(archive).touch()
|
||||
|
||||
doc = Document.objects.create(mime_type="application/pdf", title="document", filename="document.pdf", checksum="A",
|
||||
archive_checksum="B", archive_filename="document_01.pdf")
|
||||
|
||||
self.assertEqual(doc.filename, "document.pdf")
|
||||
self.assertEqual(doc.archive_filename, "document.pdf")
|
||||
|
||||
self.assertTrue(os.path.isfile(original))
|
||||
self.assertTrue(os.path.isfile(archive))
|
||||
self.assertTrue(os.path.isfile(doc.source_path))
|
||||
self.assertTrue(os.path.isfile(doc.archive_path))
|
||||
|
||||
@@ -514,8 +568,9 @@ class TestFileHandlingWithArchive(DirectoriesMixin, TestCase):
|
||||
archive = os.path.join(settings.ARCHIVE_DIR, "0000001.pdf")
|
||||
Path(original).touch()
|
||||
Path(archive).touch()
|
||||
doc = Document.objects.create(mime_type="application/pdf", title="my_doc", filename="0000001.pdf", checksum="A", archive_checksum="B")
|
||||
doc = Document.objects.create(mime_type="application/pdf", title="my_doc", filename="0000001.pdf", checksum="A", archive_checksum="B", archive_filename="0000001.pdf")
|
||||
|
||||
m.assert_called()
|
||||
self.assertTrue(os.path.isfile(original))
|
||||
self.assertTrue(os.path.isfile(archive))
|
||||
self.assertTrue(os.path.isfile(doc.source_path))
|
||||
@@ -527,7 +582,7 @@ class TestFileHandlingWithArchive(DirectoriesMixin, TestCase):
|
||||
archive = os.path.join(settings.ARCHIVE_DIR, "0000001.pdf")
|
||||
#Path(original).touch()
|
||||
Path(archive).touch()
|
||||
doc = Document.objects.create(mime_type="application/pdf", title="my_doc", filename="0000001.pdf", checksum="A", archive_checksum="B")
|
||||
doc = Document.objects.create(mime_type="application/pdf", title="my_doc", filename="0000001.pdf", archive_filename="0000001.pdf", checksum="A", archive_checksum="B")
|
||||
|
||||
self.assertFalse(os.path.isfile(original))
|
||||
self.assertTrue(os.path.isfile(archive))
|
||||
@@ -551,19 +606,21 @@ class TestFileHandlingWithArchive(DirectoriesMixin, TestCase):
|
||||
archive = os.path.join(settings.ARCHIVE_DIR, "0000001.pdf")
|
||||
Path(original).touch()
|
||||
Path(archive).touch()
|
||||
doc = Document.objects.create(mime_type="application/pdf", title="my_doc", filename="0000001.pdf", checksum="A", archive_checksum="B")
|
||||
doc = Document.objects.create(mime_type="application/pdf", title="my_doc", filename="0000001.pdf", archive_filename="0000001.pdf", checksum="A", archive_checksum="B")
|
||||
|
||||
m.assert_called()
|
||||
self.assertTrue(os.path.isfile(original))
|
||||
self.assertTrue(os.path.isfile(archive))
|
||||
self.assertTrue(os.path.isfile(doc.source_path))
|
||||
self.assertTrue(os.path.isfile(doc.archive_path))
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="")
|
||||
def test_archive_deleted(self):
|
||||
original = os.path.join(settings.ORIGINALS_DIR, "0000001.pdf")
|
||||
archive = os.path.join(settings.ARCHIVE_DIR, "0000001.pdf")
|
||||
Path(original).touch()
|
||||
Path(archive).touch()
|
||||
doc = Document.objects.create(mime_type="application/pdf", title="my_doc", filename="0000001.pdf", checksum="A", archive_checksum="B")
|
||||
doc = Document.objects.create(mime_type="application/pdf", title="my_doc", filename="0000001.pdf", checksum="A", archive_checksum="B", archive_filename="0000001.pdf")
|
||||
|
||||
self.assertTrue(os.path.isfile(original))
|
||||
self.assertTrue(os.path.isfile(archive))
|
||||
@@ -577,6 +634,28 @@ class TestFileHandlingWithArchive(DirectoriesMixin, TestCase):
|
||||
self.assertFalse(os.path.isfile(doc.source_path))
|
||||
self.assertFalse(os.path.isfile(doc.archive_path))
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="{title}")
|
||||
def test_archive_deleted2(self):
|
||||
original = os.path.join(settings.ORIGINALS_DIR, "document.png")
|
||||
original2 = os.path.join(settings.ORIGINALS_DIR, "0000001.pdf")
|
||||
archive = os.path.join(settings.ARCHIVE_DIR, "0000001.pdf")
|
||||
Path(original).touch()
|
||||
Path(original2).touch()
|
||||
Path(archive).touch()
|
||||
|
||||
doc1 = Document.objects.create(mime_type="image/png", title="document", filename="document.png", checksum="A", archive_checksum="B", archive_filename="0000001.pdf")
|
||||
doc2 = Document.objects.create(mime_type="application/pdf", title="0000001", filename="0000001.pdf", checksum="C")
|
||||
|
||||
self.assertTrue(os.path.isfile(doc1.source_path))
|
||||
self.assertTrue(os.path.isfile(doc1.archive_path))
|
||||
self.assertTrue(os.path.isfile(doc2.source_path))
|
||||
|
||||
doc2.delete()
|
||||
|
||||
self.assertTrue(os.path.isfile(doc1.source_path))
|
||||
self.assertTrue(os.path.isfile(doc1.archive_path))
|
||||
self.assertFalse(os.path.isfile(doc2.source_path))
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="{correspondent}/{title}")
|
||||
def test_database_error(self):
|
||||
|
||||
@@ -584,7 +663,7 @@ class TestFileHandlingWithArchive(DirectoriesMixin, TestCase):
|
||||
archive = os.path.join(settings.ARCHIVE_DIR, "0000001.pdf")
|
||||
Path(original).touch()
|
||||
Path(archive).touch()
|
||||
doc = Document(mime_type="application/pdf", title="my_doc", filename="0000001.pdf", checksum="A", archive_checksum="B")
|
||||
doc = Document(mime_type="application/pdf", title="my_doc", filename="0000001.pdf", checksum="A", archive_filename="0000001.pdf", archive_checksum="B")
|
||||
with mock.patch("documents.signals.handlers.Document.objects.filter") as m:
|
||||
m.side_effect = DatabaseError()
|
||||
doc.save()
|
||||
@@ -594,6 +673,7 @@ class TestFileHandlingWithArchive(DirectoriesMixin, TestCase):
|
||||
self.assertTrue(os.path.isfile(doc.source_path))
|
||||
self.assertTrue(os.path.isfile(doc.archive_path))
|
||||
|
||||
|
||||
class TestFilenameGeneration(TestCase):
|
||||
|
||||
@override_settings(
|
||||
@@ -617,7 +697,7 @@ class TestFilenameGeneration(TestCase):
|
||||
|
||||
def run():
|
||||
doc = Document.objects.create(checksum=str(uuid.uuid4()), title=str(uuid.uuid4()), content="wow")
|
||||
doc.filename = generate_unique_filename(doc, settings.ORIGINALS_DIR)
|
||||
doc.filename = generate_unique_filename(doc)
|
||||
Path(doc.thumbnail_path).touch()
|
||||
with open(doc.source_path, "w") as f:
|
||||
f.write(str(uuid.uuid4()))
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from documents import index
|
||||
from documents.index import JsonFormatter
|
||||
from documents.models import Document
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
|
||||
|
||||
class JsonFormatterTest(TestCase):
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.formatter = JsonFormatter()
|
||||
|
||||
def test_empty_fragments(self):
|
||||
self.assertListEqual(self.formatter.format([]), [])
|
||||
|
||||
|
||||
class TestAutoComplete(DirectoriesMixin, TestCase):
|
||||
|
||||
def test_auto_complete(self):
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import logging
|
||||
import uuid
|
||||
from unittest import mock
|
||||
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
from ..models import Log
|
||||
|
||||
|
||||
class TestPaperlessLog(TestCase):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
TestCase.__init__(self, *args, **kwargs)
|
||||
self.logger = logging.getLogger(
|
||||
"documents.management.commands.document_consumer")
|
||||
|
||||
@override_settings(DISABLE_DBHANDLER=False)
|
||||
def test_that_it_saves_at_all(self):
|
||||
|
||||
kw = {"group": uuid.uuid4()}
|
||||
|
||||
self.assertEqual(Log.objects.all().count(), 0)
|
||||
|
||||
with mock.patch("logging.StreamHandler.emit") as __:
|
||||
|
||||
# Debug messages are ignored by default
|
||||
self.logger.debug("This is a debugging message", extra=kw)
|
||||
self.assertEqual(Log.objects.all().count(), 1)
|
||||
|
||||
self.logger.info("This is an informational message", extra=kw)
|
||||
self.assertEqual(Log.objects.all().count(), 2)
|
||||
|
||||
self.logger.warning("This is an warning message", extra=kw)
|
||||
self.assertEqual(Log.objects.all().count(), 3)
|
||||
|
||||
self.logger.error("This is an error message", extra=kw)
|
||||
self.assertEqual(Log.objects.all().count(), 4)
|
||||
|
||||
self.logger.critical("This is a critical message", extra=kw)
|
||||
self.assertEqual(Log.objects.all().count(), 5)
|
||||
|
||||
@override_settings(DISABLE_DBHANDLER=False)
|
||||
def test_groups(self):
|
||||
|
||||
kw1 = {"group": uuid.uuid4()}
|
||||
kw2 = {"group": uuid.uuid4()}
|
||||
|
||||
self.assertEqual(Log.objects.all().count(), 0)
|
||||
|
||||
with mock.patch("logging.StreamHandler.emit") as __:
|
||||
|
||||
self.logger.info("This is an informational message", extra=kw2)
|
||||
self.assertEqual(Log.objects.all().count(), 1)
|
||||
self.assertEqual(Log.objects.filter(group=kw2["group"]).count(), 1)
|
||||
|
||||
self.logger.warning("This is an warning message", extra=kw1)
|
||||
self.assertEqual(Log.objects.all().count(), 2)
|
||||
self.assertEqual(Log.objects.filter(group=kw1["group"]).count(), 1)
|
||||
|
||||
self.logger.error("This is an error message", extra=kw2)
|
||||
self.assertEqual(Log.objects.all().count(), 3)
|
||||
self.assertEqual(Log.objects.filter(group=kw2["group"]).count(), 2)
|
||||
|
||||
self.logger.critical("This is a critical message", extra=kw1)
|
||||
self.assertEqual(Log.objects.all().count(), 4)
|
||||
self.assertEqual(Log.objects.filter(group=kw1["group"]).count(), 2)
|
||||
@@ -20,6 +20,7 @@ from documents.tests.utils import DirectoriesMixin
|
||||
sample_file = os.path.join(os.path.dirname(__file__), "samples", "simple.pdf")
|
||||
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="{correspondent}/{title}")
|
||||
class TestArchiver(DirectoriesMixin, TestCase):
|
||||
|
||||
def make_models(self):
|
||||
@@ -42,9 +43,42 @@ class TestArchiver(DirectoriesMixin, TestCase):
|
||||
doc = Document.objects.get(id=doc.id)
|
||||
|
||||
self.assertIsNotNone(doc.checksum)
|
||||
self.assertIsNotNone(doc.archive_checksum)
|
||||
self.assertTrue(os.path.isfile(doc.archive_path))
|
||||
self.assertTrue(os.path.isfile(doc.source_path))
|
||||
self.assertTrue(filecmp.cmp(sample_file, doc.source_path))
|
||||
self.assertEqual(doc.archive_filename, "none/A.pdf")
|
||||
|
||||
def test_unknown_mime_type(self):
|
||||
doc = self.make_models()
|
||||
doc.mime_type = "sdgfh"
|
||||
doc.save()
|
||||
shutil.copy(sample_file, doc.source_path)
|
||||
|
||||
handle_document(doc.pk)
|
||||
|
||||
doc = Document.objects.get(id=doc.id)
|
||||
|
||||
self.assertIsNotNone(doc.checksum)
|
||||
self.assertIsNone(doc.archive_checksum)
|
||||
self.assertIsNone(doc.archive_filename)
|
||||
self.assertTrue(os.path.isfile(doc.source_path))
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="{title}")
|
||||
def test_naming_priorities(self):
|
||||
doc1 = Document.objects.create(checksum="A", title="document", content="first document", mime_type="application/pdf", filename="document.pdf")
|
||||
doc2 = Document.objects.create(checksum="B", title="document", content="second document", mime_type="application/pdf", filename="document_01.pdf")
|
||||
shutil.copy(sample_file, os.path.join(self.dirs.originals_dir, f"document.pdf"))
|
||||
shutil.copy(sample_file, os.path.join(self.dirs.originals_dir, f"document_01.pdf"))
|
||||
|
||||
handle_document(doc2.pk)
|
||||
handle_document(doc1.pk)
|
||||
|
||||
doc1 = Document.objects.get(id=doc1.id)
|
||||
doc2 = Document.objects.get(id=doc2.id)
|
||||
|
||||
self.assertEqual(doc1.archive_filename, "document.pdf")
|
||||
self.assertEqual(doc2.archive_filename, "document_01.pdf")
|
||||
|
||||
|
||||
class TestDecryptDocuments(TestCase):
|
||||
@@ -106,24 +140,27 @@ class TestMakeIndex(TestCase):
|
||||
|
||||
class TestRenamer(DirectoriesMixin, TestCase):
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="")
|
||||
def test_rename(self):
|
||||
doc = Document.objects.create(title="test", mime_type="application/pdf")
|
||||
doc = Document.objects.create(title="test", mime_type="image/jpeg")
|
||||
doc.filename = generate_filename(doc)
|
||||
doc.archive_filename = generate_filename(doc, archive_filename=True)
|
||||
doc.save()
|
||||
|
||||
Path(doc.source_path).touch()
|
||||
Path(doc.archive_path).touch()
|
||||
|
||||
old_source_path = doc.source_path
|
||||
|
||||
with override_settings(PAPERLESS_FILENAME_FORMAT="{title}"):
|
||||
with override_settings(PAPERLESS_FILENAME_FORMAT="{correspondent}/{title}"):
|
||||
call_command("document_renamer")
|
||||
|
||||
doc2 = Document.objects.get(id=doc.id)
|
||||
|
||||
self.assertEqual(doc2.filename, "test.pdf")
|
||||
self.assertFalse(os.path.isfile(old_source_path))
|
||||
self.assertEqual(doc2.filename, "none/test.jpg")
|
||||
self.assertEqual(doc2.archive_filename, "none/test.pdf")
|
||||
self.assertFalse(os.path.isfile(doc.source_path))
|
||||
self.assertFalse(os.path.isfile(doc.archive_path))
|
||||
self.assertTrue(os.path.isfile(doc2.source_path))
|
||||
self.assertTrue(os.path.isfile(doc2.archive_path))
|
||||
|
||||
|
||||
class TestCreateClassifier(TestCase):
|
||||
@@ -133,3 +170,24 @@ class TestCreateClassifier(TestCase):
|
||||
call_command("document_create_classifier")
|
||||
|
||||
m.assert_called_once()
|
||||
|
||||
|
||||
class TestSanityChecker(DirectoriesMixin, TestCase):
|
||||
|
||||
def test_no_issues(self):
|
||||
with self.assertLogs() as capture:
|
||||
call_command("document_sanity_checker")
|
||||
|
||||
self.assertEqual(len(capture.output), 1)
|
||||
self.assertIn("Sanity checker detected no issues.", capture.output[0])
|
||||
|
||||
def test_errors(self):
|
||||
doc = Document.objects.create(title="test", content="test", filename="test.pdf", checksum="abc")
|
||||
Path(doc.source_path).touch()
|
||||
Path(doc.thumbnail_path).touch()
|
||||
|
||||
with self.assertLogs() as capture:
|
||||
call_command("document_sanity_checker")
|
||||
|
||||
self.assertEqual(len(capture.output), 1)
|
||||
self.assertIn("Checksum mismatch of document", capture.output[0])
|
||||
|
||||
@@ -60,10 +60,10 @@ class ConsumerMixin:
|
||||
|
||||
super(ConsumerMixin, self).tearDown()
|
||||
|
||||
def wait_for_task_mock_call(self):
|
||||
def wait_for_task_mock_call(self, excpeted_call_count=1):
|
||||
n = 0
|
||||
while n < 100:
|
||||
if self.task_mock.call_count > 0:
|
||||
if self.task_mock.call_count >= excpeted_call_count:
|
||||
# give task_mock some time to finish and raise errors
|
||||
sleep(1)
|
||||
return
|
||||
@@ -202,8 +202,44 @@ class TestConsumer(DirectoriesMixin, ConsumerMixin, TransactionTestCase):
|
||||
|
||||
self.assertRaises(CommandError, call_command, 'document_consumer', '--oneshot')
|
||||
|
||||
def test_mac_write(self):
|
||||
self.task_mock.side_effect = self.bogus_task
|
||||
|
||||
@override_settings(CONSUMER_POLLING=1)
|
||||
self.t_start()
|
||||
|
||||
shutil.copy(self.sample_file, os.path.join(self.dirs.consumption_dir, ".DS_STORE"))
|
||||
shutil.copy(self.sample_file, os.path.join(self.dirs.consumption_dir, "my_file.pdf"))
|
||||
shutil.copy(self.sample_file, os.path.join(self.dirs.consumption_dir, "._my_file.pdf"))
|
||||
shutil.copy(self.sample_file, os.path.join(self.dirs.consumption_dir, "my_second_file.pdf"))
|
||||
shutil.copy(self.sample_file, os.path.join(self.dirs.consumption_dir, "._my_second_file.pdf"))
|
||||
|
||||
sleep(5)
|
||||
|
||||
self.wait_for_task_mock_call(excpeted_call_count=2)
|
||||
|
||||
self.assertEqual(2, self.task_mock.call_count)
|
||||
|
||||
fnames = [os.path.basename(args[1]) for args, _ in self.task_mock.call_args_list]
|
||||
self.assertCountEqual(fnames, ["my_file.pdf", "my_second_file.pdf"])
|
||||
|
||||
def test_is_ignored(self):
|
||||
test_paths = [
|
||||
(os.path.join(self.dirs.consumption_dir, "foo.pdf"), False),
|
||||
(os.path.join(self.dirs.consumption_dir, "foo","bar.pdf"), False),
|
||||
(os.path.join(self.dirs.consumption_dir, ".DS_STORE", "foo.pdf"), True),
|
||||
(os.path.join(self.dirs.consumption_dir, "foo", ".DS_STORE", "bar.pdf"), True),
|
||||
(os.path.join(self.dirs.consumption_dir, ".stfolder", "foo.pdf"), True),
|
||||
(os.path.join(self.dirs.consumption_dir, "._foo.pdf"), True),
|
||||
(os.path.join(self.dirs.consumption_dir, "._foo", "bar.pdf"), False),
|
||||
]
|
||||
for file_path, expected_ignored in test_paths:
|
||||
self.assertEqual(
|
||||
expected_ignored,
|
||||
document_consumer._is_ignored(file_path),
|
||||
f'_is_ignored("{file_path}") != {expected_ignored}')
|
||||
|
||||
|
||||
@override_settings(CONSUMER_POLLING=1, CONSUMER_POLLING_DELAY=1, CONSUMER_POLLING_RETRY_COUNT=20)
|
||||
class TestConsumerPolling(TestConsumer):
|
||||
# just do all the tests with polling
|
||||
pass
|
||||
@@ -215,8 +251,7 @@ class TestConsumerRecursive(TestConsumer):
|
||||
pass
|
||||
|
||||
|
||||
@override_settings(CONSUMER_RECURSIVE=True)
|
||||
@override_settings(CONSUMER_POLLING=1)
|
||||
@override_settings(CONSUMER_RECURSIVE=True, CONSUMER_POLLING=1, CONSUMER_POLLING_DELAY=1, CONSUMER_POLLING_RETRY_COUNT=20)
|
||||
class TestConsumerRecursivePolling(TestConsumer):
|
||||
# just do all the tests with polling and recursive
|
||||
pass
|
||||
@@ -257,6 +292,6 @@ class TestConsumerTags(DirectoriesMixin, ConsumerMixin, TransactionTestCase):
|
||||
# their order.
|
||||
self.assertCountEqual(kwargs["override_tag_ids"], tag_ids)
|
||||
|
||||
@override_settings(CONSUMER_POLLING=1)
|
||||
@override_settings(CONSUMER_POLLING=1, CONSUMER_POLLING_DELAY=1, CONSUMER_POLLING_RETRY_COUNT=20)
|
||||
def test_consume_file_with_path_tags_polling(self):
|
||||
self.test_consume_file_with_path_tags()
|
||||
|
||||
@@ -22,7 +22,7 @@ class TestExportImport(DirectoriesMixin, TestCase):
|
||||
self.target = tempfile.mkdtemp()
|
||||
self.addCleanup(shutil.rmtree, self.target)
|
||||
|
||||
self.d1 = Document.objects.create(content="Content", checksum="42995833e01aea9b3edee44bbfdd7ce1", archive_checksum="62acb0bcbfbcaa62ca6ad3668e4e404b", title="wow1", filename="0000001.pdf", mime_type="application/pdf")
|
||||
self.d1 = Document.objects.create(content="Content", checksum="42995833e01aea9b3edee44bbfdd7ce1", archive_checksum="62acb0bcbfbcaa62ca6ad3668e4e404b", title="wow1", filename="0000001.pdf", mime_type="application/pdf", archive_filename="0000001.pdf")
|
||||
self.d2 = Document.objects.create(content="Content", checksum="9c9691e51741c1f4f41a20896af31770", title="wow2", filename="0000002.pdf", mime_type="application/pdf")
|
||||
self.d3 = Document.objects.create(content="Content", checksum="d38d7ed02e988e072caf924e0f3fcb76", title="wow2", filename="0000003.pdf", mime_type="application/pdf")
|
||||
self.d4 = Document.objects.create(content="Content", checksum="82186aaa94f0b98697d704b90fd1c072", title="wow_dec", filename="0000004.pdf.gpg", mime_type="application/pdf", storage_type=Document.STORAGE_TYPE_GPG)
|
||||
@@ -69,7 +69,7 @@ class TestExportImport(DirectoriesMixin, TestCase):
|
||||
|
||||
manifest = self._do_export(use_filename_format=use_filename_format)
|
||||
|
||||
self.assertEqual(len(manifest), 7)
|
||||
self.assertEqual(len(manifest), 8)
|
||||
self.assertEqual(len(list(filter(lambda e: e['model'] == 'documents.document', manifest))), 4)
|
||||
|
||||
self.assertTrue(os.path.exists(os.path.join(self.target, "manifest.json")))
|
||||
|
||||
@@ -11,14 +11,17 @@ class TestRetagger(DirectoriesMixin, TestCase):
|
||||
self.d1 = Document.objects.create(checksum="A", title="A", content="first document")
|
||||
self.d2 = Document.objects.create(checksum="B", title="B", content="second document")
|
||||
self.d3 = Document.objects.create(checksum="C", title="C", content="unrelated document")
|
||||
self.d4 = Document.objects.create(checksum="D", title="D", content="auto document")
|
||||
|
||||
self.tag_first = Tag.objects.create(name="tag1", match="first", matching_algorithm=Tag.MATCH_ANY)
|
||||
self.tag_second = Tag.objects.create(name="tag2", match="second", matching_algorithm=Tag.MATCH_ANY)
|
||||
self.tag_inbox = Tag.objects.create(name="test", is_inbox_tag=True)
|
||||
self.tag_no_match = Tag.objects.create(name="test2")
|
||||
self.tag_auto = Tag.objects.create(name="tagauto", matching_algorithm=Tag.MATCH_AUTO)
|
||||
|
||||
self.d3.tags.add(self.tag_inbox)
|
||||
self.d3.tags.add(self.tag_no_match)
|
||||
self.d4.tags.add(self.tag_auto)
|
||||
|
||||
|
||||
self.correspondent_first = Correspondent.objects.create(
|
||||
@@ -32,7 +35,8 @@ class TestRetagger(DirectoriesMixin, TestCase):
|
||||
name="dt2", match="second", matching_algorithm=DocumentType.MATCH_ANY)
|
||||
|
||||
def get_updated_docs(self):
|
||||
return Document.objects.get(title="A"), Document.objects.get(title="B"), Document.objects.get(title="C")
|
||||
return Document.objects.get(title="A"), Document.objects.get(title="B"), \
|
||||
Document.objects.get(title="C"), Document.objects.get(title="D")
|
||||
|
||||
def setUp(self) -> None:
|
||||
super(TestRetagger, self).setUp()
|
||||
@@ -40,25 +44,26 @@ class TestRetagger(DirectoriesMixin, TestCase):
|
||||
|
||||
def test_add_tags(self):
|
||||
call_command('document_retagger', '--tags')
|
||||
d_first, d_second, d_unrelated = self.get_updated_docs()
|
||||
d_first, d_second, d_unrelated, d_auto = self.get_updated_docs()
|
||||
|
||||
self.assertEqual(d_first.tags.count(), 1)
|
||||
self.assertEqual(d_second.tags.count(), 1)
|
||||
self.assertEqual(d_unrelated.tags.count(), 2)
|
||||
self.assertEqual(d_auto.tags.count(), 1)
|
||||
|
||||
self.assertEqual(d_first.tags.first(), self.tag_first)
|
||||
self.assertEqual(d_second.tags.first(), self.tag_second)
|
||||
|
||||
def test_add_type(self):
|
||||
call_command('document_retagger', '--document_type')
|
||||
d_first, d_second, d_unrelated = self.get_updated_docs()
|
||||
d_first, d_second, d_unrelated, d_auto = self.get_updated_docs()
|
||||
|
||||
self.assertEqual(d_first.document_type, self.doctype_first)
|
||||
self.assertEqual(d_second.document_type, self.doctype_second)
|
||||
|
||||
def test_add_correspondent(self):
|
||||
call_command('document_retagger', '--correspondent')
|
||||
d_first, d_second, d_unrelated = self.get_updated_docs()
|
||||
d_first, d_second, d_unrelated, d_auto = self.get_updated_docs()
|
||||
|
||||
self.assertEqual(d_first.correspondent, self.correspondent_first)
|
||||
self.assertEqual(d_second.correspondent, self.correspondent_second)
|
||||
@@ -68,11 +73,55 @@ class TestRetagger(DirectoriesMixin, TestCase):
|
||||
|
||||
call_command('document_retagger', '--tags', '--overwrite')
|
||||
|
||||
d_first, d_second, d_unrelated = self.get_updated_docs()
|
||||
d_first, d_second, d_unrelated, d_auto = self.get_updated_docs()
|
||||
|
||||
self.assertIsNotNone(Tag.objects.get(id=self.tag_second.id))
|
||||
|
||||
self.assertCountEqual([tag.id for tag in d_first.tags.all()], [self.tag_first.id])
|
||||
self.assertCountEqual([tag.id for tag in d_second.tags.all()], [self.tag_second.id])
|
||||
self.assertCountEqual([tag.id for tag in d_unrelated.tags.all()], [self.tag_inbox.id, self.tag_no_match.id])
|
||||
self.assertEqual(d_auto.tags.count(), 0)
|
||||
|
||||
def test_add_tags_suggest(self):
|
||||
call_command('document_retagger', '--tags', '--suggest')
|
||||
d_first, d_second, d_unrelated, d_auto = self.get_updated_docs()
|
||||
|
||||
self.assertEqual(d_first.tags.count(), 0)
|
||||
self.assertEqual(d_second.tags.count(), 0)
|
||||
self.assertEqual(d_auto.tags.count(), 1)
|
||||
|
||||
def test_add_type_suggest(self):
|
||||
call_command('document_retagger', '--document_type', '--suggest')
|
||||
d_first, d_second, d_unrelated, d_auto = self.get_updated_docs()
|
||||
|
||||
self.assertEqual(d_first.document_type, None)
|
||||
self.assertEqual(d_second.document_type, None)
|
||||
|
||||
def test_add_correspondent_suggest(self):
|
||||
call_command('document_retagger', '--correspondent', '--suggest')
|
||||
d_first, d_second, d_unrelated, d_auto = self.get_updated_docs()
|
||||
|
||||
self.assertEqual(d_first.correspondent, None)
|
||||
self.assertEqual(d_second.correspondent, None)
|
||||
|
||||
def test_add_tags_suggest_url(self):
|
||||
call_command('document_retagger', '--tags', '--suggest', '--base-url=http://localhost')
|
||||
d_first, d_second, d_unrelated, d_auto = self.get_updated_docs()
|
||||
|
||||
self.assertEqual(d_first.tags.count(), 0)
|
||||
self.assertEqual(d_second.tags.count(), 0)
|
||||
self.assertEqual(d_auto.tags.count(), 1)
|
||||
|
||||
def test_add_type_suggest_url(self):
|
||||
call_command('document_retagger', '--document_type', '--suggest', '--base-url=http://localhost')
|
||||
d_first, d_second, d_unrelated, d_auto = self.get_updated_docs()
|
||||
|
||||
self.assertEqual(d_first.document_type, None)
|
||||
self.assertEqual(d_second.document_type, None)
|
||||
|
||||
def test_add_correspondent_suggest_url(self):
|
||||
call_command('document_retagger', '--correspondent', '--suggest', '--base-url=http://localhost')
|
||||
d_first, d_second, d_unrelated, d_auto = self.get_updated_docs()
|
||||
|
||||
self.assertEqual(d_first.correspondent, None)
|
||||
self.assertEqual(d_second.correspondent, None)
|
||||
|
||||
66
src/documents/tests/test_management_superuser.py
Normal file
66
src/documents/tests/test_management_superuser.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import os
|
||||
import shutil
|
||||
from unittest import mock
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.management import call_command
|
||||
from django.test import TestCase
|
||||
|
||||
from documents.management.commands.document_thumbnails import _process_document
|
||||
from documents.models import Document, Tag, Correspondent, DocumentType
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
|
||||
|
||||
class TestManageSuperUser(DirectoriesMixin, TestCase):
|
||||
|
||||
def reset_environment(self):
|
||||
if "PAPERLESS_ADMIN_USER" in os.environ:
|
||||
del os.environ["PAPERLESS_ADMIN_USER"]
|
||||
if "PAPERLESS_ADMIN_PASSWORD" in os.environ:
|
||||
del os.environ["PAPERLESS_ADMIN_PASSWORD"]
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.reset_environment()
|
||||
|
||||
def tearDown(self) -> None:
|
||||
super().tearDown()
|
||||
self.reset_environment()
|
||||
|
||||
def test_no_user(self):
|
||||
call_command("manage_superuser")
|
||||
|
||||
# just the consumer user.
|
||||
self.assertEqual(User.objects.count(), 1)
|
||||
self.assertTrue(User.objects.filter(username="consumer").exists())
|
||||
|
||||
def test_create(self):
|
||||
os.environ["PAPERLESS_ADMIN_USER"] = "new_user"
|
||||
os.environ["PAPERLESS_ADMIN_PASSWORD"] = "123456"
|
||||
|
||||
call_command("manage_superuser")
|
||||
|
||||
user: User = User.objects.get_by_natural_key("new_user")
|
||||
self.assertTrue(user.check_password("123456"))
|
||||
|
||||
def test_update(self):
|
||||
os.environ["PAPERLESS_ADMIN_USER"] = "new_user"
|
||||
os.environ["PAPERLESS_ADMIN_PASSWORD"] = "123456"
|
||||
|
||||
call_command("manage_superuser")
|
||||
|
||||
os.environ["PAPERLESS_ADMIN_USER"] = "new_user"
|
||||
os.environ["PAPERLESS_ADMIN_PASSWORD"] = "more_secure_pwd_7645"
|
||||
|
||||
call_command("manage_superuser")
|
||||
|
||||
user: User = User.objects.get_by_natural_key("new_user")
|
||||
self.assertTrue(user.check_password("more_secure_pwd_7645"))
|
||||
|
||||
def test_no_password(self):
|
||||
os.environ["PAPERLESS_ADMIN_USER"] = "new_user"
|
||||
|
||||
call_command("manage_superuser")
|
||||
|
||||
with self.assertRaises(User.DoesNotExist):
|
||||
User.objects.get_by_natural_key("new_user")
|
||||
325
src/documents/tests/test_migration_archive_files.py
Normal file
325
src/documents/tests/test_migration_archive_files.py
Normal file
@@ -0,0 +1,325 @@
|
||||
import hashlib
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
from django.conf import settings
|
||||
from django.test import override_settings
|
||||
|
||||
from documents.parsers import ParseError
|
||||
from documents.tests.utils import DirectoriesMixin, TestMigrations
|
||||
|
||||
|
||||
STORAGE_TYPE_GPG = "gpg"
|
||||
|
||||
|
||||
def archive_name_from_filename(filename):
|
||||
return os.path.splitext(filename)[0] + ".pdf"
|
||||
|
||||
|
||||
def archive_path_old(self):
|
||||
if self.filename:
|
||||
fname = archive_name_from_filename(self.filename)
|
||||
else:
|
||||
fname = "{:07}.pdf".format(self.pk)
|
||||
|
||||
return os.path.join(
|
||||
settings.ARCHIVE_DIR,
|
||||
fname
|
||||
)
|
||||
|
||||
|
||||
def archive_path_new(doc):
|
||||
if doc.archive_filename is not None:
|
||||
return os.path.join(
|
||||
settings.ARCHIVE_DIR,
|
||||
str(doc.archive_filename)
|
||||
)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def source_path(doc):
|
||||
if doc.filename:
|
||||
fname = str(doc.filename)
|
||||
else:
|
||||
fname = "{:07}{}".format(doc.pk, doc.file_type)
|
||||
if doc.storage_type == STORAGE_TYPE_GPG:
|
||||
fname += ".gpg" # pragma: no cover
|
||||
|
||||
return os.path.join(
|
||||
settings.ORIGINALS_DIR,
|
||||
fname
|
||||
)
|
||||
|
||||
|
||||
def thumbnail_path(doc):
|
||||
file_name = "{:07}.png".format(doc.pk)
|
||||
if doc.storage_type == STORAGE_TYPE_GPG:
|
||||
file_name += ".gpg"
|
||||
|
||||
return os.path.join(
|
||||
settings.THUMBNAIL_DIR,
|
||||
file_name
|
||||
)
|
||||
|
||||
|
||||
def make_test_document(document_class, title: str, mime_type: str, original: str, original_filename: str, archive: str = None, archive_filename: str = None):
|
||||
doc = document_class()
|
||||
doc.filename = original_filename
|
||||
doc.title = title
|
||||
doc.mime_type = mime_type
|
||||
doc.content = "the content, does not matter for this test"
|
||||
doc.save()
|
||||
|
||||
shutil.copy2(original, source_path(doc))
|
||||
with open(original, "rb") as f:
|
||||
doc.checksum = hashlib.md5(f.read()).hexdigest()
|
||||
|
||||
if archive:
|
||||
if archive_filename:
|
||||
doc.archive_filename = archive_filename
|
||||
shutil.copy2(archive, archive_path_new(doc))
|
||||
else:
|
||||
shutil.copy2(archive, archive_path_old(doc))
|
||||
|
||||
with open(archive, "rb") as f:
|
||||
doc.archive_checksum = hashlib.md5(f.read()).hexdigest()
|
||||
|
||||
doc.save()
|
||||
|
||||
Path(thumbnail_path(doc)).touch()
|
||||
|
||||
return doc
|
||||
|
||||
|
||||
simple_jpg = os.path.join(os.path.dirname(__file__), "samples", "simple.jpg")
|
||||
simple_pdf = os.path.join(os.path.dirname(__file__), "samples", "simple.pdf")
|
||||
simple_pdf2 = os.path.join(os.path.dirname(__file__), "samples", "documents", "originals", "0000002.pdf")
|
||||
simple_pdf3 = os.path.join(os.path.dirname(__file__), "samples", "documents", "originals", "0000003.pdf")
|
||||
simple_txt = os.path.join(os.path.dirname(__file__), "samples", "simple.txt")
|
||||
simple_png = os.path.join(os.path.dirname(__file__), "samples", "simple-noalpha.png")
|
||||
simple_png2 = os.path.join(os.path.dirname(__file__), "examples", "no-text.png")
|
||||
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="")
|
||||
class TestMigrateArchiveFiles(DirectoriesMixin, TestMigrations):
|
||||
|
||||
migrate_from = '1011_auto_20210101_2340'
|
||||
migrate_to = '1012_fix_archive_files'
|
||||
|
||||
def setUpBeforeMigration(self, apps):
|
||||
Document = apps.get_model("documents", "Document")
|
||||
|
||||
self.unrelated = make_test_document(Document, "unrelated", "application/pdf", simple_pdf3, "unrelated.pdf", simple_pdf)
|
||||
self.no_text = make_test_document(Document, "no-text", "image/png", simple_png2, "no-text.png", simple_pdf)
|
||||
self.doc_no_archive = make_test_document(Document, "no_archive", "text/plain", simple_txt, "no_archive.txt")
|
||||
self.clash1 = make_test_document(Document, "clash", "application/pdf", simple_pdf, "clash.pdf", simple_pdf)
|
||||
self.clash2 = make_test_document(Document, "clash", "image/jpeg", simple_jpg, "clash.jpg", simple_pdf)
|
||||
self.clash3 = make_test_document(Document, "clash", "image/png", simple_png, "clash.png", simple_pdf)
|
||||
self.clash4 = make_test_document(Document, "clash.png", "application/pdf", simple_pdf2, "clash.png.pdf", simple_pdf2)
|
||||
|
||||
self.assertEqual(archive_path_old(self.clash1), archive_path_old(self.clash2))
|
||||
self.assertEqual(archive_path_old(self.clash1), archive_path_old(self.clash3))
|
||||
self.assertNotEqual(archive_path_old(self.clash1), archive_path_old(self.clash4))
|
||||
|
||||
def testArchiveFilesMigrated(self):
|
||||
Document = self.apps.get_model('documents', 'Document')
|
||||
|
||||
for doc in Document.objects.all():
|
||||
if doc.archive_checksum:
|
||||
self.assertIsNotNone(doc.archive_filename)
|
||||
self.assertTrue(os.path.isfile(archive_path_new(doc)))
|
||||
else:
|
||||
self.assertIsNone(doc.archive_filename)
|
||||
|
||||
with open(source_path(doc), "rb") as f:
|
||||
original_checksum = hashlib.md5(f.read()).hexdigest()
|
||||
self.assertEqual(original_checksum, doc.checksum)
|
||||
|
||||
if doc.archive_checksum:
|
||||
self.assertTrue(os.path.isfile(archive_path_new(doc)))
|
||||
with open(archive_path_new(doc), "rb") as f:
|
||||
archive_checksum = hashlib.md5(f.read()).hexdigest()
|
||||
self.assertEqual(archive_checksum, doc.archive_checksum)
|
||||
|
||||
self.assertEqual(Document.objects.filter(archive_checksum__isnull=False).count(), 6)
|
||||
|
||||
def test_filenames(self):
|
||||
Document = self.apps.get_model('documents', 'Document')
|
||||
self.assertEqual(Document.objects.get(id=self.unrelated.id).archive_filename, "unrelated.pdf")
|
||||
self.assertEqual(Document.objects.get(id=self.no_text.id).archive_filename, "no-text.pdf")
|
||||
self.assertEqual(Document.objects.get(id=self.doc_no_archive.id).archive_filename, None)
|
||||
self.assertEqual(Document.objects.get(id=self.clash1.id).archive_filename, f"{self.clash1.id:07}.pdf")
|
||||
self.assertEqual(Document.objects.get(id=self.clash2.id).archive_filename, f"{self.clash2.id:07}.pdf")
|
||||
self.assertEqual(Document.objects.get(id=self.clash3.id).archive_filename, f"{self.clash3.id:07}.pdf")
|
||||
self.assertEqual(Document.objects.get(id=self.clash4.id).archive_filename, "clash.png.pdf")
|
||||
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="{correspondent}/{title}")
|
||||
class TestMigrateArchiveFilesWithFilenameFormat(TestMigrateArchiveFiles):
|
||||
|
||||
def test_filenames(self):
|
||||
Document = self.apps.get_model('documents', 'Document')
|
||||
self.assertEqual(Document.objects.get(id=self.unrelated.id).archive_filename, "unrelated.pdf")
|
||||
self.assertEqual(Document.objects.get(id=self.no_text.id).archive_filename, "no-text.pdf")
|
||||
self.assertEqual(Document.objects.get(id=self.doc_no_archive.id).archive_filename, None)
|
||||
self.assertEqual(Document.objects.get(id=self.clash1.id).archive_filename, "none/clash.pdf")
|
||||
self.assertEqual(Document.objects.get(id=self.clash2.id).archive_filename, "none/clash_01.pdf")
|
||||
self.assertEqual(Document.objects.get(id=self.clash3.id).archive_filename, "none/clash_02.pdf")
|
||||
self.assertEqual(Document.objects.get(id=self.clash4.id).archive_filename, "clash.png.pdf")
|
||||
|
||||
|
||||
def fake_parse_wrapper(parser, path, mime_type, file_name):
|
||||
parser.archive_path = None
|
||||
parser.text = "the text"
|
||||
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="")
|
||||
class TestMigrateArchiveFilesErrors(DirectoriesMixin, TestMigrations):
|
||||
|
||||
migrate_from = '1011_auto_20210101_2340'
|
||||
migrate_to = '1012_fix_archive_files'
|
||||
auto_migrate = False
|
||||
|
||||
def test_archive_missing(self):
|
||||
|
||||
Document = self.apps.get_model("documents", "Document")
|
||||
|
||||
doc = make_test_document(Document, "clash", "application/pdf", simple_pdf, "clash.pdf", simple_pdf)
|
||||
os.unlink(archive_path_old(doc))
|
||||
|
||||
self.assertRaisesMessage(ValueError, "does not exist at: ", self.performMigration)
|
||||
|
||||
def test_parser_missing(self):
|
||||
Document = self.apps.get_model("documents", "Document")
|
||||
|
||||
doc1 = make_test_document(Document, "document", "invalid/typesss768", simple_png, "document.png", simple_pdf)
|
||||
doc2 = make_test_document(Document, "document", "invalid/typesss768", simple_jpg, "document.jpg", simple_pdf)
|
||||
|
||||
self.assertRaisesMessage(ValueError, "no parsers are available", self.performMigration)
|
||||
|
||||
@mock.patch("documents.migrations.1012_fix_archive_files.parse_wrapper")
|
||||
def test_parser_error(self, m):
|
||||
m.side_effect = ParseError()
|
||||
Document = self.apps.get_model("documents", "Document")
|
||||
|
||||
doc1 = make_test_document(Document, "document", "image/png", simple_png, "document.png", simple_pdf)
|
||||
doc2 = make_test_document(Document, "document", "application/pdf", simple_jpg, "document.jpg", simple_pdf)
|
||||
|
||||
self.assertIsNotNone(doc1.archive_checksum)
|
||||
self.assertIsNotNone(doc2.archive_checksum)
|
||||
|
||||
with self.assertLogs() as capture:
|
||||
self.performMigration()
|
||||
|
||||
self.assertEqual(m.call_count, 6)
|
||||
|
||||
self.assertEqual(
|
||||
len(list(filter(lambda log: "Parse error, will try again in 5 seconds" in log, capture.output))),
|
||||
4)
|
||||
|
||||
self.assertEqual(
|
||||
len(list(filter(lambda log: "Unable to regenerate archive document for ID:" in log, capture.output))),
|
||||
2)
|
||||
|
||||
Document = self.apps.get_model("documents", "Document")
|
||||
|
||||
doc1 = Document.objects.get(id=doc1.id)
|
||||
doc2 = Document.objects.get(id=doc2.id)
|
||||
|
||||
self.assertIsNone(doc1.archive_checksum)
|
||||
self.assertIsNone(doc2.archive_checksum)
|
||||
self.assertIsNone(doc1.archive_filename)
|
||||
self.assertIsNone(doc2.archive_filename)
|
||||
|
||||
@mock.patch("documents.migrations.1012_fix_archive_files.parse_wrapper")
|
||||
def test_parser_no_archive(self, m):
|
||||
m.side_effect = fake_parse_wrapper
|
||||
|
||||
Document = self.apps.get_model("documents", "Document")
|
||||
|
||||
doc1 = make_test_document(Document, "document", "image/png", simple_png, "document.png", simple_pdf)
|
||||
doc2 = make_test_document(Document, "document", "application/pdf", simple_jpg, "document.jpg", simple_pdf)
|
||||
|
||||
with self.assertLogs() as capture:
|
||||
self.performMigration()
|
||||
|
||||
self.assertEqual(
|
||||
len(list(filter(lambda log: "Parser did not return an archive document for document" in log, capture.output))),
|
||||
2)
|
||||
|
||||
Document = self.apps.get_model("documents", "Document")
|
||||
|
||||
doc1 = Document.objects.get(id=doc1.id)
|
||||
doc2 = Document.objects.get(id=doc2.id)
|
||||
|
||||
self.assertIsNone(doc1.archive_checksum)
|
||||
self.assertIsNone(doc2.archive_checksum)
|
||||
self.assertIsNone(doc1.archive_filename)
|
||||
self.assertIsNone(doc2.archive_filename)
|
||||
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="")
|
||||
class TestMigrateArchiveFilesBackwards(DirectoriesMixin, TestMigrations):
|
||||
|
||||
migrate_from = '1012_fix_archive_files'
|
||||
migrate_to = '1011_auto_20210101_2340'
|
||||
|
||||
def setUpBeforeMigration(self, apps):
|
||||
|
||||
Document = apps.get_model("documents", "Document")
|
||||
|
||||
doc_unrelated = make_test_document(Document, "unrelated", "application/pdf", simple_pdf2, "unrelated.txt", simple_pdf2, "unrelated.pdf")
|
||||
doc_no_archive = make_test_document(Document, "no_archive", "text/plain", simple_txt, "no_archive.txt")
|
||||
clashB = make_test_document(Document, "clash", "image/jpeg", simple_jpg, "clash.jpg", simple_pdf, "clash_02.pdf")
|
||||
|
||||
def testArchiveFilesReverted(self):
|
||||
Document = self.apps.get_model('documents', 'Document')
|
||||
|
||||
for doc in Document.objects.all():
|
||||
if doc.archive_checksum:
|
||||
self.assertTrue(os.path.isfile(archive_path_old(doc)))
|
||||
with open(source_path(doc), "rb") as f:
|
||||
original_checksum = hashlib.md5(f.read()).hexdigest()
|
||||
self.assertEqual(original_checksum, doc.checksum)
|
||||
|
||||
if doc.archive_checksum:
|
||||
self.assertTrue(os.path.isfile(archive_path_old(doc)))
|
||||
with open(archive_path_old(doc), "rb") as f:
|
||||
archive_checksum = hashlib.md5(f.read()).hexdigest()
|
||||
self.assertEqual(archive_checksum, doc.archive_checksum)
|
||||
|
||||
self.assertEqual(Document.objects.filter(archive_checksum__isnull=False).count(), 2)
|
||||
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="{correspondent}/{title}")
|
||||
class TestMigrateArchiveFilesBackwardsWithFilenameFormat(TestMigrateArchiveFilesBackwards):
|
||||
pass
|
||||
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="")
|
||||
class TestMigrateArchiveFilesBackwardsErrors(DirectoriesMixin, TestMigrations):
|
||||
|
||||
migrate_from = '1012_fix_archive_files'
|
||||
migrate_to = '1011_auto_20210101_2340'
|
||||
auto_migrate = False
|
||||
|
||||
def test_filename_clash(self):
|
||||
|
||||
Document = self.apps.get_model("documents", "Document")
|
||||
|
||||
self.clashA = make_test_document(Document, "clash", "application/pdf", simple_pdf, "clash.pdf", simple_pdf, "clash_02.pdf")
|
||||
self.clashB = make_test_document(Document, "clash", "image/jpeg", simple_jpg, "clash.jpg", simple_pdf, "clash_01.pdf")
|
||||
|
||||
self.assertRaisesMessage(ValueError, "would clash with another archive filename", self.performMigration)
|
||||
|
||||
def test_filename_exists(self):
|
||||
|
||||
Document = self.apps.get_model("documents", "Document")
|
||||
|
||||
self.clashA = make_test_document(Document, "clash", "application/pdf", simple_pdf, "clash.pdf", simple_pdf, "clash.pdf")
|
||||
self.clashB = make_test_document(Document, "clash", "image/jpeg", simple_jpg, "clash.jpg", simple_pdf, "clash_01.pdf")
|
||||
|
||||
self.assertRaisesMessage(ValueError, "file already exists.", self.performMigration)
|
||||
@@ -1,52 +1,11 @@
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.db import connection
|
||||
from django.db.migrations.executor import MigrationExecutor
|
||||
from django.test import TestCase, TransactionTestCase, override_settings
|
||||
from django.test import override_settings
|
||||
|
||||
from documents.models import Document
|
||||
from documents.parsers import get_default_file_extension
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
|
||||
|
||||
class TestMigrations(TransactionTestCase):
|
||||
|
||||
@property
|
||||
def app(self):
|
||||
return apps.get_containing_app_config(type(self).__module__).name
|
||||
|
||||
migrate_from = None
|
||||
migrate_to = None
|
||||
|
||||
def setUp(self):
|
||||
super(TestMigrations, self).setUp()
|
||||
|
||||
assert self.migrate_from and self.migrate_to, \
|
||||
"TestCase '{}' must define migrate_from and migrate_to properties".format(type(self).__name__)
|
||||
self.migrate_from = [(self.app, self.migrate_from)]
|
||||
self.migrate_to = [(self.app, self.migrate_to)]
|
||||
executor = MigrationExecutor(connection)
|
||||
old_apps = executor.loader.project_state(self.migrate_from).apps
|
||||
|
||||
# Reverse to the original migration
|
||||
executor.migrate(self.migrate_from)
|
||||
|
||||
self.setUpBeforeMigration(old_apps)
|
||||
|
||||
# Run the migration to test
|
||||
executor = MigrationExecutor(connection)
|
||||
executor.loader.build_graph() # reload.
|
||||
executor.migrate(self.migrate_to)
|
||||
|
||||
self.apps = executor.loader.project_state(self.migrate_to).apps
|
||||
|
||||
def setUpBeforeMigration(self, apps):
|
||||
pass
|
||||
|
||||
from documents.tests.utils import DirectoriesMixin, TestMigrations
|
||||
|
||||
STORAGE_TYPE_UNENCRYPTED = "unencrypted"
|
||||
STORAGE_TYPE_GPG = "gpg"
|
||||
15
src/documents/tests/test_migration_remove_null_characters.py
Normal file
15
src/documents/tests/test_migration_remove_null_characters.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from documents.tests.utils import DirectoriesMixin, TestMigrations
|
||||
|
||||
|
||||
class TestMigrateNullCharacters(DirectoriesMixin, TestMigrations):
|
||||
|
||||
migrate_from = '1014_auto_20210228_1614'
|
||||
migrate_to = '1015_remove_null_characters'
|
||||
|
||||
def setUpBeforeMigration(self, apps):
|
||||
Document = apps.get_model("documents", "Document")
|
||||
self.doc = Document.objects.create(content="aaa\0bbb")
|
||||
|
||||
def testMimeTypesMigrated(self):
|
||||
Document = self.apps.get_model('documents', 'Document')
|
||||
self.assertNotIn("\0", Document.objects.get(id=self.doc.id).content)
|
||||
37
src/documents/tests/test_migration_tag_colors.py
Normal file
37
src/documents/tests/test_migration_tag_colors.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from documents.tests.utils import DirectoriesMixin, TestMigrations
|
||||
|
||||
|
||||
class TestMigrateTagColor(DirectoriesMixin, TestMigrations):
|
||||
|
||||
migrate_from = '1012_fix_archive_files'
|
||||
migrate_to = '1013_migrate_tag_colour'
|
||||
|
||||
def setUpBeforeMigration(self, apps):
|
||||
Tag = apps.get_model("documents", "Tag")
|
||||
self.t1_id = Tag.objects.create(name="tag1").id
|
||||
self.t2_id = Tag.objects.create(name="tag2", colour=1).id
|
||||
self.t3_id = Tag.objects.create(name="tag3", colour=5).id
|
||||
|
||||
def testMimeTypesMigrated(self):
|
||||
Tag = self.apps.get_model('documents', 'Tag')
|
||||
self.assertEqual(Tag.objects.get(id=self.t1_id).color, "#a6cee3")
|
||||
self.assertEqual(Tag.objects.get(id=self.t2_id).color, "#a6cee3")
|
||||
self.assertEqual(Tag.objects.get(id=self.t3_id).color, "#fb9a99")
|
||||
|
||||
|
||||
class TestMigrateTagColorBackwards(DirectoriesMixin, TestMigrations):
|
||||
|
||||
migrate_from = '1013_migrate_tag_colour'
|
||||
migrate_to = '1012_fix_archive_files'
|
||||
|
||||
def setUpBeforeMigration(self, apps):
|
||||
Tag = apps.get_model("documents", "Tag")
|
||||
self.t1_id = Tag.objects.create(name="tag1").id
|
||||
self.t2_id = Tag.objects.create(name="tag2", color="#cab2d6").id
|
||||
self.t3_id = Tag.objects.create(name="tag3", color="#123456").id
|
||||
|
||||
def testMimeTypesReverted(self):
|
||||
Tag = self.apps.get_model('documents', 'Tag')
|
||||
self.assertEqual(Tag.objects.get(id=self.t1_id).colour, 1)
|
||||
self.assertEqual(Tag.objects.get(id=self.t2_id).colour, 9)
|
||||
self.assertEqual(Tag.objects.get(id=self.t3_id).colour, 1)
|
||||
@@ -68,7 +68,7 @@ class TestParserDiscovery(TestCase):
|
||||
)
|
||||
|
||||
|
||||
def fake_get_thumbnail(self, path, mimetype):
|
||||
def fake_get_thumbnail(self, path, mimetype, file_name):
|
||||
return os.path.join(os.path.dirname(__file__), "examples", "no-text.png")
|
||||
|
||||
|
||||
@@ -89,15 +89,15 @@ class TestBaseParser(TestCase):
|
||||
def test_get_optimised_thumbnail(self):
|
||||
parser = DocumentParser(None)
|
||||
|
||||
parser.get_optimised_thumbnail("any", "not important")
|
||||
parser.get_optimised_thumbnail("any", "not important", "document.pdf")
|
||||
|
||||
@mock.patch("documents.parsers.DocumentParser.get_thumbnail", fake_get_thumbnail)
|
||||
@override_settings(OPTIMIZE_THUMBNAILS=False)
|
||||
def test_get_optimised_thumb_disabled(self):
|
||||
parser = DocumentParser(None)
|
||||
|
||||
path = parser.get_optimised_thumbnail("any", "not important")
|
||||
self.assertEqual(path, fake_get_thumbnail(None, None, None))
|
||||
path = parser.get_optimised_thumbnail("any", "not important", "document.pdf")
|
||||
self.assertEqual(path, fake_get_thumbnail(None, None, None, None))
|
||||
|
||||
|
||||
class TestParserAvailability(TestCase):
|
||||
@@ -114,8 +114,8 @@ class TestParserAvailability(TestCase):
|
||||
self.assertEqual(get_default_file_extension('application/zip'), ".zip")
|
||||
self.assertEqual(get_default_file_extension('aasdasd/dgfgf'), "")
|
||||
|
||||
self.assertEqual(get_parser_class_for_mime_type('application/pdf'), RasterisedDocumentParser)
|
||||
self.assertEqual(get_parser_class_for_mime_type('text/plain'), TextDocumentParser)
|
||||
self.assertIsInstance(get_parser_class_for_mime_type('application/pdf')(logging_group=None), RasterisedDocumentParser)
|
||||
self.assertIsInstance(get_parser_class_for_mime_type('text/plain')(logging_group=None), TextDocumentParser)
|
||||
self.assertEqual(get_parser_class_for_mime_type('text/sdgsdf'), None)
|
||||
|
||||
self.assertTrue(is_file_ext_supported('.pdf'))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
@@ -7,10 +8,59 @@ from django.conf import settings
|
||||
from django.test import TestCase
|
||||
|
||||
from documents.models import Document
|
||||
from documents.sanity_checker import check_sanity, SanityFailedError
|
||||
from documents.sanity_checker import check_sanity, SanityCheckMessages
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
|
||||
|
||||
class TestSanityCheckMessages(TestCase):
|
||||
|
||||
def test_no_messages(self):
|
||||
messages = SanityCheckMessages()
|
||||
self.assertEqual(len(messages), 0)
|
||||
self.assertFalse(messages.has_error())
|
||||
self.assertFalse(messages.has_warning())
|
||||
with self.assertLogs() as capture:
|
||||
messages.log_messages()
|
||||
self.assertEqual(len(capture.output), 1)
|
||||
self.assertEqual(capture.records[0].levelno, logging.INFO)
|
||||
self.assertEqual(capture.records[0].message, "Sanity checker detected no issues.")
|
||||
|
||||
def test_info(self):
|
||||
messages = SanityCheckMessages()
|
||||
messages.info("Something might be wrong")
|
||||
self.assertEqual(len(messages), 1)
|
||||
self.assertFalse(messages.has_error())
|
||||
self.assertFalse(messages.has_warning())
|
||||
with self.assertLogs() as capture:
|
||||
messages.log_messages()
|
||||
self.assertEqual(len(capture.output), 1)
|
||||
self.assertEqual(capture.records[0].levelno, logging.INFO)
|
||||
self.assertEqual(capture.records[0].message, "Something might be wrong")
|
||||
|
||||
def test_warning(self):
|
||||
messages = SanityCheckMessages()
|
||||
messages.warning("Something is wrong")
|
||||
self.assertEqual(len(messages), 1)
|
||||
self.assertFalse(messages.has_error())
|
||||
self.assertTrue(messages.has_warning())
|
||||
with self.assertLogs() as capture:
|
||||
messages.log_messages()
|
||||
self.assertEqual(len(capture.output), 1)
|
||||
self.assertEqual(capture.records[0].levelno, logging.WARNING)
|
||||
self.assertEqual(capture.records[0].message, "Something is wrong")
|
||||
|
||||
def test_error(self):
|
||||
messages = SanityCheckMessages()
|
||||
messages.error("Something is seriously wrong")
|
||||
self.assertEqual(len(messages), 1)
|
||||
self.assertTrue(messages.has_error())
|
||||
self.assertFalse(messages.has_warning())
|
||||
with self.assertLogs() as capture:
|
||||
messages.log_messages()
|
||||
self.assertEqual(len(capture.output), 1)
|
||||
self.assertEqual(capture.records[0].levelno, logging.ERROR)
|
||||
self.assertEqual(capture.records[0].message, "Something is seriously wrong")
|
||||
|
||||
class TestSanityCheck(DirectoriesMixin, TestCase):
|
||||
|
||||
def make_test_data(self):
|
||||
@@ -21,7 +71,12 @@ class TestSanityCheck(DirectoriesMixin, TestCase):
|
||||
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "documents", "archive", "0000001.pdf"), os.path.join(self.dirs.archive_dir, "0000001.pdf"))
|
||||
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "documents", "thumbnails", "0000001.png"), os.path.join(self.dirs.thumbnail_dir, "0000001.png"))
|
||||
|
||||
return Document.objects.create(title="test", checksum="42995833e01aea9b3edee44bbfdd7ce1", archive_checksum="62acb0bcbfbcaa62ca6ad3668e4e404b", content="test", pk=1, filename="0000001.pdf", mime_type="application/pdf")
|
||||
return Document.objects.create(title="test", checksum="42995833e01aea9b3edee44bbfdd7ce1", archive_checksum="62acb0bcbfbcaa62ca6ad3668e4e404b", content="test", pk=1, filename="0000001.pdf", mime_type="application/pdf", archive_filename="0000001.pdf")
|
||||
|
||||
def assertSanityError(self, messageRegex):
|
||||
messages = check_sanity()
|
||||
self.assertTrue(messages.has_error())
|
||||
self.assertRegex(messages[0]['message'], messageRegex)
|
||||
|
||||
def test_no_docs(self):
|
||||
self.assertEqual(len(check_sanity()), 0)
|
||||
@@ -33,59 +88,75 @@ class TestSanityCheck(DirectoriesMixin, TestCase):
|
||||
def test_no_thumbnail(self):
|
||||
doc = self.make_test_data()
|
||||
os.remove(doc.thumbnail_path)
|
||||
self.assertEqual(len(check_sanity()), 1)
|
||||
self.assertSanityError("Thumbnail of document .* does not exist")
|
||||
|
||||
def test_thumbnail_no_access(self):
|
||||
doc = self.make_test_data()
|
||||
os.chmod(doc.thumbnail_path, 0o000)
|
||||
self.assertEqual(len(check_sanity()), 1)
|
||||
self.assertSanityError("Cannot read thumbnail file of document")
|
||||
os.chmod(doc.thumbnail_path, 0o777)
|
||||
|
||||
def test_no_original(self):
|
||||
doc = self.make_test_data()
|
||||
os.remove(doc.source_path)
|
||||
self.assertEqual(len(check_sanity()), 1)
|
||||
self.assertSanityError("Original of document .* does not exist.")
|
||||
|
||||
def test_original_no_access(self):
|
||||
doc = self.make_test_data()
|
||||
os.chmod(doc.source_path, 0o000)
|
||||
self.assertEqual(len(check_sanity()), 1)
|
||||
self.assertSanityError("Cannot read original file of document")
|
||||
os.chmod(doc.source_path, 0o777)
|
||||
|
||||
def test_original_checksum_mismatch(self):
|
||||
doc = self.make_test_data()
|
||||
doc.checksum = "WOW"
|
||||
doc.save()
|
||||
self.assertEqual(len(check_sanity()), 1)
|
||||
self.assertSanityError("Checksum mismatch of document")
|
||||
|
||||
def test_no_archive(self):
|
||||
doc = self.make_test_data()
|
||||
os.remove(doc.archive_path)
|
||||
self.assertEqual(len(check_sanity()), 1)
|
||||
self.assertSanityError("Archived version of document .* does not exist.")
|
||||
|
||||
def test_archive_no_access(self):
|
||||
doc = self.make_test_data()
|
||||
os.chmod(doc.archive_path, 0o000)
|
||||
self.assertEqual(len(check_sanity()), 1)
|
||||
self.assertSanityError("Cannot read archive file of document")
|
||||
os.chmod(doc.archive_path, 0o777)
|
||||
|
||||
def test_archive_checksum_mismatch(self):
|
||||
doc = self.make_test_data()
|
||||
doc.archive_checksum = "WOW"
|
||||
doc.save()
|
||||
self.assertEqual(len(check_sanity()), 1)
|
||||
self.assertSanityError("Checksum mismatch of archived document")
|
||||
|
||||
def test_empty_content(self):
|
||||
doc = self.make_test_data()
|
||||
doc.content = ""
|
||||
doc.save()
|
||||
self.assertEqual(len(check_sanity()), 1)
|
||||
messages = check_sanity()
|
||||
self.assertFalse(messages.has_error())
|
||||
self.assertFalse(messages.has_warning())
|
||||
self.assertEqual(len(messages), 1)
|
||||
self.assertRegex(messages[0]['message'], "Document .* has no content.")
|
||||
|
||||
def test_orphaned_file(self):
|
||||
doc = self.make_test_data()
|
||||
Path(self.dirs.originals_dir, "orphaned").touch()
|
||||
self.assertEqual(len(check_sanity()), 1)
|
||||
messages = check_sanity()
|
||||
self.assertFalse(messages.has_error())
|
||||
self.assertTrue(messages.has_warning())
|
||||
self.assertEqual(len(messages), 1)
|
||||
self.assertRegex(messages[0]['message'], "Orphaned file in media dir")
|
||||
|
||||
def test_all(self):
|
||||
Document.objects.create(title="test", checksum="dgfhj", archive_checksum="dfhg", content="", pk=1, filename="0000001.pdf")
|
||||
string = str(SanityFailedError(check_sanity()))
|
||||
def test_archive_filename_no_checksum(self):
|
||||
doc = self.make_test_data()
|
||||
doc.archive_checksum = None
|
||||
doc.save()
|
||||
self.assertSanityError("has an archive file, but its checksum is missing.")
|
||||
|
||||
def test_archive_checksum_no_filename(self):
|
||||
doc = self.make_test_data()
|
||||
doc.archive_filename = None
|
||||
doc.save()
|
||||
self.assertSanityError("has an archive file checksum, but no archive filename.")
|
||||
|
||||
@@ -20,7 +20,7 @@ class TestSettings(TestCase):
|
||||
self.assertEqual(default_threads, 1)
|
||||
|
||||
def test_workers_threads(self):
|
||||
for i in range(2, 64):
|
||||
for i in range(1, 64):
|
||||
with mock.patch("paperless.settings.multiprocessing.cpu_count") as cpu_count:
|
||||
cpu_count.return_value = i
|
||||
|
||||
@@ -31,4 +31,4 @@ class TestSettings(TestCase):
|
||||
self.assertTrue(default_workers >= 1)
|
||||
self.assertTrue(default_threads >= 1)
|
||||
|
||||
self.assertTrue(default_workers * default_threads < i, f"{i}")
|
||||
self.assertTrue(default_workers * default_threads <= i, f"{i}")
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
from datetime import datetime
|
||||
import os
|
||||
from unittest import mock
|
||||
|
||||
from django.conf import settings
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
from documents import tasks
|
||||
from documents.models import Document
|
||||
from documents.sanity_checker import SanityError, SanityFailedError
|
||||
from documents.models import Document, Tag, Correspondent, DocumentType
|
||||
from documents.sanity_checker import SanityCheckMessages, SanityCheckFailedException
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
|
||||
|
||||
@@ -22,20 +23,87 @@ class TestTasks(DirectoriesMixin, TestCase):
|
||||
|
||||
tasks.index_optimize()
|
||||
|
||||
def test_train_classifier(self):
|
||||
@mock.patch("documents.tasks.load_classifier")
|
||||
def test_train_classifier_no_auto_matching(self, load_classifier):
|
||||
tasks.train_classifier()
|
||||
load_classifier.assert_not_called()
|
||||
|
||||
@mock.patch("documents.tasks.load_classifier")
|
||||
def test_train_classifier_with_auto_tag(self, load_classifier):
|
||||
load_classifier.return_value = None
|
||||
Tag.objects.create(matching_algorithm=Tag.MATCH_AUTO, name="test")
|
||||
tasks.train_classifier()
|
||||
load_classifier.assert_called_once()
|
||||
self.assertFalse(os.path.isfile(settings.MODEL_FILE))
|
||||
|
||||
@mock.patch("documents.tasks.load_classifier")
|
||||
def test_train_classifier_with_auto_type(self, load_classifier):
|
||||
load_classifier.return_value = None
|
||||
DocumentType.objects.create(matching_algorithm=Tag.MATCH_AUTO, name="test")
|
||||
tasks.train_classifier()
|
||||
load_classifier.assert_called_once()
|
||||
self.assertFalse(os.path.isfile(settings.MODEL_FILE))
|
||||
|
||||
@mock.patch("documents.tasks.load_classifier")
|
||||
def test_train_classifier_with_auto_correspondent(self, load_classifier):
|
||||
load_classifier.return_value = None
|
||||
Correspondent.objects.create(matching_algorithm=Tag.MATCH_AUTO, name="test")
|
||||
tasks.train_classifier()
|
||||
load_classifier.assert_called_once()
|
||||
self.assertFalse(os.path.isfile(settings.MODEL_FILE))
|
||||
|
||||
def test_train_classifier(self):
|
||||
c = Correspondent.objects.create(matching_algorithm=Tag.MATCH_AUTO, name="test")
|
||||
doc = Document.objects.create(correspondent=c, content="test", title="test")
|
||||
self.assertFalse(os.path.isfile(settings.MODEL_FILE))
|
||||
|
||||
tasks.train_classifier()
|
||||
self.assertTrue(os.path.isfile(settings.MODEL_FILE))
|
||||
mtime = os.stat(settings.MODEL_FILE).st_mtime
|
||||
|
||||
tasks.train_classifier()
|
||||
self.assertTrue(os.path.isfile(settings.MODEL_FILE))
|
||||
mtime2 = os.stat(settings.MODEL_FILE).st_mtime
|
||||
self.assertEqual(mtime, mtime2)
|
||||
|
||||
doc.content = "test2"
|
||||
doc.save()
|
||||
tasks.train_classifier()
|
||||
self.assertTrue(os.path.isfile(settings.MODEL_FILE))
|
||||
mtime3 = os.stat(settings.MODEL_FILE).st_mtime
|
||||
self.assertNotEqual(mtime2, mtime3)
|
||||
|
||||
@mock.patch("documents.tasks.sanity_checker.check_sanity")
|
||||
def test_sanity_check(self, m):
|
||||
m.return_value = []
|
||||
tasks.sanity_check()
|
||||
m.assert_called_once()
|
||||
m.reset_mock()
|
||||
m.return_value = [SanityError("")]
|
||||
self.assertRaises(SanityFailedError, tasks.sanity_check)
|
||||
def test_sanity_check_success(self, m):
|
||||
m.return_value = SanityCheckMessages()
|
||||
self.assertEqual(tasks.sanity_check(), "No issues detected.")
|
||||
m.assert_called_once()
|
||||
|
||||
def test_culk_update_documents(self):
|
||||
@mock.patch("documents.tasks.sanity_checker.check_sanity")
|
||||
def test_sanity_check_error(self, m):
|
||||
messages = SanityCheckMessages()
|
||||
messages.error("Some error")
|
||||
m.return_value = messages
|
||||
self.assertRaises(SanityCheckFailedException, tasks.sanity_check)
|
||||
m.assert_called_once()
|
||||
|
||||
@mock.patch("documents.tasks.sanity_checker.check_sanity")
|
||||
def test_sanity_check_warning(self, m):
|
||||
messages = SanityCheckMessages()
|
||||
messages.warning("Some warning")
|
||||
m.return_value = messages
|
||||
self.assertEqual(tasks.sanity_check(), "Sanity check exited with warnings. See log.")
|
||||
m.assert_called_once()
|
||||
|
||||
@mock.patch("documents.tasks.sanity_checker.check_sanity")
|
||||
def test_sanity_check_info(self, m):
|
||||
messages = SanityCheckMessages()
|
||||
messages.info("Some info")
|
||||
m.return_value = messages
|
||||
self.assertEqual(tasks.sanity_check(), "Sanity check exited with infos. See log.")
|
||||
m.assert_called_once()
|
||||
|
||||
def test_bulk_update_documents(self):
|
||||
doc1 = Document.objects.create(title="test", content="my document", checksum="wow", added=timezone.now(),
|
||||
created=timezone.now(), modified=timezone.now())
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class TestViews(TestCase):
|
||||
|
||||
def test_index(self):
|
||||
self.client.force_login(self.user)
|
||||
for (language_given, language_actual) in [("", "en-US"), ("en-US", "en-US"), ("de", "de"), ("en", "en-US"), ("en-us", "en-US"), ("fr", "fr"), ("jp", "en-US")]:
|
||||
for (language_given, language_actual) in [("", "en-US"), ("en-US", "en-US"), ("de", "de-DE"), ("en", "en-US"), ("en-us", "en-US"), ("fr", "fr-FR"), ("jp", "en-US")]:
|
||||
if language_given:
|
||||
self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: language_given})
|
||||
elif settings.LANGUAGE_COOKIE_NAME in self.client.cookies.keys():
|
||||
|
||||
@@ -4,7 +4,10 @@ import tempfile
|
||||
from collections import namedtuple
|
||||
from contextlib import contextmanager
|
||||
|
||||
from django.test import override_settings
|
||||
from django.apps import apps
|
||||
from django.db import connection
|
||||
from django.db.migrations.executor import MigrationExecutor
|
||||
from django.test import override_settings, TransactionTestCase
|
||||
|
||||
|
||||
def setup_directories():
|
||||
@@ -19,12 +22,15 @@ def setup_directories():
|
||||
dirs.originals_dir = os.path.join(dirs.media_dir, "documents", "originals")
|
||||
dirs.thumbnail_dir = os.path.join(dirs.media_dir, "documents", "thumbnails")
|
||||
dirs.archive_dir = os.path.join(dirs.media_dir, "documents", "archive")
|
||||
dirs.logging_dir = os.path.join(dirs.data_dir, "log")
|
||||
|
||||
os.makedirs(dirs.index_dir, exist_ok=True)
|
||||
os.makedirs(dirs.originals_dir, exist_ok=True)
|
||||
os.makedirs(dirs.thumbnail_dir, exist_ok=True)
|
||||
os.makedirs(dirs.archive_dir, exist_ok=True)
|
||||
|
||||
os.makedirs(dirs.logging_dir, exist_ok=True)
|
||||
|
||||
dirs.settings_override = override_settings(
|
||||
DATA_DIR=dirs.data_dir,
|
||||
SCRATCH_DIR=dirs.scratch_dir,
|
||||
@@ -33,6 +39,7 @@ def setup_directories():
|
||||
THUMBNAIL_DIR=dirs.thumbnail_dir,
|
||||
ARCHIVE_DIR=dirs.archive_dir,
|
||||
CONSUMPTION_DIR=dirs.consumption_dir,
|
||||
LOGGING_DIR=dirs.logging_dir,
|
||||
INDEX_DIR=dirs.index_dir,
|
||||
MODEL_FILE=os.path.join(dirs.data_dir, "classification_model.pickle"),
|
||||
MEDIA_LOCK=os.path.join(dirs.media_dir, "media.lock")
|
||||
@@ -75,3 +82,45 @@ class DirectoriesMixin:
|
||||
def tearDown(self) -> None:
|
||||
super(DirectoriesMixin, self).tearDown()
|
||||
remove_dirs(self.dirs)
|
||||
|
||||
|
||||
class TestMigrations(TransactionTestCase):
|
||||
|
||||
@property
|
||||
def app(self):
|
||||
return apps.get_containing_app_config(type(self).__module__).name
|
||||
|
||||
migrate_from = None
|
||||
migrate_to = None
|
||||
auto_migrate = True
|
||||
|
||||
def setUp(self):
|
||||
super(TestMigrations, self).setUp()
|
||||
|
||||
assert self.migrate_from and self.migrate_to, \
|
||||
"TestCase '{}' must define migrate_from and migrate_to properties".format(type(self).__name__)
|
||||
self.migrate_from = [(self.app, self.migrate_from)]
|
||||
self.migrate_to = [(self.app, self.migrate_to)]
|
||||
executor = MigrationExecutor(connection)
|
||||
old_apps = executor.loader.project_state(self.migrate_from).apps
|
||||
|
||||
# Reverse to the original migration
|
||||
executor.migrate(self.migrate_from)
|
||||
|
||||
self.setUpBeforeMigration(old_apps)
|
||||
|
||||
self.apps = old_apps
|
||||
|
||||
if self.auto_migrate:
|
||||
self.performMigration()
|
||||
|
||||
def performMigration(self):
|
||||
# Run the migration to test
|
||||
executor = MigrationExecutor(connection)
|
||||
executor.loader.build_graph() # reload.
|
||||
executor.migrate(self.migrate_to)
|
||||
|
||||
self.apps = executor.loader.project_state(self.migrate_to).apps
|
||||
|
||||
def setUpBeforeMigration(self, apps):
|
||||
pass
|
||||
|
||||
403
src/documents/views.py
Executable file → Normal file
403
src/documents/views.py
Executable file → Normal file
@@ -1,6 +1,8 @@
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
import uuid
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
from time import mktime
|
||||
|
||||
@@ -15,7 +17,9 @@ from django_filters.rest_framework import DjangoFilterBackend
|
||||
from django_q.tasks import async_task
|
||||
from rest_framework import parsers
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import NotFound
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.generics import GenericAPIView
|
||||
from rest_framework.mixins import (
|
||||
DestroyModelMixin,
|
||||
ListModelMixin,
|
||||
@@ -28,33 +32,40 @@ from rest_framework.views import APIView
|
||||
from rest_framework.viewsets import (
|
||||
GenericViewSet,
|
||||
ModelViewSet,
|
||||
ReadOnlyModelViewSet
|
||||
ViewSet
|
||||
)
|
||||
|
||||
import documents.index as index
|
||||
from paperless.db import GnuPG
|
||||
from paperless.views import StandardPagination
|
||||
from .bulk_download import OriginalAndArchiveStrategy, OriginalsOnlyStrategy, \
|
||||
ArchiveOnlyStrategy
|
||||
from .classifier import load_classifier
|
||||
from .filters import (
|
||||
CorrespondentFilterSet,
|
||||
DocumentFilterSet,
|
||||
TagFilterSet,
|
||||
DocumentTypeFilterSet,
|
||||
LogFilterSet
|
||||
DocumentTypeFilterSet
|
||||
)
|
||||
from .models import Correspondent, Document, Log, Tag, DocumentType, SavedView
|
||||
from .matching import match_correspondents, match_tags, match_document_types
|
||||
from .models import Correspondent, Document, Tag, DocumentType, SavedView
|
||||
from .parsers import get_parser_class_for_mime_type
|
||||
from .serialisers import (
|
||||
CorrespondentSerializer,
|
||||
DocumentSerializer,
|
||||
LogSerializer,
|
||||
TagSerializerVersion1,
|
||||
TagSerializer,
|
||||
DocumentTypeSerializer,
|
||||
PostDocumentSerializer,
|
||||
SavedViewSerializer,
|
||||
BulkEditSerializer, SelectionDataSerializer
|
||||
BulkEditSerializer,
|
||||
DocumentListSerializer,
|
||||
BulkDownloadSerializer
|
||||
)
|
||||
|
||||
|
||||
logger = logging.getLogger("paperless.api")
|
||||
|
||||
|
||||
class IndexView(TemplateView):
|
||||
template_name = "index.html"
|
||||
|
||||
@@ -81,6 +92,7 @@ class IndexView(TemplateView):
|
||||
context['polyfills_js'] = f"frontend/{self.get_language()}/polyfills.js" # NOQA: E501
|
||||
context['main_js'] = f"frontend/{self.get_language()}/main.js"
|
||||
context['webmanifest'] = f"frontend/{self.get_language()}/manifest.webmanifest" # NOQA: E501
|
||||
context['apple_touch_icon'] = f"frontend/{self.get_language()}/apple-touch-icon.png" # NOQA: E501
|
||||
return context
|
||||
|
||||
|
||||
@@ -110,7 +122,12 @@ class TagViewSet(ModelViewSet):
|
||||
queryset = Tag.objects.annotate(
|
||||
document_count=Count('documents')).order_by(Lower('name'))
|
||||
|
||||
serializer_class = TagSerializer
|
||||
def get_serializer_class(self):
|
||||
if int(self.request.version) == 1:
|
||||
return TagSerializerVersion1
|
||||
else:
|
||||
return TagSerializer
|
||||
|
||||
pagination_class = StandardPagination
|
||||
permission_classes = (IsAuthenticated,)
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||
@@ -132,10 +149,6 @@ class DocumentTypeViewSet(ModelViewSet):
|
||||
ordering_fields = ("name", "matching_algorithm", "match", "document_count")
|
||||
|
||||
|
||||
class BulkEditForm(object):
|
||||
pass
|
||||
|
||||
|
||||
class DocumentViewSet(RetrieveModelMixin,
|
||||
UpdateModelMixin,
|
||||
DestroyModelMixin,
|
||||
@@ -159,6 +172,9 @@ class DocumentViewSet(RetrieveModelMixin,
|
||||
"added",
|
||||
"archive_serial_number")
|
||||
|
||||
def get_queryset(self):
|
||||
return Document.objects.distinct()
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
fields_param = self.request.query_params.get('fields', None)
|
||||
if fields_param:
|
||||
@@ -173,10 +189,12 @@ class DocumentViewSet(RetrieveModelMixin,
|
||||
def update(self, request, *args, **kwargs):
|
||||
response = super(DocumentViewSet, self).update(
|
||||
request, *args, **kwargs)
|
||||
from documents import index
|
||||
index.add_or_update_document(self.get_object())
|
||||
return response
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
from documents import index
|
||||
index.remove_document_from_index(self.get_object())
|
||||
return super(DocumentViewSet, self).destroy(request, *args, **kwargs)
|
||||
|
||||
@@ -189,7 +207,7 @@ class DocumentViewSet(RetrieveModelMixin,
|
||||
|
||||
def file_response(self, pk, request, disposition):
|
||||
doc = Document.objects.get(id=pk)
|
||||
if not self.original_requested(request) and os.path.isfile(doc.archive_path): # NOQA: E501
|
||||
if not self.original_requested(request) and doc.has_archive_version: # NOQA: E501
|
||||
file_handle = doc.archive_file
|
||||
filename = doc.get_public_filename(archive=True)
|
||||
mime_type = 'application/pdf'
|
||||
@@ -212,7 +230,7 @@ class DocumentViewSet(RetrieveModelMixin,
|
||||
|
||||
parser_class = get_parser_class_for_mime_type(mime_type)
|
||||
if parser_class:
|
||||
parser = parser_class(logging_group=None)
|
||||
parser = parser_class(progress_callback=None, logging_group=None)
|
||||
|
||||
try:
|
||||
return parser.extract_metadata(file, mime_type)
|
||||
@@ -222,35 +240,60 @@ class DocumentViewSet(RetrieveModelMixin,
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_filesize(self, filename):
|
||||
if os.path.isfile(filename):
|
||||
return os.stat(filename).st_size
|
||||
else:
|
||||
return None
|
||||
|
||||
@action(methods=['get'], detail=True)
|
||||
def metadata(self, request, pk=None):
|
||||
try:
|
||||
doc = Document.objects.get(pk=pk)
|
||||
|
||||
meta = {
|
||||
"original_checksum": doc.checksum,
|
||||
"original_size": os.stat(doc.source_path).st_size,
|
||||
"original_mime_type": doc.mime_type,
|
||||
"media_filename": doc.filename,
|
||||
"has_archive_version": os.path.isfile(doc.archive_path),
|
||||
"original_metadata": self.get_metadata(
|
||||
doc.source_path, doc.mime_type)
|
||||
}
|
||||
|
||||
if doc.archive_checksum and os.path.isfile(doc.archive_path):
|
||||
meta['archive_checksum'] = doc.archive_checksum
|
||||
meta['archive_size'] = os.stat(doc.archive_path).st_size,
|
||||
meta['archive_metadata'] = self.get_metadata(
|
||||
doc.archive_path, "application/pdf")
|
||||
else:
|
||||
meta['archive_checksum'] = None
|
||||
meta['archive_size'] = None
|
||||
meta['archive_metadata'] = None
|
||||
|
||||
return Response(meta)
|
||||
except Document.DoesNotExist:
|
||||
raise Http404()
|
||||
|
||||
meta = {
|
||||
"original_checksum": doc.checksum,
|
||||
"original_size": self.get_filesize(doc.source_path),
|
||||
"original_mime_type": doc.mime_type,
|
||||
"media_filename": doc.filename,
|
||||
"has_archive_version": doc.has_archive_version,
|
||||
"original_metadata": self.get_metadata(
|
||||
doc.source_path, doc.mime_type),
|
||||
"archive_checksum": doc.archive_checksum,
|
||||
"archive_media_filename": doc.archive_filename
|
||||
}
|
||||
|
||||
if doc.has_archive_version:
|
||||
meta['archive_size'] = self.get_filesize(doc.archive_path)
|
||||
meta['archive_metadata'] = self.get_metadata(
|
||||
doc.archive_path, "application/pdf")
|
||||
else:
|
||||
meta['archive_size'] = None
|
||||
meta['archive_metadata'] = None
|
||||
|
||||
return Response(meta)
|
||||
|
||||
@action(methods=['get'], detail=True)
|
||||
def suggestions(self, request, pk=None):
|
||||
try:
|
||||
doc = Document.objects.get(pk=pk)
|
||||
except Document.DoesNotExist:
|
||||
raise Http404()
|
||||
|
||||
classifier = load_classifier()
|
||||
|
||||
return Response({
|
||||
"correspondents": [
|
||||
c.id for c in match_correspondents(doc, classifier)
|
||||
],
|
||||
"tags": [t.id for t in match_tags(doc, classifier)],
|
||||
"document_types": [
|
||||
dt.id for dt in match_document_types(doc, classifier)
|
||||
]
|
||||
})
|
||||
|
||||
@action(methods=['get'], detail=True)
|
||||
def preview(self, request, pk=None):
|
||||
try:
|
||||
@@ -269,6 +312,8 @@ class DocumentViewSet(RetrieveModelMixin,
|
||||
handle = GnuPG.decrypted(doc.thumbnail_file)
|
||||
else:
|
||||
handle = doc.thumbnail_file
|
||||
# TODO: Send ETag information and use that to send new thumbnails
|
||||
# if available
|
||||
return HttpResponse(handle,
|
||||
content_type='image/png')
|
||||
except (FileNotFoundError, Document.DoesNotExist):
|
||||
@@ -283,16 +328,92 @@ class DocumentViewSet(RetrieveModelMixin,
|
||||
raise Http404()
|
||||
|
||||
|
||||
class LogViewSet(ReadOnlyModelViewSet):
|
||||
model = Log
|
||||
class SearchResultSerializer(DocumentSerializer):
|
||||
|
||||
def to_representation(self, instance):
|
||||
doc = Document.objects.get(id=instance['id'])
|
||||
r = super(SearchResultSerializer, self).to_representation(doc)
|
||||
r['__search_hit__'] = {
|
||||
"score": instance.score,
|
||||
"highlights": instance.highlights("content",
|
||||
text=doc.content) if doc else None, # NOQA: E501
|
||||
"rank": instance.rank
|
||||
}
|
||||
|
||||
return r
|
||||
|
||||
|
||||
class UnifiedSearchViewSet(DocumentViewSet):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(UnifiedSearchViewSet, self).__init__(*args, **kwargs)
|
||||
self.searcher = None
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self._is_search_request():
|
||||
return SearchResultSerializer
|
||||
else:
|
||||
return DocumentSerializer
|
||||
|
||||
def _is_search_request(self):
|
||||
return ("query" in self.request.query_params or
|
||||
"more_like_id" in self.request.query_params)
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
if self._is_search_request():
|
||||
from documents import index
|
||||
|
||||
if "query" in self.request.query_params:
|
||||
query_class = index.DelayedFullTextQuery
|
||||
elif "more_like_id" in self.request.query_params:
|
||||
query_class = index.DelayedMoreLikeThisQuery
|
||||
else:
|
||||
raise ValueError()
|
||||
|
||||
return query_class(
|
||||
self.searcher,
|
||||
self.request.query_params,
|
||||
self.paginator.get_page_size(self.request))
|
||||
else:
|
||||
return super(UnifiedSearchViewSet, self).filter_queryset(queryset)
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
if self._is_search_request():
|
||||
from documents import index
|
||||
try:
|
||||
with index.open_index_searcher() as s:
|
||||
self.searcher = s
|
||||
return super(UnifiedSearchViewSet, self).list(request)
|
||||
except NotFound:
|
||||
raise
|
||||
except Exception as e:
|
||||
return HttpResponseBadRequest(str(e))
|
||||
else:
|
||||
return super(UnifiedSearchViewSet, self).list(request)
|
||||
|
||||
|
||||
class LogViewSet(ViewSet):
|
||||
|
||||
queryset = Log.objects.all()
|
||||
serializer_class = LogSerializer
|
||||
pagination_class = StandardPagination
|
||||
permission_classes = (IsAuthenticated,)
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||
filterset_class = LogFilterSet
|
||||
ordering_fields = ("created",)
|
||||
|
||||
log_files = ["paperless", "mail"]
|
||||
|
||||
def retrieve(self, request, pk=None, *args, **kwargs):
|
||||
if pk not in self.log_files:
|
||||
raise Http404()
|
||||
|
||||
filename = os.path.join(settings.LOGGING_DIR, f"{pk}.log")
|
||||
|
||||
if not os.path.isfile(filename):
|
||||
raise Http404()
|
||||
|
||||
with open(filename, "r") as f:
|
||||
lines = [line.rstrip() for line in f.readlines()]
|
||||
|
||||
return Response(lines)
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
return Response(self.log_files)
|
||||
|
||||
|
||||
class SavedViewViewSet(ModelViewSet):
|
||||
@@ -311,23 +432,12 @@ class SavedViewViewSet(ModelViewSet):
|
||||
serializer.save(user=self.request.user)
|
||||
|
||||
|
||||
class BulkEditView(APIView):
|
||||
class BulkEditView(GenericAPIView):
|
||||
|
||||
permission_classes = (IsAuthenticated,)
|
||||
serializer_class = BulkEditSerializer
|
||||
parser_classes = (parsers.JSONParser,)
|
||||
|
||||
def get_serializer_context(self):
|
||||
return {
|
||||
'request': self.request,
|
||||
'format': self.format_kwarg,
|
||||
'view': self
|
||||
}
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
kwargs['context'] = self.get_serializer_context()
|
||||
return self.serializer_class(*args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
@@ -344,23 +454,12 @@ class BulkEditView(APIView):
|
||||
return HttpResponseBadRequest(str(e))
|
||||
|
||||
|
||||
class PostDocumentView(APIView):
|
||||
class PostDocumentView(GenericAPIView):
|
||||
|
||||
permission_classes = (IsAuthenticated,)
|
||||
serializer_class = PostDocumentSerializer
|
||||
parser_classes = (parsers.MultiPartParser,)
|
||||
|
||||
def get_serializer_context(self):
|
||||
return {
|
||||
'request': self.request,
|
||||
'format': self.format_kwarg,
|
||||
'view': self
|
||||
}
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
kwargs['context'] = self.get_serializer_context()
|
||||
return self.serializer_class(*args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
@@ -381,35 +480,29 @@ class PostDocumentView(APIView):
|
||||
delete=False) as f:
|
||||
f.write(doc_data)
|
||||
os.utime(f.name, times=(t, t))
|
||||
temp_filename = f.name
|
||||
|
||||
task_id = str(uuid.uuid4())
|
||||
|
||||
async_task("documents.tasks.consume_file",
|
||||
temp_filename,
|
||||
override_filename=doc_name,
|
||||
override_title=title,
|
||||
override_correspondent_id=correspondent_id,
|
||||
override_document_type_id=document_type_id,
|
||||
override_tag_ids=tag_ids,
|
||||
task_id=task_id,
|
||||
task_name=os.path.basename(doc_name)[:100])
|
||||
|
||||
async_task("documents.tasks.consume_file",
|
||||
f.name,
|
||||
override_filename=doc_name,
|
||||
override_title=title,
|
||||
override_correspondent_id=correspondent_id,
|
||||
override_document_type_id=document_type_id,
|
||||
override_tag_ids=tag_ids,
|
||||
task_name=os.path.basename(doc_name)[:100])
|
||||
return Response("OK")
|
||||
|
||||
|
||||
class SelectionDataView(APIView):
|
||||
class SelectionDataView(GenericAPIView):
|
||||
|
||||
permission_classes = (IsAuthenticated,)
|
||||
serializer_class = SelectionDataSerializer
|
||||
serializer_class = DocumentListSerializer
|
||||
parser_classes = (parsers.MultiPartParser, parsers.JSONParser)
|
||||
|
||||
def get_serializer_context(self):
|
||||
return {
|
||||
'request': self.request,
|
||||
'format': self.format_kwarg,
|
||||
'view': self
|
||||
}
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
kwargs['context'] = self.get_serializer_context()
|
||||
return self.serializer_class(*args, **kwargs)
|
||||
|
||||
def post(self, request, format=None):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
@@ -450,83 +543,10 @@ class SelectionDataView(APIView):
|
||||
return r
|
||||
|
||||
|
||||
class SearchView(APIView):
|
||||
|
||||
permission_classes = (IsAuthenticated,)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SearchView, self).__init__(*args, **kwargs)
|
||||
self.ix = index.open_index()
|
||||
|
||||
def add_infos_to_hit(self, r):
|
||||
try:
|
||||
doc = Document.objects.get(id=r['id'])
|
||||
except Document.DoesNotExist:
|
||||
logging.getLogger(__name__).warning(
|
||||
f"Search index returned a non-existing document: "
|
||||
f"id: {r['id']}, title: {r['title']}. "
|
||||
f"Search index needs reindex."
|
||||
)
|
||||
doc = None
|
||||
|
||||
return {'id': r['id'],
|
||||
'highlights': r.highlights("content", text=doc.content) if doc else None, # NOQA: E501
|
||||
'score': r.score,
|
||||
'rank': r.rank,
|
||||
'document': DocumentSerializer(doc).data if doc else None,
|
||||
'title': r['title']
|
||||
}
|
||||
|
||||
def get(self, request, format=None):
|
||||
|
||||
if 'query' in request.query_params:
|
||||
query = request.query_params['query']
|
||||
else:
|
||||
query = None
|
||||
|
||||
if 'more_like' in request.query_params:
|
||||
more_like_id = request.query_params['more_like']
|
||||
more_like_content = Document.objects.get(id=more_like_id).content
|
||||
else:
|
||||
more_like_id = None
|
||||
more_like_content = None
|
||||
|
||||
if not query and not more_like_id:
|
||||
return Response({
|
||||
'count': 0,
|
||||
'page': 0,
|
||||
'page_count': 0,
|
||||
'corrected_query': None,
|
||||
'results': []})
|
||||
|
||||
try:
|
||||
page = int(request.query_params.get('page', 1))
|
||||
except (ValueError, TypeError):
|
||||
page = 1
|
||||
|
||||
if page < 1:
|
||||
page = 1
|
||||
|
||||
try:
|
||||
with index.query_page(self.ix, page, query, more_like_id, more_like_content) as (result_page, corrected_query): # NOQA: E501
|
||||
return Response(
|
||||
{'count': len(result_page),
|
||||
'page': result_page.pagenum,
|
||||
'page_count': result_page.pagecount,
|
||||
'corrected_query': corrected_query,
|
||||
'results': list(map(self.add_infos_to_hit, result_page))})
|
||||
except Exception as e:
|
||||
return HttpResponseBadRequest(str(e))
|
||||
|
||||
|
||||
class SearchAutoCompleteView(APIView):
|
||||
|
||||
permission_classes = (IsAuthenticated,)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SearchAutoCompleteView, self).__init__(*args, **kwargs)
|
||||
self.ix = index.open_index()
|
||||
|
||||
def get(self, request, format=None):
|
||||
if 'term' in request.query_params:
|
||||
term = request.query_params['term']
|
||||
@@ -540,7 +560,11 @@ class SearchAutoCompleteView(APIView):
|
||||
else:
|
||||
limit = 10
|
||||
|
||||
return Response(index.autocomplete(self.ix, term, limit))
|
||||
from documents import index
|
||||
|
||||
ix = index.open_index()
|
||||
|
||||
return Response(index.autocomplete(ix, term, limit))
|
||||
|
||||
|
||||
class StatisticsView(APIView):
|
||||
@@ -548,8 +572,55 @@ class StatisticsView(APIView):
|
||||
permission_classes = (IsAuthenticated,)
|
||||
|
||||
def get(self, request, format=None):
|
||||
return Response({
|
||||
'documents_total': Document.objects.all().count(),
|
||||
'documents_inbox': Document.objects.filter(
|
||||
documents_total = Document.objects.all().count()
|
||||
if Tag.objects.filter(is_inbox_tag=True).exists():
|
||||
documents_inbox = Document.objects.filter(
|
||||
tags__is_inbox_tag=True).distinct().count()
|
||||
else:
|
||||
documents_inbox = None
|
||||
|
||||
return Response({
|
||||
'documents_total': documents_total,
|
||||
'documents_inbox': documents_inbox,
|
||||
})
|
||||
|
||||
|
||||
class BulkDownloadView(GenericAPIView):
|
||||
|
||||
permission_classes = (IsAuthenticated,)
|
||||
serializer_class = BulkDownloadSerializer
|
||||
parser_classes = (parsers.JSONParser,)
|
||||
|
||||
def post(self, request, format=None):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
ids = serializer.validated_data.get('documents')
|
||||
compression = serializer.validated_data.get('compression')
|
||||
content = serializer.validated_data.get('content')
|
||||
|
||||
os.makedirs(settings.SCRATCH_DIR, exist_ok=True)
|
||||
temp = tempfile.NamedTemporaryFile(
|
||||
dir=settings.SCRATCH_DIR,
|
||||
suffix="-compressed-archive",
|
||||
delete=False)
|
||||
|
||||
if content == 'both':
|
||||
strategy_class = OriginalAndArchiveStrategy
|
||||
elif content == 'originals':
|
||||
strategy_class = OriginalsOnlyStrategy
|
||||
else:
|
||||
strategy_class = ArchiveOnlyStrategy
|
||||
|
||||
with zipfile.ZipFile(temp.name, "w", compression) as zipf:
|
||||
strategy = strategy_class(zipf)
|
||||
for id in ids:
|
||||
doc = Document.objects.get(id=id)
|
||||
strategy.add_document(doc)
|
||||
|
||||
with open(temp.name, "rb") as f:
|
||||
response = HttpResponse(f, content_type="application/zip")
|
||||
response["Content-Disposition"] = '{}; filename="{}"'.format(
|
||||
"attachment", "documents.zip")
|
||||
|
||||
return response
|
||||
|
||||
698
src/locale/cs_CZ/LC_MESSAGES/django.po
Normal file
698
src/locale/cs_CZ/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,698 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ng\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-05-16 09:38+0000\n"
|
||||
"PO-Revision-Date: 2021-05-16 10:09\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Czech\n"
|
||||
"Language: cs_CZ\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n"
|
||||
"X-Crowdin-Project: paperless-ng\n"
|
||||
"X-Crowdin-Project-ID: 434940\n"
|
||||
"X-Crowdin-Language: cs\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 54\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
msgstr "Dokumenty"
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr "Jakékoliv slovo"
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr "Všechna slova"
|
||||
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr "Přesná shoda"
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr "Regulární výraz"
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr "Fuzzy slovo"
|
||||
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr "Automatický"
|
||||
|
||||
#: documents/models.py:41 documents/models.py:350 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:117
|
||||
msgid "name"
|
||||
msgstr "název"
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "match"
|
||||
msgstr "shoda"
|
||||
|
||||
#: documents/models.py:49
|
||||
msgid "matching algorithm"
|
||||
msgstr "algoritmus pro shodu"
|
||||
|
||||
#: documents/models.py:55
|
||||
msgid "is insensitive"
|
||||
msgstr "je ignorováno"
|
||||
|
||||
#: documents/models.py:74 documents/models.py:120
|
||||
msgid "correspondent"
|
||||
msgstr "korespondent"
|
||||
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr "korespondenti"
|
||||
|
||||
#: documents/models.py:81
|
||||
msgid "color"
|
||||
msgstr "barva"
|
||||
|
||||
#: documents/models.py:87
|
||||
msgid "is inbox tag"
|
||||
msgstr "tag přichozí"
|
||||
|
||||
#: documents/models.py:89
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr "Označí tento tag jako tag pro příchozí: Všechny nově zkonzumované dokumenty budou označeny tagem pro přichozí"
|
||||
|
||||
#: documents/models.py:94
|
||||
msgid "tag"
|
||||
msgstr "tag"
|
||||
|
||||
#: documents/models.py:95 documents/models.py:151
|
||||
msgid "tags"
|
||||
msgstr "tagy"
|
||||
|
||||
#: documents/models.py:101 documents/models.py:133
|
||||
msgid "document type"
|
||||
msgstr "typ dokumentu"
|
||||
|
||||
#: documents/models.py:102
|
||||
msgid "document types"
|
||||
msgstr "typy dokumentu"
|
||||
|
||||
#: documents/models.py:110
|
||||
msgid "Unencrypted"
|
||||
msgstr "Nešifrované"
|
||||
|
||||
#: documents/models.py:111
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "Šifrované pomocí GNU Privacy Guard"
|
||||
|
||||
#: documents/models.py:124
|
||||
msgid "title"
|
||||
msgstr "titulek"
|
||||
|
||||
#: documents/models.py:137
|
||||
msgid "content"
|
||||
msgstr "obsah"
|
||||
|
||||
#: documents/models.py:139
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr "Nezpracovaná, pouze textová data dokumentu. Toto pole je používáno především pro vyhledávání."
|
||||
|
||||
#: documents/models.py:144
|
||||
msgid "mime type"
|
||||
msgstr "mime typ"
|
||||
|
||||
#: documents/models.py:155
|
||||
msgid "checksum"
|
||||
msgstr "kontrolní součet"
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid "The checksum of the original document."
|
||||
msgstr "Kontrolní součet původního dokumentu"
|
||||
|
||||
#: documents/models.py:163
|
||||
msgid "archive checksum"
|
||||
msgstr "kontrolní součet archivu"
|
||||
|
||||
#: documents/models.py:168
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr "Kontrolní součet archivovaného dokumentu."
|
||||
|
||||
#: documents/models.py:172 documents/models.py:328
|
||||
msgid "created"
|
||||
msgstr "vytvořeno"
|
||||
|
||||
#: documents/models.py:176
|
||||
msgid "modified"
|
||||
msgstr "upraveno"
|
||||
|
||||
#: documents/models.py:180
|
||||
msgid "storage type"
|
||||
msgstr "typ úložiště"
|
||||
|
||||
#: documents/models.py:188
|
||||
msgid "added"
|
||||
msgstr "přidáno"
|
||||
|
||||
#: documents/models.py:192
|
||||
msgid "filename"
|
||||
msgstr "název souboru"
|
||||
|
||||
#: documents/models.py:198
|
||||
msgid "Current filename in storage"
|
||||
msgstr "Aktuální název souboru v úložišti"
|
||||
|
||||
#: documents/models.py:202
|
||||
msgid "archive filename"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:208
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "archive serial number"
|
||||
msgstr "sériové číslo archivu"
|
||||
|
||||
#: documents/models.py:217
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr "Pozice dokumentu ve vašem archivu fyzických dokumentů"
|
||||
|
||||
#: documents/models.py:223
|
||||
msgid "document"
|
||||
msgstr "dokument"
|
||||
|
||||
#: documents/models.py:224
|
||||
msgid "documents"
|
||||
msgstr "dokumenty"
|
||||
|
||||
#: documents/models.py:311
|
||||
msgid "debug"
|
||||
msgstr "debug"
|
||||
|
||||
#: documents/models.py:312
|
||||
msgid "information"
|
||||
msgstr "informace"
|
||||
|
||||
#: documents/models.py:313
|
||||
msgid "warning"
|
||||
msgstr "varování"
|
||||
|
||||
#: documents/models.py:314
|
||||
msgid "error"
|
||||
msgstr "chyba"
|
||||
|
||||
#: documents/models.py:315
|
||||
msgid "critical"
|
||||
msgstr "kritická"
|
||||
|
||||
#: documents/models.py:319
|
||||
msgid "group"
|
||||
msgstr "skupina"
|
||||
|
||||
#: documents/models.py:322
|
||||
msgid "message"
|
||||
msgstr "zpráva"
|
||||
|
||||
#: documents/models.py:325
|
||||
msgid "level"
|
||||
msgstr "úroveň"
|
||||
|
||||
#: documents/models.py:332
|
||||
msgid "log"
|
||||
msgstr "záznam"
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "logs"
|
||||
msgstr "záznamy"
|
||||
|
||||
#: documents/models.py:344 documents/models.py:401
|
||||
msgid "saved view"
|
||||
msgstr "uložený pohled"
|
||||
|
||||
#: documents/models.py:345
|
||||
msgid "saved views"
|
||||
msgstr "uložené pohledy"
|
||||
|
||||
#: documents/models.py:348
|
||||
msgid "user"
|
||||
msgstr "uživatel"
|
||||
|
||||
#: documents/models.py:354
|
||||
msgid "show on dashboard"
|
||||
msgstr "zobrazit v dashboardu"
|
||||
|
||||
#: documents/models.py:357
|
||||
msgid "show in sidebar"
|
||||
msgstr "zobrazit v postranním menu"
|
||||
|
||||
#: documents/models.py:361
|
||||
msgid "sort field"
|
||||
msgstr "pole na řazení"
|
||||
|
||||
#: documents/models.py:367
|
||||
msgid "sort reverse"
|
||||
msgstr "třídit opačně"
|
||||
|
||||
#: documents/models.py:373
|
||||
msgid "title contains"
|
||||
msgstr "titulek obsahuje"
|
||||
|
||||
#: documents/models.py:374
|
||||
msgid "content contains"
|
||||
msgstr "obsah obsahuje"
|
||||
|
||||
#: documents/models.py:375
|
||||
msgid "ASN is"
|
||||
msgstr "ASN je"
|
||||
|
||||
#: documents/models.py:376
|
||||
msgid "correspondent is"
|
||||
msgstr "korespondent je"
|
||||
|
||||
#: documents/models.py:377
|
||||
msgid "document type is"
|
||||
msgstr "typ dokumentu je"
|
||||
|
||||
#: documents/models.py:378
|
||||
msgid "is in inbox"
|
||||
msgstr "je v příchozích"
|
||||
|
||||
#: documents/models.py:379
|
||||
msgid "has tag"
|
||||
msgstr "má tag"
|
||||
|
||||
#: documents/models.py:380
|
||||
msgid "has any tag"
|
||||
msgstr "má jakýkoliv tag"
|
||||
|
||||
#: documents/models.py:381
|
||||
msgid "created before"
|
||||
msgstr "vytvořeno před"
|
||||
|
||||
#: documents/models.py:382
|
||||
msgid "created after"
|
||||
msgstr "vytvořeno po"
|
||||
|
||||
#: documents/models.py:383
|
||||
msgid "created year is"
|
||||
msgstr "rok vytvoření je"
|
||||
|
||||
#: documents/models.py:384
|
||||
msgid "created month is"
|
||||
msgstr "měsíc vytvoření je"
|
||||
|
||||
#: documents/models.py:385
|
||||
msgid "created day is"
|
||||
msgstr "den vytvoření je"
|
||||
|
||||
#: documents/models.py:386
|
||||
msgid "added before"
|
||||
msgstr "přidáno před"
|
||||
|
||||
#: documents/models.py:387
|
||||
msgid "added after"
|
||||
msgstr "přidáno po"
|
||||
|
||||
#: documents/models.py:388
|
||||
msgid "modified before"
|
||||
msgstr "upraveno před"
|
||||
|
||||
#: documents/models.py:389
|
||||
msgid "modified after"
|
||||
msgstr "upraveno po"
|
||||
|
||||
#: documents/models.py:390
|
||||
msgid "does not have tag"
|
||||
msgstr "nemá tag"
|
||||
|
||||
#: documents/models.py:391
|
||||
msgid "does not have ASN"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:392
|
||||
msgid "title or content contains"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:393
|
||||
msgid "fulltext query"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:394
|
||||
msgid "more like this"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:405
|
||||
msgid "rule type"
|
||||
msgstr "typ pravidla"
|
||||
|
||||
#: documents/models.py:409
|
||||
msgid "value"
|
||||
msgstr "hodnota"
|
||||
|
||||
#: documents/models.py:415
|
||||
msgid "filter rule"
|
||||
msgstr "filtrovací pravidlo"
|
||||
|
||||
#: documents/models.py:416
|
||||
msgid "filter rules"
|
||||
msgstr "filtrovací pravidla"
|
||||
|
||||
#: documents/serialisers.py:53
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:177
|
||||
msgid "Invalid color."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:451
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Typ souboru %(type)s není podporován"
|
||||
|
||||
#: documents/templates/index.html:22
|
||||
msgid "Paperless-ng is loading..."
|
||||
msgstr "Paperless-ng se načítá..."
|
||||
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ng signed out"
|
||||
msgstr "Odhlášeno od Paperless-ng"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:45
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr "Byli jste úspěšně odhlášeni. Nashledanou!"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:46
|
||||
msgid "Sign in again"
|
||||
msgstr "Přihlašte se znovu"
|
||||
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ng sign in"
|
||||
msgstr "Paperless-ng přihlášení"
|
||||
|
||||
#: documents/templates/registration/login.html:47
|
||||
msgid "Please sign in."
|
||||
msgstr "Prosím přihlaste se."
|
||||
|
||||
#: documents/templates/registration/login.html:50
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr "Vaše uživatelské jméno a heslo se neshodují. Prosím, zkuste to znovu."
|
||||
|
||||
#: documents/templates/registration/login.html:53
|
||||
msgid "Username"
|
||||
msgstr "Uživatelské jméno"
|
||||
|
||||
#: documents/templates/registration/login.html:54
|
||||
msgid "Password"
|
||||
msgstr "Heslo"
|
||||
|
||||
#: documents/templates/registration/login.html:59
|
||||
msgid "Sign in"
|
||||
msgstr "Přihlásit se"
|
||||
|
||||
#: paperless/settings.py:303
|
||||
msgid "English (US)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:304
|
||||
msgid "English (GB)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:305
|
||||
msgid "German"
|
||||
msgstr "Němčina"
|
||||
|
||||
#: paperless/settings.py:306
|
||||
msgid "Dutch"
|
||||
msgstr "Holandština"
|
||||
|
||||
#: paperless/settings.py:307
|
||||
msgid "French"
|
||||
msgstr "Francouzština"
|
||||
|
||||
#: paperless/settings.py:308
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:309
|
||||
msgid "Portuguese"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Romanian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:312
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:313
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:314
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:315
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/urls.py:120
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr "Správa Paperless-ng"
|
||||
|
||||
#: paperless_mail/admin.py:15
|
||||
msgid "Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:18
|
||||
msgid "Advanced settings"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
msgid "Filter"
|
||||
msgstr "Filtr"
|
||||
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr "Paperless zpracuje pouze emaily které odpovídají VŠEM níže zadaným filtrům."
|
||||
|
||||
#: paperless_mail/admin.py:49
|
||||
msgid "Actions"
|
||||
msgstr "Akce"
|
||||
|
||||
#: paperless_mail/admin.py:51
|
||||
msgid "The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched."
|
||||
msgstr "Akce provedena na emailu. Tato akce je provedena jen pokud byly dokumenty zkonzumovány z emailu. Emaily bez příloh zůstanou nedotčeny."
|
||||
|
||||
#: paperless_mail/admin.py:58
|
||||
msgid "Metadata"
|
||||
msgstr "Metadata"
|
||||
|
||||
#: paperless_mail/admin.py:60
|
||||
msgid "Assign metadata to documents consumed from this rule automatically. If you do not assign tags, types or correspondents here, paperless will still process all matching rules that you have defined."
|
||||
msgstr "Automaticky přiřadit metadata dokumentům zkonzumovaných z tohoto pravidla. Pokud zde nepřiřadíte tagy, typy nebo korespondenty, paperless stále zpracuje všechna shodující-se pravidla které jste definovali."
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
msgstr "Paperless pošta"
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail account"
|
||||
msgstr "emailový účet"
|
||||
|
||||
#: paperless_mail/models.py:12
|
||||
msgid "mail accounts"
|
||||
msgstr "emailové účty"
|
||||
|
||||
#: paperless_mail/models.py:19
|
||||
msgid "No encryption"
|
||||
msgstr "Žádné šifrování"
|
||||
|
||||
#: paperless_mail/models.py:20
|
||||
msgid "Use SSL"
|
||||
msgstr "Používat SSL"
|
||||
|
||||
#: paperless_mail/models.py:21
|
||||
msgid "Use STARTTLS"
|
||||
msgstr "Používat STARTTLS"
|
||||
|
||||
#: paperless_mail/models.py:29
|
||||
msgid "IMAP server"
|
||||
msgstr "IMAP server"
|
||||
|
||||
#: paperless_mail/models.py:33
|
||||
msgid "IMAP port"
|
||||
msgstr "IMAP port"
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr "Toto je většinou 143 pro nešifrovaná připojení/připojení používající STARTTLS a 993 pro SSL připojení."
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
msgid "IMAP security"
|
||||
msgstr "IMAP bezpečnost"
|
||||
|
||||
#: paperless_mail/models.py:46
|
||||
msgid "username"
|
||||
msgstr "uživatelské jméno"
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "password"
|
||||
msgstr "heslo"
|
||||
|
||||
#: paperless_mail/models.py:54
|
||||
msgid "character set"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:57
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "mail rule"
|
||||
msgstr "mailové pravidlo"
|
||||
|
||||
#: paperless_mail/models.py:69
|
||||
msgid "mail rules"
|
||||
msgstr "mailová pravidla"
|
||||
|
||||
#: paperless_mail/models.py:75
|
||||
msgid "Only process attachments."
|
||||
msgstr "Zpracovávat jen přílohy"
|
||||
|
||||
#: paperless_mail/models.py:76
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr "Zpracovat všechny soubory, včetně vložených příloh"
|
||||
|
||||
#: paperless_mail/models.py:86
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr "Označit jako přečtené, nezpracovávat přečtené emaily"
|
||||
|
||||
#: paperless_mail/models.py:87
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr "Označit email, nezpracovávat označené emaily"
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "Move to specified folder"
|
||||
msgstr "Přesunout do specifikované složky"
|
||||
|
||||
#: paperless_mail/models.py:89
|
||||
msgid "Delete"
|
||||
msgstr "Odstranit"
|
||||
|
||||
#: paperless_mail/models.py:96
|
||||
msgid "Use subject as title"
|
||||
msgstr "Použít předmět jako titulek"
|
||||
|
||||
#: paperless_mail/models.py:97
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr "Použít název souboru u přílohy jako titulek"
|
||||
|
||||
#: paperless_mail/models.py:107
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr "Nepřiřazovat korespondenta"
|
||||
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "Use mail address"
|
||||
msgstr "Použít emailovou adresu"
|
||||
|
||||
#: paperless_mail/models.py:111
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr "Použít jméno (nebo emailovou adresu pokud jméno není dostupné)"
|
||||
|
||||
#: paperless_mail/models.py:113
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr "Použít korespondenta vybraného níže"
|
||||
|
||||
#: paperless_mail/models.py:121
|
||||
msgid "order"
|
||||
msgstr "pořadí"
|
||||
|
||||
#: paperless_mail/models.py:128
|
||||
msgid "account"
|
||||
msgstr "účet"
|
||||
|
||||
#: paperless_mail/models.py:132
|
||||
msgid "folder"
|
||||
msgstr "složka"
|
||||
|
||||
#: paperless_mail/models.py:134
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
msgid "filter from"
|
||||
msgstr "filtrovat z"
|
||||
|
||||
#: paperless_mail/models.py:141
|
||||
msgid "filter subject"
|
||||
msgstr "název filtru"
|
||||
|
||||
#: paperless_mail/models.py:144
|
||||
msgid "filter body"
|
||||
msgstr "tělo filtru"
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "filter attachment filename"
|
||||
msgstr "název souboru u přílohy filtru"
|
||||
|
||||
#: paperless_mail/models.py:150
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr "Konzumovat jen dokumenty které přesně odpovídají tomuto názvu souboru pokud specifikováno. Zástupné znaky jako *.pdf nebo *invoice* jsou povoleny. Nezáleží na velikosti písmen."
|
||||
|
||||
#: paperless_mail/models.py:156
|
||||
msgid "maximum age"
|
||||
msgstr "maximální stáří"
|
||||
|
||||
#: paperless_mail/models.py:158
|
||||
msgid "Specified in days."
|
||||
msgstr "Specifikováno ve dnech."
|
||||
|
||||
#: paperless_mail/models.py:161
|
||||
msgid "attachment type"
|
||||
msgstr "typ přílohy"
|
||||
|
||||
#: paperless_mail/models.py:164
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr "Vložené přílohy zahrnují vložené obrázky, takže je nejlepší tuto možnost kombinovat s filtrem na název souboru"
|
||||
|
||||
#: paperless_mail/models.py:169
|
||||
msgid "action"
|
||||
msgstr "akce"
|
||||
|
||||
#: paperless_mail/models.py:175
|
||||
msgid "action parameter"
|
||||
msgstr "parametr akce"
|
||||
|
||||
#: paperless_mail/models.py:177
|
||||
msgid "Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:184
|
||||
msgid "assign title from"
|
||||
msgstr "nastavit titulek z"
|
||||
|
||||
#: paperless_mail/models.py:194
|
||||
msgid "assign this tag"
|
||||
msgstr "přiřadit tento tag"
|
||||
|
||||
#: paperless_mail/models.py:202
|
||||
msgid "assign this document type"
|
||||
msgstr "přiřadit tento typ dokumentu"
|
||||
|
||||
#: paperless_mail/models.py:206
|
||||
msgid "assign correspondent from"
|
||||
msgstr "přiřadit korespondenta z"
|
||||
|
||||
#: paperless_mail/models.py:216
|
||||
msgid "assign this correspondent"
|
||||
msgstr "přiřadit tohoto korespondenta"
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Jonas Winkler, 2021
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Project-Id-Version: paperless-ng\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-01-10 21:41+0000\n"
|
||||
"PO-Revision-Date: 2020-12-30 19:27+0000\n"
|
||||
"Last-Translator: Jonas Winkler, 2021\n"
|
||||
"Language-Team: German (https://www.transifex.com/paperless/teams/115905/de/)\n"
|
||||
"POT-Creation-Date: 2021-05-16 09:38+0000\n"
|
||||
"PO-Revision-Date: 2021-07-05 11:17\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"Language: de_DE\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: de\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: paperless-ng\n"
|
||||
"X-Crowdin-Project-ID: 434940\n"
|
||||
"X-Crowdin-Language: de\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 54\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
@@ -39,7 +35,7 @@ msgstr "Exakte Übereinstimmung"
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr "Regulärer Ausdruck"
|
||||
msgstr "Regular expression / Reguläre Ausdrücke"
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
@@ -49,8 +45,8 @@ msgstr "Ungenaues Wort"
|
||||
msgid "Automatic"
|
||||
msgstr "Automatisch"
|
||||
|
||||
#: documents/models.py:41 documents/models.py:354 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:109
|
||||
#: documents/models.py:41 documents/models.py:350 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:117
|
||||
msgid "name"
|
||||
msgstr "Name"
|
||||
|
||||
@@ -66,387 +62,443 @@ msgstr "Zuweisungsalgorithmus"
|
||||
msgid "is insensitive"
|
||||
msgstr "Groß-/Kleinschreibung irrelevant"
|
||||
|
||||
#: documents/models.py:80 documents/models.py:140
|
||||
#: documents/models.py:74 documents/models.py:120
|
||||
msgid "correspondent"
|
||||
msgstr "Korrespondent"
|
||||
|
||||
#: documents/models.py:81
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr "Korrespondenten"
|
||||
|
||||
#: documents/models.py:103
|
||||
#: documents/models.py:81
|
||||
msgid "color"
|
||||
msgstr "Farbe"
|
||||
|
||||
#: documents/models.py:107
|
||||
#: documents/models.py:87
|
||||
msgid "is inbox tag"
|
||||
msgstr "Posteingangs-Tag"
|
||||
|
||||
#: documents/models.py:109
|
||||
msgid ""
|
||||
"Marks this tag as an inbox tag: All newly consumed documents will be tagged "
|
||||
"with inbox tags."
|
||||
msgstr ""
|
||||
"Markiert das Tag als Posteingangs-Tag. Neue Dokumente werden immer mit "
|
||||
"diesem Tag versehen."
|
||||
#: documents/models.py:89
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr "Markiert das Tag als Posteingangs-Tag. Neue Dokumente werden immer mit diesem Tag versehen."
|
||||
|
||||
#: documents/models.py:114
|
||||
#: documents/models.py:94
|
||||
msgid "tag"
|
||||
msgstr "Tag"
|
||||
|
||||
#: documents/models.py:115 documents/models.py:171
|
||||
#: documents/models.py:95 documents/models.py:151
|
||||
msgid "tags"
|
||||
msgstr "Tags"
|
||||
|
||||
#: documents/models.py:121 documents/models.py:153
|
||||
#: documents/models.py:101 documents/models.py:133
|
||||
msgid "document type"
|
||||
msgstr "Dokumenttyp"
|
||||
|
||||
#: documents/models.py:122
|
||||
#: documents/models.py:102
|
||||
msgid "document types"
|
||||
msgstr "Dokumenttypen"
|
||||
|
||||
#: documents/models.py:130
|
||||
#: documents/models.py:110
|
||||
msgid "Unencrypted"
|
||||
msgstr "Nicht verschlüsselt"
|
||||
|
||||
#: documents/models.py:131
|
||||
#: documents/models.py:111
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "Verschlüsselt mit GNU Privacy Guard"
|
||||
|
||||
#: documents/models.py:144
|
||||
#: documents/models.py:124
|
||||
msgid "title"
|
||||
msgstr "Titel"
|
||||
|
||||
#: documents/models.py:157
|
||||
#: documents/models.py:137
|
||||
msgid "content"
|
||||
msgstr "Inhalt"
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid ""
|
||||
"The raw, text-only data of the document. This field is primarily used for "
|
||||
"searching."
|
||||
msgstr ""
|
||||
"Der Inhalt des Dokuments in Textform. Dieses Feld wird primär für die Suche "
|
||||
"verwendet."
|
||||
#: documents/models.py:139
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr "Der Inhalt des Dokuments in Textform. Dieses Feld wird primär für die Suche verwendet."
|
||||
|
||||
#: documents/models.py:164
|
||||
#: documents/models.py:144
|
||||
msgid "mime type"
|
||||
msgstr "MIME-Typ"
|
||||
|
||||
#: documents/models.py:175
|
||||
#: documents/models.py:155
|
||||
msgid "checksum"
|
||||
msgstr "Prüfsumme"
|
||||
|
||||
#: documents/models.py:179
|
||||
#: documents/models.py:159
|
||||
msgid "The checksum of the original document."
|
||||
msgstr "Die Prüfsumme des originalen Dokuments."
|
||||
|
||||
#: documents/models.py:183
|
||||
#: documents/models.py:163
|
||||
msgid "archive checksum"
|
||||
msgstr "Archiv-Prüfsumme"
|
||||
|
||||
#: documents/models.py:188
|
||||
#: documents/models.py:168
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr "Die Prüfsumme des archivierten Dokuments."
|
||||
|
||||
#: documents/models.py:192 documents/models.py:332
|
||||
#: documents/models.py:172 documents/models.py:328
|
||||
msgid "created"
|
||||
msgstr "Erstellt"
|
||||
msgstr "Ausgestellt"
|
||||
|
||||
#: documents/models.py:196
|
||||
#: documents/models.py:176
|
||||
msgid "modified"
|
||||
msgstr "Geändert"
|
||||
|
||||
#: documents/models.py:200
|
||||
#: documents/models.py:180
|
||||
msgid "storage type"
|
||||
msgstr "Speichertyp"
|
||||
|
||||
#: documents/models.py:208
|
||||
#: documents/models.py:188
|
||||
msgid "added"
|
||||
msgstr "Hinzugefügt"
|
||||
|
||||
#: documents/models.py:212
|
||||
#: documents/models.py:192
|
||||
msgid "filename"
|
||||
msgstr "Dateiname"
|
||||
|
||||
#: documents/models.py:217
|
||||
#: documents/models.py:198
|
||||
msgid "Current filename in storage"
|
||||
msgstr "Aktueller Dateiname im Datenspeicher"
|
||||
|
||||
#: documents/models.py:221
|
||||
#: documents/models.py:202
|
||||
msgid "archive filename"
|
||||
msgstr "Archiv-Dateiname"
|
||||
|
||||
#: documents/models.py:208
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr "Aktueller Dateiname im Archiv"
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "archive serial number"
|
||||
msgstr "Archiv-Seriennummer"
|
||||
|
||||
#: documents/models.py:226
|
||||
#: documents/models.py:217
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr "Die Position dieses Dokuments in Ihrem physischen Dokumentenarchiv."
|
||||
|
||||
#: documents/models.py:232
|
||||
#: documents/models.py:223
|
||||
msgid "document"
|
||||
msgstr "Dokument"
|
||||
|
||||
#: documents/models.py:233
|
||||
#: documents/models.py:224
|
||||
msgid "documents"
|
||||
msgstr "Dokumente"
|
||||
|
||||
#: documents/models.py:315
|
||||
#: documents/models.py:311
|
||||
msgid "debug"
|
||||
msgstr "Debug"
|
||||
|
||||
#: documents/models.py:316
|
||||
#: documents/models.py:312
|
||||
msgid "information"
|
||||
msgstr "Information"
|
||||
|
||||
#: documents/models.py:317
|
||||
#: documents/models.py:313
|
||||
msgid "warning"
|
||||
msgstr "Warnung"
|
||||
|
||||
#: documents/models.py:318
|
||||
#: documents/models.py:314
|
||||
msgid "error"
|
||||
msgstr "Fehler"
|
||||
|
||||
#: documents/models.py:319
|
||||
#: documents/models.py:315
|
||||
msgid "critical"
|
||||
msgstr "Kritisch"
|
||||
|
||||
#: documents/models.py:323
|
||||
#: documents/models.py:319
|
||||
msgid "group"
|
||||
msgstr "Gruppe"
|
||||
|
||||
#: documents/models.py:326
|
||||
#: documents/models.py:322
|
||||
msgid "message"
|
||||
msgstr "Nachricht"
|
||||
|
||||
#: documents/models.py:329
|
||||
#: documents/models.py:325
|
||||
msgid "level"
|
||||
msgstr "Level"
|
||||
|
||||
#: documents/models.py:336
|
||||
#: documents/models.py:332
|
||||
msgid "log"
|
||||
msgstr "Protokoll"
|
||||
|
||||
#: documents/models.py:337
|
||||
#: documents/models.py:333
|
||||
msgid "logs"
|
||||
msgstr "Protokoll"
|
||||
|
||||
#: documents/models.py:348 documents/models.py:398
|
||||
#: documents/models.py:344 documents/models.py:401
|
||||
msgid "saved view"
|
||||
msgstr "Gespeicherte Ansicht"
|
||||
|
||||
#: documents/models.py:349
|
||||
#: documents/models.py:345
|
||||
msgid "saved views"
|
||||
msgstr "Gespeicherte Ansichten"
|
||||
|
||||
#: documents/models.py:352
|
||||
#: documents/models.py:348
|
||||
msgid "user"
|
||||
msgstr "Benutzer"
|
||||
|
||||
#: documents/models.py:358
|
||||
#: documents/models.py:354
|
||||
msgid "show on dashboard"
|
||||
msgstr "Auf Startseite zeigen"
|
||||
|
||||
#: documents/models.py:361
|
||||
#: documents/models.py:357
|
||||
msgid "show in sidebar"
|
||||
msgstr "In Seitenleiste zeigen"
|
||||
|
||||
#: documents/models.py:365
|
||||
#: documents/models.py:361
|
||||
msgid "sort field"
|
||||
msgstr "Sortierfeld"
|
||||
|
||||
#: documents/models.py:368
|
||||
#: documents/models.py:367
|
||||
msgid "sort reverse"
|
||||
msgstr "Umgekehrte Sortierung"
|
||||
|
||||
#: documents/models.py:374
|
||||
#: documents/models.py:373
|
||||
msgid "title contains"
|
||||
msgstr "Titel enthält"
|
||||
|
||||
#: documents/models.py:375
|
||||
#: documents/models.py:374
|
||||
msgid "content contains"
|
||||
msgstr "Inhalt enthält"
|
||||
|
||||
#: documents/models.py:376
|
||||
#: documents/models.py:375
|
||||
msgid "ASN is"
|
||||
msgstr "ASN ist"
|
||||
|
||||
#: documents/models.py:377
|
||||
#: documents/models.py:376
|
||||
msgid "correspondent is"
|
||||
msgstr "Korrespondent ist"
|
||||
|
||||
#: documents/models.py:378
|
||||
#: documents/models.py:377
|
||||
msgid "document type is"
|
||||
msgstr "Dokumenttyp ist"
|
||||
|
||||
#: documents/models.py:379
|
||||
#: documents/models.py:378
|
||||
msgid "is in inbox"
|
||||
msgstr "Ist im Posteingang"
|
||||
|
||||
#: documents/models.py:380
|
||||
#: documents/models.py:379
|
||||
msgid "has tag"
|
||||
msgstr "Hat Tag"
|
||||
|
||||
#: documents/models.py:381
|
||||
#: documents/models.py:380
|
||||
msgid "has any tag"
|
||||
msgstr "Hat irgendein Tag"
|
||||
|
||||
#: documents/models.py:382
|
||||
#: documents/models.py:381
|
||||
msgid "created before"
|
||||
msgstr "Erstellt vor"
|
||||
msgstr "Ausgestellt vor"
|
||||
|
||||
#: documents/models.py:382
|
||||
msgid "created after"
|
||||
msgstr "Ausgestellt nach"
|
||||
|
||||
#: documents/models.py:383
|
||||
msgid "created after"
|
||||
msgstr "Erstellt nach"
|
||||
msgid "created year is"
|
||||
msgstr "Ausgestellt im Jahr"
|
||||
|
||||
#: documents/models.py:384
|
||||
msgid "created year is"
|
||||
msgstr "Erstellt im Jahr"
|
||||
msgid "created month is"
|
||||
msgstr "Ausgestellt im Monat"
|
||||
|
||||
#: documents/models.py:385
|
||||
msgid "created month is"
|
||||
msgstr "Erstellt im Monat"
|
||||
msgid "created day is"
|
||||
msgstr "Ausgestellt am Tag"
|
||||
|
||||
#: documents/models.py:386
|
||||
msgid "created day is"
|
||||
msgstr "Erstellt am Tag"
|
||||
|
||||
#: documents/models.py:387
|
||||
msgid "added before"
|
||||
msgstr "Hinzugefügt vor"
|
||||
|
||||
#: documents/models.py:388
|
||||
#: documents/models.py:387
|
||||
msgid "added after"
|
||||
msgstr "Hinzugefügt nach"
|
||||
|
||||
#: documents/models.py:389
|
||||
#: documents/models.py:388
|
||||
msgid "modified before"
|
||||
msgstr "Geändert vor"
|
||||
|
||||
#: documents/models.py:390
|
||||
#: documents/models.py:389
|
||||
msgid "modified after"
|
||||
msgstr "Geändert nach"
|
||||
|
||||
#: documents/models.py:391
|
||||
#: documents/models.py:390
|
||||
msgid "does not have tag"
|
||||
msgstr "Hat nicht folgendes Tag"
|
||||
|
||||
#: documents/models.py:402
|
||||
#: documents/models.py:391
|
||||
msgid "does not have ASN"
|
||||
msgstr "Dokument hat keine ASN"
|
||||
|
||||
#: documents/models.py:392
|
||||
msgid "title or content contains"
|
||||
msgstr "Titel oder Inhalt enthält"
|
||||
|
||||
#: documents/models.py:393
|
||||
msgid "fulltext query"
|
||||
msgstr "Volltextsuche"
|
||||
|
||||
#: documents/models.py:394
|
||||
msgid "more like this"
|
||||
msgstr "Ähnliche Dokumente"
|
||||
|
||||
#: documents/models.py:405
|
||||
msgid "rule type"
|
||||
msgstr "Regeltyp"
|
||||
|
||||
#: documents/models.py:406
|
||||
#: documents/models.py:409
|
||||
msgid "value"
|
||||
msgstr "Wert"
|
||||
|
||||
#: documents/models.py:412
|
||||
#: documents/models.py:415
|
||||
msgid "filter rule"
|
||||
msgstr "Filterregel"
|
||||
|
||||
#: documents/models.py:413
|
||||
#: documents/models.py:416
|
||||
msgid "filter rules"
|
||||
msgstr "Filterregeln"
|
||||
|
||||
#: documents/templates/index.html:20
|
||||
#: documents/serialisers.py:53
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr "Ungültiger regulärer Ausdruck: %(error)s"
|
||||
|
||||
#: documents/serialisers.py:177
|
||||
msgid "Invalid color."
|
||||
msgstr "Ungültige Farbe."
|
||||
|
||||
#: documents/serialisers.py:451
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Dateityp %(type)s nicht unterstützt"
|
||||
|
||||
#: documents/templates/index.html:22
|
||||
msgid "Paperless-ng is loading..."
|
||||
msgstr "Paperless-ng wird geladen..."
|
||||
|
||||
#: documents/templates/registration/logged_out.html:13
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ng signed out"
|
||||
msgstr "Paperless-ng abgemeldet"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:41
|
||||
#: documents/templates/registration/logged_out.html:45
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr "Sie wurden erfolgreich abgemeldet. Auf Wiedersehen!"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:42
|
||||
#: documents/templates/registration/logged_out.html:46
|
||||
msgid "Sign in again"
|
||||
msgstr "Erneut anmelden"
|
||||
|
||||
#: documents/templates/registration/login.html:13
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ng sign in"
|
||||
msgstr "Paperless-ng Anmeldung"
|
||||
|
||||
#: documents/templates/registration/login.html:42
|
||||
#: documents/templates/registration/login.html:47
|
||||
msgid "Please sign in."
|
||||
msgstr "Bitte melden Sie sich an."
|
||||
|
||||
#: documents/templates/registration/login.html:45
|
||||
#: documents/templates/registration/login.html:50
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr ""
|
||||
"Ihr Benutzername und Passwort stimmen nicht überein. Bitte versuchen Sie es "
|
||||
"erneut."
|
||||
msgstr "Ihr Benutzername und Kennwort stimmen nicht überein. Bitte versuchen Sie es erneut."
|
||||
|
||||
#: documents/templates/registration/login.html:48
|
||||
#: documents/templates/registration/login.html:53
|
||||
msgid "Username"
|
||||
msgstr "Benutzername"
|
||||
|
||||
#: documents/templates/registration/login.html:49
|
||||
msgid "Password"
|
||||
msgstr "Passwort"
|
||||
|
||||
#: documents/templates/registration/login.html:54
|
||||
msgid "Password"
|
||||
msgstr "Kennwort"
|
||||
|
||||
#: documents/templates/registration/login.html:59
|
||||
msgid "Sign in"
|
||||
msgstr "Anmelden"
|
||||
|
||||
#: paperless/settings.py:268
|
||||
msgid "English"
|
||||
msgstr "Englisch"
|
||||
#: paperless/settings.py:303
|
||||
msgid "English (US)"
|
||||
msgstr "Englisch (US)"
|
||||
|
||||
#: paperless/settings.py:269
|
||||
#: paperless/settings.py:304
|
||||
msgid "English (GB)"
|
||||
msgstr "Englisch (UK)"
|
||||
|
||||
#: paperless/settings.py:305
|
||||
msgid "German"
|
||||
msgstr "Deutsch"
|
||||
|
||||
#: paperless/settings.py:270
|
||||
#: paperless/settings.py:306
|
||||
msgid "Dutch"
|
||||
msgstr "Niederländisch"
|
||||
|
||||
#: paperless/settings.py:271
|
||||
#: paperless/settings.py:307
|
||||
msgid "French"
|
||||
msgstr "Französisch"
|
||||
|
||||
#: paperless/urls.py:108
|
||||
#: paperless/settings.py:308
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Portugiesisch (Brasilien)"
|
||||
|
||||
#: paperless/settings.py:309
|
||||
msgid "Portuguese"
|
||||
msgstr "Portugiesisch"
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Italian"
|
||||
msgstr "Italienisch"
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Romanian"
|
||||
msgstr "Rumänisch"
|
||||
|
||||
#: paperless/settings.py:312
|
||||
msgid "Russian"
|
||||
msgstr "Russisch"
|
||||
|
||||
#: paperless/settings.py:313
|
||||
msgid "Spanish"
|
||||
msgstr "Spanisch"
|
||||
|
||||
#: paperless/settings.py:314
|
||||
msgid "Polish"
|
||||
msgstr "Polnisch"
|
||||
|
||||
#: paperless/settings.py:315
|
||||
msgid "Swedish"
|
||||
msgstr "Schwedisch"
|
||||
|
||||
#: paperless/urls.py:120
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr "Paperless-ng Administration"
|
||||
|
||||
#: paperless_mail/admin.py:25
|
||||
#: paperless_mail/admin.py:15
|
||||
msgid "Authentication"
|
||||
msgstr "Authentifizierung"
|
||||
|
||||
#: paperless_mail/admin.py:18
|
||||
msgid "Advanced settings"
|
||||
msgstr "Erweiterte Einstellungen"
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
msgid "Filter"
|
||||
msgstr "Filter"
|
||||
|
||||
#: paperless_mail/admin.py:27
|
||||
msgid ""
|
||||
"Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr ""
|
||||
"Paperless wird nur E-Mails verarbeiten, für die alle der hier angegebenen "
|
||||
"Filter zutreffen."
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr "Paperless wird nur E-Mails verarbeiten, für die alle der hier angegebenen Filter zutreffen."
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
#: paperless_mail/admin.py:49
|
||||
msgid "Actions"
|
||||
msgstr "Aktionen"
|
||||
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid ""
|
||||
"The action applied to the mail. This action is only performed when documents"
|
||||
" were consumed from the mail. Mails without attachments will remain entirely"
|
||||
" untouched."
|
||||
msgstr ""
|
||||
"Die Aktion, die auf E-Mails angewendet werden soll. Diese Aktion wird nur "
|
||||
"auf E-Mails angewendet, aus denen Anhänge verarbeitet wurden. E-Mails ohne "
|
||||
"Anhänge werden vollständig ignoriert."
|
||||
#: paperless_mail/admin.py:51
|
||||
msgid "The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched."
|
||||
msgstr "Die Aktion, die auf E-Mails angewendet werden soll. Diese Aktion wird nur auf E-Mails angewendet, aus denen Anhänge verarbeitet wurden. E-Mails ohne Anhänge werden vollständig ignoriert."
|
||||
|
||||
#: paperless_mail/admin.py:46
|
||||
#: paperless_mail/admin.py:58
|
||||
msgid "Metadata"
|
||||
msgstr "Metadaten"
|
||||
|
||||
#: paperless_mail/admin.py:48
|
||||
msgid ""
|
||||
"Assign metadata to documents consumed from this rule automatically. If you "
|
||||
"do not assign tags, types or correspondents here, paperless will still "
|
||||
"process all matching rules that you have defined."
|
||||
msgstr ""
|
||||
"Folgende Metadaten werden Dokumenten dieser Regel automatisch zugewiesen. "
|
||||
"Wenn Sie hier nichts auswählen wird Paperless weiterhin alle "
|
||||
"Zuweisungsalgorithmen ausführen und Metadaten auf Basis des Dokumentinhalts "
|
||||
"zuweisen."
|
||||
#: paperless_mail/admin.py:60
|
||||
msgid "Assign metadata to documents consumed from this rule automatically. If you do not assign tags, types or correspondents here, paperless will still process all matching rules that you have defined."
|
||||
msgstr "Folgende Metadaten werden Dokumenten dieser Regel automatisch zugewiesen. Wenn Sie hier nichts auswählen wird Paperless weiterhin alle Zuweisungsalgorithmen ausführen und Metadaten auf Basis des Dokumentinhalts zuweisen."
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
@@ -481,12 +533,8 @@ msgid "IMAP port"
|
||||
msgstr "IMAP-Port"
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid ""
|
||||
"This is usually 143 for unencrypted and STARTTLS connections, and 993 for "
|
||||
"SSL connections."
|
||||
msgstr ""
|
||||
"Dies ist in der Regel 143 für unverschlüsselte und STARTTLS-Verbindungen und"
|
||||
" 993 für SSL-Verbindungen."
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr "Dies ist in der Regel 143 für unverschlüsselte und STARTTLS-Verbindungen und 993 für SSL-Verbindungen."
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
msgid "IMAP security"
|
||||
@@ -498,153 +546,153 @@ msgstr "Benutzername"
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "password"
|
||||
msgstr "Passwort"
|
||||
msgstr "Kennwort"
|
||||
|
||||
#: paperless_mail/models.py:60
|
||||
#: paperless_mail/models.py:54
|
||||
msgid "character set"
|
||||
msgstr "Zeichensatz"
|
||||
|
||||
#: paperless_mail/models.py:57
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
msgstr "Der Zeichensatz, der bei der Kommunikation mit dem Mailserver verwendet werden soll, wie z.B. 'UTF-8' oder 'US-ASCII'."
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "mail rule"
|
||||
msgstr "E-Mail-Regel"
|
||||
|
||||
#: paperless_mail/models.py:61
|
||||
#: paperless_mail/models.py:69
|
||||
msgid "mail rules"
|
||||
msgstr "E-Mail-Regeln"
|
||||
|
||||
#: paperless_mail/models.py:67
|
||||
#: paperless_mail/models.py:75
|
||||
msgid "Only process attachments."
|
||||
msgstr "Nur Anhänge verarbeiten."
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
#: paperless_mail/models.py:76
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr "Alle Dateien verarbeiten, auch 'inline'-Anhänge."
|
||||
|
||||
#: paperless_mail/models.py:78
|
||||
#: paperless_mail/models.py:86
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr "Als gelesen markieren, gelesene E-Mails nicht verarbeiten"
|
||||
|
||||
#: paperless_mail/models.py:79
|
||||
#: paperless_mail/models.py:87
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr "Als wichtig markieren, markierte E-Mails nicht verarbeiten"
|
||||
|
||||
#: paperless_mail/models.py:80
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "Move to specified folder"
|
||||
msgstr "In angegebenen Ordner verschieben"
|
||||
|
||||
#: paperless_mail/models.py:81
|
||||
#: paperless_mail/models.py:89
|
||||
msgid "Delete"
|
||||
msgstr "Löschen"
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
#: paperless_mail/models.py:96
|
||||
msgid "Use subject as title"
|
||||
msgstr "Betreff als Titel verwenden"
|
||||
|
||||
#: paperless_mail/models.py:89
|
||||
#: paperless_mail/models.py:97
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr "Dateiname des Anhangs als Titel verwenden"
|
||||
|
||||
#: paperless_mail/models.py:99
|
||||
#: paperless_mail/models.py:107
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr "Keinen Korrespondenten zuweisen"
|
||||
|
||||
#: paperless_mail/models.py:101
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "Use mail address"
|
||||
msgstr "E-Mail-Adresse benutzen"
|
||||
|
||||
#: paperless_mail/models.py:103
|
||||
#: paperless_mail/models.py:111
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr "Absendername benutzen (oder E-Mail-Adressen, wenn nicht verfügbar)"
|
||||
|
||||
#: paperless_mail/models.py:105
|
||||
#: paperless_mail/models.py:113
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr "Nachfolgend ausgewählten Korrespondent verwenden"
|
||||
|
||||
#: paperless_mail/models.py:113
|
||||
#: paperless_mail/models.py:121
|
||||
msgid "order"
|
||||
msgstr "Reihenfolge"
|
||||
|
||||
#: paperless_mail/models.py:120
|
||||
#: paperless_mail/models.py:128
|
||||
msgid "account"
|
||||
msgstr "Konto"
|
||||
|
||||
#: paperless_mail/models.py:124
|
||||
#: paperless_mail/models.py:132
|
||||
msgid "folder"
|
||||
msgstr "Ordner"
|
||||
|
||||
#: paperless_mail/models.py:128
|
||||
#: paperless_mail/models.py:134
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr "Unterordner müssen durch Punkte getrennt werden."
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
msgid "filter from"
|
||||
msgstr "Absender filtern"
|
||||
|
||||
#: paperless_mail/models.py:131
|
||||
#: paperless_mail/models.py:141
|
||||
msgid "filter subject"
|
||||
msgstr "Betreff filtern"
|
||||
|
||||
#: paperless_mail/models.py:134
|
||||
#: paperless_mail/models.py:144
|
||||
msgid "filter body"
|
||||
msgstr "Nachrichteninhalt filtern"
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "filter attachment filename"
|
||||
msgstr "Anhang-Dateiname filtern"
|
||||
|
||||
#: paperless_mail/models.py:140
|
||||
msgid ""
|
||||
"Only consume documents which entirely match this filename if specified. "
|
||||
"Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr ""
|
||||
"Wenn angegeben werden nur Dateien verarbeitet, die diesem Dateinamen exakt "
|
||||
"entsprechen. Platzhalter wie *.pdf oder *rechnung* sind erlaubt. Groß- und "
|
||||
"Kleinschreibung ist irrelevant."
|
||||
#: paperless_mail/models.py:150
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr "Wenn angegeben werden nur Dateien verarbeitet, die diesem Dateinamen exakt entsprechen. Platzhalter wie *.pdf oder *rechnung* sind erlaubt. Groß- und Kleinschreibung ist irrelevant."
|
||||
|
||||
#: paperless_mail/models.py:146
|
||||
#: paperless_mail/models.py:156
|
||||
msgid "maximum age"
|
||||
msgstr "Maximales Alter"
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
#: paperless_mail/models.py:158
|
||||
msgid "Specified in days."
|
||||
msgstr "Angegeben in Tagen."
|
||||
|
||||
#: paperless_mail/models.py:151
|
||||
#: paperless_mail/models.py:161
|
||||
msgid "attachment type"
|
||||
msgstr "Dateianhangstyp"
|
||||
|
||||
#: paperless_mail/models.py:154
|
||||
msgid ""
|
||||
"Inline attachments include embedded images, so it's best to combine this "
|
||||
"option with a filename filter."
|
||||
msgstr ""
|
||||
"'Inline'-Anhänge schließen eingebettete Bilder mit ein, daher sollte diese "
|
||||
"Einstellung mit einem Dateinamenfilter kombiniert werden."
|
||||
#: paperless_mail/models.py:164
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr "'Inline'-Anhänge schließen eingebettete Bilder mit ein, daher sollte diese Einstellung mit einem Dateinamenfilter kombiniert werden."
|
||||
|
||||
#: paperless_mail/models.py:159
|
||||
#: paperless_mail/models.py:169
|
||||
msgid "action"
|
||||
msgstr "Aktion"
|
||||
|
||||
#: paperless_mail/models.py:165
|
||||
#: paperless_mail/models.py:175
|
||||
msgid "action parameter"
|
||||
msgstr "Parameter für Aktion"
|
||||
|
||||
#: paperless_mail/models.py:167
|
||||
msgid ""
|
||||
"Additional parameter for the action selected above, i.e., the target folder "
|
||||
"of the move to folder action."
|
||||
msgstr ""
|
||||
"Zusätzlicher Parameter für die oben ausgewählte Aktion, zum Beispiel der "
|
||||
"Zielordner für die Aktion \"In angegebenen Ordner verschieben\""
|
||||
#: paperless_mail/models.py:177
|
||||
msgid "Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots."
|
||||
msgstr "Zusätzlicher Parameter für die oben ausgewählte Aktion, zum Beispiel der Zielordner für die Aktion \"In angegebenen Ordner verschieben\". Unterordner müssen durch Punkte getrennt werden."
|
||||
|
||||
#: paperless_mail/models.py:173
|
||||
#: paperless_mail/models.py:184
|
||||
msgid "assign title from"
|
||||
msgstr "Titel zuweisen von"
|
||||
|
||||
#: paperless_mail/models.py:183
|
||||
#: paperless_mail/models.py:194
|
||||
msgid "assign this tag"
|
||||
msgstr "Dieses Tag zuweisen"
|
||||
|
||||
#: paperless_mail/models.py:191
|
||||
#: paperless_mail/models.py:202
|
||||
msgid "assign this document type"
|
||||
msgstr "Diesen Dokumenttyp zuweisen"
|
||||
|
||||
#: paperless_mail/models.py:195
|
||||
#: paperless_mail/models.py:206
|
||||
msgid "assign correspondent from"
|
||||
msgstr "Korrespondent zuweisen von"
|
||||
|
||||
#: paperless_mail/models.py:205
|
||||
#: paperless_mail/models.py:216
|
||||
msgid "assign this correspondent"
|
||||
msgstr "Diesen Korrespondent zuweisen"
|
||||
|
||||
698
src/locale/en_GB/LC_MESSAGES/django.po
Normal file
698
src/locale/en_GB/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,698 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ng\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-05-16 09:38+0000\n"
|
||||
"PO-Revision-Date: 2021-06-19 21:20\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: English, United Kingdom\n"
|
||||
"Language: en_GB\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: paperless-ng\n"
|
||||
"X-Crowdin-Project-ID: 434940\n"
|
||||
"X-Crowdin-Language: en-GB\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 54\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
msgstr "Documents"
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr "Any word"
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr "All words"
|
||||
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr "Exact match"
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr "Regular expression"
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr "Fuzzy word"
|
||||
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr "Automatic"
|
||||
|
||||
#: documents/models.py:41 documents/models.py:350 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:117
|
||||
msgid "name"
|
||||
msgstr "name"
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "match"
|
||||
msgstr "match"
|
||||
|
||||
#: documents/models.py:49
|
||||
msgid "matching algorithm"
|
||||
msgstr "matching algorithm"
|
||||
|
||||
#: documents/models.py:55
|
||||
msgid "is insensitive"
|
||||
msgstr "is insensitive"
|
||||
|
||||
#: documents/models.py:74 documents/models.py:120
|
||||
msgid "correspondent"
|
||||
msgstr "correspondent"
|
||||
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr "correspondents"
|
||||
|
||||
#: documents/models.py:81
|
||||
msgid "color"
|
||||
msgstr "colour"
|
||||
|
||||
#: documents/models.py:87
|
||||
msgid "is inbox tag"
|
||||
msgstr "is inbox tag"
|
||||
|
||||
#: documents/models.py:89
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
|
||||
#: documents/models.py:94
|
||||
msgid "tag"
|
||||
msgstr "tag"
|
||||
|
||||
#: documents/models.py:95 documents/models.py:151
|
||||
msgid "tags"
|
||||
msgstr "tags"
|
||||
|
||||
#: documents/models.py:101 documents/models.py:133
|
||||
msgid "document type"
|
||||
msgstr "document type"
|
||||
|
||||
#: documents/models.py:102
|
||||
msgid "document types"
|
||||
msgstr "document types"
|
||||
|
||||
#: documents/models.py:110
|
||||
msgid "Unencrypted"
|
||||
msgstr "Unencrypted"
|
||||
|
||||
#: documents/models.py:111
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "Encrypted with GNU Privacy Guard"
|
||||
|
||||
#: documents/models.py:124
|
||||
msgid "title"
|
||||
msgstr "title"
|
||||
|
||||
#: documents/models.py:137
|
||||
msgid "content"
|
||||
msgstr "content"
|
||||
|
||||
#: documents/models.py:139
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
|
||||
#: documents/models.py:144
|
||||
msgid "mime type"
|
||||
msgstr "mime type"
|
||||
|
||||
#: documents/models.py:155
|
||||
msgid "checksum"
|
||||
msgstr "checksum"
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid "The checksum of the original document."
|
||||
msgstr "The checksum of the original document."
|
||||
|
||||
#: documents/models.py:163
|
||||
msgid "archive checksum"
|
||||
msgstr "archive checksum"
|
||||
|
||||
#: documents/models.py:168
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr "The checksum of the archived document."
|
||||
|
||||
#: documents/models.py:172 documents/models.py:328
|
||||
msgid "created"
|
||||
msgstr "created"
|
||||
|
||||
#: documents/models.py:176
|
||||
msgid "modified"
|
||||
msgstr "modified"
|
||||
|
||||
#: documents/models.py:180
|
||||
msgid "storage type"
|
||||
msgstr "storage type"
|
||||
|
||||
#: documents/models.py:188
|
||||
msgid "added"
|
||||
msgstr "added"
|
||||
|
||||
#: documents/models.py:192
|
||||
msgid "filename"
|
||||
msgstr "filename"
|
||||
|
||||
#: documents/models.py:198
|
||||
msgid "Current filename in storage"
|
||||
msgstr "Current filename in storage"
|
||||
|
||||
#: documents/models.py:202
|
||||
msgid "archive filename"
|
||||
msgstr "archive filename"
|
||||
|
||||
#: documents/models.py:208
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr "Current archive filename in storage"
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "archive serial number"
|
||||
msgstr "archive serial number"
|
||||
|
||||
#: documents/models.py:217
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr "The position of this document in your physical document archive."
|
||||
|
||||
#: documents/models.py:223
|
||||
msgid "document"
|
||||
msgstr "document"
|
||||
|
||||
#: documents/models.py:224
|
||||
msgid "documents"
|
||||
msgstr "documents"
|
||||
|
||||
#: documents/models.py:311
|
||||
msgid "debug"
|
||||
msgstr "debug"
|
||||
|
||||
#: documents/models.py:312
|
||||
msgid "information"
|
||||
msgstr "information"
|
||||
|
||||
#: documents/models.py:313
|
||||
msgid "warning"
|
||||
msgstr "warning"
|
||||
|
||||
#: documents/models.py:314
|
||||
msgid "error"
|
||||
msgstr "error"
|
||||
|
||||
#: documents/models.py:315
|
||||
msgid "critical"
|
||||
msgstr "critical"
|
||||
|
||||
#: documents/models.py:319
|
||||
msgid "group"
|
||||
msgstr "group"
|
||||
|
||||
#: documents/models.py:322
|
||||
msgid "message"
|
||||
msgstr "message"
|
||||
|
||||
#: documents/models.py:325
|
||||
msgid "level"
|
||||
msgstr "level"
|
||||
|
||||
#: documents/models.py:332
|
||||
msgid "log"
|
||||
msgstr "log"
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "logs"
|
||||
msgstr "logs"
|
||||
|
||||
#: documents/models.py:344 documents/models.py:401
|
||||
msgid "saved view"
|
||||
msgstr "saved view"
|
||||
|
||||
#: documents/models.py:345
|
||||
msgid "saved views"
|
||||
msgstr "saved views"
|
||||
|
||||
#: documents/models.py:348
|
||||
msgid "user"
|
||||
msgstr "user"
|
||||
|
||||
#: documents/models.py:354
|
||||
msgid "show on dashboard"
|
||||
msgstr "show on dashboard"
|
||||
|
||||
#: documents/models.py:357
|
||||
msgid "show in sidebar"
|
||||
msgstr "show in sidebar"
|
||||
|
||||
#: documents/models.py:361
|
||||
msgid "sort field"
|
||||
msgstr "sort field"
|
||||
|
||||
#: documents/models.py:367
|
||||
msgid "sort reverse"
|
||||
msgstr "sort reverse"
|
||||
|
||||
#: documents/models.py:373
|
||||
msgid "title contains"
|
||||
msgstr "title contains"
|
||||
|
||||
#: documents/models.py:374
|
||||
msgid "content contains"
|
||||
msgstr "content contains"
|
||||
|
||||
#: documents/models.py:375
|
||||
msgid "ASN is"
|
||||
msgstr "ASN is"
|
||||
|
||||
#: documents/models.py:376
|
||||
msgid "correspondent is"
|
||||
msgstr "correspondent is"
|
||||
|
||||
#: documents/models.py:377
|
||||
msgid "document type is"
|
||||
msgstr "document type is"
|
||||
|
||||
#: documents/models.py:378
|
||||
msgid "is in inbox"
|
||||
msgstr "is in inbox"
|
||||
|
||||
#: documents/models.py:379
|
||||
msgid "has tag"
|
||||
msgstr "has tag"
|
||||
|
||||
#: documents/models.py:380
|
||||
msgid "has any tag"
|
||||
msgstr "has any tag"
|
||||
|
||||
#: documents/models.py:381
|
||||
msgid "created before"
|
||||
msgstr "created before"
|
||||
|
||||
#: documents/models.py:382
|
||||
msgid "created after"
|
||||
msgstr "created after"
|
||||
|
||||
#: documents/models.py:383
|
||||
msgid "created year is"
|
||||
msgstr "created year is"
|
||||
|
||||
#: documents/models.py:384
|
||||
msgid "created month is"
|
||||
msgstr "created month is"
|
||||
|
||||
#: documents/models.py:385
|
||||
msgid "created day is"
|
||||
msgstr "created day is"
|
||||
|
||||
#: documents/models.py:386
|
||||
msgid "added before"
|
||||
msgstr "added before"
|
||||
|
||||
#: documents/models.py:387
|
||||
msgid "added after"
|
||||
msgstr "added after"
|
||||
|
||||
#: documents/models.py:388
|
||||
msgid "modified before"
|
||||
msgstr "modified before"
|
||||
|
||||
#: documents/models.py:389
|
||||
msgid "modified after"
|
||||
msgstr "modified after"
|
||||
|
||||
#: documents/models.py:390
|
||||
msgid "does not have tag"
|
||||
msgstr "does not have tag"
|
||||
|
||||
#: documents/models.py:391
|
||||
msgid "does not have ASN"
|
||||
msgstr "does not have ASN"
|
||||
|
||||
#: documents/models.py:392
|
||||
msgid "title or content contains"
|
||||
msgstr "title or content contains"
|
||||
|
||||
#: documents/models.py:393
|
||||
msgid "fulltext query"
|
||||
msgstr "fulltext query"
|
||||
|
||||
#: documents/models.py:394
|
||||
msgid "more like this"
|
||||
msgstr "more like this"
|
||||
|
||||
#: documents/models.py:405
|
||||
msgid "rule type"
|
||||
msgstr "rule type"
|
||||
|
||||
#: documents/models.py:409
|
||||
msgid "value"
|
||||
msgstr "value"
|
||||
|
||||
#: documents/models.py:415
|
||||
msgid "filter rule"
|
||||
msgstr "filter rule"
|
||||
|
||||
#: documents/models.py:416
|
||||
msgid "filter rules"
|
||||
msgstr "filter rules"
|
||||
|
||||
#: documents/serialisers.py:53
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr "Invalid regular expression: %(error)s"
|
||||
|
||||
#: documents/serialisers.py:177
|
||||
msgid "Invalid color."
|
||||
msgstr "Invalid colour."
|
||||
|
||||
#: documents/serialisers.py:451
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "File type %(type)s not supported"
|
||||
|
||||
#: documents/templates/index.html:22
|
||||
msgid "Paperless-ng is loading..."
|
||||
msgstr "Paperless-ng is loading..."
|
||||
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ng signed out"
|
||||
msgstr "Paperless-ng signed out"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:45
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr "You have been successfully logged out. Bye!"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:46
|
||||
msgid "Sign in again"
|
||||
msgstr "Sign in again"
|
||||
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ng sign in"
|
||||
msgstr "Paperless-ng sign in"
|
||||
|
||||
#: documents/templates/registration/login.html:47
|
||||
msgid "Please sign in."
|
||||
msgstr "Please sign in."
|
||||
|
||||
#: documents/templates/registration/login.html:50
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr "Your username and password didn't match. Please try again."
|
||||
|
||||
#: documents/templates/registration/login.html:53
|
||||
msgid "Username"
|
||||
msgstr "Username"
|
||||
|
||||
#: documents/templates/registration/login.html:54
|
||||
msgid "Password"
|
||||
msgstr "Password"
|
||||
|
||||
#: documents/templates/registration/login.html:59
|
||||
msgid "Sign in"
|
||||
msgstr "Sign in"
|
||||
|
||||
#: paperless/settings.py:303
|
||||
msgid "English (US)"
|
||||
msgstr "English (US)"
|
||||
|
||||
#: paperless/settings.py:304
|
||||
msgid "English (GB)"
|
||||
msgstr "English (GB)"
|
||||
|
||||
#: paperless/settings.py:305
|
||||
msgid "German"
|
||||
msgstr "German"
|
||||
|
||||
#: paperless/settings.py:306
|
||||
msgid "Dutch"
|
||||
msgstr "Dutch"
|
||||
|
||||
#: paperless/settings.py:307
|
||||
msgid "French"
|
||||
msgstr "French"
|
||||
|
||||
#: paperless/settings.py:308
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Portuguese (Brazil)"
|
||||
|
||||
#: paperless/settings.py:309
|
||||
msgid "Portuguese"
|
||||
msgstr "Portuguese"
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Italian"
|
||||
msgstr "Italian"
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Romanian"
|
||||
msgstr "Romanian"
|
||||
|
||||
#: paperless/settings.py:312
|
||||
msgid "Russian"
|
||||
msgstr "Russian"
|
||||
|
||||
#: paperless/settings.py:313
|
||||
msgid "Spanish"
|
||||
msgstr "Spanish"
|
||||
|
||||
#: paperless/settings.py:314
|
||||
msgid "Polish"
|
||||
msgstr "Polish"
|
||||
|
||||
#: paperless/settings.py:315
|
||||
msgid "Swedish"
|
||||
msgstr "Swedish"
|
||||
|
||||
#: paperless/urls.py:120
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr "Paperless-ng administration"
|
||||
|
||||
#: paperless_mail/admin.py:15
|
||||
msgid "Authentication"
|
||||
msgstr "Authentication"
|
||||
|
||||
#: paperless_mail/admin.py:18
|
||||
msgid "Advanced settings"
|
||||
msgstr "Advanced settings"
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
msgid "Filter"
|
||||
msgstr "Filter"
|
||||
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr "Paperless will only process mails that match ALL of the filters given below."
|
||||
|
||||
#: paperless_mail/admin.py:49
|
||||
msgid "Actions"
|
||||
msgstr "Actions"
|
||||
|
||||
#: paperless_mail/admin.py:51
|
||||
msgid "The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched."
|
||||
msgstr "The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched."
|
||||
|
||||
#: paperless_mail/admin.py:58
|
||||
msgid "Metadata"
|
||||
msgstr "Metadata"
|
||||
|
||||
#: paperless_mail/admin.py:60
|
||||
msgid "Assign metadata to documents consumed from this rule automatically. If you do not assign tags, types or correspondents here, paperless will still process all matching rules that you have defined."
|
||||
msgstr "Assign metadata to documents consumed from this rule automatically. If you do not assign tags, types or correspondents here, paperless will still process all matching rules that you have defined."
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
msgstr "Paperless mail"
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail account"
|
||||
msgstr "mail account"
|
||||
|
||||
#: paperless_mail/models.py:12
|
||||
msgid "mail accounts"
|
||||
msgstr "mail accounts"
|
||||
|
||||
#: paperless_mail/models.py:19
|
||||
msgid "No encryption"
|
||||
msgstr "No encryption"
|
||||
|
||||
#: paperless_mail/models.py:20
|
||||
msgid "Use SSL"
|
||||
msgstr "Use SSL"
|
||||
|
||||
#: paperless_mail/models.py:21
|
||||
msgid "Use STARTTLS"
|
||||
msgstr "Use STARTTLS"
|
||||
|
||||
#: paperless_mail/models.py:29
|
||||
msgid "IMAP server"
|
||||
msgstr "IMAP server"
|
||||
|
||||
#: paperless_mail/models.py:33
|
||||
msgid "IMAP port"
|
||||
msgstr "IMAP port"
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
msgid "IMAP security"
|
||||
msgstr "IMAP security"
|
||||
|
||||
#: paperless_mail/models.py:46
|
||||
msgid "username"
|
||||
msgstr "username"
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "password"
|
||||
msgstr "password"
|
||||
|
||||
#: paperless_mail/models.py:54
|
||||
msgid "character set"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:57
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "mail rule"
|
||||
msgstr "mail rule"
|
||||
|
||||
#: paperless_mail/models.py:69
|
||||
msgid "mail rules"
|
||||
msgstr "mail rules"
|
||||
|
||||
#: paperless_mail/models.py:75
|
||||
msgid "Only process attachments."
|
||||
msgstr "Only process attachments."
|
||||
|
||||
#: paperless_mail/models.py:76
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr "Process all files, including 'inline' attachments."
|
||||
|
||||
#: paperless_mail/models.py:86
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr "Mark as read, don't process read mails"
|
||||
|
||||
#: paperless_mail/models.py:87
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr "Flag the mail, don't process flagged mails"
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "Move to specified folder"
|
||||
msgstr "Move to specified folder"
|
||||
|
||||
#: paperless_mail/models.py:89
|
||||
msgid "Delete"
|
||||
msgstr "Delete"
|
||||
|
||||
#: paperless_mail/models.py:96
|
||||
msgid "Use subject as title"
|
||||
msgstr "Use subject as title"
|
||||
|
||||
#: paperless_mail/models.py:97
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr "Use attachment filename as title"
|
||||
|
||||
#: paperless_mail/models.py:107
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr "Do not assign a correspondent"
|
||||
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "Use mail address"
|
||||
msgstr "Use mail address"
|
||||
|
||||
#: paperless_mail/models.py:111
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr "Use name (or mail address if not available)"
|
||||
|
||||
#: paperless_mail/models.py:113
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr "Use correspondent selected below"
|
||||
|
||||
#: paperless_mail/models.py:121
|
||||
msgid "order"
|
||||
msgstr "order"
|
||||
|
||||
#: paperless_mail/models.py:128
|
||||
msgid "account"
|
||||
msgstr "account"
|
||||
|
||||
#: paperless_mail/models.py:132
|
||||
msgid "folder"
|
||||
msgstr "folder"
|
||||
|
||||
#: paperless_mail/models.py:134
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
msgid "filter from"
|
||||
msgstr "filter from"
|
||||
|
||||
#: paperless_mail/models.py:141
|
||||
msgid "filter subject"
|
||||
msgstr "filter subject"
|
||||
|
||||
#: paperless_mail/models.py:144
|
||||
msgid "filter body"
|
||||
msgstr "filter body"
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "filter attachment filename"
|
||||
msgstr "filter attachment filename"
|
||||
|
||||
#: paperless_mail/models.py:150
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
|
||||
#: paperless_mail/models.py:156
|
||||
msgid "maximum age"
|
||||
msgstr "maximum age"
|
||||
|
||||
#: paperless_mail/models.py:158
|
||||
msgid "Specified in days."
|
||||
msgstr "Specified in days."
|
||||
|
||||
#: paperless_mail/models.py:161
|
||||
msgid "attachment type"
|
||||
msgstr "attachment type"
|
||||
|
||||
#: paperless_mail/models.py:164
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
|
||||
#: paperless_mail/models.py:169
|
||||
msgid "action"
|
||||
msgstr "action"
|
||||
|
||||
#: paperless_mail/models.py:175
|
||||
msgid "action parameter"
|
||||
msgstr "action parameter"
|
||||
|
||||
#: paperless_mail/models.py:177
|
||||
msgid "Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:184
|
||||
msgid "assign title from"
|
||||
msgstr "assign title from"
|
||||
|
||||
#: paperless_mail/models.py:194
|
||||
msgid "assign this tag"
|
||||
msgstr "assign this tag"
|
||||
|
||||
#: paperless_mail/models.py:202
|
||||
msgid "assign this document type"
|
||||
msgstr "assign this document type"
|
||||
|
||||
#: paperless_mail/models.py:206
|
||||
msgid "assign correspondent from"
|
||||
msgstr "assign correspondent from"
|
||||
|
||||
#: paperless_mail/models.py:216
|
||||
msgid "assign this correspondent"
|
||||
msgstr "assign this correspondent"
|
||||
|
||||
718
src/locale/en_US/LC_MESSAGES/django.po
Normal file
718
src/locale/en_US/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,718 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-05-16 09:38+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:41 documents/models.py:350 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:117
|
||||
msgid "name"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "match"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:49
|
||||
msgid "matching algorithm"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:55
|
||||
msgid "is insensitive"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:74 documents/models.py:120
|
||||
msgid "correspondent"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:81
|
||||
msgid "color"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:87
|
||||
msgid "is inbox tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:89
|
||||
msgid ""
|
||||
"Marks this tag as an inbox tag: All newly consumed documents will be tagged "
|
||||
"with inbox tags."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:94
|
||||
msgid "tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:95 documents/models.py:151
|
||||
msgid "tags"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:101 documents/models.py:133
|
||||
msgid "document type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:102
|
||||
msgid "document types"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:110
|
||||
msgid "Unencrypted"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:111
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:124
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:137
|
||||
msgid "content"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:139
|
||||
msgid ""
|
||||
"The raw, text-only data of the document. This field is primarily used for "
|
||||
"searching."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:144
|
||||
msgid "mime type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:155
|
||||
msgid "checksum"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid "The checksum of the original document."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:163
|
||||
msgid "archive checksum"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:168
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:172 documents/models.py:328
|
||||
msgid "created"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:176
|
||||
msgid "modified"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:180
|
||||
msgid "storage type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:188
|
||||
msgid "added"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:192
|
||||
msgid "filename"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:198
|
||||
msgid "Current filename in storage"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:202
|
||||
msgid "archive filename"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:208
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "archive serial number"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:217
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:223
|
||||
msgid "document"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:224
|
||||
msgid "documents"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:311
|
||||
msgid "debug"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:312
|
||||
msgid "information"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:313
|
||||
msgid "warning"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:314
|
||||
msgid "error"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:315
|
||||
msgid "critical"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:319
|
||||
msgid "group"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:322
|
||||
msgid "message"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:325
|
||||
msgid "level"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:332
|
||||
msgid "log"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "logs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:344 documents/models.py:401
|
||||
msgid "saved view"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:345
|
||||
msgid "saved views"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:348
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:354
|
||||
msgid "show on dashboard"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:357
|
||||
msgid "show in sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:361
|
||||
msgid "sort field"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:367
|
||||
msgid "sort reverse"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:373
|
||||
msgid "title contains"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:374
|
||||
msgid "content contains"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:375
|
||||
msgid "ASN is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:376
|
||||
msgid "correspondent is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:377
|
||||
msgid "document type is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:378
|
||||
msgid "is in inbox"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:379
|
||||
msgid "has tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:380
|
||||
msgid "has any tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:381
|
||||
msgid "created before"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:382
|
||||
msgid "created after"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:383
|
||||
msgid "created year is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:384
|
||||
msgid "created month is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:385
|
||||
msgid "created day is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:386
|
||||
msgid "added before"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:387
|
||||
msgid "added after"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:388
|
||||
msgid "modified before"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:389
|
||||
msgid "modified after"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:390
|
||||
msgid "does not have tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:391
|
||||
msgid "does not have ASN"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:392
|
||||
msgid "title or content contains"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:393
|
||||
msgid "fulltext query"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:394
|
||||
msgid "more like this"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:405
|
||||
msgid "rule type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:409
|
||||
msgid "value"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:415
|
||||
msgid "filter rule"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:416
|
||||
msgid "filter rules"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:53
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:177
|
||||
msgid "Invalid color."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:451
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/index.html:22
|
||||
msgid "Paperless-ng is loading..."
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ng signed out"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/logged_out.html:45
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/logged_out.html:46
|
||||
msgid "Sign in again"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ng sign in"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:47
|
||||
msgid "Please sign in."
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:50
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:53
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:54
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:59
|
||||
msgid "Sign in"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:303
|
||||
msgid "English (US)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:304
|
||||
msgid "English (GB)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:305
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:306
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:307
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:308
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:309
|
||||
msgid "Portuguese"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Romanian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:312
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:313
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:314
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:315
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/urls.py:120
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:15
|
||||
msgid "Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:18
|
||||
msgid "Advanced settings"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
msgid "Filter"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid ""
|
||||
"Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:49
|
||||
msgid "Actions"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:51
|
||||
msgid ""
|
||||
"The action applied to the mail. This action is only performed when documents "
|
||||
"were consumed from the mail. Mails without attachments will remain entirely "
|
||||
"untouched."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:58
|
||||
msgid "Metadata"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:60
|
||||
msgid ""
|
||||
"Assign metadata to documents consumed from this rule automatically. If you "
|
||||
"do not assign tags, types or correspondents here, paperless will still "
|
||||
"process all matching rules that you have defined."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail account"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:12
|
||||
msgid "mail accounts"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:19
|
||||
msgid "No encryption"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:20
|
||||
msgid "Use SSL"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:21
|
||||
msgid "Use STARTTLS"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:29
|
||||
msgid "IMAP server"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:33
|
||||
msgid "IMAP port"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid ""
|
||||
"This is usually 143 for unencrypted and STARTTLS connections, and 993 for "
|
||||
"SSL connections."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
msgid "IMAP security"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:46
|
||||
msgid "username"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "password"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:54
|
||||
msgid "character set"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:57
|
||||
msgid ""
|
||||
"The character set to use when communicating with the mail server, such as "
|
||||
"'UTF-8' or 'US-ASCII'."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "mail rule"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:69
|
||||
msgid "mail rules"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:75
|
||||
msgid "Only process attachments."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:76
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:86
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:87
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "Move to specified folder"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:89
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:96
|
||||
msgid "Use subject as title"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:97
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:107
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "Use mail address"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:111
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:113
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:121
|
||||
msgid "order"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:128
|
||||
msgid "account"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:132
|
||||
msgid "folder"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:134
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
msgid "filter from"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:141
|
||||
msgid "filter subject"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:144
|
||||
msgid "filter body"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "filter attachment filename"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:150
|
||||
msgid ""
|
||||
"Only consume documents which entirely match this filename if specified. "
|
||||
"Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:156
|
||||
msgid "maximum age"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:158
|
||||
msgid "Specified in days."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:161
|
||||
msgid "attachment type"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:164
|
||||
msgid ""
|
||||
"Inline attachments include embedded images, so it's best to combine this "
|
||||
"option with a filename filter."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:169
|
||||
msgid "action"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:175
|
||||
msgid "action parameter"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:177
|
||||
msgid ""
|
||||
"Additional parameter for the action selected above, i.e., the target folder "
|
||||
"of the move to folder action. Subfolders must be separated by dots."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:184
|
||||
msgid "assign title from"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:194
|
||||
msgid "assign this tag"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:202
|
||||
msgid "assign this document type"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:206
|
||||
msgid "assign correspondent from"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:216
|
||||
msgid "assign this correspondent"
|
||||
msgstr ""
|
||||
698
src/locale/es_ES/LC_MESSAGES/django.po
Normal file
698
src/locale/es_ES/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,698 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ng\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-05-16 09:38+0000\n"
|
||||
"PO-Revision-Date: 2021-07-29 20:57\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Spanish\n"
|
||||
"Language: es_ES\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: paperless-ng\n"
|
||||
"X-Crowdin-Project-ID: 434940\n"
|
||||
"X-Crowdin-Language: es-ES\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 54\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
msgstr "Documentos"
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr "Cualquier palabra"
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr "Todas las palabras"
|
||||
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr "Coincidencia exacta"
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr "Expresión regular"
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr "Palabra borrosa"
|
||||
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr "Automático"
|
||||
|
||||
#: documents/models.py:41 documents/models.py:350 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:117
|
||||
msgid "name"
|
||||
msgstr "nombre"
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "match"
|
||||
msgstr "coincidencia"
|
||||
|
||||
#: documents/models.py:49
|
||||
msgid "matching algorithm"
|
||||
msgstr "Algoritmo de coincidencia"
|
||||
|
||||
#: documents/models.py:55
|
||||
msgid "is insensitive"
|
||||
msgstr "es insensible"
|
||||
|
||||
#: documents/models.py:74 documents/models.py:120
|
||||
msgid "correspondent"
|
||||
msgstr "interlocutor"
|
||||
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr "interlocutores"
|
||||
|
||||
#: documents/models.py:81
|
||||
msgid "color"
|
||||
msgstr "color"
|
||||
|
||||
#: documents/models.py:87
|
||||
msgid "is inbox tag"
|
||||
msgstr "es etiqueta de bandeja"
|
||||
|
||||
#: documents/models.py:89
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr "Marca esta etiqueta como una etiqueta de bandeja: todos los documentos recién consumidos serán etiquetados con las etiquetas de bandeja."
|
||||
|
||||
#: documents/models.py:94
|
||||
msgid "tag"
|
||||
msgstr "etiqueta"
|
||||
|
||||
#: documents/models.py:95 documents/models.py:151
|
||||
msgid "tags"
|
||||
msgstr "etiquetas"
|
||||
|
||||
#: documents/models.py:101 documents/models.py:133
|
||||
msgid "document type"
|
||||
msgstr "tipo de documento"
|
||||
|
||||
#: documents/models.py:102
|
||||
msgid "document types"
|
||||
msgstr "tipos de documento"
|
||||
|
||||
#: documents/models.py:110
|
||||
msgid "Unencrypted"
|
||||
msgstr "Sin cifrar"
|
||||
|
||||
#: documents/models.py:111
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "Cifrado con GNU Privacy Guard"
|
||||
|
||||
#: documents/models.py:124
|
||||
msgid "title"
|
||||
msgstr "título"
|
||||
|
||||
#: documents/models.py:137
|
||||
msgid "content"
|
||||
msgstr "contenido"
|
||||
|
||||
#: documents/models.py:139
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr "Los datos de texto en bruto del documento. Este campo se utiliza principalmente para las búsquedas."
|
||||
|
||||
#: documents/models.py:144
|
||||
msgid "mime type"
|
||||
msgstr "tipo MIME"
|
||||
|
||||
#: documents/models.py:155
|
||||
msgid "checksum"
|
||||
msgstr "Cadena de verificación"
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid "The checksum of the original document."
|
||||
msgstr "La cadena de verificación del documento original."
|
||||
|
||||
#: documents/models.py:163
|
||||
msgid "archive checksum"
|
||||
msgstr "cadena de comprobación del archivo"
|
||||
|
||||
#: documents/models.py:168
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr "La cadena de verificación del documento archivado."
|
||||
|
||||
#: documents/models.py:172 documents/models.py:328
|
||||
msgid "created"
|
||||
msgstr "creado"
|
||||
|
||||
#: documents/models.py:176
|
||||
msgid "modified"
|
||||
msgstr "modificado"
|
||||
|
||||
#: documents/models.py:180
|
||||
msgid "storage type"
|
||||
msgstr "tipo de almacenamiento"
|
||||
|
||||
#: documents/models.py:188
|
||||
msgid "added"
|
||||
msgstr "añadido"
|
||||
|
||||
#: documents/models.py:192
|
||||
msgid "filename"
|
||||
msgstr "nombre del archivo"
|
||||
|
||||
#: documents/models.py:198
|
||||
msgid "Current filename in storage"
|
||||
msgstr "Nombre de archivo actual en disco"
|
||||
|
||||
#: documents/models.py:202
|
||||
msgid "archive filename"
|
||||
msgstr "nombre de archivo"
|
||||
|
||||
#: documents/models.py:208
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr "Nombre de archivo actual en disco"
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "archive serial number"
|
||||
msgstr "número de serie del archivo"
|
||||
|
||||
#: documents/models.py:217
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr "Posición de este documento en tu archivo físico de documentos."
|
||||
|
||||
#: documents/models.py:223
|
||||
msgid "document"
|
||||
msgstr "documento"
|
||||
|
||||
#: documents/models.py:224
|
||||
msgid "documents"
|
||||
msgstr "documentos"
|
||||
|
||||
#: documents/models.py:311
|
||||
msgid "debug"
|
||||
msgstr "depuración"
|
||||
|
||||
#: documents/models.py:312
|
||||
msgid "information"
|
||||
msgstr "información"
|
||||
|
||||
#: documents/models.py:313
|
||||
msgid "warning"
|
||||
msgstr "alerta"
|
||||
|
||||
#: documents/models.py:314
|
||||
msgid "error"
|
||||
msgstr "error"
|
||||
|
||||
#: documents/models.py:315
|
||||
msgid "critical"
|
||||
msgstr "crítico"
|
||||
|
||||
#: documents/models.py:319
|
||||
msgid "group"
|
||||
msgstr "grupo"
|
||||
|
||||
#: documents/models.py:322
|
||||
msgid "message"
|
||||
msgstr "mensaje"
|
||||
|
||||
#: documents/models.py:325
|
||||
msgid "level"
|
||||
msgstr "nivel"
|
||||
|
||||
#: documents/models.py:332
|
||||
msgid "log"
|
||||
msgstr "log"
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "logs"
|
||||
msgstr "logs"
|
||||
|
||||
#: documents/models.py:344 documents/models.py:401
|
||||
msgid "saved view"
|
||||
msgstr "vista guardada"
|
||||
|
||||
#: documents/models.py:345
|
||||
msgid "saved views"
|
||||
msgstr "vistas guardadas"
|
||||
|
||||
#: documents/models.py:348
|
||||
msgid "user"
|
||||
msgstr "usuario"
|
||||
|
||||
#: documents/models.py:354
|
||||
msgid "show on dashboard"
|
||||
msgstr "mostrar en el panel de control"
|
||||
|
||||
#: documents/models.py:357
|
||||
msgid "show in sidebar"
|
||||
msgstr "mostrar en barra lateral"
|
||||
|
||||
#: documents/models.py:361
|
||||
msgid "sort field"
|
||||
msgstr "campo de ordenación"
|
||||
|
||||
#: documents/models.py:367
|
||||
msgid "sort reverse"
|
||||
msgstr "ordenar al revés"
|
||||
|
||||
#: documents/models.py:373
|
||||
msgid "title contains"
|
||||
msgstr "el título contiene"
|
||||
|
||||
#: documents/models.py:374
|
||||
msgid "content contains"
|
||||
msgstr "el contenido contiene"
|
||||
|
||||
#: documents/models.py:375
|
||||
msgid "ASN is"
|
||||
msgstr "ASN es"
|
||||
|
||||
#: documents/models.py:376
|
||||
msgid "correspondent is"
|
||||
msgstr "interlocutor es"
|
||||
|
||||
#: documents/models.py:377
|
||||
msgid "document type is"
|
||||
msgstr "el tipo de documento es"
|
||||
|
||||
#: documents/models.py:378
|
||||
msgid "is in inbox"
|
||||
msgstr "está en la bandeja de entrada"
|
||||
|
||||
#: documents/models.py:379
|
||||
msgid "has tag"
|
||||
msgstr "tiene la etiqueta"
|
||||
|
||||
#: documents/models.py:380
|
||||
msgid "has any tag"
|
||||
msgstr "tiene cualquier etiqueta"
|
||||
|
||||
#: documents/models.py:381
|
||||
msgid "created before"
|
||||
msgstr "creado antes"
|
||||
|
||||
#: documents/models.py:382
|
||||
msgid "created after"
|
||||
msgstr "creado después"
|
||||
|
||||
#: documents/models.py:383
|
||||
msgid "created year is"
|
||||
msgstr "el año de creación es"
|
||||
|
||||
#: documents/models.py:384
|
||||
msgid "created month is"
|
||||
msgstr "el mes de creación es"
|
||||
|
||||
#: documents/models.py:385
|
||||
msgid "created day is"
|
||||
msgstr "creado el día"
|
||||
|
||||
#: documents/models.py:386
|
||||
msgid "added before"
|
||||
msgstr "agregado antes de"
|
||||
|
||||
#: documents/models.py:387
|
||||
msgid "added after"
|
||||
msgstr "agregado después de"
|
||||
|
||||
#: documents/models.py:388
|
||||
msgid "modified before"
|
||||
msgstr "modificado después de"
|
||||
|
||||
#: documents/models.py:389
|
||||
msgid "modified after"
|
||||
msgstr "modificado antes de"
|
||||
|
||||
#: documents/models.py:390
|
||||
msgid "does not have tag"
|
||||
msgstr "no tiene la etiqueta"
|
||||
|
||||
#: documents/models.py:391
|
||||
msgid "does not have ASN"
|
||||
msgstr "no tiene ASN"
|
||||
|
||||
#: documents/models.py:392
|
||||
msgid "title or content contains"
|
||||
msgstr "el título o cuerpo contiene"
|
||||
|
||||
#: documents/models.py:393
|
||||
msgid "fulltext query"
|
||||
msgstr "consulta de texto completo"
|
||||
|
||||
#: documents/models.py:394
|
||||
msgid "more like this"
|
||||
msgstr "más contenido similar"
|
||||
|
||||
#: documents/models.py:405
|
||||
msgid "rule type"
|
||||
msgstr "tipo de regla"
|
||||
|
||||
#: documents/models.py:409
|
||||
msgid "value"
|
||||
msgstr "valor"
|
||||
|
||||
#: documents/models.py:415
|
||||
msgid "filter rule"
|
||||
msgstr "regla de filtrado"
|
||||
|
||||
#: documents/models.py:416
|
||||
msgid "filter rules"
|
||||
msgstr "reglas de filtrado"
|
||||
|
||||
#: documents/serialisers.py:53
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr "Expresión irregular inválida: %(error)s"
|
||||
|
||||
#: documents/serialisers.py:177
|
||||
msgid "Invalid color."
|
||||
msgstr "Color inválido."
|
||||
|
||||
#: documents/serialisers.py:451
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Tipo de fichero %(type)s no suportado"
|
||||
|
||||
#: documents/templates/index.html:22
|
||||
msgid "Paperless-ng is loading..."
|
||||
msgstr "Paperless-ng está cargándose..."
|
||||
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ng signed out"
|
||||
msgstr "Paperless-ng Sesión cerrada"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:45
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr "Has cerrado la sesión satisfactoriamente. ¡Adiós!"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:46
|
||||
msgid "Sign in again"
|
||||
msgstr "Iniciar sesión de nuevo"
|
||||
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ng sign in"
|
||||
msgstr "Paperless-ng Iniciar sesión"
|
||||
|
||||
#: documents/templates/registration/login.html:47
|
||||
msgid "Please sign in."
|
||||
msgstr "Por favor, inicie sesión"
|
||||
|
||||
#: documents/templates/registration/login.html:50
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr "Tu usuario y contraseña no coinciden. Inténtalo de nuevo."
|
||||
|
||||
#: documents/templates/registration/login.html:53
|
||||
msgid "Username"
|
||||
msgstr "Usuario"
|
||||
|
||||
#: documents/templates/registration/login.html:54
|
||||
msgid "Password"
|
||||
msgstr "Contraseña"
|
||||
|
||||
#: documents/templates/registration/login.html:59
|
||||
msgid "Sign in"
|
||||
msgstr "Iniciar sesión"
|
||||
|
||||
#: paperless/settings.py:303
|
||||
msgid "English (US)"
|
||||
msgstr "Inglés (US)"
|
||||
|
||||
#: paperless/settings.py:304
|
||||
msgid "English (GB)"
|
||||
msgstr "Inglés (Gran Bretaña)"
|
||||
|
||||
#: paperless/settings.py:305
|
||||
msgid "German"
|
||||
msgstr "Alemán"
|
||||
|
||||
#: paperless/settings.py:306
|
||||
msgid "Dutch"
|
||||
msgstr "Alemán"
|
||||
|
||||
#: paperless/settings.py:307
|
||||
msgid "French"
|
||||
msgstr "Francés"
|
||||
|
||||
#: paperless/settings.py:308
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Portugués (Brasil)"
|
||||
|
||||
#: paperless/settings.py:309
|
||||
msgid "Portuguese"
|
||||
msgstr "Portugués"
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Italian"
|
||||
msgstr "Italiano"
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Romanian"
|
||||
msgstr "Rumano"
|
||||
|
||||
#: paperless/settings.py:312
|
||||
msgid "Russian"
|
||||
msgstr "Ruso"
|
||||
|
||||
#: paperless/settings.py:313
|
||||
msgid "Spanish"
|
||||
msgstr "Español"
|
||||
|
||||
#: paperless/settings.py:314
|
||||
msgid "Polish"
|
||||
msgstr "Polaco"
|
||||
|
||||
#: paperless/settings.py:315
|
||||
msgid "Swedish"
|
||||
msgstr "Sueco"
|
||||
|
||||
#: paperless/urls.py:120
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr "Paperless-ng Administración"
|
||||
|
||||
#: paperless_mail/admin.py:15
|
||||
msgid "Authentication"
|
||||
msgstr "Autentificación"
|
||||
|
||||
#: paperless_mail/admin.py:18
|
||||
msgid "Advanced settings"
|
||||
msgstr "Configuración avanzada"
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
msgid "Filter"
|
||||
msgstr "Filtro"
|
||||
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr "Paperless solo procesará los correos que coincidan con TODOS los filtros escritos abajo."
|
||||
|
||||
#: paperless_mail/admin.py:49
|
||||
msgid "Actions"
|
||||
msgstr "Acciones"
|
||||
|
||||
#: paperless_mail/admin.py:51
|
||||
msgid "The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched."
|
||||
msgstr "La acción se aplicó al correo. Esta acción sólo se realiza cuando los documentos se consumen desde el correo. Los correos sin archivos adjuntos permanecerán totalmente intactos."
|
||||
|
||||
#: paperless_mail/admin.py:58
|
||||
msgid "Metadata"
|
||||
msgstr "Metadatos"
|
||||
|
||||
#: paperless_mail/admin.py:60
|
||||
msgid "Assign metadata to documents consumed from this rule automatically. If you do not assign tags, types or correspondents here, paperless will still process all matching rules that you have defined."
|
||||
msgstr "Asignar metadatos a documentos consumidos por esta regla automáticamente. Si no asigna etiquetas, tipos o interlocutores aquí, paperless procesará igualmente todas las reglas que haya definido."
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
msgstr "Correo Paperless"
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail account"
|
||||
msgstr "cuenta de correo"
|
||||
|
||||
#: paperless_mail/models.py:12
|
||||
msgid "mail accounts"
|
||||
msgstr "cuentas de correo"
|
||||
|
||||
#: paperless_mail/models.py:19
|
||||
msgid "No encryption"
|
||||
msgstr "Sin encriptar"
|
||||
|
||||
#: paperless_mail/models.py:20
|
||||
msgid "Use SSL"
|
||||
msgstr "Usar SSL"
|
||||
|
||||
#: paperless_mail/models.py:21
|
||||
msgid "Use STARTTLS"
|
||||
msgstr "Usar STARTTLS"
|
||||
|
||||
#: paperless_mail/models.py:29
|
||||
msgid "IMAP server"
|
||||
msgstr "Servidor IMAP"
|
||||
|
||||
#: paperless_mail/models.py:33
|
||||
msgid "IMAP port"
|
||||
msgstr "Puerto IMAP"
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr "Normalmente 143 para conexiones sin encriptar y STARTTLS, y 993 para conexiones SSL."
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
msgid "IMAP security"
|
||||
msgstr "Seguridad IMAP"
|
||||
|
||||
#: paperless_mail/models.py:46
|
||||
msgid "username"
|
||||
msgstr "usuario"
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "password"
|
||||
msgstr "contraseña"
|
||||
|
||||
#: paperless_mail/models.py:54
|
||||
msgid "character set"
|
||||
msgstr "conjunto de caracteres"
|
||||
|
||||
#: paperless_mail/models.py:57
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
msgstr "El conjunto de caracteres a usar al comunicarse con el servidor de correo, como 'UTF-8' o 'US-ASCII'."
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "mail rule"
|
||||
msgstr "regla de correo"
|
||||
|
||||
#: paperless_mail/models.py:69
|
||||
msgid "mail rules"
|
||||
msgstr "reglas de correo"
|
||||
|
||||
#: paperless_mail/models.py:75
|
||||
msgid "Only process attachments."
|
||||
msgstr "Solo procesar ficheros adjuntos."
|
||||
|
||||
#: paperless_mail/models.py:76
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr "Procesar todos los ficheros, incluyendo ficheros 'incrustados'."
|
||||
|
||||
#: paperless_mail/models.py:86
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr "Marcar como leído, no procesar archivos leídos"
|
||||
|
||||
#: paperless_mail/models.py:87
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr "Marcar el correo, no procesar correos marcados"
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "Move to specified folder"
|
||||
msgstr "Mover a carpeta específica"
|
||||
|
||||
#: paperless_mail/models.py:89
|
||||
msgid "Delete"
|
||||
msgstr "Borrar"
|
||||
|
||||
#: paperless_mail/models.py:96
|
||||
msgid "Use subject as title"
|
||||
msgstr "Usar asunto como título"
|
||||
|
||||
#: paperless_mail/models.py:97
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr "Usar nombre del fichero adjunto como título"
|
||||
|
||||
#: paperless_mail/models.py:107
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr "No asignar interlocutor"
|
||||
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "Use mail address"
|
||||
msgstr "Usar dirección de correo"
|
||||
|
||||
#: paperless_mail/models.py:111
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr "Usar nombre (o dirección de correo sino está disponible)"
|
||||
|
||||
#: paperless_mail/models.py:113
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr "Usar el interlocutor seleccionado a continuación"
|
||||
|
||||
#: paperless_mail/models.py:121
|
||||
msgid "order"
|
||||
msgstr "orden"
|
||||
|
||||
#: paperless_mail/models.py:128
|
||||
msgid "account"
|
||||
msgstr "cuenta"
|
||||
|
||||
#: paperless_mail/models.py:132
|
||||
msgid "folder"
|
||||
msgstr "carpeta"
|
||||
|
||||
#: paperless_mail/models.py:134
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr "Las subcarpetas deben estar separadas por puntos."
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
msgid "filter from"
|
||||
msgstr "filtrar desde"
|
||||
|
||||
#: paperless_mail/models.py:141
|
||||
msgid "filter subject"
|
||||
msgstr "filtrar asunto"
|
||||
|
||||
#: paperless_mail/models.py:144
|
||||
msgid "filter body"
|
||||
msgstr "filtrar cuerpo"
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "filter attachment filename"
|
||||
msgstr "filtrar nombre del fichero adjunto"
|
||||
|
||||
#: paperless_mail/models.py:150
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr "Sólo consumirá documentos que coincidan completamente con este nombre de archivo si se especifica. Se permiten comodines como *.pdf o *factura*. No diferencia mayúsculas."
|
||||
|
||||
#: paperless_mail/models.py:156
|
||||
msgid "maximum age"
|
||||
msgstr "antigüedad máxima"
|
||||
|
||||
#: paperless_mail/models.py:158
|
||||
msgid "Specified in days."
|
||||
msgstr "Especificado en días."
|
||||
|
||||
#: paperless_mail/models.py:161
|
||||
msgid "attachment type"
|
||||
msgstr "tipo de fichero adjunto"
|
||||
|
||||
#: paperless_mail/models.py:164
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr "Adjuntos incrustados incluyen imágenes, por lo que es mejor combina resta opción un filtro de nombre de fichero."
|
||||
|
||||
#: paperless_mail/models.py:169
|
||||
msgid "action"
|
||||
msgstr "acción"
|
||||
|
||||
#: paperless_mail/models.py:175
|
||||
msgid "action parameter"
|
||||
msgstr "parámetro de acción"
|
||||
|
||||
#: paperless_mail/models.py:177
|
||||
msgid "Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots."
|
||||
msgstr "Parámetro adicional para la acción seleccionada arriba. Ej. la carpeta de destino de la acción \"mover a carpeta\". Las subcarpetas deben estar separadas por puntos."
|
||||
|
||||
#: paperless_mail/models.py:184
|
||||
msgid "assign title from"
|
||||
msgstr "asignar título desde"
|
||||
|
||||
#: paperless_mail/models.py:194
|
||||
msgid "assign this tag"
|
||||
msgstr "asignar esta etiqueta"
|
||||
|
||||
#: paperless_mail/models.py:202
|
||||
msgid "assign this document type"
|
||||
msgstr "asignar este tipo de documento"
|
||||
|
||||
#: paperless_mail/models.py:206
|
||||
msgid "assign correspondent from"
|
||||
msgstr "asignar interlocutor desde"
|
||||
|
||||
#: paperless_mail/models.py:216
|
||||
msgid "assign this correspondent"
|
||||
msgstr "asignar este interlocutor"
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Jonas Winkler, 2020
|
||||
# Philmo67, 2021
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Project-Id-Version: paperless-ng\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-01-10 21:41+0000\n"
|
||||
"PO-Revision-Date: 2020-12-30 19:27+0000\n"
|
||||
"Last-Translator: Philmo67, 2021\n"
|
||||
"Language-Team: French (https://www.transifex.com/paperless/teams/115905/fr/)\n"
|
||||
"POT-Creation-Date: 2021-05-16 09:38+0000\n"
|
||||
"PO-Revision-Date: 2021-05-17 13:13\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: French\n"
|
||||
"Language: fr_FR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: fr\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
"X-Crowdin-Project: paperless-ng\n"
|
||||
"X-Crowdin-Project-ID: 434940\n"
|
||||
"X-Crowdin-Language: fr\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 54\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
@@ -50,8 +45,8 @@ msgstr "Mot approximatif"
|
||||
msgid "Automatic"
|
||||
msgstr "Automatique"
|
||||
|
||||
#: documents/models.py:41 documents/models.py:354 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:109
|
||||
#: documents/models.py:41 documents/models.py:350 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:117
|
||||
msgid "name"
|
||||
msgstr "nom"
|
||||
|
||||
@@ -67,388 +62,443 @@ msgstr "algorithme de rapprochement"
|
||||
msgid "is insensitive"
|
||||
msgstr "est insensible à la casse"
|
||||
|
||||
#: documents/models.py:80 documents/models.py:140
|
||||
#: documents/models.py:74 documents/models.py:120
|
||||
msgid "correspondent"
|
||||
msgstr "correspondant"
|
||||
|
||||
#: documents/models.py:81
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr "correspondants"
|
||||
|
||||
#: documents/models.py:103
|
||||
#: documents/models.py:81
|
||||
msgid "color"
|
||||
msgstr "couleur"
|
||||
|
||||
#: documents/models.py:107
|
||||
#: documents/models.py:87
|
||||
msgid "is inbox tag"
|
||||
msgstr "est une étiquette de boîte de réception"
|
||||
|
||||
#: documents/models.py:109
|
||||
msgid ""
|
||||
"Marks this tag as an inbox tag: All newly consumed documents will be tagged "
|
||||
"with inbox tags."
|
||||
msgstr ""
|
||||
"Marque cette étiquette comme étiquette de boîte de réception : ces "
|
||||
"étiquettes sont affectées à tous les documents nouvellement traités."
|
||||
#: documents/models.py:89
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr "Marque cette étiquette comme étiquette de boîte de réception : ces étiquettes sont affectées à tous les documents nouvellement traités."
|
||||
|
||||
#: documents/models.py:114
|
||||
#: documents/models.py:94
|
||||
msgid "tag"
|
||||
msgstr "étiquette"
|
||||
|
||||
#: documents/models.py:115 documents/models.py:171
|
||||
#: documents/models.py:95 documents/models.py:151
|
||||
msgid "tags"
|
||||
msgstr "étiquettes"
|
||||
|
||||
#: documents/models.py:121 documents/models.py:153
|
||||
#: documents/models.py:101 documents/models.py:133
|
||||
msgid "document type"
|
||||
msgstr "type de document"
|
||||
|
||||
#: documents/models.py:122
|
||||
#: documents/models.py:102
|
||||
msgid "document types"
|
||||
msgstr "types de document"
|
||||
|
||||
#: documents/models.py:130
|
||||
#: documents/models.py:110
|
||||
msgid "Unencrypted"
|
||||
msgstr "Non chiffré"
|
||||
|
||||
#: documents/models.py:131
|
||||
#: documents/models.py:111
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "Chiffré avec GNU Privacy Guard"
|
||||
|
||||
#: documents/models.py:144
|
||||
#: documents/models.py:124
|
||||
msgid "title"
|
||||
msgstr "titre"
|
||||
|
||||
#: documents/models.py:157
|
||||
#: documents/models.py:137
|
||||
msgid "content"
|
||||
msgstr "contenu"
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid ""
|
||||
"The raw, text-only data of the document. This field is primarily used for "
|
||||
"searching."
|
||||
msgstr ""
|
||||
"Les données brutes du document, en format texte uniquement. Ce champ est "
|
||||
"principalement utilisé pour la recherche."
|
||||
#: documents/models.py:139
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr "Les données brutes du document, en format texte uniquement. Ce champ est principalement utilisé pour la recherche."
|
||||
|
||||
#: documents/models.py:164
|
||||
#: documents/models.py:144
|
||||
msgid "mime type"
|
||||
msgstr "type mime"
|
||||
|
||||
#: documents/models.py:175
|
||||
#: documents/models.py:155
|
||||
msgid "checksum"
|
||||
msgstr "somme de contrôle"
|
||||
|
||||
#: documents/models.py:179
|
||||
#: documents/models.py:159
|
||||
msgid "The checksum of the original document."
|
||||
msgstr "La somme de contrôle du document original."
|
||||
|
||||
#: documents/models.py:183
|
||||
#: documents/models.py:163
|
||||
msgid "archive checksum"
|
||||
msgstr "somme de contrôle de l'archive"
|
||||
|
||||
#: documents/models.py:188
|
||||
#: documents/models.py:168
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr "La somme de contrôle du document archivé."
|
||||
|
||||
#: documents/models.py:192 documents/models.py:332
|
||||
#: documents/models.py:172 documents/models.py:328
|
||||
msgid "created"
|
||||
msgstr "créé le"
|
||||
|
||||
#: documents/models.py:196
|
||||
#: documents/models.py:176
|
||||
msgid "modified"
|
||||
msgstr "modifié"
|
||||
|
||||
#: documents/models.py:200
|
||||
#: documents/models.py:180
|
||||
msgid "storage type"
|
||||
msgstr "forme d'enregistrement :"
|
||||
|
||||
#: documents/models.py:208
|
||||
#: documents/models.py:188
|
||||
msgid "added"
|
||||
msgstr "date d'ajout"
|
||||
|
||||
#: documents/models.py:212
|
||||
#: documents/models.py:192
|
||||
msgid "filename"
|
||||
msgstr "nom du fichier"
|
||||
|
||||
#: documents/models.py:217
|
||||
#: documents/models.py:198
|
||||
msgid "Current filename in storage"
|
||||
msgstr "Nom du fichier courant en base de données"
|
||||
|
||||
#: documents/models.py:221
|
||||
#: documents/models.py:202
|
||||
msgid "archive filename"
|
||||
msgstr "nom de fichier de l'archive"
|
||||
|
||||
#: documents/models.py:208
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr "Nom du fichier d'archive courant en base de données"
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "archive serial number"
|
||||
msgstr "numéro de série de l'archive"
|
||||
|
||||
#: documents/models.py:226
|
||||
#: documents/models.py:217
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr ""
|
||||
"Le classement de ce document dans votre archive de documents physiques."
|
||||
msgstr "Le classement de ce document dans votre archive de documents physiques."
|
||||
|
||||
#: documents/models.py:232
|
||||
#: documents/models.py:223
|
||||
msgid "document"
|
||||
msgstr "document"
|
||||
|
||||
#: documents/models.py:233
|
||||
#: documents/models.py:224
|
||||
msgid "documents"
|
||||
msgstr "documents"
|
||||
|
||||
#: documents/models.py:315
|
||||
#: documents/models.py:311
|
||||
msgid "debug"
|
||||
msgstr "débogage"
|
||||
|
||||
#: documents/models.py:316
|
||||
#: documents/models.py:312
|
||||
msgid "information"
|
||||
msgstr "information"
|
||||
|
||||
#: documents/models.py:317
|
||||
#: documents/models.py:313
|
||||
msgid "warning"
|
||||
msgstr "avertissement"
|
||||
|
||||
#: documents/models.py:318
|
||||
#: documents/models.py:314
|
||||
msgid "error"
|
||||
msgstr "erreur"
|
||||
|
||||
#: documents/models.py:319
|
||||
#: documents/models.py:315
|
||||
msgid "critical"
|
||||
msgstr "critique"
|
||||
|
||||
#: documents/models.py:323
|
||||
#: documents/models.py:319
|
||||
msgid "group"
|
||||
msgstr "groupe"
|
||||
|
||||
#: documents/models.py:326
|
||||
#: documents/models.py:322
|
||||
msgid "message"
|
||||
msgstr "message"
|
||||
|
||||
#: documents/models.py:329
|
||||
#: documents/models.py:325
|
||||
msgid "level"
|
||||
msgstr "niveau"
|
||||
|
||||
#: documents/models.py:336
|
||||
#: documents/models.py:332
|
||||
msgid "log"
|
||||
msgstr "rapport"
|
||||
msgstr "journal"
|
||||
|
||||
#: documents/models.py:337
|
||||
#: documents/models.py:333
|
||||
msgid "logs"
|
||||
msgstr "rapports"
|
||||
msgstr "journaux"
|
||||
|
||||
#: documents/models.py:348 documents/models.py:398
|
||||
#: documents/models.py:344 documents/models.py:401
|
||||
msgid "saved view"
|
||||
msgstr "vue enregistrée"
|
||||
|
||||
#: documents/models.py:349
|
||||
#: documents/models.py:345
|
||||
msgid "saved views"
|
||||
msgstr "vues enregistrées"
|
||||
|
||||
#: documents/models.py:352
|
||||
#: documents/models.py:348
|
||||
msgid "user"
|
||||
msgstr "utilisateur"
|
||||
|
||||
#: documents/models.py:358
|
||||
#: documents/models.py:354
|
||||
msgid "show on dashboard"
|
||||
msgstr "montrer sur le tableau de bord"
|
||||
|
||||
#: documents/models.py:361
|
||||
#: documents/models.py:357
|
||||
msgid "show in sidebar"
|
||||
msgstr "montrer dans la barre latérale"
|
||||
|
||||
#: documents/models.py:365
|
||||
#: documents/models.py:361
|
||||
msgid "sort field"
|
||||
msgstr "champ de tri"
|
||||
|
||||
#: documents/models.py:368
|
||||
#: documents/models.py:367
|
||||
msgid "sort reverse"
|
||||
msgstr "tri inverse"
|
||||
|
||||
#: documents/models.py:374
|
||||
#: documents/models.py:373
|
||||
msgid "title contains"
|
||||
msgstr "le titre contient"
|
||||
|
||||
#: documents/models.py:375
|
||||
#: documents/models.py:374
|
||||
msgid "content contains"
|
||||
msgstr "le contenu contient"
|
||||
|
||||
#: documents/models.py:376
|
||||
#: documents/models.py:375
|
||||
msgid "ASN is"
|
||||
msgstr "le NSA est"
|
||||
|
||||
#: documents/models.py:377
|
||||
#: documents/models.py:376
|
||||
msgid "correspondent is"
|
||||
msgstr "le correspondant est"
|
||||
|
||||
#: documents/models.py:378
|
||||
#: documents/models.py:377
|
||||
msgid "document type is"
|
||||
msgstr "le type de document est"
|
||||
|
||||
#: documents/models.py:379
|
||||
#: documents/models.py:378
|
||||
msgid "is in inbox"
|
||||
msgstr "est dans la boîte de réception"
|
||||
|
||||
#: documents/models.py:380
|
||||
#: documents/models.py:379
|
||||
msgid "has tag"
|
||||
msgstr "porte l'étiquette"
|
||||
|
||||
#: documents/models.py:381
|
||||
#: documents/models.py:380
|
||||
msgid "has any tag"
|
||||
msgstr "porte l'une des étiquettes"
|
||||
|
||||
#: documents/models.py:382
|
||||
#: documents/models.py:381
|
||||
msgid "created before"
|
||||
msgstr "créé avant"
|
||||
|
||||
#: documents/models.py:383
|
||||
#: documents/models.py:382
|
||||
msgid "created after"
|
||||
msgstr "créé après"
|
||||
|
||||
#: documents/models.py:384
|
||||
#: documents/models.py:383
|
||||
msgid "created year is"
|
||||
msgstr "l'année de création est"
|
||||
|
||||
#: documents/models.py:385
|
||||
#: documents/models.py:384
|
||||
msgid "created month is"
|
||||
msgstr "le mois de création est"
|
||||
|
||||
#: documents/models.py:386
|
||||
#: documents/models.py:385
|
||||
msgid "created day is"
|
||||
msgstr "le jour de création est"
|
||||
|
||||
#: documents/models.py:387
|
||||
#: documents/models.py:386
|
||||
msgid "added before"
|
||||
msgstr "ajouté avant"
|
||||
|
||||
#: documents/models.py:388
|
||||
#: documents/models.py:387
|
||||
msgid "added after"
|
||||
msgstr "ajouté après"
|
||||
|
||||
#: documents/models.py:389
|
||||
#: documents/models.py:388
|
||||
msgid "modified before"
|
||||
msgstr "modifié avant"
|
||||
|
||||
#: documents/models.py:390
|
||||
#: documents/models.py:389
|
||||
msgid "modified after"
|
||||
msgstr "modifié après"
|
||||
|
||||
#: documents/models.py:391
|
||||
#: documents/models.py:390
|
||||
msgid "does not have tag"
|
||||
msgstr "ne porte pas d'étiquette"
|
||||
|
||||
#: documents/models.py:402
|
||||
#: documents/models.py:391
|
||||
msgid "does not have ASN"
|
||||
msgstr "ne porte pas de NSA"
|
||||
|
||||
#: documents/models.py:392
|
||||
msgid "title or content contains"
|
||||
msgstr "le titre ou le contenu contient"
|
||||
|
||||
#: documents/models.py:393
|
||||
msgid "fulltext query"
|
||||
msgstr "recherche en texte intégral"
|
||||
|
||||
#: documents/models.py:394
|
||||
msgid "more like this"
|
||||
msgstr "documents relatifs"
|
||||
|
||||
#: documents/models.py:405
|
||||
msgid "rule type"
|
||||
msgstr "type de règle"
|
||||
|
||||
#: documents/models.py:406
|
||||
#: documents/models.py:409
|
||||
msgid "value"
|
||||
msgstr "valeur"
|
||||
|
||||
#: documents/models.py:412
|
||||
#: documents/models.py:415
|
||||
msgid "filter rule"
|
||||
msgstr "règle de filtrage"
|
||||
|
||||
#: documents/models.py:413
|
||||
#: documents/models.py:416
|
||||
msgid "filter rules"
|
||||
msgstr "règles de filtrage"
|
||||
|
||||
#: documents/templates/index.html:20
|
||||
#: documents/serialisers.py:53
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr "Expression régulière incorrecte : %(error)s"
|
||||
|
||||
#: documents/serialisers.py:177
|
||||
msgid "Invalid color."
|
||||
msgstr "Couleur incorrecte."
|
||||
|
||||
#: documents/serialisers.py:451
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Type de fichier %(type)s non pris en charge"
|
||||
|
||||
#: documents/templates/index.html:22
|
||||
msgid "Paperless-ng is loading..."
|
||||
msgstr "Paperless-ng est en cours de chargement..."
|
||||
|
||||
#: documents/templates/registration/logged_out.html:13
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ng signed out"
|
||||
msgstr "Déconnecté de Paperless-ng"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:41
|
||||
#: documents/templates/registration/logged_out.html:45
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr "Vous avez été déconnecté avec succès. Au revoir !"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:42
|
||||
#: documents/templates/registration/logged_out.html:46
|
||||
msgid "Sign in again"
|
||||
msgstr "Se reconnecter"
|
||||
|
||||
#: documents/templates/registration/login.html:13
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ng sign in"
|
||||
msgstr "Connexion à Paperless-ng"
|
||||
|
||||
#: documents/templates/registration/login.html:42
|
||||
#: documents/templates/registration/login.html:47
|
||||
msgid "Please sign in."
|
||||
msgstr "Veuillez vous connecter."
|
||||
|
||||
#: documents/templates/registration/login.html:45
|
||||
#: documents/templates/registration/login.html:50
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr ""
|
||||
"Votre nom d'utilisateur et votre mot de passe ne correspondent pas. Veuillez"
|
||||
" réessayer."
|
||||
msgstr "Votre nom d'utilisateur et votre mot de passe ne correspondent pas. Veuillez réessayer."
|
||||
|
||||
#: documents/templates/registration/login.html:48
|
||||
#: documents/templates/registration/login.html:53
|
||||
msgid "Username"
|
||||
msgstr "Nom d'utilisateur"
|
||||
|
||||
#: documents/templates/registration/login.html:49
|
||||
#: documents/templates/registration/login.html:54
|
||||
msgid "Password"
|
||||
msgstr "Mot de passe"
|
||||
|
||||
#: documents/templates/registration/login.html:54
|
||||
#: documents/templates/registration/login.html:59
|
||||
msgid "Sign in"
|
||||
msgstr "S'identifier"
|
||||
|
||||
#: paperless/settings.py:268
|
||||
msgid "English"
|
||||
msgstr "Anglais"
|
||||
#: paperless/settings.py:303
|
||||
msgid "English (US)"
|
||||
msgstr "Anglais (US)"
|
||||
|
||||
#: paperless/settings.py:269
|
||||
#: paperless/settings.py:304
|
||||
msgid "English (GB)"
|
||||
msgstr "Anglais (GB)"
|
||||
|
||||
#: paperless/settings.py:305
|
||||
msgid "German"
|
||||
msgstr "Allemand"
|
||||
|
||||
#: paperless/settings.py:270
|
||||
#: paperless/settings.py:306
|
||||
msgid "Dutch"
|
||||
msgstr "Néerlandais"
|
||||
|
||||
#: paperless/settings.py:271
|
||||
#: paperless/settings.py:307
|
||||
msgid "French"
|
||||
msgstr "Français"
|
||||
|
||||
#: paperless/urls.py:108
|
||||
#: paperless/settings.py:308
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Portugais (Brésil)"
|
||||
|
||||
#: paperless/settings.py:309
|
||||
msgid "Portuguese"
|
||||
msgstr "Portugais"
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Italian"
|
||||
msgstr "Italien"
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Romanian"
|
||||
msgstr "Roumain"
|
||||
|
||||
#: paperless/settings.py:312
|
||||
msgid "Russian"
|
||||
msgstr "Russe"
|
||||
|
||||
#: paperless/settings.py:313
|
||||
msgid "Spanish"
|
||||
msgstr "Espagnol"
|
||||
|
||||
#: paperless/settings.py:314
|
||||
msgid "Polish"
|
||||
msgstr "Polonais"
|
||||
|
||||
#: paperless/settings.py:315
|
||||
msgid "Swedish"
|
||||
msgstr "Suédois"
|
||||
|
||||
#: paperless/urls.py:120
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr "Administration de Paperless-ng"
|
||||
|
||||
#: paperless_mail/admin.py:25
|
||||
#: paperless_mail/admin.py:15
|
||||
msgid "Authentication"
|
||||
msgstr "Authentification"
|
||||
|
||||
#: paperless_mail/admin.py:18
|
||||
msgid "Advanced settings"
|
||||
msgstr "Paramètres avancés"
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
msgid "Filter"
|
||||
msgstr "Filtrage"
|
||||
|
||||
#: paperless_mail/admin.py:27
|
||||
msgid ""
|
||||
"Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr ""
|
||||
"Paperless-ng ne traitera que les courriers qui correspondent à TOUS les "
|
||||
"filtres ci-dessous."
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr "Paperless-ng ne traitera que les courriers qui correspondent à TOUS les filtres ci-dessous."
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
#: paperless_mail/admin.py:49
|
||||
msgid "Actions"
|
||||
msgstr "Actions"
|
||||
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid ""
|
||||
"The action applied to the mail. This action is only performed when documents"
|
||||
" were consumed from the mail. Mails without attachments will remain entirely"
|
||||
" untouched."
|
||||
msgstr ""
|
||||
"Action appliquée au courriel. Cette action n'est exécutée que lorsque les "
|
||||
"documents ont été traités depuis des courriels. Les courriels sans pièces "
|
||||
"jointes demeurent totalement inchangés."
|
||||
#: paperless_mail/admin.py:51
|
||||
msgid "The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched."
|
||||
msgstr "Action appliquée au courriel. Cette action n'est exécutée que lorsque les documents ont été traités depuis des courriels. Les courriels sans pièces jointes demeurent totalement inchangés."
|
||||
|
||||
#: paperless_mail/admin.py:46
|
||||
#: paperless_mail/admin.py:58
|
||||
msgid "Metadata"
|
||||
msgstr "Métadonnées"
|
||||
|
||||
#: paperless_mail/admin.py:48
|
||||
msgid ""
|
||||
"Assign metadata to documents consumed from this rule automatically. If you "
|
||||
"do not assign tags, types or correspondents here, paperless will still "
|
||||
"process all matching rules that you have defined."
|
||||
msgstr ""
|
||||
"Affecter automatiquement des métadonnées aux documents traités à partir de "
|
||||
"cette règle. Si vous n'affectez pas d'étiquette, de type ou de correspondant"
|
||||
" ici, Paperless-ng appliquera toutes les autres règles de rapprochement que "
|
||||
"vous avez définies."
|
||||
#: paperless_mail/admin.py:60
|
||||
msgid "Assign metadata to documents consumed from this rule automatically. If you do not assign tags, types or correspondents here, paperless will still process all matching rules that you have defined."
|
||||
msgstr "Affecter automatiquement des métadonnées aux documents traités à partir de cette règle. Si vous n'affectez pas d'étiquette, de type ou de correspondant ici, Paperless-ng appliquera toutes les autres règles de rapprochement que vous avez définies."
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
@@ -483,12 +533,8 @@ msgid "IMAP port"
|
||||
msgstr "Port IMAP"
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid ""
|
||||
"This is usually 143 for unencrypted and STARTTLS connections, and 993 for "
|
||||
"SSL connections."
|
||||
msgstr ""
|
||||
"Généralement 143 pour les connexions non chiffrées et STARTTLS, et 993 pour "
|
||||
"les connexions SSL."
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr "Généralement 143 pour les connexions non chiffrées et STARTTLS, et 993 pour les connexions SSL."
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
msgid "IMAP security"
|
||||
@@ -502,151 +548,151 @@ msgstr "nom d'utilisateur"
|
||||
msgid "password"
|
||||
msgstr "mot de passe"
|
||||
|
||||
#: paperless_mail/models.py:60
|
||||
#: paperless_mail/models.py:54
|
||||
msgid "character set"
|
||||
msgstr "jeu de caractères"
|
||||
|
||||
#: paperless_mail/models.py:57
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
msgstr "Le jeu de caractères à utiliser lors de la communication avec le serveur de messagerie, par exemple 'UTF-8' ou 'US-ASCII'."
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "mail rule"
|
||||
msgstr "règle de courriel"
|
||||
|
||||
#: paperless_mail/models.py:61
|
||||
#: paperless_mail/models.py:69
|
||||
msgid "mail rules"
|
||||
msgstr "règles de courriel"
|
||||
|
||||
#: paperless_mail/models.py:67
|
||||
#: paperless_mail/models.py:75
|
||||
msgid "Only process attachments."
|
||||
msgstr "Ne traiter que les pièces jointes."
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
#: paperless_mail/models.py:76
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr "Traiter tous les fichiers, y compris les pièces jointes \"en ligne\"."
|
||||
|
||||
#: paperless_mail/models.py:78
|
||||
#: paperless_mail/models.py:86
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr "Marquer comme lu, ne pas traiter les courriels lus"
|
||||
|
||||
#: paperless_mail/models.py:79
|
||||
#: paperless_mail/models.py:87
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr "Marquer le courriel, ne pas traiter les courriels marqués"
|
||||
|
||||
#: paperless_mail/models.py:80
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "Move to specified folder"
|
||||
msgstr "Déplacer vers le dossier spécifié"
|
||||
|
||||
#: paperless_mail/models.py:81
|
||||
#: paperless_mail/models.py:89
|
||||
msgid "Delete"
|
||||
msgstr "Supprimer"
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
#: paperless_mail/models.py:96
|
||||
msgid "Use subject as title"
|
||||
msgstr "Utiliser le sujet en tant que titre"
|
||||
|
||||
#: paperless_mail/models.py:89
|
||||
#: paperless_mail/models.py:97
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr "Utiliser le nom de la pièce jointe en tant que titre"
|
||||
|
||||
#: paperless_mail/models.py:99
|
||||
#: paperless_mail/models.py:107
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr "Ne pas affecter de correspondant"
|
||||
|
||||
#: paperless_mail/models.py:101
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "Use mail address"
|
||||
msgstr "Utiliser l'adresse électronique"
|
||||
|
||||
#: paperless_mail/models.py:103
|
||||
#: paperless_mail/models.py:111
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr "Utiliser le nom (ou l'adresse électronique s'il n'est pas disponible)"
|
||||
|
||||
#: paperless_mail/models.py:105
|
||||
#: paperless_mail/models.py:113
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr "Utiliser le correspondant sélectionné ci-dessous"
|
||||
|
||||
#: paperless_mail/models.py:113
|
||||
#: paperless_mail/models.py:121
|
||||
msgid "order"
|
||||
msgstr "ordre"
|
||||
|
||||
#: paperless_mail/models.py:120
|
||||
#: paperless_mail/models.py:128
|
||||
msgid "account"
|
||||
msgstr "compte"
|
||||
|
||||
#: paperless_mail/models.py:124
|
||||
#: paperless_mail/models.py:132
|
||||
msgid "folder"
|
||||
msgstr "répertoire"
|
||||
|
||||
#: paperless_mail/models.py:128
|
||||
#: paperless_mail/models.py:134
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr "Les sous-dossiers doivent être séparés par des points."
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
msgid "filter from"
|
||||
msgstr "filtrer l'expéditeur"
|
||||
|
||||
#: paperless_mail/models.py:131
|
||||
#: paperless_mail/models.py:141
|
||||
msgid "filter subject"
|
||||
msgstr "filtrer le sujet"
|
||||
|
||||
#: paperless_mail/models.py:134
|
||||
#: paperless_mail/models.py:144
|
||||
msgid "filter body"
|
||||
msgstr "filtrer le corps du message"
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "filter attachment filename"
|
||||
msgstr "filtrer le nom de fichier de la pièce jointe"
|
||||
|
||||
#: paperless_mail/models.py:140
|
||||
msgid ""
|
||||
"Only consume documents which entirely match this filename if specified. "
|
||||
"Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr ""
|
||||
"Ne traiter que les documents correspondant intégralement à ce nom de fichier"
|
||||
" s'il est spécifié. Les jokers tels que *.pdf ou *facture* sont autorisés. "
|
||||
"La casse n'est pas prise en compte."
|
||||
#: paperless_mail/models.py:150
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr "Ne traiter que les documents correspondant intégralement à ce nom de fichier s'il est spécifié. Les jokers tels que *.pdf ou *facture* sont autorisés. La casse n'est pas prise en compte."
|
||||
|
||||
#: paperless_mail/models.py:146
|
||||
#: paperless_mail/models.py:156
|
||||
msgid "maximum age"
|
||||
msgstr "âge maximum"
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
#: paperless_mail/models.py:158
|
||||
msgid "Specified in days."
|
||||
msgstr "En jours."
|
||||
|
||||
#: paperless_mail/models.py:151
|
||||
#: paperless_mail/models.py:161
|
||||
msgid "attachment type"
|
||||
msgstr "type de pièce jointe"
|
||||
|
||||
#: paperless_mail/models.py:154
|
||||
msgid ""
|
||||
"Inline attachments include embedded images, so it's best to combine this "
|
||||
"option with a filename filter."
|
||||
msgstr ""
|
||||
"Les pièces jointes en ligne comprennent les images intégrées, il est donc "
|
||||
"préférable de combiner cette option avec un filtre de nom de fichier."
|
||||
#: paperless_mail/models.py:164
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr "Les pièces jointes en ligne comprennent les images intégrées, il est donc préférable de combiner cette option avec un filtre de nom de fichier."
|
||||
|
||||
#: paperless_mail/models.py:159
|
||||
#: paperless_mail/models.py:169
|
||||
msgid "action"
|
||||
msgstr "action"
|
||||
|
||||
#: paperless_mail/models.py:165
|
||||
#: paperless_mail/models.py:175
|
||||
msgid "action parameter"
|
||||
msgstr "paramètre d'action"
|
||||
|
||||
#: paperless_mail/models.py:167
|
||||
msgid ""
|
||||
"Additional parameter for the action selected above, i.e., the target folder "
|
||||
"of the move to folder action."
|
||||
msgstr ""
|
||||
"Paramètre supplémentaire pour l'action sélectionnée ci-dessus, par exemple "
|
||||
"le dossier cible de l'action de déplacement vers un dossier."
|
||||
#: paperless_mail/models.py:177
|
||||
msgid "Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots."
|
||||
msgstr "Paramètre supplémentaire pour l'action sélectionnée ci-dessus, par exemple le dossier cible de l'action de déplacement vers un dossier. Les sous-dossiers doivent être séparés par des points."
|
||||
|
||||
#: paperless_mail/models.py:173
|
||||
#: paperless_mail/models.py:184
|
||||
msgid "assign title from"
|
||||
msgstr "affecter le titre depuis"
|
||||
|
||||
#: paperless_mail/models.py:183
|
||||
#: paperless_mail/models.py:194
|
||||
msgid "assign this tag"
|
||||
msgstr "affecter cette étiquette"
|
||||
|
||||
#: paperless_mail/models.py:191
|
||||
#: paperless_mail/models.py:202
|
||||
msgid "assign this document type"
|
||||
msgstr "affecter ce type de document"
|
||||
|
||||
#: paperless_mail/models.py:195
|
||||
#: paperless_mail/models.py:206
|
||||
msgid "assign correspondent from"
|
||||
msgstr "affecter le correspondant depuis"
|
||||
|
||||
#: paperless_mail/models.py:205
|
||||
#: paperless_mail/models.py:216
|
||||
msgid "assign this correspondent"
|
||||
msgstr "affecter ce correspondant"
|
||||
|
||||
698
src/locale/hu_HU/LC_MESSAGES/django.po
Normal file
698
src/locale/hu_HU/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,698 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ng\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-05-16 09:38+0000\n"
|
||||
"PO-Revision-Date: 2021-05-16 10:09\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Hungarian\n"
|
||||
"Language: hu_HU\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: paperless-ng\n"
|
||||
"X-Crowdin-Project-ID: 434940\n"
|
||||
"X-Crowdin-Language: hu\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 54\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
msgstr "Dokumentumok"
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr "Regexp"
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr "Automatikus"
|
||||
|
||||
#: documents/models.py:41 documents/models.py:350 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:117
|
||||
msgid "name"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "match"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:49
|
||||
msgid "matching algorithm"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:55
|
||||
msgid "is insensitive"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:74 documents/models.py:120
|
||||
msgid "correspondent"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:81
|
||||
msgid "color"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:87
|
||||
msgid "is inbox tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:89
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:94
|
||||
msgid "tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:95 documents/models.py:151
|
||||
msgid "tags"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:101 documents/models.py:133
|
||||
msgid "document type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:102
|
||||
msgid "document types"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:110
|
||||
msgid "Unencrypted"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:111
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:124
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:137
|
||||
msgid "content"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:139
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:144
|
||||
msgid "mime type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:155
|
||||
msgid "checksum"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid "The checksum of the original document."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:163
|
||||
msgid "archive checksum"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:168
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:172 documents/models.py:328
|
||||
msgid "created"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:176
|
||||
msgid "modified"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:180
|
||||
msgid "storage type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:188
|
||||
msgid "added"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:192
|
||||
msgid "filename"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:198
|
||||
msgid "Current filename in storage"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:202
|
||||
msgid "archive filename"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:208
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "archive serial number"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:217
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:223
|
||||
msgid "document"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:224
|
||||
msgid "documents"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:311
|
||||
msgid "debug"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:312
|
||||
msgid "information"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:313
|
||||
msgid "warning"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:314
|
||||
msgid "error"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:315
|
||||
msgid "critical"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:319
|
||||
msgid "group"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:322
|
||||
msgid "message"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:325
|
||||
msgid "level"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:332
|
||||
msgid "log"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "logs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:344 documents/models.py:401
|
||||
msgid "saved view"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:345
|
||||
msgid "saved views"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:348
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:354
|
||||
msgid "show on dashboard"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:357
|
||||
msgid "show in sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:361
|
||||
msgid "sort field"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:367
|
||||
msgid "sort reverse"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:373
|
||||
msgid "title contains"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:374
|
||||
msgid "content contains"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:375
|
||||
msgid "ASN is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:376
|
||||
msgid "correspondent is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:377
|
||||
msgid "document type is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:378
|
||||
msgid "is in inbox"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:379
|
||||
msgid "has tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:380
|
||||
msgid "has any tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:381
|
||||
msgid "created before"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:382
|
||||
msgid "created after"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:383
|
||||
msgid "created year is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:384
|
||||
msgid "created month is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:385
|
||||
msgid "created day is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:386
|
||||
msgid "added before"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:387
|
||||
msgid "added after"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:388
|
||||
msgid "modified before"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:389
|
||||
msgid "modified after"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:390
|
||||
msgid "does not have tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:391
|
||||
msgid "does not have ASN"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:392
|
||||
msgid "title or content contains"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:393
|
||||
msgid "fulltext query"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:394
|
||||
msgid "more like this"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:405
|
||||
msgid "rule type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:409
|
||||
msgid "value"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:415
|
||||
msgid "filter rule"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:416
|
||||
msgid "filter rules"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:53
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:177
|
||||
msgid "Invalid color."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:451
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/index.html:22
|
||||
msgid "Paperless-ng is loading..."
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ng signed out"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/logged_out.html:45
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/logged_out.html:46
|
||||
msgid "Sign in again"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ng sign in"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:47
|
||||
msgid "Please sign in."
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:50
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:53
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:54
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:59
|
||||
msgid "Sign in"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:303
|
||||
msgid "English (US)"
|
||||
msgstr "Angol (US)"
|
||||
|
||||
#: paperless/settings.py:304
|
||||
msgid "English (GB)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:305
|
||||
msgid "German"
|
||||
msgstr "Német"
|
||||
|
||||
#: paperless/settings.py:306
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:307
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:308
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:309
|
||||
msgid "Portuguese"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Romanian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:312
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:313
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:314
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:315
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/urls.py:120
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:15
|
||||
msgid "Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:18
|
||||
msgid "Advanced settings"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
msgid "Filter"
|
||||
msgstr "Szűrő"
|
||||
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:49
|
||||
msgid "Actions"
|
||||
msgstr "Műveletek"
|
||||
|
||||
#: paperless_mail/admin.py:51
|
||||
msgid "The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:58
|
||||
msgid "Metadata"
|
||||
msgstr "Metaadat"
|
||||
|
||||
#: paperless_mail/admin.py:60
|
||||
msgid "Assign metadata to documents consumed from this rule automatically. If you do not assign tags, types or correspondents here, paperless will still process all matching rules that you have defined."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail account"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:12
|
||||
msgid "mail accounts"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:19
|
||||
msgid "No encryption"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:20
|
||||
msgid "Use SSL"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:21
|
||||
msgid "Use STARTTLS"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:29
|
||||
msgid "IMAP server"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:33
|
||||
msgid "IMAP port"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
msgid "IMAP security"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:46
|
||||
msgid "username"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "password"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:54
|
||||
msgid "character set"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:57
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "mail rule"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:69
|
||||
msgid "mail rules"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:75
|
||||
msgid "Only process attachments."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:76
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:86
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:87
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "Move to specified folder"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:89
|
||||
msgid "Delete"
|
||||
msgstr "Törlés"
|
||||
|
||||
#: paperless_mail/models.py:96
|
||||
msgid "Use subject as title"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:97
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:107
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "Use mail address"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:111
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:113
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:121
|
||||
msgid "order"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:128
|
||||
msgid "account"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:132
|
||||
msgid "folder"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:134
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
msgid "filter from"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:141
|
||||
msgid "filter subject"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:144
|
||||
msgid "filter body"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "filter attachment filename"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:150
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:156
|
||||
msgid "maximum age"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:158
|
||||
msgid "Specified in days."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:161
|
||||
msgid "attachment type"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:164
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:169
|
||||
msgid "action"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:175
|
||||
msgid "action parameter"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:177
|
||||
msgid "Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:184
|
||||
msgid "assign title from"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:194
|
||||
msgid "assign this tag"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:202
|
||||
msgid "assign this document type"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:206
|
||||
msgid "assign correspondent from"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:216
|
||||
msgid "assign this correspondent"
|
||||
msgstr ""
|
||||
|
||||
698
src/locale/it_IT/LC_MESSAGES/django.po
Normal file
698
src/locale/it_IT/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,698 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ng\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-05-16 09:38+0000\n"
|
||||
"PO-Revision-Date: 2021-05-17 11:06\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Italian\n"
|
||||
"Language: it_IT\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: paperless-ng\n"
|
||||
"X-Crowdin-Project-ID: 434940\n"
|
||||
"X-Crowdin-Language: it\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 54\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
msgstr "Documenti"
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr "Qualsiasi parola"
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr "Tutte le parole"
|
||||
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr "Corrispondenza esatta"
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr "Espressione regolare"
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr "Parole fuzzy"
|
||||
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr "Automatico"
|
||||
|
||||
#: documents/models.py:41 documents/models.py:350 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:117
|
||||
msgid "name"
|
||||
msgstr "nome"
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "match"
|
||||
msgstr "corrispondenza"
|
||||
|
||||
#: documents/models.py:49
|
||||
msgid "matching algorithm"
|
||||
msgstr "algoritmo di corrispondenza"
|
||||
|
||||
#: documents/models.py:55
|
||||
msgid "is insensitive"
|
||||
msgstr "non distingue maiuscole e minuscole"
|
||||
|
||||
#: documents/models.py:74 documents/models.py:120
|
||||
msgid "correspondent"
|
||||
msgstr "corrispondente"
|
||||
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr "corrispondenti"
|
||||
|
||||
#: documents/models.py:81
|
||||
msgid "color"
|
||||
msgstr "colore"
|
||||
|
||||
#: documents/models.py:87
|
||||
msgid "is inbox tag"
|
||||
msgstr "è tag di arrivo"
|
||||
|
||||
#: documents/models.py:89
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr "Contrassegna questo tag come tag in arrivo: tutti i documenti elaborati verranno taggati con questo tag."
|
||||
|
||||
#: documents/models.py:94
|
||||
msgid "tag"
|
||||
msgstr "tag"
|
||||
|
||||
#: documents/models.py:95 documents/models.py:151
|
||||
msgid "tags"
|
||||
msgstr "tag"
|
||||
|
||||
#: documents/models.py:101 documents/models.py:133
|
||||
msgid "document type"
|
||||
msgstr "tipo di documento"
|
||||
|
||||
#: documents/models.py:102
|
||||
msgid "document types"
|
||||
msgstr "tipi di documento"
|
||||
|
||||
#: documents/models.py:110
|
||||
msgid "Unencrypted"
|
||||
msgstr "Non criptato"
|
||||
|
||||
#: documents/models.py:111
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "Criptato con GNU Privacy Guard"
|
||||
|
||||
#: documents/models.py:124
|
||||
msgid "title"
|
||||
msgstr "titolo"
|
||||
|
||||
#: documents/models.py:137
|
||||
msgid "content"
|
||||
msgstr "contenuto"
|
||||
|
||||
#: documents/models.py:139
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr "I dati grezzi o solo testo del documento. Questo campo è usato principalmente per la ricerca."
|
||||
|
||||
#: documents/models.py:144
|
||||
msgid "mime type"
|
||||
msgstr "tipo mime"
|
||||
|
||||
#: documents/models.py:155
|
||||
msgid "checksum"
|
||||
msgstr "checksum"
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid "The checksum of the original document."
|
||||
msgstr "Il checksum del documento originale."
|
||||
|
||||
#: documents/models.py:163
|
||||
msgid "archive checksum"
|
||||
msgstr "checksum dell'archivio"
|
||||
|
||||
#: documents/models.py:168
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr "Il checksum del documento archiviato."
|
||||
|
||||
#: documents/models.py:172 documents/models.py:328
|
||||
msgid "created"
|
||||
msgstr "creato il"
|
||||
|
||||
#: documents/models.py:176
|
||||
msgid "modified"
|
||||
msgstr "modificato il"
|
||||
|
||||
#: documents/models.py:180
|
||||
msgid "storage type"
|
||||
msgstr "tipo di storage"
|
||||
|
||||
#: documents/models.py:188
|
||||
msgid "added"
|
||||
msgstr "aggiunto il"
|
||||
|
||||
#: documents/models.py:192
|
||||
msgid "filename"
|
||||
msgstr "nome del file"
|
||||
|
||||
#: documents/models.py:198
|
||||
msgid "Current filename in storage"
|
||||
msgstr "Nome del file corrente nello storage"
|
||||
|
||||
#: documents/models.py:202
|
||||
msgid "archive filename"
|
||||
msgstr "Nome file in archivio"
|
||||
|
||||
#: documents/models.py:208
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr "Il nome del file nell'archiviazione"
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "archive serial number"
|
||||
msgstr "numero seriale dell'archivio"
|
||||
|
||||
#: documents/models.py:217
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr "Posizione di questo documento all'interno dell'archivio fisico."
|
||||
|
||||
#: documents/models.py:223
|
||||
msgid "document"
|
||||
msgstr "documento"
|
||||
|
||||
#: documents/models.py:224
|
||||
msgid "documents"
|
||||
msgstr "documenti"
|
||||
|
||||
#: documents/models.py:311
|
||||
msgid "debug"
|
||||
msgstr "debug"
|
||||
|
||||
#: documents/models.py:312
|
||||
msgid "information"
|
||||
msgstr "informazione"
|
||||
|
||||
#: documents/models.py:313
|
||||
msgid "warning"
|
||||
msgstr "avvertimento"
|
||||
|
||||
#: documents/models.py:314
|
||||
msgid "error"
|
||||
msgstr "errore"
|
||||
|
||||
#: documents/models.py:315
|
||||
msgid "critical"
|
||||
msgstr "critico"
|
||||
|
||||
#: documents/models.py:319
|
||||
msgid "group"
|
||||
msgstr "gruppo"
|
||||
|
||||
#: documents/models.py:322
|
||||
msgid "message"
|
||||
msgstr "messaggio"
|
||||
|
||||
#: documents/models.py:325
|
||||
msgid "level"
|
||||
msgstr "livello"
|
||||
|
||||
#: documents/models.py:332
|
||||
msgid "log"
|
||||
msgstr "log"
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "logs"
|
||||
msgstr "log"
|
||||
|
||||
#: documents/models.py:344 documents/models.py:401
|
||||
msgid "saved view"
|
||||
msgstr "vista salvata"
|
||||
|
||||
#: documents/models.py:345
|
||||
msgid "saved views"
|
||||
msgstr "viste salvate"
|
||||
|
||||
#: documents/models.py:348
|
||||
msgid "user"
|
||||
msgstr "utente"
|
||||
|
||||
#: documents/models.py:354
|
||||
msgid "show on dashboard"
|
||||
msgstr "mostra sul cruscotto"
|
||||
|
||||
#: documents/models.py:357
|
||||
msgid "show in sidebar"
|
||||
msgstr "mostra nella barra laterale"
|
||||
|
||||
#: documents/models.py:361
|
||||
msgid "sort field"
|
||||
msgstr "campo di ordinamento"
|
||||
|
||||
#: documents/models.py:367
|
||||
msgid "sort reverse"
|
||||
msgstr "ordine invertito"
|
||||
|
||||
#: documents/models.py:373
|
||||
msgid "title contains"
|
||||
msgstr "il titolo contiene"
|
||||
|
||||
#: documents/models.py:374
|
||||
msgid "content contains"
|
||||
msgstr "il contenuto contiene"
|
||||
|
||||
#: documents/models.py:375
|
||||
msgid "ASN is"
|
||||
msgstr "ASN è"
|
||||
|
||||
#: documents/models.py:376
|
||||
msgid "correspondent is"
|
||||
msgstr "la corrispondenza è"
|
||||
|
||||
#: documents/models.py:377
|
||||
msgid "document type is"
|
||||
msgstr "il tipo di documento è"
|
||||
|
||||
#: documents/models.py:378
|
||||
msgid "is in inbox"
|
||||
msgstr "è in arrivo"
|
||||
|
||||
#: documents/models.py:379
|
||||
msgid "has tag"
|
||||
msgstr "ha etichetta"
|
||||
|
||||
#: documents/models.py:380
|
||||
msgid "has any tag"
|
||||
msgstr "ha qualsiasi etichetta"
|
||||
|
||||
#: documents/models.py:381
|
||||
msgid "created before"
|
||||
msgstr "creato prima del"
|
||||
|
||||
#: documents/models.py:382
|
||||
msgid "created after"
|
||||
msgstr "creato dopo il"
|
||||
|
||||
#: documents/models.py:383
|
||||
msgid "created year is"
|
||||
msgstr "l'anno di creazione è"
|
||||
|
||||
#: documents/models.py:384
|
||||
msgid "created month is"
|
||||
msgstr "il mese di creazione è"
|
||||
|
||||
#: documents/models.py:385
|
||||
msgid "created day is"
|
||||
msgstr "il giorno di creazione è"
|
||||
|
||||
#: documents/models.py:386
|
||||
msgid "added before"
|
||||
msgstr "aggiunto prima del"
|
||||
|
||||
#: documents/models.py:387
|
||||
msgid "added after"
|
||||
msgstr "aggiunto dopo il"
|
||||
|
||||
#: documents/models.py:388
|
||||
msgid "modified before"
|
||||
msgstr "modificato prima del"
|
||||
|
||||
#: documents/models.py:389
|
||||
msgid "modified after"
|
||||
msgstr "modificato dopo"
|
||||
|
||||
#: documents/models.py:390
|
||||
msgid "does not have tag"
|
||||
msgstr "non ha tag"
|
||||
|
||||
#: documents/models.py:391
|
||||
msgid "does not have ASN"
|
||||
msgstr "non ha ASN"
|
||||
|
||||
#: documents/models.py:392
|
||||
msgid "title or content contains"
|
||||
msgstr "il titolo o il contenuto contiene"
|
||||
|
||||
#: documents/models.py:393
|
||||
msgid "fulltext query"
|
||||
msgstr "query fulltext"
|
||||
|
||||
#: documents/models.py:394
|
||||
msgid "more like this"
|
||||
msgstr "altro come questo"
|
||||
|
||||
#: documents/models.py:405
|
||||
msgid "rule type"
|
||||
msgstr "tipo di regola"
|
||||
|
||||
#: documents/models.py:409
|
||||
msgid "value"
|
||||
msgstr "valore"
|
||||
|
||||
#: documents/models.py:415
|
||||
msgid "filter rule"
|
||||
msgstr "regola filtro"
|
||||
|
||||
#: documents/models.py:416
|
||||
msgid "filter rules"
|
||||
msgstr "regole filtro"
|
||||
|
||||
#: documents/serialisers.py:53
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr "Espressione regolare non valida: %(error)s"
|
||||
|
||||
#: documents/serialisers.py:177
|
||||
msgid "Invalid color."
|
||||
msgstr "Colore non valido."
|
||||
|
||||
#: documents/serialisers.py:451
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Il tipo di file %(type)s non è supportato"
|
||||
|
||||
#: documents/templates/index.html:22
|
||||
msgid "Paperless-ng is loading..."
|
||||
msgstr "Paperless-ng è in caricamento..."
|
||||
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ng signed out"
|
||||
msgstr "Paperless-ng è uscito"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:45
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr "Ti sei disconnesso. A presto!"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:46
|
||||
msgid "Sign in again"
|
||||
msgstr "Accedi nuovamente"
|
||||
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ng sign in"
|
||||
msgstr "Accedi a Paperless-ng"
|
||||
|
||||
#: documents/templates/registration/login.html:47
|
||||
msgid "Please sign in."
|
||||
msgstr "Accedi"
|
||||
|
||||
#: documents/templates/registration/login.html:50
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr "Il nome utente e la password non sono corretti. Riprovare."
|
||||
|
||||
#: documents/templates/registration/login.html:53
|
||||
msgid "Username"
|
||||
msgstr "Nome utente"
|
||||
|
||||
#: documents/templates/registration/login.html:54
|
||||
msgid "Password"
|
||||
msgstr "Password"
|
||||
|
||||
#: documents/templates/registration/login.html:59
|
||||
msgid "Sign in"
|
||||
msgstr "Accedi"
|
||||
|
||||
#: paperless/settings.py:303
|
||||
msgid "English (US)"
|
||||
msgstr "Inglese (US)"
|
||||
|
||||
#: paperless/settings.py:304
|
||||
msgid "English (GB)"
|
||||
msgstr "Inglese (GB)"
|
||||
|
||||
#: paperless/settings.py:305
|
||||
msgid "German"
|
||||
msgstr "Tedesco"
|
||||
|
||||
#: paperless/settings.py:306
|
||||
msgid "Dutch"
|
||||
msgstr "Olandese"
|
||||
|
||||
#: paperless/settings.py:307
|
||||
msgid "French"
|
||||
msgstr "Francese"
|
||||
|
||||
#: paperless/settings.py:308
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Portoghese (Brasile)"
|
||||
|
||||
#: paperless/settings.py:309
|
||||
msgid "Portuguese"
|
||||
msgstr "Portoghese"
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Italian"
|
||||
msgstr "Italiano"
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Romanian"
|
||||
msgstr "Rumeno"
|
||||
|
||||
#: paperless/settings.py:312
|
||||
msgid "Russian"
|
||||
msgstr "Russo"
|
||||
|
||||
#: paperless/settings.py:313
|
||||
msgid "Spanish"
|
||||
msgstr "Spagnolo"
|
||||
|
||||
#: paperless/settings.py:314
|
||||
msgid "Polish"
|
||||
msgstr "Polacco"
|
||||
|
||||
#: paperless/settings.py:315
|
||||
msgid "Swedish"
|
||||
msgstr "Svedese"
|
||||
|
||||
#: paperless/urls.py:120
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr "Amministrazione di Paperless-ng"
|
||||
|
||||
#: paperless_mail/admin.py:15
|
||||
msgid "Authentication"
|
||||
msgstr "Autenticazione"
|
||||
|
||||
#: paperless_mail/admin.py:18
|
||||
msgid "Advanced settings"
|
||||
msgstr "Impostazioni avanzate"
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
msgid "Filter"
|
||||
msgstr "Filtro"
|
||||
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr "Paperless-ng processerà solo la posta che rientra in TUTTI i filtri impostati."
|
||||
|
||||
#: paperless_mail/admin.py:49
|
||||
msgid "Actions"
|
||||
msgstr "Azioni"
|
||||
|
||||
#: paperless_mail/admin.py:51
|
||||
msgid "The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched."
|
||||
msgstr "L'azione che viene applicata alla email. Questa azione viene eseguita solo quando dei documenti vengono elaborati dalla email. Le email senza allegati vengono ignorate."
|
||||
|
||||
#: paperless_mail/admin.py:58
|
||||
msgid "Metadata"
|
||||
msgstr "Metadati"
|
||||
|
||||
#: paperless_mail/admin.py:60
|
||||
msgid "Assign metadata to documents consumed from this rule automatically. If you do not assign tags, types or correspondents here, paperless will still process all matching rules that you have defined."
|
||||
msgstr "Assegna automaticamente i metadati ai documenti elaborati da questa regola. Se non assegni qui dei tag, tipi di documenti o corrispondenti, Paperless userà comunque le regole corrispondenti che hai configurato."
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
msgstr "Email Paperless"
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail account"
|
||||
msgstr "account email"
|
||||
|
||||
#: paperless_mail/models.py:12
|
||||
msgid "mail accounts"
|
||||
msgstr "account email"
|
||||
|
||||
#: paperless_mail/models.py:19
|
||||
msgid "No encryption"
|
||||
msgstr "Nessuna crittografia"
|
||||
|
||||
#: paperless_mail/models.py:20
|
||||
msgid "Use SSL"
|
||||
msgstr "Usa SSL"
|
||||
|
||||
#: paperless_mail/models.py:21
|
||||
msgid "Use STARTTLS"
|
||||
msgstr "Usa STARTTLS"
|
||||
|
||||
#: paperless_mail/models.py:29
|
||||
msgid "IMAP server"
|
||||
msgstr "Server IMAP"
|
||||
|
||||
#: paperless_mail/models.py:33
|
||||
msgid "IMAP port"
|
||||
msgstr "Porta IMAP"
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr "Di solito si usa 143 per STARTTLS o nessuna crittografia e 993 per SSL."
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
msgid "IMAP security"
|
||||
msgstr "Sicurezza IMAP"
|
||||
|
||||
#: paperless_mail/models.py:46
|
||||
msgid "username"
|
||||
msgstr "nome utente"
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "password"
|
||||
msgstr "password"
|
||||
|
||||
#: paperless_mail/models.py:54
|
||||
msgid "character set"
|
||||
msgstr "set di caratteri"
|
||||
|
||||
#: paperless_mail/models.py:57
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
msgstr "Il set di caratteri da usare quando si comunica con il server di posta, come 'UTF-8' o 'US-ASCII'."
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "mail rule"
|
||||
msgstr "regola email"
|
||||
|
||||
#: paperless_mail/models.py:69
|
||||
msgid "mail rules"
|
||||
msgstr "regole email"
|
||||
|
||||
#: paperless_mail/models.py:75
|
||||
msgid "Only process attachments."
|
||||
msgstr "Elabora solo gli allegati."
|
||||
|
||||
#: paperless_mail/models.py:76
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr "Elabora tutti i file, inclusi gli allegati nel corpo."
|
||||
|
||||
#: paperless_mail/models.py:86
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr "Segna come letto, non elaborare le email lette"
|
||||
|
||||
#: paperless_mail/models.py:87
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr "Contrassegna la email, non elaborare le email elaborate."
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "Move to specified folder"
|
||||
msgstr "Sposta in una cartella"
|
||||
|
||||
#: paperless_mail/models.py:89
|
||||
msgid "Delete"
|
||||
msgstr "Elimina"
|
||||
|
||||
#: paperless_mail/models.py:96
|
||||
msgid "Use subject as title"
|
||||
msgstr "Usa oggetto come titolo"
|
||||
|
||||
#: paperless_mail/models.py:97
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr "Usa il nome dell'allegato come titolo"
|
||||
|
||||
#: paperless_mail/models.py:107
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr "Non assegnare un corrispondente"
|
||||
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "Use mail address"
|
||||
msgstr "Usa indirizzo email"
|
||||
|
||||
#: paperless_mail/models.py:111
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr "Usa nome (o indirizzo email se non disponibile)"
|
||||
|
||||
#: paperless_mail/models.py:113
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr "Usa il corrispondente selezionato qui sotto"
|
||||
|
||||
#: paperless_mail/models.py:121
|
||||
msgid "order"
|
||||
msgstr "priorità"
|
||||
|
||||
#: paperless_mail/models.py:128
|
||||
msgid "account"
|
||||
msgstr "account"
|
||||
|
||||
#: paperless_mail/models.py:132
|
||||
msgid "folder"
|
||||
msgstr "cartella"
|
||||
|
||||
#: paperless_mail/models.py:134
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr "Le sottocartelle devono essere separate da punti."
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
msgid "filter from"
|
||||
msgstr "filtra da"
|
||||
|
||||
#: paperless_mail/models.py:141
|
||||
msgid "filter subject"
|
||||
msgstr "filtra oggetto"
|
||||
|
||||
#: paperless_mail/models.py:144
|
||||
msgid "filter body"
|
||||
msgstr "filtra corpo"
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "filter attachment filename"
|
||||
msgstr "filtra nome allegato"
|
||||
|
||||
#: paperless_mail/models.py:150
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr "Elabora i documenti che corrispondono a questo nome. Puoi usare wildcard come *.pdf o *fattura*. Non fa differenza fra maiuscole e minuscole."
|
||||
|
||||
#: paperless_mail/models.py:156
|
||||
msgid "maximum age"
|
||||
msgstr "età massima"
|
||||
|
||||
#: paperless_mail/models.py:158
|
||||
msgid "Specified in days."
|
||||
msgstr "Definito in giorni."
|
||||
|
||||
#: paperless_mail/models.py:161
|
||||
msgid "attachment type"
|
||||
msgstr "tipo di allegato"
|
||||
|
||||
#: paperless_mail/models.py:164
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr "Gli allegati in linea includono le immagini nel corpo, quindi è meglio combinare questa opzione con il filtro nome."
|
||||
|
||||
#: paperless_mail/models.py:169
|
||||
msgid "action"
|
||||
msgstr "azione"
|
||||
|
||||
#: paperless_mail/models.py:175
|
||||
msgid "action parameter"
|
||||
msgstr "parametro azione"
|
||||
|
||||
#: paperless_mail/models.py:177
|
||||
msgid "Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots."
|
||||
msgstr "Parametro aggiuntivo per l'azione selezionata, ad esempio la cartella di destinazione per l'azione che sposta una cartella. Le sottocartelle devono essere separate da punti."
|
||||
|
||||
#: paperless_mail/models.py:184
|
||||
msgid "assign title from"
|
||||
msgstr "assegna tittolo da"
|
||||
|
||||
#: paperless_mail/models.py:194
|
||||
msgid "assign this tag"
|
||||
msgstr "assegna questo tag"
|
||||
|
||||
#: paperless_mail/models.py:202
|
||||
msgid "assign this document type"
|
||||
msgstr "assegna questo tipo di documento"
|
||||
|
||||
#: paperless_mail/models.py:206
|
||||
msgid "assign correspondent from"
|
||||
msgstr "assegna corrispondente da"
|
||||
|
||||
#: paperless_mail/models.py:216
|
||||
msgid "assign this correspondent"
|
||||
msgstr "assegna questo corrispondente"
|
||||
|
||||
642
src/locale/la_LA/LC_MESSAGES/django.po
Normal file
642
src/locale/la_LA/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,642 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ng\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-02-28 12:40+0100\n"
|
||||
"PO-Revision-Date: 2021-03-06 21:39\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Latin\n"
|
||||
"Language: la_LA\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: paperless-ng\n"
|
||||
"X-Crowdin-Project-ID: 434940\n"
|
||||
"X-Crowdin-Language: la-LA\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 54\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:41 documents/models.py:350 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "name"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "match"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:49
|
||||
msgid "matching algorithm"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:55
|
||||
msgid "is insensitive"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:74 documents/models.py:120
|
||||
msgid "correspondent"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:81
|
||||
msgid "color"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:87
|
||||
msgid "is inbox tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:89
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:94
|
||||
msgid "tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:95 documents/models.py:151
|
||||
msgid "tags"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:101 documents/models.py:133
|
||||
msgid "document type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:102
|
||||
msgid "document types"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:110
|
||||
msgid "Unencrypted"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:111
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:124
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:137
|
||||
msgid "content"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:139
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:144
|
||||
msgid "mime type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:155
|
||||
msgid "checksum"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid "The checksum of the original document."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:163
|
||||
msgid "archive checksum"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:168
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:172 documents/models.py:328
|
||||
msgid "created"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:176
|
||||
msgid "modified"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:180
|
||||
msgid "storage type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:188
|
||||
msgid "added"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:192
|
||||
msgid "filename"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:198
|
||||
msgid "Current filename in storage"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:202
|
||||
msgid "archive filename"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:208
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "archive serial number"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:217
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:223
|
||||
msgid "document"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:224
|
||||
msgid "documents"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:311
|
||||
msgid "debug"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:312
|
||||
msgid "information"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:313
|
||||
msgid "warning"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:314
|
||||
msgid "error"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:315
|
||||
msgid "critical"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:319
|
||||
msgid "group"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:322
|
||||
msgid "message"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:325
|
||||
msgid "level"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:332
|
||||
msgid "log"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "logs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:344 documents/models.py:394
|
||||
msgid "saved view"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:345
|
||||
msgid "saved views"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:348
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:354
|
||||
msgid "show on dashboard"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:357
|
||||
msgid "show in sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:361
|
||||
msgid "sort field"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:364
|
||||
msgid "sort reverse"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:370
|
||||
msgid "title contains"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:371
|
||||
msgid "content contains"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:372
|
||||
msgid "ASN is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:373
|
||||
msgid "correspondent is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:374
|
||||
msgid "document type is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:375
|
||||
msgid "is in inbox"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:376
|
||||
msgid "has tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:377
|
||||
msgid "has any tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:378
|
||||
msgid "created before"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:379
|
||||
msgid "created after"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:380
|
||||
msgid "created year is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:381
|
||||
msgid "created month is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:382
|
||||
msgid "created day is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:383
|
||||
msgid "added before"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:384
|
||||
msgid "added after"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:385
|
||||
msgid "modified before"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:386
|
||||
msgid "modified after"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:387
|
||||
msgid "does not have tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:398
|
||||
msgid "rule type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:402
|
||||
msgid "value"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:408
|
||||
msgid "filter rule"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:409
|
||||
msgid "filter rules"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:53
|
||||
#, python-format
|
||||
msgid "Invalid regular expresssion: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:177
|
||||
msgid "Invalid color."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:451
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/index.html:21
|
||||
msgid "Paperless-ng is loading..."
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/logged_out.html:13
|
||||
msgid "Paperless-ng signed out"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/logged_out.html:41
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/logged_out.html:42
|
||||
msgid "Sign in again"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:13
|
||||
msgid "Paperless-ng sign in"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:42
|
||||
msgid "Please sign in."
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:45
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:48
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:49
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:54
|
||||
msgid "Sign in"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:297
|
||||
msgid "English (US)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:298
|
||||
msgid "English (GB)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:299
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:300
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:301
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:302
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:303
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:304
|
||||
msgid "Romanian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/urls.py:118
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:25
|
||||
msgid "Filter"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:27
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
msgid "Actions"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid "The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:46
|
||||
msgid "Metadata"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:48
|
||||
msgid "Assign metadata to documents consumed from this rule automatically. If you do not assign tags, types or correspondents here, paperless will still process all matching rules that you have defined."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail account"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:12
|
||||
msgid "mail accounts"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:19
|
||||
msgid "No encryption"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:20
|
||||
msgid "Use SSL"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:21
|
||||
msgid "Use STARTTLS"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:29
|
||||
msgid "IMAP server"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:33
|
||||
msgid "IMAP port"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
msgid "IMAP security"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:46
|
||||
msgid "username"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "password"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:60
|
||||
msgid "mail rule"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:61
|
||||
msgid "mail rules"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:67
|
||||
msgid "Only process attachments."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:78
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:79
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:80
|
||||
msgid "Move to specified folder"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:81
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "Use subject as title"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:89
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:99
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:101
|
||||
msgid "Use mail address"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:103
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:105
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:113
|
||||
msgid "order"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:120
|
||||
msgid "account"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:124
|
||||
msgid "folder"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:128
|
||||
msgid "filter from"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:131
|
||||
msgid "filter subject"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:134
|
||||
msgid "filter body"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
msgid "filter attachment filename"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:140
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:146
|
||||
msgid "maximum age"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "Specified in days."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:151
|
||||
msgid "attachment type"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:154
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:159
|
||||
msgid "action"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:165
|
||||
msgid "action parameter"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:167
|
||||
msgid "Additional parameter for the action selected above, i.e., the target folder of the move to folder action."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:173
|
||||
msgid "assign title from"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:183
|
||||
msgid "assign this tag"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:191
|
||||
msgid "assign this document type"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:195
|
||||
msgid "assign correspondent from"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:205
|
||||
msgid "assign this correspondent"
|
||||
msgstr ""
|
||||
|
||||
698
src/locale/lb_LU/LC_MESSAGES/django.po
Normal file
698
src/locale/lb_LU/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,698 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ng\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-05-16 09:38+0000\n"
|
||||
"PO-Revision-Date: 2021-07-16 14:22\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Luxembourgish\n"
|
||||
"Language: lb_LU\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: paperless-ng\n"
|
||||
"X-Crowdin-Project-ID: 434940\n"
|
||||
"X-Crowdin-Language: lb\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 54\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
msgstr "Dokumenter"
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr "Iergendee Wuert"
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr "All d'Wierder"
|
||||
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr "Exakten Treffer"
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr "Regulären Ausdrock"
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr "Ongenaut Wuert"
|
||||
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr "Automatesch"
|
||||
|
||||
#: documents/models.py:41 documents/models.py:350 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:117
|
||||
msgid "name"
|
||||
msgstr "Numm"
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "match"
|
||||
msgstr "Zouweisungsmuster"
|
||||
|
||||
#: documents/models.py:49
|
||||
msgid "matching algorithm"
|
||||
msgstr "Zouweisungsalgorithmus"
|
||||
|
||||
#: documents/models.py:55
|
||||
msgid "is insensitive"
|
||||
msgstr "Grouss-/Klengschreiwung ignoréieren"
|
||||
|
||||
#: documents/models.py:74 documents/models.py:120
|
||||
msgid "correspondent"
|
||||
msgstr "Korrespondent"
|
||||
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr "Korrespondenten"
|
||||
|
||||
#: documents/models.py:81
|
||||
msgid "color"
|
||||
msgstr "Faarf"
|
||||
|
||||
#: documents/models.py:87
|
||||
msgid "is inbox tag"
|
||||
msgstr "Postaganks-Etikett"
|
||||
|
||||
#: documents/models.py:89
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr "Dës Etikett als Postaganks-Etikett markéieren: All nei importéiert Dokumenter kréien ëmmer dës Etikett zougewisen."
|
||||
|
||||
#: documents/models.py:94
|
||||
msgid "tag"
|
||||
msgstr "Etikett"
|
||||
|
||||
#: documents/models.py:95 documents/models.py:151
|
||||
msgid "tags"
|
||||
msgstr "Etiketten"
|
||||
|
||||
#: documents/models.py:101 documents/models.py:133
|
||||
msgid "document type"
|
||||
msgstr "Dokumententyp"
|
||||
|
||||
#: documents/models.py:102
|
||||
msgid "document types"
|
||||
msgstr "Dokumententypen"
|
||||
|
||||
#: documents/models.py:110
|
||||
msgid "Unencrypted"
|
||||
msgstr "Onverschlësselt"
|
||||
|
||||
#: documents/models.py:111
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "Verschlësselt mat GNU Privacy Guard"
|
||||
|
||||
#: documents/models.py:124
|
||||
msgid "title"
|
||||
msgstr "Titel"
|
||||
|
||||
#: documents/models.py:137
|
||||
msgid "content"
|
||||
msgstr "Inhalt"
|
||||
|
||||
#: documents/models.py:139
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr "De réien Textinhalt vum Dokument. Dëst Feld gëtt primär fir d'Sich benotzt."
|
||||
|
||||
#: documents/models.py:144
|
||||
msgid "mime type"
|
||||
msgstr "MIME-Typ"
|
||||
|
||||
#: documents/models.py:155
|
||||
msgid "checksum"
|
||||
msgstr "Préifzomm"
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid "The checksum of the original document."
|
||||
msgstr "D'Préifzomm vum Original-Dokument."
|
||||
|
||||
#: documents/models.py:163
|
||||
msgid "archive checksum"
|
||||
msgstr "Archiv-Préifzomm"
|
||||
|
||||
#: documents/models.py:168
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr "D'Préifzomm vum archivéierten Dokument."
|
||||
|
||||
#: documents/models.py:172 documents/models.py:328
|
||||
msgid "created"
|
||||
msgstr "erstallt"
|
||||
|
||||
#: documents/models.py:176
|
||||
msgid "modified"
|
||||
msgstr "verännert"
|
||||
|
||||
#: documents/models.py:180
|
||||
msgid "storage type"
|
||||
msgstr "Späichertyp"
|
||||
|
||||
#: documents/models.py:188
|
||||
msgid "added"
|
||||
msgstr "derbäigesat"
|
||||
|
||||
#: documents/models.py:192
|
||||
msgid "filename"
|
||||
msgstr "Fichiersnumm"
|
||||
|
||||
#: documents/models.py:198
|
||||
msgid "Current filename in storage"
|
||||
msgstr "Aktuellen Dateinumm am Späicher"
|
||||
|
||||
#: documents/models.py:202
|
||||
msgid "archive filename"
|
||||
msgstr "Archiv-Dateinumm"
|
||||
|
||||
#: documents/models.py:208
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr "Aktuellen Dateinumm am Archiv"
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "archive serial number"
|
||||
msgstr "Archiv-Seriennummer"
|
||||
|
||||
#: documents/models.py:217
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr "D'Positioun vun dësem Dokument am physeschen Dokumentenarchiv."
|
||||
|
||||
#: documents/models.py:223
|
||||
msgid "document"
|
||||
msgstr "Dokument"
|
||||
|
||||
#: documents/models.py:224
|
||||
msgid "documents"
|
||||
msgstr "Dokumenter"
|
||||
|
||||
#: documents/models.py:311
|
||||
msgid "debug"
|
||||
msgstr "Fehlersiich"
|
||||
|
||||
#: documents/models.py:312
|
||||
msgid "information"
|
||||
msgstr "Informatioun"
|
||||
|
||||
#: documents/models.py:313
|
||||
msgid "warning"
|
||||
msgstr "Warnung"
|
||||
|
||||
#: documents/models.py:314
|
||||
msgid "error"
|
||||
msgstr "Feeler"
|
||||
|
||||
#: documents/models.py:315
|
||||
msgid "critical"
|
||||
msgstr "kritesch"
|
||||
|
||||
#: documents/models.py:319
|
||||
msgid "group"
|
||||
msgstr "Grupp"
|
||||
|
||||
#: documents/models.py:322
|
||||
msgid "message"
|
||||
msgstr "Message"
|
||||
|
||||
#: documents/models.py:325
|
||||
msgid "level"
|
||||
msgstr "Niveau"
|
||||
|
||||
#: documents/models.py:332
|
||||
msgid "log"
|
||||
msgstr "Protokoll"
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "logs"
|
||||
msgstr "Protokoller"
|
||||
|
||||
#: documents/models.py:344 documents/models.py:401
|
||||
msgid "saved view"
|
||||
msgstr "Gespäichert Usiicht"
|
||||
|
||||
#: documents/models.py:345
|
||||
msgid "saved views"
|
||||
msgstr "Gespäichert Usiichten"
|
||||
|
||||
#: documents/models.py:348
|
||||
msgid "user"
|
||||
msgstr "Benotzer"
|
||||
|
||||
#: documents/models.py:354
|
||||
msgid "show on dashboard"
|
||||
msgstr "Op der Startsäit uweisen"
|
||||
|
||||
#: documents/models.py:357
|
||||
msgid "show in sidebar"
|
||||
msgstr "An der Säiteleescht uweisen"
|
||||
|
||||
#: documents/models.py:361
|
||||
msgid "sort field"
|
||||
msgstr "Zortéierfeld"
|
||||
|
||||
#: documents/models.py:367
|
||||
msgid "sort reverse"
|
||||
msgstr "ëmgedréint zortéieren"
|
||||
|
||||
#: documents/models.py:373
|
||||
msgid "title contains"
|
||||
msgstr "Titel enthält"
|
||||
|
||||
#: documents/models.py:374
|
||||
msgid "content contains"
|
||||
msgstr "Inhalt enthält"
|
||||
|
||||
#: documents/models.py:375
|
||||
msgid "ASN is"
|
||||
msgstr "ASN ass"
|
||||
|
||||
#: documents/models.py:376
|
||||
msgid "correspondent is"
|
||||
msgstr "Korrespondent ass"
|
||||
|
||||
#: documents/models.py:377
|
||||
msgid "document type is"
|
||||
msgstr "Dokumententyp ass"
|
||||
|
||||
#: documents/models.py:378
|
||||
msgid "is in inbox"
|
||||
msgstr "ass am Postagank"
|
||||
|
||||
#: documents/models.py:379
|
||||
msgid "has tag"
|
||||
msgstr "huet Etikett"
|
||||
|
||||
#: documents/models.py:380
|
||||
msgid "has any tag"
|
||||
msgstr "huet iergendeng Etikett"
|
||||
|
||||
#: documents/models.py:381
|
||||
msgid "created before"
|
||||
msgstr "erstallt virun"
|
||||
|
||||
#: documents/models.py:382
|
||||
msgid "created after"
|
||||
msgstr "erstallt no"
|
||||
|
||||
#: documents/models.py:383
|
||||
msgid "created year is"
|
||||
msgstr "Erstellungsjoer ass"
|
||||
|
||||
#: documents/models.py:384
|
||||
msgid "created month is"
|
||||
msgstr "Erstellungsmount ass"
|
||||
|
||||
#: documents/models.py:385
|
||||
msgid "created day is"
|
||||
msgstr "Erstellungsdag ass"
|
||||
|
||||
#: documents/models.py:386
|
||||
msgid "added before"
|
||||
msgstr "dobäigesat virun"
|
||||
|
||||
#: documents/models.py:387
|
||||
msgid "added after"
|
||||
msgstr "dobäigesat no"
|
||||
|
||||
#: documents/models.py:388
|
||||
msgid "modified before"
|
||||
msgstr "verännert virun"
|
||||
|
||||
#: documents/models.py:389
|
||||
msgid "modified after"
|
||||
msgstr "verännert no"
|
||||
|
||||
#: documents/models.py:390
|
||||
msgid "does not have tag"
|
||||
msgstr "huet dës Etikett net"
|
||||
|
||||
#: documents/models.py:391
|
||||
msgid "does not have ASN"
|
||||
msgstr "huet keng ASN"
|
||||
|
||||
#: documents/models.py:392
|
||||
msgid "title or content contains"
|
||||
msgstr "Titel oder Inhalt enthalen"
|
||||
|
||||
#: documents/models.py:393
|
||||
msgid "fulltext query"
|
||||
msgstr "Volltextsich"
|
||||
|
||||
#: documents/models.py:394
|
||||
msgid "more like this"
|
||||
msgstr "ähnlech Dokumenter"
|
||||
|
||||
#: documents/models.py:405
|
||||
msgid "rule type"
|
||||
msgstr "Reegeltyp"
|
||||
|
||||
#: documents/models.py:409
|
||||
msgid "value"
|
||||
msgstr "Wäert"
|
||||
|
||||
#: documents/models.py:415
|
||||
msgid "filter rule"
|
||||
msgstr "Filterreegel"
|
||||
|
||||
#: documents/models.py:416
|
||||
msgid "filter rules"
|
||||
msgstr "Filterreegelen"
|
||||
|
||||
#: documents/serialisers.py:53
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr "Ongëltege regulären Ausdrock: %(error)s"
|
||||
|
||||
#: documents/serialisers.py:177
|
||||
msgid "Invalid color."
|
||||
msgstr "Ongëlteg Faarf."
|
||||
|
||||
#: documents/serialisers.py:451
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Fichierstyp %(type)s net ënnerstëtzt"
|
||||
|
||||
#: documents/templates/index.html:22
|
||||
msgid "Paperless-ng is loading..."
|
||||
msgstr "Paperless-ng gëtt gelueden..."
|
||||
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ng signed out"
|
||||
msgstr "Paperless-ng ofgemellt"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:45
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr "Dir hutt Iech erfollegräich ofgemellt. Bis geschwënn!"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:46
|
||||
msgid "Sign in again"
|
||||
msgstr "Nees umellen"
|
||||
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ng sign in"
|
||||
msgstr "Umeldung bei Paperless-ng"
|
||||
|
||||
#: documents/templates/registration/login.html:47
|
||||
msgid "Please sign in."
|
||||
msgstr "W. e. g. umellen."
|
||||
|
||||
#: documents/templates/registration/login.html:50
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr "Äre Benotzernumm a Passwuert stëmmen net iwwereneen. Probéiert w. e. g. nach emol."
|
||||
|
||||
#: documents/templates/registration/login.html:53
|
||||
msgid "Username"
|
||||
msgstr "Benotzernumm"
|
||||
|
||||
#: documents/templates/registration/login.html:54
|
||||
msgid "Password"
|
||||
msgstr "Passwuert"
|
||||
|
||||
#: documents/templates/registration/login.html:59
|
||||
msgid "Sign in"
|
||||
msgstr "Umellen"
|
||||
|
||||
#: paperless/settings.py:303
|
||||
msgid "English (US)"
|
||||
msgstr "Englesch (USA)"
|
||||
|
||||
#: paperless/settings.py:304
|
||||
msgid "English (GB)"
|
||||
msgstr "Englesch (GB)"
|
||||
|
||||
#: paperless/settings.py:305
|
||||
msgid "German"
|
||||
msgstr "Däitsch"
|
||||
|
||||
#: paperless/settings.py:306
|
||||
msgid "Dutch"
|
||||
msgstr "Hollännesch"
|
||||
|
||||
#: paperless/settings.py:307
|
||||
msgid "French"
|
||||
msgstr "Franséisch"
|
||||
|
||||
#: paperless/settings.py:308
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Portugisesch (Brasilien)"
|
||||
|
||||
#: paperless/settings.py:309
|
||||
msgid "Portuguese"
|
||||
msgstr "Portugisesch"
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Italian"
|
||||
msgstr "Italienesch"
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Romanian"
|
||||
msgstr "Rumänesch"
|
||||
|
||||
#: paperless/settings.py:312
|
||||
msgid "Russian"
|
||||
msgstr "Russesch"
|
||||
|
||||
#: paperless/settings.py:313
|
||||
msgid "Spanish"
|
||||
msgstr "Spuenesch"
|
||||
|
||||
#: paperless/settings.py:314
|
||||
msgid "Polish"
|
||||
msgstr "Polnesch"
|
||||
|
||||
#: paperless/settings.py:315
|
||||
msgid "Swedish"
|
||||
msgstr "Schwedesch"
|
||||
|
||||
#: paperless/urls.py:120
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr "Paperless-ng-Administratioun"
|
||||
|
||||
#: paperless_mail/admin.py:15
|
||||
msgid "Authentication"
|
||||
msgstr "Authentifizéierung"
|
||||
|
||||
#: paperless_mail/admin.py:18
|
||||
msgid "Advanced settings"
|
||||
msgstr "Erweidert Astellungen"
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
msgid "Filter"
|
||||
msgstr "Filter"
|
||||
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr "Paperless wäert nëmmen E-Maile veraarbechten, fir déi all déi hei definéiert Filteren zoutreffen."
|
||||
|
||||
#: paperless_mail/admin.py:49
|
||||
msgid "Actions"
|
||||
msgstr "Aktiounen"
|
||||
|
||||
#: paperless_mail/admin.py:51
|
||||
msgid "The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched."
|
||||
msgstr "D'Aktioun, déi op E-Mailen applizéiert sill ginn. Dës Aktioun gëtt just ausgefouert, wann Dokumenter aus der E-Mail veraarbecht goufen. E-Mailen ouni Unhank bleiwe komplett onberéiert."
|
||||
|
||||
#: paperless_mail/admin.py:58
|
||||
msgid "Metadata"
|
||||
msgstr "Metadaten"
|
||||
|
||||
#: paperless_mail/admin.py:60
|
||||
msgid "Assign metadata to documents consumed from this rule automatically. If you do not assign tags, types or correspondents here, paperless will still process all matching rules that you have defined."
|
||||
msgstr "Den Dokumenter, déi iwwer dës Reegel veraarbecht ginn, automatesch Metadaten zouweisen. Wann hei keng Etiketten, Typen oder Korrespondenten zougewise ginn, veraarbecht Paperless-ng trotzdeem all zoutreffend Reegelen déi definéiert sinn."
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
msgstr "Paperless E-Mail"
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail account"
|
||||
msgstr "Mailkont"
|
||||
|
||||
#: paperless_mail/models.py:12
|
||||
msgid "mail accounts"
|
||||
msgstr "Mailkonten"
|
||||
|
||||
#: paperless_mail/models.py:19
|
||||
msgid "No encryption"
|
||||
msgstr "Keng Verschlësselung"
|
||||
|
||||
#: paperless_mail/models.py:20
|
||||
msgid "Use SSL"
|
||||
msgstr "SSL benotzen"
|
||||
|
||||
#: paperless_mail/models.py:21
|
||||
msgid "Use STARTTLS"
|
||||
msgstr "STARTTLS benotzen"
|
||||
|
||||
#: paperless_mail/models.py:29
|
||||
msgid "IMAP server"
|
||||
msgstr "IMAP-Server"
|
||||
|
||||
#: paperless_mail/models.py:33
|
||||
msgid "IMAP port"
|
||||
msgstr "IMAP-Port"
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr "Dëst ass normalerweis 143 fir onverschësselt oder STARTTLS-Connectiounen an 993 fir SSL-Connectiounen."
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
msgid "IMAP security"
|
||||
msgstr "IMAP-Sécherheet"
|
||||
|
||||
#: paperless_mail/models.py:46
|
||||
msgid "username"
|
||||
msgstr "Benotzernumm"
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "password"
|
||||
msgstr "Passwuert"
|
||||
|
||||
#: paperless_mail/models.py:54
|
||||
msgid "character set"
|
||||
msgstr "Zeechesaz"
|
||||
|
||||
#: paperless_mail/models.py:57
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
msgstr "Den Zeechesaz dee benotzt gëtt wa mam Mailserver kommunizéiert gëtt, wéi beispillsweis 'UTF-8' oder 'US-ASCII'."
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "mail rule"
|
||||
msgstr "E-Mail-Reegel"
|
||||
|
||||
#: paperless_mail/models.py:69
|
||||
msgid "mail rules"
|
||||
msgstr "E-Mail-Reegelen"
|
||||
|
||||
#: paperless_mail/models.py:75
|
||||
msgid "Only process attachments."
|
||||
msgstr "Just Unhäng veraarbechten."
|
||||
|
||||
#: paperless_mail/models.py:76
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr "All d'Fichiere veraarbechten, inklusiv \"inline\"-Unhäng."
|
||||
|
||||
#: paperless_mail/models.py:86
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr "Als gelies markéieren, gelies Mailen net traitéieren"
|
||||
|
||||
#: paperless_mail/models.py:87
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr "Als wichteg markéieren, markéiert E-Mailen net veraarbechten"
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "Move to specified folder"
|
||||
msgstr "An e virdefinéierten Dossier réckelen"
|
||||
|
||||
#: paperless_mail/models.py:89
|
||||
msgid "Delete"
|
||||
msgstr "Läschen"
|
||||
|
||||
#: paperless_mail/models.py:96
|
||||
msgid "Use subject as title"
|
||||
msgstr "Sujet als TItel notzen"
|
||||
|
||||
#: paperless_mail/models.py:97
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr "Numm vum Dateiunhank als Titel benotzen"
|
||||
|
||||
#: paperless_mail/models.py:107
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr "Kee Korrespondent zouweisen"
|
||||
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "Use mail address"
|
||||
msgstr "E-Mail-Adress benotzen"
|
||||
|
||||
#: paperless_mail/models.py:111
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr "Numm benotzen (oder E-Mail-Adress falls den Numm net disponibel ass)"
|
||||
|
||||
#: paperless_mail/models.py:113
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr "Korrespondent benotzen deen hei drënner ausgewielt ass"
|
||||
|
||||
#: paperless_mail/models.py:121
|
||||
msgid "order"
|
||||
msgstr "Reiefolleg"
|
||||
|
||||
#: paperless_mail/models.py:128
|
||||
msgid "account"
|
||||
msgstr "Kont"
|
||||
|
||||
#: paperless_mail/models.py:132
|
||||
msgid "folder"
|
||||
msgstr "Dossier"
|
||||
|
||||
#: paperless_mail/models.py:134
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr "Ënnerdossiere mussen duerch Punkte getrennt ginn."
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
msgid "filter from"
|
||||
msgstr "Ofsenderfilter"
|
||||
|
||||
#: paperless_mail/models.py:141
|
||||
msgid "filter subject"
|
||||
msgstr "Sujets-Filter"
|
||||
|
||||
#: paperless_mail/models.py:144
|
||||
msgid "filter body"
|
||||
msgstr "Contenu-Filter"
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "filter attachment filename"
|
||||
msgstr "Filter fir den Numm vum Unhank"
|
||||
|
||||
#: paperless_mail/models.py:150
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr "Just Dokumenter traitéieren, déi exakt dësen Dateinumm hunn (falls definéiert). Platzhalter wéi *.pdf oder *invoice* sinn erlaabt. D'Grouss-/Klengschreiwung gëtt ignoréiert."
|
||||
|
||||
#: paperless_mail/models.py:156
|
||||
msgid "maximum age"
|
||||
msgstr "Maximalen Alter"
|
||||
|
||||
#: paperless_mail/models.py:158
|
||||
msgid "Specified in days."
|
||||
msgstr "An Deeg."
|
||||
|
||||
#: paperless_mail/models.py:161
|
||||
msgid "attachment type"
|
||||
msgstr "Fichierstyp vum Unhank"
|
||||
|
||||
#: paperless_mail/models.py:164
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr "\"Inline\"-Unhänk schléissen och agebonne Biller mat an, dofir sollt dës Astellung mat engem Filter fir den Numm vum Unhank kombinéiert ginn."
|
||||
|
||||
#: paperless_mail/models.py:169
|
||||
msgid "action"
|
||||
msgstr "Aktioun"
|
||||
|
||||
#: paperless_mail/models.py:175
|
||||
msgid "action parameter"
|
||||
msgstr "Parameter fir Aktioun"
|
||||
|
||||
#: paperless_mail/models.py:177
|
||||
msgid "Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots."
|
||||
msgstr "Zousätzleche Parameter fir d'Aktioun déi hei driwwer ausgewielt ass, zum Beispill den Numm vum Zildossier fir d'Aktioun \"An e virdefinéierten Dossier réckelen\". Ënnerdossiere musse mat Punkte getrennt ginn."
|
||||
|
||||
#: paperless_mail/models.py:184
|
||||
msgid "assign title from"
|
||||
msgstr "Titel zouweisen aus"
|
||||
|
||||
#: paperless_mail/models.py:194
|
||||
msgid "assign this tag"
|
||||
msgstr "dës Etikett zouweisen"
|
||||
|
||||
#: paperless_mail/models.py:202
|
||||
msgid "assign this document type"
|
||||
msgstr "Dësen Dokumententyp zouweisen"
|
||||
|
||||
#: paperless_mail/models.py:206
|
||||
msgid "assign correspondent from"
|
||||
msgstr "Korrespondent zouweisen aus"
|
||||
|
||||
#: paperless_mail/models.py:216
|
||||
msgid "assign this correspondent"
|
||||
msgstr "Dëse Korrespondent zouweisen"
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Ben <bzweekhorst@gmail.com>, 2021
|
||||
# Jo Vandeginste <jo.vandeginste@gmail.com>, 2021
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Project-Id-Version: paperless-ng\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-01-10 21:41+0000\n"
|
||||
"PO-Revision-Date: 2020-12-30 19:27+0000\n"
|
||||
"Last-Translator: Jo Vandeginste <jo.vandeginste@gmail.com>, 2021\n"
|
||||
"Language-Team: Dutch (Netherlands) (https://www.transifex.com/paperless/teams/115905/nl_NL/)\n"
|
||||
"POT-Creation-Date: 2021-05-16 09:38+0000\n"
|
||||
"PO-Revision-Date: 2021-05-22 10:12\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Dutch\n"
|
||||
"Language: nl_NL\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: nl_NL\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: paperless-ng\n"
|
||||
"X-Crowdin-Project-ID: 434940\n"
|
||||
"X-Crowdin-Language: nl\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 54\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
@@ -28,7 +23,7 @@ msgstr "Documenten"
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr "Eender welk woord"
|
||||
msgstr "Elk woord"
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
@@ -50,8 +45,8 @@ msgstr "Gelijkaardig woord"
|
||||
msgid "Automatic"
|
||||
msgstr "Automatisch"
|
||||
|
||||
#: documents/models.py:41 documents/models.py:354 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:109
|
||||
#: documents/models.py:41 documents/models.py:350 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:117
|
||||
msgid "name"
|
||||
msgstr "naam"
|
||||
|
||||
@@ -67,383 +62,443 @@ msgstr "Algoritme voor het bepalen van de overeenkomst"
|
||||
msgid "is insensitive"
|
||||
msgstr "is niet hoofdlettergevoelig"
|
||||
|
||||
#: documents/models.py:80 documents/models.py:140
|
||||
#: documents/models.py:74 documents/models.py:120
|
||||
msgid "correspondent"
|
||||
msgstr "correspondent"
|
||||
|
||||
#: documents/models.py:81
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr "correspondenten"
|
||||
|
||||
#: documents/models.py:103
|
||||
#: documents/models.py:81
|
||||
msgid "color"
|
||||
msgstr "Kleur"
|
||||
|
||||
#: documents/models.py:107
|
||||
#: documents/models.py:87
|
||||
msgid "is inbox tag"
|
||||
msgstr "is \"Postvak in\"-etiket"
|
||||
msgstr "is \"Postvak in\"-label"
|
||||
|
||||
#: documents/models.py:109
|
||||
msgid ""
|
||||
"Marks this tag as an inbox tag: All newly consumed documents will be tagged "
|
||||
"with inbox tags."
|
||||
msgstr ""
|
||||
"Markeer dit etiket als een \"Postvak in\"-etiket: alle nieuw verwerkte "
|
||||
"documenten krijgen de \"Postvak in\"-etiketten."
|
||||
#: documents/models.py:89
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr "Markeert dit label als een \"Postvak in\"-label: alle nieuw verwerkte documenten krijgen de \"Postvak in\"-labels."
|
||||
|
||||
#: documents/models.py:114
|
||||
#: documents/models.py:94
|
||||
msgid "tag"
|
||||
msgstr "etiket"
|
||||
msgstr "label"
|
||||
|
||||
#: documents/models.py:115 documents/models.py:171
|
||||
#: documents/models.py:95 documents/models.py:151
|
||||
msgid "tags"
|
||||
msgstr "etiketten"
|
||||
msgstr "labels"
|
||||
|
||||
#: documents/models.py:121 documents/models.py:153
|
||||
#: documents/models.py:101 documents/models.py:133
|
||||
msgid "document type"
|
||||
msgstr "documenttype"
|
||||
|
||||
#: documents/models.py:122
|
||||
#: documents/models.py:102
|
||||
msgid "document types"
|
||||
msgstr "documenttypen"
|
||||
|
||||
#: documents/models.py:130
|
||||
#: documents/models.py:110
|
||||
msgid "Unencrypted"
|
||||
msgstr "Niet versleuteld"
|
||||
|
||||
#: documents/models.py:131
|
||||
#: documents/models.py:111
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "Versleuteld met GNU Privacy Guard"
|
||||
|
||||
#: documents/models.py:144
|
||||
#: documents/models.py:124
|
||||
msgid "title"
|
||||
msgstr "titel"
|
||||
|
||||
#: documents/models.py:157
|
||||
#: documents/models.py:137
|
||||
msgid "content"
|
||||
msgstr "inhoud"
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid ""
|
||||
"The raw, text-only data of the document. This field is primarily used for "
|
||||
"searching."
|
||||
msgstr ""
|
||||
"De onbewerkte gegevens van het document. Dit veld wordt voornamelijk "
|
||||
"gebruikt om te zoeken."
|
||||
#: documents/models.py:139
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr "De onbewerkte gegevens van het document. Dit veld wordt voornamelijk gebruikt om te zoeken."
|
||||
|
||||
#: documents/models.py:164
|
||||
#: documents/models.py:144
|
||||
msgid "mime type"
|
||||
msgstr "mimetype"
|
||||
|
||||
#: documents/models.py:175
|
||||
#: documents/models.py:155
|
||||
msgid "checksum"
|
||||
msgstr "checksum"
|
||||
|
||||
#: documents/models.py:179
|
||||
#: documents/models.py:159
|
||||
msgid "The checksum of the original document."
|
||||
msgstr "Het controlecijfer van het originele document."
|
||||
msgstr "De checksum van het oorspronkelijke document."
|
||||
|
||||
#: documents/models.py:183
|
||||
#: documents/models.py:163
|
||||
msgid "archive checksum"
|
||||
msgstr "archief checksum"
|
||||
|
||||
#: documents/models.py:188
|
||||
#: documents/models.py:168
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr "De checksum van het gearchiveerde document."
|
||||
|
||||
#: documents/models.py:192 documents/models.py:332
|
||||
#: documents/models.py:172 documents/models.py:328
|
||||
msgid "created"
|
||||
msgstr "aangemaakt"
|
||||
|
||||
#: documents/models.py:196
|
||||
#: documents/models.py:176
|
||||
msgid "modified"
|
||||
msgstr "gewijzigd"
|
||||
|
||||
#: documents/models.py:200
|
||||
#: documents/models.py:180
|
||||
msgid "storage type"
|
||||
msgstr "type opslag"
|
||||
|
||||
#: documents/models.py:208
|
||||
#: documents/models.py:188
|
||||
msgid "added"
|
||||
msgstr "toegevoegd"
|
||||
|
||||
#: documents/models.py:212
|
||||
#: documents/models.py:192
|
||||
msgid "filename"
|
||||
msgstr "bestandsnaam"
|
||||
|
||||
#: documents/models.py:217
|
||||
#: documents/models.py:198
|
||||
msgid "Current filename in storage"
|
||||
msgstr "Huidige bestandsnaam in opslag"
|
||||
|
||||
#: documents/models.py:221
|
||||
#: documents/models.py:202
|
||||
msgid "archive filename"
|
||||
msgstr "Bestandsnaam in archief"
|
||||
|
||||
#: documents/models.py:208
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr "Huidige bestandsnaam in archief"
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "archive serial number"
|
||||
msgstr "serienummer in archief"
|
||||
|
||||
#: documents/models.py:226
|
||||
#: documents/models.py:217
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr "De positie van dit document in je fysieke documentenarchief."
|
||||
|
||||
#: documents/models.py:232
|
||||
#: documents/models.py:223
|
||||
msgid "document"
|
||||
msgstr "document"
|
||||
|
||||
#: documents/models.py:233
|
||||
#: documents/models.py:224
|
||||
msgid "documents"
|
||||
msgstr "documenten"
|
||||
|
||||
#: documents/models.py:315
|
||||
#: documents/models.py:311
|
||||
msgid "debug"
|
||||
msgstr "debug"
|
||||
|
||||
#: documents/models.py:316
|
||||
#: documents/models.py:312
|
||||
msgid "information"
|
||||
msgstr "informatie"
|
||||
|
||||
#: documents/models.py:317
|
||||
#: documents/models.py:313
|
||||
msgid "warning"
|
||||
msgstr "waarschuwing"
|
||||
|
||||
#: documents/models.py:318
|
||||
#: documents/models.py:314
|
||||
msgid "error"
|
||||
msgstr "fout"
|
||||
|
||||
#: documents/models.py:319
|
||||
#: documents/models.py:315
|
||||
msgid "critical"
|
||||
msgstr "kritisch"
|
||||
|
||||
#: documents/models.py:323
|
||||
#: documents/models.py:319
|
||||
msgid "group"
|
||||
msgstr "groep"
|
||||
|
||||
#: documents/models.py:326
|
||||
#: documents/models.py:322
|
||||
msgid "message"
|
||||
msgstr "bericht"
|
||||
|
||||
#: documents/models.py:329
|
||||
#: documents/models.py:325
|
||||
msgid "level"
|
||||
msgstr "niveau"
|
||||
|
||||
#: documents/models.py:336
|
||||
#: documents/models.py:332
|
||||
msgid "log"
|
||||
msgstr "bericht"
|
||||
|
||||
#: documents/models.py:337
|
||||
#: documents/models.py:333
|
||||
msgid "logs"
|
||||
msgstr "berichten"
|
||||
|
||||
#: documents/models.py:348 documents/models.py:398
|
||||
#: documents/models.py:344 documents/models.py:401
|
||||
msgid "saved view"
|
||||
msgstr "opgeslagen view"
|
||||
|
||||
#: documents/models.py:349
|
||||
#: documents/models.py:345
|
||||
msgid "saved views"
|
||||
msgstr "opgeslagen views"
|
||||
|
||||
#: documents/models.py:352
|
||||
#: documents/models.py:348
|
||||
msgid "user"
|
||||
msgstr "gebruiker"
|
||||
|
||||
#: documents/models.py:358
|
||||
#: documents/models.py:354
|
||||
msgid "show on dashboard"
|
||||
msgstr "weergeven op dashboard"
|
||||
|
||||
#: documents/models.py:361
|
||||
#: documents/models.py:357
|
||||
msgid "show in sidebar"
|
||||
msgstr "weergeven in zijbalk"
|
||||
|
||||
#: documents/models.py:365
|
||||
#: documents/models.py:361
|
||||
msgid "sort field"
|
||||
msgstr "sorteerveld"
|
||||
|
||||
#: documents/models.py:368
|
||||
#: documents/models.py:367
|
||||
msgid "sort reverse"
|
||||
msgstr "omgekeerd sorteren"
|
||||
|
||||
#: documents/models.py:374
|
||||
#: documents/models.py:373
|
||||
msgid "title contains"
|
||||
msgstr "titel bevat"
|
||||
|
||||
#: documents/models.py:375
|
||||
#: documents/models.py:374
|
||||
msgid "content contains"
|
||||
msgstr "inhoud bevat"
|
||||
|
||||
#: documents/models.py:376
|
||||
#: documents/models.py:375
|
||||
msgid "ASN is"
|
||||
msgstr "ASN is"
|
||||
|
||||
#: documents/models.py:377
|
||||
#: documents/models.py:376
|
||||
msgid "correspondent is"
|
||||
msgstr "correspondent is"
|
||||
|
||||
#: documents/models.py:378
|
||||
#: documents/models.py:377
|
||||
msgid "document type is"
|
||||
msgstr "documenttype is"
|
||||
|
||||
#: documents/models.py:379
|
||||
#: documents/models.py:378
|
||||
msgid "is in inbox"
|
||||
msgstr "zit in \"Postvak in\""
|
||||
|
||||
#: documents/models.py:380
|
||||
#: documents/models.py:379
|
||||
msgid "has tag"
|
||||
msgstr "heeft etiket"
|
||||
msgstr "heeft label"
|
||||
|
||||
#: documents/models.py:380
|
||||
msgid "has any tag"
|
||||
msgstr "heeft één van de labels"
|
||||
|
||||
#: documents/models.py:381
|
||||
msgid "has any tag"
|
||||
msgstr "heeft één van de etiketten"
|
||||
|
||||
#: documents/models.py:382
|
||||
msgid "created before"
|
||||
msgstr "aangemaakt voor"
|
||||
|
||||
#: documents/models.py:383
|
||||
#: documents/models.py:382
|
||||
msgid "created after"
|
||||
msgstr "aangemaakt na"
|
||||
|
||||
#: documents/models.py:384
|
||||
#: documents/models.py:383
|
||||
msgid "created year is"
|
||||
msgstr "aangemaakt jaar is"
|
||||
|
||||
#: documents/models.py:385
|
||||
#: documents/models.py:384
|
||||
msgid "created month is"
|
||||
msgstr "aangemaakte maand is"
|
||||
|
||||
#: documents/models.py:386
|
||||
#: documents/models.py:385
|
||||
msgid "created day is"
|
||||
msgstr "aangemaakte dag is"
|
||||
|
||||
#: documents/models.py:387
|
||||
#: documents/models.py:386
|
||||
msgid "added before"
|
||||
msgstr "toegevoegd voor"
|
||||
|
||||
#: documents/models.py:388
|
||||
#: documents/models.py:387
|
||||
msgid "added after"
|
||||
msgstr "toegevoegd na"
|
||||
|
||||
#: documents/models.py:389
|
||||
#: documents/models.py:388
|
||||
msgid "modified before"
|
||||
msgstr "gewijzigd voor"
|
||||
|
||||
#: documents/models.py:390
|
||||
#: documents/models.py:389
|
||||
msgid "modified after"
|
||||
msgstr "gewijzigd na"
|
||||
|
||||
#: documents/models.py:391
|
||||
#: documents/models.py:390
|
||||
msgid "does not have tag"
|
||||
msgstr "heeft geen etiket"
|
||||
msgstr "heeft geen label"
|
||||
|
||||
#: documents/models.py:402
|
||||
#: documents/models.py:391
|
||||
msgid "does not have ASN"
|
||||
msgstr "heeft geen ASN"
|
||||
|
||||
#: documents/models.py:392
|
||||
msgid "title or content contains"
|
||||
msgstr "titel of inhoud bevat"
|
||||
|
||||
#: documents/models.py:393
|
||||
msgid "fulltext query"
|
||||
msgstr "inhoud doorzoeken"
|
||||
|
||||
#: documents/models.py:394
|
||||
msgid "more like this"
|
||||
msgstr "meer zoals dit"
|
||||
|
||||
#: documents/models.py:405
|
||||
msgid "rule type"
|
||||
msgstr "type regel"
|
||||
|
||||
#: documents/models.py:406
|
||||
#: documents/models.py:409
|
||||
msgid "value"
|
||||
msgstr "waarde"
|
||||
|
||||
#: documents/models.py:412
|
||||
#: documents/models.py:415
|
||||
msgid "filter rule"
|
||||
msgstr "filterregel"
|
||||
|
||||
#: documents/models.py:413
|
||||
#: documents/models.py:416
|
||||
msgid "filter rules"
|
||||
msgstr "filterregels"
|
||||
|
||||
#: documents/templates/index.html:20
|
||||
#: documents/serialisers.py:53
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr "Ongeldige reguliere expressie: %(error)s"
|
||||
|
||||
#: documents/serialisers.py:177
|
||||
msgid "Invalid color."
|
||||
msgstr "Ongeldig kleur."
|
||||
|
||||
#: documents/serialisers.py:451
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Bestandstype %(type)s niet ondersteund"
|
||||
|
||||
#: documents/templates/index.html:22
|
||||
msgid "Paperless-ng is loading..."
|
||||
msgstr "Paperless-ng is aan het laden..."
|
||||
|
||||
#: documents/templates/registration/logged_out.html:13
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ng signed out"
|
||||
msgstr "Paperless-ng - afmelden"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:41
|
||||
#: documents/templates/registration/logged_out.html:45
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr "Je bent nu afgemeld. Tot later!"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:42
|
||||
#: documents/templates/registration/logged_out.html:46
|
||||
msgid "Sign in again"
|
||||
msgstr "Meld je opnieuw aan"
|
||||
|
||||
#: documents/templates/registration/login.html:13
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ng sign in"
|
||||
msgstr "Paperless-ng - aanmelden"
|
||||
|
||||
#: documents/templates/registration/login.html:42
|
||||
#: documents/templates/registration/login.html:47
|
||||
msgid "Please sign in."
|
||||
msgstr "Gelieve aan te melden."
|
||||
|
||||
#: documents/templates/registration/login.html:45
|
||||
#: documents/templates/registration/login.html:50
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr "Je gebruikersnaam en wachtwoord komen niet overeen. Probeer opnieuw."
|
||||
|
||||
#: documents/templates/registration/login.html:48
|
||||
#: documents/templates/registration/login.html:53
|
||||
msgid "Username"
|
||||
msgstr "Gebruikersnaam"
|
||||
|
||||
#: documents/templates/registration/login.html:49
|
||||
#: documents/templates/registration/login.html:54
|
||||
msgid "Password"
|
||||
msgstr "Wachtwoord"
|
||||
|
||||
#: documents/templates/registration/login.html:54
|
||||
#: documents/templates/registration/login.html:59
|
||||
msgid "Sign in"
|
||||
msgstr "Aanmelden"
|
||||
|
||||
#: paperless/settings.py:268
|
||||
msgid "English"
|
||||
msgstr "Engels"
|
||||
#: paperless/settings.py:303
|
||||
msgid "English (US)"
|
||||
msgstr "Engels (US)"
|
||||
|
||||
#: paperless/settings.py:269
|
||||
#: paperless/settings.py:304
|
||||
msgid "English (GB)"
|
||||
msgstr "Engels (Brits)"
|
||||
|
||||
#: paperless/settings.py:305
|
||||
msgid "German"
|
||||
msgstr "Duits"
|
||||
|
||||
#: paperless/settings.py:270
|
||||
#: paperless/settings.py:306
|
||||
msgid "Dutch"
|
||||
msgstr "Nederlands"
|
||||
|
||||
#: paperless/settings.py:271
|
||||
#: paperless/settings.py:307
|
||||
msgid "French"
|
||||
msgstr "Frans"
|
||||
|
||||
#: paperless/urls.py:108
|
||||
#: paperless/settings.py:308
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Portugees (Brazilië)"
|
||||
|
||||
#: paperless/settings.py:309
|
||||
msgid "Portuguese"
|
||||
msgstr "Portugees"
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Italian"
|
||||
msgstr "Italiaans"
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Romanian"
|
||||
msgstr "Roemeens"
|
||||
|
||||
#: paperless/settings.py:312
|
||||
msgid "Russian"
|
||||
msgstr "Russisch"
|
||||
|
||||
#: paperless/settings.py:313
|
||||
msgid "Spanish"
|
||||
msgstr "Spaans"
|
||||
|
||||
#: paperless/settings.py:314
|
||||
msgid "Polish"
|
||||
msgstr "Pools"
|
||||
|
||||
#: paperless/settings.py:315
|
||||
msgid "Swedish"
|
||||
msgstr "Zweeds"
|
||||
|
||||
#: paperless/urls.py:120
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr "Paperless-ng administratie"
|
||||
|
||||
#: paperless_mail/admin.py:25
|
||||
#: paperless_mail/admin.py:15
|
||||
msgid "Authentication"
|
||||
msgstr "Authenticatie"
|
||||
|
||||
#: paperless_mail/admin.py:18
|
||||
msgid "Advanced settings"
|
||||
msgstr "Geavanceerde instellingen"
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
msgid "Filter"
|
||||
msgstr "Filter"
|
||||
|
||||
#: paperless_mail/admin.py:27
|
||||
msgid ""
|
||||
"Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr ""
|
||||
"Paperless verwerkt alleen e-mails die voldoen aan ALLE onderstaande filters."
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr "Paperless verwerkt alleen e-mails die voldoen aan ALLE onderstaande filters."
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
#: paperless_mail/admin.py:49
|
||||
msgid "Actions"
|
||||
msgstr "Acties"
|
||||
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid ""
|
||||
"The action applied to the mail. This action is only performed when documents"
|
||||
" were consumed from the mail. Mails without attachments will remain entirely"
|
||||
" untouched."
|
||||
msgstr ""
|
||||
"De actie die wordt toegepast op de mail. Deze actie wordt alleen uitgevoerd "
|
||||
"wanneer documenten verwerkt werden uit de mail. Mails zonder bijlage blijven"
|
||||
" onaangeroerd."
|
||||
#: paperless_mail/admin.py:51
|
||||
msgid "The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched."
|
||||
msgstr "De actie die wordt toegepast op de mail. Deze actie wordt alleen uitgevoerd wanneer documenten verwerkt werden uit de mail. Mails zonder bijlage blijven onaangeroerd."
|
||||
|
||||
#: paperless_mail/admin.py:46
|
||||
#: paperless_mail/admin.py:58
|
||||
msgid "Metadata"
|
||||
msgstr "Metadata"
|
||||
|
||||
#: paperless_mail/admin.py:48
|
||||
msgid ""
|
||||
"Assign metadata to documents consumed from this rule automatically. If you "
|
||||
"do not assign tags, types or correspondents here, paperless will still "
|
||||
"process all matching rules that you have defined."
|
||||
msgstr ""
|
||||
"Automatisch metadata toewijzen aan documenten vanuit deze regel. Indien je "
|
||||
"geen etiketten, documenttypes of correspondenten toewijst, zal Paperless nog"
|
||||
" steeds alle regels verwerken die je hebt gedefinieerd."
|
||||
#: paperless_mail/admin.py:60
|
||||
msgid "Assign metadata to documents consumed from this rule automatically. If you do not assign tags, types or correspondents here, paperless will still process all matching rules that you have defined."
|
||||
msgstr "Automatisch metadata toewijzen aan documenten vanuit deze regel. Indien je geen labels, documenttypes of correspondenten toewijst, zal Paperless nog steeds alle regels verwerken die je hebt gedefinieerd."
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
@@ -478,12 +533,8 @@ msgid "IMAP port"
|
||||
msgstr "IMAP-poort"
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid ""
|
||||
"This is usually 143 for unencrypted and STARTTLS connections, and 993 for "
|
||||
"SSL connections."
|
||||
msgstr ""
|
||||
"Dit is gewoonlijk 143 voor onversleutelde of STARTTLS verbindingen, en 993 "
|
||||
"voor SSL verbindingen."
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr "Dit is gewoonlijk 143 voor onversleutelde of STARTTLS verbindingen, en 993 voor SSL verbindingen."
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
msgid "IMAP security"
|
||||
@@ -497,151 +548,151 @@ msgstr "gebruikersnaam"
|
||||
msgid "password"
|
||||
msgstr "wachtwoord"
|
||||
|
||||
#: paperless_mail/models.py:60
|
||||
#: paperless_mail/models.py:54
|
||||
msgid "character set"
|
||||
msgstr "Tekenset"
|
||||
|
||||
#: paperless_mail/models.py:57
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
msgstr "Tekenset die gebruikt moet worden bij communicatie met de mailserver, zoals 'UTF-8' of 'US-ASCII'."
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "mail rule"
|
||||
msgstr "email-regel"
|
||||
|
||||
#: paperless_mail/models.py:61
|
||||
#: paperless_mail/models.py:69
|
||||
msgid "mail rules"
|
||||
msgstr "email-regels"
|
||||
|
||||
#: paperless_mail/models.py:67
|
||||
#: paperless_mail/models.py:75
|
||||
msgid "Only process attachments."
|
||||
msgstr "Alleen bijlagen verwerken"
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
#: paperless_mail/models.py:76
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr "Verwerk alle bestanden, inclusief 'inline' bijlagen."
|
||||
|
||||
#: paperless_mail/models.py:78
|
||||
#: paperless_mail/models.py:86
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr "Markeer als gelezen, verwerk geen gelezen mails"
|
||||
|
||||
#: paperless_mail/models.py:79
|
||||
#: paperless_mail/models.py:87
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr "Markeer de mail, verwerk geen mails met markering"
|
||||
|
||||
#: paperless_mail/models.py:80
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "Move to specified folder"
|
||||
msgstr "Verplaats naar gegeven map"
|
||||
|
||||
#: paperless_mail/models.py:81
|
||||
#: paperless_mail/models.py:89
|
||||
msgid "Delete"
|
||||
msgstr "Verwijder"
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
#: paperless_mail/models.py:96
|
||||
msgid "Use subject as title"
|
||||
msgstr "Gebruik onderwerp als titel"
|
||||
|
||||
#: paperless_mail/models.py:89
|
||||
#: paperless_mail/models.py:97
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr "Gebruik naam van bijlage als titel"
|
||||
|
||||
#: paperless_mail/models.py:99
|
||||
#: paperless_mail/models.py:107
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr "Wijs geen correspondent toe"
|
||||
|
||||
#: paperless_mail/models.py:101
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "Use mail address"
|
||||
msgstr "Gebruik het email-adres"
|
||||
|
||||
#: paperless_mail/models.py:103
|
||||
#: paperless_mail/models.py:111
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr "Gebruik de naam, en anders het email-adres"
|
||||
|
||||
#: paperless_mail/models.py:105
|
||||
#: paperless_mail/models.py:113
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr "Gebruik de hieronder aangeduide correspondent"
|
||||
|
||||
#: paperless_mail/models.py:113
|
||||
#: paperless_mail/models.py:121
|
||||
msgid "order"
|
||||
msgstr "volgorde"
|
||||
|
||||
#: paperless_mail/models.py:120
|
||||
#: paperless_mail/models.py:128
|
||||
msgid "account"
|
||||
msgstr "account"
|
||||
|
||||
#: paperless_mail/models.py:124
|
||||
#: paperless_mail/models.py:132
|
||||
msgid "folder"
|
||||
msgstr "map"
|
||||
|
||||
#: paperless_mail/models.py:128
|
||||
#: paperless_mail/models.py:134
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr "Submappen moeten gescheiden worden door punten."
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
msgid "filter from"
|
||||
msgstr "filter afzender"
|
||||
|
||||
#: paperless_mail/models.py:131
|
||||
#: paperless_mail/models.py:141
|
||||
msgid "filter subject"
|
||||
msgstr "filter onderwerp"
|
||||
|
||||
#: paperless_mail/models.py:134
|
||||
#: paperless_mail/models.py:144
|
||||
msgid "filter body"
|
||||
msgstr "filter inhoud"
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "filter attachment filename"
|
||||
msgstr "Filter bestandsnaam van bijlage"
|
||||
|
||||
#: paperless_mail/models.py:140
|
||||
msgid ""
|
||||
"Only consume documents which entirely match this filename if specified. "
|
||||
"Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr ""
|
||||
"Alleen documenten verwerken die volledig overeenkomen, indien aangegeven. Je"
|
||||
" kunt jokertekens gebruiken, zoals *.pdf of *factuur*. Dit is niet "
|
||||
"hoofdlettergevoelig."
|
||||
#: paperless_mail/models.py:150
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr "Alleen documenten verwerken die volledig overeenkomen, indien aangegeven. Je kunt jokertekens gebruiken, zoals *.pdf of *factuur*. Dit is niet hoofdlettergevoelig."
|
||||
|
||||
#: paperless_mail/models.py:146
|
||||
#: paperless_mail/models.py:156
|
||||
msgid "maximum age"
|
||||
msgstr "Maximale leeftijd"
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
#: paperless_mail/models.py:158
|
||||
msgid "Specified in days."
|
||||
msgstr "Aangegeven in dagen"
|
||||
|
||||
#: paperless_mail/models.py:151
|
||||
#: paperless_mail/models.py:161
|
||||
msgid "attachment type"
|
||||
msgstr "Type bijlage"
|
||||
|
||||
#: paperless_mail/models.py:154
|
||||
msgid ""
|
||||
"Inline attachments include embedded images, so it's best to combine this "
|
||||
"option with a filename filter."
|
||||
msgstr ""
|
||||
"\"Inline\" bijlagen bevatten vaak ook afbeeldingen. In dit geval valt het "
|
||||
"aan te raden om ook een filter voor de bestandsnaam op te geven."
|
||||
#: paperless_mail/models.py:164
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr "\"Inline\" bijlagen bevatten vaak ook afbeeldingen. In dit geval valt het aan te raden om ook een filter voor de bestandsnaam op te geven."
|
||||
|
||||
#: paperless_mail/models.py:159
|
||||
#: paperless_mail/models.py:169
|
||||
msgid "action"
|
||||
msgstr "actie"
|
||||
|
||||
#: paperless_mail/models.py:165
|
||||
#: paperless_mail/models.py:175
|
||||
msgid "action parameter"
|
||||
msgstr "actie parameters"
|
||||
|
||||
#: paperless_mail/models.py:167
|
||||
msgid ""
|
||||
"Additional parameter for the action selected above, i.e., the target folder "
|
||||
"of the move to folder action."
|
||||
msgstr ""
|
||||
"Extra parameters voor de hierboven gekozen actie, met andere woorden: de "
|
||||
"bestemmingsmap voor de verplaats-actie."
|
||||
#: paperless_mail/models.py:177
|
||||
msgid "Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots."
|
||||
msgstr "Extra parameter voor de hierboven geselecteerde actie, bijvoorbeeld: de doelmap voor de \"verplaats naar map\"-actie. Submappen moeten gescheiden worden door punten."
|
||||
|
||||
#: paperless_mail/models.py:173
|
||||
#: paperless_mail/models.py:184
|
||||
msgid "assign title from"
|
||||
msgstr "wijs titel toe van"
|
||||
|
||||
#: paperless_mail/models.py:183
|
||||
#: paperless_mail/models.py:194
|
||||
msgid "assign this tag"
|
||||
msgstr "wijs dit etiket toe"
|
||||
|
||||
#: paperless_mail/models.py:191
|
||||
#: paperless_mail/models.py:202
|
||||
msgid "assign this document type"
|
||||
msgstr "wijs dit documenttype toe"
|
||||
|
||||
#: paperless_mail/models.py:195
|
||||
#: paperless_mail/models.py:206
|
||||
msgid "assign correspondent from"
|
||||
msgstr "wijs correspondent toe van"
|
||||
|
||||
#: paperless_mail/models.py:205
|
||||
#: paperless_mail/models.py:216
|
||||
msgid "assign this correspondent"
|
||||
msgstr "wijs deze correspondent toe"
|
||||
|
||||
|
||||
698
src/locale/pl_PL/LC_MESSAGES/django.po
Normal file
698
src/locale/pl_PL/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,698 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ng\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-05-16 09:38+0000\n"
|
||||
"PO-Revision-Date: 2021-08-07 13:02\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Polish\n"
|
||||
"Language: pl_PL\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
|
||||
"X-Crowdin-Project: paperless-ng\n"
|
||||
"X-Crowdin-Project-ID: 434940\n"
|
||||
"X-Crowdin-Language: pl\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 54\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
msgstr "Dokumenty"
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr "Dowolne słowo"
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr "Wszystkie słowa"
|
||||
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr "Dokładne dopasowanie"
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr "Wyrażenie regularne"
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr "Dopasowanie rozmyte"
|
||||
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr "Automatyczny"
|
||||
|
||||
#: documents/models.py:41 documents/models.py:350 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:117
|
||||
msgid "name"
|
||||
msgstr "nazwa"
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "match"
|
||||
msgstr "dopasowanie"
|
||||
|
||||
#: documents/models.py:49
|
||||
msgid "matching algorithm"
|
||||
msgstr "algorytm dopasowania"
|
||||
|
||||
#: documents/models.py:55
|
||||
msgid "is insensitive"
|
||||
msgstr "bez rozróżniania wielkości liter"
|
||||
|
||||
#: documents/models.py:74 documents/models.py:120
|
||||
msgid "correspondent"
|
||||
msgstr "korespondent"
|
||||
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr "korespondenci"
|
||||
|
||||
#: documents/models.py:81
|
||||
msgid "color"
|
||||
msgstr "kolor"
|
||||
|
||||
#: documents/models.py:87
|
||||
msgid "is inbox tag"
|
||||
msgstr "jest tagiem skrzynki odbiorczej"
|
||||
|
||||
#: documents/models.py:89
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr "Zaznacza ten tag jako tag skrzynki odbiorczej: Wszystkie nowo przetworzone dokumenty będą oznaczone tagami skrzynki odbiorczej."
|
||||
|
||||
#: documents/models.py:94
|
||||
msgid "tag"
|
||||
msgstr "tag"
|
||||
|
||||
#: documents/models.py:95 documents/models.py:151
|
||||
msgid "tags"
|
||||
msgstr "tagi"
|
||||
|
||||
#: documents/models.py:101 documents/models.py:133
|
||||
msgid "document type"
|
||||
msgstr "typ dokumentu"
|
||||
|
||||
#: documents/models.py:102
|
||||
msgid "document types"
|
||||
msgstr "typy dokumentów"
|
||||
|
||||
#: documents/models.py:110
|
||||
msgid "Unencrypted"
|
||||
msgstr "Niezaszyfrowane"
|
||||
|
||||
#: documents/models.py:111
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "Zaszyfrowane przy użyciu GNU Privacy Guard"
|
||||
|
||||
#: documents/models.py:124
|
||||
msgid "title"
|
||||
msgstr "tytuł"
|
||||
|
||||
#: documents/models.py:137
|
||||
msgid "content"
|
||||
msgstr "zawartość"
|
||||
|
||||
#: documents/models.py:139
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr "Surowe, tekstowe dane dokumentu. To pole jest używane głównie do wyszukiwania."
|
||||
|
||||
#: documents/models.py:144
|
||||
msgid "mime type"
|
||||
msgstr "typ mime"
|
||||
|
||||
#: documents/models.py:155
|
||||
msgid "checksum"
|
||||
msgstr "suma kontrolna"
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid "The checksum of the original document."
|
||||
msgstr "Suma kontrolna oryginalnego dokumentu."
|
||||
|
||||
#: documents/models.py:163
|
||||
msgid "archive checksum"
|
||||
msgstr "suma kontrolna archiwum"
|
||||
|
||||
#: documents/models.py:168
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr "Suma kontrolna zarchiwizowanego dokumentu."
|
||||
|
||||
#: documents/models.py:172 documents/models.py:328
|
||||
msgid "created"
|
||||
msgstr "utworzono"
|
||||
|
||||
#: documents/models.py:176
|
||||
msgid "modified"
|
||||
msgstr "zmodyfikowano"
|
||||
|
||||
#: documents/models.py:180
|
||||
msgid "storage type"
|
||||
msgstr "typ przechowywania"
|
||||
|
||||
#: documents/models.py:188
|
||||
msgid "added"
|
||||
msgstr "dodano"
|
||||
|
||||
#: documents/models.py:192
|
||||
msgid "filename"
|
||||
msgstr "nazwa pliku"
|
||||
|
||||
#: documents/models.py:198
|
||||
msgid "Current filename in storage"
|
||||
msgstr "Aktualna nazwa pliku w pamięci"
|
||||
|
||||
#: documents/models.py:202
|
||||
msgid "archive filename"
|
||||
msgstr "nazwa pliku archiwum"
|
||||
|
||||
#: documents/models.py:208
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr "Aktualna nazwa pliku archiwum w pamięci"
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "archive serial number"
|
||||
msgstr "numer seryjny archiwum"
|
||||
|
||||
#: documents/models.py:217
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr "Pozycja tego dokumentu w archiwum dokumentów fizycznych."
|
||||
|
||||
#: documents/models.py:223
|
||||
msgid "document"
|
||||
msgstr "dokument"
|
||||
|
||||
#: documents/models.py:224
|
||||
msgid "documents"
|
||||
msgstr "dokumenty"
|
||||
|
||||
#: documents/models.py:311
|
||||
msgid "debug"
|
||||
msgstr "debug"
|
||||
|
||||
#: documents/models.py:312
|
||||
msgid "information"
|
||||
msgstr "informacja"
|
||||
|
||||
#: documents/models.py:313
|
||||
msgid "warning"
|
||||
msgstr "ostrzeżenie"
|
||||
|
||||
#: documents/models.py:314
|
||||
msgid "error"
|
||||
msgstr "błąd"
|
||||
|
||||
#: documents/models.py:315
|
||||
msgid "critical"
|
||||
msgstr "krytyczne"
|
||||
|
||||
#: documents/models.py:319
|
||||
msgid "group"
|
||||
msgstr "grupa"
|
||||
|
||||
#: documents/models.py:322
|
||||
msgid "message"
|
||||
msgstr "wiadomość"
|
||||
|
||||
#: documents/models.py:325
|
||||
msgid "level"
|
||||
msgstr "poziom"
|
||||
|
||||
#: documents/models.py:332
|
||||
msgid "log"
|
||||
msgstr "log"
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "logs"
|
||||
msgstr "logi"
|
||||
|
||||
#: documents/models.py:344 documents/models.py:401
|
||||
msgid "saved view"
|
||||
msgstr "zapisany widok"
|
||||
|
||||
#: documents/models.py:345
|
||||
msgid "saved views"
|
||||
msgstr "zapisane widoki"
|
||||
|
||||
#: documents/models.py:348
|
||||
msgid "user"
|
||||
msgstr "użytkownik"
|
||||
|
||||
#: documents/models.py:354
|
||||
msgid "show on dashboard"
|
||||
msgstr "pokaż na pulpicie"
|
||||
|
||||
#: documents/models.py:357
|
||||
msgid "show in sidebar"
|
||||
msgstr "pokaż na pasku bocznym"
|
||||
|
||||
#: documents/models.py:361
|
||||
msgid "sort field"
|
||||
msgstr "pole sortowania"
|
||||
|
||||
#: documents/models.py:367
|
||||
msgid "sort reverse"
|
||||
msgstr "sortuj malejąco"
|
||||
|
||||
#: documents/models.py:373
|
||||
msgid "title contains"
|
||||
msgstr "tytuł zawiera"
|
||||
|
||||
#: documents/models.py:374
|
||||
msgid "content contains"
|
||||
msgstr "zawartość zawiera"
|
||||
|
||||
#: documents/models.py:375
|
||||
msgid "ASN is"
|
||||
msgstr "numer archiwum jest"
|
||||
|
||||
#: documents/models.py:376
|
||||
msgid "correspondent is"
|
||||
msgstr "korespondentem jest"
|
||||
|
||||
#: documents/models.py:377
|
||||
msgid "document type is"
|
||||
msgstr "typ dokumentu jest"
|
||||
|
||||
#: documents/models.py:378
|
||||
msgid "is in inbox"
|
||||
msgstr "jest w skrzynce odbiorczej"
|
||||
|
||||
#: documents/models.py:379
|
||||
msgid "has tag"
|
||||
msgstr "ma tag"
|
||||
|
||||
#: documents/models.py:380
|
||||
msgid "has any tag"
|
||||
msgstr "ma dowolny tag"
|
||||
|
||||
#: documents/models.py:381
|
||||
msgid "created before"
|
||||
msgstr "utworzony przed"
|
||||
|
||||
#: documents/models.py:382
|
||||
msgid "created after"
|
||||
msgstr "utworzony po"
|
||||
|
||||
#: documents/models.py:383
|
||||
msgid "created year is"
|
||||
msgstr "rok utworzenia to"
|
||||
|
||||
#: documents/models.py:384
|
||||
msgid "created month is"
|
||||
msgstr "miesiąc utworzenia to"
|
||||
|
||||
#: documents/models.py:385
|
||||
msgid "created day is"
|
||||
msgstr "dzień utworzenia to"
|
||||
|
||||
#: documents/models.py:386
|
||||
msgid "added before"
|
||||
msgstr "dodany przed"
|
||||
|
||||
#: documents/models.py:387
|
||||
msgid "added after"
|
||||
msgstr "dodany po"
|
||||
|
||||
#: documents/models.py:388
|
||||
msgid "modified before"
|
||||
msgstr "zmodyfikowany przed"
|
||||
|
||||
#: documents/models.py:389
|
||||
msgid "modified after"
|
||||
msgstr "zmodyfikowany po"
|
||||
|
||||
#: documents/models.py:390
|
||||
msgid "does not have tag"
|
||||
msgstr "nie ma tagu"
|
||||
|
||||
#: documents/models.py:391
|
||||
msgid "does not have ASN"
|
||||
msgstr "nie ma numeru archiwum"
|
||||
|
||||
#: documents/models.py:392
|
||||
msgid "title or content contains"
|
||||
msgstr "tytuł lub zawartość zawiera"
|
||||
|
||||
#: documents/models.py:393
|
||||
msgid "fulltext query"
|
||||
msgstr "zapytanie pełnotekstowe"
|
||||
|
||||
#: documents/models.py:394
|
||||
msgid "more like this"
|
||||
msgstr "podobne dokumenty"
|
||||
|
||||
#: documents/models.py:405
|
||||
msgid "rule type"
|
||||
msgstr "typ reguły"
|
||||
|
||||
#: documents/models.py:409
|
||||
msgid "value"
|
||||
msgstr "wartość"
|
||||
|
||||
#: documents/models.py:415
|
||||
msgid "filter rule"
|
||||
msgstr "reguła filtrowania"
|
||||
|
||||
#: documents/models.py:416
|
||||
msgid "filter rules"
|
||||
msgstr "reguły filtrowania"
|
||||
|
||||
#: documents/serialisers.py:53
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr "Nieprawidłowe wyrażenie regularne: %(error)s"
|
||||
|
||||
#: documents/serialisers.py:177
|
||||
msgid "Invalid color."
|
||||
msgstr "Nieprawidłowy kolor."
|
||||
|
||||
#: documents/serialisers.py:451
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Typ pliku %(type)s nie jest obsługiwany"
|
||||
|
||||
#: documents/templates/index.html:22
|
||||
msgid "Paperless-ng is loading..."
|
||||
msgstr "Ładowanie Paperless-ng..."
|
||||
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ng signed out"
|
||||
msgstr "Wylogowano z Paperless-ng"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:45
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr "Poprawnie wylogowano. Do zobaczenia!"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:46
|
||||
msgid "Sign in again"
|
||||
msgstr "Zaloguj się ponownie"
|
||||
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ng sign in"
|
||||
msgstr "Logowanie do Paperless-ng"
|
||||
|
||||
#: documents/templates/registration/login.html:47
|
||||
msgid "Please sign in."
|
||||
msgstr "Proszę się zalogować."
|
||||
|
||||
#: documents/templates/registration/login.html:50
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr "Twoja nazwa użytkownika i hasło nie są zgodne. Spróbuj ponownie."
|
||||
|
||||
#: documents/templates/registration/login.html:53
|
||||
msgid "Username"
|
||||
msgstr "Użytkownik"
|
||||
|
||||
#: documents/templates/registration/login.html:54
|
||||
msgid "Password"
|
||||
msgstr "Hasło"
|
||||
|
||||
#: documents/templates/registration/login.html:59
|
||||
msgid "Sign in"
|
||||
msgstr "Zaloguj się"
|
||||
|
||||
#: paperless/settings.py:303
|
||||
msgid "English (US)"
|
||||
msgstr "Angielski (USA)"
|
||||
|
||||
#: paperless/settings.py:304
|
||||
msgid "English (GB)"
|
||||
msgstr "Angielski (Wielka Brytania)"
|
||||
|
||||
#: paperless/settings.py:305
|
||||
msgid "German"
|
||||
msgstr "Niemiecki"
|
||||
|
||||
#: paperless/settings.py:306
|
||||
msgid "Dutch"
|
||||
msgstr "Holenderski"
|
||||
|
||||
#: paperless/settings.py:307
|
||||
msgid "French"
|
||||
msgstr "Francuski"
|
||||
|
||||
#: paperless/settings.py:308
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Portugalski (Brazylia)"
|
||||
|
||||
#: paperless/settings.py:309
|
||||
msgid "Portuguese"
|
||||
msgstr "Portugalski"
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Italian"
|
||||
msgstr "Włoski"
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Romanian"
|
||||
msgstr "Rumuński"
|
||||
|
||||
#: paperless/settings.py:312
|
||||
msgid "Russian"
|
||||
msgstr "Rosyjski"
|
||||
|
||||
#: paperless/settings.py:313
|
||||
msgid "Spanish"
|
||||
msgstr "Hiszpański"
|
||||
|
||||
#: paperless/settings.py:314
|
||||
msgid "Polish"
|
||||
msgstr "Polski"
|
||||
|
||||
#: paperless/settings.py:315
|
||||
msgid "Swedish"
|
||||
msgstr "Szwedzki"
|
||||
|
||||
#: paperless/urls.py:120
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr "Administracja Paperless-ng"
|
||||
|
||||
#: paperless_mail/admin.py:15
|
||||
msgid "Authentication"
|
||||
msgstr "Uwierzytelnianie"
|
||||
|
||||
#: paperless_mail/admin.py:18
|
||||
msgid "Advanced settings"
|
||||
msgstr "Ustawienia zaawansowane"
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
msgid "Filter"
|
||||
msgstr "Filtry"
|
||||
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr "Paperless przetworzy tylko wiadomości pasujące do WSZYSTKICH filtrów podanych poniżej."
|
||||
|
||||
#: paperless_mail/admin.py:49
|
||||
msgid "Actions"
|
||||
msgstr "Akcje"
|
||||
|
||||
#: paperless_mail/admin.py:51
|
||||
msgid "The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched."
|
||||
msgstr "Akcja zastosowana do wiadomości. Ta akcja jest wykonywana tylko wtedy, gdy dokumenty zostały przetworzone z wiadomości. Poczta bez załączników pozostanie całkowicie niezmieniona."
|
||||
|
||||
#: paperless_mail/admin.py:58
|
||||
msgid "Metadata"
|
||||
msgstr "Metadane"
|
||||
|
||||
#: paperless_mail/admin.py:60
|
||||
msgid "Assign metadata to documents consumed from this rule automatically. If you do not assign tags, types or correspondents here, paperless will still process all matching rules that you have defined."
|
||||
msgstr "Przypisz metadane do dokumentów zużywanych z tej reguły automatycznie. Jeśli nie przypisujesz tutaj tagów, typów lub korespondentów, Paperless będzie nadal przetwarzał wszystkie zdefiniowane przez Ciebie reguły."
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
msgstr "Poczta Paperless"
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail account"
|
||||
msgstr "konto pocztowe"
|
||||
|
||||
#: paperless_mail/models.py:12
|
||||
msgid "mail accounts"
|
||||
msgstr "konta pocztowe"
|
||||
|
||||
#: paperless_mail/models.py:19
|
||||
msgid "No encryption"
|
||||
msgstr "Brak szyfrowania"
|
||||
|
||||
#: paperless_mail/models.py:20
|
||||
msgid "Use SSL"
|
||||
msgstr "Użyj SSL"
|
||||
|
||||
#: paperless_mail/models.py:21
|
||||
msgid "Use STARTTLS"
|
||||
msgstr "Użyj STARTTLS"
|
||||
|
||||
#: paperless_mail/models.py:29
|
||||
msgid "IMAP server"
|
||||
msgstr "Serwer IMAP"
|
||||
|
||||
#: paperless_mail/models.py:33
|
||||
msgid "IMAP port"
|
||||
msgstr "Port IMAP"
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr "Zwykle jest to 143 dla połączeń niezaszyfrowanych i STARTTLS oraz 993 dla połączeń SSL."
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
msgid "IMAP security"
|
||||
msgstr "Zabezpieczenia IMAP"
|
||||
|
||||
#: paperless_mail/models.py:46
|
||||
msgid "username"
|
||||
msgstr "nazwa użytkownika"
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "password"
|
||||
msgstr "hasło"
|
||||
|
||||
#: paperless_mail/models.py:54
|
||||
msgid "character set"
|
||||
msgstr "Kodowanie"
|
||||
|
||||
#: paperless_mail/models.py:57
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
msgstr "Zestaw znaków używany podczas komunikowania się z serwerem poczty, np. \"UTF-8\" lub \"US-ASCII\"."
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "mail rule"
|
||||
msgstr "reguła wiadomości"
|
||||
|
||||
#: paperless_mail/models.py:69
|
||||
msgid "mail rules"
|
||||
msgstr "reguły wiadomości"
|
||||
|
||||
#: paperless_mail/models.py:75
|
||||
msgid "Only process attachments."
|
||||
msgstr "Przetwarzaj tylko załączniki."
|
||||
|
||||
#: paperless_mail/models.py:76
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr "Przetwarzaj wszystkie pliki, łącznie z załącznikami „inline”."
|
||||
|
||||
#: paperless_mail/models.py:86
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr "Oznacz jako przeczytane, nie przetwarzaj przeczytanych wiadomości"
|
||||
|
||||
#: paperless_mail/models.py:87
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr "Oznacz wiadomość, nie przetwarzaj oznaczonych wiadomości"
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "Move to specified folder"
|
||||
msgstr "Przenieś do określonego folderu"
|
||||
|
||||
#: paperless_mail/models.py:89
|
||||
msgid "Delete"
|
||||
msgstr "Usuń"
|
||||
|
||||
#: paperless_mail/models.py:96
|
||||
msgid "Use subject as title"
|
||||
msgstr "Użyj tematu jako tytułu"
|
||||
|
||||
#: paperless_mail/models.py:97
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr "Użyj nazwy pliku załącznika jako tytułu"
|
||||
|
||||
#: paperless_mail/models.py:107
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr "Nie przypisuj korespondenta"
|
||||
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "Use mail address"
|
||||
msgstr "Użyj adresu e-mail"
|
||||
|
||||
#: paperless_mail/models.py:111
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr "Użyj nazwy nadawcy (lub adresu e-mail, jeśli jest niedostępna)"
|
||||
|
||||
#: paperless_mail/models.py:113
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr "Użyj korespondenta wybranego poniżej"
|
||||
|
||||
#: paperless_mail/models.py:121
|
||||
msgid "order"
|
||||
msgstr "kolejność"
|
||||
|
||||
#: paperless_mail/models.py:128
|
||||
msgid "account"
|
||||
msgstr "konto"
|
||||
|
||||
#: paperless_mail/models.py:132
|
||||
msgid "folder"
|
||||
msgstr "folder"
|
||||
|
||||
#: paperless_mail/models.py:134
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr "Podfoldery muszą być oddzielone kropkami."
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
msgid "filter from"
|
||||
msgstr "filtruj po nadawcy"
|
||||
|
||||
#: paperless_mail/models.py:141
|
||||
msgid "filter subject"
|
||||
msgstr "filtruj po temacie"
|
||||
|
||||
#: paperless_mail/models.py:144
|
||||
msgid "filter body"
|
||||
msgstr "filtruj po treści"
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "filter attachment filename"
|
||||
msgstr "filtruj po nazwie pliku załącznika"
|
||||
|
||||
#: paperless_mail/models.py:150
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr "Przetwarzaj tylko dokumenty, które całkowicie pasują do tej nazwy pliku, jeśli jest podana. Wzorce dopasowania jak *.pdf lub *faktura* są dozwolone. Wielkość liter nie jest rozróżniana."
|
||||
|
||||
#: paperless_mail/models.py:156
|
||||
msgid "maximum age"
|
||||
msgstr "nie starsze niż"
|
||||
|
||||
#: paperless_mail/models.py:158
|
||||
msgid "Specified in days."
|
||||
msgstr "dni."
|
||||
|
||||
#: paperless_mail/models.py:161
|
||||
msgid "attachment type"
|
||||
msgstr "typ załącznika"
|
||||
|
||||
#: paperless_mail/models.py:164
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr "Załączniki typu \"inline\" zawierają osadzone obrazy, więc najlepiej połączyć tę opcję z filtrem nazwy pliku."
|
||||
|
||||
#: paperless_mail/models.py:169
|
||||
msgid "action"
|
||||
msgstr "akcja"
|
||||
|
||||
#: paperless_mail/models.py:175
|
||||
msgid "action parameter"
|
||||
msgstr "parametr akcji"
|
||||
|
||||
#: paperless_mail/models.py:177
|
||||
msgid "Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots."
|
||||
msgstr "Dodatkowy parametr dla akcji wybranej powyżej, tj. docelowy folder akcji \"Przenieś do określonego folderu\". Podfoldery muszą być oddzielone kropkami."
|
||||
|
||||
#: paperless_mail/models.py:184
|
||||
msgid "assign title from"
|
||||
msgstr "przypisz tytuł"
|
||||
|
||||
#: paperless_mail/models.py:194
|
||||
msgid "assign this tag"
|
||||
msgstr "przypisz ten tag"
|
||||
|
||||
#: paperless_mail/models.py:202
|
||||
msgid "assign this document type"
|
||||
msgstr "przypisz ten typ dokumentu"
|
||||
|
||||
#: paperless_mail/models.py:206
|
||||
msgid "assign correspondent from"
|
||||
msgstr "przypisz korespondenta z"
|
||||
|
||||
#: paperless_mail/models.py:216
|
||||
msgid "assign this correspondent"
|
||||
msgstr "przypisz tego korespondenta"
|
||||
|
||||
699
src/locale/pt_BR/LC_MESSAGES/django.po
Normal file
699
src/locale/pt_BR/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,699 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ng\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-05-16 09:38+0000\n"
|
||||
"PO-Revision-Date: 2021-05-16 10:09\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Portuguese, Brazilian\n"
|
||||
"Language: pt_BR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: paperless-ng\n"
|
||||
"X-Crowdin-Project-ID: 434940\n"
|
||||
"X-Crowdin-Language: pt-BR\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 54\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
msgstr "Documentos"
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr "Qualquer palavra"
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr "Todas as palavras"
|
||||
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr "Detecção exata"
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr "Expressão regular"
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr "Palavra difusa (fuzzy)"
|
||||
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr "Automático"
|
||||
|
||||
#: documents/models.py:41 documents/models.py:350 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:117
|
||||
msgid "name"
|
||||
msgstr "nome"
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "match"
|
||||
msgstr "detecção"
|
||||
|
||||
#: documents/models.py:49
|
||||
msgid "matching algorithm"
|
||||
msgstr "algoritmo de detecção"
|
||||
|
||||
#: documents/models.py:55
|
||||
msgid "is insensitive"
|
||||
msgstr "diferencia maiúsculas de minúsculas"
|
||||
|
||||
#: documents/models.py:74 documents/models.py:120
|
||||
msgid "correspondent"
|
||||
msgstr "correspondente"
|
||||
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr "correspondentes"
|
||||
|
||||
#: documents/models.py:81
|
||||
msgid "color"
|
||||
msgstr "cor"
|
||||
|
||||
#: documents/models.py:87
|
||||
msgid "is inbox tag"
|
||||
msgstr "é etiqueta caixa de entrada"
|
||||
|
||||
#: documents/models.py:89
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr "Marca essa etiqueta como caixa de entrada: Todos os novos documentos consumidos terão as etiquetas de caixa de entrada."
|
||||
|
||||
#: documents/models.py:94
|
||||
msgid "tag"
|
||||
msgstr "etiqueta"
|
||||
|
||||
#: documents/models.py:95 documents/models.py:151
|
||||
msgid "tags"
|
||||
msgstr "etiquetas"
|
||||
|
||||
#: documents/models.py:101 documents/models.py:133
|
||||
msgid "document type"
|
||||
msgstr "tipo de documento"
|
||||
|
||||
#: documents/models.py:102
|
||||
msgid "document types"
|
||||
msgstr "tipos de documento"
|
||||
|
||||
#: documents/models.py:110
|
||||
msgid "Unencrypted"
|
||||
msgstr "Não encriptado"
|
||||
|
||||
#: documents/models.py:111
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "Encriptado com GNU Privacy Guard"
|
||||
|
||||
#: documents/models.py:124
|
||||
msgid "title"
|
||||
msgstr "título"
|
||||
|
||||
#: documents/models.py:137
|
||||
msgid "content"
|
||||
msgstr "conteúdo"
|
||||
|
||||
#: documents/models.py:139
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr "O conteúdo de texto bruto do documento. Esse campo é usado principalmente para busca."
|
||||
|
||||
#: documents/models.py:144
|
||||
msgid "mime type"
|
||||
msgstr "tipo mime"
|
||||
|
||||
#: documents/models.py:155
|
||||
msgid "checksum"
|
||||
msgstr "some de verificação"
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid "The checksum of the original document."
|
||||
msgstr "A soma de verificação original do documento."
|
||||
|
||||
#: documents/models.py:163
|
||||
msgid "archive checksum"
|
||||
msgstr "Soma de verificação de arquivamento."
|
||||
|
||||
#: documents/models.py:168
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr "A soma de verificação do documento arquivado."
|
||||
|
||||
#: documents/models.py:172 documents/models.py:328
|
||||
msgid "created"
|
||||
msgstr "criado"
|
||||
|
||||
#: documents/models.py:176
|
||||
msgid "modified"
|
||||
msgstr "modificado"
|
||||
|
||||
#: documents/models.py:180
|
||||
msgid "storage type"
|
||||
msgstr "tipo de armazenamento"
|
||||
|
||||
#: documents/models.py:188
|
||||
msgid "added"
|
||||
msgstr "adicionado"
|
||||
|
||||
#: documents/models.py:192
|
||||
msgid "filename"
|
||||
msgstr "nome do arquivo"
|
||||
|
||||
#: documents/models.py:198
|
||||
msgid "Current filename in storage"
|
||||
msgstr "Nome do arquivo atual armazenado"
|
||||
|
||||
#: documents/models.py:202
|
||||
msgid "archive filename"
|
||||
msgstr "nome do arquivo para arquivamento"
|
||||
|
||||
#: documents/models.py:208
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr "Nome do arquivo para arquivamento armazenado"
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "archive serial number"
|
||||
msgstr "número de sério de arquivamento"
|
||||
|
||||
#: documents/models.py:217
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr "A posição deste documento no seu arquivamento físico."
|
||||
|
||||
#: documents/models.py:223
|
||||
msgid "document"
|
||||
msgstr "documento"
|
||||
|
||||
#: documents/models.py:224
|
||||
msgid "documents"
|
||||
msgstr "documentos"
|
||||
|
||||
#: documents/models.py:311
|
||||
msgid "debug"
|
||||
msgstr "debug"
|
||||
|
||||
#: documents/models.py:312
|
||||
msgid "information"
|
||||
msgstr "informação"
|
||||
|
||||
#: documents/models.py:313
|
||||
msgid "warning"
|
||||
msgstr "aviso"
|
||||
|
||||
#: documents/models.py:314
|
||||
msgid "error"
|
||||
msgstr "erro"
|
||||
|
||||
#: documents/models.py:315
|
||||
msgid "critical"
|
||||
msgstr "crítico"
|
||||
|
||||
#: documents/models.py:319
|
||||
msgid "group"
|
||||
msgstr "grupo"
|
||||
|
||||
#: documents/models.py:322
|
||||
msgid "message"
|
||||
msgstr "mensagem"
|
||||
|
||||
#: documents/models.py:325
|
||||
msgid "level"
|
||||
msgstr "nível"
|
||||
|
||||
#: documents/models.py:332
|
||||
msgid "log"
|
||||
msgstr "log"
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "logs"
|
||||
msgstr "logs"
|
||||
|
||||
#: documents/models.py:344 documents/models.py:401
|
||||
msgid "saved view"
|
||||
msgstr "visualização"
|
||||
|
||||
#: documents/models.py:345
|
||||
msgid "saved views"
|
||||
msgstr "visualizações"
|
||||
|
||||
#: documents/models.py:348
|
||||
msgid "user"
|
||||
msgstr "usuário"
|
||||
|
||||
#: documents/models.py:354
|
||||
msgid "show on dashboard"
|
||||
msgstr "exibir no painel de controle"
|
||||
|
||||
#: documents/models.py:357
|
||||
msgid "show in sidebar"
|
||||
msgstr "exibir no painel lateral"
|
||||
|
||||
#: documents/models.py:361
|
||||
msgid "sort field"
|
||||
msgstr "ordenar campo"
|
||||
|
||||
#: documents/models.py:367
|
||||
msgid "sort reverse"
|
||||
msgstr "odernar reverso"
|
||||
|
||||
#: documents/models.py:373
|
||||
msgid "title contains"
|
||||
msgstr "título contém"
|
||||
|
||||
#: documents/models.py:374
|
||||
msgid "content contains"
|
||||
msgstr "conteúdo contém"
|
||||
|
||||
#: documents/models.py:375
|
||||
msgid "ASN is"
|
||||
msgstr "NSA é"
|
||||
|
||||
#: documents/models.py:376
|
||||
msgid "correspondent is"
|
||||
msgstr "correspondente é"
|
||||
|
||||
#: documents/models.py:377
|
||||
msgid "document type is"
|
||||
msgstr "tipo de documento é"
|
||||
|
||||
#: documents/models.py:378
|
||||
msgid "is in inbox"
|
||||
msgstr "é caixa de entrada"
|
||||
|
||||
#: documents/models.py:379
|
||||
msgid "has tag"
|
||||
msgstr "contém etiqueta"
|
||||
|
||||
#: documents/models.py:380
|
||||
msgid "has any tag"
|
||||
msgstr "contém qualquer etiqueta"
|
||||
|
||||
#: documents/models.py:381
|
||||
msgid "created before"
|
||||
msgstr "criado antes de"
|
||||
|
||||
#: documents/models.py:382
|
||||
msgid "created after"
|
||||
msgstr "criado depois de"
|
||||
|
||||
#: documents/models.py:383
|
||||
msgid "created year is"
|
||||
msgstr "ano de criação é"
|
||||
|
||||
#: documents/models.py:384
|
||||
msgid "created month is"
|
||||
msgstr "mês de criação é"
|
||||
|
||||
#: documents/models.py:385
|
||||
msgid "created day is"
|
||||
msgstr "dia de criação é"
|
||||
|
||||
#: documents/models.py:386
|
||||
msgid "added before"
|
||||
msgstr "adicionado antes de"
|
||||
|
||||
#: documents/models.py:387
|
||||
msgid "added after"
|
||||
msgstr "adicionado depois de"
|
||||
|
||||
#: documents/models.py:388
|
||||
msgid "modified before"
|
||||
msgstr "modificado antes de"
|
||||
|
||||
#: documents/models.py:389
|
||||
msgid "modified after"
|
||||
msgstr "modificado depois de"
|
||||
|
||||
#: documents/models.py:390
|
||||
msgid "does not have tag"
|
||||
msgstr "não tem etiqueta"
|
||||
|
||||
#: documents/models.py:391
|
||||
msgid "does not have ASN"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:392
|
||||
msgid "title or content contains"
|
||||
msgstr "título ou conteúdo contém"
|
||||
|
||||
#: documents/models.py:393
|
||||
msgid "fulltext query"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:394
|
||||
msgid "more like this"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:405
|
||||
msgid "rule type"
|
||||
msgstr "tipo de regra"
|
||||
|
||||
#: documents/models.py:409
|
||||
msgid "value"
|
||||
msgstr "valor"
|
||||
|
||||
#: documents/models.py:415
|
||||
msgid "filter rule"
|
||||
msgstr "regra de filtragem"
|
||||
|
||||
#: documents/models.py:416
|
||||
msgid "filter rules"
|
||||
msgstr "regras de filtragem"
|
||||
|
||||
#: documents/serialisers.py:53
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr "Expressão regular inválida: %(error)s"
|
||||
|
||||
#: documents/serialisers.py:177
|
||||
msgid "Invalid color."
|
||||
msgstr "Cor inválida."
|
||||
|
||||
#: documents/serialisers.py:451
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Tipo de arquivo %(type)s não suportado"
|
||||
|
||||
#: documents/templates/index.html:22
|
||||
msgid "Paperless-ng is loading..."
|
||||
msgstr "Paperless-ng está carregando..."
|
||||
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ng signed out"
|
||||
msgstr "Paperless-ng saiu"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:45
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr "Sua sessão foi encerrada com sucesso. Até mais!"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:46
|
||||
msgid "Sign in again"
|
||||
msgstr "Entre novamente"
|
||||
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ng sign in"
|
||||
msgstr "Entrar no Paperless-ng"
|
||||
|
||||
#: documents/templates/registration/login.html:47
|
||||
msgid "Please sign in."
|
||||
msgstr "Por favor, entre na sua conta"
|
||||
|
||||
#: documents/templates/registration/login.html:50
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr "Seu usuário e senha estão incorretos. Por favor, tente novamente."
|
||||
|
||||
#: documents/templates/registration/login.html:53
|
||||
msgid "Username"
|
||||
msgstr "Usuário"
|
||||
|
||||
#: documents/templates/registration/login.html:54
|
||||
msgid "Password"
|
||||
msgstr "Senha"
|
||||
|
||||
#: documents/templates/registration/login.html:59
|
||||
msgid "Sign in"
|
||||
msgstr "Entrar"
|
||||
|
||||
#: paperless/settings.py:303
|
||||
msgid "English (US)"
|
||||
msgstr "Inglês (EUA)"
|
||||
|
||||
#: paperless/settings.py:304
|
||||
msgid "English (GB)"
|
||||
msgstr "Inglês (GB)"
|
||||
|
||||
#: paperless/settings.py:305
|
||||
msgid "German"
|
||||
msgstr "Alemão"
|
||||
|
||||
#: paperless/settings.py:306
|
||||
msgid "Dutch"
|
||||
msgstr "Holandês"
|
||||
|
||||
#: paperless/settings.py:307
|
||||
msgid "French"
|
||||
msgstr "Francês"
|
||||
|
||||
#: paperless/settings.py:308
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Português (Brasil)"
|
||||
|
||||
#: paperless/settings.py:309
|
||||
msgid "Portuguese"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Italian"
|
||||
msgstr "Italiano"
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Romanian"
|
||||
msgstr "Romeno"
|
||||
|
||||
#: paperless/settings.py:312
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:313
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:314
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:315
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/urls.py:120
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr "Administração do Paperless-ng"
|
||||
|
||||
#: paperless_mail/admin.py:15
|
||||
msgid "Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:18
|
||||
msgid "Advanced settings"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
msgid "Filter"
|
||||
msgstr "Filtro"
|
||||
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr "Paperless processará somente e-mails que se encaixam em TODOS os filtros abaixo."
|
||||
|
||||
#: paperless_mail/admin.py:49
|
||||
msgid "Actions"
|
||||
msgstr "Ações"
|
||||
|
||||
#: paperless_mail/admin.py:51
|
||||
msgid "The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched."
|
||||
msgstr "A ação se aplica ao e-mail. Essa ação só é executada quando documentos foram consumidos do e-mail. E-mails sem anexos permanecerão intactos."
|
||||
|
||||
#: paperless_mail/admin.py:58
|
||||
msgid "Metadata"
|
||||
msgstr "Metadados"
|
||||
|
||||
#: paperless_mail/admin.py:60
|
||||
msgid "Assign metadata to documents consumed from this rule automatically. If you do not assign tags, types or correspondents here, paperless will still process all matching rules that you have defined."
|
||||
msgstr "Atribua metadados aos documentos consumidos por esta regra automaticamente. Se você não atribuir etiquetas, tipos ou correspondentes aqui, paperless ainda sim processará todas as regras de detecção que você definiu."
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
msgstr "Paperless mail"
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail account"
|
||||
msgstr "conta de e-mail"
|
||||
|
||||
#: paperless_mail/models.py:12
|
||||
msgid "mail accounts"
|
||||
msgstr "contas de e-mail"
|
||||
|
||||
#: paperless_mail/models.py:19
|
||||
msgid "No encryption"
|
||||
msgstr "Sem encriptação"
|
||||
|
||||
#: paperless_mail/models.py:20
|
||||
msgid "Use SSL"
|
||||
msgstr "Usar SSL"
|
||||
|
||||
#: paperless_mail/models.py:21
|
||||
msgid "Use STARTTLS"
|
||||
msgstr "Usar STARTTLS"
|
||||
|
||||
#: paperless_mail/models.py:29
|
||||
msgid "IMAP server"
|
||||
msgstr "Servidor IMAP"
|
||||
|
||||
#: paperless_mail/models.py:33
|
||||
msgid "IMAP port"
|
||||
msgstr "Porta IMAP"
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr "É geralmente 143 para não encriptado e conexões STARTTLS, e 993 para conexões SSL."
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
msgid "IMAP security"
|
||||
msgstr "segurança IMAP"
|
||||
|
||||
#: paperless_mail/models.py:46
|
||||
msgid "username"
|
||||
msgstr "usuário"
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "password"
|
||||
msgstr "senha"
|
||||
|
||||
#: paperless_mail/models.py:54
|
||||
msgid "character set"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:57
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "mail rule"
|
||||
msgstr "regra de e-mail"
|
||||
|
||||
#: paperless_mail/models.py:69
|
||||
msgid "mail rules"
|
||||
msgstr "regras de e-mail"
|
||||
|
||||
#: paperless_mail/models.py:75
|
||||
msgid "Only process attachments."
|
||||
msgstr "Processar somente anexos."
|
||||
|
||||
#: paperless_mail/models.py:76
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr "Processar todos os arquivos, incluindo anexos 'inline'."
|
||||
|
||||
#: paperless_mail/models.py:86
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr "Marcar como lido, não processar e-mails lidos"
|
||||
|
||||
#: paperless_mail/models.py:87
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr "Sinalizar o e-mail, não processar e-mails sinalizados"
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "Move to specified folder"
|
||||
msgstr "Mover para pasta especificada"
|
||||
|
||||
#: paperless_mail/models.py:89
|
||||
msgid "Delete"
|
||||
msgstr "Excluir"
|
||||
|
||||
#: paperless_mail/models.py:96
|
||||
msgid "Use subject as title"
|
||||
msgstr "Usar assunto como título"
|
||||
|
||||
#: paperless_mail/models.py:97
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr "Usar nome do arquivo anexo como título"
|
||||
|
||||
#: paperless_mail/models.py:107
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr "Não atribuir um correspondente"
|
||||
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "Use mail address"
|
||||
msgstr "Usar endereço de e-mail"
|
||||
|
||||
#: paperless_mail/models.py:111
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr "Usar nome (ou endereço de e-mail se não disponível)"
|
||||
|
||||
#: paperless_mail/models.py:113
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr "Usar correspondente selecionado abaixo"
|
||||
|
||||
#: paperless_mail/models.py:121
|
||||
msgid "order"
|
||||
msgstr "ordem"
|
||||
|
||||
#: paperless_mail/models.py:128
|
||||
msgid "account"
|
||||
msgstr "conta"
|
||||
|
||||
#: paperless_mail/models.py:132
|
||||
msgid "folder"
|
||||
msgstr "pasta"
|
||||
|
||||
#: paperless_mail/models.py:134
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
msgid "filter from"
|
||||
msgstr "filtrar de"
|
||||
|
||||
#: paperless_mail/models.py:141
|
||||
msgid "filter subject"
|
||||
msgstr "filtrar assunto"
|
||||
|
||||
#: paperless_mail/models.py:144
|
||||
msgid "filter body"
|
||||
msgstr "filtrar corpo"
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "filter attachment filename"
|
||||
msgstr "filtrar nome do arquivo anexo"
|
||||
|
||||
#: paperless_mail/models.py:150
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr "Consumir somente documentos que correspondem a este nome de arquivo se especificado.\n"
|
||||
"Curingas como *.pdf ou *invoice* são permitidos. Sem diferenciação de maiúsculas e minúsculas."
|
||||
|
||||
#: paperless_mail/models.py:156
|
||||
msgid "maximum age"
|
||||
msgstr "idade máxima"
|
||||
|
||||
#: paperless_mail/models.py:158
|
||||
msgid "Specified in days."
|
||||
msgstr "Especificada em dias."
|
||||
|
||||
#: paperless_mail/models.py:161
|
||||
msgid "attachment type"
|
||||
msgstr "tipo de anexo"
|
||||
|
||||
#: paperless_mail/models.py:164
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr "Anexos inline incluem imagens inseridas, por isso é melhor combinar essa opção com um filtro de nome de arquivo."
|
||||
|
||||
#: paperless_mail/models.py:169
|
||||
msgid "action"
|
||||
msgstr "ação"
|
||||
|
||||
#: paperless_mail/models.py:175
|
||||
msgid "action parameter"
|
||||
msgstr "parâmetro da ação"
|
||||
|
||||
#: paperless_mail/models.py:177
|
||||
msgid "Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:184
|
||||
msgid "assign title from"
|
||||
msgstr "atribuir título de"
|
||||
|
||||
#: paperless_mail/models.py:194
|
||||
msgid "assign this tag"
|
||||
msgstr "atribuir esta etiqueta"
|
||||
|
||||
#: paperless_mail/models.py:202
|
||||
msgid "assign this document type"
|
||||
msgstr "atribuir este tipo de documento"
|
||||
|
||||
#: paperless_mail/models.py:206
|
||||
msgid "assign correspondent from"
|
||||
msgstr "atribuir correspondente de"
|
||||
|
||||
#: paperless_mail/models.py:216
|
||||
msgid "assign this correspondent"
|
||||
msgstr "atribuir este correspondente"
|
||||
|
||||
698
src/locale/pt_PT/LC_MESSAGES/django.po
Normal file
698
src/locale/pt_PT/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,698 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ng\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-05-16 09:38+0000\n"
|
||||
"PO-Revision-Date: 2021-05-16 20:45\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Portuguese\n"
|
||||
"Language: pt_PT\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: paperless-ng\n"
|
||||
"X-Crowdin-Project-ID: 434940\n"
|
||||
"X-Crowdin-Language: pt-PT\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 54\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
msgstr "Documentos"
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr "Qualquer palavra"
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr "Todas as palavras"
|
||||
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr "Detecção exata"
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr "Expressão regular"
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr "Palavra difusa (fuzzy)"
|
||||
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr "Automático"
|
||||
|
||||
#: documents/models.py:41 documents/models.py:350 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:117
|
||||
msgid "name"
|
||||
msgstr "nome"
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "match"
|
||||
msgstr "correspondência"
|
||||
|
||||
#: documents/models.py:49
|
||||
msgid "matching algorithm"
|
||||
msgstr "algoritmo correspondente"
|
||||
|
||||
#: documents/models.py:55
|
||||
msgid "is insensitive"
|
||||
msgstr "é insensível"
|
||||
|
||||
#: documents/models.py:74 documents/models.py:120
|
||||
msgid "correspondent"
|
||||
msgstr "correspondente"
|
||||
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr "correspondentes"
|
||||
|
||||
#: documents/models.py:81
|
||||
msgid "color"
|
||||
msgstr "cor"
|
||||
|
||||
#: documents/models.py:87
|
||||
msgid "is inbox tag"
|
||||
msgstr "é etiqueta de novo"
|
||||
|
||||
#: documents/models.py:89
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr "Marca esta etiqueta como uma etiqueta de entrada. Todos os documentos recentemente consumidos serão etiquetados com a etiqueta de entrada."
|
||||
|
||||
#: documents/models.py:94
|
||||
msgid "tag"
|
||||
msgstr "etiqueta"
|
||||
|
||||
#: documents/models.py:95 documents/models.py:151
|
||||
msgid "tags"
|
||||
msgstr "etiquetas"
|
||||
|
||||
#: documents/models.py:101 documents/models.py:133
|
||||
msgid "document type"
|
||||
msgstr "tipo de documento"
|
||||
|
||||
#: documents/models.py:102
|
||||
msgid "document types"
|
||||
msgstr "tipos de documento"
|
||||
|
||||
#: documents/models.py:110
|
||||
msgid "Unencrypted"
|
||||
msgstr "Não encriptado"
|
||||
|
||||
#: documents/models.py:111
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "Encriptado com GNU Privacy Guard"
|
||||
|
||||
#: documents/models.py:124
|
||||
msgid "title"
|
||||
msgstr "título"
|
||||
|
||||
#: documents/models.py:137
|
||||
msgid "content"
|
||||
msgstr "conteúdo"
|
||||
|
||||
#: documents/models.py:139
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr "Os dados de texto, em cru, do documento. Este campo é utilizado principalmente para pesquisar."
|
||||
|
||||
#: documents/models.py:144
|
||||
msgid "mime type"
|
||||
msgstr "tipo mime"
|
||||
|
||||
#: documents/models.py:155
|
||||
msgid "checksum"
|
||||
msgstr "soma de verificação"
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid "The checksum of the original document."
|
||||
msgstr "A soma de verificação do documento original."
|
||||
|
||||
#: documents/models.py:163
|
||||
msgid "archive checksum"
|
||||
msgstr "arquivar soma de verificação"
|
||||
|
||||
#: documents/models.py:168
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr "A soma de verificação do documento arquivado."
|
||||
|
||||
#: documents/models.py:172 documents/models.py:328
|
||||
msgid "created"
|
||||
msgstr "criado"
|
||||
|
||||
#: documents/models.py:176
|
||||
msgid "modified"
|
||||
msgstr "modificado"
|
||||
|
||||
#: documents/models.py:180
|
||||
msgid "storage type"
|
||||
msgstr "tipo de armazenamento"
|
||||
|
||||
#: documents/models.py:188
|
||||
msgid "added"
|
||||
msgstr "adicionado"
|
||||
|
||||
#: documents/models.py:192
|
||||
msgid "filename"
|
||||
msgstr "nome de ficheiro"
|
||||
|
||||
#: documents/models.py:198
|
||||
msgid "Current filename in storage"
|
||||
msgstr "Nome do arquivo atual no armazenamento"
|
||||
|
||||
#: documents/models.py:202
|
||||
msgid "archive filename"
|
||||
msgstr "nome do ficheiro de arquivo"
|
||||
|
||||
#: documents/models.py:208
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr "Nome do arquivo atual em no armazenamento"
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "archive serial number"
|
||||
msgstr "numero de série de arquivo"
|
||||
|
||||
#: documents/models.py:217
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr "A posição do documento no seu arquivo físico de documentos."
|
||||
|
||||
#: documents/models.py:223
|
||||
msgid "document"
|
||||
msgstr "documento"
|
||||
|
||||
#: documents/models.py:224
|
||||
msgid "documents"
|
||||
msgstr "documentos"
|
||||
|
||||
#: documents/models.py:311
|
||||
msgid "debug"
|
||||
msgstr "depurar"
|
||||
|
||||
#: documents/models.py:312
|
||||
msgid "information"
|
||||
msgstr "informação"
|
||||
|
||||
#: documents/models.py:313
|
||||
msgid "warning"
|
||||
msgstr "aviso"
|
||||
|
||||
#: documents/models.py:314
|
||||
msgid "error"
|
||||
msgstr "erro"
|
||||
|
||||
#: documents/models.py:315
|
||||
msgid "critical"
|
||||
msgstr "crítico"
|
||||
|
||||
#: documents/models.py:319
|
||||
msgid "group"
|
||||
msgstr "grupo"
|
||||
|
||||
#: documents/models.py:322
|
||||
msgid "message"
|
||||
msgstr "mensagem"
|
||||
|
||||
#: documents/models.py:325
|
||||
msgid "level"
|
||||
msgstr "nível"
|
||||
|
||||
#: documents/models.py:332
|
||||
msgid "log"
|
||||
msgstr "registo"
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "logs"
|
||||
msgstr "registos"
|
||||
|
||||
#: documents/models.py:344 documents/models.py:401
|
||||
msgid "saved view"
|
||||
msgstr "vista guardada"
|
||||
|
||||
#: documents/models.py:345
|
||||
msgid "saved views"
|
||||
msgstr "vistas guardadas"
|
||||
|
||||
#: documents/models.py:348
|
||||
msgid "user"
|
||||
msgstr "utilizador"
|
||||
|
||||
#: documents/models.py:354
|
||||
msgid "show on dashboard"
|
||||
msgstr "exibir no painel de controlo"
|
||||
|
||||
#: documents/models.py:357
|
||||
msgid "show in sidebar"
|
||||
msgstr "mostrar na navegação lateral"
|
||||
|
||||
#: documents/models.py:361
|
||||
msgid "sort field"
|
||||
msgstr "ordenar campo"
|
||||
|
||||
#: documents/models.py:367
|
||||
msgid "sort reverse"
|
||||
msgstr "ordenar inversamente"
|
||||
|
||||
#: documents/models.py:373
|
||||
msgid "title contains"
|
||||
msgstr "o título contém"
|
||||
|
||||
#: documents/models.py:374
|
||||
msgid "content contains"
|
||||
msgstr "o conteúdo contém"
|
||||
|
||||
#: documents/models.py:375
|
||||
msgid "ASN is"
|
||||
msgstr "O NSA é"
|
||||
|
||||
#: documents/models.py:376
|
||||
msgid "correspondent is"
|
||||
msgstr "o correspondente é"
|
||||
|
||||
#: documents/models.py:377
|
||||
msgid "document type is"
|
||||
msgstr "o tipo de documento é"
|
||||
|
||||
#: documents/models.py:378
|
||||
msgid "is in inbox"
|
||||
msgstr "está na entrada"
|
||||
|
||||
#: documents/models.py:379
|
||||
msgid "has tag"
|
||||
msgstr "tem etiqueta"
|
||||
|
||||
#: documents/models.py:380
|
||||
msgid "has any tag"
|
||||
msgstr "tem qualquer etiqueta"
|
||||
|
||||
#: documents/models.py:381
|
||||
msgid "created before"
|
||||
msgstr "criado antes"
|
||||
|
||||
#: documents/models.py:382
|
||||
msgid "created after"
|
||||
msgstr "criado depois"
|
||||
|
||||
#: documents/models.py:383
|
||||
msgid "created year is"
|
||||
msgstr "ano criada é"
|
||||
|
||||
#: documents/models.py:384
|
||||
msgid "created month is"
|
||||
msgstr "mês criado é"
|
||||
|
||||
#: documents/models.py:385
|
||||
msgid "created day is"
|
||||
msgstr "dia criado é"
|
||||
|
||||
#: documents/models.py:386
|
||||
msgid "added before"
|
||||
msgstr "adicionada antes"
|
||||
|
||||
#: documents/models.py:387
|
||||
msgid "added after"
|
||||
msgstr "adicionado depois de"
|
||||
|
||||
#: documents/models.py:388
|
||||
msgid "modified before"
|
||||
msgstr "modificado antes de"
|
||||
|
||||
#: documents/models.py:389
|
||||
msgid "modified after"
|
||||
msgstr "modificado depois de"
|
||||
|
||||
#: documents/models.py:390
|
||||
msgid "does not have tag"
|
||||
msgstr "não tem etiqueta"
|
||||
|
||||
#: documents/models.py:391
|
||||
msgid "does not have ASN"
|
||||
msgstr "não possui um NSA"
|
||||
|
||||
#: documents/models.py:392
|
||||
msgid "title or content contains"
|
||||
msgstr "título ou conteúdo contém"
|
||||
|
||||
#: documents/models.py:393
|
||||
msgid "fulltext query"
|
||||
msgstr "consulta de texto completo"
|
||||
|
||||
#: documents/models.py:394
|
||||
msgid "more like this"
|
||||
msgstr "mais como este"
|
||||
|
||||
#: documents/models.py:405
|
||||
msgid "rule type"
|
||||
msgstr "tipo de regra"
|
||||
|
||||
#: documents/models.py:409
|
||||
msgid "value"
|
||||
msgstr "valor"
|
||||
|
||||
#: documents/models.py:415
|
||||
msgid "filter rule"
|
||||
msgstr "regra de filtragem"
|
||||
|
||||
#: documents/models.py:416
|
||||
msgid "filter rules"
|
||||
msgstr "regras de filtragem"
|
||||
|
||||
#: documents/serialisers.py:53
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr "Expressão regular inválida: %(error)s"
|
||||
|
||||
#: documents/serialisers.py:177
|
||||
msgid "Invalid color."
|
||||
msgstr "Cor invalida."
|
||||
|
||||
#: documents/serialisers.py:451
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Tipo de arquivo %(type)s não suportado"
|
||||
|
||||
#: documents/templates/index.html:22
|
||||
msgid "Paperless-ng is loading..."
|
||||
msgstr "O paperless-ng está a carregar..."
|
||||
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ng signed out"
|
||||
msgstr "Paperless-ng com sessão terminada"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:45
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr "Terminou a sessão com sucesso. Adeus!"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:46
|
||||
msgid "Sign in again"
|
||||
msgstr "Iniciar sessão novamente"
|
||||
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ng sign in"
|
||||
msgstr "Inicio de sessão Paperless-ng"
|
||||
|
||||
#: documents/templates/registration/login.html:47
|
||||
msgid "Please sign in."
|
||||
msgstr "Por favor inicie sessão."
|
||||
|
||||
#: documents/templates/registration/login.html:50
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr "O utilizador e a senha não correspondem. Tente novamente."
|
||||
|
||||
#: documents/templates/registration/login.html:53
|
||||
msgid "Username"
|
||||
msgstr "Nome de utilizador"
|
||||
|
||||
#: documents/templates/registration/login.html:54
|
||||
msgid "Password"
|
||||
msgstr "Palavra-passe"
|
||||
|
||||
#: documents/templates/registration/login.html:59
|
||||
msgid "Sign in"
|
||||
msgstr "Iniciar sessão"
|
||||
|
||||
#: paperless/settings.py:303
|
||||
msgid "English (US)"
|
||||
msgstr "Inglês (EUA)"
|
||||
|
||||
#: paperless/settings.py:304
|
||||
msgid "English (GB)"
|
||||
msgstr "English (GB)"
|
||||
|
||||
#: paperless/settings.py:305
|
||||
msgid "German"
|
||||
msgstr "Deutsch"
|
||||
|
||||
#: paperless/settings.py:306
|
||||
msgid "Dutch"
|
||||
msgstr "Nederlandse"
|
||||
|
||||
#: paperless/settings.py:307
|
||||
msgid "French"
|
||||
msgstr "Français"
|
||||
|
||||
#: paperless/settings.py:308
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Português (Brasil)"
|
||||
|
||||
#: paperless/settings.py:309
|
||||
msgid "Portuguese"
|
||||
msgstr "Português"
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Italian"
|
||||
msgstr "Italiano"
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Romanian"
|
||||
msgstr "Romeno"
|
||||
|
||||
#: paperless/settings.py:312
|
||||
msgid "Russian"
|
||||
msgstr "Russo"
|
||||
|
||||
#: paperless/settings.py:313
|
||||
msgid "Spanish"
|
||||
msgstr "Espanhol"
|
||||
|
||||
#: paperless/settings.py:314
|
||||
msgid "Polish"
|
||||
msgstr "Polaco"
|
||||
|
||||
#: paperless/settings.py:315
|
||||
msgid "Swedish"
|
||||
msgstr "Sueco"
|
||||
|
||||
#: paperless/urls.py:120
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr "Administração do Paperless-ng"
|
||||
|
||||
#: paperless_mail/admin.py:15
|
||||
msgid "Authentication"
|
||||
msgstr "Autenticação"
|
||||
|
||||
#: paperless_mail/admin.py:18
|
||||
msgid "Advanced settings"
|
||||
msgstr "Definições avançadas"
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
msgid "Filter"
|
||||
msgstr "Filtro"
|
||||
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr "O Paperless apenas irá processar emails que coincidem com TODOS os filtros dados abaixo."
|
||||
|
||||
#: paperless_mail/admin.py:49
|
||||
msgid "Actions"
|
||||
msgstr "Ações"
|
||||
|
||||
#: paperless_mail/admin.py:51
|
||||
msgid "The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched."
|
||||
msgstr "A ação aplicada a correio. Esta ação apenas será efetuada com documentos que tenham sido consumidos através do correio. E-mails sem anexos permanecerão intactos."
|
||||
|
||||
#: paperless_mail/admin.py:58
|
||||
msgid "Metadata"
|
||||
msgstr "Metadados"
|
||||
|
||||
#: paperless_mail/admin.py:60
|
||||
msgid "Assign metadata to documents consumed from this rule automatically. If you do not assign tags, types or correspondents here, paperless will still process all matching rules that you have defined."
|
||||
msgstr "Atribuir meta-dados aos documentos consumidos automaticamente através desta regra. Se você não atribuir etiquetas, tipos ou correspondentes aqui, o paperless ainda assim processará todas as regras correspondentes que tenha definido."
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
msgstr "Correio Paperless"
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail account"
|
||||
msgstr "conta de email"
|
||||
|
||||
#: paperless_mail/models.py:12
|
||||
msgid "mail accounts"
|
||||
msgstr "contas de email"
|
||||
|
||||
#: paperless_mail/models.py:19
|
||||
msgid "No encryption"
|
||||
msgstr "Sem encriptação"
|
||||
|
||||
#: paperless_mail/models.py:20
|
||||
msgid "Use SSL"
|
||||
msgstr "Utilizar SSL"
|
||||
|
||||
#: paperless_mail/models.py:21
|
||||
msgid "Use STARTTLS"
|
||||
msgstr "Utilizar STARTTLS"
|
||||
|
||||
#: paperless_mail/models.py:29
|
||||
msgid "IMAP server"
|
||||
msgstr "Servidor IMAP"
|
||||
|
||||
#: paperless_mail/models.py:33
|
||||
msgid "IMAP port"
|
||||
msgstr "Porto IMAP"
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr "Por norma é o 143 sem encriptação e conexões STARTTLS, e o 993 para conexões com SSL."
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
msgid "IMAP security"
|
||||
msgstr "Segurança IMAP"
|
||||
|
||||
#: paperless_mail/models.py:46
|
||||
msgid "username"
|
||||
msgstr "nome de utilizador"
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "password"
|
||||
msgstr "palavra-passe"
|
||||
|
||||
#: paperless_mail/models.py:54
|
||||
msgid "character set"
|
||||
msgstr "conjunto de caracteres"
|
||||
|
||||
#: paperless_mail/models.py:57
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
msgstr "O conjunto de caracteres a utilizar ao comunicar com um servidor de email, tal como 'UTF-8' ou 'US-ASCII'."
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "mail rule"
|
||||
msgstr "regra de correio"
|
||||
|
||||
#: paperless_mail/models.py:69
|
||||
msgid "mail rules"
|
||||
msgstr "regras de correio"
|
||||
|
||||
#: paperless_mail/models.py:75
|
||||
msgid "Only process attachments."
|
||||
msgstr "Processar anexos apenas."
|
||||
|
||||
#: paperless_mail/models.py:76
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr "Processar todos os ficheiros, incluindo ficheiros 'embutidos (inline)'."
|
||||
|
||||
#: paperless_mail/models.py:86
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr "Marcar como lido, não processar emails lidos"
|
||||
|
||||
#: paperless_mail/models.py:87
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr "Marcar o email, não processar emails marcados"
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "Move to specified folder"
|
||||
msgstr "Mover para uma diretoria específica"
|
||||
|
||||
#: paperless_mail/models.py:89
|
||||
msgid "Delete"
|
||||
msgstr "Excluir"
|
||||
|
||||
#: paperless_mail/models.py:96
|
||||
msgid "Use subject as title"
|
||||
msgstr "Utilizar o assunto como título"
|
||||
|
||||
#: paperless_mail/models.py:97
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr "Utilizar o nome do anexo como título"
|
||||
|
||||
#: paperless_mail/models.py:107
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr "Não atribuir um correspondente"
|
||||
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "Use mail address"
|
||||
msgstr "Utilizar o endereço de email"
|
||||
|
||||
#: paperless_mail/models.py:111
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr "Utilizar nome (ou endereço de email se não disponível)"
|
||||
|
||||
#: paperless_mail/models.py:113
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr "Utilizar o correspondente selecionado abaixo"
|
||||
|
||||
#: paperless_mail/models.py:121
|
||||
msgid "order"
|
||||
msgstr "ordem"
|
||||
|
||||
#: paperless_mail/models.py:128
|
||||
msgid "account"
|
||||
msgstr "conta"
|
||||
|
||||
#: paperless_mail/models.py:132
|
||||
msgid "folder"
|
||||
msgstr "directoria"
|
||||
|
||||
#: paperless_mail/models.py:134
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr "Sub-pastas devem ser separadas por pontos."
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
msgid "filter from"
|
||||
msgstr "filtrar de"
|
||||
|
||||
#: paperless_mail/models.py:141
|
||||
msgid "filter subject"
|
||||
msgstr "filtrar assunto"
|
||||
|
||||
#: paperless_mail/models.py:144
|
||||
msgid "filter body"
|
||||
msgstr "filtrar corpo"
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "filter attachment filename"
|
||||
msgstr "filtrar nome do arquivo anexo"
|
||||
|
||||
#: paperless_mail/models.py:150
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr "Consumir apenas documentos que correspondam inteiramente ao nome de arquivo se especificado. Genéricos como *.pdf ou *fatura* são permitidos. Não é sensível a letras maiúsculas/minúsculas."
|
||||
|
||||
#: paperless_mail/models.py:156
|
||||
msgid "maximum age"
|
||||
msgstr "idade máxima"
|
||||
|
||||
#: paperless_mail/models.py:158
|
||||
msgid "Specified in days."
|
||||
msgstr "Especificado em dias."
|
||||
|
||||
#: paperless_mail/models.py:161
|
||||
msgid "attachment type"
|
||||
msgstr "tipo de anexo"
|
||||
|
||||
#: paperless_mail/models.py:164
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr "Anexos embutidos incluem imagens incorporadas, por isso é melhor combinar esta opção com um filtro de nome do arquivo."
|
||||
|
||||
#: paperless_mail/models.py:169
|
||||
msgid "action"
|
||||
msgstr "ação"
|
||||
|
||||
#: paperless_mail/models.py:175
|
||||
msgid "action parameter"
|
||||
msgstr "parâmetro de ação"
|
||||
|
||||
#: paperless_mail/models.py:177
|
||||
msgid "Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots."
|
||||
msgstr "Parâmetros adicionais para a ação selecionada acima, isto é, a pasta alvo da ação mover para pasta. Sub-pastas devem ser separadas por pontos."
|
||||
|
||||
#: paperless_mail/models.py:184
|
||||
msgid "assign title from"
|
||||
msgstr "atribuir titulo de"
|
||||
|
||||
#: paperless_mail/models.py:194
|
||||
msgid "assign this tag"
|
||||
msgstr "atribuir esta etiqueta"
|
||||
|
||||
#: paperless_mail/models.py:202
|
||||
msgid "assign this document type"
|
||||
msgstr "atribuir este tipo de documento"
|
||||
|
||||
#: paperless_mail/models.py:206
|
||||
msgid "assign correspondent from"
|
||||
msgstr "atribuir correspondente de"
|
||||
|
||||
#: paperless_mail/models.py:216
|
||||
msgid "assign this correspondent"
|
||||
msgstr "atribuir este correspondente"
|
||||
|
||||
698
src/locale/ro_RO/LC_MESSAGES/django.po
Normal file
698
src/locale/ro_RO/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,698 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ng\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-05-16 09:38+0000\n"
|
||||
"PO-Revision-Date: 2021-08-16 09:06\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Romanian\n"
|
||||
"Language: ro_RO\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
|
||||
"X-Crowdin-Project: paperless-ng\n"
|
||||
"X-Crowdin-Project-ID: 434940\n"
|
||||
"X-Crowdin-Language: ro\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 54\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
msgstr "Documente"
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr "Orice cuvânt"
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr "Toate cuvintele"
|
||||
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr "Potrivire exactă"
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr "Expresie regulată"
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr "Mod neatent"
|
||||
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr "Automat"
|
||||
|
||||
#: documents/models.py:41 documents/models.py:350 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:117
|
||||
msgid "name"
|
||||
msgstr "nume"
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "match"
|
||||
msgstr "potrivire"
|
||||
|
||||
#: documents/models.py:49
|
||||
msgid "matching algorithm"
|
||||
msgstr "algoritm de potrivire"
|
||||
|
||||
#: documents/models.py:55
|
||||
msgid "is insensitive"
|
||||
msgstr "nu ține cont de majuscule"
|
||||
|
||||
#: documents/models.py:74 documents/models.py:120
|
||||
msgid "correspondent"
|
||||
msgstr "corespondent"
|
||||
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr "corespondenți"
|
||||
|
||||
#: documents/models.py:81
|
||||
msgid "color"
|
||||
msgstr "culoare"
|
||||
|
||||
#: documents/models.py:87
|
||||
msgid "is inbox tag"
|
||||
msgstr "este etichetă inbox"
|
||||
|
||||
#: documents/models.py:89
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr "Marchează aceasta eticheta ca etichetă inbox: Toate documentele nou consumate primesc aceasta eticheta."
|
||||
|
||||
#: documents/models.py:94
|
||||
msgid "tag"
|
||||
msgstr "etichetă"
|
||||
|
||||
#: documents/models.py:95 documents/models.py:151
|
||||
msgid "tags"
|
||||
msgstr "etichete"
|
||||
|
||||
#: documents/models.py:101 documents/models.py:133
|
||||
msgid "document type"
|
||||
msgstr "tip de document"
|
||||
|
||||
#: documents/models.py:102
|
||||
msgid "document types"
|
||||
msgstr "tipuri de document"
|
||||
|
||||
#: documents/models.py:110
|
||||
msgid "Unencrypted"
|
||||
msgstr "Necriptat"
|
||||
|
||||
#: documents/models.py:111
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "Criptat cu GNU Privacy Guard"
|
||||
|
||||
#: documents/models.py:124
|
||||
msgid "title"
|
||||
msgstr "titlu"
|
||||
|
||||
#: documents/models.py:137
|
||||
msgid "content"
|
||||
msgstr "conținut"
|
||||
|
||||
#: documents/models.py:139
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr "Textul brut al documentului. Acest camp este folosit in principal pentru căutare."
|
||||
|
||||
#: documents/models.py:144
|
||||
msgid "mime type"
|
||||
msgstr "tip MIME"
|
||||
|
||||
#: documents/models.py:155
|
||||
msgid "checksum"
|
||||
msgstr "sumă de control"
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid "The checksum of the original document."
|
||||
msgstr "Suma de control a documentului original."
|
||||
|
||||
#: documents/models.py:163
|
||||
msgid "archive checksum"
|
||||
msgstr "suma de control a arhivei"
|
||||
|
||||
#: documents/models.py:168
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr "Suma de control a documentului arhivat."
|
||||
|
||||
#: documents/models.py:172 documents/models.py:328
|
||||
msgid "created"
|
||||
msgstr "creat"
|
||||
|
||||
#: documents/models.py:176
|
||||
msgid "modified"
|
||||
msgstr "modificat"
|
||||
|
||||
#: documents/models.py:180
|
||||
msgid "storage type"
|
||||
msgstr "tip de stocare"
|
||||
|
||||
#: documents/models.py:188
|
||||
msgid "added"
|
||||
msgstr "adăugat"
|
||||
|
||||
#: documents/models.py:192
|
||||
msgid "filename"
|
||||
msgstr "nume fișier"
|
||||
|
||||
#: documents/models.py:198
|
||||
msgid "Current filename in storage"
|
||||
msgstr "Numele curent al fișierului stocat"
|
||||
|
||||
#: documents/models.py:202
|
||||
msgid "archive filename"
|
||||
msgstr "nume fișier arhiva"
|
||||
|
||||
#: documents/models.py:208
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr "Numele curent al arhivei stocate"
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "archive serial number"
|
||||
msgstr "număr serial in arhiva"
|
||||
|
||||
#: documents/models.py:217
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr "Poziția acestui document in arhiva fizica."
|
||||
|
||||
#: documents/models.py:223
|
||||
msgid "document"
|
||||
msgstr "document"
|
||||
|
||||
#: documents/models.py:224
|
||||
msgid "documents"
|
||||
msgstr "documente"
|
||||
|
||||
#: documents/models.py:311
|
||||
msgid "debug"
|
||||
msgstr "depanare"
|
||||
|
||||
#: documents/models.py:312
|
||||
msgid "information"
|
||||
msgstr "informații"
|
||||
|
||||
#: documents/models.py:313
|
||||
msgid "warning"
|
||||
msgstr "avertizare"
|
||||
|
||||
#: documents/models.py:314
|
||||
msgid "error"
|
||||
msgstr "eroare"
|
||||
|
||||
#: documents/models.py:315
|
||||
msgid "critical"
|
||||
msgstr "critic"
|
||||
|
||||
#: documents/models.py:319
|
||||
msgid "group"
|
||||
msgstr "grup"
|
||||
|
||||
#: documents/models.py:322
|
||||
msgid "message"
|
||||
msgstr "mesaj"
|
||||
|
||||
#: documents/models.py:325
|
||||
msgid "level"
|
||||
msgstr "nivel"
|
||||
|
||||
#: documents/models.py:332
|
||||
msgid "log"
|
||||
msgstr "jurnal"
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "logs"
|
||||
msgstr "jurnale"
|
||||
|
||||
#: documents/models.py:344 documents/models.py:401
|
||||
msgid "saved view"
|
||||
msgstr "vizualizare"
|
||||
|
||||
#: documents/models.py:345
|
||||
msgid "saved views"
|
||||
msgstr "vizualizări"
|
||||
|
||||
#: documents/models.py:348
|
||||
msgid "user"
|
||||
msgstr "utilizator"
|
||||
|
||||
#: documents/models.py:354
|
||||
msgid "show on dashboard"
|
||||
msgstr "afișează pe tabloul de bord"
|
||||
|
||||
#: documents/models.py:357
|
||||
msgid "show in sidebar"
|
||||
msgstr "afișează in bara laterala"
|
||||
|
||||
#: documents/models.py:361
|
||||
msgid "sort field"
|
||||
msgstr "sortează camp"
|
||||
|
||||
#: documents/models.py:367
|
||||
msgid "sort reverse"
|
||||
msgstr "sortează invers"
|
||||
|
||||
#: documents/models.py:373
|
||||
msgid "title contains"
|
||||
msgstr "titlul conține"
|
||||
|
||||
#: documents/models.py:374
|
||||
msgid "content contains"
|
||||
msgstr "conținutul conține"
|
||||
|
||||
#: documents/models.py:375
|
||||
msgid "ASN is"
|
||||
msgstr "Avizul prealabil de expediție este"
|
||||
|
||||
#: documents/models.py:376
|
||||
msgid "correspondent is"
|
||||
msgstr "corespondentul este"
|
||||
|
||||
#: documents/models.py:377
|
||||
msgid "document type is"
|
||||
msgstr "tipul documentului este"
|
||||
|
||||
#: documents/models.py:378
|
||||
msgid "is in inbox"
|
||||
msgstr "este în inbox"
|
||||
|
||||
#: documents/models.py:379
|
||||
msgid "has tag"
|
||||
msgstr "are eticheta"
|
||||
|
||||
#: documents/models.py:380
|
||||
msgid "has any tag"
|
||||
msgstr "are orice eticheta"
|
||||
|
||||
#: documents/models.py:381
|
||||
msgid "created before"
|
||||
msgstr "creat înainte de"
|
||||
|
||||
#: documents/models.py:382
|
||||
msgid "created after"
|
||||
msgstr "creat după"
|
||||
|
||||
#: documents/models.py:383
|
||||
msgid "created year is"
|
||||
msgstr "anul creării este"
|
||||
|
||||
#: documents/models.py:384
|
||||
msgid "created month is"
|
||||
msgstr "luna creării este"
|
||||
|
||||
#: documents/models.py:385
|
||||
msgid "created day is"
|
||||
msgstr "ziua creării este"
|
||||
|
||||
#: documents/models.py:386
|
||||
msgid "added before"
|
||||
msgstr "adăugat înainte de"
|
||||
|
||||
#: documents/models.py:387
|
||||
msgid "added after"
|
||||
msgstr "adăugat după"
|
||||
|
||||
#: documents/models.py:388
|
||||
msgid "modified before"
|
||||
msgstr "modificat înainte de"
|
||||
|
||||
#: documents/models.py:389
|
||||
msgid "modified after"
|
||||
msgstr "modificat după"
|
||||
|
||||
#: documents/models.py:390
|
||||
msgid "does not have tag"
|
||||
msgstr "nu are etichetă"
|
||||
|
||||
#: documents/models.py:391
|
||||
msgid "does not have ASN"
|
||||
msgstr "nu are aviz prealabil de expediție"
|
||||
|
||||
#: documents/models.py:392
|
||||
msgid "title or content contains"
|
||||
msgstr "titlul sau conținutul conține"
|
||||
|
||||
#: documents/models.py:393
|
||||
msgid "fulltext query"
|
||||
msgstr "query fulltext"
|
||||
|
||||
#: documents/models.py:394
|
||||
msgid "more like this"
|
||||
msgstr "mai multe ca aceasta"
|
||||
|
||||
#: documents/models.py:405
|
||||
msgid "rule type"
|
||||
msgstr "tip de regula"
|
||||
|
||||
#: documents/models.py:409
|
||||
msgid "value"
|
||||
msgstr "valoare"
|
||||
|
||||
#: documents/models.py:415
|
||||
msgid "filter rule"
|
||||
msgstr "regulă de filtrare"
|
||||
|
||||
#: documents/models.py:416
|
||||
msgid "filter rules"
|
||||
msgstr "reguli de filtrare"
|
||||
|
||||
#: documents/serialisers.py:53
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr "Expresie regulată invalida: %(error)s"
|
||||
|
||||
#: documents/serialisers.py:177
|
||||
msgid "Invalid color."
|
||||
msgstr "Culoare invalidă."
|
||||
|
||||
#: documents/serialisers.py:451
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Tip de fișier %(type)s nesuportat"
|
||||
|
||||
#: documents/templates/index.html:22
|
||||
msgid "Paperless-ng is loading..."
|
||||
msgstr "Paperless-ng se încarca..."
|
||||
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ng signed out"
|
||||
msgstr "Paperless-ng s-a deconectat"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:45
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr "Ați fost deconectat cu succes. La revedere!"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:46
|
||||
msgid "Sign in again"
|
||||
msgstr "Conectați-vă din nou"
|
||||
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ng sign in"
|
||||
msgstr "Conectare Paperless-ng"
|
||||
|
||||
#: documents/templates/registration/login.html:47
|
||||
msgid "Please sign in."
|
||||
msgstr "Vă rugăm conectați-vă."
|
||||
|
||||
#: documents/templates/registration/login.html:50
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr "Numele si parola nu sunt corecte. Vă rugăm incercați din nou."
|
||||
|
||||
#: documents/templates/registration/login.html:53
|
||||
msgid "Username"
|
||||
msgstr "Nume"
|
||||
|
||||
#: documents/templates/registration/login.html:54
|
||||
msgid "Password"
|
||||
msgstr "Parolă"
|
||||
|
||||
#: documents/templates/registration/login.html:59
|
||||
msgid "Sign in"
|
||||
msgstr "Conectare"
|
||||
|
||||
#: paperless/settings.py:303
|
||||
msgid "English (US)"
|
||||
msgstr "Engleză (Americană)"
|
||||
|
||||
#: paperless/settings.py:304
|
||||
msgid "English (GB)"
|
||||
msgstr "Engleză (Britanică)"
|
||||
|
||||
#: paperless/settings.py:305
|
||||
msgid "German"
|
||||
msgstr "Germană"
|
||||
|
||||
#: paperless/settings.py:306
|
||||
msgid "Dutch"
|
||||
msgstr "Olandeză"
|
||||
|
||||
#: paperless/settings.py:307
|
||||
msgid "French"
|
||||
msgstr "Franceză"
|
||||
|
||||
#: paperless/settings.py:308
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Portugheză (Brazilia)"
|
||||
|
||||
#: paperless/settings.py:309
|
||||
msgid "Portuguese"
|
||||
msgstr "Portugheză"
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Italian"
|
||||
msgstr "Italiană"
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Romanian"
|
||||
msgstr "Română"
|
||||
|
||||
#: paperless/settings.py:312
|
||||
msgid "Russian"
|
||||
msgstr "Rusă"
|
||||
|
||||
#: paperless/settings.py:313
|
||||
msgid "Spanish"
|
||||
msgstr "Spaniolă"
|
||||
|
||||
#: paperless/settings.py:314
|
||||
msgid "Polish"
|
||||
msgstr "Poloneză"
|
||||
|
||||
#: paperless/settings.py:315
|
||||
msgid "Swedish"
|
||||
msgstr "Suedeză"
|
||||
|
||||
#: paperless/urls.py:120
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr "Administrare Paperless-ng"
|
||||
|
||||
#: paperless_mail/admin.py:15
|
||||
msgid "Authentication"
|
||||
msgstr "Autentificare"
|
||||
|
||||
#: paperless_mail/admin.py:18
|
||||
msgid "Advanced settings"
|
||||
msgstr "Setări avansate"
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
msgid "Filter"
|
||||
msgstr "Filtru"
|
||||
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr "Paperless va procesa doar mail-urile care corespund TUTUROR filtrelor date mai jos."
|
||||
|
||||
#: paperless_mail/admin.py:49
|
||||
msgid "Actions"
|
||||
msgstr "Acțiuni"
|
||||
|
||||
#: paperless_mail/admin.py:51
|
||||
msgid "The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched."
|
||||
msgstr "Acțiunea aplicată tuturor email-urilor. Aceasta este realizată doar când sunt consumate documente din email. Cele fara atașamente nu vor fi procesate."
|
||||
|
||||
#: paperless_mail/admin.py:58
|
||||
msgid "Metadata"
|
||||
msgstr "Metadate"
|
||||
|
||||
#: paperless_mail/admin.py:60
|
||||
msgid "Assign metadata to documents consumed from this rule automatically. If you do not assign tags, types or correspondents here, paperless will still process all matching rules that you have defined."
|
||||
msgstr "Atribuie metadate documentelor consumate prin aceasta regula în mod automat. Chiar dacă nu sunt atribuite etichete, tipuri sau corespondenți, Paperless va procesa toate regulile definite care se potrivesc."
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
msgstr "Email Paperless"
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail account"
|
||||
msgstr "cont de email"
|
||||
|
||||
#: paperless_mail/models.py:12
|
||||
msgid "mail accounts"
|
||||
msgstr "conturi de email"
|
||||
|
||||
#: paperless_mail/models.py:19
|
||||
msgid "No encryption"
|
||||
msgstr "Fără criptare"
|
||||
|
||||
#: paperless_mail/models.py:20
|
||||
msgid "Use SSL"
|
||||
msgstr "Folosește SSL"
|
||||
|
||||
#: paperless_mail/models.py:21
|
||||
msgid "Use STARTTLS"
|
||||
msgstr "Folosește STARTTLS"
|
||||
|
||||
#: paperless_mail/models.py:29
|
||||
msgid "IMAP server"
|
||||
msgstr "server IMAP"
|
||||
|
||||
#: paperless_mail/models.py:33
|
||||
msgid "IMAP port"
|
||||
msgstr "port IMAP"
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr "De obicei este 143 pentru conexiuni necriptate și STARTTLS, sau 993 pentru conexiuni SSL."
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
msgid "IMAP security"
|
||||
msgstr "securitate IMAP"
|
||||
|
||||
#: paperless_mail/models.py:46
|
||||
msgid "username"
|
||||
msgstr "nume"
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "password"
|
||||
msgstr "parolă"
|
||||
|
||||
#: paperless_mail/models.py:54
|
||||
msgid "character set"
|
||||
msgstr "Set de caractere"
|
||||
|
||||
#: paperless_mail/models.py:57
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
msgstr "Setul de caractere folosit la comunicarea cu serverul de e-mail, cum ar fi \"UTF-8\" sau \"US-ASCII\"."
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "mail rule"
|
||||
msgstr "regulă email"
|
||||
|
||||
#: paperless_mail/models.py:69
|
||||
msgid "mail rules"
|
||||
msgstr "reguli email"
|
||||
|
||||
#: paperless_mail/models.py:75
|
||||
msgid "Only process attachments."
|
||||
msgstr "Procesează doar atașamentele."
|
||||
|
||||
#: paperless_mail/models.py:76
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr "Procesează toate fișierele, inclusiv atașamentele „inline”."
|
||||
|
||||
#: paperless_mail/models.py:86
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr "Marchează ca citit, nu procesa email-uri citite"
|
||||
|
||||
#: paperless_mail/models.py:87
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr "Marchează, nu procesa email-uri marcate"
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "Move to specified folder"
|
||||
msgstr "Mută în directorul specificat"
|
||||
|
||||
#: paperless_mail/models.py:89
|
||||
msgid "Delete"
|
||||
msgstr "Șterge"
|
||||
|
||||
#: paperless_mail/models.py:96
|
||||
msgid "Use subject as title"
|
||||
msgstr "Utilizează subiectul ca titlu"
|
||||
|
||||
#: paperless_mail/models.py:97
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr "Utilizează numele fișierului atașat ca titlu"
|
||||
|
||||
#: paperless_mail/models.py:107
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr "Nu atribui un corespondent"
|
||||
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "Use mail address"
|
||||
msgstr "Folosește adresa de email"
|
||||
|
||||
#: paperless_mail/models.py:111
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr "Folosește numele (dacă nu exista, folosește adresa de email)"
|
||||
|
||||
#: paperless_mail/models.py:113
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr "Folosește corespondentul selectat mai jos"
|
||||
|
||||
#: paperless_mail/models.py:121
|
||||
msgid "order"
|
||||
msgstr "ordonează"
|
||||
|
||||
#: paperless_mail/models.py:128
|
||||
msgid "account"
|
||||
msgstr "cont"
|
||||
|
||||
#: paperless_mail/models.py:132
|
||||
msgid "folder"
|
||||
msgstr "director"
|
||||
|
||||
#: paperless_mail/models.py:134
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr "Subdosarele trebuie separate prin puncte."
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
msgid "filter from"
|
||||
msgstr "filtrează de la"
|
||||
|
||||
#: paperless_mail/models.py:141
|
||||
msgid "filter subject"
|
||||
msgstr "filtrează subiect"
|
||||
|
||||
#: paperless_mail/models.py:144
|
||||
msgid "filter body"
|
||||
msgstr "filtrează corpul email-ului"
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "filter attachment filename"
|
||||
msgstr "filtrează numele fișierului atașat"
|
||||
|
||||
#: paperless_mail/models.py:150
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr "Consumă doar documentele care se potrivesc în întregime cu acest nume de fișier, dacă este specificat. Simbolul * ține locul oricărui șir de caractere. Majusculele nu contează."
|
||||
|
||||
#: paperless_mail/models.py:156
|
||||
msgid "maximum age"
|
||||
msgstr "vârsta maximă"
|
||||
|
||||
#: paperless_mail/models.py:158
|
||||
msgid "Specified in days."
|
||||
msgstr "Specificată in zile."
|
||||
|
||||
#: paperless_mail/models.py:161
|
||||
msgid "attachment type"
|
||||
msgstr "tip atașament"
|
||||
|
||||
#: paperless_mail/models.py:164
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr "Atașamentele \"inline\" includ și imaginile încorporate, deci această opțiune funcționează cel mai bine combinată cu un filtru pentru numele fișierului."
|
||||
|
||||
#: paperless_mail/models.py:169
|
||||
msgid "action"
|
||||
msgstr "acţiune"
|
||||
|
||||
#: paperless_mail/models.py:175
|
||||
msgid "action parameter"
|
||||
msgstr "parametru acțiune"
|
||||
|
||||
#: paperless_mail/models.py:177
|
||||
msgid "Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots."
|
||||
msgstr "Parametru adițional pentru acțiunea definită mai sus (ex. directorul în care să se realizeze o mutare). Subdosarele trebuie separate prin puncte."
|
||||
|
||||
#: paperless_mail/models.py:184
|
||||
msgid "assign title from"
|
||||
msgstr "atribuie titlu din"
|
||||
|
||||
#: paperless_mail/models.py:194
|
||||
msgid "assign this tag"
|
||||
msgstr "atribuie această etichetă"
|
||||
|
||||
#: paperless_mail/models.py:202
|
||||
msgid "assign this document type"
|
||||
msgstr "atribuie acest tip"
|
||||
|
||||
#: paperless_mail/models.py:206
|
||||
msgid "assign correspondent from"
|
||||
msgstr "atribuie corespondent din"
|
||||
|
||||
#: paperless_mail/models.py:216
|
||||
msgid "assign this correspondent"
|
||||
msgstr "atribuie acest corespondent"
|
||||
|
||||
698
src/locale/ru_RU/LC_MESSAGES/django.po
Normal file
698
src/locale/ru_RU/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,698 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ng\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-05-16 09:38+0000\n"
|
||||
"PO-Revision-Date: 2021-05-16 10:09\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Russian\n"
|
||||
"Language: ru_RU\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
|
||||
"X-Crowdin-Project: paperless-ng\n"
|
||||
"X-Crowdin-Project-ID: 434940\n"
|
||||
"X-Crowdin-Language: ru\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 54\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
msgstr "Документы"
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr "Любые слова"
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr "Все слова"
|
||||
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr "Точное соответствие"
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr "Регулярное выражение"
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr "\"Нечёткий\" режим"
|
||||
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr "Автоматически"
|
||||
|
||||
#: documents/models.py:41 documents/models.py:350 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:117
|
||||
msgid "name"
|
||||
msgstr "имя"
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "match"
|
||||
msgstr "соответствие"
|
||||
|
||||
#: documents/models.py:49
|
||||
msgid "matching algorithm"
|
||||
msgstr "алгоритм сопоставления"
|
||||
|
||||
#: documents/models.py:55
|
||||
msgid "is insensitive"
|
||||
msgstr "без учёта регистра"
|
||||
|
||||
#: documents/models.py:74 documents/models.py:120
|
||||
msgid "correspondent"
|
||||
msgstr "корреспондент"
|
||||
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr "корреспонденты"
|
||||
|
||||
#: documents/models.py:81
|
||||
msgid "color"
|
||||
msgstr "цвет"
|
||||
|
||||
#: documents/models.py:87
|
||||
msgid "is inbox tag"
|
||||
msgstr "это входящий тег"
|
||||
|
||||
#: documents/models.py:89
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr "Отметить этот тег как «Входящий»: все вновь добавленные документы будут помечены тегами «Входящие»."
|
||||
|
||||
#: documents/models.py:94
|
||||
msgid "tag"
|
||||
msgstr "тег"
|
||||
|
||||
#: documents/models.py:95 documents/models.py:151
|
||||
msgid "tags"
|
||||
msgstr "теги"
|
||||
|
||||
#: documents/models.py:101 documents/models.py:133
|
||||
msgid "document type"
|
||||
msgstr "тип документа"
|
||||
|
||||
#: documents/models.py:102
|
||||
msgid "document types"
|
||||
msgstr "типы документов"
|
||||
|
||||
#: documents/models.py:110
|
||||
msgid "Unencrypted"
|
||||
msgstr "не зашифровано"
|
||||
|
||||
#: documents/models.py:111
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "Зашифровано с помощью GNU Privacy Guard"
|
||||
|
||||
#: documents/models.py:124
|
||||
msgid "title"
|
||||
msgstr "заголовок"
|
||||
|
||||
#: documents/models.py:137
|
||||
msgid "content"
|
||||
msgstr "содержимое"
|
||||
|
||||
#: documents/models.py:139
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr "Это поле используется в основном для поиска."
|
||||
|
||||
#: documents/models.py:144
|
||||
msgid "mime type"
|
||||
msgstr "тип Mime"
|
||||
|
||||
#: documents/models.py:155
|
||||
msgid "checksum"
|
||||
msgstr "контрольная сумма"
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid "The checksum of the original document."
|
||||
msgstr "Контрольная сумма оригинального документа."
|
||||
|
||||
#: documents/models.py:163
|
||||
msgid "archive checksum"
|
||||
msgstr "контрольная сумма архива"
|
||||
|
||||
#: documents/models.py:168
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr "Контрольная сумма архивного документа."
|
||||
|
||||
#: documents/models.py:172 documents/models.py:328
|
||||
msgid "created"
|
||||
msgstr "создано"
|
||||
|
||||
#: documents/models.py:176
|
||||
msgid "modified"
|
||||
msgstr "изменено"
|
||||
|
||||
#: documents/models.py:180
|
||||
msgid "storage type"
|
||||
msgstr "тип хранилища"
|
||||
|
||||
#: documents/models.py:188
|
||||
msgid "added"
|
||||
msgstr "добавлено"
|
||||
|
||||
#: documents/models.py:192
|
||||
msgid "filename"
|
||||
msgstr "имя файла"
|
||||
|
||||
#: documents/models.py:198
|
||||
msgid "Current filename in storage"
|
||||
msgstr "Текущее имя файла в хранилище"
|
||||
|
||||
#: documents/models.py:202
|
||||
msgid "archive filename"
|
||||
msgstr "имя файла архива"
|
||||
|
||||
#: documents/models.py:208
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr "Текущее имя файла архива в хранилище"
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "archive serial number"
|
||||
msgstr "архивный номер (АН)"
|
||||
|
||||
#: documents/models.py:217
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr "Позиция этого документа в вашем физическом архиве документов."
|
||||
|
||||
#: documents/models.py:223
|
||||
msgid "document"
|
||||
msgstr "документ"
|
||||
|
||||
#: documents/models.py:224
|
||||
msgid "documents"
|
||||
msgstr "документы"
|
||||
|
||||
#: documents/models.py:311
|
||||
msgid "debug"
|
||||
msgstr "отладка"
|
||||
|
||||
#: documents/models.py:312
|
||||
msgid "information"
|
||||
msgstr "информация"
|
||||
|
||||
#: documents/models.py:313
|
||||
msgid "warning"
|
||||
msgstr "предупреждение"
|
||||
|
||||
#: documents/models.py:314
|
||||
msgid "error"
|
||||
msgstr "ошибка"
|
||||
|
||||
#: documents/models.py:315
|
||||
msgid "critical"
|
||||
msgstr "критическая"
|
||||
|
||||
#: documents/models.py:319
|
||||
msgid "group"
|
||||
msgstr "группа"
|
||||
|
||||
#: documents/models.py:322
|
||||
msgid "message"
|
||||
msgstr "сообщение"
|
||||
|
||||
#: documents/models.py:325
|
||||
msgid "level"
|
||||
msgstr "уровень"
|
||||
|
||||
#: documents/models.py:332
|
||||
msgid "log"
|
||||
msgstr "журнал"
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "logs"
|
||||
msgstr "логи"
|
||||
|
||||
#: documents/models.py:344 documents/models.py:401
|
||||
msgid "saved view"
|
||||
msgstr "сохранённое представление"
|
||||
|
||||
#: documents/models.py:345
|
||||
msgid "saved views"
|
||||
msgstr "сохраненные представления"
|
||||
|
||||
#: documents/models.py:348
|
||||
msgid "user"
|
||||
msgstr "пользователь"
|
||||
|
||||
#: documents/models.py:354
|
||||
msgid "show on dashboard"
|
||||
msgstr "показать на панели"
|
||||
|
||||
#: documents/models.py:357
|
||||
msgid "show in sidebar"
|
||||
msgstr "показать в боковой панели"
|
||||
|
||||
#: documents/models.py:361
|
||||
msgid "sort field"
|
||||
msgstr "Поле сортировки"
|
||||
|
||||
#: documents/models.py:367
|
||||
msgid "sort reverse"
|
||||
msgstr "обратная сортировка"
|
||||
|
||||
#: documents/models.py:373
|
||||
msgid "title contains"
|
||||
msgstr "заголовок содержит"
|
||||
|
||||
#: documents/models.py:374
|
||||
msgid "content contains"
|
||||
msgstr "содержимое содержит"
|
||||
|
||||
#: documents/models.py:375
|
||||
msgid "ASN is"
|
||||
msgstr "АН"
|
||||
|
||||
#: documents/models.py:376
|
||||
msgid "correspondent is"
|
||||
msgstr "корреспондент"
|
||||
|
||||
#: documents/models.py:377
|
||||
msgid "document type is"
|
||||
msgstr "тип документа"
|
||||
|
||||
#: documents/models.py:378
|
||||
msgid "is in inbox"
|
||||
msgstr "во входящих"
|
||||
|
||||
#: documents/models.py:379
|
||||
msgid "has tag"
|
||||
msgstr "есть тег"
|
||||
|
||||
#: documents/models.py:380
|
||||
msgid "has any tag"
|
||||
msgstr "есть любой тег"
|
||||
|
||||
#: documents/models.py:381
|
||||
msgid "created before"
|
||||
msgstr "создан до"
|
||||
|
||||
#: documents/models.py:382
|
||||
msgid "created after"
|
||||
msgstr "создан после"
|
||||
|
||||
#: documents/models.py:383
|
||||
msgid "created year is"
|
||||
msgstr "год создания"
|
||||
|
||||
#: documents/models.py:384
|
||||
msgid "created month is"
|
||||
msgstr "месяц создания"
|
||||
|
||||
#: documents/models.py:385
|
||||
msgid "created day is"
|
||||
msgstr "день создания"
|
||||
|
||||
#: documents/models.py:386
|
||||
msgid "added before"
|
||||
msgstr "добавлен до"
|
||||
|
||||
#: documents/models.py:387
|
||||
msgid "added after"
|
||||
msgstr "добавлен после"
|
||||
|
||||
#: documents/models.py:388
|
||||
msgid "modified before"
|
||||
msgstr "изменен до"
|
||||
|
||||
#: documents/models.py:389
|
||||
msgid "modified after"
|
||||
msgstr "изменен после"
|
||||
|
||||
#: documents/models.py:390
|
||||
msgid "does not have tag"
|
||||
msgstr "не имеет тега"
|
||||
|
||||
#: documents/models.py:391
|
||||
msgid "does not have ASN"
|
||||
msgstr "не имеет архивного номера"
|
||||
|
||||
#: documents/models.py:392
|
||||
msgid "title or content contains"
|
||||
msgstr "Название или содержимое включает"
|
||||
|
||||
#: documents/models.py:393
|
||||
msgid "fulltext query"
|
||||
msgstr "полнотекстовый запрос"
|
||||
|
||||
#: documents/models.py:394
|
||||
msgid "more like this"
|
||||
msgstr "больше похожих"
|
||||
|
||||
#: documents/models.py:405
|
||||
msgid "rule type"
|
||||
msgstr "Тип правила"
|
||||
|
||||
#: documents/models.py:409
|
||||
msgid "value"
|
||||
msgstr "значение"
|
||||
|
||||
#: documents/models.py:415
|
||||
msgid "filter rule"
|
||||
msgstr "Правило фильтрации"
|
||||
|
||||
#: documents/models.py:416
|
||||
msgid "filter rules"
|
||||
msgstr "правила фильтрации"
|
||||
|
||||
#: documents/serialisers.py:53
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr "неверное регулярное выражение: %(error)s"
|
||||
|
||||
#: documents/serialisers.py:177
|
||||
msgid "Invalid color."
|
||||
msgstr "Неверный цвет."
|
||||
|
||||
#: documents/serialisers.py:451
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Тип файла %(type)s не поддерживается"
|
||||
|
||||
#: documents/templates/index.html:22
|
||||
msgid "Paperless-ng is loading..."
|
||||
msgstr "Paperless-ng загружается..."
|
||||
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ng signed out"
|
||||
msgstr "Выполнен выход из Paperless-ng"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:45
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr "Вы успешно вышли из системы. До свидания!"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:46
|
||||
msgid "Sign in again"
|
||||
msgstr "Войти снова"
|
||||
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ng sign in"
|
||||
msgstr "Выполнен выход в Paperless-ng"
|
||||
|
||||
#: documents/templates/registration/login.html:47
|
||||
msgid "Please sign in."
|
||||
msgstr "Пожалуйста, войдите."
|
||||
|
||||
#: documents/templates/registration/login.html:50
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr "Неправильные имя пользователя или пароль! Попробуйте еще раз."
|
||||
|
||||
#: documents/templates/registration/login.html:53
|
||||
msgid "Username"
|
||||
msgstr "Имя пользователя"
|
||||
|
||||
#: documents/templates/registration/login.html:54
|
||||
msgid "Password"
|
||||
msgstr "Пароль"
|
||||
|
||||
#: documents/templates/registration/login.html:59
|
||||
msgid "Sign in"
|
||||
msgstr "Вход"
|
||||
|
||||
#: paperless/settings.py:303
|
||||
msgid "English (US)"
|
||||
msgstr "Английский (США)"
|
||||
|
||||
#: paperless/settings.py:304
|
||||
msgid "English (GB)"
|
||||
msgstr "Английский (Великобритании)"
|
||||
|
||||
#: paperless/settings.py:305
|
||||
msgid "German"
|
||||
msgstr "Немецкий"
|
||||
|
||||
#: paperless/settings.py:306
|
||||
msgid "Dutch"
|
||||
msgstr "Датский"
|
||||
|
||||
#: paperless/settings.py:307
|
||||
msgid "French"
|
||||
msgstr "Французский"
|
||||
|
||||
#: paperless/settings.py:308
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Portuguese (Brazil)"
|
||||
|
||||
#: paperless/settings.py:309
|
||||
msgid "Portuguese"
|
||||
msgstr "Португальский"
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Italian"
|
||||
msgstr "Italian"
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Romanian"
|
||||
msgstr "Romanian"
|
||||
|
||||
#: paperless/settings.py:312
|
||||
msgid "Russian"
|
||||
msgstr "Русский"
|
||||
|
||||
#: paperless/settings.py:313
|
||||
msgid "Spanish"
|
||||
msgstr "Испанский"
|
||||
|
||||
#: paperless/settings.py:314
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:315
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/urls.py:120
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr "Администрирование Paperless-ng"
|
||||
|
||||
#: paperless_mail/admin.py:15
|
||||
msgid "Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:18
|
||||
msgid "Advanced settings"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
msgid "Filter"
|
||||
msgstr "Фильтр"
|
||||
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr "Paperless будет обрабатывать только те письма, которые соответствуют всем фильтрам, указанным ниже."
|
||||
|
||||
#: paperless_mail/admin.py:49
|
||||
msgid "Actions"
|
||||
msgstr "Действия"
|
||||
|
||||
#: paperless_mail/admin.py:51
|
||||
msgid "The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched."
|
||||
msgstr "Действие применено к письму. Это действие применяется только при обработке документов из почты. Сообщения без вложений не обрабатываются."
|
||||
|
||||
#: paperless_mail/admin.py:58
|
||||
msgid "Metadata"
|
||||
msgstr "Метаданные"
|
||||
|
||||
#: paperless_mail/admin.py:60
|
||||
msgid "Assign metadata to documents consumed from this rule automatically. If you do not assign tags, types or correspondents here, paperless will still process all matching rules that you have defined."
|
||||
msgstr "Автоматически назначать метаданные документам, полученным из этого правила. Если вы не назначаете здесь теги, типы или корреспонденты, paperless все равно будут обрабатывать все соответствующие правила, которые вы определили."
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
msgstr "Безбумажная почта"
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail account"
|
||||
msgstr "почтовый ящик"
|
||||
|
||||
#: paperless_mail/models.py:12
|
||||
msgid "mail accounts"
|
||||
msgstr "Почтовые ящики"
|
||||
|
||||
#: paperless_mail/models.py:19
|
||||
msgid "No encryption"
|
||||
msgstr "Без шифрования"
|
||||
|
||||
#: paperless_mail/models.py:20
|
||||
msgid "Use SSL"
|
||||
msgstr "Использовать SSL"
|
||||
|
||||
#: paperless_mail/models.py:21
|
||||
msgid "Use STARTTLS"
|
||||
msgstr "Использовать STARTTLS"
|
||||
|
||||
#: paperless_mail/models.py:29
|
||||
msgid "IMAP server"
|
||||
msgstr "Сервер IMAP"
|
||||
|
||||
#: paperless_mail/models.py:33
|
||||
msgid "IMAP port"
|
||||
msgstr "Порт IMAP"
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr "Обычно это 143 для нешифрованных и STARTTLS соединений и 993 для SSL-соединений."
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
msgid "IMAP security"
|
||||
msgstr "Безопасность IMAP"
|
||||
|
||||
#: paperless_mail/models.py:46
|
||||
msgid "username"
|
||||
msgstr "Имя пользователя"
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "password"
|
||||
msgstr "пароль"
|
||||
|
||||
#: paperless_mail/models.py:54
|
||||
msgid "character set"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:57
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "mail rule"
|
||||
msgstr "правило почты"
|
||||
|
||||
#: paperless_mail/models.py:69
|
||||
msgid "mail rules"
|
||||
msgstr "правила почты"
|
||||
|
||||
#: paperless_mail/models.py:75
|
||||
msgid "Only process attachments."
|
||||
msgstr "Обрабатывать только вложения."
|
||||
|
||||
#: paperless_mail/models.py:76
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr "Обрабатывать все файлы, включая 'встроенные' вложения."
|
||||
|
||||
#: paperless_mail/models.py:86
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr "Пометить как прочитанное, не обрабатывать прочитанные письма"
|
||||
|
||||
#: paperless_mail/models.py:87
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr "Пометить почту, не обрабатывать помеченные письма"
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "Move to specified folder"
|
||||
msgstr "Переместить в указанную папку"
|
||||
|
||||
#: paperless_mail/models.py:89
|
||||
msgid "Delete"
|
||||
msgstr "Удалить"
|
||||
|
||||
#: paperless_mail/models.py:96
|
||||
msgid "Use subject as title"
|
||||
msgstr "Тема в качестве заголовка"
|
||||
|
||||
#: paperless_mail/models.py:97
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr "Использовать имя вложенного файла как заголовок"
|
||||
|
||||
#: paperless_mail/models.py:107
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr "Не назначать корреспондента"
|
||||
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "Use mail address"
|
||||
msgstr "Использовать email адрес"
|
||||
|
||||
#: paperless_mail/models.py:111
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr "Использовать имя (или адрес электронной почты, если недоступно)"
|
||||
|
||||
#: paperless_mail/models.py:113
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr "Использовать корреспондента, выбранного ниже"
|
||||
|
||||
#: paperless_mail/models.py:121
|
||||
msgid "order"
|
||||
msgstr "порядок"
|
||||
|
||||
#: paperless_mail/models.py:128
|
||||
msgid "account"
|
||||
msgstr "Учётная запись"
|
||||
|
||||
#: paperless_mail/models.py:132
|
||||
msgid "folder"
|
||||
msgstr "каталог"
|
||||
|
||||
#: paperless_mail/models.py:134
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
msgid "filter from"
|
||||
msgstr "фильтр по отправителю"
|
||||
|
||||
#: paperless_mail/models.py:141
|
||||
msgid "filter subject"
|
||||
msgstr "фильтр по теме"
|
||||
|
||||
#: paperless_mail/models.py:144
|
||||
msgid "filter body"
|
||||
msgstr "фильтр по тексту сообщения"
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "filter attachment filename"
|
||||
msgstr "фильтр по имени вложения"
|
||||
|
||||
#: paperless_mail/models.py:150
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr "Обрабатывать только документы, которые полностью совпадают с именем файла (если оно указано). Маски, например *.pdf или *счет*, разрешены. Без учёта регистра."
|
||||
|
||||
#: paperless_mail/models.py:156
|
||||
msgid "maximum age"
|
||||
msgstr "Максимальный возраст"
|
||||
|
||||
#: paperless_mail/models.py:158
|
||||
msgid "Specified in days."
|
||||
msgstr "Указывается в днях."
|
||||
|
||||
#: paperless_mail/models.py:161
|
||||
msgid "attachment type"
|
||||
msgstr "Тип вложения"
|
||||
|
||||
#: paperless_mail/models.py:164
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr "Вложенные вложения включая встраиваемые изображения. Лучше совместить эту опцию с фильтром по имени вложения."
|
||||
|
||||
#: paperless_mail/models.py:169
|
||||
msgid "action"
|
||||
msgstr "действие"
|
||||
|
||||
#: paperless_mail/models.py:175
|
||||
msgid "action parameter"
|
||||
msgstr "параметр действия"
|
||||
|
||||
#: paperless_mail/models.py:177
|
||||
msgid "Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:184
|
||||
msgid "assign title from"
|
||||
msgstr "назначить заголовок из"
|
||||
|
||||
#: paperless_mail/models.py:194
|
||||
msgid "assign this tag"
|
||||
msgstr "назначить этот тег"
|
||||
|
||||
#: paperless_mail/models.py:202
|
||||
msgid "assign this document type"
|
||||
msgstr "назначить этот тип документа"
|
||||
|
||||
#: paperless_mail/models.py:206
|
||||
msgid "assign correspondent from"
|
||||
msgstr "назначить корреспондента из"
|
||||
|
||||
#: paperless_mail/models.py:216
|
||||
msgid "assign this correspondent"
|
||||
msgstr "назначить этого корреспондента"
|
||||
|
||||
698
src/locale/sv_SE/LC_MESSAGES/django.po
Normal file
698
src/locale/sv_SE/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,698 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ng\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-05-16 09:38+0000\n"
|
||||
"PO-Revision-Date: 2021-07-30 18:02\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Swedish\n"
|
||||
"Language: sv_SE\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: paperless-ng\n"
|
||||
"X-Crowdin-Project-ID: 434940\n"
|
||||
"X-Crowdin-Language: sv-SE\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 54\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
msgstr "Dokument"
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr "Valfritt ord"
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr "Alla ord"
|
||||
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr "Exakt matchning"
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr "Reguljära uttryck"
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr "Ungefärligt ord"
|
||||
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr "Automatisk"
|
||||
|
||||
#: documents/models.py:41 documents/models.py:350 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:117
|
||||
msgid "name"
|
||||
msgstr "namn"
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "match"
|
||||
msgstr "träff"
|
||||
|
||||
#: documents/models.py:49
|
||||
msgid "matching algorithm"
|
||||
msgstr "matchande algoritm"
|
||||
|
||||
#: documents/models.py:55
|
||||
msgid "is insensitive"
|
||||
msgstr "är ej skiftlägeskänsligt"
|
||||
|
||||
#: documents/models.py:74 documents/models.py:120
|
||||
msgid "correspondent"
|
||||
msgstr "korrespondent"
|
||||
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr "korrespondenter"
|
||||
|
||||
#: documents/models.py:81
|
||||
msgid "color"
|
||||
msgstr "färg"
|
||||
|
||||
#: documents/models.py:87
|
||||
msgid "is inbox tag"
|
||||
msgstr "är inkorgsetikett"
|
||||
|
||||
#: documents/models.py:89
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr "Markerar denna etikett som en inkorgsetikett: Alla nyligen konsumerade dokument kommer att märkas med inkorgsetiketter."
|
||||
|
||||
#: documents/models.py:94
|
||||
msgid "tag"
|
||||
msgstr "etikett"
|
||||
|
||||
#: documents/models.py:95 documents/models.py:151
|
||||
msgid "tags"
|
||||
msgstr "etiketter"
|
||||
|
||||
#: documents/models.py:101 documents/models.py:133
|
||||
msgid "document type"
|
||||
msgstr "dokumenttyp"
|
||||
|
||||
#: documents/models.py:102
|
||||
msgid "document types"
|
||||
msgstr "dokumenttyper"
|
||||
|
||||
#: documents/models.py:110
|
||||
msgid "Unencrypted"
|
||||
msgstr "Okrypterad"
|
||||
|
||||
#: documents/models.py:111
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "Krypterad med GNU Privacy Guard"
|
||||
|
||||
#: documents/models.py:124
|
||||
msgid "title"
|
||||
msgstr "titel"
|
||||
|
||||
#: documents/models.py:137
|
||||
msgid "content"
|
||||
msgstr "innehåll"
|
||||
|
||||
#: documents/models.py:139
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr "Dokumentets obearbetade textdata. Detta fält används främst för sökning."
|
||||
|
||||
#: documents/models.py:144
|
||||
msgid "mime type"
|
||||
msgstr "MIME-typ"
|
||||
|
||||
#: documents/models.py:155
|
||||
msgid "checksum"
|
||||
msgstr "kontrollsumma"
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid "The checksum of the original document."
|
||||
msgstr "Kontrollsumman för originaldokumentet."
|
||||
|
||||
#: documents/models.py:163
|
||||
msgid "archive checksum"
|
||||
msgstr "arkivera kontrollsumma"
|
||||
|
||||
#: documents/models.py:168
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr "Kontrollsumman för det arkiverade dokumentet."
|
||||
|
||||
#: documents/models.py:172 documents/models.py:328
|
||||
msgid "created"
|
||||
msgstr "skapad"
|
||||
|
||||
#: documents/models.py:176
|
||||
msgid "modified"
|
||||
msgstr "ändrad"
|
||||
|
||||
#: documents/models.py:180
|
||||
msgid "storage type"
|
||||
msgstr "lagringstyp"
|
||||
|
||||
#: documents/models.py:188
|
||||
msgid "added"
|
||||
msgstr "tillagd"
|
||||
|
||||
#: documents/models.py:192
|
||||
msgid "filename"
|
||||
msgstr "filnamn"
|
||||
|
||||
#: documents/models.py:198
|
||||
msgid "Current filename in storage"
|
||||
msgstr "Nuvarande filnamn i lagringsutrymmet"
|
||||
|
||||
#: documents/models.py:202
|
||||
msgid "archive filename"
|
||||
msgstr "arkivfilnamn"
|
||||
|
||||
#: documents/models.py:208
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr "Nuvarande arkivfilnamn i lagringsutrymmet"
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "archive serial number"
|
||||
msgstr "serienummer (arkivering)"
|
||||
|
||||
#: documents/models.py:217
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr "Placeringen av detta dokument i ditt fysiska dokumentarkiv."
|
||||
|
||||
#: documents/models.py:223
|
||||
msgid "document"
|
||||
msgstr "dokument"
|
||||
|
||||
#: documents/models.py:224
|
||||
msgid "documents"
|
||||
msgstr "dokument"
|
||||
|
||||
#: documents/models.py:311
|
||||
msgid "debug"
|
||||
msgstr "felsök"
|
||||
|
||||
#: documents/models.py:312
|
||||
msgid "information"
|
||||
msgstr "information"
|
||||
|
||||
#: documents/models.py:313
|
||||
msgid "warning"
|
||||
msgstr "varning"
|
||||
|
||||
#: documents/models.py:314
|
||||
msgid "error"
|
||||
msgstr "fel"
|
||||
|
||||
#: documents/models.py:315
|
||||
msgid "critical"
|
||||
msgstr "kritisk"
|
||||
|
||||
#: documents/models.py:319
|
||||
msgid "group"
|
||||
msgstr "grupp"
|
||||
|
||||
#: documents/models.py:322
|
||||
msgid "message"
|
||||
msgstr "meddelande"
|
||||
|
||||
#: documents/models.py:325
|
||||
msgid "level"
|
||||
msgstr "nivå"
|
||||
|
||||
#: documents/models.py:332
|
||||
msgid "log"
|
||||
msgstr "logg"
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "logs"
|
||||
msgstr "loggar"
|
||||
|
||||
#: documents/models.py:344 documents/models.py:401
|
||||
msgid "saved view"
|
||||
msgstr "sparad vy"
|
||||
|
||||
#: documents/models.py:345
|
||||
msgid "saved views"
|
||||
msgstr "sparade vyer"
|
||||
|
||||
#: documents/models.py:348
|
||||
msgid "user"
|
||||
msgstr "användare"
|
||||
|
||||
#: documents/models.py:354
|
||||
msgid "show on dashboard"
|
||||
msgstr "visa på kontrollpanelen"
|
||||
|
||||
#: documents/models.py:357
|
||||
msgid "show in sidebar"
|
||||
msgstr "visa i sidofältet"
|
||||
|
||||
#: documents/models.py:361
|
||||
msgid "sort field"
|
||||
msgstr "sortera fält"
|
||||
|
||||
#: documents/models.py:367
|
||||
msgid "sort reverse"
|
||||
msgstr "sortera omvänt"
|
||||
|
||||
#: documents/models.py:373
|
||||
msgid "title contains"
|
||||
msgstr "titel innehåller"
|
||||
|
||||
#: documents/models.py:374
|
||||
msgid "content contains"
|
||||
msgstr "innehåll innehåller"
|
||||
|
||||
#: documents/models.py:375
|
||||
msgid "ASN is"
|
||||
msgstr "ASN är"
|
||||
|
||||
#: documents/models.py:376
|
||||
msgid "correspondent is"
|
||||
msgstr "korrespondent är"
|
||||
|
||||
#: documents/models.py:377
|
||||
msgid "document type is"
|
||||
msgstr "dokumenttyp är"
|
||||
|
||||
#: documents/models.py:378
|
||||
msgid "is in inbox"
|
||||
msgstr "är i inkorgen"
|
||||
|
||||
#: documents/models.py:379
|
||||
msgid "has tag"
|
||||
msgstr "har etikett"
|
||||
|
||||
#: documents/models.py:380
|
||||
msgid "has any tag"
|
||||
msgstr "har någon etikett"
|
||||
|
||||
#: documents/models.py:381
|
||||
msgid "created before"
|
||||
msgstr "skapad före"
|
||||
|
||||
#: documents/models.py:382
|
||||
msgid "created after"
|
||||
msgstr "skapad efter"
|
||||
|
||||
#: documents/models.py:383
|
||||
msgid "created year is"
|
||||
msgstr "skapat år är"
|
||||
|
||||
#: documents/models.py:384
|
||||
msgid "created month is"
|
||||
msgstr "skapad månad är"
|
||||
|
||||
#: documents/models.py:385
|
||||
msgid "created day is"
|
||||
msgstr "skapad dag är"
|
||||
|
||||
#: documents/models.py:386
|
||||
msgid "added before"
|
||||
msgstr "tillagd före"
|
||||
|
||||
#: documents/models.py:387
|
||||
msgid "added after"
|
||||
msgstr "tillagd efter"
|
||||
|
||||
#: documents/models.py:388
|
||||
msgid "modified before"
|
||||
msgstr "ändrad före"
|
||||
|
||||
#: documents/models.py:389
|
||||
msgid "modified after"
|
||||
msgstr "ändrad efter"
|
||||
|
||||
#: documents/models.py:390
|
||||
msgid "does not have tag"
|
||||
msgstr "har inte etikett"
|
||||
|
||||
#: documents/models.py:391
|
||||
msgid "does not have ASN"
|
||||
msgstr "har inte ASN"
|
||||
|
||||
#: documents/models.py:392
|
||||
msgid "title or content contains"
|
||||
msgstr "titel eller innehåll innehåller"
|
||||
|
||||
#: documents/models.py:393
|
||||
msgid "fulltext query"
|
||||
msgstr "fulltextfråga"
|
||||
|
||||
#: documents/models.py:394
|
||||
msgid "more like this"
|
||||
msgstr "mer som detta"
|
||||
|
||||
#: documents/models.py:405
|
||||
msgid "rule type"
|
||||
msgstr "regeltyp"
|
||||
|
||||
#: documents/models.py:409
|
||||
msgid "value"
|
||||
msgstr "värde"
|
||||
|
||||
#: documents/models.py:415
|
||||
msgid "filter rule"
|
||||
msgstr "filtrera regel"
|
||||
|
||||
#: documents/models.py:416
|
||||
msgid "filter rules"
|
||||
msgstr "filtrera regler"
|
||||
|
||||
#: documents/serialisers.py:53
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr "Ogiltigt reguljärt uttryck: %(error)s"
|
||||
|
||||
#: documents/serialisers.py:177
|
||||
msgid "Invalid color."
|
||||
msgstr "Ogiltig färg."
|
||||
|
||||
#: documents/serialisers.py:451
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Filtypen %(type)s stöds inte"
|
||||
|
||||
#: documents/templates/index.html:22
|
||||
msgid "Paperless-ng is loading..."
|
||||
msgstr "Paperless-ng laddas..."
|
||||
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ng signed out"
|
||||
msgstr "Paperless-ng utloggad"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:45
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr "Du är nu utloggad. Hejdå!"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:46
|
||||
msgid "Sign in again"
|
||||
msgstr "Logga in igen"
|
||||
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ng sign in"
|
||||
msgstr "Paperless-ng inloggning"
|
||||
|
||||
#: documents/templates/registration/login.html:47
|
||||
msgid "Please sign in."
|
||||
msgstr "Vänligen logga in."
|
||||
|
||||
#: documents/templates/registration/login.html:50
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr "Ditt användarnamn och lösenord matchade inte. Vänligen försök igen."
|
||||
|
||||
#: documents/templates/registration/login.html:53
|
||||
msgid "Username"
|
||||
msgstr "Användarnamn"
|
||||
|
||||
#: documents/templates/registration/login.html:54
|
||||
msgid "Password"
|
||||
msgstr "Lösenord"
|
||||
|
||||
#: documents/templates/registration/login.html:59
|
||||
msgid "Sign in"
|
||||
msgstr "Logga in"
|
||||
|
||||
#: paperless/settings.py:303
|
||||
msgid "English (US)"
|
||||
msgstr "Engelska (USA)"
|
||||
|
||||
#: paperless/settings.py:304
|
||||
msgid "English (GB)"
|
||||
msgstr "Engelska (GB)"
|
||||
|
||||
#: paperless/settings.py:305
|
||||
msgid "German"
|
||||
msgstr "Tyska"
|
||||
|
||||
#: paperless/settings.py:306
|
||||
msgid "Dutch"
|
||||
msgstr "Holländska"
|
||||
|
||||
#: paperless/settings.py:307
|
||||
msgid "French"
|
||||
msgstr "Franska"
|
||||
|
||||
#: paperless/settings.py:308
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Portugisiska (Brasilien)"
|
||||
|
||||
#: paperless/settings.py:309
|
||||
msgid "Portuguese"
|
||||
msgstr "Portugisiska"
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Italian"
|
||||
msgstr "Italienska"
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Romanian"
|
||||
msgstr "Rumänska"
|
||||
|
||||
#: paperless/settings.py:312
|
||||
msgid "Russian"
|
||||
msgstr "Ryska"
|
||||
|
||||
#: paperless/settings.py:313
|
||||
msgid "Spanish"
|
||||
msgstr "Spanska"
|
||||
|
||||
#: paperless/settings.py:314
|
||||
msgid "Polish"
|
||||
msgstr "Polska"
|
||||
|
||||
#: paperless/settings.py:315
|
||||
msgid "Swedish"
|
||||
msgstr "Svenska"
|
||||
|
||||
#: paperless/urls.py:120
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr "Paperless-ng administration"
|
||||
|
||||
#: paperless_mail/admin.py:15
|
||||
msgid "Authentication"
|
||||
msgstr "Autentisering"
|
||||
|
||||
#: paperless_mail/admin.py:18
|
||||
msgid "Advanced settings"
|
||||
msgstr "Avancerade inställningar"
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
msgid "Filter"
|
||||
msgstr "Filter"
|
||||
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr "Paperless kommer endast att behandla e-postmeddelanden som matchar ALLA filter som anges nedan."
|
||||
|
||||
#: paperless_mail/admin.py:49
|
||||
msgid "Actions"
|
||||
msgstr "Åtgärder"
|
||||
|
||||
#: paperless_mail/admin.py:51
|
||||
msgid "The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched."
|
||||
msgstr "Åtgärden tillämpas på e-postmeddelandet. Denna åtgärd utförs endast när dokument konsumerades från e-postmeddelandet. E-post utan bilagor kommer att förbli helt orörda."
|
||||
|
||||
#: paperless_mail/admin.py:58
|
||||
msgid "Metadata"
|
||||
msgstr "Metadata"
|
||||
|
||||
#: paperless_mail/admin.py:60
|
||||
msgid "Assign metadata to documents consumed from this rule automatically. If you do not assign tags, types or correspondents here, paperless will still process all matching rules that you have defined."
|
||||
msgstr "Tilldela metadata till dokument som konsumeras från denna regel automatiskt. Om du inte tilldelar etiketter, typer eller korrespondenter här kommer paperless fortfarande att behandla alla matchande regler som du har definierat."
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
msgstr "Paperless e-post"
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail account"
|
||||
msgstr "e-postkonto"
|
||||
|
||||
#: paperless_mail/models.py:12
|
||||
msgid "mail accounts"
|
||||
msgstr "e-postkonton"
|
||||
|
||||
#: paperless_mail/models.py:19
|
||||
msgid "No encryption"
|
||||
msgstr "Ingen kryptering"
|
||||
|
||||
#: paperless_mail/models.py:20
|
||||
msgid "Use SSL"
|
||||
msgstr "Använd SSL"
|
||||
|
||||
#: paperless_mail/models.py:21
|
||||
msgid "Use STARTTLS"
|
||||
msgstr "Använd STARTTLS"
|
||||
|
||||
#: paperless_mail/models.py:29
|
||||
msgid "IMAP server"
|
||||
msgstr "IMAP-server"
|
||||
|
||||
#: paperless_mail/models.py:33
|
||||
msgid "IMAP port"
|
||||
msgstr "IMAP-port"
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr "Detta är vanligtvis 143 för okrypterade och STARTTLS-anslutningar, och 993 för SSL-anslutningar."
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
msgid "IMAP security"
|
||||
msgstr "IMAP-säkerhet"
|
||||
|
||||
#: paperless_mail/models.py:46
|
||||
msgid "username"
|
||||
msgstr "användarnamn"
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "password"
|
||||
msgstr "lösenord"
|
||||
|
||||
#: paperless_mail/models.py:54
|
||||
msgid "character set"
|
||||
msgstr "Teckenuppsättning"
|
||||
|
||||
#: paperless_mail/models.py:57
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
msgstr "Teckenuppsättningen som är tänkt att användas vid kommunikation med mailservern, exempelvis ’UTF-8’ eller ’US-ASCII’."
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "mail rule"
|
||||
msgstr "e-postregel"
|
||||
|
||||
#: paperless_mail/models.py:69
|
||||
msgid "mail rules"
|
||||
msgstr "e-postregler"
|
||||
|
||||
#: paperless_mail/models.py:75
|
||||
msgid "Only process attachments."
|
||||
msgstr "Behandla endast bilagor."
|
||||
|
||||
#: paperless_mail/models.py:76
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr "Behandla alla filer, inklusive infogade bilagor."
|
||||
|
||||
#: paperless_mail/models.py:86
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr "Markera som läst, bearbeta inte lästa meddelanden"
|
||||
|
||||
#: paperless_mail/models.py:87
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr "Flagga meddelandet, bearbeta inte flaggade meddelanden"
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "Move to specified folder"
|
||||
msgstr "Flytta till angiven mapp"
|
||||
|
||||
#: paperless_mail/models.py:89
|
||||
msgid "Delete"
|
||||
msgstr "Radera"
|
||||
|
||||
#: paperless_mail/models.py:96
|
||||
msgid "Use subject as title"
|
||||
msgstr "Använd ämne som titel"
|
||||
|
||||
#: paperless_mail/models.py:97
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr "Använd bilagans filnamn som titel"
|
||||
|
||||
#: paperless_mail/models.py:107
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr "Tilldela inte en korrespondent"
|
||||
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "Use mail address"
|
||||
msgstr "Använd e-postadress"
|
||||
|
||||
#: paperless_mail/models.py:111
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr "Använd namn (eller e-postadress om inte tillgängligt)"
|
||||
|
||||
#: paperless_mail/models.py:113
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr "Använd korrespondent som valts nedan"
|
||||
|
||||
#: paperless_mail/models.py:121
|
||||
msgid "order"
|
||||
msgstr "ordning"
|
||||
|
||||
#: paperless_mail/models.py:128
|
||||
msgid "account"
|
||||
msgstr "konto"
|
||||
|
||||
#: paperless_mail/models.py:132
|
||||
msgid "folder"
|
||||
msgstr "mapp"
|
||||
|
||||
#: paperless_mail/models.py:134
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr "Undermappar måste vara separerade med punkter."
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
msgid "filter from"
|
||||
msgstr "filtrera från"
|
||||
|
||||
#: paperless_mail/models.py:141
|
||||
msgid "filter subject"
|
||||
msgstr "filtrera ämne"
|
||||
|
||||
#: paperless_mail/models.py:144
|
||||
msgid "filter body"
|
||||
msgstr "filtrera kropp"
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "filter attachment filename"
|
||||
msgstr "filtrera filnamn för bilaga"
|
||||
|
||||
#: paperless_mail/models.py:150
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr "Konsumera endast dokument som matchar exakt detta filnamn, om det är angivet. Jokertecken som *.pdf eller *faktura* är tillåtna. Ej skiftlägeskänsligt."
|
||||
|
||||
#: paperless_mail/models.py:156
|
||||
msgid "maximum age"
|
||||
msgstr "högsta ålder"
|
||||
|
||||
#: paperless_mail/models.py:158
|
||||
msgid "Specified in days."
|
||||
msgstr "Anges i dagar."
|
||||
|
||||
#: paperless_mail/models.py:161
|
||||
msgid "attachment type"
|
||||
msgstr "typ av bilaga"
|
||||
|
||||
#: paperless_mail/models.py:164
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr "Infogade bilagor inkluderar inbäddade bilder, så det är bäst att kombinera detta alternativ med ett filnamnsfilter."
|
||||
|
||||
#: paperless_mail/models.py:169
|
||||
msgid "action"
|
||||
msgstr "åtgärd"
|
||||
|
||||
#: paperless_mail/models.py:175
|
||||
msgid "action parameter"
|
||||
msgstr "åtgärdsparameter"
|
||||
|
||||
#: paperless_mail/models.py:177
|
||||
msgid "Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots."
|
||||
msgstr "Ytterligare parametrar för åtgärden som valts ovan, d.v.s. målmappen för åtgärden \"flytta till angiven mapp\". Undermappar måste vara separerade med punkter."
|
||||
|
||||
#: paperless_mail/models.py:184
|
||||
msgid "assign title from"
|
||||
msgstr "tilldela titel från"
|
||||
|
||||
#: paperless_mail/models.py:194
|
||||
msgid "assign this tag"
|
||||
msgstr "tilldela denna etikett"
|
||||
|
||||
#: paperless_mail/models.py:202
|
||||
msgid "assign this document type"
|
||||
msgstr "tilldela den här dokumenttypen"
|
||||
|
||||
#: paperless_mail/models.py:206
|
||||
msgid "assign correspondent from"
|
||||
msgstr "tilldela korrespondent från"
|
||||
|
||||
#: paperless_mail/models.py:216
|
||||
msgid "assign this correspondent"
|
||||
msgstr "tilldela denna korrespondent"
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Project-Id-Version: paperless-ng\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-01-10 21:41+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"POT-Creation-Date: 2021-02-28 12:40+0100\n"
|
||||
"PO-Revision-Date: 2021-03-06 21:39\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Thai\n"
|
||||
"Language: th_TH\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Crowdin-Project: paperless-ng\n"
|
||||
"X-Crowdin-Project-ID: 434940\n"
|
||||
"X-Crowdin-Language: th\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 54\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
@@ -45,7 +45,7 @@ msgstr ""
|
||||
msgid "Automatic"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:41 documents/models.py:354 paperless_mail/models.py:25
|
||||
#: documents/models.py:41 documents/models.py:350 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "name"
|
||||
msgstr ""
|
||||
@@ -62,283 +62,301 @@ msgstr ""
|
||||
msgid "is insensitive"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:80 documents/models.py:140
|
||||
#: documents/models.py:74 documents/models.py:120
|
||||
msgid "correspondent"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:81
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:103
|
||||
#: documents/models.py:81
|
||||
msgid "color"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:107
|
||||
#: documents/models.py:87
|
||||
msgid "is inbox tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:109
|
||||
msgid ""
|
||||
"Marks this tag as an inbox tag: All newly consumed documents will be tagged "
|
||||
"with inbox tags."
|
||||
#: documents/models.py:89
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:114
|
||||
#: documents/models.py:94
|
||||
msgid "tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:115 documents/models.py:171
|
||||
#: documents/models.py:95 documents/models.py:151
|
||||
msgid "tags"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:121 documents/models.py:153
|
||||
#: documents/models.py:101 documents/models.py:133
|
||||
msgid "document type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:122
|
||||
#: documents/models.py:102
|
||||
msgid "document types"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:130
|
||||
#: documents/models.py:110
|
||||
msgid "Unencrypted"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:131
|
||||
#: documents/models.py:111
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:144
|
||||
#: documents/models.py:124
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:157
|
||||
#: documents/models.py:137
|
||||
msgid "content"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid ""
|
||||
"The raw, text-only data of the document. This field is primarily used for "
|
||||
"searching."
|
||||
#: documents/models.py:139
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:164
|
||||
#: documents/models.py:144
|
||||
msgid "mime type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:175
|
||||
#: documents/models.py:155
|
||||
msgid "checksum"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:179
|
||||
#: documents/models.py:159
|
||||
msgid "The checksum of the original document."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:183
|
||||
#: documents/models.py:163
|
||||
msgid "archive checksum"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:188
|
||||
#: documents/models.py:168
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:192 documents/models.py:332
|
||||
#: documents/models.py:172 documents/models.py:328
|
||||
msgid "created"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:196
|
||||
#: documents/models.py:176
|
||||
msgid "modified"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:200
|
||||
#: documents/models.py:180
|
||||
msgid "storage type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:208
|
||||
#: documents/models.py:188
|
||||
msgid "added"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:212
|
||||
#: documents/models.py:192
|
||||
msgid "filename"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:217
|
||||
#: documents/models.py:198
|
||||
msgid "Current filename in storage"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:221
|
||||
#: documents/models.py:202
|
||||
msgid "archive filename"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:208
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "archive serial number"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:226
|
||||
#: documents/models.py:217
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:232
|
||||
#: documents/models.py:223
|
||||
msgid "document"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:233
|
||||
#: documents/models.py:224
|
||||
msgid "documents"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:315
|
||||
#: documents/models.py:311
|
||||
msgid "debug"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:316
|
||||
#: documents/models.py:312
|
||||
msgid "information"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:317
|
||||
#: documents/models.py:313
|
||||
msgid "warning"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:318
|
||||
#: documents/models.py:314
|
||||
msgid "error"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:319
|
||||
#: documents/models.py:315
|
||||
msgid "critical"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:323
|
||||
#: documents/models.py:319
|
||||
msgid "group"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:326
|
||||
#: documents/models.py:322
|
||||
msgid "message"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:329
|
||||
#: documents/models.py:325
|
||||
msgid "level"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:336
|
||||
#: documents/models.py:332
|
||||
msgid "log"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:337
|
||||
#: documents/models.py:333
|
||||
msgid "logs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:348 documents/models.py:398
|
||||
#: documents/models.py:344 documents/models.py:394
|
||||
msgid "saved view"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:349
|
||||
#: documents/models.py:345
|
||||
msgid "saved views"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:352
|
||||
#: documents/models.py:348
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:358
|
||||
#: documents/models.py:354
|
||||
msgid "show on dashboard"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:361
|
||||
#: documents/models.py:357
|
||||
msgid "show in sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:365
|
||||
#: documents/models.py:361
|
||||
msgid "sort field"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:368
|
||||
#: documents/models.py:364
|
||||
msgid "sort reverse"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:374
|
||||
#: documents/models.py:370
|
||||
msgid "title contains"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:375
|
||||
#: documents/models.py:371
|
||||
msgid "content contains"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:376
|
||||
#: documents/models.py:372
|
||||
msgid "ASN is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:377
|
||||
#: documents/models.py:373
|
||||
msgid "correspondent is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:378
|
||||
#: documents/models.py:374
|
||||
msgid "document type is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:379
|
||||
#: documents/models.py:375
|
||||
msgid "is in inbox"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:380
|
||||
#: documents/models.py:376
|
||||
msgid "has tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:381
|
||||
#: documents/models.py:377
|
||||
msgid "has any tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:382
|
||||
#: documents/models.py:378
|
||||
msgid "created before"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:383
|
||||
#: documents/models.py:379
|
||||
msgid "created after"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:384
|
||||
#: documents/models.py:380
|
||||
msgid "created year is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:385
|
||||
#: documents/models.py:381
|
||||
msgid "created month is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:386
|
||||
#: documents/models.py:382
|
||||
msgid "created day is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:387
|
||||
#: documents/models.py:383
|
||||
msgid "added before"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:388
|
||||
#: documents/models.py:384
|
||||
msgid "added after"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:389
|
||||
#: documents/models.py:385
|
||||
msgid "modified before"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:390
|
||||
#: documents/models.py:386
|
||||
msgid "modified after"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:391
|
||||
#: documents/models.py:387
|
||||
msgid "does not have tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:402
|
||||
#: documents/models.py:398
|
||||
msgid "rule type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:406
|
||||
#: documents/models.py:402
|
||||
msgid "value"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:412
|
||||
#: documents/models.py:408
|
||||
msgid "filter rule"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:413
|
||||
#: documents/models.py:409
|
||||
msgid "filter rules"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/index.html:20
|
||||
#: documents/serialisers.py:53
|
||||
#, python-format
|
||||
msgid "Invalid regular expresssion: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:177
|
||||
msgid "Invalid color."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:451
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/index.html:21
|
||||
msgid "Paperless-ng is loading..."
|
||||
msgstr ""
|
||||
|
||||
@@ -378,23 +396,39 @@ msgstr ""
|
||||
msgid "Sign in"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:268
|
||||
msgid "English"
|
||||
#: paperless/settings.py:297
|
||||
msgid "English (US)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:269
|
||||
#: paperless/settings.py:298
|
||||
msgid "English (GB)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:299
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:270
|
||||
#: paperless/settings.py:300
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:271
|
||||
#: paperless/settings.py:301
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/urls.py:108
|
||||
#: paperless/settings.py:302
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:303
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:304
|
||||
msgid "Romanian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/urls.py:118
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr ""
|
||||
|
||||
@@ -403,8 +437,7 @@ msgid "Filter"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:27
|
||||
msgid ""
|
||||
"Paperless will only process mails that match ALL of the filters given below."
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
@@ -412,10 +445,7 @@ msgid "Actions"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid ""
|
||||
"The action applied to the mail. This action is only performed when documents "
|
||||
"were consumed from the mail. Mails without attachments will remain entirely "
|
||||
"untouched."
|
||||
msgid "The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:46
|
||||
@@ -423,10 +453,7 @@ msgid "Metadata"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:48
|
||||
msgid ""
|
||||
"Assign metadata to documents consumed from this rule automatically. If you "
|
||||
"do not assign tags, types or correspondents here, paperless will still "
|
||||
"process all matching rules that you have defined."
|
||||
msgid "Assign metadata to documents consumed from this rule automatically. If you do not assign tags, types or correspondents here, paperless will still process all matching rules that you have defined."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
@@ -462,9 +489,7 @@ msgid "IMAP port"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid ""
|
||||
"This is usually 143 for unencrypted and STARTTLS connections, and 993 for "
|
||||
"SSL connections."
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
@@ -564,9 +589,7 @@ msgid "filter attachment filename"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:140
|
||||
msgid ""
|
||||
"Only consume documents which entirely match this filename if specified. "
|
||||
"Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:146
|
||||
@@ -582,9 +605,7 @@ msgid "attachment type"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:154
|
||||
msgid ""
|
||||
"Inline attachments include embedded images, so it's best to combine this "
|
||||
"option with a filename filter."
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:159
|
||||
@@ -596,9 +617,7 @@ msgid "action parameter"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:167
|
||||
msgid ""
|
||||
"Additional parameter for the action selected above, i.e., the target folder "
|
||||
"of the move to folder action."
|
||||
msgid "Additional parameter for the action selected above, i.e., the target folder of the move to folder action."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:173
|
||||
@@ -620,3 +639,4 @@ msgstr ""
|
||||
#: paperless_mail/models.py:205
|
||||
msgid "assign this correspondent"
|
||||
msgstr ""
|
||||
|
||||
698
src/locale/xh_ZA/LC_MESSAGES/django.po
Normal file
698
src/locale/xh_ZA/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,698 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ng\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-05-16 09:38+0000\n"
|
||||
"PO-Revision-Date: 2021-05-16 10:09\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Xhosa\n"
|
||||
"Language: xh_ZA\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: paperless-ng\n"
|
||||
"X-Crowdin-Project-ID: 434940\n"
|
||||
"X-Crowdin-Language: xh\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 54\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
msgstr "crwdns2528:0crwdne2528:0"
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr "crwdns2530:0crwdne2530:0"
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr "crwdns2532:0crwdne2532:0"
|
||||
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr "crwdns2534:0crwdne2534:0"
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr "crwdns2536:0crwdne2536:0"
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr "crwdns2538:0crwdne2538:0"
|
||||
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr "crwdns2540:0crwdne2540:0"
|
||||
|
||||
#: documents/models.py:41 documents/models.py:350 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:117
|
||||
msgid "name"
|
||||
msgstr "crwdns2542:0crwdne2542:0"
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "match"
|
||||
msgstr "crwdns2544:0crwdne2544:0"
|
||||
|
||||
#: documents/models.py:49
|
||||
msgid "matching algorithm"
|
||||
msgstr "crwdns2546:0crwdne2546:0"
|
||||
|
||||
#: documents/models.py:55
|
||||
msgid "is insensitive"
|
||||
msgstr "crwdns2548:0crwdne2548:0"
|
||||
|
||||
#: documents/models.py:74 documents/models.py:120
|
||||
msgid "correspondent"
|
||||
msgstr "crwdns2550:0crwdne2550:0"
|
||||
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr "crwdns2552:0crwdne2552:0"
|
||||
|
||||
#: documents/models.py:81
|
||||
msgid "color"
|
||||
msgstr "crwdns2554:0crwdne2554:0"
|
||||
|
||||
#: documents/models.py:87
|
||||
msgid "is inbox tag"
|
||||
msgstr "crwdns2556:0crwdne2556:0"
|
||||
|
||||
#: documents/models.py:89
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr "crwdns2558:0crwdne2558:0"
|
||||
|
||||
#: documents/models.py:94
|
||||
msgid "tag"
|
||||
msgstr "crwdns2560:0crwdne2560:0"
|
||||
|
||||
#: documents/models.py:95 documents/models.py:151
|
||||
msgid "tags"
|
||||
msgstr "crwdns2562:0crwdne2562:0"
|
||||
|
||||
#: documents/models.py:101 documents/models.py:133
|
||||
msgid "document type"
|
||||
msgstr "crwdns2564:0crwdne2564:0"
|
||||
|
||||
#: documents/models.py:102
|
||||
msgid "document types"
|
||||
msgstr "crwdns2566:0crwdne2566:0"
|
||||
|
||||
#: documents/models.py:110
|
||||
msgid "Unencrypted"
|
||||
msgstr "crwdns2568:0crwdne2568:0"
|
||||
|
||||
#: documents/models.py:111
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "crwdns2570:0crwdne2570:0"
|
||||
|
||||
#: documents/models.py:124
|
||||
msgid "title"
|
||||
msgstr "crwdns2572:0crwdne2572:0"
|
||||
|
||||
#: documents/models.py:137
|
||||
msgid "content"
|
||||
msgstr "crwdns2574:0crwdne2574:0"
|
||||
|
||||
#: documents/models.py:139
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr "crwdns2576:0crwdne2576:0"
|
||||
|
||||
#: documents/models.py:144
|
||||
msgid "mime type"
|
||||
msgstr "crwdns2578:0crwdne2578:0"
|
||||
|
||||
#: documents/models.py:155
|
||||
msgid "checksum"
|
||||
msgstr "crwdns2580:0crwdne2580:0"
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid "The checksum of the original document."
|
||||
msgstr "crwdns2582:0crwdne2582:0"
|
||||
|
||||
#: documents/models.py:163
|
||||
msgid "archive checksum"
|
||||
msgstr "crwdns2584:0crwdne2584:0"
|
||||
|
||||
#: documents/models.py:168
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr "crwdns2586:0crwdne2586:0"
|
||||
|
||||
#: documents/models.py:172 documents/models.py:328
|
||||
msgid "created"
|
||||
msgstr "crwdns2588:0crwdne2588:0"
|
||||
|
||||
#: documents/models.py:176
|
||||
msgid "modified"
|
||||
msgstr "crwdns2590:0crwdne2590:0"
|
||||
|
||||
#: documents/models.py:180
|
||||
msgid "storage type"
|
||||
msgstr "crwdns2592:0crwdne2592:0"
|
||||
|
||||
#: documents/models.py:188
|
||||
msgid "added"
|
||||
msgstr "crwdns2594:0crwdne2594:0"
|
||||
|
||||
#: documents/models.py:192
|
||||
msgid "filename"
|
||||
msgstr "crwdns2596:0crwdne2596:0"
|
||||
|
||||
#: documents/models.py:198
|
||||
msgid "Current filename in storage"
|
||||
msgstr "crwdns2598:0crwdne2598:0"
|
||||
|
||||
#: documents/models.py:202
|
||||
msgid "archive filename"
|
||||
msgstr "crwdns2600:0crwdne2600:0"
|
||||
|
||||
#: documents/models.py:208
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr "crwdns2602:0crwdne2602:0"
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "archive serial number"
|
||||
msgstr "crwdns2604:0crwdne2604:0"
|
||||
|
||||
#: documents/models.py:217
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr "crwdns2606:0crwdne2606:0"
|
||||
|
||||
#: documents/models.py:223
|
||||
msgid "document"
|
||||
msgstr "crwdns2608:0crwdne2608:0"
|
||||
|
||||
#: documents/models.py:224
|
||||
msgid "documents"
|
||||
msgstr "crwdns2610:0crwdne2610:0"
|
||||
|
||||
#: documents/models.py:311
|
||||
msgid "debug"
|
||||
msgstr "crwdns2612:0crwdne2612:0"
|
||||
|
||||
#: documents/models.py:312
|
||||
msgid "information"
|
||||
msgstr "crwdns2614:0crwdne2614:0"
|
||||
|
||||
#: documents/models.py:313
|
||||
msgid "warning"
|
||||
msgstr "crwdns2616:0crwdne2616:0"
|
||||
|
||||
#: documents/models.py:314
|
||||
msgid "error"
|
||||
msgstr "crwdns2618:0crwdne2618:0"
|
||||
|
||||
#: documents/models.py:315
|
||||
msgid "critical"
|
||||
msgstr "crwdns2620:0crwdne2620:0"
|
||||
|
||||
#: documents/models.py:319
|
||||
msgid "group"
|
||||
msgstr "crwdns2622:0crwdne2622:0"
|
||||
|
||||
#: documents/models.py:322
|
||||
msgid "message"
|
||||
msgstr "crwdns2624:0crwdne2624:0"
|
||||
|
||||
#: documents/models.py:325
|
||||
msgid "level"
|
||||
msgstr "crwdns2626:0crwdne2626:0"
|
||||
|
||||
#: documents/models.py:332
|
||||
msgid "log"
|
||||
msgstr "crwdns2628:0crwdne2628:0"
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "logs"
|
||||
msgstr "crwdns2630:0crwdne2630:0"
|
||||
|
||||
#: documents/models.py:344 documents/models.py:401
|
||||
msgid "saved view"
|
||||
msgstr "crwdns2632:0crwdne2632:0"
|
||||
|
||||
#: documents/models.py:345
|
||||
msgid "saved views"
|
||||
msgstr "crwdns2634:0crwdne2634:0"
|
||||
|
||||
#: documents/models.py:348
|
||||
msgid "user"
|
||||
msgstr "crwdns2636:0crwdne2636:0"
|
||||
|
||||
#: documents/models.py:354
|
||||
msgid "show on dashboard"
|
||||
msgstr "crwdns2638:0crwdne2638:0"
|
||||
|
||||
#: documents/models.py:357
|
||||
msgid "show in sidebar"
|
||||
msgstr "crwdns2640:0crwdne2640:0"
|
||||
|
||||
#: documents/models.py:361
|
||||
msgid "sort field"
|
||||
msgstr "crwdns2642:0crwdne2642:0"
|
||||
|
||||
#: documents/models.py:367
|
||||
msgid "sort reverse"
|
||||
msgstr "crwdns2644:0crwdne2644:0"
|
||||
|
||||
#: documents/models.py:373
|
||||
msgid "title contains"
|
||||
msgstr "crwdns2646:0crwdne2646:0"
|
||||
|
||||
#: documents/models.py:374
|
||||
msgid "content contains"
|
||||
msgstr "crwdns2648:0crwdne2648:0"
|
||||
|
||||
#: documents/models.py:375
|
||||
msgid "ASN is"
|
||||
msgstr "crwdns2650:0crwdne2650:0"
|
||||
|
||||
#: documents/models.py:376
|
||||
msgid "correspondent is"
|
||||
msgstr "crwdns2652:0crwdne2652:0"
|
||||
|
||||
#: documents/models.py:377
|
||||
msgid "document type is"
|
||||
msgstr "crwdns2654:0crwdne2654:0"
|
||||
|
||||
#: documents/models.py:378
|
||||
msgid "is in inbox"
|
||||
msgstr "crwdns2656:0crwdne2656:0"
|
||||
|
||||
#: documents/models.py:379
|
||||
msgid "has tag"
|
||||
msgstr "crwdns2658:0crwdne2658:0"
|
||||
|
||||
#: documents/models.py:380
|
||||
msgid "has any tag"
|
||||
msgstr "crwdns2660:0crwdne2660:0"
|
||||
|
||||
#: documents/models.py:381
|
||||
msgid "created before"
|
||||
msgstr "crwdns2662:0crwdne2662:0"
|
||||
|
||||
#: documents/models.py:382
|
||||
msgid "created after"
|
||||
msgstr "crwdns2664:0crwdne2664:0"
|
||||
|
||||
#: documents/models.py:383
|
||||
msgid "created year is"
|
||||
msgstr "crwdns2666:0crwdne2666:0"
|
||||
|
||||
#: documents/models.py:384
|
||||
msgid "created month is"
|
||||
msgstr "crwdns2668:0crwdne2668:0"
|
||||
|
||||
#: documents/models.py:385
|
||||
msgid "created day is"
|
||||
msgstr "crwdns2670:0crwdne2670:0"
|
||||
|
||||
#: documents/models.py:386
|
||||
msgid "added before"
|
||||
msgstr "crwdns2672:0crwdne2672:0"
|
||||
|
||||
#: documents/models.py:387
|
||||
msgid "added after"
|
||||
msgstr "crwdns2674:0crwdne2674:0"
|
||||
|
||||
#: documents/models.py:388
|
||||
msgid "modified before"
|
||||
msgstr "crwdns2676:0crwdne2676:0"
|
||||
|
||||
#: documents/models.py:389
|
||||
msgid "modified after"
|
||||
msgstr "crwdns2678:0crwdne2678:0"
|
||||
|
||||
#: documents/models.py:390
|
||||
msgid "does not have tag"
|
||||
msgstr "crwdns2680:0crwdne2680:0"
|
||||
|
||||
#: documents/models.py:391
|
||||
msgid "does not have ASN"
|
||||
msgstr "crwdns3408:0crwdne3408:0"
|
||||
|
||||
#: documents/models.py:392
|
||||
msgid "title or content contains"
|
||||
msgstr "crwdns3410:0crwdne3410:0"
|
||||
|
||||
#: documents/models.py:393
|
||||
msgid "fulltext query"
|
||||
msgstr "crwdns3438:0crwdne3438:0"
|
||||
|
||||
#: documents/models.py:394
|
||||
msgid "more like this"
|
||||
msgstr "crwdns3440:0crwdne3440:0"
|
||||
|
||||
#: documents/models.py:405
|
||||
msgid "rule type"
|
||||
msgstr "crwdns2682:0crwdne2682:0"
|
||||
|
||||
#: documents/models.py:409
|
||||
msgid "value"
|
||||
msgstr "crwdns2684:0crwdne2684:0"
|
||||
|
||||
#: documents/models.py:415
|
||||
msgid "filter rule"
|
||||
msgstr "crwdns2686:0crwdne2686:0"
|
||||
|
||||
#: documents/models.py:416
|
||||
msgid "filter rules"
|
||||
msgstr "crwdns2688:0crwdne2688:0"
|
||||
|
||||
#: documents/serialisers.py:53
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr "crwdns3412:0%(error)scrwdne3412:0"
|
||||
|
||||
#: documents/serialisers.py:177
|
||||
msgid "Invalid color."
|
||||
msgstr "crwdns2692:0crwdne2692:0"
|
||||
|
||||
#: documents/serialisers.py:451
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "crwdns2694:0%(type)scrwdne2694:0"
|
||||
|
||||
#: documents/templates/index.html:22
|
||||
msgid "Paperless-ng is loading..."
|
||||
msgstr "crwdns2696:0crwdne2696:0"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ng signed out"
|
||||
msgstr "crwdns2698:0crwdne2698:0"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:45
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr "crwdns2700:0crwdne2700:0"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:46
|
||||
msgid "Sign in again"
|
||||
msgstr "crwdns2702:0crwdne2702:0"
|
||||
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ng sign in"
|
||||
msgstr "crwdns2704:0crwdne2704:0"
|
||||
|
||||
#: documents/templates/registration/login.html:47
|
||||
msgid "Please sign in."
|
||||
msgstr "crwdns2706:0crwdne2706:0"
|
||||
|
||||
#: documents/templates/registration/login.html:50
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr "crwdns2708:0crwdne2708:0"
|
||||
|
||||
#: documents/templates/registration/login.html:53
|
||||
msgid "Username"
|
||||
msgstr "crwdns2710:0crwdne2710:0"
|
||||
|
||||
#: documents/templates/registration/login.html:54
|
||||
msgid "Password"
|
||||
msgstr "crwdns2712:0crwdne2712:0"
|
||||
|
||||
#: documents/templates/registration/login.html:59
|
||||
msgid "Sign in"
|
||||
msgstr "crwdns2714:0crwdne2714:0"
|
||||
|
||||
#: paperless/settings.py:303
|
||||
msgid "English (US)"
|
||||
msgstr "crwdns2716:0crwdne2716:0"
|
||||
|
||||
#: paperless/settings.py:304
|
||||
msgid "English (GB)"
|
||||
msgstr "crwdns2718:0crwdne2718:0"
|
||||
|
||||
#: paperless/settings.py:305
|
||||
msgid "German"
|
||||
msgstr "crwdns2720:0crwdne2720:0"
|
||||
|
||||
#: paperless/settings.py:306
|
||||
msgid "Dutch"
|
||||
msgstr "crwdns2722:0crwdne2722:0"
|
||||
|
||||
#: paperless/settings.py:307
|
||||
msgid "French"
|
||||
msgstr "crwdns2724:0crwdne2724:0"
|
||||
|
||||
#: paperless/settings.py:308
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "crwdns2726:0crwdne2726:0"
|
||||
|
||||
#: paperless/settings.py:309
|
||||
msgid "Portuguese"
|
||||
msgstr "crwdns3424:0crwdne3424:0"
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Italian"
|
||||
msgstr "crwdns2728:0crwdne2728:0"
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Romanian"
|
||||
msgstr "crwdns2730:0crwdne2730:0"
|
||||
|
||||
#: paperless/settings.py:312
|
||||
msgid "Russian"
|
||||
msgstr "crwdns3414:0crwdne3414:0"
|
||||
|
||||
#: paperless/settings.py:313
|
||||
msgid "Spanish"
|
||||
msgstr "crwdns3420:0crwdne3420:0"
|
||||
|
||||
#: paperless/settings.py:314
|
||||
msgid "Polish"
|
||||
msgstr "crwdns3444:0crwdne3444:0"
|
||||
|
||||
#: paperless/settings.py:315
|
||||
msgid "Swedish"
|
||||
msgstr "crwdns3448:0crwdne3448:0"
|
||||
|
||||
#: paperless/urls.py:120
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr "crwdns2732:0crwdne2732:0"
|
||||
|
||||
#: paperless_mail/admin.py:15
|
||||
msgid "Authentication"
|
||||
msgstr "crwdns3456:0crwdne3456:0"
|
||||
|
||||
#: paperless_mail/admin.py:18
|
||||
msgid "Advanced settings"
|
||||
msgstr "crwdns3458:0crwdne3458:0"
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
msgid "Filter"
|
||||
msgstr "crwdns2734:0crwdne2734:0"
|
||||
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr "crwdns2736:0crwdne2736:0"
|
||||
|
||||
#: paperless_mail/admin.py:49
|
||||
msgid "Actions"
|
||||
msgstr "crwdns2738:0crwdne2738:0"
|
||||
|
||||
#: paperless_mail/admin.py:51
|
||||
msgid "The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched."
|
||||
msgstr "crwdns2740:0crwdne2740:0"
|
||||
|
||||
#: paperless_mail/admin.py:58
|
||||
msgid "Metadata"
|
||||
msgstr "crwdns2742:0crwdne2742:0"
|
||||
|
||||
#: paperless_mail/admin.py:60
|
||||
msgid "Assign metadata to documents consumed from this rule automatically. If you do not assign tags, types or correspondents here, paperless will still process all matching rules that you have defined."
|
||||
msgstr "crwdns2744:0crwdne2744:0"
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
msgstr "crwdns2746:0crwdne2746:0"
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail account"
|
||||
msgstr "crwdns2748:0crwdne2748:0"
|
||||
|
||||
#: paperless_mail/models.py:12
|
||||
msgid "mail accounts"
|
||||
msgstr "crwdns2750:0crwdne2750:0"
|
||||
|
||||
#: paperless_mail/models.py:19
|
||||
msgid "No encryption"
|
||||
msgstr "crwdns2752:0crwdne2752:0"
|
||||
|
||||
#: paperless_mail/models.py:20
|
||||
msgid "Use SSL"
|
||||
msgstr "crwdns2754:0crwdne2754:0"
|
||||
|
||||
#: paperless_mail/models.py:21
|
||||
msgid "Use STARTTLS"
|
||||
msgstr "crwdns2756:0crwdne2756:0"
|
||||
|
||||
#: paperless_mail/models.py:29
|
||||
msgid "IMAP server"
|
||||
msgstr "crwdns2758:0crwdne2758:0"
|
||||
|
||||
#: paperless_mail/models.py:33
|
||||
msgid "IMAP port"
|
||||
msgstr "crwdns2760:0crwdne2760:0"
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr "crwdns2762:0crwdne2762:0"
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
msgid "IMAP security"
|
||||
msgstr "crwdns2764:0crwdne2764:0"
|
||||
|
||||
#: paperless_mail/models.py:46
|
||||
msgid "username"
|
||||
msgstr "crwdns2766:0crwdne2766:0"
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "password"
|
||||
msgstr "crwdns2768:0crwdne2768:0"
|
||||
|
||||
#: paperless_mail/models.py:54
|
||||
msgid "character set"
|
||||
msgstr "crwdns3460:0crwdne3460:0"
|
||||
|
||||
#: paperless_mail/models.py:57
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
msgstr "crwdns3462:0crwdne3462:0"
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "mail rule"
|
||||
msgstr "crwdns2770:0crwdne2770:0"
|
||||
|
||||
#: paperless_mail/models.py:69
|
||||
msgid "mail rules"
|
||||
msgstr "crwdns2772:0crwdne2772:0"
|
||||
|
||||
#: paperless_mail/models.py:75
|
||||
msgid "Only process attachments."
|
||||
msgstr "crwdns2774:0crwdne2774:0"
|
||||
|
||||
#: paperless_mail/models.py:76
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr "crwdns2776:0crwdne2776:0"
|
||||
|
||||
#: paperless_mail/models.py:86
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr "crwdns2778:0crwdne2778:0"
|
||||
|
||||
#: paperless_mail/models.py:87
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr "crwdns2780:0crwdne2780:0"
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "Move to specified folder"
|
||||
msgstr "crwdns2782:0crwdne2782:0"
|
||||
|
||||
#: paperless_mail/models.py:89
|
||||
msgid "Delete"
|
||||
msgstr "crwdns2784:0crwdne2784:0"
|
||||
|
||||
#: paperless_mail/models.py:96
|
||||
msgid "Use subject as title"
|
||||
msgstr "crwdns2786:0crwdne2786:0"
|
||||
|
||||
#: paperless_mail/models.py:97
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr "crwdns2788:0crwdne2788:0"
|
||||
|
||||
#: paperless_mail/models.py:107
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr "crwdns2790:0crwdne2790:0"
|
||||
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "Use mail address"
|
||||
msgstr "crwdns2792:0crwdne2792:0"
|
||||
|
||||
#: paperless_mail/models.py:111
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr "crwdns2794:0crwdne2794:0"
|
||||
|
||||
#: paperless_mail/models.py:113
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr "crwdns2796:0crwdne2796:0"
|
||||
|
||||
#: paperless_mail/models.py:121
|
||||
msgid "order"
|
||||
msgstr "crwdns2798:0crwdne2798:0"
|
||||
|
||||
#: paperless_mail/models.py:128
|
||||
msgid "account"
|
||||
msgstr "crwdns2800:0crwdne2800:0"
|
||||
|
||||
#: paperless_mail/models.py:132
|
||||
msgid "folder"
|
||||
msgstr "crwdns2802:0crwdne2802:0"
|
||||
|
||||
#: paperless_mail/models.py:134
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr "crwdns3464:0crwdne3464:0"
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
msgid "filter from"
|
||||
msgstr "crwdns2804:0crwdne2804:0"
|
||||
|
||||
#: paperless_mail/models.py:141
|
||||
msgid "filter subject"
|
||||
msgstr "crwdns2806:0crwdne2806:0"
|
||||
|
||||
#: paperless_mail/models.py:144
|
||||
msgid "filter body"
|
||||
msgstr "crwdns2808:0crwdne2808:0"
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "filter attachment filename"
|
||||
msgstr "crwdns2810:0crwdne2810:0"
|
||||
|
||||
#: paperless_mail/models.py:150
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr "crwdns2812:0crwdne2812:0"
|
||||
|
||||
#: paperless_mail/models.py:156
|
||||
msgid "maximum age"
|
||||
msgstr "crwdns2814:0crwdne2814:0"
|
||||
|
||||
#: paperless_mail/models.py:158
|
||||
msgid "Specified in days."
|
||||
msgstr "crwdns2816:0crwdne2816:0"
|
||||
|
||||
#: paperless_mail/models.py:161
|
||||
msgid "attachment type"
|
||||
msgstr "crwdns2818:0crwdne2818:0"
|
||||
|
||||
#: paperless_mail/models.py:164
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr "crwdns2820:0crwdne2820:0"
|
||||
|
||||
#: paperless_mail/models.py:169
|
||||
msgid "action"
|
||||
msgstr "crwdns2822:0crwdne2822:0"
|
||||
|
||||
#: paperless_mail/models.py:175
|
||||
msgid "action parameter"
|
||||
msgstr "crwdns2824:0crwdne2824:0"
|
||||
|
||||
#: paperless_mail/models.py:177
|
||||
msgid "Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots."
|
||||
msgstr "crwdns3466:0crwdne3466:0"
|
||||
|
||||
#: paperless_mail/models.py:184
|
||||
msgid "assign title from"
|
||||
msgstr "crwdns2828:0crwdne2828:0"
|
||||
|
||||
#: paperless_mail/models.py:194
|
||||
msgid "assign this tag"
|
||||
msgstr "crwdns2830:0crwdne2830:0"
|
||||
|
||||
#: paperless_mail/models.py:202
|
||||
msgid "assign this document type"
|
||||
msgstr "crwdns2832:0crwdne2832:0"
|
||||
|
||||
#: paperless_mail/models.py:206
|
||||
msgid "assign correspondent from"
|
||||
msgstr "crwdns2834:0crwdne2834:0"
|
||||
|
||||
#: paperless_mail/models.py:216
|
||||
msgid "assign this correspondent"
|
||||
msgstr "crwdns2836:0crwdne2836:0"
|
||||
|
||||
698
src/locale/zh_CN/LC_MESSAGES/django.po
Normal file
698
src/locale/zh_CN/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,698 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ng\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-05-16 09:38+0000\n"
|
||||
"PO-Revision-Date: 2021-05-16 10:09\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Chinese Simplified\n"
|
||||
"Language: zh_CN\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Crowdin-Project: paperless-ng\n"
|
||||
"X-Crowdin-Project-ID: 434940\n"
|
||||
"X-Crowdin-Language: zh-CN\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 54\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
msgstr "文件"
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr "任何词"
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr "所有词"
|
||||
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr "完全符合"
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr "正则表达式"
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr "模糊词汇"
|
||||
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr "自动"
|
||||
|
||||
#: documents/models.py:41 documents/models.py:350 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:117
|
||||
msgid "name"
|
||||
msgstr "名字"
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "match"
|
||||
msgstr "配对"
|
||||
|
||||
#: documents/models.py:49
|
||||
msgid "matching algorithm"
|
||||
msgstr "配对算法"
|
||||
|
||||
#: documents/models.py:55
|
||||
msgid "is insensitive"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:74 documents/models.py:120
|
||||
msgid "correspondent"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:75
|
||||
msgid "correspondents"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:81
|
||||
msgid "color"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:87
|
||||
msgid "is inbox tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:89
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:94
|
||||
msgid "tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:95 documents/models.py:151
|
||||
msgid "tags"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:101 documents/models.py:133
|
||||
msgid "document type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:102
|
||||
msgid "document types"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:110
|
||||
msgid "Unencrypted"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:111
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:124
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:137
|
||||
msgid "content"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:139
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:144
|
||||
msgid "mime type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:155
|
||||
msgid "checksum"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid "The checksum of the original document."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:163
|
||||
msgid "archive checksum"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:168
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:172 documents/models.py:328
|
||||
msgid "created"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:176
|
||||
msgid "modified"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:180
|
||||
msgid "storage type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:188
|
||||
msgid "added"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:192
|
||||
msgid "filename"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:198
|
||||
msgid "Current filename in storage"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:202
|
||||
msgid "archive filename"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:208
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "archive serial number"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:217
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:223
|
||||
msgid "document"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:224
|
||||
msgid "documents"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:311
|
||||
msgid "debug"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:312
|
||||
msgid "information"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:313
|
||||
msgid "warning"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:314
|
||||
msgid "error"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:315
|
||||
msgid "critical"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:319
|
||||
msgid "group"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:322
|
||||
msgid "message"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:325
|
||||
msgid "level"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:332
|
||||
msgid "log"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "logs"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:344 documents/models.py:401
|
||||
msgid "saved view"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:345
|
||||
msgid "saved views"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:348
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:354
|
||||
msgid "show on dashboard"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:357
|
||||
msgid "show in sidebar"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:361
|
||||
msgid "sort field"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:367
|
||||
msgid "sort reverse"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:373
|
||||
msgid "title contains"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:374
|
||||
msgid "content contains"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:375
|
||||
msgid "ASN is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:376
|
||||
msgid "correspondent is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:377
|
||||
msgid "document type is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:378
|
||||
msgid "is in inbox"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:379
|
||||
msgid "has tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:380
|
||||
msgid "has any tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:381
|
||||
msgid "created before"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:382
|
||||
msgid "created after"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:383
|
||||
msgid "created year is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:384
|
||||
msgid "created month is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:385
|
||||
msgid "created day is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:386
|
||||
msgid "added before"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:387
|
||||
msgid "added after"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:388
|
||||
msgid "modified before"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:389
|
||||
msgid "modified after"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:390
|
||||
msgid "does not have tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:391
|
||||
msgid "does not have ASN"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:392
|
||||
msgid "title or content contains"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:393
|
||||
msgid "fulltext query"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:394
|
||||
msgid "more like this"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:405
|
||||
msgid "rule type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:409
|
||||
msgid "value"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:415
|
||||
msgid "filter rule"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:416
|
||||
msgid "filter rules"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:53
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:177
|
||||
msgid "Invalid color."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:451
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/index.html:22
|
||||
msgid "Paperless-ng is loading..."
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ng signed out"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/logged_out.html:45
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/logged_out.html:46
|
||||
msgid "Sign in again"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ng sign in"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:47
|
||||
msgid "Please sign in."
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:50
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:53
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:54
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:59
|
||||
msgid "Sign in"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:303
|
||||
msgid "English (US)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:304
|
||||
msgid "English (GB)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:305
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:306
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:307
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:308
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:309
|
||||
msgid "Portuguese"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Romanian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:312
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:313
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:314
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:315
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/urls.py:120
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:15
|
||||
msgid "Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:18
|
||||
msgid "Advanced settings"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:37
|
||||
msgid "Filter"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:39
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:49
|
||||
msgid "Actions"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:51
|
||||
msgid "The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:58
|
||||
msgid "Metadata"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:60
|
||||
msgid "Assign metadata to documents consumed from this rule automatically. If you do not assign tags, types or correspondents here, paperless will still process all matching rules that you have defined."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail account"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:12
|
||||
msgid "mail accounts"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:19
|
||||
msgid "No encryption"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:20
|
||||
msgid "Use SSL"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:21
|
||||
msgid "Use STARTTLS"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:29
|
||||
msgid "IMAP server"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:33
|
||||
msgid "IMAP port"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
msgid "IMAP security"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:46
|
||||
msgid "username"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "password"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:54
|
||||
msgid "character set"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:57
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "mail rule"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:69
|
||||
msgid "mail rules"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:75
|
||||
msgid "Only process attachments."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:76
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:86
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:87
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "Move to specified folder"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:89
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:96
|
||||
msgid "Use subject as title"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:97
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:107
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "Use mail address"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:111
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:113
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:121
|
||||
msgid "order"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:128
|
||||
msgid "account"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:132
|
||||
msgid "folder"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:134
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:138
|
||||
msgid "filter from"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:141
|
||||
msgid "filter subject"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:144
|
||||
msgid "filter body"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "filter attachment filename"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:150
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:156
|
||||
msgid "maximum age"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:158
|
||||
msgid "Specified in days."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:161
|
||||
msgid "attachment type"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:164
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:169
|
||||
msgid "action"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:175
|
||||
msgid "action parameter"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:177
|
||||
msgid "Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:184
|
||||
msgid "assign title from"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:194
|
||||
msgid "assign this tag"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:202
|
||||
msgid "assign this document type"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:206
|
||||
msgid "assign correspondent from"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:216
|
||||
msgid "assign this correspondent"
|
||||
msgstr ""
|
||||
|
||||
23
src/paperless/asgi.py
Normal file
23
src/paperless/asgi.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
# Fetch Django ASGI application early to ensure AppRegistry is populated
|
||||
# before importing consumers and AuthMiddlewareStack that may import ORM
|
||||
# models.
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "paperless.settings")
|
||||
django_asgi_app = get_asgi_application()
|
||||
|
||||
from channels.auth import AuthMiddlewareStack # NOQA: E402
|
||||
from channels.routing import ProtocolTypeRouter, URLRouter # NOQA: E402
|
||||
|
||||
from paperless.urls import websocket_urlpatterns # NOQA: E402
|
||||
|
||||
application = ProtocolTypeRouter({
|
||||
"http": get_asgi_application(),
|
||||
"websocket": AuthMiddlewareStack(
|
||||
URLRouter(
|
||||
websocket_urlpatterns
|
||||
)
|
||||
),
|
||||
})
|
||||
@@ -1,4 +1,5 @@
|
||||
from django.conf import settings
|
||||
from django.contrib import auth
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
from rest_framework import authentication
|
||||
@@ -11,6 +12,7 @@ class AutoLoginMiddleware(MiddlewareMixin):
|
||||
try:
|
||||
request.user = User.objects.get(
|
||||
username=settings.AUTO_LOGIN_USERNAME)
|
||||
auth.login(request, request.user)
|
||||
except User.DoesNotExist:
|
||||
pass
|
||||
|
||||
@@ -33,5 +35,4 @@ class HttpRemoteUserMiddleware(RemoteUserMiddleware):
|
||||
""" This class allows authentication via HTTP_REMOTE_USER which is set for
|
||||
example by certain SSO applications.
|
||||
"""
|
||||
|
||||
header = 'HTTP_REMOTE_USER'
|
||||
header = settings.HTTP_REMOTE_USER_HEADER_NAME
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.checks import Error, Warning, register
|
||||
@@ -16,16 +17,29 @@ writeable_hint = (
|
||||
def path_check(var, directory):
|
||||
messages = []
|
||||
if directory:
|
||||
if not os.path.exists(directory):
|
||||
if not os.path.isdir(directory):
|
||||
messages.append(Error(
|
||||
exists_message.format(var),
|
||||
exists_hint.format(directory)
|
||||
))
|
||||
elif not os.access(directory, os.W_OK | os.X_OK):
|
||||
messages.append(Error(
|
||||
writeable_message.format(var),
|
||||
writeable_hint.format(directory)
|
||||
))
|
||||
else:
|
||||
test_file = os.path.join(
|
||||
directory, f'__paperless_write_test_{os.getpid()}__'
|
||||
)
|
||||
try:
|
||||
with open(test_file, 'w'):
|
||||
pass
|
||||
except PermissionError:
|
||||
messages.append(Error(
|
||||
writeable_message.format(var),
|
||||
writeable_hint.format(
|
||||
f'\n{stat.filemode(os.stat(directory).st_mode)} '
|
||||
f'{directory}\n')
|
||||
))
|
||||
finally:
|
||||
if os.path.isfile(test_file):
|
||||
os.remove(test_file)
|
||||
|
||||
return messages
|
||||
|
||||
|
||||
|
||||
29
src/paperless/consumers.py
Normal file
29
src/paperless/consumers.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import json
|
||||
|
||||
from asgiref.sync import async_to_sync
|
||||
from channels.exceptions import DenyConnection, AcceptConnection
|
||||
from channels.generic.websocket import WebsocketConsumer
|
||||
|
||||
|
||||
class StatusConsumer(WebsocketConsumer):
|
||||
|
||||
def _authenticated(self):
|
||||
return 'user' in self.scope and self.scope['user'].is_authenticated
|
||||
|
||||
def connect(self):
|
||||
if not self._authenticated():
|
||||
raise DenyConnection()
|
||||
else:
|
||||
async_to_sync(self.channel_layer.group_add)(
|
||||
'status_updates', self.channel_name)
|
||||
raise AcceptConnection()
|
||||
|
||||
def disconnect(self, close_code):
|
||||
async_to_sync(self.channel_layer.group_discard)(
|
||||
'status_updates', self.channel_name)
|
||||
|
||||
def status_update(self, event):
|
||||
if not self._authenticated():
|
||||
self.close()
|
||||
else:
|
||||
self.send(json.dumps(event['data']))
|
||||
20
src/paperless/middleware.py
Normal file
20
src/paperless/middleware.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from django.conf import settings
|
||||
|
||||
from paperless import version
|
||||
|
||||
|
||||
class ApiVersionMiddleware:
|
||||
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
response = self.get_response(request)
|
||||
if request.user.is_authenticated:
|
||||
versions = settings.REST_FRAMEWORK['ALLOWED_VERSIONS']
|
||||
response['X-Api-Version'] = versions[len(versions)-1]
|
||||
response['X-Version'] = ".".join(
|
||||
[str(_) for _ in version.__version__]
|
||||
)
|
||||
|
||||
return response
|
||||
@@ -4,7 +4,7 @@ import multiprocessing
|
||||
import os
|
||||
import re
|
||||
|
||||
import dateparser
|
||||
from concurrent_log_handler.queue import setup_logging_queues
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -63,6 +63,8 @@ MEDIA_LOCK = os.path.join(MEDIA_ROOT, "media.lock")
|
||||
INDEX_DIR = os.path.join(DATA_DIR, "index")
|
||||
MODEL_FILE = os.path.join(DATA_DIR, "classification_model.pickle")
|
||||
|
||||
LOGGING_DIR = os.getenv('PAPERLESS_LOGGING_DIR', os.path.join(DATA_DIR, "log"))
|
||||
|
||||
CONSUMPTION_DIR = os.getenv("PAPERLESS_CONSUMPTION_DIR", os.path.join(BASE_DIR, "..", "consume"))
|
||||
|
||||
# This will be created if it doesn't exist
|
||||
@@ -102,12 +104,20 @@ INSTALLED_APPS = [
|
||||
|
||||
] + env_apps
|
||||
|
||||
if DEBUG:
|
||||
INSTALLED_APPS.append("channels")
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||
'rest_framework.authentication.BasicAuthentication',
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
'rest_framework.authentication.TokenAuthentication'
|
||||
]
|
||||
],
|
||||
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning',
|
||||
'DEFAULT_VERSION': '1',
|
||||
# Make sure these are ordered and that the most recent version appears
|
||||
# last
|
||||
'ALLOWED_VERSIONS': ['1', '2']
|
||||
}
|
||||
|
||||
if DEBUG:
|
||||
@@ -123,6 +133,7 @@ MIDDLEWARE = [
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'paperless.middleware.ApiVersionMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
@@ -131,12 +142,17 @@ MIDDLEWARE = [
|
||||
ROOT_URLCONF = 'paperless.urls'
|
||||
|
||||
FORCE_SCRIPT_NAME = os.getenv("PAPERLESS_FORCE_SCRIPT_NAME")
|
||||
BASE_URL = (FORCE_SCRIPT_NAME or "") + "/"
|
||||
LOGIN_URL = BASE_URL + "accounts/login/"
|
||||
LOGOUT_REDIRECT_URL = os.getenv("PAPERLESS_LOGOUT_REDIRECT_URL")
|
||||
|
||||
WSGI_APPLICATION = 'paperless.wsgi.application'
|
||||
ASGI_APPLICATION = "paperless.asgi.application"
|
||||
|
||||
STATIC_URL = os.getenv("PAPERLESS_STATIC_URL", "/static/")
|
||||
STATIC_URL = os.getenv("PAPERLESS_STATIC_URL", BASE_URL + "static/")
|
||||
WHITENOISE_STATIC_PREFIX = "/static/"
|
||||
|
||||
# what is this used for?
|
||||
# TODO: what is this used for?
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
@@ -153,6 +169,17 @@ TEMPLATES = [
|
||||
},
|
||||
]
|
||||
|
||||
CHANNEL_LAYERS = {
|
||||
"default": {
|
||||
"BACKEND": "channels_redis.core.RedisChannelLayer",
|
||||
"CONFIG": {
|
||||
"hosts": [os.getenv("PAPERLESS_REDIS", "redis://localhost:6379")],
|
||||
"capacity": 2000, # default 100
|
||||
"expiry": 15, # default 60
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Security #
|
||||
###############################################################################
|
||||
@@ -166,6 +193,7 @@ if AUTO_LOGIN_USERNAME:
|
||||
MIDDLEWARE.insert(_index+1, 'paperless.auth.AutoLoginMiddleware')
|
||||
|
||||
ENABLE_HTTP_REMOTE_USER = __get_boolean("PAPERLESS_ENABLE_HTTP_REMOTE_USER")
|
||||
HTTP_REMOTE_USER_HEADER_NAME = os.getenv("PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME", "HTTP_REMOTE_USER")
|
||||
|
||||
if ENABLE_HTTP_REMOTE_USER:
|
||||
MIDDLEWARE.append(
|
||||
@@ -264,6 +292,8 @@ if os.getenv("PAPERLESS_DBHOST"):
|
||||
if os.getenv("PAPERLESS_DBPORT"):
|
||||
DATABASES["default"]["PORT"] = os.getenv("PAPERLESS_DBPORT")
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||
|
||||
###############################################################################
|
||||
# Internationalization #
|
||||
###############################################################################
|
||||
@@ -271,10 +301,20 @@ if os.getenv("PAPERLESS_DBHOST"):
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
LANGUAGES = [
|
||||
("en-us", _("English")),
|
||||
("de", _("German")),
|
||||
("en-us", _("English (US)")),
|
||||
("en-gb", _("English (GB)")),
|
||||
("de-de", _("German")),
|
||||
("nl-nl", _("Dutch")),
|
||||
("fr", _("French"))
|
||||
("fr-fr", _("French")),
|
||||
("pt-br", _("Portuguese (Brazil)")),
|
||||
("pt-pt", _("Portuguese")),
|
||||
("it-it", _("Italian")),
|
||||
("ro-ro", _("Romanian")),
|
||||
("ru-ru", _("Russian")),
|
||||
("es-es", _("Spanish")),
|
||||
("pl-pl", _("Polish")),
|
||||
("sv-se", _("Swedish")),
|
||||
("lb-lu", _("Luxembourgish")),
|
||||
]
|
||||
|
||||
LOCALE_PATHS = [
|
||||
@@ -293,14 +333,19 @@ USE_TZ = True
|
||||
# Logging #
|
||||
###############################################################################
|
||||
|
||||
DISABLE_DBHANDLER = __get_boolean("PAPERLESS_DISABLE_DBHANDLER")
|
||||
setup_logging_queues()
|
||||
|
||||
os.makedirs(LOGGING_DIR, exist_ok=True)
|
||||
|
||||
LOGROTATE_MAX_SIZE = os.getenv("PAPERLESS_LOGROTATE_MAX_SIZE", 1024*1024)
|
||||
LOGROTATE_MAX_BACKUPS = os.getenv("PAPERLESS_LOGROTATE_MAX_BACKUPS", 20)
|
||||
|
||||
LOGGING = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format': '{levelname} {asctime} {module} {message}',
|
||||
'format': '[{asctime}] [{levelname}] [{name}] {message}',
|
||||
'style': '{',
|
||||
},
|
||||
'simple': {
|
||||
@@ -309,34 +354,39 @@ LOGGING = {
|
||||
},
|
||||
},
|
||||
"handlers": {
|
||||
"db": {
|
||||
"level": "DEBUG",
|
||||
"class": "documents.loggers.PaperlessHandler",
|
||||
},
|
||||
"console": {
|
||||
"level": "DEBUG" if DEBUG else "INFO",
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "verbose",
|
||||
},
|
||||
"file_paperless": {
|
||||
"class": "concurrent_log_handler.ConcurrentRotatingFileHandler",
|
||||
"formatter": "verbose",
|
||||
"filename": os.path.join(LOGGING_DIR, "paperless.log"),
|
||||
"maxBytes": LOGROTATE_MAX_SIZE,
|
||||
"backupCount": LOGROTATE_MAX_BACKUPS
|
||||
},
|
||||
"file_mail": {
|
||||
"class": "concurrent_log_handler.ConcurrentRotatingFileHandler",
|
||||
"formatter": "verbose",
|
||||
"filename": os.path.join(LOGGING_DIR, "mail.log"),
|
||||
"maxBytes": LOGROTATE_MAX_SIZE,
|
||||
"backupCount": LOGROTATE_MAX_BACKUPS
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"handlers": ["console"],
|
||||
"level": "DEBUG",
|
||||
"handlers": ["console"]
|
||||
},
|
||||
"loggers": {
|
||||
"documents": {
|
||||
"handlers": ["db"],
|
||||
"propagate": True,
|
||||
"paperless": {
|
||||
"handlers": ["file_paperless"],
|
||||
"level": "DEBUG"
|
||||
},
|
||||
"paperless_mail": {
|
||||
"handlers": ["db"],
|
||||
"propagate": True,
|
||||
},
|
||||
"paperless_tesseract": {
|
||||
"handlers": ["db"],
|
||||
"propagate": True,
|
||||
},
|
||||
},
|
||||
"handlers": ["file_mail"],
|
||||
"level": "DEBUG"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
@@ -354,8 +404,10 @@ LOGGING = {
|
||||
|
||||
def default_task_workers():
|
||||
# always leave one core open
|
||||
available_cores = max(multiprocessing.cpu_count() - 1, 1)
|
||||
available_cores = max(multiprocessing.cpu_count(), 1)
|
||||
try:
|
||||
if available_cores < 4:
|
||||
return available_cores
|
||||
return max(
|
||||
math.floor(math.sqrt(available_cores)),
|
||||
1
|
||||
@@ -369,6 +421,9 @@ TASK_WORKERS = int(os.getenv("PAPERLESS_TASK_WORKERS", default_task_workers()))
|
||||
Q_CLUSTER = {
|
||||
'name': 'paperless',
|
||||
'catch_up': False,
|
||||
'recycle': 1,
|
||||
'retry': 1800,
|
||||
'timeout': 1800,
|
||||
'workers': TASK_WORKERS,
|
||||
'redis': os.getenv("PAPERLESS_REDIS", "redis://localhost:6379")
|
||||
}
|
||||
@@ -376,7 +431,7 @@ Q_CLUSTER = {
|
||||
|
||||
def default_threads_per_worker(task_workers):
|
||||
# always leave one core open
|
||||
available_cores = max(multiprocessing.cpu_count() - 1, 1)
|
||||
available_cores = max(multiprocessing.cpu_count(), 1)
|
||||
try:
|
||||
return max(
|
||||
math.floor(available_cores / task_workers),
|
||||
@@ -394,10 +449,22 @@ THREADS_PER_WORKER = os.getenv("PAPERLESS_THREADS_PER_WORKER", default_threads_p
|
||||
|
||||
CONSUMER_POLLING = int(os.getenv("PAPERLESS_CONSUMER_POLLING", 0))
|
||||
|
||||
CONSUMER_POLLING_DELAY = int(os.getenv("PAPERLESS_CONSUMER_POLLING_DELAY", 5))
|
||||
|
||||
CONSUMER_POLLING_RETRY_COUNT = int(
|
||||
os.getenv("PAPERLESS_CONSUMER_POLLING_RETRY_COUNT", 5)
|
||||
)
|
||||
|
||||
CONSUMER_DELETE_DUPLICATES = __get_boolean("PAPERLESS_CONSUMER_DELETE_DUPLICATES")
|
||||
|
||||
CONSUMER_RECURSIVE = __get_boolean("PAPERLESS_CONSUMER_RECURSIVE")
|
||||
|
||||
# Ignore glob patterns, relative to PAPERLESS_CONSUMPTION_DIR
|
||||
CONSUMER_IGNORE_PATTERNS = list(
|
||||
json.loads(
|
||||
os.getenv("PAPERLESS_CONSUMER_IGNORE_PATTERNS",
|
||||
'[".DS_STORE/*", "._*", ".stfolder/*"]')))
|
||||
|
||||
CONSUMER_SUBDIRS_AS_TAGS = __get_boolean("PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS")
|
||||
|
||||
OPTIMIZE_THUMBNAILS = __get_boolean("PAPERLESS_OPTIMIZE_THUMBNAILS", "true")
|
||||
@@ -418,6 +485,14 @@ OCR_MODE = os.getenv("PAPERLESS_OCR_MODE", "skip")
|
||||
|
||||
OCR_IMAGE_DPI = os.getenv("PAPERLESS_OCR_IMAGE_DPI")
|
||||
|
||||
OCR_CLEAN = os.getenv("PAPERLESS_OCR_CLEAN", "clean")
|
||||
|
||||
OCR_DESKEW = __get_boolean("PAPERLESS_OCR_DESKEW", "true")
|
||||
|
||||
OCR_ROTATE_PAGES = __get_boolean("PAPERLESS_OCR_ROTATE_PAGES", "true")
|
||||
|
||||
OCR_ROTATE_PAGES_THRESHOLD = float(os.getenv("PAPERLESS_OCR_ROTATE_PAGES_THRESHOLD", 12.0))
|
||||
|
||||
OCR_USER_ARGS = os.getenv("PAPERLESS_OCR_USER_ARGS", "{}")
|
||||
|
||||
# GNUPG needs a home directory for some reason
|
||||
@@ -477,7 +552,11 @@ if PAPERLESS_TIKA_ENABLED:
|
||||
|
||||
# List dates that should be ignored when trying to parse date from document text
|
||||
IGNORE_DATES = set()
|
||||
for s in os.getenv("PAPERLESS_IGNORE_DATES", "").split(","):
|
||||
d = dateparser.parse(s)
|
||||
if d:
|
||||
IGNORE_DATES.add(d.date())
|
||||
|
||||
if os.getenv("PAPERLESS_IGNORE_DATES", ""):
|
||||
import dateparser
|
||||
|
||||
for s in os.getenv("PAPERLESS_IGNORE_DATES", "").split(","):
|
||||
d = dateparser.parse(s)
|
||||
if d:
|
||||
IGNORE_DATES.add(d.date())
|
||||
|
||||
60
src/paperless/tests/test_websockets.py
Normal file
60
src/paperless/tests/test_websockets.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from unittest import mock
|
||||
|
||||
from channels.layers import get_channel_layer
|
||||
from channels.testing import WebsocketCommunicator
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
from paperless.asgi import application
|
||||
|
||||
|
||||
TEST_CHANNEL_LAYERS = {
|
||||
'default': {
|
||||
'BACKEND': 'channels.layers.InMemoryChannelLayer',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class TestWebSockets(TestCase):
|
||||
|
||||
@override_settings(CHANNEL_LAYERS=TEST_CHANNEL_LAYERS)
|
||||
async def test_no_auth(self):
|
||||
communicator = WebsocketCommunicator(application, "/ws/status/")
|
||||
connected, subprotocol = await communicator.connect()
|
||||
self.assertFalse(connected)
|
||||
await communicator.disconnect()
|
||||
|
||||
@override_settings(CHANNEL_LAYERS=TEST_CHANNEL_LAYERS)
|
||||
@mock.patch("paperless.consumers.StatusConsumer._authenticated")
|
||||
async def test_auth(self, _authenticated):
|
||||
_authenticated.return_value = True
|
||||
|
||||
communicator = WebsocketCommunicator(application, "/ws/status/")
|
||||
connected, subprotocol = await communicator.connect()
|
||||
self.assertTrue(connected)
|
||||
|
||||
await communicator.disconnect()
|
||||
|
||||
@override_settings(CHANNEL_LAYERS=TEST_CHANNEL_LAYERS)
|
||||
@mock.patch("paperless.consumers.StatusConsumer._authenticated")
|
||||
async def test_receive(self, _authenticated):
|
||||
_authenticated.return_value = True
|
||||
|
||||
communicator = WebsocketCommunicator(application, "/ws/status/")
|
||||
connected, subprotocol = await communicator.connect()
|
||||
self.assertTrue(connected)
|
||||
|
||||
message = {
|
||||
"task_id": "test"
|
||||
}
|
||||
|
||||
channel_layer = get_channel_layer()
|
||||
await channel_layer.group_send("status_updates", {
|
||||
"type": "status_update",
|
||||
"data": message
|
||||
})
|
||||
|
||||
response = await communicator.receive_json_from()
|
||||
|
||||
self.assertEqual(response, message)
|
||||
|
||||
await communicator.disconnect()
|
||||
42
src/paperless/urls.py
Executable file → Normal file
42
src/paperless/urls.py
Executable file → Normal file
@@ -9,28 +9,31 @@ from rest_framework.routers import DefaultRouter
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from paperless.consumers import StatusConsumer
|
||||
from documents.views import (
|
||||
CorrespondentViewSet,
|
||||
DocumentViewSet,
|
||||
UnifiedSearchViewSet,
|
||||
LogViewSet,
|
||||
TagViewSet,
|
||||
DocumentTypeViewSet,
|
||||
SearchView,
|
||||
IndexView,
|
||||
SearchAutoCompleteView,
|
||||
StatisticsView,
|
||||
PostDocumentView,
|
||||
SavedViewViewSet,
|
||||
BulkEditView,
|
||||
SelectionDataView
|
||||
SelectionDataView,
|
||||
BulkDownloadView
|
||||
)
|
||||
from paperless.views import FaviconView
|
||||
|
||||
api_router = DefaultRouter()
|
||||
api_router.register(r"correspondents", CorrespondentViewSet)
|
||||
api_router.register(r"document_types", DocumentTypeViewSet)
|
||||
api_router.register(r"documents", DocumentViewSet)
|
||||
api_router.register(r"logs", LogViewSet)
|
||||
api_router.register(r"documents", UnifiedSearchViewSet)
|
||||
api_router.register(r"logs", LogViewSet, basename="logs")
|
||||
api_router.register(r"tags", TagViewSet)
|
||||
api_router.register(r"saved_views", SavedViewViewSet)
|
||||
|
||||
@@ -45,10 +48,6 @@ urlpatterns = [
|
||||
SearchAutoCompleteView.as_view(),
|
||||
name="autocomplete"),
|
||||
|
||||
re_path(r"^search/",
|
||||
SearchView.as_view(),
|
||||
name="search"),
|
||||
|
||||
re_path(r"^statistics/",
|
||||
StatisticsView.as_view(),
|
||||
name="statistics"),
|
||||
@@ -62,6 +61,9 @@ urlpatterns = [
|
||||
re_path(r"^documents/selection_data/", SelectionDataView.as_view(),
|
||||
name="selection_data"),
|
||||
|
||||
re_path(r"^documents/bulk_download/", BulkDownloadView.as_view(),
|
||||
name="bulk_download"),
|
||||
|
||||
path('token/', views.obtain_auth_token)
|
||||
|
||||
] + api_router.urls)),
|
||||
@@ -73,31 +75,41 @@ urlpatterns = [
|
||||
re_path(r"^fetch/", include([
|
||||
re_path(
|
||||
r"^doc/(?P<pk>\d+)$",
|
||||
RedirectView.as_view(url='/api/documents/%(pk)s/download/'),
|
||||
RedirectView.as_view(url=settings.BASE_URL +
|
||||
'api/documents/%(pk)s/download/'),
|
||||
),
|
||||
re_path(
|
||||
r"^thumb/(?P<pk>\d+)$",
|
||||
RedirectView.as_view(url='/api/documents/%(pk)s/thumb/'),
|
||||
RedirectView.as_view(url=settings.BASE_URL +
|
||||
'api/documents/%(pk)s/thumb/'),
|
||||
),
|
||||
re_path(
|
||||
r"^preview/(?P<pk>\d+)$",
|
||||
RedirectView.as_view(url='/api/documents/%(pk)s/preview/'),
|
||||
RedirectView.as_view(url=settings.BASE_URL +
|
||||
'api/documents/%(pk)s/preview/'),
|
||||
),
|
||||
])),
|
||||
|
||||
re_path(r"^push$", csrf_exempt(
|
||||
RedirectView.as_view(url='/api/documents/post_document/'))),
|
||||
RedirectView.as_view(url=settings.BASE_URL +
|
||||
'api/documents/post_document/'))),
|
||||
|
||||
# Frontend assets TODO: this is pretty bad, but it works.
|
||||
path('assets/<path:path>',
|
||||
RedirectView.as_view(url='/static/frontend/en-US/assets/%(path)s')),
|
||||
RedirectView.as_view(url=settings.STATIC_URL +
|
||||
'frontend/en-US/assets/%(path)s')),
|
||||
# TODO: with localization, this is even worse! :/
|
||||
|
||||
# login, logout
|
||||
path('accounts/', include('django.contrib.auth.urls')),
|
||||
|
||||
# Root of the Frontent
|
||||
re_path(r".*", login_required(IndexView.as_view())),
|
||||
re_path(r".*", login_required(IndexView.as_view()), name='base'),
|
||||
]
|
||||
|
||||
|
||||
websocket_urlpatterns = [
|
||||
re_path(r'ws/status/$', StatusConsumer.as_asgi()),
|
||||
]
|
||||
|
||||
# Text in each page's <h1> (and above login form).
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = (1, 0, 0)
|
||||
__version__ = (1, 5, 0)
|
||||
|
||||
11
src/paperless/workers.py
Normal file
11
src/paperless/workers.py
Normal file
@@ -0,0 +1,11 @@
|
||||
import os
|
||||
from uvicorn.workers import UvicornWorker
|
||||
from django.conf import settings
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "paperless.settings")
|
||||
|
||||
|
||||
class ConfigurableWorker(UvicornWorker):
|
||||
CONFIG_KWARGS = {
|
||||
"root_path": settings.FORCE_SCRIPT_NAME or "",
|
||||
}
|
||||
@@ -8,6 +8,18 @@ class MailAccountAdmin(admin.ModelAdmin):
|
||||
|
||||
list_display = ("name", "imap_server", "username")
|
||||
|
||||
fieldsets = [
|
||||
(None, {
|
||||
'fields': ['name', 'imap_server', 'imap_port']
|
||||
}),
|
||||
(_("Authentication"), {
|
||||
'fields': ['imap_security', 'username', 'password']
|
||||
}),
|
||||
(_("Advanced settings"), {
|
||||
'fields': ['character_set']
|
||||
})
|
||||
]
|
||||
|
||||
|
||||
class MailRuleAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user