Merge branch 'dev' into fix-mail-starttls
@@ -3,7 +3,9 @@ import os
|
||||
from pathlib import Path
|
||||
from pathlib import PurePath
|
||||
from threading import Thread
|
||||
from time import monotonic
|
||||
from time import sleep
|
||||
from typing import Final
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
@@ -53,6 +55,25 @@ def _consume(filepath):
|
||||
logger.warning(f"Not consuming file {filepath}: Unknown file extension.")
|
||||
return
|
||||
|
||||
# Total wait time: up to 500ms
|
||||
os_error_retry_count: Final[int] = 50
|
||||
os_error_retry_wait: Final[float] = 0.01
|
||||
|
||||
read_try_count = 0
|
||||
file_open_ok = False
|
||||
|
||||
while (read_try_count < os_error_retry_count) and not file_open_ok:
|
||||
try:
|
||||
with open(filepath, "rb"):
|
||||
file_open_ok = True
|
||||
except OSError:
|
||||
read_try_count += 1
|
||||
sleep(os_error_retry_wait)
|
||||
|
||||
if read_try_count >= os_error_retry_count:
|
||||
logger.warning(f"Not consuming file {filepath}: OS reports file as busy still")
|
||||
return
|
||||
|
||||
tag_ids = None
|
||||
try:
|
||||
if settings.CONSUMER_SUBDIRS_AS_TAGS:
|
||||
@@ -81,19 +102,23 @@ def _consume_wait_unmodified(file):
|
||||
|
||||
logger.debug(f"Waiting for file {file} to remain unmodified")
|
||||
mtime = -1
|
||||
size = -1
|
||||
current_try = 0
|
||||
while current_try < settings.CONSUMER_POLLING_RETRY_COUNT:
|
||||
try:
|
||||
new_mtime = os.stat(file).st_mtime
|
||||
stat_data = os.stat(file)
|
||||
new_mtime = stat_data.st_mtime
|
||||
new_size = stat_data.st_size
|
||||
except FileNotFoundError:
|
||||
logger.debug(
|
||||
f"File {file} moved while waiting for it to remain " f"unmodified.",
|
||||
)
|
||||
return
|
||||
if new_mtime == mtime:
|
||||
if new_mtime == mtime and new_size == size:
|
||||
_consume(file)
|
||||
return
|
||||
mtime = new_mtime
|
||||
size = new_size
|
||||
sleep(settings.CONSUMER_POLLING_DELAY)
|
||||
current_try += 1
|
||||
|
||||
@@ -182,14 +207,32 @@ class Command(BaseCommand):
|
||||
descriptor = inotify.add_watch(directory, inotify_flags)
|
||||
|
||||
try:
|
||||
|
||||
inotify_debounce: Final[float] = 0.5
|
||||
notified_files = {}
|
||||
|
||||
while not self.stop_flag:
|
||||
|
||||
for event in inotify.read(timeout=1000):
|
||||
if recursive:
|
||||
path = inotify.get_path(event.wd)
|
||||
else:
|
||||
path = directory
|
||||
filepath = os.path.join(path, event.name)
|
||||
_consume(filepath)
|
||||
notified_files[filepath] = monotonic()
|
||||
|
||||
# Check the files against the timeout
|
||||
still_waiting = {}
|
||||
for filepath in notified_files:
|
||||
# Time of the last inotify event for this file
|
||||
last_event_time = notified_files[filepath]
|
||||
if (monotonic() - last_event_time) > inotify_debounce:
|
||||
_consume(filepath)
|
||||
else:
|
||||
still_waiting[filepath] = last_event_time
|
||||
# These files are still waiting to hit the timeout
|
||||
notified_files = still_waiting
|
||||
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
|
@@ -60,7 +60,7 @@ def match_tags(document, classifier):
|
||||
def matches(matching_model, document):
|
||||
search_kwargs = {}
|
||||
|
||||
document_content = document.content.lower()
|
||||
document_content = document.content
|
||||
|
||||
# Check that match is not empty
|
||||
if matching_model.match.strip() == "":
|
||||
|
@@ -0,0 +1,20 @@
|
||||
# Generated by Django 4.0.3 on 2022-04-01 22:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("documents", "1017_alter_savedviewfilterrule_rule_type"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="savedviewfilterrule",
|
||||
name="value",
|
||||
field=models.CharField(
|
||||
blank=True, max_length=255, null=True, verbose_name="value"
|
||||
),
|
||||
),
|
||||
]
|
@@ -375,7 +375,7 @@ class SavedViewFilterRule(models.Model):
|
||||
|
||||
rule_type = models.PositiveIntegerField(_("rule type"), choices=RULE_TYPES)
|
||||
|
||||
value = models.CharField(_("value"), max_length=128, blank=True, null=True)
|
||||
value = models.CharField(_("value"), max_length=255, blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("filter rule")
|
||||
|
@@ -23,6 +23,7 @@ from documents.signals import document_consumer_declaration
|
||||
# - XX. MONTH ZZZZ with XX being 1 or 2 and ZZZZ being 2 or 4 digits
|
||||
# - MONTH ZZZZ, with ZZZZ being 4 digits
|
||||
# - MONTH XX, ZZZZ with XX being 1 or 2 and ZZZZ being 4 digits
|
||||
# - XX MON ZZZZ with XX being 1 or 2 and ZZZZ being 4 digits. MONTH is 3 letters
|
||||
|
||||
# TODO: isnt there a date parsing library for this?
|
||||
|
||||
@@ -31,7 +32,8 @@ DATE_REGEX = re.compile(
|
||||
r"(\b|(?!=([_-])))([0-9]{4}|[0-9]{2})[\.\/-]([0-9]{1,2})[\.\/-]([0-9]{1,2})(\b|(?=([_-])))|" # noqa: E501
|
||||
r"(\b|(?!=([_-])))([0-9]{1,2}[\. ]+[^ ]{3,9} ([0-9]{4}|[0-9]{2}))(\b|(?=([_-])))|" # noqa: E501
|
||||
r"(\b|(?!=([_-])))([^\W\d_]{3,9} [0-9]{1,2}, ([0-9]{4}))(\b|(?=([_-])))|"
|
||||
r"(\b|(?!=([_-])))([^\W\d_]{3,9} [0-9]{4})(\b|(?=([_-])))",
|
||||
r"(\b|(?!=([_-])))([^\W\d_]{3,9} [0-9]{4})(\b|(?=([_-])))|"
|
||||
r"(\b|(?!=([_-])))(\b[0-9]{1,2}[ \.\/-][A-Z]{3}[ \.\/-][0-9]{4})(\b|(?=([_-])))", # noqa: E501
|
||||
)
|
||||
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.admin.models import ADDITION
|
||||
@@ -252,7 +253,7 @@ def cleanup_document_deletion(sender, instance, using, **kwargs):
|
||||
|
||||
logger.debug(f"Moving {instance.source_path} to trash at {new_file_path}")
|
||||
try:
|
||||
os.rename(instance.source_path, new_file_path)
|
||||
shutil.move(instance.source_path, new_file_path)
|
||||
except OSError as e:
|
||||
logger.error(
|
||||
f"Failed to move {instance.source_path} to trash at "
|
||||
|
@@ -1,6 +1,12 @@
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from typing import List # for type hinting. Can be removed, if only Python >3.8 is used
|
||||
|
||||
import tqdm
|
||||
from asgiref.sync import async_to_sync
|
||||
from channels.layers import get_channel_layer
|
||||
from django.conf import settings
|
||||
from django.db.models.signals import post_save
|
||||
from documents import index
|
||||
@@ -14,8 +20,12 @@ from documents.models import Document
|
||||
from documents.models import DocumentType
|
||||
from documents.models import Tag
|
||||
from documents.sanity_checker import SanityCheckFailedException
|
||||
from pdf2image import convert_from_path
|
||||
from pikepdf import Pdf
|
||||
from pyzbar import pyzbar
|
||||
from whoosh.writing import AsyncWriter
|
||||
|
||||
|
||||
logger = logging.getLogger("paperless.tasks")
|
||||
|
||||
|
||||
@@ -62,6 +72,115 @@ def train_classifier():
|
||||
logger.warning("Classifier error: " + str(e))
|
||||
|
||||
|
||||
def barcode_reader(image) -> List[str]:
|
||||
"""
|
||||
Read any barcodes contained in image
|
||||
Returns a list containing all found barcodes
|
||||
"""
|
||||
barcodes = []
|
||||
# Decode the barcode image
|
||||
detected_barcodes = pyzbar.decode(image)
|
||||
|
||||
if detected_barcodes:
|
||||
# Traverse through all the detected barcodes in image
|
||||
for barcode in detected_barcodes:
|
||||
if barcode.data:
|
||||
decoded_barcode = barcode.data.decode("utf-8")
|
||||
barcodes.append(decoded_barcode)
|
||||
logger.debug(
|
||||
f"Barcode of type {str(barcode.type)} found: {decoded_barcode}",
|
||||
)
|
||||
return barcodes
|
||||
|
||||
|
||||
def scan_file_for_separating_barcodes(filepath: str) -> List[int]:
|
||||
"""
|
||||
Scan the provided file for page separating barcodes
|
||||
Returns a list of pagenumbers, which separate the file
|
||||
"""
|
||||
separator_page_numbers = []
|
||||
separator_barcode = str(settings.CONSUMER_BARCODE_STRING)
|
||||
# use a temporary directory in case the file os too big to handle in memory
|
||||
with tempfile.TemporaryDirectory() as path:
|
||||
pages_from_path = convert_from_path(filepath, output_folder=path)
|
||||
for current_page_number, page in enumerate(pages_from_path):
|
||||
current_barcodes = barcode_reader(page)
|
||||
if separator_barcode in current_barcodes:
|
||||
separator_page_numbers.append(current_page_number)
|
||||
return separator_page_numbers
|
||||
|
||||
|
||||
def separate_pages(filepath: str, pages_to_split_on: List[int]) -> List[str]:
|
||||
"""
|
||||
Separate the provided file on the pages_to_split_on.
|
||||
The pages which are defined by page_numbers will be removed.
|
||||
Returns a list of (temporary) filepaths to consume.
|
||||
These will need to be deleted later.
|
||||
"""
|
||||
os.makedirs(settings.SCRATCH_DIR, exist_ok=True)
|
||||
tempdir = tempfile.mkdtemp(prefix="paperless-", dir=settings.SCRATCH_DIR)
|
||||
fname = os.path.splitext(os.path.basename(filepath))[0]
|
||||
pdf = Pdf.open(filepath)
|
||||
document_paths = []
|
||||
logger.debug(f"Temp dir is {str(tempdir)}")
|
||||
if not pages_to_split_on:
|
||||
logger.warning("No pages to split on!")
|
||||
else:
|
||||
# go from the first page to the first separator page
|
||||
dst = Pdf.new()
|
||||
for n, page in enumerate(pdf.pages):
|
||||
if n < pages_to_split_on[0]:
|
||||
dst.pages.append(page)
|
||||
output_filename = "{}_document_0.pdf".format(fname)
|
||||
savepath = os.path.join(tempdir, output_filename)
|
||||
with open(savepath, "wb") as out:
|
||||
dst.save(out)
|
||||
document_paths = [savepath]
|
||||
|
||||
# iterate through the rest of the document
|
||||
for count, page_number in enumerate(pages_to_split_on):
|
||||
logger.debug(f"Count: {str(count)} page_number: {str(page_number)}")
|
||||
dst = Pdf.new()
|
||||
try:
|
||||
next_page = pages_to_split_on[count + 1]
|
||||
except IndexError:
|
||||
next_page = len(pdf.pages)
|
||||
# skip the first page_number. This contains the barcode page
|
||||
for page in range(page_number + 1, next_page):
|
||||
logger.debug(
|
||||
f"page_number: {str(page_number)} next_page: {str(next_page)}",
|
||||
)
|
||||
dst.pages.append(pdf.pages[page])
|
||||
output_filename = "{}_document_{}.pdf".format(fname, str(count + 1))
|
||||
logger.debug(f"pdf no:{str(count)} has {str(len(dst.pages))} pages")
|
||||
savepath = os.path.join(tempdir, output_filename)
|
||||
with open(savepath, "wb") as out:
|
||||
dst.save(out)
|
||||
document_paths.append(savepath)
|
||||
logger.debug(f"Temp files are {str(document_paths)}")
|
||||
return document_paths
|
||||
|
||||
|
||||
def save_to_dir(
|
||||
filepath: str,
|
||||
newname: str = None,
|
||||
target_dir: str = settings.CONSUMPTION_DIR,
|
||||
):
|
||||
"""
|
||||
Copies filepath to target_dir.
|
||||
Optionally rename the file.
|
||||
"""
|
||||
if os.path.isfile(filepath) and os.path.isdir(target_dir):
|
||||
dst = shutil.copy(filepath, target_dir)
|
||||
logging.debug(f"saved {str(filepath)} to {str(dst)}")
|
||||
if newname:
|
||||
dst_new = os.path.join(target_dir, newname)
|
||||
logger.debug(f"moving {str(dst)} to {str(dst_new)}")
|
||||
os.rename(dst, dst_new)
|
||||
else:
|
||||
logger.warning(f"{str(filepath)} or {str(target_dir)} don't exist.")
|
||||
|
||||
|
||||
def consume_file(
|
||||
path,
|
||||
override_filename=None,
|
||||
@@ -72,6 +191,48 @@ def consume_file(
|
||||
task_id=None,
|
||||
):
|
||||
|
||||
# check for separators in current document
|
||||
if settings.CONSUMER_ENABLE_BARCODES:
|
||||
separators = []
|
||||
document_list = []
|
||||
separators = scan_file_for_separating_barcodes(path)
|
||||
if separators:
|
||||
logger.debug(f"Pages with separators found in: {str(path)}")
|
||||
document_list = separate_pages(path, separators)
|
||||
if document_list:
|
||||
for n, document in enumerate(document_list):
|
||||
# save to consumption dir
|
||||
# rename it to the original filename with number prefix
|
||||
if override_filename:
|
||||
newname = f"{str(n)}_" + override_filename
|
||||
else:
|
||||
newname = None
|
||||
save_to_dir(document, newname=newname)
|
||||
# if we got here, the document was successfully split
|
||||
# and can safely be deleted
|
||||
logger.debug("Deleting file {}".format(path))
|
||||
os.unlink(path)
|
||||
# notify the sender, otherwise the progress bar
|
||||
# in the UI stays stuck
|
||||
payload = {
|
||||
"filename": override_filename,
|
||||
"task_id": task_id,
|
||||
"current_progress": 100,
|
||||
"max_progress": 100,
|
||||
"status": "SUCCESS",
|
||||
"message": "finished",
|
||||
}
|
||||
try:
|
||||
async_to_sync(get_channel_layer().group_send)(
|
||||
"status_updates",
|
||||
{"type": "status_update", "data": payload},
|
||||
)
|
||||
except OSError as e:
|
||||
logger.warning("OSError. It could be, the broker cannot be reached.")
|
||||
logger.warning(str(e))
|
||||
return "File successfully split"
|
||||
|
||||
# continue with consumption if no barcode was found
|
||||
document = Consumer().try_consume_file(
|
||||
path,
|
||||
override_filename=override_filename,
|
||||
|
@@ -23,8 +23,10 @@
|
||||
<script type="text/javascript">
|
||||
setTimeout(() => {
|
||||
let warning = document.getElementsByClassName('warning').item(0)
|
||||
warning.classList.remove('hide')
|
||||
warning.classList.add('show')
|
||||
if (warning) {
|
||||
warning.classList.remove('hide')
|
||||
warning.classList.add('show')
|
||||
}
|
||||
}, 8000)
|
||||
</script>
|
||||
<style type="text/css">
|
||||
|
BIN
src/documents/tests/samples/barcodes/barcode-128-PATCHT.png
Normal file
After Width: | Height: | Size: 836 B |
BIN
src/documents/tests/samples/barcodes/barcode-128-custom.pdf
Normal file
BIN
src/documents/tests/samples/barcodes/barcode-128-custom.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 9.5 KiB |
BIN
src/documents/tests/samples/barcodes/barcode-39-PATCHT.png
Normal file
After Width: | Height: | Size: 891 B |
243
src/documents/tests/samples/barcodes/barcode-39-custom.pdf
Normal file
BIN
src/documents/tests/samples/barcodes/barcode-39-custom.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
src/documents/tests/samples/barcodes/barcode-qr-custom.pdf
Normal file
BIN
src/documents/tests/samples/barcodes/barcode-qr-custom.png
Normal file
After Width: | Height: | Size: 337 B |
BIN
src/documents/tests/samples/barcodes/patch-code-t-middle.pdf
Normal file
BIN
src/documents/tests/samples/barcodes/patch-code-t-qr.pdf
Normal file
BIN
src/documents/tests/samples/barcodes/patch-code-t.pbm
Normal file
BIN
src/documents/tests/samples/barcodes/patch-code-t.pdf
Normal file
BIN
src/documents/tests/samples/barcodes/qr-code-PATCHT.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
src/documents/tests/samples/barcodes/several-patcht-codes.pdf
Normal file
@@ -100,6 +100,57 @@ class TestDate(TestCase):
|
||||
datetime.datetime(2020, 3, 1, 0, 0, tzinfo=tz.gettz(settings.TIME_ZONE)),
|
||||
)
|
||||
|
||||
def test_date_format_10(self):
|
||||
text = "Customer Number Currency 22-MAR-2022 Credit Card 1934829304"
|
||||
self.assertEqual(
|
||||
parse_date("", text),
|
||||
datetime.datetime(2022, 3, 22, 0, 0, tzinfo=tz.gettz(settings.TIME_ZONE)),
|
||||
)
|
||||
|
||||
def test_date_format_11(self):
|
||||
text = "Customer Number Currency 22 MAR 2022 Credit Card 1934829304"
|
||||
self.assertEqual(
|
||||
parse_date("", text),
|
||||
datetime.datetime(2022, 3, 22, 0, 0, tzinfo=tz.gettz(settings.TIME_ZONE)),
|
||||
)
|
||||
|
||||
def test_date_format_12(self):
|
||||
text = "Customer Number Currency 22/MAR/2022 Credit Card 1934829304"
|
||||
self.assertEqual(
|
||||
parse_date("", text),
|
||||
datetime.datetime(2022, 3, 22, 0, 0, tzinfo=tz.gettz(settings.TIME_ZONE)),
|
||||
)
|
||||
|
||||
def test_date_format_13(self):
|
||||
text = "Customer Number Currency 22.MAR.2022 Credit Card 1934829304"
|
||||
self.assertEqual(
|
||||
parse_date("", text),
|
||||
datetime.datetime(2022, 3, 22, 0, 0, tzinfo=tz.gettz(settings.TIME_ZONE)),
|
||||
)
|
||||
|
||||
def test_date_format_14(self):
|
||||
text = "Customer Number Currency 22.MAR 2022 Credit Card 1934829304"
|
||||
self.assertEqual(
|
||||
parse_date("", text),
|
||||
datetime.datetime(2022, 3, 22, 0, 0, tzinfo=tz.gettz(settings.TIME_ZONE)),
|
||||
)
|
||||
|
||||
def test_date_format_15(self):
|
||||
text = "Customer Number Currency 22.MAR.22 Credit Card 1934829304"
|
||||
self.assertIsNone(parse_date("", text), None)
|
||||
|
||||
def test_date_format_16(self):
|
||||
text = "Customer Number Currency 22.MAR,22 Credit Card 1934829304"
|
||||
self.assertIsNone(parse_date("", text), None)
|
||||
|
||||
def test_date_format_17(self):
|
||||
text = "Customer Number Currency 22,MAR,2022 Credit Card 1934829304"
|
||||
self.assertIsNone(parse_date("", text), None)
|
||||
|
||||
def test_date_format_18(self):
|
||||
text = "Customer Number Currency 22 MAR,2022 Credit Card 1934829304"
|
||||
self.assertIsNone(parse_date("", text), None)
|
||||
|
||||
def test_crazy_date_past(self, *args):
|
||||
self.assertIsNone(parse_date("", "01-07-0590 00:00:00"))
|
||||
|
||||
|
@@ -260,6 +260,21 @@ class TestConsumer(DirectoriesMixin, ConsumerMixin, TransactionTestCase):
|
||||
f'_is_ignored("{file_path}") != {expected_ignored}',
|
||||
)
|
||||
|
||||
@mock.patch("documents.management.commands.document_consumer.open")
|
||||
def test_consume_file_busy(self, open_mock):
|
||||
|
||||
# Calling this mock always raises this
|
||||
open_mock.side_effect = OSError
|
||||
|
||||
self.t_start()
|
||||
|
||||
f = os.path.join(self.dirs.consumption_dir, "my_file.pdf")
|
||||
shutil.copy(self.sample_file, f)
|
||||
|
||||
self.wait_for_task_mock_call()
|
||||
|
||||
self.task_mock.assert_not_called()
|
||||
|
||||
|
||||
@override_settings(
|
||||
CONSUMER_POLLING=1,
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import shutil
|
||||
import tempfile
|
||||
from random import randint
|
||||
from typing import Iterable
|
||||
|
||||
from django.contrib.admin.models import LogEntry
|
||||
from django.contrib.auth.models import User
|
||||
@@ -15,27 +16,37 @@ from ..models import Tag
|
||||
from ..signals import document_consumption_finished
|
||||
|
||||
|
||||
class TestMatching(TestCase):
|
||||
def _test_matching(self, text, algorithm, true, false):
|
||||
class _TestMatchingBase(TestCase):
|
||||
def _test_matching(
|
||||
self,
|
||||
match_text: str,
|
||||
match_algorithm: str,
|
||||
should_match: Iterable[str],
|
||||
no_match: Iterable[str],
|
||||
case_sensitive: bool = False,
|
||||
):
|
||||
for klass in (Tag, Correspondent, DocumentType):
|
||||
instance = klass.objects.create(
|
||||
name=str(randint(10000, 99999)),
|
||||
match=text,
|
||||
matching_algorithm=getattr(klass, algorithm),
|
||||
match=match_text,
|
||||
matching_algorithm=getattr(klass, match_algorithm),
|
||||
is_insensitive=not case_sensitive,
|
||||
)
|
||||
for string in true:
|
||||
for string in should_match:
|
||||
doc = Document(content=string)
|
||||
self.assertTrue(
|
||||
matching.matches(instance, doc),
|
||||
'"%s" should match "%s" but it does not' % (text, string),
|
||||
'"%s" should match "%s" but it does not' % (match_text, string),
|
||||
)
|
||||
for string in false:
|
||||
for string in no_match:
|
||||
doc = Document(content=string)
|
||||
self.assertFalse(
|
||||
matching.matches(instance, doc),
|
||||
'"%s" should not match "%s" but it does' % (text, string),
|
||||
'"%s" should not match "%s" but it does' % (match_text, string),
|
||||
)
|
||||
|
||||
|
||||
class TestMatching(_TestMatchingBase):
|
||||
def test_match_all(self):
|
||||
|
||||
self._test_matching(
|
||||
@@ -202,6 +213,169 @@ class TestMatching(TestCase):
|
||||
)
|
||||
|
||||
|
||||
class TestCaseSensitiveMatching(_TestMatchingBase):
|
||||
def test_match_all(self):
|
||||
self._test_matching(
|
||||
"alpha charlie gamma",
|
||||
"MATCH_ALL",
|
||||
(
|
||||
"I have alpha, charlie, and gamma in me",
|
||||
"I have gamma, charlie, and alpha in me",
|
||||
),
|
||||
(
|
||||
"I have Alpha, charlie, and gamma in me",
|
||||
"I have gamma, Charlie, and alpha in me",
|
||||
"I have alpha, charlie, and Gamma in me",
|
||||
"I have gamma, charlie, and ALPHA in me",
|
||||
),
|
||||
case_sensitive=True,
|
||||
)
|
||||
|
||||
self._test_matching(
|
||||
"Alpha charlie Gamma",
|
||||
"MATCH_ALL",
|
||||
(
|
||||
"I have Alpha, charlie, and Gamma in me",
|
||||
"I have Gamma, charlie, and Alpha in me",
|
||||
),
|
||||
(
|
||||
"I have Alpha, charlie, and gamma in me",
|
||||
"I have gamma, charlie, and alpha in me",
|
||||
"I have alpha, charlie, and Gamma in me",
|
||||
"I have Gamma, Charlie, and ALPHA in me",
|
||||
),
|
||||
case_sensitive=True,
|
||||
)
|
||||
|
||||
self._test_matching(
|
||||
'brown fox "lazy dogs"',
|
||||
"MATCH_ALL",
|
||||
(
|
||||
"the quick brown fox jumped over the lazy dogs",
|
||||
"the quick brown fox jumped over the lazy dogs",
|
||||
),
|
||||
(
|
||||
"the quick Brown fox jumped over the lazy dogs",
|
||||
"the quick brown Fox jumped over the lazy dogs",
|
||||
"the quick brown fox jumped over the Lazy dogs",
|
||||
"the quick brown fox jumped over the lazy Dogs",
|
||||
),
|
||||
case_sensitive=True,
|
||||
)
|
||||
|
||||
def test_match_any(self):
|
||||
self._test_matching(
|
||||
"alpha charlie gamma",
|
||||
"MATCH_ANY",
|
||||
(
|
||||
"I have alpha in me",
|
||||
"I have charlie in me",
|
||||
"I have gamma in me",
|
||||
"I have alpha, charlie, and gamma in me",
|
||||
"I have alpha and charlie in me",
|
||||
),
|
||||
(
|
||||
"I have Alpha in me",
|
||||
"I have chaRLie in me",
|
||||
"I have gamMA in me",
|
||||
"I have aLPha, cHArlie, and gAMma in me",
|
||||
"I have AlphA and CharlIe in me",
|
||||
),
|
||||
case_sensitive=True,
|
||||
)
|
||||
|
||||
self._test_matching(
|
||||
"Alpha Charlie Gamma",
|
||||
"MATCH_ANY",
|
||||
(
|
||||
"I have Alpha in me",
|
||||
"I have Charlie in me",
|
||||
"I have Gamma in me",
|
||||
"I have Alpha, Charlie, and Gamma in me",
|
||||
"I have Alpha and Charlie in me",
|
||||
),
|
||||
(
|
||||
"I have alpha in me",
|
||||
"I have ChaRLie in me",
|
||||
"I have GamMA in me",
|
||||
"I have ALPha, CHArlie, and GAMma in me",
|
||||
"I have AlphA and CharlIe in me",
|
||||
),
|
||||
case_sensitive=True,
|
||||
)
|
||||
|
||||
self._test_matching(
|
||||
'"brown fox" " lazy dogs "',
|
||||
"MATCH_ANY",
|
||||
(
|
||||
"the quick brown fox",
|
||||
"jumped over the lazy dogs.",
|
||||
),
|
||||
(
|
||||
"the quick Brown fox",
|
||||
"jumped over the lazy Dogs.",
|
||||
),
|
||||
case_sensitive=True,
|
||||
)
|
||||
|
||||
def test_match_literal(self):
|
||||
|
||||
self._test_matching(
|
||||
"alpha charlie gamma",
|
||||
"MATCH_LITERAL",
|
||||
("I have 'alpha charlie gamma' in me",),
|
||||
(
|
||||
"I have 'Alpha charlie gamma' in me",
|
||||
"I have 'alpha Charlie gamma' in me",
|
||||
"I have 'alpha charlie Gamma' in me",
|
||||
"I have 'Alpha Charlie Gamma' in me",
|
||||
),
|
||||
case_sensitive=True,
|
||||
)
|
||||
|
||||
self._test_matching(
|
||||
"Alpha Charlie Gamma",
|
||||
"MATCH_LITERAL",
|
||||
("I have 'Alpha Charlie Gamma' in me",),
|
||||
(
|
||||
"I have 'Alpha charlie gamma' in me",
|
||||
"I have 'alpha Charlie gamma' in me",
|
||||
"I have 'alpha charlie Gamma' in me",
|
||||
"I have 'alpha charlie gamma' in me",
|
||||
),
|
||||
case_sensitive=True,
|
||||
)
|
||||
|
||||
def test_match_regex(self):
|
||||
self._test_matching(
|
||||
r"alpha\w+gamma",
|
||||
"MATCH_REGEX",
|
||||
(
|
||||
"I have alpha_and_gamma in me",
|
||||
"I have alphas_and_gamma in me",
|
||||
),
|
||||
(
|
||||
"I have Alpha_and_Gamma in me",
|
||||
"I have alpHAs_and_gaMMa in me",
|
||||
),
|
||||
case_sensitive=True,
|
||||
)
|
||||
|
||||
self._test_matching(
|
||||
r"Alpha\w+gamma",
|
||||
"MATCH_REGEX",
|
||||
(
|
||||
"I have Alpha_and_gamma in me",
|
||||
"I have Alphas_and_gamma in me",
|
||||
),
|
||||
(
|
||||
"I have Alpha_and_Gamma in me",
|
||||
"I have alphas_and_gamma in me",
|
||||
),
|
||||
case_sensitive=True,
|
||||
)
|
||||
|
||||
|
||||
@override_settings(POST_CONSUME_SCRIPT=None)
|
||||
class TestDocumentConsumptionFinishedSignal(TestCase):
|
||||
"""
|
||||
|
@@ -1,7 +1,10 @@
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from unittest import mock
|
||||
|
||||
from django.conf import settings
|
||||
from django.test import override_settings
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
from documents import tasks
|
||||
@@ -12,6 +15,7 @@ from documents.models import Tag
|
||||
from documents.sanity_checker import SanityCheckFailedException
|
||||
from documents.sanity_checker import SanityCheckMessages
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
from PIL import Image
|
||||
|
||||
|
||||
class TestTasks(DirectoriesMixin, TestCase):
|
||||
@@ -89,6 +93,318 @@ class TestTasks(DirectoriesMixin, TestCase):
|
||||
mtime3 = os.stat(settings.MODEL_FILE).st_mtime
|
||||
self.assertNotEqual(mtime2, mtime3)
|
||||
|
||||
def test_barcode_reader(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"barcode-39-PATCHT.png",
|
||||
)
|
||||
img = Image.open(test_file)
|
||||
separator_barcode = str(settings.CONSUMER_BARCODE_STRING)
|
||||
self.assertEqual(tasks.barcode_reader(img), [separator_barcode])
|
||||
|
||||
def test_barcode_reader2(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"patch-code-t.pbm",
|
||||
)
|
||||
img = Image.open(test_file)
|
||||
separator_barcode = str(settings.CONSUMER_BARCODE_STRING)
|
||||
self.assertEqual(tasks.barcode_reader(img), [separator_barcode])
|
||||
|
||||
def test_barcode_reader_distorsion(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"barcode-39-PATCHT-distorsion.png",
|
||||
)
|
||||
img = Image.open(test_file)
|
||||
separator_barcode = str(settings.CONSUMER_BARCODE_STRING)
|
||||
self.assertEqual(tasks.barcode_reader(img), [separator_barcode])
|
||||
|
||||
def test_barcode_reader_distorsion2(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"barcode-39-PATCHT-distorsion2.png",
|
||||
)
|
||||
img = Image.open(test_file)
|
||||
separator_barcode = str(settings.CONSUMER_BARCODE_STRING)
|
||||
self.assertEqual(tasks.barcode_reader(img), [separator_barcode])
|
||||
|
||||
def test_barcode_reader_unreadable(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"barcode-39-PATCHT-unreadable.png",
|
||||
)
|
||||
img = Image.open(test_file)
|
||||
self.assertEqual(tasks.barcode_reader(img), [])
|
||||
|
||||
def test_barcode_reader_qr(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"qr-code-PATCHT.png",
|
||||
)
|
||||
img = Image.open(test_file)
|
||||
separator_barcode = str(settings.CONSUMER_BARCODE_STRING)
|
||||
self.assertEqual(tasks.barcode_reader(img), [separator_barcode])
|
||||
|
||||
def test_barcode_reader_128(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"barcode-128-PATCHT.png",
|
||||
)
|
||||
img = Image.open(test_file)
|
||||
separator_barcode = str(settings.CONSUMER_BARCODE_STRING)
|
||||
self.assertEqual(tasks.barcode_reader(img), [separator_barcode])
|
||||
|
||||
def test_barcode_reader_no_barcode(self):
|
||||
test_file = os.path.join(os.path.dirname(__file__), "samples", "simple.png")
|
||||
img = Image.open(test_file)
|
||||
self.assertEqual(tasks.barcode_reader(img), [])
|
||||
|
||||
def test_barcode_reader_custom_separator(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"barcode-39-custom.png",
|
||||
)
|
||||
img = Image.open(test_file)
|
||||
self.assertEqual(tasks.barcode_reader(img), ["CUSTOM BARCODE"])
|
||||
|
||||
def test_barcode_reader_custom_qr_separator(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"barcode-qr-custom.png",
|
||||
)
|
||||
img = Image.open(test_file)
|
||||
self.assertEqual(tasks.barcode_reader(img), ["CUSTOM BARCODE"])
|
||||
|
||||
def test_barcode_reader_custom_128_separator(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"barcode-128-custom.png",
|
||||
)
|
||||
img = Image.open(test_file)
|
||||
self.assertEqual(tasks.barcode_reader(img), ["CUSTOM BARCODE"])
|
||||
|
||||
def test_scan_file_for_separating_barcodes(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"patch-code-t.pdf",
|
||||
)
|
||||
pages = tasks.scan_file_for_separating_barcodes(test_file)
|
||||
self.assertEqual(pages, [0])
|
||||
|
||||
def test_scan_file_for_separating_barcodes2(self):
|
||||
test_file = os.path.join(os.path.dirname(__file__), "samples", "simple.pdf")
|
||||
pages = tasks.scan_file_for_separating_barcodes(test_file)
|
||||
self.assertEqual(pages, [])
|
||||
|
||||
def test_scan_file_for_separating_barcodes3(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"patch-code-t-middle.pdf",
|
||||
)
|
||||
pages = tasks.scan_file_for_separating_barcodes(test_file)
|
||||
self.assertEqual(pages, [1])
|
||||
|
||||
def test_scan_file_for_separating_barcodes4(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"several-patcht-codes.pdf",
|
||||
)
|
||||
pages = tasks.scan_file_for_separating_barcodes(test_file)
|
||||
self.assertEqual(pages, [2, 5])
|
||||
|
||||
def test_scan_file_for_separating_barcodes_upsidedown(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"patch-code-t-middle_reverse.pdf",
|
||||
)
|
||||
pages = tasks.scan_file_for_separating_barcodes(test_file)
|
||||
self.assertEqual(pages, [1])
|
||||
|
||||
def test_scan_file_for_separating_qr_barcodes(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"patch-code-t-qr.pdf",
|
||||
)
|
||||
pages = tasks.scan_file_for_separating_barcodes(test_file)
|
||||
self.assertEqual(pages, [0])
|
||||
|
||||
@override_settings(CONSUMER_BARCODE_STRING="CUSTOM BARCODE")
|
||||
def test_scan_file_for_separating_custom_barcodes(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"barcode-39-custom.pdf",
|
||||
)
|
||||
pages = tasks.scan_file_for_separating_barcodes(test_file)
|
||||
self.assertEqual(pages, [0])
|
||||
|
||||
@override_settings(CONSUMER_BARCODE_STRING="CUSTOM BARCODE")
|
||||
def test_scan_file_for_separating_custom_qr_barcodes(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"barcode-qr-custom.pdf",
|
||||
)
|
||||
pages = tasks.scan_file_for_separating_barcodes(test_file)
|
||||
self.assertEqual(pages, [0])
|
||||
|
||||
@override_settings(CONSUMER_BARCODE_STRING="CUSTOM BARCODE")
|
||||
def test_scan_file_for_separating_custom_128_barcodes(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"barcode-128-custom.pdf",
|
||||
)
|
||||
pages = tasks.scan_file_for_separating_barcodes(test_file)
|
||||
self.assertEqual(pages, [0])
|
||||
|
||||
def test_scan_file_for_separating_wrong_qr_barcodes(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"barcode-39-custom.pdf",
|
||||
)
|
||||
pages = tasks.scan_file_for_separating_barcodes(test_file)
|
||||
self.assertEqual(pages, [])
|
||||
|
||||
def test_separate_pages(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"patch-code-t-middle.pdf",
|
||||
)
|
||||
pages = tasks.separate_pages(test_file, [1])
|
||||
self.assertEqual(len(pages), 2)
|
||||
|
||||
def test_separate_pages_no_list(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"patch-code-t-middle.pdf",
|
||||
)
|
||||
with self.assertLogs("paperless.tasks", level="WARNING") as cm:
|
||||
pages = tasks.separate_pages(test_file, [])
|
||||
self.assertEqual(pages, [])
|
||||
self.assertEqual(
|
||||
cm.output,
|
||||
[
|
||||
f"WARNING:paperless.tasks:No pages to split on!",
|
||||
],
|
||||
)
|
||||
|
||||
def test_save_to_dir(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"patch-code-t.pdf",
|
||||
)
|
||||
tempdir = tempfile.mkdtemp(prefix="paperless-", dir=settings.SCRATCH_DIR)
|
||||
tasks.save_to_dir(test_file, target_dir=tempdir)
|
||||
target_file = os.path.join(tempdir, "patch-code-t.pdf")
|
||||
self.assertTrue(os.path.isfile(target_file))
|
||||
|
||||
def test_save_to_dir2(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"patch-code-t.pdf",
|
||||
)
|
||||
nonexistingdir = "/nowhere"
|
||||
if os.path.isdir(nonexistingdir):
|
||||
self.fail("non-existing dir exists")
|
||||
else:
|
||||
with self.assertLogs("paperless.tasks", level="WARNING") as cm:
|
||||
tasks.save_to_dir(test_file, target_dir=nonexistingdir)
|
||||
self.assertEqual(
|
||||
cm.output,
|
||||
[
|
||||
f"WARNING:paperless.tasks:{str(test_file)} or {str(nonexistingdir)} don't exist.",
|
||||
],
|
||||
)
|
||||
|
||||
def test_save_to_dir3(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"patch-code-t.pdf",
|
||||
)
|
||||
tempdir = tempfile.mkdtemp(prefix="paperless-", dir=settings.SCRATCH_DIR)
|
||||
tasks.save_to_dir(test_file, newname="newname.pdf", target_dir=tempdir)
|
||||
target_file = os.path.join(tempdir, "newname.pdf")
|
||||
self.assertTrue(os.path.isfile(target_file))
|
||||
|
||||
def test_barcode_splitter(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"patch-code-t-middle.pdf",
|
||||
)
|
||||
tempdir = tempfile.mkdtemp(prefix="paperless-", dir=settings.SCRATCH_DIR)
|
||||
separators = tasks.scan_file_for_separating_barcodes(test_file)
|
||||
self.assertTrue(separators)
|
||||
document_list = tasks.separate_pages(test_file, separators)
|
||||
self.assertTrue(document_list)
|
||||
for document in document_list:
|
||||
tasks.save_to_dir(document, target_dir=tempdir)
|
||||
target_file1 = os.path.join(tempdir, "patch-code-t-middle_document_0.pdf")
|
||||
target_file2 = os.path.join(tempdir, "patch-code-t-middle_document_1.pdf")
|
||||
self.assertTrue(os.path.isfile(target_file1))
|
||||
self.assertTrue(os.path.isfile(target_file2))
|
||||
|
||||
@override_settings(CONSUMER_ENABLE_BARCODES=True)
|
||||
def test_consume_barcode_file(self):
|
||||
test_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"samples",
|
||||
"barcodes",
|
||||
"patch-code-t-middle.pdf",
|
||||
)
|
||||
dst = os.path.join(settings.SCRATCH_DIR, "patch-code-t-middle.pd")
|
||||
shutil.copy(test_file, dst)
|
||||
|
||||
self.assertEqual(tasks.consume_file(dst), "File successfully split")
|
||||
|
||||
@mock.patch("documents.tasks.sanity_checker.check_sanity")
|
||||
def test_sanity_check_success(self, m):
|
||||
m.return_value = SanityCheckMessages()
|
||||
|
@@ -1,12 +1,14 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
import urllib
|
||||
import uuid
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
from time import mktime
|
||||
from unicodedata import normalize
|
||||
from urllib.parse import quote_plus
|
||||
from urllib.parse import quote
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models import Case
|
||||
@@ -24,6 +26,8 @@ from django.views.decorators.cache import cache_control
|
||||
from django.views.generic import TemplateView
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from django_q.tasks import async_task
|
||||
from packaging import version as packaging_version
|
||||
from paperless import version
|
||||
from paperless.db import GnuPG
|
||||
from paperless.views import StandardPagination
|
||||
from rest_framework import parsers
|
||||
@@ -244,7 +248,7 @@ class DocumentViewSet(
|
||||
# RFC 5987 addresses this issue
|
||||
# see https://datatracker.ietf.org/doc/html/rfc5987#section-4.2
|
||||
filename_normalized = normalize("NFKD", filename).encode("ascii", "ignore")
|
||||
filename_encoded = quote_plus(filename)
|
||||
filename_encoded = quote(filename)
|
||||
content_disposition = (
|
||||
f"{disposition}; "
|
||||
f'filename="{filename_normalized}"; '
|
||||
@@ -666,3 +670,40 @@ class BulkDownloadView(GenericAPIView):
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class RemoteVersionView(GenericAPIView):
|
||||
def get(self, request, format=None):
|
||||
remote_version = "0.0.0"
|
||||
is_greater_than_current = False
|
||||
# TODO: this can likely be removed when frontend settings are saved to DB
|
||||
feature_is_set = settings.ENABLE_UPDATE_CHECK != "default"
|
||||
if feature_is_set and settings.ENABLE_UPDATE_CHECK:
|
||||
try:
|
||||
with urllib.request.urlopen(
|
||||
"https://api.github.com/repos/"
|
||||
+ "paperless-ngx/paperless-ngx/releases/latest",
|
||||
) as response:
|
||||
remote = response.read().decode("utf-8")
|
||||
try:
|
||||
remote_json = json.loads(remote)
|
||||
remote_version = remote_json["tag_name"].replace("ngx-", "")
|
||||
except ValueError:
|
||||
logger.debug("An error occured parsing remote version json")
|
||||
except urllib.error.URLError:
|
||||
logger.debug("An error occured checking for available updates")
|
||||
|
||||
current_version = ".".join([str(_) for _ in version.__version__[:3]])
|
||||
is_greater_than_current = packaging_version.parse(
|
||||
remote_version,
|
||||
) > packaging_version.parse(
|
||||
current_version,
|
||||
)
|
||||
|
||||
return Response(
|
||||
{
|
||||
"version": remote_version,
|
||||
"update_available": is_greater_than_current,
|
||||
"feature_is_set": feature_is_set,
|
||||
},
|
||||
)
|
||||
|
714
src/locale/be_BY/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,714 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-03-02 11:20-0800\n"
|
||||
"PO-Revision-Date: 2022-03-31 10:58\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Belarusian\n"
|
||||
"Language: be_BY\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-ngx\n"
|
||||
"X-Crowdin-Project-ID: 500308\n"
|
||||
"X-Crowdin-Language: be\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 14\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:40 documents/models.py:314 paperless_mail/models.py:23
|
||||
#: paperless_mail/models.py:107
|
||||
msgid "name"
|
||||
msgstr "назва"
|
||||
|
||||
#: documents/models.py:42
|
||||
msgid "match"
|
||||
msgstr "супадзенне"
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "matching algorithm"
|
||||
msgstr "алгарытм супастаўлення"
|
||||
|
||||
#: documents/models.py:48
|
||||
msgid "is insensitive"
|
||||
msgstr "без уліку рэгістра"
|
||||
|
||||
#: documents/models.py:61 documents/models.py:104
|
||||
msgid "correspondent"
|
||||
msgstr "карэспандэнт"
|
||||
|
||||
#: documents/models.py:62
|
||||
msgid "correspondents"
|
||||
msgstr "карэспандэнты"
|
||||
|
||||
#: documents/models.py:67
|
||||
msgid "color"
|
||||
msgstr "колер"
|
||||
|
||||
#: documents/models.py:70
|
||||
msgid "is inbox tag"
|
||||
msgstr "гэта ўваходны тэг"
|
||||
|
||||
#: documents/models.py:73
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr "Пазначыць гэты тэг як тэг папкі \"Уваходныя\": Усе нядаўна спажытыя дакументы будуць пазначаны тэгамі \"Уваходныя\"."
|
||||
|
||||
#: documents/models.py:79
|
||||
msgid "tag"
|
||||
msgstr "тэг"
|
||||
|
||||
#: documents/models.py:80 documents/models.py:130
|
||||
msgid "tags"
|
||||
msgstr "тэгі"
|
||||
|
||||
#: documents/models.py:85 documents/models.py:115
|
||||
msgid "document type"
|
||||
msgstr "тып дакумента"
|
||||
|
||||
#: documents/models.py:86
|
||||
msgid "document types"
|
||||
msgstr "тыпы дакументаў"
|
||||
|
||||
#: documents/models.py:94
|
||||
msgid "Unencrypted"
|
||||
msgstr "Незашыфраваны"
|
||||
|
||||
#: documents/models.py:95
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "Зашыфравана з дапамогай GNU Privacy Guard"
|
||||
|
||||
#: documents/models.py:107
|
||||
msgid "title"
|
||||
msgstr "назва"
|
||||
|
||||
#: documents/models.py:119
|
||||
msgid "content"
|
||||
msgstr "змест"
|
||||
|
||||
#: documents/models.py:122
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr "Неапрацаваныя тэкставыя даныя дакумента. Гэта поле ў асноўным выкарыстоўваецца для пошуку."
|
||||
|
||||
#: documents/models.py:127
|
||||
msgid "mime type"
|
||||
msgstr "тып MIME"
|
||||
|
||||
#: documents/models.py:134
|
||||
msgid "checksum"
|
||||
msgstr "кантрольная сума"
|
||||
|
||||
#: documents/models.py:138
|
||||
msgid "The checksum of the original document."
|
||||
msgstr "Кантрольная сума зыходнага дакумента."
|
||||
|
||||
#: documents/models.py:142
|
||||
msgid "archive checksum"
|
||||
msgstr "кантрольная сума архіва"
|
||||
|
||||
#: documents/models.py:147
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr "Кантрольная сума архіўнага дакумента."
|
||||
|
||||
#: documents/models.py:150 documents/models.py:295
|
||||
msgid "created"
|
||||
msgstr "створаны"
|
||||
|
||||
#: documents/models.py:153
|
||||
msgid "modified"
|
||||
msgstr "мадыфікаваны"
|
||||
|
||||
#: documents/models.py:157
|
||||
msgid "storage type"
|
||||
msgstr "тып захоўвання"
|
||||
|
||||
#: documents/models.py:165
|
||||
msgid "added"
|
||||
msgstr "дададзена"
|
||||
|
||||
#: documents/models.py:169
|
||||
msgid "filename"
|
||||
msgstr "імя файла"
|
||||
|
||||
#: documents/models.py:175
|
||||
msgid "Current filename in storage"
|
||||
msgstr "Цяперашняе імя файла ў сховішчы"
|
||||
|
||||
#: documents/models.py:179
|
||||
msgid "archive filename"
|
||||
msgstr "імя файла архіва"
|
||||
|
||||
#: documents/models.py:185
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr "Цяперашняе імя файла архіва ў сховішчы"
|
||||
|
||||
#: documents/models.py:189
|
||||
msgid "archive serial number"
|
||||
msgstr "парадкавы нумар архіва"
|
||||
|
||||
#: documents/models.py:195
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr "Пазіцыя гэтага дакумента ў вашым фізічным архіве дакументаў."
|
||||
|
||||
#: documents/models.py:201
|
||||
msgid "document"
|
||||
msgstr "дакумент"
|
||||
|
||||
#: documents/models.py:202
|
||||
msgid "documents"
|
||||
msgstr "дакументы"
|
||||
|
||||
#: documents/models.py:280
|
||||
msgid "debug"
|
||||
msgstr "адладка"
|
||||
|
||||
#: documents/models.py:281
|
||||
msgid "information"
|
||||
msgstr "інфармацыя"
|
||||
|
||||
#: documents/models.py:282
|
||||
msgid "warning"
|
||||
msgstr "папярэджанне"
|
||||
|
||||
#: documents/models.py:283
|
||||
msgid "error"
|
||||
msgstr "памылка"
|
||||
|
||||
#: documents/models.py:284
|
||||
msgid "critical"
|
||||
msgstr "крытычны"
|
||||
|
||||
#: documents/models.py:287
|
||||
msgid "group"
|
||||
msgstr "група"
|
||||
|
||||
#: documents/models.py:289
|
||||
msgid "message"
|
||||
msgstr "паведамленне"
|
||||
|
||||
#: documents/models.py:292
|
||||
msgid "level"
|
||||
msgstr "узровень"
|
||||
|
||||
#: documents/models.py:299
|
||||
msgid "log"
|
||||
msgstr "лог"
|
||||
|
||||
#: documents/models.py:300
|
||||
msgid "logs"
|
||||
msgstr "логі"
|
||||
|
||||
#: documents/models.py:310 documents/models.py:360
|
||||
msgid "saved view"
|
||||
msgstr "захаваны выгляд"
|
||||
|
||||
#: documents/models.py:311
|
||||
msgid "saved views"
|
||||
msgstr "захаваныя выгляды"
|
||||
|
||||
#: documents/models.py:313
|
||||
msgid "user"
|
||||
msgstr "карыстальнік"
|
||||
|
||||
#: documents/models.py:317
|
||||
msgid "show on dashboard"
|
||||
msgstr "паказаць на панэлі"
|
||||
|
||||
#: documents/models.py:320
|
||||
msgid "show in sidebar"
|
||||
msgstr "паказаць у бакавой панэлі"
|
||||
|
||||
#: documents/models.py:324
|
||||
msgid "sort field"
|
||||
msgstr "поле сартавання"
|
||||
|
||||
#: documents/models.py:326
|
||||
msgid "sort reverse"
|
||||
msgstr "сартаваць у адваротным парадку"
|
||||
|
||||
#: documents/models.py:331
|
||||
msgid "title contains"
|
||||
msgstr "назва змяшчае"
|
||||
|
||||
#: documents/models.py:332
|
||||
msgid "content contains"
|
||||
msgstr "змест змяшчае"
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "ASN is"
|
||||
msgstr "ASN"
|
||||
|
||||
#: documents/models.py:334
|
||||
msgid "correspondent is"
|
||||
msgstr "карэспандэнт"
|
||||
|
||||
#: documents/models.py:335
|
||||
msgid "document type is"
|
||||
msgstr "тып дакумента"
|
||||
|
||||
#: documents/models.py:336
|
||||
msgid "is in inbox"
|
||||
msgstr "ва ўваходных"
|
||||
|
||||
#: documents/models.py:337
|
||||
msgid "has tag"
|
||||
msgstr "мае тэг"
|
||||
|
||||
#: documents/models.py:338
|
||||
msgid "has any tag"
|
||||
msgstr "мае любы тэг"
|
||||
|
||||
#: documents/models.py:339
|
||||
msgid "created before"
|
||||
msgstr "створана перад"
|
||||
|
||||
#: documents/models.py:340
|
||||
msgid "created after"
|
||||
msgstr "створана пасля"
|
||||
|
||||
#: documents/models.py:341
|
||||
msgid "created year is"
|
||||
msgstr "год стварэння"
|
||||
|
||||
#: documents/models.py:342
|
||||
msgid "created month is"
|
||||
msgstr "месяц стварэння"
|
||||
|
||||
#: documents/models.py:343
|
||||
msgid "created day is"
|
||||
msgstr "дзень стварэння"
|
||||
|
||||
#: documents/models.py:344
|
||||
msgid "added before"
|
||||
msgstr "даданы перад"
|
||||
|
||||
#: documents/models.py:345
|
||||
msgid "added after"
|
||||
msgstr "даданы пасля"
|
||||
|
||||
#: documents/models.py:346
|
||||
msgid "modified before"
|
||||
msgstr "зменены перад"
|
||||
|
||||
#: documents/models.py:347
|
||||
msgid "modified after"
|
||||
msgstr "зменены пасля"
|
||||
|
||||
#: documents/models.py:348
|
||||
msgid "does not have tag"
|
||||
msgstr "не мае тэга"
|
||||
|
||||
#: documents/models.py:349
|
||||
msgid "does not have ASN"
|
||||
msgstr "не мае ASN"
|
||||
|
||||
#: documents/models.py:350
|
||||
msgid "title or content contains"
|
||||
msgstr "назва або змест смяшчае"
|
||||
|
||||
#: documents/models.py:351
|
||||
msgid "fulltext query"
|
||||
msgstr "поўнатэкставы запыт"
|
||||
|
||||
#: documents/models.py:352
|
||||
msgid "more like this"
|
||||
msgstr "больш падобнага"
|
||||
|
||||
#: documents/models.py:353
|
||||
msgid "has tags in"
|
||||
msgstr "мае тэгі ў"
|
||||
|
||||
#: documents/models.py:363
|
||||
msgid "rule type"
|
||||
msgstr "тып правіла"
|
||||
|
||||
#: documents/models.py:365
|
||||
msgid "value"
|
||||
msgstr "значэнне"
|
||||
|
||||
#: documents/models.py:368
|
||||
msgid "filter rule"
|
||||
msgstr "правіла фільтрацыі"
|
||||
|
||||
#: documents/models.py:369
|
||||
msgid "filter rules"
|
||||
msgstr "правілы фільтрацыі"
|
||||
|
||||
#: documents/serialisers.py:64
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr "Няправільны рэгулярны выраз: %(error)s"
|
||||
|
||||
#: documents/serialisers.py:185
|
||||
msgid "Invalid color."
|
||||
msgstr "Няправільны колер."
|
||||
|
||||
#: documents/serialisers.py:459
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "Тып файла %(type)s не падтрымліваецца"
|
||||
|
||||
#: documents/templates/index.html:22
|
||||
msgid "Paperless-ngx is loading..."
|
||||
msgstr "Paperless-ngx загружаецца..."
|
||||
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ngx signed out"
|
||||
msgstr "Выкананы выхад з Paperless-ngx"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:59
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr "Вы паспяхова выйшлі з сістэмы. Да пабачэння!"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:60
|
||||
msgid "Sign in again"
|
||||
msgstr "Увайсці зноў"
|
||||
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ngx sign in"
|
||||
msgstr "Увайсці ў Paperless-ngx"
|
||||
|
||||
#: documents/templates/registration/login.html:61
|
||||
msgid "Please sign in."
|
||||
msgstr "Калі ласка, увайдзіце."
|
||||
|
||||
#: documents/templates/registration/login.html:64
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr "Няправільныя імя карыстальніка або пароль! Паспрабуйце яшчэ раз."
|
||||
|
||||
#: documents/templates/registration/login.html:67
|
||||
msgid "Username"
|
||||
msgstr "Імя карыстальніка"
|
||||
|
||||
#: documents/templates/registration/login.html:68
|
||||
msgid "Password"
|
||||
msgstr "Пароль"
|
||||
|
||||
#: documents/templates/registration/login.html:73
|
||||
msgid "Sign in"
|
||||
msgstr "Увайсці"
|
||||
|
||||
#: paperless/settings.py:299
|
||||
msgid "English (US)"
|
||||
msgstr "Англійская (ЗША)"
|
||||
|
||||
#: paperless/settings.py:300
|
||||
msgid "Czech"
|
||||
msgstr "Чэшская"
|
||||
|
||||
#: paperless/settings.py:301
|
||||
msgid "Danish"
|
||||
msgstr "Дацкая"
|
||||
|
||||
#: paperless/settings.py:302
|
||||
msgid "German"
|
||||
msgstr "Нямецкая"
|
||||
|
||||
#: paperless/settings.py:303
|
||||
msgid "English (GB)"
|
||||
msgstr "Англійская (Вялікабрытанія)"
|
||||
|
||||
#: paperless/settings.py:304
|
||||
msgid "Spanish"
|
||||
msgstr "Іспанская"
|
||||
|
||||
#: paperless/settings.py:305
|
||||
msgid "French"
|
||||
msgstr "Французская"
|
||||
|
||||
#: paperless/settings.py:306
|
||||
msgid "Italian"
|
||||
msgstr "Італьянская"
|
||||
|
||||
#: paperless/settings.py:307
|
||||
msgid "Luxembourgish"
|
||||
msgstr "Люксембургская"
|
||||
|
||||
#: paperless/settings.py:308
|
||||
msgid "Dutch"
|
||||
msgstr "Нідэрландская"
|
||||
|
||||
#: paperless/settings.py:309
|
||||
msgid "Polish"
|
||||
msgstr "Польская"
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "Партугальская (Бразілія)"
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Portuguese"
|
||||
msgstr "Партугальская"
|
||||
|
||||
#: paperless/settings.py:312
|
||||
msgid "Romanian"
|
||||
msgstr "Румынская"
|
||||
|
||||
#: paperless/settings.py:313
|
||||
msgid "Russian"
|
||||
msgstr "Руская"
|
||||
|
||||
#: paperless/settings.py:314
|
||||
msgid "Swedish"
|
||||
msgstr "Шведская"
|
||||
|
||||
#: paperless/urls.py:139
|
||||
msgid "Paperless-ngx administration"
|
||||
msgstr "Адміністраванне Paperless-ngx"
|
||||
|
||||
#: paperless_mail/admin.py:29
|
||||
msgid "Authentication"
|
||||
msgstr "Аўтэнтыфікацыя"
|
||||
|
||||
#: paperless_mail/admin.py:30
|
||||
msgid "Advanced settings"
|
||||
msgstr "Пашыраныя налады"
|
||||
|
||||
#: paperless_mail/admin.py:47
|
||||
msgid "Filter"
|
||||
msgstr "Фільтр"
|
||||
|
||||
#: paperless_mail/admin.py:50
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr "Paperless-ngx будзе апрацоўваць толькі лісты, якія адпавядаюць УСІМ фільтрам, прыведзеным ніжэй."
|
||||
|
||||
#: paperless_mail/admin.py:64
|
||||
msgid "Actions"
|
||||
msgstr "Дзеянні"
|
||||
|
||||
#: paperless_mail/admin.py:67
|
||||
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:75
|
||||
msgid "Metadata"
|
||||
msgstr "Метаданыя"
|
||||
|
||||
#: paperless_mail/admin.py:78
|
||||
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-ngx усё роўна будуць апрацоўваць усе адпаведныя правілы, якія вы вызначылі."
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
msgstr "Paperless-ngx пошта"
|
||||
|
||||
#: paperless_mail/models.py:10
|
||||
msgid "mail account"
|
||||
msgstr "паштовы акаўнт"
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail accounts"
|
||||
msgstr "паштовыя акаўнты"
|
||||
|
||||
#: paperless_mail/models.py:18
|
||||
msgid "No encryption"
|
||||
msgstr "Без шыфравання"
|
||||
|
||||
#: paperless_mail/models.py:19
|
||||
msgid "Use SSL"
|
||||
msgstr "Выкарыстоўваць SSL"
|
||||
|
||||
#: paperless_mail/models.py:20
|
||||
msgid "Use STARTTLS"
|
||||
msgstr "Выкарыстоўваць STARTTLS"
|
||||
|
||||
#: paperless_mail/models.py:25
|
||||
msgid "IMAP server"
|
||||
msgstr "Сервер IMAP"
|
||||
|
||||
#: paperless_mail/models.py:28
|
||||
msgid "IMAP port"
|
||||
msgstr "Порт IMAP"
|
||||
|
||||
#: paperless_mail/models.py:32
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr "Звычайна гэта 143 для незашыфраваных і STARTTLS злучэнняў і 993 для злучэнняў SSL."
|
||||
|
||||
#: paperless_mail/models.py:38
|
||||
msgid "IMAP security"
|
||||
msgstr "Бяспека IMAP"
|
||||
|
||||
#: paperless_mail/models.py:41
|
||||
msgid "username"
|
||||
msgstr "імя карыстальніка"
|
||||
|
||||
#: paperless_mail/models.py:43
|
||||
msgid "password"
|
||||
msgstr "пароль"
|
||||
|
||||
#: paperless_mail/models.py:46
|
||||
msgid "character set"
|
||||
msgstr "кадзіроўка"
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
msgstr "Кадзіроўка для сувязі з паштовым серверам, напрыклад «UTF-8» або «US-ASCII»."
|
||||
|
||||
#: paperless_mail/models.py:61
|
||||
msgid "mail rule"
|
||||
msgstr "правіла пошты"
|
||||
|
||||
#: paperless_mail/models.py:62
|
||||
msgid "mail rules"
|
||||
msgstr "правілы пошты"
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "Only process attachments."
|
||||
msgstr "Апрацоўваць толькі ўкладанні."
|
||||
|
||||
#: paperless_mail/models.py:71
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr "Апрацоўваць усе файлы, уключаючы 'убудаваныя' укладанні."
|
||||
|
||||
#: paperless_mail/models.py:81
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr "Пазначыць як прачытанае, не апрацоўваць прачытаныя лісты"
|
||||
|
||||
#: paperless_mail/models.py:82
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr "Пазначыць пошту, не апрацоўваць пазначаныя лісты"
|
||||
|
||||
#: paperless_mail/models.py:83
|
||||
msgid "Move to specified folder"
|
||||
msgstr "Перамясціць у паказаную папку"
|
||||
|
||||
#: paperless_mail/models.py:84
|
||||
msgid "Delete"
|
||||
msgstr "Выдаліць"
|
||||
|
||||
#: paperless_mail/models.py:91
|
||||
msgid "Use subject as title"
|
||||
msgstr "Тэма ў якасці загалоўка"
|
||||
|
||||
#: paperless_mail/models.py:92
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr "Выкарыстоўваць імя ўкладзенага файла як загаловак"
|
||||
|
||||
#: paperless_mail/models.py:101
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr "Не прызначаць карэспандэнта"
|
||||
|
||||
#: paperless_mail/models.py:102
|
||||
msgid "Use mail address"
|
||||
msgstr "Выкарыстоўваць email адрас"
|
||||
|
||||
#: paperless_mail/models.py:103
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr "Выкарыстоўваць імя (або адрас электроннай пошты, калі недаступна)"
|
||||
|
||||
#: paperless_mail/models.py:104
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr "Выкарыстоўваць карэспандэнта, абранага ніжэй"
|
||||
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "order"
|
||||
msgstr "парадак"
|
||||
|
||||
#: paperless_mail/models.py:115
|
||||
msgid "account"
|
||||
msgstr "ўліковы запіс"
|
||||
|
||||
#: paperless_mail/models.py:119
|
||||
msgid "folder"
|
||||
msgstr "каталог"
|
||||
|
||||
#: paperless_mail/models.py:122
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr "Падкаталогі павінны быць падзелены кропкамі."
|
||||
|
||||
#: paperless_mail/models.py:126
|
||||
msgid "filter from"
|
||||
msgstr "фільтр па адпраўніку"
|
||||
|
||||
#: paperless_mail/models.py:129
|
||||
msgid "filter subject"
|
||||
msgstr "фільтр па тэме"
|
||||
|
||||
#: paperless_mail/models.py:132
|
||||
msgid "filter body"
|
||||
msgstr "фільтр па тэксце паведамлення"
|
||||
|
||||
#: paperless_mail/models.py:136
|
||||
msgid "filter attachment filename"
|
||||
msgstr "фільтр па імені ўкладання"
|
||||
|
||||
#: paperless_mail/models.py:141
|
||||
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:148
|
||||
msgid "maximum age"
|
||||
msgstr "максімальны ўзрост"
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "Specified in days."
|
||||
msgstr "Указваецца ў днях."
|
||||
|
||||
#: paperless_mail/models.py:152
|
||||
msgid "attachment type"
|
||||
msgstr "тып укладання"
|
||||
|
||||
#: paperless_mail/models.py:156
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr "Убудаваныя ўкладанні ўключаюць убудаваныя выявы, таму лепш камбінаваць гэты варыянт з фільтрам імёнаў файла."
|
||||
|
||||
#: paperless_mail/models.py:162
|
||||
msgid "action"
|
||||
msgstr "дзеянне"
|
||||
|
||||
#: paperless_mail/models.py:168
|
||||
msgid "action parameter"
|
||||
msgstr "параметр дзеяння"
|
||||
|
||||
#: paperless_mail/models.py:173
|
||||
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:181
|
||||
msgid "assign title from"
|
||||
msgstr "прызначыць загаловак з"
|
||||
|
||||
#: paperless_mail/models.py:189
|
||||
msgid "assign this tag"
|
||||
msgstr "прызначыць гэты тэг"
|
||||
|
||||
#: paperless_mail/models.py:197
|
||||
msgid "assign this document type"
|
||||
msgstr "прызначыць гэты тып дакумента"
|
||||
|
||||
#: paperless_mail/models.py:201
|
||||
msgid "assign correspondent from"
|
||||
msgstr "прызначыць карэспандэнта з"
|
||||
|
||||
#: paperless_mail/models.py:211
|
||||
msgid "assign this correspondent"
|
||||
msgstr "прызначыць гэтага карэспандэнта"
|
||||
|
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-03-02 11:20-0800\n"
|
||||
"PO-Revision-Date: 2022-03-02 22:29\n"
|
||||
"PO-Revision-Date: 2022-03-26 21:49\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Spanish\n"
|
||||
"Language: es_ES\n"
|
||||
@@ -72,7 +72,7 @@ msgstr "interlocutores"
|
||||
|
||||
#: documents/models.py:67
|
||||
msgid "color"
|
||||
msgstr ""
|
||||
msgstr "color"
|
||||
|
||||
#: documents/models.py:70
|
||||
msgid "is inbox tag"
|
||||
@@ -200,7 +200,7 @@ msgstr "alerta"
|
||||
|
||||
#: documents/models.py:283
|
||||
msgid "error"
|
||||
msgstr ""
|
||||
msgstr "error"
|
||||
|
||||
#: documents/models.py:284
|
||||
msgid "critical"
|
||||
@@ -220,11 +220,11 @@ msgstr "nivel"
|
||||
|
||||
#: documents/models.py:299
|
||||
msgid "log"
|
||||
msgstr ""
|
||||
msgstr "log"
|
||||
|
||||
#: documents/models.py:300
|
||||
msgid "logs"
|
||||
msgstr ""
|
||||
msgstr "logs"
|
||||
|
||||
#: documents/models.py:310 documents/models.py:360
|
||||
msgid "saved view"
|
||||
@@ -344,7 +344,7 @@ msgstr "más contenido similar"
|
||||
|
||||
#: documents/models.py:353
|
||||
msgid "has tags in"
|
||||
msgstr ""
|
||||
msgstr "tiene etiquetas en"
|
||||
|
||||
#: documents/models.py:363
|
||||
msgid "rule type"
|
||||
@@ -378,11 +378,11 @@ msgstr "Tipo de fichero %(type)s no suportado"
|
||||
|
||||
#: documents/templates/index.html:22
|
||||
msgid "Paperless-ngx is loading..."
|
||||
msgstr ""
|
||||
msgstr "Paperless-ngx está cargando..."
|
||||
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ngx signed out"
|
||||
msgstr ""
|
||||
msgstr "Paperless-ngx cerró sesión"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:59
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
@@ -394,7 +394,7 @@ msgstr "Iniciar sesión de nuevo"
|
||||
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ngx sign in"
|
||||
msgstr ""
|
||||
msgstr "Paperless-ngx inicio de sesión"
|
||||
|
||||
#: documents/templates/registration/login.html:61
|
||||
msgid "Please sign in."
|
||||
@@ -422,11 +422,11 @@ msgstr "Inglés (US)"
|
||||
|
||||
#: paperless/settings.py:300
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
msgstr "Checo"
|
||||
|
||||
#: paperless/settings.py:301
|
||||
msgid "Danish"
|
||||
msgstr ""
|
||||
msgstr "Danés"
|
||||
|
||||
#: paperless/settings.py:302
|
||||
msgid "German"
|
||||
@@ -450,7 +450,7 @@ msgstr "Italiano"
|
||||
|
||||
#: paperless/settings.py:307
|
||||
msgid "Luxembourgish"
|
||||
msgstr ""
|
||||
msgstr "Luxemburgués"
|
||||
|
||||
#: paperless/settings.py:308
|
||||
msgid "Dutch"
|
||||
@@ -482,7 +482,7 @@ msgstr "Sueco"
|
||||
|
||||
#: paperless/urls.py:139
|
||||
msgid "Paperless-ngx administration"
|
||||
msgstr ""
|
||||
msgstr "Administración de Paperless-ngx"
|
||||
|
||||
#: paperless_mail/admin.py:29
|
||||
msgid "Authentication"
|
||||
|
714
src/locale/fi_FI/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,714 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-03-02 11:20-0800\n"
|
||||
"PO-Revision-Date: 2022-03-31 08:58\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Finnish\n"
|
||||
"Language: fi_FI\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-ngx\n"
|
||||
"X-Crowdin-Project-ID: 500308\n"
|
||||
"X-Crowdin-Language: fi\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 14\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
msgstr "Dokumentit"
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr "Mikä tahansa sana"
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr "Kaikki sanat"
|
||||
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr "Tarkka osuma"
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr "Säännöllinen lauseke (regex)"
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr "Sumea sana"
|
||||
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr "Automaattinen"
|
||||
|
||||
#: documents/models.py:40 documents/models.py:314 paperless_mail/models.py:23
|
||||
#: paperless_mail/models.py:107
|
||||
msgid "name"
|
||||
msgstr "nimi"
|
||||
|
||||
#: documents/models.py:42
|
||||
msgid "match"
|
||||
msgstr "osuma"
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "matching algorithm"
|
||||
msgstr "tunnistusalgoritmi"
|
||||
|
||||
#: documents/models.py:48
|
||||
msgid "is insensitive"
|
||||
msgstr "ei ole herkkä"
|
||||
|
||||
#: documents/models.py:61 documents/models.py:104
|
||||
msgid "correspondent"
|
||||
msgstr "yhteyshenkilö"
|
||||
|
||||
#: documents/models.py:62
|
||||
msgid "correspondents"
|
||||
msgstr "yhteyshenkilöt"
|
||||
|
||||
#: documents/models.py:67
|
||||
msgid "color"
|
||||
msgstr "väri"
|
||||
|
||||
#: documents/models.py:70
|
||||
msgid "is inbox tag"
|
||||
msgstr "on uusien tunniste"
|
||||
|
||||
#: documents/models.py:73
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr "Merkitsee tämän tunnisteen uusien tunnisteeksi: Kaikille vastasyötetyille tiedostoille annetaan tämä tunniste."
|
||||
|
||||
#: documents/models.py:79
|
||||
msgid "tag"
|
||||
msgstr "tunniste"
|
||||
|
||||
#: documents/models.py:80 documents/models.py:130
|
||||
msgid "tags"
|
||||
msgstr "tunnisteet"
|
||||
|
||||
#: documents/models.py:85 documents/models.py:115
|
||||
msgid "document type"
|
||||
msgstr "asiakirjatyyppi"
|
||||
|
||||
#: documents/models.py:86
|
||||
msgid "document types"
|
||||
msgstr "asiakirjatyypit"
|
||||
|
||||
#: documents/models.py:94
|
||||
msgid "Unencrypted"
|
||||
msgstr "Salaamaton"
|
||||
|
||||
#: documents/models.py:95
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "GNU Privacy Guard -salattu"
|
||||
|
||||
#: documents/models.py:107
|
||||
msgid "title"
|
||||
msgstr "otsikko"
|
||||
|
||||
#: documents/models.py:119
|
||||
msgid "content"
|
||||
msgstr "sisältö"
|
||||
|
||||
#: documents/models.py:122
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr "Raaka vain teksti -muotoinen dokumentin sisältö. Kenttää käytetään pääasiassa hakutoiminnossa."
|
||||
|
||||
#: documents/models.py:127
|
||||
msgid "mime type"
|
||||
msgstr "mime-tyyppi"
|
||||
|
||||
#: documents/models.py:134
|
||||
msgid "checksum"
|
||||
msgstr "tarkistussumma"
|
||||
|
||||
#: documents/models.py:138
|
||||
msgid "The checksum of the original document."
|
||||
msgstr "Alkuperäisen dokumentin tarkistussumma."
|
||||
|
||||
#: documents/models.py:142
|
||||
msgid "archive checksum"
|
||||
msgstr "arkistotarkastussumma"
|
||||
|
||||
#: documents/models.py:147
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr "Arkistoidun dokumentin tarkistussumma."
|
||||
|
||||
#: documents/models.py:150 documents/models.py:295
|
||||
msgid "created"
|
||||
msgstr "luotu"
|
||||
|
||||
#: documents/models.py:153
|
||||
msgid "modified"
|
||||
msgstr "muokattu"
|
||||
|
||||
#: documents/models.py:157
|
||||
msgid "storage type"
|
||||
msgstr "tallennustilan tyyppi"
|
||||
|
||||
#: documents/models.py:165
|
||||
msgid "added"
|
||||
msgstr "lisätty"
|
||||
|
||||
#: documents/models.py:169
|
||||
msgid "filename"
|
||||
msgstr "tiedostonimi"
|
||||
|
||||
#: documents/models.py:175
|
||||
msgid "Current filename in storage"
|
||||
msgstr "Tiedostonimi tallennustilassa"
|
||||
|
||||
#: documents/models.py:179
|
||||
msgid "archive filename"
|
||||
msgstr "arkistointitiedostonimi"
|
||||
|
||||
#: documents/models.py:185
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr "Tämänhetkinen arkistointitiedostoimi tallennustilassa"
|
||||
|
||||
#: documents/models.py:189
|
||||
msgid "archive serial number"
|
||||
msgstr "arkistointisarjanumero"
|
||||
|
||||
#: documents/models.py:195
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr "Dokumentin sijainti fyysisessa dokumenttiarkistossa."
|
||||
|
||||
#: documents/models.py:201
|
||||
msgid "document"
|
||||
msgstr "dokumentti"
|
||||
|
||||
#: documents/models.py:202
|
||||
msgid "documents"
|
||||
msgstr "dokumentit"
|
||||
|
||||
#: documents/models.py:280
|
||||
msgid "debug"
|
||||
msgstr "virheenjäljitys"
|
||||
|
||||
#: documents/models.py:281
|
||||
msgid "information"
|
||||
msgstr "informaatio"
|
||||
|
||||
#: documents/models.py:282
|
||||
msgid "warning"
|
||||
msgstr "varoitus"
|
||||
|
||||
#: documents/models.py:283
|
||||
msgid "error"
|
||||
msgstr "virhe"
|
||||
|
||||
#: documents/models.py:284
|
||||
msgid "critical"
|
||||
msgstr "kriittinen"
|
||||
|
||||
#: documents/models.py:287
|
||||
msgid "group"
|
||||
msgstr "ryhmä"
|
||||
|
||||
#: documents/models.py:289
|
||||
msgid "message"
|
||||
msgstr "viesti"
|
||||
|
||||
#: documents/models.py:292
|
||||
msgid "level"
|
||||
msgstr "taso"
|
||||
|
||||
#: documents/models.py:299
|
||||
msgid "log"
|
||||
msgstr "loki"
|
||||
|
||||
#: documents/models.py:300
|
||||
msgid "logs"
|
||||
msgstr "lokit"
|
||||
|
||||
#: documents/models.py:310 documents/models.py:360
|
||||
msgid "saved view"
|
||||
msgstr "tallennettu näkymä"
|
||||
|
||||
#: documents/models.py:311
|
||||
msgid "saved views"
|
||||
msgstr "tallennetut näkymät"
|
||||
|
||||
#: documents/models.py:313
|
||||
msgid "user"
|
||||
msgstr "käyttäjä"
|
||||
|
||||
#: documents/models.py:317
|
||||
msgid "show on dashboard"
|
||||
msgstr "näytä etusivulla"
|
||||
|
||||
#: documents/models.py:320
|
||||
msgid "show in sidebar"
|
||||
msgstr "näytä sivupaneelissa"
|
||||
|
||||
#: documents/models.py:324
|
||||
msgid "sort field"
|
||||
msgstr "lajittelukenttä"
|
||||
|
||||
#: documents/models.py:326
|
||||
msgid "sort reverse"
|
||||
msgstr "lajittele käänteisesti"
|
||||
|
||||
#: documents/models.py:331
|
||||
msgid "title contains"
|
||||
msgstr "otsikko sisältää"
|
||||
|
||||
#: documents/models.py:332
|
||||
msgid "content contains"
|
||||
msgstr "sisältö sisältää"
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "ASN is"
|
||||
msgstr "ASN on"
|
||||
|
||||
#: documents/models.py:334
|
||||
msgid "correspondent is"
|
||||
msgstr "yhteyshenkilö on"
|
||||
|
||||
#: documents/models.py:335
|
||||
msgid "document type is"
|
||||
msgstr "dokumenttityyppi on"
|
||||
|
||||
#: documents/models.py:336
|
||||
msgid "is in inbox"
|
||||
msgstr "on uusi"
|
||||
|
||||
#: documents/models.py:337
|
||||
msgid "has tag"
|
||||
msgstr "on tagattu"
|
||||
|
||||
#: documents/models.py:338
|
||||
msgid "has any tag"
|
||||
msgstr "on mikä tahansa tagi"
|
||||
|
||||
#: documents/models.py:339
|
||||
msgid "created before"
|
||||
msgstr "luotu ennen"
|
||||
|
||||
#: documents/models.py:340
|
||||
msgid "created after"
|
||||
msgstr "luotu jälkeen"
|
||||
|
||||
#: documents/models.py:341
|
||||
msgid "created year is"
|
||||
msgstr "luotu vuonna"
|
||||
|
||||
#: documents/models.py:342
|
||||
msgid "created month is"
|
||||
msgstr "luotu kuukautena"
|
||||
|
||||
#: documents/models.py:343
|
||||
msgid "created day is"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:344
|
||||
msgid "added before"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:345
|
||||
msgid "added after"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:346
|
||||
msgid "modified before"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:347
|
||||
msgid "modified after"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:348
|
||||
msgid "does not have tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:349
|
||||
msgid "does not have ASN"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:350
|
||||
msgid "title or content contains"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:351
|
||||
msgid "fulltext query"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:352
|
||||
msgid "more like this"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:353
|
||||
msgid "has tags in"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:363
|
||||
msgid "rule type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:365
|
||||
msgid "value"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:368
|
||||
msgid "filter rule"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:369
|
||||
msgid "filter rules"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:64
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:185
|
||||
msgid "Invalid color."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:459
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/index.html:22
|
||||
msgid "Paperless-ngx is loading..."
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ngx signed out"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/logged_out.html:59
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/logged_out.html:60
|
||||
msgid "Sign in again"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ngx sign in"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:61
|
||||
msgid "Please sign in."
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:64
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:67
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:68
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:73
|
||||
msgid "Sign in"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:299
|
||||
msgid "English (US)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:300
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:301
|
||||
msgid "Danish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:302
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:303
|
||||
msgid "English (GB)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:304
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:305
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:306
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:307
|
||||
msgid "Luxembourgish"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:308
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: paperless/settings.py:309
|
||||
msgid "Polish"
|
||||
msgstr "puola"
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "portugali (Brasilia)"
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Portuguese"
|
||||
msgstr "portugali"
|
||||
|
||||
#: paperless/settings.py:312
|
||||
msgid "Romanian"
|
||||
msgstr "romania"
|
||||
|
||||
#: paperless/settings.py:313
|
||||
msgid "Russian"
|
||||
msgstr "venäjä"
|
||||
|
||||
#: paperless/settings.py:314
|
||||
msgid "Swedish"
|
||||
msgstr "ruotsi"
|
||||
|
||||
#: paperless/urls.py:139
|
||||
msgid "Paperless-ngx administration"
|
||||
msgstr "Paperless-ngx hallintapaneeli"
|
||||
|
||||
#: paperless_mail/admin.py:29
|
||||
msgid "Authentication"
|
||||
msgstr "Autentikaatio"
|
||||
|
||||
#: paperless_mail/admin.py:30
|
||||
msgid "Advanced settings"
|
||||
msgstr "Edistyneet asetukset"
|
||||
|
||||
#: paperless_mail/admin.py:47
|
||||
msgid "Filter"
|
||||
msgstr "Suodatin"
|
||||
|
||||
#: paperless_mail/admin.py:50
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr "Paperless prosessoi vain sähköpostit jotka sopivat KAIKKIIN annettuihin filttereihin."
|
||||
|
||||
#: paperless_mail/admin.py:64
|
||||
msgid "Actions"
|
||||
msgstr "Toiminnot"
|
||||
|
||||
#: paperless_mail/admin.py:67
|
||||
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:75
|
||||
msgid "Metadata"
|
||||
msgstr "Metatiedot"
|
||||
|
||||
#: paperless_mail/admin.py:78
|
||||
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 "Määritä tuodun dokumentin metadata tämän säännön perusteella automaattisesti. Jos et aseta tageja, tyyppejä tai omistajia täällä, Paperless prosessoi silti kaikki sopivat määritellyt säännöt."
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
msgstr "Paperless-sähköposti"
|
||||
|
||||
#: paperless_mail/models.py:10
|
||||
msgid "mail account"
|
||||
msgstr "sähköpostitili"
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail accounts"
|
||||
msgstr "sähköpostitilit"
|
||||
|
||||
#: paperless_mail/models.py:18
|
||||
msgid "No encryption"
|
||||
msgstr "Ei salausta"
|
||||
|
||||
#: paperless_mail/models.py:19
|
||||
msgid "Use SSL"
|
||||
msgstr "Käytä SSL-salausta"
|
||||
|
||||
#: paperless_mail/models.py:20
|
||||
msgid "Use STARTTLS"
|
||||
msgstr "Käytä STARTTLS-salausta"
|
||||
|
||||
#: paperless_mail/models.py:25
|
||||
msgid "IMAP server"
|
||||
msgstr "IMAP-palvelin"
|
||||
|
||||
#: paperless_mail/models.py:28
|
||||
msgid "IMAP port"
|
||||
msgstr "IMAP-portti"
|
||||
|
||||
#: paperless_mail/models.py:32
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr "Tämä on yleensä 142 salaamattomille sekä STARTTLS-yhteyksille, ja 993 SSL-yhteyksille."
|
||||
|
||||
#: paperless_mail/models.py:38
|
||||
msgid "IMAP security"
|
||||
msgstr "IMAP-suojaus"
|
||||
|
||||
#: paperless_mail/models.py:41
|
||||
msgid "username"
|
||||
msgstr "käyttäjänimi"
|
||||
|
||||
#: paperless_mail/models.py:43
|
||||
msgid "password"
|
||||
msgstr "salasana"
|
||||
|
||||
#: paperless_mail/models.py:46
|
||||
msgid "character set"
|
||||
msgstr "merkistö"
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
msgstr "Merkistö määritellään sähköpostipalvelimen kanssa komminukointia varten. Se voi olla esimerkiksi \"UTF-8\" tai \"US-ASCII\"."
|
||||
|
||||
#: paperless_mail/models.py:61
|
||||
msgid "mail rule"
|
||||
msgstr "sähköpostisääntö"
|
||||
|
||||
#: paperless_mail/models.py:62
|
||||
msgid "mail rules"
|
||||
msgstr "sähköpostisäännöt"
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "Only process attachments."
|
||||
msgstr "Prosessoi vain liitteet."
|
||||
|
||||
#: paperless_mail/models.py:71
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr "Prosessoi kaikki tiedostot, sisältäen \"inline\"-liitteet."
|
||||
|
||||
#: paperless_mail/models.py:81
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr "Merkitse luetuksi, älä prosessoi luettuja sähköposteja"
|
||||
|
||||
#: paperless_mail/models.py:82
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:83
|
||||
msgid "Move to specified folder"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:84
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:91
|
||||
msgid "Use subject as title"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:92
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:101
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:102
|
||||
msgid "Use mail address"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:103
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:104
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "order"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:115
|
||||
msgid "account"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:119
|
||||
msgid "folder"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:122
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:126
|
||||
msgid "filter from"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:129
|
||||
msgid "filter subject"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:132
|
||||
msgid "filter body"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:136
|
||||
msgid "filter attachment filename"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:141
|
||||
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:148
|
||||
msgid "maximum age"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "Specified in days."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:152
|
||||
msgid "attachment type"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:156
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:162
|
||||
msgid "action"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:168
|
||||
msgid "action parameter"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:173
|
||||
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:181
|
||||
msgid "assign title from"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:189
|
||||
msgid "assign this tag"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:197
|
||||
msgid "assign this document type"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:201
|
||||
msgid "assign correspondent from"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:211
|
||||
msgid "assign this correspondent"
|
||||
msgstr ""
|
||||
|
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-03-02 11:20-0800\n"
|
||||
"PO-Revision-Date: 2022-03-24 18:56\n"
|
||||
"PO-Revision-Date: 2022-03-30 11:46\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Russian\n"
|
||||
"Language: ru_RU\n"
|
||||
@@ -344,7 +344,7 @@ msgstr "больше похожих"
|
||||
|
||||
#: documents/models.py:353
|
||||
msgid "has tags in"
|
||||
msgstr ""
|
||||
msgstr "имеет теги в"
|
||||
|
||||
#: documents/models.py:363
|
||||
msgid "rule type"
|
||||
@@ -382,7 +382,7 @@ msgstr "Paperless-ngx загружается..."
|
||||
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ngx signed out"
|
||||
msgstr ""
|
||||
msgstr "Выполнен выход из Paperless-ngx"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:59
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
@@ -394,7 +394,7 @@ msgstr "Войти снова"
|
||||
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ngx sign in"
|
||||
msgstr ""
|
||||
msgstr "Войти в Paperless-ngx"
|
||||
|
||||
#: documents/templates/registration/login.html:61
|
||||
msgid "Please sign in."
|
||||
@@ -450,7 +450,7 @@ msgstr "Итальянский"
|
||||
|
||||
#: paperless/settings.py:307
|
||||
msgid "Luxembourgish"
|
||||
msgstr ""
|
||||
msgstr "Люксембургский"
|
||||
|
||||
#: paperless/settings.py:308
|
||||
msgid "Dutch"
|
||||
@@ -462,7 +462,7 @@ msgstr "Польский"
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr ""
|
||||
msgstr "Португальский (Бразилия)"
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Portuguese"
|
||||
@@ -482,7 +482,7 @@ msgstr "Шведский"
|
||||
|
||||
#: paperless/urls.py:139
|
||||
msgid "Paperless-ngx administration"
|
||||
msgstr ""
|
||||
msgstr "Администрирование Paperless-ngx"
|
||||
|
||||
#: paperless_mail/admin.py:29
|
||||
msgid "Authentication"
|
||||
|
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-03-02 11:20-0800\n"
|
||||
"PO-Revision-Date: 2022-03-22 08:43\n"
|
||||
"PO-Revision-Date: 2022-03-27 17:08\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Serbian (Latin)\n"
|
||||
"Language: sr_CS\n"
|
||||
@@ -19,44 +19,44 @@ msgstr ""
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
msgstr ""
|
||||
msgstr "Dokumenta"
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr ""
|
||||
msgstr "Bilo koja reč"
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr ""
|
||||
msgstr "Sve reči"
|
||||
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr ""
|
||||
msgstr "Tačno podudaranje"
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr ""
|
||||
msgstr "Regularni izraz"
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr ""
|
||||
msgstr "Fuzzy reč"
|
||||
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr ""
|
||||
msgstr "Automatski"
|
||||
|
||||
#: documents/models.py:40 documents/models.py:314 paperless_mail/models.py:23
|
||||
#: paperless_mail/models.py:107
|
||||
msgid "name"
|
||||
msgstr ""
|
||||
msgstr "naziv"
|
||||
|
||||
#: documents/models.py:42
|
||||
msgid "match"
|
||||
msgstr ""
|
||||
msgstr "poklapanje"
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "matching algorithm"
|
||||
msgstr ""
|
||||
msgstr "algoritam podudaranja"
|
||||
|
||||
#: documents/models.py:48
|
||||
msgid "is insensitive"
|
||||
@@ -64,19 +64,19 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:61 documents/models.py:104
|
||||
msgid "correspondent"
|
||||
msgstr ""
|
||||
msgstr "dopisnik"
|
||||
|
||||
#: documents/models.py:62
|
||||
msgid "correspondents"
|
||||
msgstr ""
|
||||
msgstr "dopisnici"
|
||||
|
||||
#: documents/models.py:67
|
||||
msgid "color"
|
||||
msgstr ""
|
||||
msgstr "boja"
|
||||
|
||||
#: documents/models.py:70
|
||||
msgid "is inbox tag"
|
||||
msgstr ""
|
||||
msgstr "je oznaka prijemnog sandučeta"
|
||||
|
||||
#: documents/models.py:73
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
@@ -84,19 +84,19 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:79
|
||||
msgid "tag"
|
||||
msgstr ""
|
||||
msgstr "oznaka"
|
||||
|
||||
#: documents/models.py:80 documents/models.py:130
|
||||
msgid "tags"
|
||||
msgstr ""
|
||||
msgstr "oznake"
|
||||
|
||||
#: documents/models.py:85 documents/models.py:115
|
||||
msgid "document type"
|
||||
msgstr ""
|
||||
msgstr "tip dokumenta"
|
||||
|
||||
#: documents/models.py:86
|
||||
msgid "document types"
|
||||
msgstr ""
|
||||
msgstr "tipovi dokumenta"
|
||||
|
||||
#: documents/models.py:94
|
||||
msgid "Unencrypted"
|
||||
@@ -108,11 +108,11 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:107
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
msgstr "naslov"
|
||||
|
||||
#: documents/models.py:119
|
||||
msgid "content"
|
||||
msgstr ""
|
||||
msgstr "sadržaj"
|
||||
|
||||
#: documents/models.py:122
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
@@ -120,43 +120,43 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:127
|
||||
msgid "mime type"
|
||||
msgstr ""
|
||||
msgstr "mime tip"
|
||||
|
||||
#: documents/models.py:134
|
||||
msgid "checksum"
|
||||
msgstr ""
|
||||
msgstr "kontrolna suma"
|
||||
|
||||
#: documents/models.py:138
|
||||
msgid "The checksum of the original document."
|
||||
msgstr ""
|
||||
msgstr "Kontrolna suma originalnog dokumenta."
|
||||
|
||||
#: documents/models.py:142
|
||||
msgid "archive checksum"
|
||||
msgstr ""
|
||||
msgstr "arhivni checksum"
|
||||
|
||||
#: documents/models.py:147
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr ""
|
||||
msgstr "Kontrolna suma arhivnog dokumenta."
|
||||
|
||||
#: documents/models.py:150 documents/models.py:295
|
||||
msgid "created"
|
||||
msgstr ""
|
||||
msgstr "kreirano"
|
||||
|
||||
#: documents/models.py:153
|
||||
msgid "modified"
|
||||
msgstr ""
|
||||
msgstr "izmenjeno"
|
||||
|
||||
#: documents/models.py:157
|
||||
msgid "storage type"
|
||||
msgstr ""
|
||||
msgstr "tip skladišta"
|
||||
|
||||
#: documents/models.py:165
|
||||
msgid "added"
|
||||
msgstr ""
|
||||
msgstr "dodato"
|
||||
|
||||
#: documents/models.py:169
|
||||
msgid "filename"
|
||||
msgstr ""
|
||||
msgstr "naziv fajla"
|
||||
|
||||
#: documents/models.py:175
|
||||
msgid "Current filename in storage"
|
||||
@@ -164,7 +164,7 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:179
|
||||
msgid "archive filename"
|
||||
msgstr ""
|
||||
msgstr "naziv fajla arhive"
|
||||
|
||||
#: documents/models.py:185
|
||||
msgid "Current archive filename in storage"
|
||||
@@ -172,7 +172,7 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:189
|
||||
msgid "archive serial number"
|
||||
msgstr ""
|
||||
msgstr "arhivski serijski broj"
|
||||
|
||||
#: documents/models.py:195
|
||||
msgid "The position of this document in your physical document archive."
|
||||
@@ -180,75 +180,75 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:201
|
||||
msgid "document"
|
||||
msgstr ""
|
||||
msgstr "dokument"
|
||||
|
||||
#: documents/models.py:202
|
||||
msgid "documents"
|
||||
msgstr ""
|
||||
msgstr "dokumenta"
|
||||
|
||||
#: documents/models.py:280
|
||||
msgid "debug"
|
||||
msgstr ""
|
||||
msgstr "okloni greške"
|
||||
|
||||
#: documents/models.py:281
|
||||
msgid "information"
|
||||
msgstr ""
|
||||
msgstr "informacija"
|
||||
|
||||
#: documents/models.py:282
|
||||
msgid "warning"
|
||||
msgstr ""
|
||||
msgstr "upozorenje"
|
||||
|
||||
#: documents/models.py:283
|
||||
msgid "error"
|
||||
msgstr ""
|
||||
msgstr "grеška"
|
||||
|
||||
#: documents/models.py:284
|
||||
msgid "critical"
|
||||
msgstr ""
|
||||
msgstr "kritično"
|
||||
|
||||
#: documents/models.py:287
|
||||
msgid "group"
|
||||
msgstr ""
|
||||
msgstr "grupa"
|
||||
|
||||
#: documents/models.py:289
|
||||
msgid "message"
|
||||
msgstr ""
|
||||
msgstr "poruka"
|
||||
|
||||
#: documents/models.py:292
|
||||
msgid "level"
|
||||
msgstr ""
|
||||
msgstr "nivo"
|
||||
|
||||
#: documents/models.py:299
|
||||
msgid "log"
|
||||
msgstr ""
|
||||
msgstr "log"
|
||||
|
||||
#: documents/models.py:300
|
||||
msgid "logs"
|
||||
msgstr ""
|
||||
msgstr "logovi"
|
||||
|
||||
#: documents/models.py:310 documents/models.py:360
|
||||
msgid "saved view"
|
||||
msgstr ""
|
||||
msgstr "sačuvani prikaz"
|
||||
|
||||
#: documents/models.py:311
|
||||
msgid "saved views"
|
||||
msgstr ""
|
||||
msgstr "sačuvani prikazi"
|
||||
|
||||
#: documents/models.py:313
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
msgstr "korisnik"
|
||||
|
||||
#: documents/models.py:317
|
||||
msgid "show on dashboard"
|
||||
msgstr ""
|
||||
msgstr "prikaži na kontrolnoj tabli"
|
||||
|
||||
#: documents/models.py:320
|
||||
msgid "show in sidebar"
|
||||
msgstr ""
|
||||
msgstr "prikaži u bočnoj traci"
|
||||
|
||||
#: documents/models.py:324
|
||||
msgid "sort field"
|
||||
msgstr ""
|
||||
msgstr "polje za sortiranje"
|
||||
|
||||
#: documents/models.py:326
|
||||
msgid "sort reverse"
|
||||
@@ -256,83 +256,83 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:331
|
||||
msgid "title contains"
|
||||
msgstr ""
|
||||
msgstr "naslov sadrži"
|
||||
|
||||
#: documents/models.py:332
|
||||
msgid "content contains"
|
||||
msgstr ""
|
||||
msgstr "sadržaj sadrži"
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "ASN is"
|
||||
msgstr ""
|
||||
msgstr "ASN je"
|
||||
|
||||
#: documents/models.py:334
|
||||
msgid "correspondent is"
|
||||
msgstr ""
|
||||
msgstr "dopisnik je"
|
||||
|
||||
#: documents/models.py:335
|
||||
msgid "document type is"
|
||||
msgstr ""
|
||||
msgstr "tip dokumenta je"
|
||||
|
||||
#: documents/models.py:336
|
||||
msgid "is in inbox"
|
||||
msgstr ""
|
||||
msgstr "je u prijemnog sandučetu"
|
||||
|
||||
#: documents/models.py:337
|
||||
msgid "has tag"
|
||||
msgstr ""
|
||||
msgstr "ima oznaku"
|
||||
|
||||
#: documents/models.py:338
|
||||
msgid "has any tag"
|
||||
msgstr ""
|
||||
msgstr "ima bilo koju oznaku"
|
||||
|
||||
#: documents/models.py:339
|
||||
msgid "created before"
|
||||
msgstr ""
|
||||
msgstr "kreiran pre"
|
||||
|
||||
#: documents/models.py:340
|
||||
msgid "created after"
|
||||
msgstr ""
|
||||
msgstr "kreiran posle"
|
||||
|
||||
#: documents/models.py:341
|
||||
msgid "created year is"
|
||||
msgstr ""
|
||||
msgstr "godina kreiranja je"
|
||||
|
||||
#: documents/models.py:342
|
||||
msgid "created month is"
|
||||
msgstr ""
|
||||
msgstr "mesec kreiranja je"
|
||||
|
||||
#: documents/models.py:343
|
||||
msgid "created day is"
|
||||
msgstr ""
|
||||
msgstr "dan kreiranja je"
|
||||
|
||||
#: documents/models.py:344
|
||||
msgid "added before"
|
||||
msgstr ""
|
||||
msgstr "dodat pre"
|
||||
|
||||
#: documents/models.py:345
|
||||
msgid "added after"
|
||||
msgstr ""
|
||||
msgstr "dodat posle"
|
||||
|
||||
#: documents/models.py:346
|
||||
msgid "modified before"
|
||||
msgstr ""
|
||||
msgstr "izmenjen pre"
|
||||
|
||||
#: documents/models.py:347
|
||||
msgid "modified after"
|
||||
msgstr ""
|
||||
msgstr "izmenjen posle"
|
||||
|
||||
#: documents/models.py:348
|
||||
msgid "does not have tag"
|
||||
msgstr ""
|
||||
msgstr "nema oznaku"
|
||||
|
||||
#: documents/models.py:349
|
||||
msgid "does not have ASN"
|
||||
msgstr ""
|
||||
msgstr "nema ASN"
|
||||
|
||||
#: documents/models.py:350
|
||||
msgid "title or content contains"
|
||||
msgstr ""
|
||||
msgstr "naslov i sadržaj sadrži"
|
||||
|
||||
#: documents/models.py:351
|
||||
msgid "fulltext query"
|
||||
@@ -340,19 +340,19 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:352
|
||||
msgid "more like this"
|
||||
msgstr ""
|
||||
msgstr "više ovakvih"
|
||||
|
||||
#: documents/models.py:353
|
||||
msgid "has tags in"
|
||||
msgstr ""
|
||||
msgstr "ima oznake u"
|
||||
|
||||
#: documents/models.py:363
|
||||
msgid "rule type"
|
||||
msgstr ""
|
||||
msgstr "tip pravila"
|
||||
|
||||
#: documents/models.py:365
|
||||
msgid "value"
|
||||
msgstr ""
|
||||
msgstr "vrednost"
|
||||
|
||||
#: documents/models.py:368
|
||||
msgid "filter rule"
|
||||
@@ -390,7 +390,7 @@ msgstr ""
|
||||
|
||||
#: documents/templates/registration/logged_out.html:60
|
||||
msgid "Sign in again"
|
||||
msgstr ""
|
||||
msgstr "Prijavitе sе ponovo"
|
||||
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ngx sign in"
|
||||
@@ -398,7 +398,7 @@ msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:61
|
||||
msgid "Please sign in."
|
||||
msgstr ""
|
||||
msgstr "Prijavite se."
|
||||
|
||||
#: documents/templates/registration/login.html:64
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
@@ -406,83 +406,83 @@ msgstr ""
|
||||
|
||||
#: documents/templates/registration/login.html:67
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
msgstr "Korisničko ime"
|
||||
|
||||
#: documents/templates/registration/login.html:68
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
msgstr "Lozinka"
|
||||
|
||||
#: documents/templates/registration/login.html:73
|
||||
msgid "Sign in"
|
||||
msgstr ""
|
||||
msgstr "Prijavite se"
|
||||
|
||||
#: paperless/settings.py:299
|
||||
msgid "English (US)"
|
||||
msgstr ""
|
||||
msgstr "Engleski (US)"
|
||||
|
||||
#: paperless/settings.py:300
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
msgstr "Češki"
|
||||
|
||||
#: paperless/settings.py:301
|
||||
msgid "Danish"
|
||||
msgstr ""
|
||||
msgstr "Danski"
|
||||
|
||||
#: paperless/settings.py:302
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
msgstr "Nemački"
|
||||
|
||||
#: paperless/settings.py:303
|
||||
msgid "English (GB)"
|
||||
msgstr ""
|
||||
msgstr "Engleski (UK)"
|
||||
|
||||
#: paperless/settings.py:304
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
msgstr "Španski"
|
||||
|
||||
#: paperless/settings.py:305
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
msgstr "Francuski"
|
||||
|
||||
#: paperless/settings.py:306
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
msgstr "Italijanski"
|
||||
|
||||
#: paperless/settings.py:307
|
||||
msgid "Luxembourgish"
|
||||
msgstr ""
|
||||
msgstr "Luksemburški"
|
||||
|
||||
#: paperless/settings.py:308
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
msgstr "Holandski"
|
||||
|
||||
#: paperless/settings.py:309
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
msgstr "Poljski"
|
||||
|
||||
#: paperless/settings.py:310
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr ""
|
||||
msgstr "Portugalski (Brazil)"
|
||||
|
||||
#: paperless/settings.py:311
|
||||
msgid "Portuguese"
|
||||
msgstr ""
|
||||
msgstr "Portugalski"
|
||||
|
||||
#: paperless/settings.py:312
|
||||
msgid "Romanian"
|
||||
msgstr ""
|
||||
msgstr "Rumunski"
|
||||
|
||||
#: paperless/settings.py:313
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
msgstr "Ruski"
|
||||
|
||||
#: paperless/settings.py:314
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
msgstr "Švedski"
|
||||
|
||||
#: paperless/urls.py:139
|
||||
msgid "Paperless-ngx administration"
|
||||
msgstr ""
|
||||
msgstr "Paperless-ngx administracija"
|
||||
|
||||
#: paperless_mail/admin.py:29
|
||||
msgid "Authentication"
|
||||
@@ -490,11 +490,11 @@ msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:30
|
||||
msgid "Advanced settings"
|
||||
msgstr ""
|
||||
msgstr "Napredna podešavanja"
|
||||
|
||||
#: paperless_mail/admin.py:47
|
||||
msgid "Filter"
|
||||
msgstr ""
|
||||
msgstr "Filter"
|
||||
|
||||
#: paperless_mail/admin.py:50
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
@@ -502,7 +502,7 @@ msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:64
|
||||
msgid "Actions"
|
||||
msgstr ""
|
||||
msgstr "Radnje"
|
||||
|
||||
#: paperless_mail/admin.py:67
|
||||
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."
|
||||
@@ -510,7 +510,7 @@ msgstr ""
|
||||
|
||||
#: paperless_mail/admin.py:75
|
||||
msgid "Metadata"
|
||||
msgstr ""
|
||||
msgstr "Metapodaci"
|
||||
|
||||
#: paperless_mail/admin.py:78
|
||||
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."
|
||||
@@ -518,15 +518,15 @@ msgstr ""
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
msgstr ""
|
||||
msgstr "Paperless mejl"
|
||||
|
||||
#: paperless_mail/models.py:10
|
||||
msgid "mail account"
|
||||
msgstr ""
|
||||
msgstr "mejl nalog"
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail accounts"
|
||||
msgstr ""
|
||||
msgstr "mejl nalozi"
|
||||
|
||||
#: paperless_mail/models.py:18
|
||||
msgid "No encryption"
|
||||
@@ -534,19 +534,19 @@ msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:19
|
||||
msgid "Use SSL"
|
||||
msgstr ""
|
||||
msgstr "Koristi SSL"
|
||||
|
||||
#: paperless_mail/models.py:20
|
||||
msgid "Use STARTTLS"
|
||||
msgstr ""
|
||||
msgstr "Koristi STARTTLS"
|
||||
|
||||
#: paperless_mail/models.py:25
|
||||
msgid "IMAP server"
|
||||
msgstr ""
|
||||
msgstr "IMAP server"
|
||||
|
||||
#: paperless_mail/models.py:28
|
||||
msgid "IMAP port"
|
||||
msgstr ""
|
||||
msgstr "IMAP port"
|
||||
|
||||
#: paperless_mail/models.py:32
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
@@ -554,19 +554,19 @@ msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:38
|
||||
msgid "IMAP security"
|
||||
msgstr ""
|
||||
msgstr "IMAP bezbednost"
|
||||
|
||||
#: paperless_mail/models.py:41
|
||||
msgid "username"
|
||||
msgstr ""
|
||||
msgstr "korisničko ime"
|
||||
|
||||
#: paperless_mail/models.py:43
|
||||
msgid "password"
|
||||
msgstr ""
|
||||
msgstr "lozinka"
|
||||
|
||||
#: paperless_mail/models.py:46
|
||||
msgid "character set"
|
||||
msgstr ""
|
||||
msgstr "karakter set"
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
@@ -602,7 +602,7 @@ msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:84
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
msgstr "Obriši"
|
||||
|
||||
#: paperless_mail/models.py:91
|
||||
msgid "Use subject as title"
|
||||
@@ -614,51 +614,51 @@ msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:101
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr ""
|
||||
msgstr "Ne dodeljuj dopisnika"
|
||||
|
||||
#: paperless_mail/models.py:102
|
||||
msgid "Use mail address"
|
||||
msgstr ""
|
||||
msgstr "Koristi mejl adresu"
|
||||
|
||||
#: paperless_mail/models.py:103
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr ""
|
||||
msgstr "Koristi naziv (ili mejl adresu ako nije dostupno)"
|
||||
|
||||
#: paperless_mail/models.py:104
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr ""
|
||||
msgstr "Koristi dopisnika ispod"
|
||||
|
||||
#: paperless_mail/models.py:109
|
||||
msgid "order"
|
||||
msgstr ""
|
||||
msgstr "raspored"
|
||||
|
||||
#: paperless_mail/models.py:115
|
||||
msgid "account"
|
||||
msgstr ""
|
||||
msgstr "nalog"
|
||||
|
||||
#: paperless_mail/models.py:119
|
||||
msgid "folder"
|
||||
msgstr ""
|
||||
msgstr "folder"
|
||||
|
||||
#: paperless_mail/models.py:122
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr ""
|
||||
msgstr "Podfolderi moraju biti odvojeni tačkama."
|
||||
|
||||
#: paperless_mail/models.py:126
|
||||
msgid "filter from"
|
||||
msgstr ""
|
||||
msgstr "filter od"
|
||||
|
||||
#: paperless_mail/models.py:129
|
||||
msgid "filter subject"
|
||||
msgstr ""
|
||||
msgstr "filter naslov"
|
||||
|
||||
#: paperless_mail/models.py:132
|
||||
msgid "filter body"
|
||||
msgstr ""
|
||||
msgstr "filter telo poruke"
|
||||
|
||||
#: paperless_mail/models.py:136
|
||||
msgid "filter attachment filename"
|
||||
msgstr ""
|
||||
msgstr "filter naziv fajla priloga"
|
||||
|
||||
#: paperless_mail/models.py:141
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
@@ -674,7 +674,7 @@ msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:152
|
||||
msgid "attachment type"
|
||||
msgstr ""
|
||||
msgstr "tip priloga"
|
||||
|
||||
#: paperless_mail/models.py:156
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
@@ -682,7 +682,7 @@ msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:162
|
||||
msgid "action"
|
||||
msgstr ""
|
||||
msgstr "radnja"
|
||||
|
||||
#: paperless_mail/models.py:168
|
||||
msgid "action parameter"
|
||||
@@ -694,21 +694,21 @@ msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:181
|
||||
msgid "assign title from"
|
||||
msgstr ""
|
||||
msgstr "dodeli naziv iz"
|
||||
|
||||
#: paperless_mail/models.py:189
|
||||
msgid "assign this tag"
|
||||
msgstr ""
|
||||
msgstr "dodeli ovu oznaku"
|
||||
|
||||
#: paperless_mail/models.py:197
|
||||
msgid "assign this document type"
|
||||
msgstr ""
|
||||
msgstr "dodeli ovaj tip dokumenta"
|
||||
|
||||
#: paperless_mail/models.py:201
|
||||
msgid "assign correspondent from"
|
||||
msgstr ""
|
||||
msgstr "dodeli dopisnika iz"
|
||||
|
||||
#: paperless_mail/models.py:211
|
||||
msgid "assign this correspondent"
|
||||
msgstr ""
|
||||
msgstr "dodeli ovog dopisnika"
|
||||
|
||||
|
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-03-02 11:20-0800\n"
|
||||
"PO-Revision-Date: 2022-03-09 23:50\n"
|
||||
"PO-Revision-Date: 2022-03-29 08:58\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Chinese Simplified\n"
|
||||
"Language: zh_CN\n"
|
||||
@@ -104,7 +104,7 @@ msgstr "未加密"
|
||||
|
||||
#: documents/models.py:95
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "使用 GNU 隐私防护加密"
|
||||
msgstr "使用 GNU 隐私防护(GPG)加密"
|
||||
|
||||
#: documents/models.py:107
|
||||
msgid "title"
|
||||
@@ -308,19 +308,19 @@ msgstr "创建日期是"
|
||||
|
||||
#: documents/models.py:344
|
||||
msgid "added before"
|
||||
msgstr "添加于"
|
||||
msgstr "添加早于"
|
||||
|
||||
#: documents/models.py:345
|
||||
msgid "added after"
|
||||
msgstr "添加后"
|
||||
msgstr "添加晚于"
|
||||
|
||||
#: documents/models.py:346
|
||||
msgid "modified before"
|
||||
msgstr "修改前"
|
||||
msgstr "修改早于"
|
||||
|
||||
#: documents/models.py:347
|
||||
msgid "modified after"
|
||||
msgstr "修改后"
|
||||
msgstr "修改晚于"
|
||||
|
||||
#: documents/models.py:348
|
||||
msgid "does not have tag"
|
||||
@@ -344,7 +344,7 @@ msgstr "更多类似内容"
|
||||
|
||||
#: documents/models.py:353
|
||||
msgid "has tags in"
|
||||
msgstr "有标签于"
|
||||
msgstr "有标签包含于"
|
||||
|
||||
#: documents/models.py:363
|
||||
msgid "rule type"
|
||||
@@ -382,11 +382,11 @@ msgstr "Paperless-ngx 正在加载..."
|
||||
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ngx signed out"
|
||||
msgstr "Paperless-ngx 注销"
|
||||
msgstr "Paperless-ngx 已退出"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:59
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr "您已成功注销。Bye!"
|
||||
msgstr "您已成功退出。再见!"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:60
|
||||
msgid "Sign in again"
|
||||
@@ -402,7 +402,7 @@ msgstr "请登录。"
|
||||
|
||||
#: documents/templates/registration/login.html:64
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr "您的用户名和密码不匹配,请重试。"
|
||||
msgstr "您的用户名和密码不匹配。请重试。"
|
||||
|
||||
#: documents/templates/registration/login.html:67
|
||||
msgid "Username"
|
||||
@@ -486,7 +486,7 @@ msgstr "Paperless-ngx 管理"
|
||||
|
||||
#: paperless_mail/admin.py:29
|
||||
msgid "Authentication"
|
||||
msgstr "认证"
|
||||
msgstr "身份验证"
|
||||
|
||||
#: paperless_mail/admin.py:30
|
||||
msgid "Advanced settings"
|
||||
@@ -506,7 +506,7 @@ msgstr "操作"
|
||||
|
||||
#: paperless_mail/admin.py:67
|
||||
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 "应用于邮件的操作。此操作仅在从邮件中处理文档时执行。没有附件的邮件将保持完全不受接触。"
|
||||
msgstr "应用于邮件的操作。此操作仅在从邮件中处理文档时执行。没有附件的邮件完全不会触及。"
|
||||
|
||||
#: paperless_mail/admin.py:75
|
||||
msgid "Metadata"
|
||||
@@ -514,7 +514,7 @@ msgstr "元数据"
|
||||
|
||||
#: paperless_mail/admin.py:78
|
||||
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-ngx 仍将处理您已定义的所有匹配规则。"
|
||||
msgstr "将元数据自动指定到被此规则所处理的文档。 如果您不在这里指定标签、类型或联系人,Paperless-ngx 仍将处理您已定义的所有匹配规则。"
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
@@ -522,7 +522,7 @@ msgstr "Paperless-ngx 邮件"
|
||||
|
||||
#: paperless_mail/models.py:10
|
||||
msgid "mail account"
|
||||
msgstr "邮件帐户"
|
||||
msgstr "邮件账号"
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail accounts"
|
||||
@@ -570,7 +570,7 @@ msgstr "字符集"
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
msgstr "与邮件服务器通信时使用的字符集,例如'UTF-8'或 'US-ASCII'。"
|
||||
msgstr "与邮件服务器通信时使用的字符集,例如“UTF-8”或“US-ASCII”。"
|
||||
|
||||
#: paperless_mail/models.py:61
|
||||
msgid "mail rule"
|
||||
@@ -594,7 +594,7 @@ msgstr "标记为已读,不处理已读邮件"
|
||||
|
||||
#: paperless_mail/models.py:82
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr "标记邮件,不处理标记的邮件"
|
||||
msgstr "标记邮件,不处理已标记的邮件"
|
||||
|
||||
#: paperless_mail/models.py:83
|
||||
msgid "Move to specified folder"
|
||||
@@ -634,7 +634,7 @@ msgstr "排序"
|
||||
|
||||
#: paperless_mail/models.py:115
|
||||
msgid "account"
|
||||
msgstr "帐户"
|
||||
msgstr "账户"
|
||||
|
||||
#: paperless_mail/models.py:119
|
||||
msgid "folder"
|
||||
@@ -642,7 +642,7 @@ msgstr "文件夹"
|
||||
|
||||
#: paperless_mail/models.py:122
|
||||
msgid "Subfolders must be separated by dots."
|
||||
msgstr "子文件夹必须用 \".\" 分隔。"
|
||||
msgstr "子文件夹必须用“.”分隔。"
|
||||
|
||||
#: paperless_mail/models.py:126
|
||||
msgid "filter from"
|
||||
@@ -662,7 +662,7 @@ msgstr "过滤附件文件名"
|
||||
|
||||
#: paperless_mail/models.py:141
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr "如果指定的话,只处理完全匹配此文件名的文档。允许使用通配符,如 *.pdf 或 *发票*。不区分大小写。"
|
||||
msgstr "如果指定了文件名,只处理完全匹配此文件名的文档。允许使用通配符,如 *.pdf 或 *发票*。不区分大小写。"
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "maximum age"
|
||||
@@ -690,11 +690,11 @@ msgstr "操作参数"
|
||||
|
||||
#: paperless_mail/models.py:173
|
||||
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 "上面选择的操作的附加参数,即移动到文件夹操作的目标文件夹。子文件夹必须用 \".\" 来分隔。"
|
||||
msgstr "上面选择的操作的附加参数,即移动到文件夹操作的目标文件夹。子文件夹必须用“.”来分隔。"
|
||||
|
||||
#: paperless_mail/models.py:181
|
||||
msgid "assign title from"
|
||||
msgstr "分配标题自"
|
||||
msgstr "分配标题来自"
|
||||
|
||||
#: paperless_mail/models.py:189
|
||||
msgid "assign this tag"
|
||||
@@ -706,7 +706,7 @@ msgstr "分配此文档类型"
|
||||
|
||||
#: paperless_mail/models.py:201
|
||||
msgid "assign correspondent from"
|
||||
msgstr "分配联系人自"
|
||||
msgstr "分配联系人来自"
|
||||
|
||||
#: paperless_mail/models.py:211
|
||||
msgid "assign this correspondent"
|
||||
|
@@ -3,6 +3,8 @@ import math
|
||||
import multiprocessing
|
||||
import os
|
||||
import re
|
||||
from typing import Final
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from concurrent_log_handler.queue import setup_logging_queues
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -29,7 +31,7 @@ elif os.path.exists("/usr/local/etc/paperless.conf"):
|
||||
os.environ["OMP_THREAD_LIMIT"] = "1"
|
||||
|
||||
|
||||
def __get_boolean(key, default="NO"):
|
||||
def __get_boolean(key: str, default: str = "NO") -> bool:
|
||||
"""
|
||||
Return a boolean value based on whatever the user has supplied in the
|
||||
environment based on whether the value "looks like" it's True or not.
|
||||
@@ -37,6 +39,13 @@ def __get_boolean(key, default="NO"):
|
||||
return bool(os.getenv(key, default).lower() in ("yes", "y", "1", "t", "true"))
|
||||
|
||||
|
||||
def __get_int(key: str, default: int) -> int:
|
||||
"""
|
||||
Return an integer value based on the environment variable or a default
|
||||
"""
|
||||
return int(os.getenv(key, default))
|
||||
|
||||
|
||||
# NEVER RUN WITH DEBUG IN PRODUCTION.
|
||||
DEBUG = __get_boolean("PAPERLESS_DEBUG", "NO")
|
||||
|
||||
@@ -211,7 +220,15 @@ if DEBUG:
|
||||
else:
|
||||
X_FRAME_OPTIONS = "SAMEORIGIN"
|
||||
|
||||
# We allow CORS from localhost:8080
|
||||
|
||||
# The next 3 settings can also be set using just PAPERLESS_URL
|
||||
_csrf_origins = os.getenv("PAPERLESS_CSRF_TRUSTED_ORIGINS")
|
||||
if _csrf_origins:
|
||||
CSRF_TRUSTED_ORIGINS = _csrf_origins.split(",")
|
||||
else:
|
||||
CSRF_TRUSTED_ORIGINS = []
|
||||
|
||||
# We allow CORS from localhost:8000
|
||||
CORS_ALLOWED_ORIGINS = tuple(
|
||||
os.getenv("PAPERLESS_CORS_ALLOWED_HOSTS", "http://localhost:8000").split(","),
|
||||
)
|
||||
@@ -220,6 +237,22 @@ if DEBUG:
|
||||
# Allow access from the angular development server during debugging
|
||||
CORS_ALLOWED_ORIGINS += ("http://localhost:4200",)
|
||||
|
||||
_allowed_hosts = os.getenv("PAPERLESS_ALLOWED_HOSTS")
|
||||
if _allowed_hosts:
|
||||
ALLOWED_HOSTS = _allowed_hosts.split(",")
|
||||
else:
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
|
||||
_paperless_url = os.getenv("PAPERLESS_URL")
|
||||
if _paperless_url:
|
||||
_paperless_uri = urlparse(_paperless_url)
|
||||
CSRF_TRUSTED_ORIGINS.append(_paperless_url)
|
||||
CORS_ALLOWED_ORIGINS += (_paperless_url,)
|
||||
if _allowed_hosts:
|
||||
ALLOWED_HOSTS.append(_paperless_uri.hostname)
|
||||
else:
|
||||
ALLOWED_HOSTS = [_paperless_uri.hostname]
|
||||
|
||||
# The secret key has a default that should be fine so long as you're hosting
|
||||
# Paperless on a closed network. However, if you're putting this anywhere
|
||||
# public, you should change the key to something unique and verbose.
|
||||
@@ -228,12 +261,6 @@ SECRET_KEY = os.getenv(
|
||||
"e11fl1oa-*ytql8p)(06fbj4ukrlo+n7k&q5+$1md7i+mge=ee",
|
||||
)
|
||||
|
||||
_allowed_hosts = os.getenv("PAPERLESS_ALLOWED_HOSTS")
|
||||
if _allowed_hosts:
|
||||
ALLOWED_HOSTS = _allowed_hosts.split(",")
|
||||
else:
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||
@@ -299,6 +326,7 @@ LANGUAGE_CODE = "en-us"
|
||||
|
||||
LANGUAGES = [
|
||||
("en-us", _("English (US)")), # needs to be first to act as fallback language
|
||||
("be-by", _("Belarusian")),
|
||||
("cs-cz", _("Czech")),
|
||||
("da-dk", _("Danish")),
|
||||
("de-de", _("German")),
|
||||
@@ -395,7 +423,7 @@ LOGGING = {
|
||||
# in total.
|
||||
|
||||
|
||||
def default_task_workers():
|
||||
def default_task_workers() -> int:
|
||||
# always leave one core open
|
||||
available_cores = max(multiprocessing.cpu_count(), 1)
|
||||
try:
|
||||
@@ -406,20 +434,29 @@ def default_task_workers():
|
||||
return 1
|
||||
|
||||
|
||||
TASK_WORKERS = int(os.getenv("PAPERLESS_TASK_WORKERS", default_task_workers()))
|
||||
TASK_WORKERS = __get_int("PAPERLESS_TASK_WORKERS", default_task_workers())
|
||||
|
||||
PAPERLESS_WORKER_TIMEOUT: Final[int] = __get_int("PAPERLESS_WORKER_TIMEOUT", 1800)
|
||||
|
||||
# Per django-q docs, timeout must be smaller than retry
|
||||
# We default retry to 10s more than the timeout
|
||||
PAPERLESS_WORKER_RETRY: Final[int] = __get_int(
|
||||
"PAPERLESS_WORKER_RETRY",
|
||||
PAPERLESS_WORKER_TIMEOUT + 10,
|
||||
)
|
||||
|
||||
Q_CLUSTER = {
|
||||
"name": "paperless",
|
||||
"catch_up": False,
|
||||
"recycle": 1,
|
||||
"retry": 1800,
|
||||
"timeout": int(os.getenv("PAPERLESS_WORKER_TIMEOUT", 1800)),
|
||||
"retry": PAPERLESS_WORKER_RETRY,
|
||||
"timeout": PAPERLESS_WORKER_TIMEOUT,
|
||||
"workers": TASK_WORKERS,
|
||||
"redis": os.getenv("PAPERLESS_REDIS", "redis://localhost:6379"),
|
||||
}
|
||||
|
||||
|
||||
def default_threads_per_worker(task_workers):
|
||||
def default_threads_per_worker(task_workers) -> int:
|
||||
# always leave one core open
|
||||
available_cores = max(multiprocessing.cpu_count(), 1)
|
||||
try:
|
||||
@@ -454,13 +491,19 @@ CONSUMER_IGNORE_PATTERNS = list(
|
||||
json.loads(
|
||||
os.getenv(
|
||||
"PAPERLESS_CONSUMER_IGNORE_PATTERNS",
|
||||
'[".DS_STORE/*", "._*", ".stfolder/*"]',
|
||||
'[".DS_STORE/*", "._*", ".stfolder/*", ".stversions/*", ".localized/*", "desktop.ini"]',
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
CONSUMER_SUBDIRS_AS_TAGS = __get_boolean("PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS")
|
||||
|
||||
CONSUMER_ENABLE_BARCODES = __get_boolean(
|
||||
"PAPERLESS_CONSUMER_ENABLE_BARCODES",
|
||||
)
|
||||
|
||||
CONSUMER_BARCODE_STRING = os.getenv("PAPERLESS_CONSUMER_BARCODE_STRING", "PATCHT")
|
||||
|
||||
OPTIMIZE_THUMBNAILS = __get_boolean("PAPERLESS_OPTIMIZE_THUMBNAILS", "true")
|
||||
|
||||
OCR_PAGES = int(os.getenv("PAPERLESS_OCR_PAGES", 0))
|
||||
@@ -489,6 +532,11 @@ OCR_ROTATE_PAGES_THRESHOLD = float(
|
||||
os.getenv("PAPERLESS_OCR_ROTATE_PAGES_THRESHOLD", 12.0),
|
||||
)
|
||||
|
||||
OCR_MAX_IMAGE_PIXELS = os.environ.get(
|
||||
"PAPERLESS_OCR_MAX_IMAGE_PIXELS",
|
||||
256000000,
|
||||
)
|
||||
|
||||
OCR_USER_ARGS = os.getenv("PAPERLESS_OCR_USER_ARGS", "{}")
|
||||
|
||||
# GNUPG needs a home directory for some reason
|
||||
@@ -560,3 +608,7 @@ if os.getenv("PAPERLESS_IGNORE_DATES", ""):
|
||||
d = dateparser.parse(s)
|
||||
if d:
|
||||
IGNORE_DATES.add(d.date())
|
||||
|
||||
ENABLE_UPDATE_CHECK = os.getenv("PAPERLESS_ENABLE_UPDATE_CHECK", "default")
|
||||
if ENABLE_UPDATE_CHECK != "default":
|
||||
ENABLE_UPDATE_CHECK = __get_boolean("PAPERLESS_ENABLE_UPDATE_CHECK")
|
||||
|
@@ -14,6 +14,7 @@ from documents.views import DocumentTypeViewSet
|
||||
from documents.views import IndexView
|
||||
from documents.views import LogViewSet
|
||||
from documents.views import PostDocumentView
|
||||
from documents.views import RemoteVersionView
|
||||
from documents.views import SavedViewViewSet
|
||||
from documents.views import SearchAutoCompleteView
|
||||
from documents.views import SelectionDataView
|
||||
@@ -72,6 +73,11 @@ urlpatterns = [
|
||||
BulkDownloadView.as_view(),
|
||||
name="bulk_download",
|
||||
),
|
||||
re_path(
|
||||
r"^remote_version/",
|
||||
RemoteVersionView.as_view(),
|
||||
name="remoteversion",
|
||||
),
|
||||
path("token/", views.obtain_auth_token),
|
||||
]
|
||||
+ api_router.urls,
|
||||
|
@@ -62,13 +62,13 @@ class FlagMailAction(BaseMailAction):
|
||||
|
||||
|
||||
def get_rule_action(rule):
|
||||
if rule.action == MailRule.ACTION_FLAG:
|
||||
if rule.action == MailRule.AttachmentAction.FLAG:
|
||||
return FlagMailAction()
|
||||
elif rule.action == MailRule.ACTION_DELETE:
|
||||
elif rule.action == MailRule.AttachmentAction.DELETE:
|
||||
return DeleteMailAction()
|
||||
elif rule.action == MailRule.ACTION_MOVE:
|
||||
elif rule.action == MailRule.AttachmentAction.MOVE:
|
||||
return MoveMailAction()
|
||||
elif rule.action == MailRule.ACTION_MARK_READ:
|
||||
elif rule.action == MailRule.AttachmentAction.MARK_READ:
|
||||
return MarkReadMailAction()
|
||||
else:
|
||||
raise NotImplementedError("Unknown action.") # pragma: nocover
|
||||
@@ -117,10 +117,10 @@ class MailAccountHandler(LoggingMixin):
|
||||
return None
|
||||
|
||||
def get_title(self, message, att, rule):
|
||||
if rule.assign_title_from == MailRule.TITLE_FROM_SUBJECT:
|
||||
if rule.assign_title_from == MailRule.TitleSource.FROM_SUBJECT:
|
||||
return message.subject
|
||||
|
||||
elif rule.assign_title_from == MailRule.TITLE_FROM_FILENAME:
|
||||
elif rule.assign_title_from == MailRule.TitleSource.FROM_FILENAME:
|
||||
return os.path.splitext(os.path.basename(att.filename))[0]
|
||||
|
||||
else:
|
||||
@@ -131,20 +131,20 @@ class MailAccountHandler(LoggingMixin):
|
||||
def get_correspondent(self, message: MailMessage, rule):
|
||||
c_from = rule.assign_correspondent_from
|
||||
|
||||
if c_from == MailRule.CORRESPONDENT_FROM_NOTHING:
|
||||
if c_from == MailRule.CorrespondentSource.FROM_NOTHING:
|
||||
return None
|
||||
|
||||
elif c_from == MailRule.CORRESPONDENT_FROM_EMAIL:
|
||||
elif c_from == MailRule.CorrespondentSource.FROM_EMAIL:
|
||||
return self._correspondent_from_name(message.from_)
|
||||
|
||||
elif c_from == MailRule.CORRESPONDENT_FROM_NAME:
|
||||
elif c_from == MailRule.CorrespondentSource.FROM_NAME:
|
||||
from_values = message.from_values
|
||||
if from_values is not None and len(from_values.name) > 0:
|
||||
return self._correspondent_from_name(from_values.name)
|
||||
else:
|
||||
return self._correspondent_from_name(message.from_)
|
||||
|
||||
elif c_from == MailRule.CORRESPONDENT_FROM_CUSTOM:
|
||||
elif c_from == MailRule.CorrespondentSource.FROM_CUSTOM:
|
||||
return rule.assign_correspondent
|
||||
|
||||
else:
|
||||
@@ -273,7 +273,7 @@ class MailAccountHandler(LoggingMixin):
|
||||
|
||||
return total_processed_files
|
||||
|
||||
def handle_message(self, message, rule):
|
||||
def handle_message(self, message, rule) -> int:
|
||||
if not message.attachments:
|
||||
return 0
|
||||
|
||||
@@ -294,7 +294,8 @@ class MailAccountHandler(LoggingMixin):
|
||||
|
||||
if (
|
||||
not att.content_disposition == "attachment"
|
||||
and rule.attachment_type == MailRule.ATTACHMENT_TYPE_ATTACHMENTS_ONLY
|
||||
and rule.attachment_type
|
||||
== MailRule.AttachmentProcessing.ATTACHMENTS_ONLY
|
||||
):
|
||||
self.log(
|
||||
"debug",
|
||||
@@ -305,7 +306,12 @@ class MailAccountHandler(LoggingMixin):
|
||||
continue
|
||||
|
||||
if rule.filter_attachment_filename:
|
||||
if not fnmatch(att.filename, rule.filter_attachment_filename):
|
||||
# Force the filename and pattern to the lowercase
|
||||
# as this is system dependent otherwise
|
||||
if not fnmatch(
|
||||
att.filename.lower(),
|
||||
rule.filter_attachment_filename.lower(),
|
||||
):
|
||||
continue
|
||||
|
||||
title = self.get_title(message, att, rule)
|
||||
|
@@ -0,0 +1,37 @@
|
||||
# Generated by Django 4.0.3 on 2022-03-28 17:40
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("paperless_mail", "0008_auto_20210516_0940"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="mailrule",
|
||||
name="action",
|
||||
field=models.PositiveIntegerField(
|
||||
choices=[
|
||||
(1, "Mark as read, don't process read mails"),
|
||||
(2, "Flag the mail, don't process flagged mails"),
|
||||
(3, "Move to specified folder"),
|
||||
(4, "Delete"),
|
||||
],
|
||||
default=3,
|
||||
verbose_name="action",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="mailrule",
|
||||
name="folder",
|
||||
field=models.CharField(
|
||||
default="INBOX",
|
||||
help_text="Subfolders must be separated by a delimiter, often a dot ('.') or slash ('/'), but it varies by mail server.",
|
||||
max_length=256,
|
||||
verbose_name="folder",
|
||||
),
|
||||
),
|
||||
]
|
@@ -8,15 +8,10 @@ class MailAccount(models.Model):
|
||||
verbose_name = _("mail account")
|
||||
verbose_name_plural = _("mail accounts")
|
||||
|
||||
IMAP_SECURITY_NONE = 1
|
||||
IMAP_SECURITY_SSL = 2
|
||||
IMAP_SECURITY_STARTTLS = 3
|
||||
|
||||
IMAP_SECURITY_OPTIONS = (
|
||||
(IMAP_SECURITY_NONE, _("No encryption")),
|
||||
(IMAP_SECURITY_SSL, _("Use SSL")),
|
||||
(IMAP_SECURITY_STARTTLS, _("Use STARTTLS")),
|
||||
)
|
||||
class ImapSecurity(models.IntegerChoices):
|
||||
NONE = 1, _("No encryption")
|
||||
SSL = 2, _("Use SSL")
|
||||
STARTTLS = 3, _("Use STARTTLS")
|
||||
|
||||
name = models.CharField(_("name"), max_length=256, unique=True)
|
||||
|
||||
@@ -34,8 +29,8 @@ class MailAccount(models.Model):
|
||||
|
||||
imap_security = models.PositiveIntegerField(
|
||||
_("IMAP security"),
|
||||
choices=IMAP_SECURITY_OPTIONS,
|
||||
default=IMAP_SECURITY_SSL,
|
||||
choices=ImapSecurity.choices,
|
||||
default=ImapSecurity.SSL,
|
||||
)
|
||||
|
||||
username = models.CharField(_("username"), max_length=256)
|
||||
@@ -61,48 +56,25 @@ class MailRule(models.Model):
|
||||
verbose_name = _("mail rule")
|
||||
verbose_name_plural = _("mail rules")
|
||||
|
||||
ATTACHMENT_TYPE_ATTACHMENTS_ONLY = 1
|
||||
ATTACHMENT_TYPE_EVERYTHING = 2
|
||||
class AttachmentProcessing(models.IntegerChoices):
|
||||
ATTACHMENTS_ONLY = 1, _("Only process attachments.")
|
||||
EVERYTHING = 2, _("Process all files, including 'inline' " "attachments.")
|
||||
|
||||
ATTACHMENT_TYPES = (
|
||||
(ATTACHMENT_TYPE_ATTACHMENTS_ONLY, _("Only process attachments.")),
|
||||
(
|
||||
ATTACHMENT_TYPE_EVERYTHING,
|
||||
_("Process all files, including 'inline' " "attachments."),
|
||||
),
|
||||
)
|
||||
class AttachmentAction(models.IntegerChoices):
|
||||
DELETE = 1, _("Mark as read, don't process read mails")
|
||||
MOVE = 2, _("Flag the mail, don't process flagged mails")
|
||||
MARK_READ = 3, _("Move to specified folder")
|
||||
FLAG = 4, _("Delete")
|
||||
|
||||
ACTION_DELETE = 1
|
||||
ACTION_MOVE = 2
|
||||
ACTION_MARK_READ = 3
|
||||
ACTION_FLAG = 4
|
||||
class TitleSource(models.IntegerChoices):
|
||||
FROM_SUBJECT = 1, _("Use subject as title")
|
||||
FROM_FILENAME = 2, _("Use attachment filename as title")
|
||||
|
||||
ACTIONS = (
|
||||
(ACTION_MARK_READ, _("Mark as read, don't process read mails")),
|
||||
(ACTION_FLAG, _("Flag the mail, don't process flagged mails")),
|
||||
(ACTION_MOVE, _("Move to specified folder")),
|
||||
(ACTION_DELETE, _("Delete")),
|
||||
)
|
||||
|
||||
TITLE_FROM_SUBJECT = 1
|
||||
TITLE_FROM_FILENAME = 2
|
||||
|
||||
TITLE_SELECTOR = (
|
||||
(TITLE_FROM_SUBJECT, _("Use subject as title")),
|
||||
(TITLE_FROM_FILENAME, _("Use attachment filename as title")),
|
||||
)
|
||||
|
||||
CORRESPONDENT_FROM_NOTHING = 1
|
||||
CORRESPONDENT_FROM_EMAIL = 2
|
||||
CORRESPONDENT_FROM_NAME = 3
|
||||
CORRESPONDENT_FROM_CUSTOM = 4
|
||||
|
||||
CORRESPONDENT_SELECTOR = (
|
||||
(CORRESPONDENT_FROM_NOTHING, _("Do not assign a correspondent")),
|
||||
(CORRESPONDENT_FROM_EMAIL, _("Use mail address")),
|
||||
(CORRESPONDENT_FROM_NAME, _("Use name (or mail address if not available)")),
|
||||
(CORRESPONDENT_FROM_CUSTOM, _("Use correspondent selected below")),
|
||||
)
|
||||
class CorrespondentSource(models.IntegerChoices):
|
||||
FROM_NOTHING = 1, _("Do not assign a correspondent")
|
||||
FROM_EMAIL = 2, _("Use mail address")
|
||||
FROM_NAME = 3, _("Use name (or mail address if not available)")
|
||||
FROM_CUSTOM = 4, _("Use correspondent selected below")
|
||||
|
||||
name = models.CharField(_("name"), max_length=256, unique=True)
|
||||
|
||||
@@ -119,7 +91,10 @@ class MailRule(models.Model):
|
||||
_("folder"),
|
||||
default="INBOX",
|
||||
max_length=256,
|
||||
help_text=_("Subfolders must be separated by dots."),
|
||||
help_text=_(
|
||||
"Subfolders must be separated by a delimiter, often a dot ('.') or"
|
||||
" slash ('/'), but it varies by mail server.",
|
||||
),
|
||||
)
|
||||
|
||||
filter_from = models.CharField(
|
||||
@@ -161,8 +136,8 @@ class MailRule(models.Model):
|
||||
|
||||
attachment_type = models.PositiveIntegerField(
|
||||
_("attachment type"),
|
||||
choices=ATTACHMENT_TYPES,
|
||||
default=ATTACHMENT_TYPE_ATTACHMENTS_ONLY,
|
||||
choices=AttachmentProcessing.choices,
|
||||
default=AttachmentProcessing.ATTACHMENTS_ONLY,
|
||||
help_text=_(
|
||||
"Inline attachments include embedded images, so it's best "
|
||||
"to combine this option with a filename filter.",
|
||||
@@ -171,8 +146,8 @@ class MailRule(models.Model):
|
||||
|
||||
action = models.PositiveIntegerField(
|
||||
_("action"),
|
||||
choices=ACTIONS,
|
||||
default=ACTION_MARK_READ,
|
||||
choices=AttachmentAction.choices,
|
||||
default=AttachmentAction.MARK_READ,
|
||||
)
|
||||
|
||||
action_parameter = models.CharField(
|
||||
@@ -190,8 +165,8 @@ class MailRule(models.Model):
|
||||
|
||||
assign_title_from = models.PositiveIntegerField(
|
||||
_("assign title from"),
|
||||
choices=TITLE_SELECTOR,
|
||||
default=TITLE_FROM_SUBJECT,
|
||||
choices=TitleSource.choices,
|
||||
default=TitleSource.FROM_SUBJECT,
|
||||
)
|
||||
|
||||
assign_tag = models.ForeignKey(
|
||||
@@ -212,8 +187,8 @@ class MailRule(models.Model):
|
||||
|
||||
assign_correspondent_from = models.PositiveIntegerField(
|
||||
_("assign correspondent from"),
|
||||
choices=CORRESPONDENT_SELECTOR,
|
||||
default=CORRESPONDENT_FROM_NOTHING,
|
||||
choices=CorrespondentSource.choices,
|
||||
default=CorrespondentSource.FROM_NOTHING,
|
||||
)
|
||||
|
||||
assign_correspondent = models.ForeignKey(
|
||||
|
@@ -246,13 +246,13 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
|
||||
rule = MailRule(
|
||||
name="a",
|
||||
assign_correspondent_from=MailRule.CORRESPONDENT_FROM_NOTHING,
|
||||
assign_correspondent_from=MailRule.CorrespondentSource.FROM_NOTHING,
|
||||
)
|
||||
self.assertIsNone(handler.get_correspondent(message, rule))
|
||||
|
||||
rule = MailRule(
|
||||
name="b",
|
||||
assign_correspondent_from=MailRule.CORRESPONDENT_FROM_EMAIL,
|
||||
assign_correspondent_from=MailRule.CorrespondentSource.FROM_EMAIL,
|
||||
)
|
||||
c = handler.get_correspondent(message, rule)
|
||||
self.assertIsNotNone(c)
|
||||
@@ -264,7 +264,7 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
|
||||
rule = MailRule(
|
||||
name="c",
|
||||
assign_correspondent_from=MailRule.CORRESPONDENT_FROM_NAME,
|
||||
assign_correspondent_from=MailRule.CorrespondentSource.FROM_NAME,
|
||||
)
|
||||
c = handler.get_correspondent(message, rule)
|
||||
self.assertIsNotNone(c)
|
||||
@@ -275,7 +275,7 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
|
||||
rule = MailRule(
|
||||
name="d",
|
||||
assign_correspondent_from=MailRule.CORRESPONDENT_FROM_CUSTOM,
|
||||
assign_correspondent_from=MailRule.CorrespondentSource.FROM_CUSTOM,
|
||||
assign_correspondent=someone_else,
|
||||
)
|
||||
c = handler.get_correspondent(message, rule)
|
||||
@@ -289,9 +289,15 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
|
||||
handler = MailAccountHandler()
|
||||
|
||||
rule = MailRule(name="a", assign_title_from=MailRule.TITLE_FROM_FILENAME)
|
||||
rule = MailRule(
|
||||
name="a",
|
||||
assign_title_from=MailRule.TitleSource.FROM_FILENAME,
|
||||
)
|
||||
self.assertEqual(handler.get_title(message, att, rule), "this_is_the_file")
|
||||
rule = MailRule(name="b", assign_title_from=MailRule.TITLE_FROM_SUBJECT)
|
||||
rule = MailRule(
|
||||
name="b",
|
||||
assign_title_from=MailRule.TitleSource.FROM_SUBJECT,
|
||||
)
|
||||
self.assertEqual(handler.get_title(message, att, rule), "the message title")
|
||||
|
||||
def test_handle_message(self):
|
||||
@@ -302,7 +308,10 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
)
|
||||
|
||||
account = MailAccount()
|
||||
rule = MailRule(assign_title_from=MailRule.TITLE_FROM_FILENAME, account=account)
|
||||
rule = MailRule(
|
||||
assign_title_from=MailRule.TitleSource.FROM_FILENAME,
|
||||
account=account,
|
||||
)
|
||||
|
||||
result = self.mail_account_handler.handle_message(message, rule)
|
||||
|
||||
@@ -346,7 +355,10 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
)
|
||||
|
||||
account = MailAccount()
|
||||
rule = MailRule(assign_title_from=MailRule.TITLE_FROM_FILENAME, account=account)
|
||||
rule = MailRule(
|
||||
assign_title_from=MailRule.TitleSource.FROM_FILENAME,
|
||||
account=account,
|
||||
)
|
||||
|
||||
result = self.mail_account_handler.handle_message(message, rule)
|
||||
|
||||
@@ -369,7 +381,10 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
)
|
||||
|
||||
account = MailAccount()
|
||||
rule = MailRule(assign_title_from=MailRule.TITLE_FROM_FILENAME, account=account)
|
||||
rule = MailRule(
|
||||
assign_title_from=MailRule.TitleSource.FROM_FILENAME,
|
||||
account=account,
|
||||
)
|
||||
|
||||
result = self.mail_account_handler.handle_message(message, rule)
|
||||
|
||||
@@ -392,9 +407,9 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
|
||||
account = MailAccount()
|
||||
rule = MailRule(
|
||||
assign_title_from=MailRule.TITLE_FROM_FILENAME,
|
||||
assign_title_from=MailRule.TitleSource.FROM_FILENAME,
|
||||
account=account,
|
||||
attachment_type=MailRule.ATTACHMENT_TYPE_EVERYTHING,
|
||||
attachment_type=MailRule.AttachmentProcessing.EVERYTHING,
|
||||
)
|
||||
|
||||
result = self.mail_account_handler.handle_message(message, rule)
|
||||
@@ -409,33 +424,36 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
_AttachmentDef(filename="f2.pdf"),
|
||||
_AttachmentDef(filename="f3.pdf"),
|
||||
_AttachmentDef(filename="f2.png"),
|
||||
_AttachmentDef(filename="file.PDf"),
|
||||
_AttachmentDef(filename="f1.Pdf"),
|
||||
],
|
||||
)
|
||||
|
||||
tests = [
|
||||
("*.pdf", ["f1.pdf", "f2.pdf", "f3.pdf"]),
|
||||
("f1.pdf", ["f1.pdf"]),
|
||||
("*.pdf", ["f1.pdf", "f1.Pdf", "f2.pdf", "f3.pdf", "file.PDf"]),
|
||||
("f1.pdf", ["f1.pdf", "f1.Pdf"]),
|
||||
("f1", []),
|
||||
("*", ["f1.pdf", "f2.pdf", "f3.pdf", "f2.png"]),
|
||||
("*", ["f1.pdf", "f2.pdf", "f3.pdf", "f2.png", "f1.Pdf", "file.PDf"]),
|
||||
("*.png", ["f2.png"]),
|
||||
]
|
||||
|
||||
for (pattern, matches) in tests:
|
||||
matches.sort()
|
||||
self.async_task.reset_mock()
|
||||
account = MailAccount()
|
||||
rule = MailRule(
|
||||
assign_title_from=MailRule.TITLE_FROM_FILENAME,
|
||||
assign_title_from=MailRule.TitleSource.FROM_FILENAME,
|
||||
account=account,
|
||||
filter_attachment_filename=pattern,
|
||||
)
|
||||
|
||||
result = self.mail_account_handler.handle_message(message, rule)
|
||||
|
||||
self.assertEqual(result, len(matches))
|
||||
filenames = [
|
||||
a[1]["override_filename"] for a in self.async_task.call_args_list
|
||||
]
|
||||
self.assertCountEqual(filenames, matches)
|
||||
self.assertEqual(result, len(matches), f"Error with pattern: {pattern}")
|
||||
filenames = sorted(
|
||||
[a[1]["override_filename"] for a in self.async_task.call_args_list],
|
||||
)
|
||||
self.assertListEqual(filenames, matches)
|
||||
|
||||
def test_handle_mail_account_mark_read(self):
|
||||
|
||||
@@ -449,7 +467,7 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
_ = MailRule.objects.create(
|
||||
name="testrule",
|
||||
account=account,
|
||||
action=MailRule.ACTION_MARK_READ,
|
||||
action=MailRule.AttachmentAction.MARK_READ,
|
||||
)
|
||||
|
||||
self.assertEqual(len(self.bogus_mailbox.messages), 3)
|
||||
@@ -472,7 +490,7 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
_ = MailRule.objects.create(
|
||||
name="testrule",
|
||||
account=account,
|
||||
action=MailRule.ACTION_DELETE,
|
||||
action=MailRule.AttachmentAction.DELETE,
|
||||
filter_subject="Invoice",
|
||||
)
|
||||
|
||||
@@ -493,7 +511,7 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
_ = MailRule.objects.create(
|
||||
name="testrule",
|
||||
account=account,
|
||||
action=MailRule.ACTION_FLAG,
|
||||
action=MailRule.AttachmentAction.FLAG,
|
||||
filter_subject="Invoice",
|
||||
)
|
||||
|
||||
@@ -516,7 +534,7 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
_ = MailRule.objects.create(
|
||||
name="testrule",
|
||||
account=account,
|
||||
action=MailRule.ACTION_MOVE,
|
||||
action=MailRule.AttachmentAction.MOVE,
|
||||
action_parameter="spam",
|
||||
filter_subject="Claim",
|
||||
)
|
||||
@@ -562,7 +580,7 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
_ = MailRule.objects.create(
|
||||
name="testrule",
|
||||
account=account,
|
||||
action=MailRule.ACTION_MOVE,
|
||||
action=MailRule.AttachmentAction.MOVE,
|
||||
action_parameter="spam",
|
||||
filter_subject="Claim",
|
||||
)
|
||||
@@ -583,7 +601,7 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
_ = MailRule.objects.create(
|
||||
name="testrule",
|
||||
account=account,
|
||||
action=MailRule.ACTION_MOVE,
|
||||
action=MailRule.AttachmentAction.MOVE,
|
||||
action_parameter="spam",
|
||||
filter_subject="Claim",
|
||||
order=1,
|
||||
@@ -592,7 +610,7 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
_ = MailRule.objects.create(
|
||||
name="testrule2",
|
||||
account=account,
|
||||
action=MailRule.ACTION_MOVE,
|
||||
action=MailRule.AttachmentAction.MOVE,
|
||||
action_parameter="spam",
|
||||
filter_subject="Claim",
|
||||
order=2,
|
||||
@@ -622,7 +640,7 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
_ = MailRule.objects.create(
|
||||
name="testrule",
|
||||
account=account,
|
||||
action=MailRule.ACTION_MOVE,
|
||||
action=MailRule.AttachmentAction.MOVE,
|
||||
action_parameter="spam",
|
||||
)
|
||||
|
||||
@@ -647,9 +665,9 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
name="testrule",
|
||||
filter_from="amazon@amazon.de",
|
||||
account=account,
|
||||
action=MailRule.ACTION_MOVE,
|
||||
action=MailRule.AttachmentAction.MOVE,
|
||||
action_parameter="spam",
|
||||
assign_correspondent_from=MailRule.CORRESPONDENT_FROM_EMAIL,
|
||||
assign_correspondent_from=MailRule.CorrespondentSource.FROM_EMAIL,
|
||||
)
|
||||
|
||||
self.mail_account_handler.handle_mail_account(account)
|
||||
@@ -684,7 +702,7 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
rule = MailRule.objects.create(
|
||||
name="testrule3",
|
||||
account=account,
|
||||
action=MailRule.ACTION_DELETE,
|
||||
action=MailRule.AttachmentAction.DELETE,
|
||||
filter_subject="Claim",
|
||||
)
|
||||
|
||||
|
@@ -8,6 +8,8 @@ from documents.parsers import make_thumbnail_from_pdf
|
||||
from documents.parsers import ParseError
|
||||
from PIL import Image
|
||||
|
||||
Image.MAX_IMAGE_PIXELS = settings.OCR_MAX_IMAGE_PIXELS
|
||||
|
||||
|
||||
class NoTextFoundException(Exception):
|
||||
pass
|
||||
|
@@ -6,6 +6,8 @@ from PIL import Image
|
||||
from PIL import ImageDraw
|
||||
from PIL import ImageFont
|
||||
|
||||
Image.MAX_IMAGE_PIXELS = settings.OCR_MAX_IMAGE_PIXELS
|
||||
|
||||
|
||||
class TextDocumentParser(DocumentParser):
|
||||
"""
|
||||
|
@@ -7,7 +7,7 @@ max-line-length = 88
|
||||
|
||||
[tool:pytest]
|
||||
DJANGO_SETTINGS_MODULE=paperless.settings
|
||||
addopts = --pythonwarnings=all --cov --cov-report=html -n auto
|
||||
addopts = --pythonwarnings=all --cov --cov-report=html --numprocesses auto --quiet
|
||||
env =
|
||||
PAPERLESS_DISABLE_DBHANDLER=true
|
||||
|
||||
|