Compare commits

...

42 Commits

Author SHA1 Message Date
Jonas Winkler
702b985ceb Merge pull request #558 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_de
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'de'
2021-02-17 14:46:25 +01:00
Jonas Winkler
7d87bcbb98 Merge pull request #560 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_en_GB
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'en_GB'
2021-02-17 14:46:14 +01:00
Jonas Winkler
340521aa0d Merge pull request #559 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_nl_NL
Translate '/src/locale/en_US/LC_MESSAGES/django.po' in 'nl_NL'
2021-02-17 14:46:03 +01:00
transifex-integration[bot]
7bc557a999 Apply translations in en_GB
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'en_GB' language.
2021-02-17 13:36:15 +00:00
jonaswinkler
dfa7cdf47e Merge branch 'dev' of github.com:jonaswinkler/paperless-ng into dev 2021-02-17 14:26:26 +01:00
jonaswinkler
0d78e58d77 fixed paperless not properly selecting en-gb 2021-02-17 14:26:06 +01:00
transifex-integration[bot]
58df3d5767 Apply translations in nl_NL
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'nl_NL' language.
2021-02-17 12:57:48 +00:00
Jonas Winkler
4e4d6e806c Update Crowdin configuration file 2021-02-17 13:22:45 +01:00
Jonas Winkler
6ff99945f3 Update Crowdin configuration file 2021-02-17 13:18:19 +01:00
transifex-integration[bot]
b7f1b9f8ad Apply translations in de
translation completed for the source file '/src/locale/en_US/LC_MESSAGES/django.po'
on the 'de' language.
2021-02-17 11:50:01 +00:00
jonaswinkler
08a44cf468 changelog and version 2021-02-17 12:31:19 +01:00
jonaswinkler
a1162d6d5a update requirements 2021-02-17 12:25:34 +01:00
jonaswinkler
1c81d88013 add support for iso 8601 date display 2021-02-17 12:15:22 +01:00
jonaswinkler
1e4ec7e29e added en-GB language 2021-02-16 14:54:18 +01:00
jonaswinkler
2c4e34dd0c changelog 2021-02-15 23:44:48 +01:00
jonaswinkler
cb308fae7b only show inbox statistics if inbox tags are defined 2021-02-15 23:14:54 +01:00
jonaswinkler
3f03d51b24 version bump 2021-02-15 16:52:45 +01:00
jonaswinkler
831db6ab87 note regarding Python 3.6 2021-02-15 16:46:06 +01:00
jonaswinkler
43fdf634f2 added a note regarding python 3.6 2021-02-15 16:37:44 +01:00
jonaswinkler
f07a6b4586 PAPERLESS_WEBSERVER_WORKERS option 2021-02-15 16:27:35 +01:00
jonaswinkler
2fcf484229 bugfix dismissing wrong status messages 2021-02-15 14:52:47 +01:00
jonaswinkler
8bf4241b16 some search index optimizations 2021-02-15 13:26:36 +01:00
jonaswinkler
56bd966c02 local import of ocrmypdf so that the webserver does not load that 2021-02-15 12:18:10 +01:00
jonaswinkler
416101d557 only import dateparser when required 2021-02-15 11:52:46 +01:00
jonaswinkler
c330cca2c9 remove unused imports 2021-02-15 11:26:13 +01:00
jonaswinkler
7e88085377 load sklearn modules only when training data has changed 2021-02-15 11:25:25 +01:00
jonaswinkler
5e669534f2 reorganized test case 2021-02-14 17:24:31 +01:00
jonaswinkler
98b147b622 better sanity checker that logs messages in the log files and does not fail on warnings. 2021-02-14 17:08:29 +01:00
jonaswinkler
df6c59bc4f update dependencies 2021-02-14 15:38:47 +01:00
jonaswinkler
6e48da41e5 changelog 2021-02-14 14:05:42 +01:00
Jonas Winkler
5c8a01a6e8 Merge pull request #538 from jonaswinkler/translations_src-locale-en-us-lc-messages-django-po--dev_cs
Translate '/src/locale/en-us/LC_MESSAGES/django.po' in 'cs'
2021-02-14 13:41:33 +01:00
jonaswinkler
3d0a52c25f only load channels app if DEBUG is enabled; its only purpose is to monkey-patch the runserver command. 2021-02-14 12:50:30 +01:00
jonaswinkler
43c729568b release worker memory after tasks are done. 2021-02-14 12:29:55 +01:00
transifex-integration[bot]
62caeed283 Apply translations in cs
translation completed for the source file '/src/locale/en-us/LC_MESSAGES/django.po'
on the 'cs' language.
2021-02-14 07:05:05 +00:00
jonaswinkler
12836d4c68 revert django-q configuration 2021-02-13 20:25:52 +01:00
jonaswinkler
b48e67d714 revert a faulty change that caused memory usage to explode #537 2021-02-13 19:51:04 +01:00
jonaswinkler
f91f4d71bb Merge branch 'master' into dev 2021-02-13 18:09:14 +01:00
jonaswinkler
0a1f264c71 Gotenberg troubleshooting 2021-02-13 18:09:00 +01:00
jonaswinkler
64d61ae2fa version bump 2021-02-13 18:01:19 +01:00
jonaswinkler
5f0e800f6e metadata tab not showing anything if files are missing #534 2021-02-13 16:41:03 +01:00
jonaswinkler
8b2965d55b added sanity checker management command for manual execution #534 2021-02-13 16:39:29 +01:00
jonaswinkler
ed478a1d73 change thumbnail display for extra wide images #433 2021-02-12 18:20:17 +01:00
48 changed files with 1691 additions and 633 deletions

View File

@@ -39,7 +39,7 @@ scikit-learn="==0.24.0"
# Prevent scipy updates because 1.6 is incompatible with python 3.6
scipy="~=1.5.4"
whitenoise = "~=5.2.0"
watchdog = "*"
watchdog = "~=1.0.0"
whoosh="~=2.7.4"
inotifyrecursive = "~=0.3.4"
ocrmypdf = "~=11.6"
@@ -51,7 +51,6 @@ channels = "~=3.0"
channels-redis = "*"
uvicorn = {extras = ["standard"], version = "*"}
concurrent-log-handler = "*"
django-redis = "*"
# uvloop 0.15+ incompatible with python 3.6
uvloop = "~=0.14.0"
# TODO: keep an eye on piwheel builds and update this once available (https://www.piwheels.org/project/cryptography/)

127
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "b3bed0a6b8981e8fffc1b6aa3bc35a0b1472f28e6f745c62469eb8045740e57b"
"sha256": "bd8b69979d91f4d8c52cac127c891d750c52959807220a98dcf74fed126bfa26"
},
"pipfile-spec": 6,
"requires": {},
@@ -60,11 +60,11 @@
},
"autobahn": {
"hashes": [
"sha256:93df8fc9d1821c9dabff9fed52181a9ad6eea5e9989d53102c391607d7c1666e",
"sha256:cceed2121b7a93024daa93c91fae33007f8346f0e522796421f36a6183abea99"
"sha256:41a3a3f89cde48643baf4e105d9491c566295f9abee951379e59121784044b8b",
"sha256:7e6b1bf95196b733978bab2d54a7ab8899c16ce11be369dc58422c07b7eea726"
],
"markers": "python_version >= '3.6'",
"version": "==21.1.1"
"version": "==21.2.1"
},
"automat": {
"hashes": [
@@ -90,47 +90,47 @@
},
"cffi": {
"hashes": [
"sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e",
"sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d",
"sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a",
"sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec",
"sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362",
"sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668",
"sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c",
"sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b",
"sha256:23f318bf74b170c6e9adb390e8bd282457f6de46c19d03b52f3fd042b5e19654",
"sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06",
"sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698",
"sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2",
"sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c",
"sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7",
"sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009",
"sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03",
"sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b",
"sha256:7ef7d4ced6b325e92eb4d3502946c78c5367bc416398d387b39591532536734e",
"sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909",
"sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53",
"sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35",
"sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26",
"sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b",
"sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01",
"sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb",
"sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293",
"sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd",
"sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d",
"sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3",
"sha256:be8661bcee1bc2fc4b033a6ab65bd1f87ce5008492601695d0b9a4e820c3bde5",
"sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d",
"sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e",
"sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca",
"sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d",
"sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775",
"sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375",
"sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b",
"sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b",
"sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f"
"sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813",
"sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06",
"sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea",
"sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee",
"sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396",
"sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73",
"sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315",
"sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1",
"sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49",
"sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892",
"sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482",
"sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058",
"sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5",
"sha256:5560dbf8deedbffb638d8a2da31da91094db361cc07f8a501a339b2daae2cbcc",
"sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53",
"sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045",
"sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3",
"sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5",
"sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e",
"sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c",
"sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369",
"sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827",
"sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053",
"sha256:9338beed13d880320450d95c9e07ccf839faa3ea7b75d788f4ed46d845044a71",
"sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa",
"sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4",
"sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322",
"sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132",
"sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62",
"sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa",
"sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0",
"sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396",
"sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e",
"sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991",
"sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6",
"sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1",
"sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406",
"sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d",
"sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"
],
"version": "==1.14.4"
"version": "==1.14.5"
},
"channels": {
"hashes": [
@@ -273,15 +273,6 @@
"index": "pypi",
"version": "==1.3.4"
},
"django-redis": {
"hashes": [
"sha256:1133b26b75baa3664164c3f44b9d5d133d1b8de45d94d79f38d1adc5b1d502e5",
"sha256:306589c7021e6468b2656edc89f62b8ba67e8d5a1c8877e2688042263daa7a63",
"sha256:f2b25b62cc95b63b7059aaf8e81710e7eea94678e545d31c46e47a6f4af99e56"
],
"index": "pypi",
"version": "==4.12.1"
},
"djangorestframework": {
"hashes": [
"sha256:0209bafcb7b5010fdfec784034f059d512256424de2a0f084cb82b096d6dd6a7",
@@ -600,11 +591,11 @@
},
"ocrmypdf": {
"hashes": [
"sha256:a54634d017a2f44aa2115b0b6ae5aa41a7cec018f5c53d16ad3abec1e70b3db7",
"sha256:d0e2da48d4abd90f48f0937b2cd4ba57503b56c603f5e3aa91e20e3b21a036cd"
"sha256:0f624456a50be0b0bc8c0b59704d159f637616c093a1cabe8bb383706561bcf7",
"sha256:b829ad640a6160423162012e094ee2f7cd074ec99efadd7f7486954ec9182985"
],
"index": "pypi",
"version": "==11.6.0"
"version": "==11.6.2"
},
"pathvalidate": {
"hashes": [
@@ -850,11 +841,11 @@
},
"python-magic": {
"hashes": [
"sha256:356efa93c8899047d1eb7d3eb91e871ba2f5b1376edbaf4cc305e3c872207355",
"sha256:b757db2a5289ea3f1ced9e60f072965243ea43a2221430048fd8cacab17be0ce"
"sha256:8551e804c09a3398790bd9e392acb26554ae2609f29c72abb0b9dee9a5571eae",
"sha256:ca884349f2c92ce830e3f498c5b7c7051fe2942c3ee4332f65213b8ebff15a62"
],
"index": "pypi",
"version": "==0.4.18"
"version": "==0.4.22"
},
"pytz": {
"hashes": [
@@ -1113,11 +1104,11 @@
},
"tqdm": {
"hashes": [
"sha256:2874fa525c051177583ec59c0fb4583e91f28ccd3f217ffad2acdb32d2c789ac",
"sha256:ab9b659241d82b8b51b2269ee243ec95286046bf06015c4e15a947cc15914211"
"sha256:11d544652edbdfc9cc41aa4c8a5c166513e279f3f2d9f1a9e1c89935b51de6ff",
"sha256:a89be573bfddb81bb0b395a416d5e55e3ecc73ce95a368a4f6360bedea33195e"
],
"index": "pypi",
"version": "==4.56.1"
"version": "==4.56.2"
},
"twisted": {
"extras": [
@@ -1649,11 +1640,11 @@
},
"pygments": {
"hashes": [
"sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435",
"sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337"
"sha256:37a13ba168a02ac54cc5891a42b1caec333e59b66addb7fa633ea8a6d73445c0",
"sha256:b21b072d0ccdf29297a82a2363359d99623597b8a265b8081760e4d0f7153c88"
],
"markers": "python_version >= '3.5'",
"version": "==2.7.4"
"version": "==2.8.0"
},
"pyparsing": {
"hashes": [
@@ -1846,11 +1837,11 @@
},
"tox": {
"hashes": [
"sha256:65d0e90ceb816638a50d64f4b47b11da767b284c0addda2294cb3cd69bd72425",
"sha256:cf7fef81a3a2434df4d7af2a6d1bf606d2970220addfbe7dea2615bd4bb2c252"
"sha256:89afa9c59c04beb55eda789c7a65feb1a70fde117f85f1bd1c27c66758456e60",
"sha256:ed1e650cf6368bcbc4a071eeeba363c480920e0ed8a9ad1793c7caaa5ad33d49"
],
"index": "pypi",
"version": "==3.21.4"
"version": "==3.22.0"
},
"urllib3": {
"hashes": [

View File

@@ -1,5 +1,5 @@
files:
- source: /src/locale/en-us/LC_MESSAGES/django.po
translation: /src/locale/%two_letters_code%/LC_MESSAGES/django.po
- source: /src/locale/en_US/LC_MESSAGES/django.po
translation: /src/locale/%locale_with_underscore%/LC_MESSAGES/django.po
- source: /src-ui/messages.xlf
translation: /src-ui/src/locale/messages.%two_letters_code%.xlf
translation: /src-ui/src/locale/messages.%locale_with_underscore%.xlf

View File

@@ -1,4 +1,4 @@
for command in document_archiver document_exporter document_importer mail_fetcher document_create_classifier document_index document_renamer document_retagger document_thumbnails;
for command in document_archiver document_exporter document_importer mail_fetcher document_create_classifier document_index document_renamer document_retagger document_thumbnails document_sanity_checker;
do
echo "installing $command..."
sed "s/management_command/$command/g" management_script.sh > /usr/local/bin/$command

View File

@@ -410,6 +410,34 @@ the naming scheme.
The command takes no arguments and processes all your documents at once.
.. _utilities-sanity-checker:
Sanity checker
==============
Paperless has a built-in sanity checker that inspects your document collection for issues.
The issues detected by the sanity checker are as follows:
* Missing original files.
* Missing archive files.
* Inaccessible original files due to improper permissions.
* Inaccessible archive files due to improper permissions.
* Corrupted original documents by comparing their checksum against what is stored in the database.
* Corrupted archive documents by comparing their checksum against what is stored in the database.
* Missing thumbnails.
* Inaccessible thumbnails due to improper permissions.
* Documents without any content (warning).
* Orphaned files in the media directory (warning). These are files that are not referenced by any document im paperless.
.. code::
document_sanity_checker
The command takes no arguments. Depending on the size of your document archive, this may take some time.
Fetching e-mail
===============

View File

@@ -5,6 +5,50 @@
Changelog
*********
paperless-ng 1.1.4
##################
* Added English (GB) locale.
* Added ISO-8601 date display option.
.. note::
Some packages that paperless depends on are slowly dropping Python 3.6
support one after another, including the web server. Supporting Python
3.6 means that I cannot update these packages anymore.
At some point, paperless will drop Python 3.6 support. If using a bare
metal installation and you're still on Python 3.6, upgrade to 3.7 or newer.
If using docker, this does not affect you.
paperless-ng 1.1.3
##################
* Added a docker-specific configuration option to adjust the number of
worker processes of the web server. See :ref:`configuration-docker`.
* Some more memory usage optimizations.
* Don't show inbox statistics if no inbox tag is defined.
paperless-ng 1.1.2
##################
* Always show top left corner of thumbnails, even for extra wide documents.
* Added a management command for executing the sanity checker directly.
See :ref:`utilities-sanity-checker`.
* The weekly sanity check now reports messages in the log files.
* Fixed an issue with the metadata tab not reporting anything in case of missing files.
* Reverted a change from 1.1.0 that caused huge memory usage due to redis caching.
* Some memory usage optimizations.
paperless-ng 1.1.1
##################

View File

@@ -555,3 +555,65 @@ PAPERLESS_GS_BINARY=<path>
PAPERLESS_OPTIPNG_BINARY=<path>
Defaults to "/usr/bin/optipng".
.. _configuration-docker:
Docker-specific options
#######################
These options don't have any effect in ``paperless.conf``. These options adjust
the behavior of the docker container. Configure these in `docker-compose.env`.
PAPERLESS_WEBSERVER_WORKERS=<num>
The number of worker processes the webserver should spawn. More worker processes
usually result in the front end to load data much quicker. However, each worker process
also loads the entire application into memory separately, so increasing this value
will increase RAM usage.
Consider configuring this to 1 on low power devices with limited amount of RAM.
Defaults to 2.
USERMAP_UID=<uid>
The ID of the paperless user in the container. Set this to your actual user ID on the
host system, which you can get by executing
.. code:: shell-session
$ id -u
Paperless will change ownership on its folders to this user, so you need to get this right
in order to be able to write to the consumption directory.
Defaults to 1000.
USERMAP_GID=<gid>
The ID of the paperless Group in the container. Set this to your actual group ID on the
host system, which you can get by executing
.. code:: shell-session
$ id -g
Paperless will change ownership on its folders to this group, so you need to get this right
in order to be able to write to the consumption directory.
Defaults to 1000.
PAPERLESS_OCR_LANGUAGES=<list>
Additional OCR languages to install. By default, paperless comes with
English, German, Italian, Spanish and French. If your language is not in this list, install
additional languages with this configuration option:
.. code:: bash
PAPERLESS_OCR_LANGUAGES=tur ces
To actually use these languages, also set the default OCR language of paperless:
.. code:: bash
PAPERLESS_OCR_LANGUAGE=tur
Defaults to none, which does not install any additional languages.

View File

@@ -763,7 +763,8 @@ configuring some options in paperless can help improve performance immensely:
* Stick with SQLite to save some resources.
* Consider setting ``PAPERLESS_OCR_PAGES`` to 1, so that paperless will only OCR
the first page of your documents.
the first page of your documents. In most cases, this page contains enough
information to be able to find it.
* ``PAPERLESS_TASK_WORKERS`` and ``PAPERLESS_THREADS_PER_WORKER`` are configured
to use all cores. The Raspberry Pi models 3 and up have 4 cores, meaning that
paperless will use 2 workers and 2 threads per worker. This may result in
@@ -776,6 +777,8 @@ configuring some options in paperless can help improve performance immensely:
file generation for already ocr'ed documents entirely.
* Set ``PAPERLESS_OPTIMIZE_THUMBNAILS`` to 'false' if you want faster consumption
times. Thumbnails will be about 20% larger.
* If using docker, consider setting ``PAPERLESS_WEBSERVER_WORKERS`` to
1. This will save some memory.
For details, refer to :ref:`configuration`.

View File

@@ -94,6 +94,30 @@ If you want to get rid of the warning or actually experience issues with automat
the file ``classification_model.pickle`` in the data directory and let paperless recreate it.
504 Server Error: Gateway Timeout when adding Office documents
##############################################################
You may experience these errors when using the optional TIKA integration:
.. code::
requests.exceptions.HTTPError: 504 Server Error: Gateway Timeout for url: http://gotenberg:3000/convert/office
Gotenberg is a server that converts Office documents into PDF documents and has a default timeout of 10 seconds.
When conversion takes longer, Gotenberg raises this error.
You can increase the timeout by configuring an environment variable for gotenberg (see also `here <https://thecodingmachine.github.io/gotenberg/#environment_variables.default_wait_timeout>`__).
If using docker-compose, this is achieved by the following configuration change in the ``docker-compose.yml`` file:
.. code:: yaml
gotenberg:
image: thecodingmachine/gotenberg
restart: unless-stopped
environment:
DISABLE_GOOGLE_CHROME: 1
DEFAULT_WAIT_TIMEOUT: 30
Permission denied errors in the consumption directory
#####################################################

View File

@@ -1,5 +1,7 @@
import os
bind = '0.0.0.0:8000'
workers = 2
workers = int(os.getenv("PAPERLESS_WEBSERVER_WORKERS", 2))
worker_class = 'uvicorn.workers.UvicornWorker'
timeout = 120

View File

@@ -12,11 +12,11 @@ arrow==0.17.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2,
asgiref==3.3.1; python_version >= '3.5'
async-timeout==3.0.1; python_full_version >= '3.5.3'
attrs==20.3.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
autobahn==21.1.1; python_version >= '3.6'
autobahn==21.2.1; python_version >= '3.6'
automat==20.2.0
blessed==1.17.12
certifi==2020.12.5
cffi==1.14.4
cffi==1.14.5
channels-redis==3.2.0
channels==3.0.3
chardet==4.0.0; python_version >= '3.1'
@@ -32,7 +32,6 @@ django-extensions==3.1.1
django-filter==2.4.0
django-picklefield==3.0.1; python_version >= '3'
django-q==1.3.4
django-redis==4.12.1
django==3.1.6
djangorestframework==3.12.2
filelock==3.0.12
@@ -54,7 +53,7 @@ langdetect==1.0.8
lxml==4.6.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
msgpack==1.0.2
numpy==1.19.5
ocrmypdf==11.6.0
ocrmypdf==11.6.2
pathvalidate==2.3.2
pdfminer.six==20201018; python_version >= '3.4'
pdftotext==2.1.5
@@ -72,7 +71,7 @@ python-dateutil==2.8.1
python-dotenv==0.15.0
python-gnupg==0.4.6
python-levenshtein==0.12.2
python-magic==0.4.18
python-magic==0.4.22
pytz==2021.1
pyyaml==5.4.1
redis==3.5.3
@@ -87,7 +86,7 @@ sortedcontainers==2.3.0
sqlparse==0.4.1; python_version >= '3.5'
threadpoolctl==2.1.0; python_version >= '3.5'
tika==1.24
tqdm==4.56.1
tqdm==4.56.2
twisted[tls]==20.3.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
txaio==20.12.1; python_version >= '3.6'
tzlocal==2.1

View File

@@ -18,7 +18,8 @@
"locales": {
"de": "src/locale/messages.de.xlf",
"nl-NL": "src/locale/messages.nl_NL.xlf",
"fr": "src/locale/messages.fr.xlf"
"fr": "src/locale/messages.fr.xlf",
"en-GB": "src/locale/messages.en_GB.xlf"
}
},
"architect": {

View File

@@ -64,10 +64,12 @@ import { CustomDatePipe } from './pipes/custom-date.pipe';
import localeFr from '@angular/common/locales/fr';
import localeNl from '@angular/common/locales/nl';
import localeDe from '@angular/common/locales/de';
import localeEnGb from '@angular/common/locales/en-GB';
registerLocaleData(localeFr)
registerLocaleData(localeNl)
registerLocaleData(localeDe)
registerLocaleData(localeEnGb)
@NgModule({
declarations: [

View File

@@ -1,6 +1,6 @@
<app-widget-frame title="Statistics" i18n-title>
<ng-container content>
<p class="card-text" i18n>Documents in inbox: {{statistics.documents_inbox}}</p>
<p class="card-text" i18n>Total documents: {{statistics.documents_total}}</p>
<p class="card-text" i18n *ngIf="statistics?.documents_inbox != null">Documents in inbox: {{statistics?.documents_inbox}}</p>
<p class="card-text" i18n>Total documents: {{statistics?.documents_total}}</p>
</ng-container>
</app-widget-frame>

View File

@@ -6,7 +6,7 @@
.doc-img {
object-fit: cover;
object-position: top;
object-position: top left;
height: 100%;
position: absolute;
mix-blend-mode: multiply;

View File

@@ -2,7 +2,7 @@
.doc-img {
object-fit: cover;
object-position: top;
object-position: top left;
height: 200px;
mix-blend-mode: multiply;
}

View File

@@ -34,7 +34,7 @@
<div class="col">
<select class="form-control" formControlName="dateLocale">
<option *ngFor="let lang of dateLocaleOptions" [ngValue]="lang.code">{{lang.name}}<span *ngIf="lang.code"> - {{today | date:'shortDate':null:lang.code}}</span></option>
<option *ngFor="let lang of dateLocaleOptions" [ngValue]="lang.code">{{lang.name}}<span *ngIf="lang.code"> - {{today | customDate:'shortDate':null:lang.code}}</span></option>
</select>
</div>
@@ -167,7 +167,7 @@
</li>
</ul>
<div [ngbNavOutlet]="nav" class="border-left border-right border-bottom p-3 mb-3 shadow"></div>
<div [ngbNavOutlet]="nav" class="border-left border-right border-bottom p-3 mb-3 shadow-sm"></div>
<button type="submit" class="btn btn-primary" i18n>Save</button>
</form>

View File

@@ -35,7 +35,7 @@ export class SettingsComponent implements OnInit {
savedViews: PaperlessSavedView[]
get computedDateLocale(): string {
return this.settingsForm.value.dateLocale || this.settingsForm.value.displayLanguage
return this.settingsForm.value.dateLocale || this.settingsForm.value.displayLanguage || this.currentLocale
}
constructor(
@@ -92,7 +92,10 @@ export class SettingsComponent implements OnInit {
}
get dateLocaleOptions(): LanguageOption[] {
return [{code: "", name: $localize`Use date format of display language`}].concat(this.settings.getLanguageOptions())
return [
{code: "", name: $localize`Use date format of display language`},
{code: "iso-8601", name: $localize`ISO 8601`}
].concat(this.settings.getLanguageOptions())
}
get today() {

View File

@@ -2,18 +2,29 @@ import { DatePipe } from '@angular/common';
import { Inject, LOCALE_ID, Pipe, PipeTransform } from '@angular/core';
import { SettingsService, SETTINGS_KEYS } from '../services/settings.service';
const FORMAT_TO_ISO_FORMAT = {
"longDate": "y-MM-dd",
"mediumDate": "yy-MM-dd",
"shortDate": "yy-MM-dd"
}
@Pipe({
name: 'customDate'
})
export class CustomDatePipe extends DatePipe implements PipeTransform {
constructor(@Inject(LOCALE_ID) locale: string, private settings: SettingsService) {
super(settings.get(SETTINGS_KEYS.DATE_LOCALE) || locale)
super(locale)
}
transform(value: any, format?: string, timezone?: string, locale?: string): string | null {
return super.transform(value, format || this.settings.get(SETTINGS_KEYS.DATE_FORMAT), timezone, locale)
let l = locale || this.settings.get(SETTINGS_KEYS.DATE_LOCALE)
let f = format || this.settings.get(SETTINGS_KEYS.DATE_FORMAT)
if (l == "iso-8601") {
return super.transform(value, FORMAT_TO_ISO_FORMAT[f], timezone)
} else {
return super.transform(value, format || this.settings.get(SETTINGS_KEYS.DATE_FORMAT), timezone, locale)
}
}
}

View File

@@ -169,7 +169,12 @@ export class ConsumerStatusService {
}
dismiss(status: FileStatus) {
let index = this.consumerStatus.findIndex(s => s.filename == status.filename)
let index
if (status.taskId != null) {
index = this.consumerStatus.findIndex(s => s.taskId == status.taskId)
} else {
index = this.consumerStatus.findIndex(s => s.filename == status.filename)
}
if (index > -1) {
this.consumerStatus.splice(index, 1)

View File

@@ -79,7 +79,8 @@ export class SettingsService {
getLanguageOptions(): LanguageOption[] {
return [
{code: "en-US", name: $localize`English (US)`, englishName: "English (US)"},
{code: "en-us", name: $localize`English (US)`, englishName: "English (US)"},
{code: "en-gb", name: $localize`English (GB)`, englishName: "English (GB)"},
{code: "de", name: $localize`German`, englishName: "German"},
{code: "nl", name: $localize`Dutch`, englishName: "Dutch"},
{code: "fr", name: $localize`French`, englishName: "French"}

View File

@@ -2,7 +2,7 @@ export const environment = {
production: true,
apiBaseUrl: "/api/",
appTitle: "Paperless-ng",
version: "1.1.1",
version: "1.1.4",
webSocketHost: window.location.host,
webSocketProtocol: (window.location.protocol == "https:" ? "wss:" : "ws:")
};

View File

@@ -515,7 +515,7 @@
</trans-unit>
<trans-unit datatype="html" id="8fa4d523f7b91df4390120b85ed0406138273e1a">
<source>Color</source>
<target>Color</target>
<target>Colour</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/tag-list/tag-list.component.html</context>
<context context-type="linenumber">20</context>

View File

@@ -1,10 +1,6 @@
from django.contrib import admin
from django.utils.html import format_html, format_html_join
from django.utils.safestring import mark_safe
from whoosh.writing import AsyncWriter
from . import index
from .models import Correspondent, Document, DocumentType, Log, Tag, \
from .models import Correspondent, Document, DocumentType, Tag, \
SavedView, SavedViewFilterRule
@@ -86,17 +82,21 @@ class DocumentAdmin(admin.ModelAdmin):
created_.short_description = "Created"
def delete_queryset(self, request, queryset):
ix = index.open_index()
with AsyncWriter(ix) as writer:
from documents import index
with index.open_index_writer() as writer:
for o in queryset:
index.remove_document(writer, o)
super(DocumentAdmin, self).delete_queryset(request, queryset)
def delete_model(self, request, obj):
from documents import index
index.remove_document_from_index(obj)
super(DocumentAdmin, self).delete_model(request, obj)
def save_model(self, request, obj, form, change):
from documents import index
index.add_or_update_document(obj)
super(DocumentAdmin, self).save_model(request, obj, form, change)

View File

@@ -2,9 +2,7 @@ import itertools
from django.db.models import Q
from django_q.tasks import async_task
from whoosh.writing import AsyncWriter
from documents import index
from documents.models import Document, Correspondent, DocumentType
@@ -99,8 +97,9 @@ def modify_tags(doc_ids, add_tags, remove_tags):
def delete(doc_ids):
Document.objects.filter(id__in=doc_ids).delete()
ix = index.open_index()
with AsyncWriter(ix) as writer:
from documents import index
with index.open_index_writer() as writer:
for id in doc_ids:
index.remove_document_by_id(writer, id)

View File

@@ -5,7 +5,6 @@ import pickle
import re
from django.conf import settings
from django.core.cache import cache
from documents.models import Document, MatchingModel
@@ -31,29 +30,23 @@ def load_classifier():
)
return None
version = os.stat(settings.MODEL_FILE).st_mtime
classifier = DocumentClassifier()
try:
classifier.load()
classifier = cache.get("paperless-classifier", version=version)
if not classifier:
classifier = DocumentClassifier()
try:
classifier.load()
cache.set("paperless-classifier", classifier,
version=version, timeout=86400)
except (EOFError, IncompatibleClassifierVersionError) as e:
# there's something wrong with the model file.
logger.exception(
f"Unrecoverable error while loading document "
f"classification model, deleting model file."
)
os.unlink(settings.MODEL_FILE)
classifier = None
except OSError as e:
logger.error(
f"Error while loading document classification model: {str(e)}"
)
classifier = None
except (EOFError, IncompatibleClassifierVersionError) as e:
# there's something wrong with the model file.
logger.exception(
f"Unrecoverable error while loading document "
f"classification model, deleting model file."
)
os.unlink(settings.MODEL_FILE)
classifier = None
except OSError as e:
logger.error(
f"Error while loading document classification model: {str(e)}"
)
classifier = None
return classifier
@@ -102,9 +95,6 @@ class DocumentClassifier(object):
pickle.dump(self.document_type_classifier, f)
def train(self):
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.neural_network import MLPClassifier
from sklearn.preprocessing import MultiLabelBinarizer, LabelBinarizer
data = list()
labels_tags = list()
@@ -169,6 +159,10 @@ class DocumentClassifier(object):
)
)
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.neural_network import MLPClassifier
from sklearn.preprocessing import MultiLabelBinarizer, LabelBinarizer
# Step 2: vectorize data
logger.debug("Vectorizing data...")
self.data_vectorizer = CountVectorizer(

View File

@@ -86,6 +86,22 @@ def open_index(recreate=False):
return create_in(settings.INDEX_DIR, get_schema())
@contextmanager
def open_index_writer(ix=None, optimize=False):
if ix:
writer = AsyncWriter(ix)
else:
writer = AsyncWriter(open_index())
try:
yield writer
except Exception as e:
logger.exception(str(e))
writer.cancel()
finally:
writer.commit(optimize=optimize)
def update_document(writer, doc):
tags = ",".join([t.name for t in doc.tags.all()])
writer.update_document(
@@ -110,14 +126,12 @@ def remove_document_by_id(writer, doc_id):
def add_or_update_document(document):
ix = open_index()
with AsyncWriter(ix) as writer:
with open_index_writer() as writer:
update_document(writer, document)
def remove_document_from_index(document):
ix = open_index()
with AsyncWriter(ix) as writer:
with open_index_writer() as writer:
remove_document(writer, document)

View File

@@ -0,0 +1,15 @@
from django.core.management.base import BaseCommand
from documents.sanity_checker import check_sanity
class Command(BaseCommand):
help = """
This command checks your document archive for issues.
""".replace(" ", "")
def handle(self, *args, **options):
messages = check_sanity(progress=True)
messages.log_messages()

View File

@@ -6,7 +6,6 @@ import shutil
import subprocess
import tempfile
import dateparser
import magic
from django.conf import settings
from django.utils import timezone
@@ -200,6 +199,8 @@ def parse_date(filename, text):
"""
Call dateparser.parse with a particular date ordering
"""
import dateparser
return dateparser.parse(
ds,
settings={

View File

@@ -1,45 +1,55 @@
import hashlib
import logging
import os
from django.conf import settings
from tqdm import tqdm
from documents.models import Document
class SanityMessage:
message = None
class SanityCheckMessages:
def __init__(self):
self._messages = []
def error(self, message):
self._messages.append({"level": logging.ERROR, "message": message})
def warning(self, message):
self._messages.append({"level": logging.WARNING, "message": message})
def info(self, message):
self._messages.append({"level": logging.INFO, "message": message})
def log_messages(self):
logger = logging.getLogger("paperless.sanity_checker")
if len(self._messages) == 0:
logger.info("Sanity checker detected no issues.")
else:
for msg in self._messages:
logger.log(msg['level'], msg['message'])
def __len__(self):
return len(self._messages)
def __getitem__(self, item):
return self._messages[item]
def has_error(self):
return any([msg['level'] == logging.ERROR for msg in self._messages])
def has_warning(self):
return any([msg['level'] == logging.WARNING for msg in self._messages])
class SanityWarning(SanityMessage):
def __init__(self, message):
self.message = message
def __str__(self):
return f"Warning: {self.message}"
class SanityCheckFailedException(Exception):
pass
class SanityError(SanityMessage):
def __init__(self, message):
self.message = message
def __str__(self):
return f"ERROR: {self.message}"
class SanityFailedError(Exception):
def __init__(self, messages):
self.messages = messages
def __str__(self):
message_string = "\n".join([str(m) for m in self.messages])
return (
f"The following issuse were found by the sanity checker:\n"
f"{message_string}\n\n===============\n\n")
def check_sanity():
messages = []
def check_sanity(progress=False):
messages = SanityCheckMessages()
present_files = []
for root, subdirs, files in os.walk(settings.MEDIA_ROOT):
@@ -50,11 +60,15 @@ def check_sanity():
if lockfile in present_files:
present_files.remove(lockfile)
for doc in Document.objects.all():
if progress:
docs = tqdm(Document.objects.all())
else:
docs = Document.objects.all()
for doc in docs:
# Check sanity of the thumbnail
if not os.path.isfile(doc.thumbnail_path):
messages.append(SanityError(
f"Thumbnail of document {doc.pk} does not exist."))
messages.error(f"Thumbnail of document {doc.pk} does not exist.")
else:
if os.path.normpath(doc.thumbnail_path) in present_files:
present_files.remove(os.path.normpath(doc.thumbnail_path))
@@ -62,15 +76,14 @@ def check_sanity():
with doc.thumbnail_file as f:
f.read()
except OSError as e:
messages.append(SanityError(
messages.error(
f"Cannot read thumbnail file of document {doc.pk}: {e}"
))
)
# Check sanity of the original file
# TODO: extract method
if not os.path.isfile(doc.source_path):
messages.append(SanityError(
f"Original of document {doc.pk} does not exist."))
messages.error(f"Original of document {doc.pk} does not exist.")
else:
if os.path.normpath(doc.source_path) in present_files:
present_files.remove(os.path.normpath(doc.source_path))
@@ -78,31 +91,31 @@ def check_sanity():
with doc.source_file as f:
checksum = hashlib.md5(f.read()).hexdigest()
except OSError as e:
messages.append(SanityError(
f"Cannot read original file of document {doc.pk}: {e}"))
messages.error(
f"Cannot read original file of document {doc.pk}: {e}")
else:
if not checksum == doc.checksum:
messages.append(SanityError(
messages.error(
f"Checksum mismatch of document {doc.pk}. "
f"Stored: {doc.checksum}, actual: {checksum}."
))
)
# Check sanity of the archive file.
if doc.archive_checksum and not doc.archive_filename:
messages.append(SanityError(
messages.error(
f"Document {doc.pk} has an archive file checksum, but no "
f"archive filename."
))
)
elif not doc.archive_checksum and doc.archive_filename:
messages.append(SanityError(
messages.error(
f"Document {doc.pk} has an archive file, but its checksum is "
f"missing."
))
)
elif doc.has_archive_version:
if not os.path.isfile(doc.archive_path):
messages.append(SanityError(
messages.error(
f"Archived version of document {doc.pk} does not exist."
))
)
else:
if os.path.normpath(doc.archive_path) in present_files:
present_files.remove(os.path.normpath(doc.archive_path))
@@ -110,26 +123,23 @@ def check_sanity():
with doc.archive_file as f:
checksum = hashlib.md5(f.read()).hexdigest()
except OSError as e:
messages.append(SanityError(
messages.error(
f"Cannot read archive file of document {doc.pk}: {e}"
))
)
else:
if not checksum == doc.archive_checksum:
messages.append(SanityError(
messages.error(
f"Checksum mismatch of archived document "
f"{doc.pk}. "
f"Stored: {doc.checksum}, actual: {checksum}."
))
f"Stored: {doc.archive_checksum}, "
f"actual: {checksum}."
)
# other document checks
if not doc.content:
messages.append(SanityWarning(
f"Document {doc.pk} has no content."
))
messages.info(f"Document {doc.pk} has no content.")
for extra_file in present_files:
messages.append(SanityWarning(
f"Orphaned file in media dir: {extra_file}"
))
messages.warning(f"Orphaned file in media dir: {extra_file}")
return messages

View File

@@ -11,7 +11,7 @@ from django.dispatch import receiver
from django.utils import timezone
from filelock import FileLock
from .. import index, matching
from .. import matching
from ..file_handling import delete_empty_directories, \
create_source_path_directory, \
generate_unique_filename
@@ -305,4 +305,6 @@ def set_log_entry(sender, document=None, logging_group=None, **kwargs):
def add_to_index(sender, document, **kwargs):
from documents import index
index.add_or_update_document(document)

View File

@@ -9,8 +9,7 @@ from documents import index, sanity_checker
from documents.classifier import DocumentClassifier, load_classifier
from documents.consumer import Consumer, ConsumerError
from documents.models import Document, Tag, DocumentType, Correspondent
from documents.sanity_checker import SanityFailedError
from documents.sanity_checker import SanityCheckFailedException
logger = logging.getLogger("paperless.tasks")
@@ -94,8 +93,15 @@ def consume_file(path,
def sanity_check():
messages = sanity_checker.check_sanity()
if len(messages) > 0:
raise SanityFailedError(messages)
messages.log_messages()
if messages.has_error():
raise SanityCheckFailedException(
"Sanity check failed with errors. See log.")
elif messages.has_warning():
return "Sanity check exited with warnings. See log."
elif len(messages) > 0:
return "Sanity check exited with infos. See log."
else:
return "No issues detected."

View File

@@ -4,6 +4,7 @@ from django.contrib.admin.sites import AdminSite
from django.test import TestCase
from django.utils import timezone
from documents import index
from documents.admin import DocumentAdmin
from documents.models import Document
from documents.tests.utils import DirectoriesMixin
@@ -11,37 +12,52 @@ from documents.tests.utils import DirectoriesMixin
class TestDocumentAdmin(DirectoriesMixin, TestCase):
def get_document_from_index(self, doc):
ix = index.open_index()
with ix.searcher() as searcher:
return searcher.document(id=doc.id)
def setUp(self) -> None:
super(TestDocumentAdmin, self).setUp()
self.doc_admin = DocumentAdmin(model=Document, admin_site=AdminSite())
@mock.patch("documents.admin.index.add_or_update_document")
def test_save_model(self, m):
def test_save_model(self):
doc = Document.objects.create(title="test")
doc.title = "new title"
self.doc_admin.save_model(None, doc, None, None)
self.assertEqual(Document.objects.get(id=doc.id).title, "new title")
m.assert_called_once()
self.assertEqual(self.get_document_from_index(doc)['title'], "new title")
@mock.patch("documents.admin.index.remove_document")
def test_delete_model(self, m):
def test_delete_model(self):
doc = Document.objects.create(title="test")
self.doc_admin.delete_model(None, doc)
self.assertRaises(Document.DoesNotExist, Document.objects.get, id=doc.id)
m.assert_called_once()
index.add_or_update_document(doc)
self.assertIsNotNone(self.get_document_from_index(doc))
@mock.patch("documents.admin.index.remove_document")
def test_delete_queryset(self, m):
self.doc_admin.delete_model(None, doc)
self.assertRaises(Document.DoesNotExist, Document.objects.get, id=doc.id)
self.assertIsNone(self.get_document_from_index(doc))
def test_delete_queryset(self):
docs = []
for i in range(42):
Document.objects.create(title="Many documents with the same title", checksum=f"{i:02}")
doc = Document.objects.create(title="Many documents with the same title", checksum=f"{i:02}")
docs.append(doc)
index.add_or_update_document(doc)
self.assertEqual(Document.objects.count(), 42)
for doc in docs:
self.assertIsNotNone(self.get_document_from_index(doc))
self.doc_admin.delete_queryset(None, Document.objects.all())
self.assertEqual(m.call_count, 42)
self.assertEqual(Document.objects.count(), 0)
for doc in docs:
self.assertIsNone(self.get_document_from_index(doc))
def test_created(self):
doc = Document.objects.create(title="test", created=timezone.datetime(2020, 4, 12))
self.assertEqual(self.doc_admin.created_(doc), "2020-04-12")

View File

@@ -442,6 +442,13 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
self.assertEqual(response.data['documents_total'], 3)
self.assertEqual(response.data['documents_inbox'], 1)
def test_statistics_no_inbox_tag(self):
Document.objects.create(title="none1", checksum="A")
response = self.client.get("/api/statistics/")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['documents_inbox'], None)
@mock.patch("documents.views.async_task")
def test_upload(self, m):
@@ -577,8 +584,11 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
def test_get_metadata(self):
doc = Document.objects.create(title="test", filename="file.pdf", mime_type="image/png", archive_checksum="A", archive_filename="archive.pdf")
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "documents", "thumbnails", "0000001.png"), doc.source_path)
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), doc.archive_path)
source_file = os.path.join(os.path.dirname(__file__), "samples", "documents", "thumbnails", "0000001.png")
archive_file = os.path.join(os.path.dirname(__file__), "samples", "simple.pdf")
shutil.copy(source_file, doc.source_path)
shutil.copy(archive_file, doc.archive_path)
response = self.client.get(f"/api/documents/{doc.pk}/metadata/")
self.assertEqual(response.status_code, 200)
@@ -591,6 +601,8 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
self.assertGreater(len(meta['archive_metadata']), 0)
self.assertEqual(meta['media_filename'], "file.pdf")
self.assertEqual(meta['archive_media_filename'], "archive.pdf")
self.assertEqual(meta['original_size'], os.stat(source_file).st_size)
self.assertEqual(meta['archive_size'], os.stat(archive_file).st_size)
def test_get_metadata_invalid_doc(self):
response = self.client.get(f"/api/documents/34576/metadata/")
@@ -612,6 +624,21 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
self.assertIsNone(meta['archive_metadata'])
self.assertIsNone(meta['archive_media_filename'])
def test_get_metadata_missing_files(self):
doc = Document.objects.create(title="test", filename="file.pdf", mime_type="application/pdf", archive_filename="file.pdf", archive_checksum="B", checksum="A")
response = self.client.get(f"/api/documents/{doc.pk}/metadata/")
self.assertEqual(response.status_code, 200)
meta = response.data
self.assertTrue(meta['has_archive_version'])
self.assertIsNone(meta['original_metadata'])
self.assertIsNone(meta['original_size'])
self.assertIsNone(meta['archive_metadata'])
self.assertIsNone(meta['archive_size'])
def test_get_empty_suggestions(self):
doc = Document.objects.create(title="test", mime_type="application/pdf")

View File

@@ -3,6 +3,7 @@ import tempfile
from pathlib import Path
from unittest import mock
import pytest
from django.conf import settings
from django.test import TestCase, override_settings
@@ -233,7 +234,6 @@ class TestClassifier(DirectoriesMixin, TestCase):
self.assertFalse(os.path.exists(settings.MODEL_FILE))
self.assertIsNone(load_classifier())
@override_settings(CACHES={'default': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'}})
@mock.patch("documents.classifier.DocumentClassifier.load")
def test_load_classifier(self, load):
Path(settings.MODEL_FILE).touch()
@@ -242,6 +242,7 @@ class TestClassifier(DirectoriesMixin, TestCase):
@override_settings(CACHES={'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'}})
@override_settings(MODEL_FILE=os.path.join(os.path.dirname(__file__), "data", "model.pickle"))
@pytest.mark.skip(reason="Disabled caching due to high memory usage - need to investigate.")
def test_load_classifier_cached(self):
classifier = load_classifier()
self.assertIsNotNone(classifier)
@@ -250,7 +251,6 @@ class TestClassifier(DirectoriesMixin, TestCase):
classifier2 = load_classifier()
load.assert_not_called()
@override_settings(CACHES={'default': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'}})
@mock.patch("documents.classifier.DocumentClassifier.load")
def test_load_classifier_incompatible_version(self, load):
Path(settings.MODEL_FILE).touch()
@@ -260,7 +260,6 @@ class TestClassifier(DirectoriesMixin, TestCase):
self.assertIsNone(load_classifier())
self.assertFalse(os.path.exists(settings.MODEL_FILE))
@override_settings(CACHES={'default': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'}})
@mock.patch("documents.classifier.DocumentClassifier.load")
def test_load_classifier_os_error(self, load):
Path(settings.MODEL_FILE).touch()

View File

@@ -65,6 +65,7 @@ class TestArchiver(DirectoriesMixin, TestCase):
self.assertEqual(doc1.archive_filename, "document.pdf")
self.assertEqual(doc2.archive_filename, "document_01.pdf")
class TestDecryptDocuments(TestCase):
@override_settings(
@@ -154,3 +155,24 @@ class TestCreateClassifier(TestCase):
call_command("document_create_classifier")
m.assert_called_once()
class TestSanityChecker(DirectoriesMixin, TestCase):
def test_no_issues(self):
with self.assertLogs() as capture:
call_command("document_sanity_checker")
self.assertEqual(len(capture.output), 1)
self.assertIn("Sanity checker detected no issues.", capture.output[0])
def test_errors(self):
doc = Document.objects.create(title="test", content="test", filename="test.pdf", checksum="abc")
Path(doc.source_path).touch()
Path(doc.thumbnail_path).touch()
with self.assertLogs() as capture:
call_command("document_sanity_checker")
self.assertEqual(len(capture.output), 1)
self.assertIn("Checksum mismatch of document", capture.output[0])

View File

@@ -1,3 +1,4 @@
import logging
import os
import shutil
from pathlib import Path
@@ -7,10 +8,59 @@ from django.conf import settings
from django.test import TestCase
from documents.models import Document
from documents.sanity_checker import check_sanity, SanityFailedError
from documents.sanity_checker import check_sanity, SanityCheckMessages
from documents.tests.utils import DirectoriesMixin
class TestSanityCheckMessages(TestCase):
def test_no_messages(self):
messages = SanityCheckMessages()
self.assertEqual(len(messages), 0)
self.assertFalse(messages.has_error())
self.assertFalse(messages.has_warning())
with self.assertLogs() as capture:
messages.log_messages()
self.assertEqual(len(capture.output), 1)
self.assertEqual(capture.records[0].levelno, logging.INFO)
self.assertEqual(capture.records[0].message, "Sanity checker detected no issues.")
def test_info(self):
messages = SanityCheckMessages()
messages.info("Something might be wrong")
self.assertEqual(len(messages), 1)
self.assertFalse(messages.has_error())
self.assertFalse(messages.has_warning())
with self.assertLogs() as capture:
messages.log_messages()
self.assertEqual(len(capture.output), 1)
self.assertEqual(capture.records[0].levelno, logging.INFO)
self.assertEqual(capture.records[0].message, "Something might be wrong")
def test_warning(self):
messages = SanityCheckMessages()
messages.warning("Something is wrong")
self.assertEqual(len(messages), 1)
self.assertFalse(messages.has_error())
self.assertTrue(messages.has_warning())
with self.assertLogs() as capture:
messages.log_messages()
self.assertEqual(len(capture.output), 1)
self.assertEqual(capture.records[0].levelno, logging.WARNING)
self.assertEqual(capture.records[0].message, "Something is wrong")
def test_error(self):
messages = SanityCheckMessages()
messages.error("Something is seriously wrong")
self.assertEqual(len(messages), 1)
self.assertTrue(messages.has_error())
self.assertFalse(messages.has_warning())
with self.assertLogs() as capture:
messages.log_messages()
self.assertEqual(len(capture.output), 1)
self.assertEqual(capture.records[0].levelno, logging.ERROR)
self.assertEqual(capture.records[0].message, "Something is seriously wrong")
class TestSanityCheck(DirectoriesMixin, TestCase):
def make_test_data(self):
@@ -23,6 +73,11 @@ class TestSanityCheck(DirectoriesMixin, TestCase):
return Document.objects.create(title="test", checksum="42995833e01aea9b3edee44bbfdd7ce1", archive_checksum="62acb0bcbfbcaa62ca6ad3668e4e404b", content="test", pk=1, filename="0000001.pdf", mime_type="application/pdf", archive_filename="0000001.pdf")
def assertSanityError(self, messageRegex):
messages = check_sanity()
self.assertTrue(messages.has_error())
self.assertRegex(messages[0]['message'], messageRegex)
def test_no_docs(self):
self.assertEqual(len(check_sanity()), 0)
@@ -33,72 +88,75 @@ class TestSanityCheck(DirectoriesMixin, TestCase):
def test_no_thumbnail(self):
doc = self.make_test_data()
os.remove(doc.thumbnail_path)
self.assertEqual(len(check_sanity()), 1)
self.assertSanityError("Thumbnail of document .* does not exist")
def test_thumbnail_no_access(self):
doc = self.make_test_data()
os.chmod(doc.thumbnail_path, 0o000)
self.assertEqual(len(check_sanity()), 1)
self.assertSanityError("Cannot read thumbnail file of document")
os.chmod(doc.thumbnail_path, 0o777)
def test_no_original(self):
doc = self.make_test_data()
os.remove(doc.source_path)
self.assertEqual(len(check_sanity()), 1)
self.assertSanityError("Original of document .* does not exist.")
def test_original_no_access(self):
doc = self.make_test_data()
os.chmod(doc.source_path, 0o000)
self.assertEqual(len(check_sanity()), 1)
self.assertSanityError("Cannot read original file of document")
os.chmod(doc.source_path, 0o777)
def test_original_checksum_mismatch(self):
doc = self.make_test_data()
doc.checksum = "WOW"
doc.save()
self.assertEqual(len(check_sanity()), 1)
self.assertSanityError("Checksum mismatch of document")
def test_no_archive(self):
doc = self.make_test_data()
os.remove(doc.archive_path)
self.assertEqual(len(check_sanity()), 1)
self.assertSanityError("Archived version of document .* does not exist.")
def test_archive_no_access(self):
doc = self.make_test_data()
os.chmod(doc.archive_path, 0o000)
self.assertEqual(len(check_sanity()), 1)
self.assertSanityError("Cannot read archive file of document")
os.chmod(doc.archive_path, 0o777)
def test_archive_checksum_mismatch(self):
doc = self.make_test_data()
doc.archive_checksum = "WOW"
doc.save()
self.assertEqual(len(check_sanity()), 1)
self.assertSanityError("Checksum mismatch of archived document")
def test_empty_content(self):
doc = self.make_test_data()
doc.content = ""
doc.save()
self.assertEqual(len(check_sanity()), 1)
messages = check_sanity()
self.assertFalse(messages.has_error())
self.assertFalse(messages.has_warning())
self.assertEqual(len(messages), 1)
self.assertRegex(messages[0]['message'], "Document .* has no content.")
def test_orphaned_file(self):
doc = self.make_test_data()
Path(self.dirs.originals_dir, "orphaned").touch()
self.assertEqual(len(check_sanity()), 1)
def test_error_tostring(self):
Document.objects.create(title="test", checksum="dgfhj", archive_checksum="dfhg", content="", pk=1, filename="0000001.pdf", archive_filename="0000001.pdf")
string = str(SanityFailedError(check_sanity()))
self.assertIsNotNone(string)
messages = check_sanity()
self.assertFalse(messages.has_error())
self.assertTrue(messages.has_warning())
self.assertEqual(len(messages), 1)
self.assertRegex(messages[0]['message'], "Orphaned file in media dir")
def test_archive_filename_no_checksum(self):
doc = self.make_test_data()
doc.archive_checksum = None
doc.save()
self.assertEqual(len(check_sanity()), 2)
self.assertSanityError("has an archive file, but its checksum is missing.")
def test_archive_checksum_no_filename(self):
doc = self.make_test_data()
doc.archive_filename = None
doc.save()
self.assertEqual(len(check_sanity()), 2)
self.assertSanityError("has an archive file checksum, but no archive filename.")

View File

@@ -2,12 +2,12 @@ import os
from unittest import mock
from django.conf import settings
from django.test import TestCase, override_settings
from django.test import TestCase
from django.utils import timezone
from documents import tasks
from documents.models import Document, Tag, Correspondent, DocumentType
from documents.sanity_checker import SanityError, SanityFailedError
from documents.sanity_checker import SanityCheckMessages, SanityCheckFailedException
from documents.tests.utils import DirectoriesMixin
@@ -52,7 +52,6 @@ class TestTasks(DirectoriesMixin, TestCase):
load_classifier.assert_called_once()
self.assertFalse(os.path.isfile(settings.MODEL_FILE))
@override_settings(CACHES={'default': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'}})
def test_train_classifier(self):
c = Correspondent.objects.create(matching_algorithm=Tag.MATCH_AUTO, name="test")
doc = Document.objects.create(correspondent=c, content="test", title="test")
@@ -75,13 +74,33 @@ class TestTasks(DirectoriesMixin, TestCase):
self.assertNotEqual(mtime2, mtime3)
@mock.patch("documents.tasks.sanity_checker.check_sanity")
def test_sanity_check(self, m):
m.return_value = []
tasks.sanity_check()
def test_sanity_check_success(self, m):
m.return_value = SanityCheckMessages()
self.assertEqual(tasks.sanity_check(), "No issues detected.")
m.assert_called_once()
m.reset_mock()
m.return_value = [SanityError("")]
self.assertRaises(SanityFailedError, tasks.sanity_check)
@mock.patch("documents.tasks.sanity_checker.check_sanity")
def test_sanity_check_error(self, m):
messages = SanityCheckMessages()
messages.error("Some error")
m.return_value = messages
self.assertRaises(SanityCheckFailedException, tasks.sanity_check)
m.assert_called_once()
@mock.patch("documents.tasks.sanity_checker.check_sanity")
def test_sanity_check_warning(self, m):
messages = SanityCheckMessages()
messages.warning("Some warning")
m.return_value = messages
self.assertEqual(tasks.sanity_check(), "Sanity check exited with warnings. See log.")
m.assert_called_once()
@mock.patch("documents.tasks.sanity_checker.check_sanity")
def test_sanity_check_info(self, m):
messages = SanityCheckMessages()
messages.info("Some info")
m.return_value = messages
self.assertEqual(tasks.sanity_check(), "Sanity check exited with infos. See log.")
m.assert_called_once()
def test_bulk_update_documents(self):

View File

@@ -32,7 +32,6 @@ from rest_framework.viewsets import (
ViewSet
)
import documents.index as index
from paperless.db import GnuPG
from paperless.views import StandardPagination
from .classifier import load_classifier
@@ -176,10 +175,12 @@ class DocumentViewSet(RetrieveModelMixin,
def update(self, request, *args, **kwargs):
response = super(DocumentViewSet, self).update(
request, *args, **kwargs)
from documents import index
index.add_or_update_document(self.get_object())
return response
def destroy(self, request, *args, **kwargs):
from documents import index
index.remove_document_from_index(self.get_object())
return super(DocumentViewSet, self).destroy(request, *args, **kwargs)
@@ -225,6 +226,12 @@ class DocumentViewSet(RetrieveModelMixin,
else:
return []
def get_filesize(self, filename):
if os.path.isfile(filename):
return os.stat(filename).st_size
else:
return None
@action(methods=['get'], detail=True)
def metadata(self, request, pk=None):
try:
@@ -234,7 +241,7 @@ class DocumentViewSet(RetrieveModelMixin,
meta = {
"original_checksum": doc.checksum,
"original_size": os.stat(doc.source_path).st_size,
"original_size": self.get_filesize(doc.source_path),
"original_mime_type": doc.mime_type,
"media_filename": doc.filename,
"has_archive_version": doc.has_archive_version,
@@ -245,7 +252,7 @@ class DocumentViewSet(RetrieveModelMixin,
}
if doc.has_archive_version:
meta['archive_size'] = os.stat(doc.archive_path).st_size,
meta['archive_size'] = self.get_filesize(doc.archive_path)
meta['archive_metadata'] = self.get_metadata(
doc.archive_path, "application/pdf")
else:
@@ -495,10 +502,6 @@ class SearchView(APIView):
permission_classes = (IsAuthenticated,)
def __init__(self, *args, **kwargs):
super(SearchView, self).__init__(*args, **kwargs)
self.ix = index.open_index()
def add_infos_to_hit(self, r):
try:
doc = Document.objects.get(id=r['id'])
@@ -519,6 +522,7 @@ class SearchView(APIView):
}
def get(self, request, format=None):
from documents import index
if 'query' in request.query_params:
query = request.query_params['query']
@@ -548,8 +552,10 @@ class SearchView(APIView):
if page < 1:
page = 1
ix = index.open_index()
try:
with index.query_page(self.ix, page, query, more_like_id, more_like_content) as (result_page, corrected_query): # NOQA: E501
with index.query_page(ix, page, query, more_like_id, more_like_content) as (result_page, corrected_query): # NOQA: E501
return Response(
{'count': len(result_page),
'page': result_page.pagenum,
@@ -564,10 +570,6 @@ class SearchAutoCompleteView(APIView):
permission_classes = (IsAuthenticated,)
def __init__(self, *args, **kwargs):
super(SearchAutoCompleteView, self).__init__(*args, **kwargs)
self.ix = index.open_index()
def get(self, request, format=None):
if 'term' in request.query_params:
term = request.query_params['term']
@@ -581,7 +583,11 @@ class SearchAutoCompleteView(APIView):
else:
limit = 10
return Response(index.autocomplete(self.ix, term, limit))
from documents import index
ix = index.open_index()
return Response(index.autocomplete(ix, term, limit))
class StatisticsView(APIView):
@@ -589,8 +595,14 @@ class StatisticsView(APIView):
permission_classes = (IsAuthenticated,)
def get(self, request, format=None):
return Response({
'documents_total': Document.objects.all().count(),
'documents_inbox': Document.objects.filter(
documents_total = Document.objects.all().count()
if Tag.objects.filter(is_inbox_tag=True).exists():
documents_inbox = Document.objects.filter(
tags__is_inbox_tag=True).distinct().count()
else:
documents_inbox = None
return Response({
'documents_total': documents_total,
'documents_inbox': documents_inbox,
})

View File

@@ -0,0 +1,650 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
# Translators:
# Štěpán Šebestian <mys.orangeorange0123@gmail.com>, 2021
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-01-28 22:02+0100\n"
"PO-Revision-Date: 2020-12-30 19:27+0000\n"
"Last-Translator: Štěpán Šebestian <mys.orangeorange0123@gmail.com>, 2021\n"
"Language-Team: Czech (https://www.transifex.com/paperless/teams/115905/cs/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: cs\n"
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
#: documents/apps.py:10
msgid "Documents"
msgstr "Dokumenty"
#: documents/models.py:33
msgid "Any word"
msgstr "Jakékoliv slovo"
#: documents/models.py:34
msgid "All words"
msgstr "Všechna slova"
#: documents/models.py:35
msgid "Exact match"
msgstr "Přesná shoda"
#: documents/models.py:36
msgid "Regular expression"
msgstr "Regulární výraz"
#: documents/models.py:37
msgid "Fuzzy word"
msgstr "Fuzzy slovo"
#: documents/models.py:38
msgid "Automatic"
msgstr "Automatický"
#: documents/models.py:42 documents/models.py:352 paperless_mail/models.py:25
#: paperless_mail/models.py:109
msgid "name"
msgstr "název"
#: documents/models.py:46
msgid "match"
msgstr "shoda"
#: documents/models.py:50
msgid "matching algorithm"
msgstr "algoritmus pro shodu"
#: documents/models.py:56
msgid "is insensitive"
msgstr "je ignorováno"
#: documents/models.py:75 documents/models.py:135
msgid "correspondent"
msgstr "korespondent"
#: documents/models.py:76
msgid "correspondents"
msgstr "korespondenti"
#: documents/models.py:98
msgid "color"
msgstr "barva"
#: documents/models.py:102
msgid "is inbox tag"
msgstr "tag přichozí"
#: documents/models.py:104
msgid ""
"Marks this tag as an inbox tag: All newly consumed documents will be tagged "
"with inbox tags."
msgstr ""
"Označí tento tag jako tag pro příchozí: Všechny nově zkonzumované dokumenty "
"budou označeny tagem pro přichozí"
#: documents/models.py:109
msgid "tag"
msgstr "tag"
#: documents/models.py:110 documents/models.py:166
msgid "tags"
msgstr "tagy"
#: documents/models.py:116 documents/models.py:148
msgid "document type"
msgstr "typ dokumentu"
#: documents/models.py:117
msgid "document types"
msgstr "typy dokumentu"
#: documents/models.py:125
msgid "Unencrypted"
msgstr "Nešifrované"
#: documents/models.py:126
msgid "Encrypted with GNU Privacy Guard"
msgstr "Šifrované pomocí GNU Privacy Guard"
#: documents/models.py:139
msgid "title"
msgstr "titulek"
#: documents/models.py:152
msgid "content"
msgstr "obsah"
#: documents/models.py:154
msgid ""
"The raw, text-only data of the document. This field is primarily used for "
"searching."
msgstr ""
"Nezpracovaná, pouze textová data dokumentu. Toto pole je používáno především"
" pro vyhledávání."
#: documents/models.py:159
msgid "mime type"
msgstr "mime typ"
#: documents/models.py:170
msgid "checksum"
msgstr "kontrolní součet"
#: documents/models.py:174
msgid "The checksum of the original document."
msgstr "Kontrolní součet původního dokumentu"
#: documents/models.py:178
msgid "archive checksum"
msgstr "kontrolní součet archivu"
#: documents/models.py:183
msgid "The checksum of the archived document."
msgstr "Kontrolní součet archivovaného dokumentu."
#: documents/models.py:187 documents/models.py:330
msgid "created"
msgstr "vytvořeno"
#: documents/models.py:191
msgid "modified"
msgstr "upraveno"
#: documents/models.py:195
msgid "storage type"
msgstr "typ úložiště"
#: documents/models.py:203
msgid "added"
msgstr "přidáno"
#: documents/models.py:207
msgid "filename"
msgstr "název souboru"
#: documents/models.py:212
msgid "Current filename in storage"
msgstr "Aktuální název souboru v úložišti"
#: documents/models.py:216
msgid "archive serial number"
msgstr "sériové číslo archivu"
#: documents/models.py:221
msgid "The position of this document in your physical document archive."
msgstr "Pozice dokumentu ve vašem archivu fyzických dokumentů"
#: documents/models.py:227
msgid "document"
msgstr "dokument"
#: documents/models.py:228
msgid "documents"
msgstr "dokumenty"
#: documents/models.py:313
msgid "debug"
msgstr "debug"
#: documents/models.py:314
msgid "information"
msgstr "informace"
#: documents/models.py:315
msgid "warning"
msgstr "varování"
#: documents/models.py:316
msgid "error"
msgstr "chyba"
#: documents/models.py:317
msgid "critical"
msgstr "kritická"
#: documents/models.py:321
msgid "group"
msgstr "skupina"
#: documents/models.py:324
msgid "message"
msgstr "zpráva"
#: documents/models.py:327
msgid "level"
msgstr "úroveň"
#: documents/models.py:334
msgid "log"
msgstr "záznam"
#: documents/models.py:335
msgid "logs"
msgstr "záznamy"
#: documents/models.py:346 documents/models.py:396
msgid "saved view"
msgstr "uložený pohled"
#: documents/models.py:347
msgid "saved views"
msgstr "uložené pohledy"
#: documents/models.py:350
msgid "user"
msgstr "uživatel"
#: documents/models.py:356
msgid "show on dashboard"
msgstr "zobrazit v dashboardu"
#: documents/models.py:359
msgid "show in sidebar"
msgstr "zobrazit v postranním menu"
#: documents/models.py:363
msgid "sort field"
msgstr "pole na řazení"
#: documents/models.py:366
msgid "sort reverse"
msgstr "třídit opačně"
#: documents/models.py:372
msgid "title contains"
msgstr "titulek obsahuje"
#: documents/models.py:373
msgid "content contains"
msgstr "obsah obsahuje"
#: documents/models.py:374
msgid "ASN is"
msgstr "ASN je"
#: documents/models.py:375
msgid "correspondent is"
msgstr "korespondent je"
#: documents/models.py:376
msgid "document type is"
msgstr "typ dokumentu je"
#: documents/models.py:377
msgid "is in inbox"
msgstr "je v příchozích"
#: documents/models.py:378
msgid "has tag"
msgstr "má tag"
#: documents/models.py:379
msgid "has any tag"
msgstr "má jakýkoliv tag"
#: documents/models.py:380
msgid "created before"
msgstr "vytvořeno před"
#: documents/models.py:381
msgid "created after"
msgstr "vytvořeno po"
#: documents/models.py:382
msgid "created year is"
msgstr "rok vytvoření je"
#: documents/models.py:383
msgid "created month is"
msgstr "měsíc vytvoření je"
#: documents/models.py:384
msgid "created day is"
msgstr "den vytvoření je"
#: documents/models.py:385
msgid "added before"
msgstr "přidáno před"
#: documents/models.py:386
msgid "added after"
msgstr "přidáno po"
#: documents/models.py:387
msgid "modified before"
msgstr "upraveno před"
#: documents/models.py:388
msgid "modified after"
msgstr "upraveno po"
#: documents/models.py:389
msgid "does not have tag"
msgstr "nemá tag"
#: documents/models.py:400
msgid "rule type"
msgstr "typ pravidla"
#: documents/models.py:404
msgid "value"
msgstr "hodnota"
#: documents/models.py:410
msgid "filter rule"
msgstr "filtrovací pravidlo"
#: documents/models.py:411
msgid "filter rules"
msgstr "filtrovací pravidla"
#: documents/serialisers.py:383
#, python-format
msgid "File type %(type)s not supported"
msgstr "Typ souboru %(type)s není podporován"
#: documents/templates/index.html:20
msgid "Paperless-ng is loading..."
msgstr "Paperless-ng se načítá..."
#: documents/templates/registration/logged_out.html:13
msgid "Paperless-ng signed out"
msgstr "Odhlášeno od Paperless-ng"
#: documents/templates/registration/logged_out.html:41
msgid "You have been successfully logged out. Bye!"
msgstr "Byli jste úspěšně odhlášeni. Nashledanou!"
#: documents/templates/registration/logged_out.html:42
msgid "Sign in again"
msgstr "Přihlašte se znovu"
#: documents/templates/registration/login.html:13
msgid "Paperless-ng sign in"
msgstr "Paperless-ng přihlášení"
#: documents/templates/registration/login.html:42
msgid "Please sign in."
msgstr "Prosím přihlaste se."
#: documents/templates/registration/login.html:45
msgid "Your username and password didn't match. Please try again."
msgstr "Vaše uživatelské jméno a heslo se neshodují. Prosím, zkuste to znovu."
#: documents/templates/registration/login.html:48
msgid "Username"
msgstr "Uživatelské jméno"
#: documents/templates/registration/login.html:49
msgid "Password"
msgstr "Heslo"
#: documents/templates/registration/login.html:54
msgid "Sign in"
msgstr "Přihlásit se"
#: paperless/settings.py:286
msgid "English"
msgstr "Angličtina"
#: paperless/settings.py:287
msgid "German"
msgstr "Němčina"
#: paperless/settings.py:288
msgid "Dutch"
msgstr "Holandština"
#: paperless/settings.py:289
msgid "French"
msgstr "Francouzština"
#: paperless/urls.py:114
msgid "Paperless-ng administration"
msgstr "Správa Paperless-ng"
#: paperless_mail/admin.py:25
msgid "Filter"
msgstr "Filtr"
#: paperless_mail/admin.py:27
msgid ""
"Paperless will only process mails that match ALL of the filters given below."
msgstr ""
"Paperless zpracuje pouze emaily které odpovídají VŠEM níže zadaným filtrům."
#: paperless_mail/admin.py:37
msgid "Actions"
msgstr "Akce"
#: paperless_mail/admin.py:39
msgid ""
"The action applied to the mail. This action is only performed when documents"
" were consumed from the mail. Mails without attachments will remain entirely"
" untouched."
msgstr ""
"Akce provedena na emailu. Tato akce je provedena jen pokud byly dokumenty "
"zkonzumovány z emailu. Emaily bez příloh zůstanou nedotčeny."
#: paperless_mail/admin.py:46
msgid "Metadata"
msgstr "Metadata"
#: paperless_mail/admin.py:48
msgid ""
"Assign metadata to documents consumed from this rule automatically. If you "
"do not assign tags, types or correspondents here, paperless will still "
"process all matching rules that you have defined."
msgstr ""
"Automaticky přiřadit metadata dokumentům zkonzumovaných z tohoto pravidla. "
"Pokud zde nepřiřadíte tagy, typy nebo korespondenty, paperless stále "
"zpracuje všechna shodující-se pravidla které jste definovali."
#: paperless_mail/apps.py:9
msgid "Paperless mail"
msgstr "Paperless pošta"
#: paperless_mail/models.py:11
msgid "mail account"
msgstr "emailový účet"
#: paperless_mail/models.py:12
msgid "mail accounts"
msgstr "emailové účty"
#: paperless_mail/models.py:19
msgid "No encryption"
msgstr "Žádné šifrování"
#: paperless_mail/models.py:20
msgid "Use SSL"
msgstr "Používat SSL"
#: paperless_mail/models.py:21
msgid "Use STARTTLS"
msgstr "Používat STARTTLS"
#: paperless_mail/models.py:29
msgid "IMAP server"
msgstr "IMAP server"
#: paperless_mail/models.py:33
msgid "IMAP port"
msgstr "IMAP port"
#: paperless_mail/models.py:36
msgid ""
"This is usually 143 for unencrypted and STARTTLS connections, and 993 for "
"SSL connections."
msgstr ""
"Toto je většinou 143 pro nešifrovaná připojení/připojení používající "
"STARTTLS a 993 pro SSL připojení."
#: paperless_mail/models.py:40
msgid "IMAP security"
msgstr "IMAP bezpečnost"
#: paperless_mail/models.py:46
msgid "username"
msgstr "uživatelské jméno"
#: paperless_mail/models.py:50
msgid "password"
msgstr "heslo"
#: paperless_mail/models.py:60
msgid "mail rule"
msgstr "mailové pravidlo"
#: paperless_mail/models.py:61
msgid "mail rules"
msgstr "mailová pravidla"
#: paperless_mail/models.py:67
msgid "Only process attachments."
msgstr "Zpracovávat jen přílohy"
#: paperless_mail/models.py:68
msgid "Process all files, including 'inline' attachments."
msgstr "Zpracovat všechny soubory, včetně vložených příloh"
#: paperless_mail/models.py:78
msgid "Mark as read, don't process read mails"
msgstr "Označit jako přečtené, nezpracovávat přečtené emaily"
#: paperless_mail/models.py:79
msgid "Flag the mail, don't process flagged mails"
msgstr "Označit email, nezpracovávat označené emaily"
#: paperless_mail/models.py:80
msgid "Move to specified folder"
msgstr "Přesunout do specifikované složky"
#: paperless_mail/models.py:81
msgid "Delete"
msgstr "Odstranit"
#: paperless_mail/models.py:88
msgid "Use subject as title"
msgstr "Použít předmět jako titulek"
#: paperless_mail/models.py:89
msgid "Use attachment filename as title"
msgstr "Použít název souboru u přílohy jako titulek"
#: paperless_mail/models.py:99
msgid "Do not assign a correspondent"
msgstr "Nepřiřazovat korespondenta"
#: paperless_mail/models.py:101
msgid "Use mail address"
msgstr "Použít emailovou adresu"
#: paperless_mail/models.py:103
msgid "Use name (or mail address if not available)"
msgstr "Použít jméno (nebo emailovou adresu pokud jméno není dostupné)"
#: paperless_mail/models.py:105
msgid "Use correspondent selected below"
msgstr "Použít korespondenta vybraného níže"
#: paperless_mail/models.py:113
msgid "order"
msgstr "pořadí"
#: paperless_mail/models.py:120
msgid "account"
msgstr "účet"
#: paperless_mail/models.py:124
msgid "folder"
msgstr "složka"
#: paperless_mail/models.py:128
msgid "filter from"
msgstr "filtrovat z"
#: paperless_mail/models.py:131
msgid "filter subject"
msgstr "název filtru"
#: paperless_mail/models.py:134
msgid "filter body"
msgstr "tělo filtru"
#: paperless_mail/models.py:138
msgid "filter attachment filename"
msgstr "název souboru u přílohy filtru"
#: paperless_mail/models.py:140
msgid ""
"Only consume documents which entirely match this filename if specified. "
"Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
msgstr ""
"Konzumovat jen dokumenty které přesně odpovídají tomuto názvu souboru pokud "
"specifikováno. Zástupné znaky jako *.pdf nebo *invoice* jsou povoleny. "
"Nezáleží na velikosti písmen."
#: paperless_mail/models.py:146
msgid "maximum age"
msgstr "maximální stáří"
#: paperless_mail/models.py:148
msgid "Specified in days."
msgstr "Specifikováno ve dnech."
#: paperless_mail/models.py:151
msgid "attachment type"
msgstr "typ přílohy"
#: paperless_mail/models.py:154
msgid ""
"Inline attachments include embedded images, so it's best to combine this "
"option with a filename filter."
msgstr ""
"Vložené přílohy zahrnují vložené obrázky, takže je nejlepší tuto možnost "
"kombinovat s filtrem na název souboru"
#: paperless_mail/models.py:159
msgid "action"
msgstr "akce"
#: paperless_mail/models.py:165
msgid "action parameter"
msgstr "parametr akce"
#: paperless_mail/models.py:167
msgid ""
"Additional parameter for the action selected above, i.e., the target folder "
"of the move to folder action."
msgstr ""
"Další parametr pro výše vybranou akci, napříkad cílová složka akce přesunutí"
" do složky."
#: paperless_mail/models.py:173
msgid "assign title from"
msgstr "nastavit titulek z"
#: paperless_mail/models.py:183
msgid "assign this tag"
msgstr "přiřadit tento tag"
#: paperless_mail/models.py:191
msgid "assign this document type"
msgstr "přiřadit tento typ dokumentu"
#: paperless_mail/models.py:195
msgid "assign correspondent from"
msgstr "přiřadit korespondenta z"
#: paperless_mail/models.py:205
msgid "assign this correspondent"
msgstr "přiřadit tohoto korespondenta"

View File

@@ -11,8 +11,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-01-28 22:02+0100\n"
"PO-Revision-Date: 2020-12-30 19:27+0000\n"
"POT-Creation-Date: 2021-02-16 14:52+0100\n"
"PO-Revision-Date: 2021-02-16 18:37+0000\n"
"Last-Translator: Jonas Winkler, 2021\n"
"Language-Team: German (https://www.transifex.com/paperless/teams/115905/de/)\n"
"MIME-Version: 1.0\n"
@@ -25,64 +25,64 @@ msgstr ""
msgid "Documents"
msgstr "Dokumente"
#: documents/models.py:33
#: documents/models.py:32
msgid "Any word"
msgstr "Irgendein Wort"
#: documents/models.py:34
#: documents/models.py:33
msgid "All words"
msgstr "Alle Wörter"
#: documents/models.py:35
#: documents/models.py:34
msgid "Exact match"
msgstr "Exakte Übereinstimmung"
#: documents/models.py:36
#: documents/models.py:35
msgid "Regular expression"
msgstr "Regulärer Ausdruck"
#: documents/models.py:37
#: documents/models.py:36
msgid "Fuzzy word"
msgstr "Ungenaues Wort"
#: documents/models.py:38
#: documents/models.py:37
msgid "Automatic"
msgstr "Automatisch"
#: documents/models.py:42 documents/models.py:352 paperless_mail/models.py:25
#: documents/models.py:41 documents/models.py:364 paperless_mail/models.py:25
#: paperless_mail/models.py:109
msgid "name"
msgstr "Name"
#: documents/models.py:46
#: documents/models.py:45
msgid "match"
msgstr "Zuweisungsmuster"
#: documents/models.py:50
#: documents/models.py:49
msgid "matching algorithm"
msgstr "Zuweisungsalgorithmus"
#: documents/models.py:56
#: documents/models.py:55
msgid "is insensitive"
msgstr "Groß-/Kleinschreibung irrelevant"
#: documents/models.py:75 documents/models.py:135
#: documents/models.py:74 documents/models.py:134
msgid "correspondent"
msgstr "Korrespondent"
#: documents/models.py:76
#: documents/models.py:75
msgid "correspondents"
msgstr "Korrespondenten"
#: documents/models.py:98
#: documents/models.py:97
msgid "color"
msgstr "Farbe"
#: documents/models.py:102
#: documents/models.py:101
msgid "is inbox tag"
msgstr "Posteingangs-Tag"
#: documents/models.py:104
#: documents/models.py:103
msgid ""
"Marks this tag as an inbox tag: All newly consumed documents will be tagged "
"with inbox tags."
@@ -90,39 +90,39 @@ msgstr ""
"Markiert das Tag als Posteingangs-Tag. Neue Dokumente werden immer mit "
"diesem Tag versehen."
#: documents/models.py:109
#: documents/models.py:108
msgid "tag"
msgstr "Tag"
#: documents/models.py:110 documents/models.py:166
#: documents/models.py:109 documents/models.py:165
msgid "tags"
msgstr "Tags"
#: documents/models.py:116 documents/models.py:148
#: documents/models.py:115 documents/models.py:147
msgid "document type"
msgstr "Dokumenttyp"
#: documents/models.py:117
#: documents/models.py:116
msgid "document types"
msgstr "Dokumenttypen"
#: documents/models.py:125
#: documents/models.py:124
msgid "Unencrypted"
msgstr "Nicht verschlüsselt"
#: documents/models.py:126
#: documents/models.py:125
msgid "Encrypted with GNU Privacy Guard"
msgstr "Verschlüsselt mit GNU Privacy Guard"
#: documents/models.py:139
#: documents/models.py:138
msgid "title"
msgstr "Titel"
#: documents/models.py:152
#: documents/models.py:151
msgid "content"
msgstr "Inhalt"
#: documents/models.py:154
#: documents/models.py:153
msgid ""
"The raw, text-only data of the document. This field is primarily used for "
"searching."
@@ -130,43 +130,43 @@ msgstr ""
"Der Inhalt des Dokuments in Textform. Dieses Feld wird primär für die Suche "
"verwendet."
#: documents/models.py:159
#: documents/models.py:158
msgid "mime type"
msgstr "MIME-Typ"
#: documents/models.py:170
#: documents/models.py:169
msgid "checksum"
msgstr "Prüfsumme"
#: documents/models.py:174
#: documents/models.py:173
msgid "The checksum of the original document."
msgstr "Die Prüfsumme des originalen Dokuments."
#: documents/models.py:178
#: documents/models.py:177
msgid "archive checksum"
msgstr "Archiv-Prüfsumme"
#: documents/models.py:183
#: documents/models.py:182
msgid "The checksum of the archived document."
msgstr "Die Prüfsumme des archivierten Dokuments."
#: documents/models.py:187 documents/models.py:330
#: documents/models.py:186 documents/models.py:342
msgid "created"
msgstr "Ausgestellt"
#: documents/models.py:191
#: documents/models.py:190
msgid "modified"
msgstr "Geändert"
#: documents/models.py:195
#: documents/models.py:194
msgid "storage type"
msgstr "Speichertyp"
#: documents/models.py:203
#: documents/models.py:202
msgid "added"
msgstr "Hinzugefügt"
#: documents/models.py:207
#: documents/models.py:206
msgid "filename"
msgstr "Dateiname"
@@ -175,178 +175,186 @@ msgid "Current filename in storage"
msgstr "Aktueller Dateiname im Datenspeicher"
#: documents/models.py:216
msgid "archive filename"
msgstr "Archiv-Dateiname"
#: documents/models.py:222
msgid "Current archive filename in storage"
msgstr "Aktueller Dateiname im Archiv"
#: documents/models.py:226
msgid "archive serial number"
msgstr "Archiv-Seriennummer"
#: documents/models.py:221
#: documents/models.py:231
msgid "The position of this document in your physical document archive."
msgstr "Die Position dieses Dokuments in Ihrem physischen Dokumentenarchiv."
#: documents/models.py:227
#: documents/models.py:237
msgid "document"
msgstr "Dokument"
#: documents/models.py:228
#: documents/models.py:238
msgid "documents"
msgstr "Dokumente"
#: documents/models.py:313
#: documents/models.py:325
msgid "debug"
msgstr "Debug"
#: documents/models.py:314
#: documents/models.py:326
msgid "information"
msgstr "Information"
#: documents/models.py:315
#: documents/models.py:327
msgid "warning"
msgstr "Warnung"
#: documents/models.py:316
#: documents/models.py:328
msgid "error"
msgstr "Fehler"
#: documents/models.py:317
#: documents/models.py:329
msgid "critical"
msgstr "Kritisch"
#: documents/models.py:321
#: documents/models.py:333
msgid "group"
msgstr "Gruppe"
#: documents/models.py:324
#: documents/models.py:336
msgid "message"
msgstr "Nachricht"
#: documents/models.py:327
#: documents/models.py:339
msgid "level"
msgstr "Level"
#: documents/models.py:334
#: documents/models.py:346
msgid "log"
msgstr "Protokoll"
#: documents/models.py:335
#: documents/models.py:347
msgid "logs"
msgstr "Protokoll"
#: documents/models.py:346 documents/models.py:396
#: documents/models.py:358 documents/models.py:408
msgid "saved view"
msgstr "Gespeicherte Ansicht"
#: documents/models.py:347
#: documents/models.py:359
msgid "saved views"
msgstr "Gespeicherte Ansichten"
#: documents/models.py:350
#: documents/models.py:362
msgid "user"
msgstr "Benutzer"
#: documents/models.py:356
#: documents/models.py:368
msgid "show on dashboard"
msgstr "Auf Startseite zeigen"
#: documents/models.py:359
#: documents/models.py:371
msgid "show in sidebar"
msgstr "In Seitenleiste zeigen"
#: documents/models.py:363
#: documents/models.py:375
msgid "sort field"
msgstr "Sortierfeld"
#: documents/models.py:366
#: documents/models.py:378
msgid "sort reverse"
msgstr "Umgekehrte Sortierung"
#: documents/models.py:372
#: documents/models.py:384
msgid "title contains"
msgstr "Titel enthält"
#: documents/models.py:373
#: documents/models.py:385
msgid "content contains"
msgstr "Inhalt enthält"
#: documents/models.py:374
#: documents/models.py:386
msgid "ASN is"
msgstr "ASN ist"
#: documents/models.py:375
#: documents/models.py:387
msgid "correspondent is"
msgstr "Korrespondent ist"
#: documents/models.py:376
#: documents/models.py:388
msgid "document type is"
msgstr "Dokumenttyp ist"
#: documents/models.py:377
#: documents/models.py:389
msgid "is in inbox"
msgstr "Ist im Posteingang"
#: documents/models.py:378
#: documents/models.py:390
msgid "has tag"
msgstr "Hat Tag"
#: documents/models.py:379
#: documents/models.py:391
msgid "has any tag"
msgstr "Hat irgendein Tag"
#: documents/models.py:380
#: documents/models.py:392
msgid "created before"
msgstr "Ausgestellt vor"
#: documents/models.py:381
#: documents/models.py:393
msgid "created after"
msgstr "Ausgestellt nach"
#: documents/models.py:382
#: documents/models.py:394
msgid "created year is"
msgstr "Ausgestellt im Jahr"
#: documents/models.py:383
#: documents/models.py:395
msgid "created month is"
msgstr "Ausgestellt im Monat"
#: documents/models.py:384
#: documents/models.py:396
msgid "created day is"
msgstr "Ausgestellt am Tag"
#: documents/models.py:385
#: documents/models.py:397
msgid "added before"
msgstr "Hinzugefügt vor"
#: documents/models.py:386
#: documents/models.py:398
msgid "added after"
msgstr "Hinzugefügt nach"
#: documents/models.py:387
#: documents/models.py:399
msgid "modified before"
msgstr "Geändert vor"
#: documents/models.py:388
#: documents/models.py:400
msgid "modified after"
msgstr "Geändert nach"
#: documents/models.py:389
#: documents/models.py:401
msgid "does not have tag"
msgstr "Hat nicht folgendes Tag"
#: documents/models.py:400
#: documents/models.py:412
msgid "rule type"
msgstr "Regeltyp"
#: documents/models.py:404
#: documents/models.py:416
msgid "value"
msgstr "Wert"
#: documents/models.py:410
#: documents/models.py:422
msgid "filter rule"
msgstr "Filterregel"
#: documents/models.py:411
#: documents/models.py:423
msgid "filter rules"
msgstr "Filterregeln"
#: documents/serialisers.py:383
#: documents/serialisers.py:370
#, python-format
msgid "File type %(type)s not supported"
msgstr "Dateityp %(type)s nicht unterstützt"
@@ -393,19 +401,23 @@ msgstr "Passwort"
msgid "Sign in"
msgstr "Anmelden"
#: paperless/settings.py:286
msgid "English"
msgstr "Englisch"
#: paperless/settings.py:291
msgid "English (US)"
msgstr "Englisch (US)"
#: paperless/settings.py:287
#: paperless/settings.py:292
msgid "English (GB)"
msgstr "Englisch (UK)"
#: paperless/settings.py:293
msgid "German"
msgstr "Deutsch"
#: paperless/settings.py:288
#: paperless/settings.py:294
msgid "Dutch"
msgstr "Niederländisch"
#: paperless/settings.py:289
#: paperless/settings.py:295
msgid "French"
msgstr "Französisch"

View File

@@ -4,16 +4,17 @@
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
# Translators:
# Ali Bates <xadium@gmail.com>, 2021
# Ali Bates, 2021
# Jonas Winkler, 2021
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-01-28 22:02+0100\n"
"PO-Revision-Date: 2020-12-30 19:27+0000\n"
"Last-Translator: Ali Bates <xadium@gmail.com>, 2021\n"
"POT-Creation-Date: 2021-02-16 14:52+0100\n"
"PO-Revision-Date: 2021-02-16 18:37+0000\n"
"Last-Translator: Jonas Winkler, 2021\n"
"Language-Team: English (United Kingdom) (https://www.transifex.com/paperless/teams/115905/en_GB/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -25,64 +26,64 @@ msgstr ""
msgid "Documents"
msgstr "Documents"
#: documents/models.py:33
#: documents/models.py:32
msgid "Any word"
msgstr "Any word"
#: documents/models.py:34
#: documents/models.py:33
msgid "All words"
msgstr "All words"
#: documents/models.py:35
#: documents/models.py:34
msgid "Exact match"
msgstr "Exact match"
#: documents/models.py:36
#: documents/models.py:35
msgid "Regular expression"
msgstr "Regular expression"
#: documents/models.py:37
#: documents/models.py:36
msgid "Fuzzy word"
msgstr "Fuzzy word"
#: documents/models.py:38
#: documents/models.py:37
msgid "Automatic"
msgstr "Automatic"
#: documents/models.py:42 documents/models.py:352 paperless_mail/models.py:25
#: documents/models.py:41 documents/models.py:364 paperless_mail/models.py:25
#: paperless_mail/models.py:109
msgid "name"
msgstr "name"
#: documents/models.py:46
#: documents/models.py:45
msgid "match"
msgstr "match"
#: documents/models.py:50
#: documents/models.py:49
msgid "matching algorithm"
msgstr "matching algorithm"
#: documents/models.py:56
#: documents/models.py:55
msgid "is insensitive"
msgstr "is insensitive"
#: documents/models.py:75 documents/models.py:135
#: documents/models.py:74 documents/models.py:134
msgid "correspondent"
msgstr "correspondent"
#: documents/models.py:76
#: documents/models.py:75
msgid "correspondents"
msgstr "correspondents"
#: documents/models.py:98
#: documents/models.py:97
msgid "color"
msgstr "color"
msgstr "colour"
#: documents/models.py:102
#: documents/models.py:101
msgid "is inbox tag"
msgstr "is inbox tag"
#: documents/models.py:104
#: documents/models.py:103
msgid ""
"Marks this tag as an inbox tag: All newly consumed documents will be tagged "
"with inbox tags."
@@ -90,39 +91,39 @@ msgstr ""
"Marks this tag as an inbox tag: All newly consumed documents will be tagged "
"with inbox tags."
#: documents/models.py:109
#: documents/models.py:108
msgid "tag"
msgstr "tag"
#: documents/models.py:110 documents/models.py:166
#: documents/models.py:109 documents/models.py:165
msgid "tags"
msgstr "tags"
#: documents/models.py:116 documents/models.py:148
#: documents/models.py:115 documents/models.py:147
msgid "document type"
msgstr "document type"
#: documents/models.py:117
#: documents/models.py:116
msgid "document types"
msgstr "document types"
#: documents/models.py:125
#: documents/models.py:124
msgid "Unencrypted"
msgstr "Unencrypted"
#: documents/models.py:126
#: documents/models.py:125
msgid "Encrypted with GNU Privacy Guard"
msgstr "Encrypted with GNU Privacy Guard"
#: documents/models.py:139
#: documents/models.py:138
msgid "title"
msgstr "title"
#: documents/models.py:152
#: documents/models.py:151
msgid "content"
msgstr "content"
#: documents/models.py:154
#: documents/models.py:153
msgid ""
"The raw, text-only data of the document. This field is primarily used for "
"searching."
@@ -130,43 +131,43 @@ msgstr ""
"The raw, text-only data of the document. This field is primarily used for "
"searching."
#: documents/models.py:159
#: documents/models.py:158
msgid "mime type"
msgstr "mime type"
#: documents/models.py:170
#: documents/models.py:169
msgid "checksum"
msgstr "checksum"
#: documents/models.py:174
#: documents/models.py:173
msgid "The checksum of the original document."
msgstr "The checksum of the original document."
#: documents/models.py:178
#: documents/models.py:177
msgid "archive checksum"
msgstr "archive checksum"
#: documents/models.py:183
#: documents/models.py:182
msgid "The checksum of the archived document."
msgstr "The checksum of the archived document."
#: documents/models.py:187 documents/models.py:330
#: documents/models.py:186 documents/models.py:342
msgid "created"
msgstr "created"
#: documents/models.py:191
#: documents/models.py:190
msgid "modified"
msgstr "modified"
#: documents/models.py:195
#: documents/models.py:194
msgid "storage type"
msgstr "storage type"
#: documents/models.py:203
#: documents/models.py:202
msgid "added"
msgstr "added"
#: documents/models.py:207
#: documents/models.py:206
msgid "filename"
msgstr "filename"
@@ -175,178 +176,186 @@ msgid "Current filename in storage"
msgstr "Current filename in storage"
#: documents/models.py:216
msgid "archive filename"
msgstr "archive filename"
#: documents/models.py:222
msgid "Current archive filename in storage"
msgstr "Current archive filename in storage"
#: documents/models.py:226
msgid "archive serial number"
msgstr "archive serial number"
#: documents/models.py:221
#: documents/models.py:231
msgid "The position of this document in your physical document archive."
msgstr "The position of this document in your physical document archive."
#: documents/models.py:227
#: documents/models.py:237
msgid "document"
msgstr "document"
#: documents/models.py:228
#: documents/models.py:238
msgid "documents"
msgstr "documents"
#: documents/models.py:313
#: documents/models.py:325
msgid "debug"
msgstr "debug"
#: documents/models.py:314
#: documents/models.py:326
msgid "information"
msgstr "information"
#: documents/models.py:315
#: documents/models.py:327
msgid "warning"
msgstr "warning"
#: documents/models.py:316
#: documents/models.py:328
msgid "error"
msgstr "error"
#: documents/models.py:317
#: documents/models.py:329
msgid "critical"
msgstr "critical"
#: documents/models.py:321
#: documents/models.py:333
msgid "group"
msgstr "group"
#: documents/models.py:324
#: documents/models.py:336
msgid "message"
msgstr "message"
#: documents/models.py:327
#: documents/models.py:339
msgid "level"
msgstr "level"
#: documents/models.py:334
#: documents/models.py:346
msgid "log"
msgstr "log"
#: documents/models.py:335
#: documents/models.py:347
msgid "logs"
msgstr "logs"
#: documents/models.py:346 documents/models.py:396
#: documents/models.py:358 documents/models.py:408
msgid "saved view"
msgstr "saved view"
#: documents/models.py:347
#: documents/models.py:359
msgid "saved views"
msgstr "saved views"
#: documents/models.py:350
#: documents/models.py:362
msgid "user"
msgstr "user"
#: documents/models.py:356
#: documents/models.py:368
msgid "show on dashboard"
msgstr "show on dashboard"
#: documents/models.py:359
#: documents/models.py:371
msgid "show in sidebar"
msgstr "show in sidebar"
#: documents/models.py:363
#: documents/models.py:375
msgid "sort field"
msgstr "sort field"
#: documents/models.py:366
#: documents/models.py:378
msgid "sort reverse"
msgstr "sort reverse"
#: documents/models.py:372
#: documents/models.py:384
msgid "title contains"
msgstr "title contains"
#: documents/models.py:373
#: documents/models.py:385
msgid "content contains"
msgstr "content contains"
#: documents/models.py:374
#: documents/models.py:386
msgid "ASN is"
msgstr "ASN is"
#: documents/models.py:375
#: documents/models.py:387
msgid "correspondent is"
msgstr "correspondent is"
#: documents/models.py:376
#: documents/models.py:388
msgid "document type is"
msgstr "document type is"
#: documents/models.py:377
#: documents/models.py:389
msgid "is in inbox"
msgstr "is in inbox"
#: documents/models.py:378
#: documents/models.py:390
msgid "has tag"
msgstr "has tag"
#: documents/models.py:379
#: documents/models.py:391
msgid "has any tag"
msgstr "has any tag"
#: documents/models.py:380
#: documents/models.py:392
msgid "created before"
msgstr "created before"
#: documents/models.py:381
#: documents/models.py:393
msgid "created after"
msgstr "created after"
#: documents/models.py:382
#: documents/models.py:394
msgid "created year is"
msgstr "created year is"
#: documents/models.py:383
#: documents/models.py:395
msgid "created month is"
msgstr "created month is"
#: documents/models.py:384
#: documents/models.py:396
msgid "created day is"
msgstr "created day is"
#: documents/models.py:385
#: documents/models.py:397
msgid "added before"
msgstr "added before"
#: documents/models.py:386
#: documents/models.py:398
msgid "added after"
msgstr "added after"
#: documents/models.py:387
#: documents/models.py:399
msgid "modified before"
msgstr "modified before"
#: documents/models.py:388
#: documents/models.py:400
msgid "modified after"
msgstr "modified after"
#: documents/models.py:389
#: documents/models.py:401
msgid "does not have tag"
msgstr "does not have tag"
#: documents/models.py:400
#: documents/models.py:412
msgid "rule type"
msgstr "rule type"
#: documents/models.py:404
#: documents/models.py:416
msgid "value"
msgstr "value"
#: documents/models.py:410
#: documents/models.py:422
msgid "filter rule"
msgstr "filter rule"
#: documents/models.py:411
#: documents/models.py:423
msgid "filter rules"
msgstr "filter rules"
#: documents/serialisers.py:383
#: documents/serialisers.py:370
#, python-format
msgid "File type %(type)s not supported"
msgstr "File type %(type)s not supported"
@@ -391,19 +400,23 @@ msgstr "Password"
msgid "Sign in"
msgstr "Sign in"
#: paperless/settings.py:286
msgid "English"
msgstr "English"
#: paperless/settings.py:291
msgid "English (US)"
msgstr "English (US)"
#: paperless/settings.py:287
#: paperless/settings.py:292
msgid "English (GB)"
msgstr "English (GB)"
#: paperless/settings.py:293
msgid "German"
msgstr "German"
#: paperless/settings.py:288
#: paperless/settings.py:294
msgid "Dutch"
msgstr "Dutch"
#: paperless/settings.py:289
#: paperless/settings.py:295
msgid "French"
msgstr "French"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-01-28 22:02+0100\n"
"POT-Creation-Date: 2021-02-16 14:52+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -21,144 +21,144 @@ msgstr ""
msgid "Documents"
msgstr ""
#: documents/models.py:33
#: documents/models.py:32
msgid "Any word"
msgstr ""
#: documents/models.py:34
#: documents/models.py:33
msgid "All words"
msgstr ""
#: documents/models.py:35
#: documents/models.py:34
msgid "Exact match"
msgstr ""
#: documents/models.py:36
#: documents/models.py:35
msgid "Regular expression"
msgstr ""
#: documents/models.py:37
#: documents/models.py:36
msgid "Fuzzy word"
msgstr ""
#: documents/models.py:38
#: documents/models.py:37
msgid "Automatic"
msgstr ""
#: documents/models.py:42 documents/models.py:352 paperless_mail/models.py:25
#: documents/models.py:41 documents/models.py:364 paperless_mail/models.py:25
#: paperless_mail/models.py:109
msgid "name"
msgstr ""
#: documents/models.py:46
#: documents/models.py:45
msgid "match"
msgstr ""
#: documents/models.py:50
#: documents/models.py:49
msgid "matching algorithm"
msgstr ""
#: documents/models.py:56
#: documents/models.py:55
msgid "is insensitive"
msgstr ""
#: documents/models.py:75 documents/models.py:135
#: documents/models.py:74 documents/models.py:134
msgid "correspondent"
msgstr ""
#: documents/models.py:76
#: documents/models.py:75
msgid "correspondents"
msgstr ""
#: documents/models.py:98
#: documents/models.py:97
msgid "color"
msgstr ""
#: documents/models.py:102
#: documents/models.py:101
msgid "is inbox tag"
msgstr ""
#: documents/models.py:104
#: documents/models.py:103
msgid ""
"Marks this tag as an inbox tag: All newly consumed documents will be tagged "
"with inbox tags."
msgstr ""
#: documents/models.py:109
#: documents/models.py:108
msgid "tag"
msgstr ""
#: documents/models.py:110 documents/models.py:166
#: documents/models.py:109 documents/models.py:165
msgid "tags"
msgstr ""
#: documents/models.py:116 documents/models.py:148
#: documents/models.py:115 documents/models.py:147
msgid "document type"
msgstr ""
#: documents/models.py:117
#: documents/models.py:116
msgid "document types"
msgstr ""
#: documents/models.py:125
#: documents/models.py:124
msgid "Unencrypted"
msgstr ""
#: documents/models.py:126
#: documents/models.py:125
msgid "Encrypted with GNU Privacy Guard"
msgstr ""
#: documents/models.py:139
#: documents/models.py:138
msgid "title"
msgstr ""
#: documents/models.py:152
#: documents/models.py:151
msgid "content"
msgstr ""
#: documents/models.py:154
#: documents/models.py:153
msgid ""
"The raw, text-only data of the document. This field is primarily used for "
"searching."
msgstr ""
#: documents/models.py:159
#: documents/models.py:158
msgid "mime type"
msgstr ""
#: documents/models.py:170
#: documents/models.py:169
msgid "checksum"
msgstr ""
#: documents/models.py:174
#: documents/models.py:173
msgid "The checksum of the original document."
msgstr ""
#: documents/models.py:178
#: documents/models.py:177
msgid "archive checksum"
msgstr ""
#: documents/models.py:183
#: documents/models.py:182
msgid "The checksum of the archived document."
msgstr ""
#: documents/models.py:187 documents/models.py:330
#: documents/models.py:186 documents/models.py:342
msgid "created"
msgstr ""
#: documents/models.py:191
#: documents/models.py:190
msgid "modified"
msgstr ""
#: documents/models.py:195
#: documents/models.py:194
msgid "storage type"
msgstr ""
#: documents/models.py:203
#: documents/models.py:202
msgid "added"
msgstr ""
#: documents/models.py:207
#: documents/models.py:206
msgid "filename"
msgstr ""
@@ -167,178 +167,186 @@ msgid "Current filename in storage"
msgstr ""
#: documents/models.py:216
msgid "archive filename"
msgstr ""
#: documents/models.py:222
msgid "Current archive filename in storage"
msgstr ""
#: documents/models.py:226
msgid "archive serial number"
msgstr ""
#: documents/models.py:221
#: documents/models.py:231
msgid "The position of this document in your physical document archive."
msgstr ""
#: documents/models.py:227
#: documents/models.py:237
msgid "document"
msgstr ""
#: documents/models.py:228
#: documents/models.py:238
msgid "documents"
msgstr ""
#: documents/models.py:313
#: documents/models.py:325
msgid "debug"
msgstr ""
#: documents/models.py:314
#: documents/models.py:326
msgid "information"
msgstr ""
#: documents/models.py:315
#: documents/models.py:327
msgid "warning"
msgstr ""
#: documents/models.py:316
#: documents/models.py:328
msgid "error"
msgstr ""
#: documents/models.py:317
#: documents/models.py:329
msgid "critical"
msgstr ""
#: documents/models.py:321
#: documents/models.py:333
msgid "group"
msgstr ""
#: documents/models.py:324
#: documents/models.py:336
msgid "message"
msgstr ""
#: documents/models.py:327
#: documents/models.py:339
msgid "level"
msgstr ""
#: documents/models.py:334
#: documents/models.py:346
msgid "log"
msgstr ""
#: documents/models.py:335
#: documents/models.py:347
msgid "logs"
msgstr ""
#: documents/models.py:346 documents/models.py:396
#: documents/models.py:358 documents/models.py:408
msgid "saved view"
msgstr ""
#: documents/models.py:347
#: documents/models.py:359
msgid "saved views"
msgstr ""
#: documents/models.py:350
#: documents/models.py:362
msgid "user"
msgstr ""
#: documents/models.py:356
#: documents/models.py:368
msgid "show on dashboard"
msgstr ""
#: documents/models.py:359
#: documents/models.py:371
msgid "show in sidebar"
msgstr ""
#: documents/models.py:363
#: documents/models.py:375
msgid "sort field"
msgstr ""
#: documents/models.py:366
#: documents/models.py:378
msgid "sort reverse"
msgstr ""
#: documents/models.py:372
#: documents/models.py:384
msgid "title contains"
msgstr ""
#: documents/models.py:373
#: documents/models.py:385
msgid "content contains"
msgstr ""
#: documents/models.py:374
#: documents/models.py:386
msgid "ASN is"
msgstr ""
#: documents/models.py:375
#: documents/models.py:387
msgid "correspondent is"
msgstr ""
#: documents/models.py:376
#: documents/models.py:388
msgid "document type is"
msgstr ""
#: documents/models.py:377
#: documents/models.py:389
msgid "is in inbox"
msgstr ""
#: documents/models.py:378
#: documents/models.py:390
msgid "has tag"
msgstr ""
#: documents/models.py:379
#: documents/models.py:391
msgid "has any tag"
msgstr ""
#: documents/models.py:380
#: documents/models.py:392
msgid "created before"
msgstr ""
#: documents/models.py:381
#: documents/models.py:393
msgid "created after"
msgstr ""
#: documents/models.py:382
#: documents/models.py:394
msgid "created year is"
msgstr ""
#: documents/models.py:383
#: documents/models.py:395
msgid "created month is"
msgstr ""
#: documents/models.py:384
#: documents/models.py:396
msgid "created day is"
msgstr ""
#: documents/models.py:385
#: documents/models.py:397
msgid "added before"
msgstr ""
#: documents/models.py:386
#: documents/models.py:398
msgid "added after"
msgstr ""
#: documents/models.py:387
#: documents/models.py:399
msgid "modified before"
msgstr ""
#: documents/models.py:388
#: documents/models.py:400
msgid "modified after"
msgstr ""
#: documents/models.py:389
#: documents/models.py:401
msgid "does not have tag"
msgstr ""
#: documents/models.py:400
#: documents/models.py:412
msgid "rule type"
msgstr ""
#: documents/models.py:404
#: documents/models.py:416
msgid "value"
msgstr ""
#: documents/models.py:410
#: documents/models.py:422
msgid "filter rule"
msgstr ""
#: documents/models.py:411
#: documents/models.py:423
msgid "filter rules"
msgstr ""
#: documents/serialisers.py:383
#: documents/serialisers.py:370
#, python-format
msgid "File type %(type)s not supported"
msgstr ""
@@ -383,19 +391,23 @@ msgstr ""
msgid "Sign in"
msgstr ""
#: paperless/settings.py:286
msgid "English"
#: paperless/settings.py:291
msgid "English (US)"
msgstr ""
#: paperless/settings.py:287
#: paperless/settings.py:292
msgid "English (GB)"
msgstr ""
#: paperless/settings.py:293
msgid "German"
msgstr ""
#: paperless/settings.py:288
#: paperless/settings.py:294
msgid "Dutch"
msgstr ""
#: paperless/settings.py:289
#: paperless/settings.py:295
msgid "French"
msgstr ""

View File

@@ -4,17 +4,17 @@
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
# Translators:
# Jonas Winkler, 2021
# Jo Vandeginste <jo.vandeginste@gmail.com>, 2021
# Ben <bzweekhorst@gmail.com>, 2021
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-01-28 22:02+0100\n"
"PO-Revision-Date: 2020-12-30 19:27+0000\n"
"Last-Translator: Ben <bzweekhorst@gmail.com>, 2021\n"
"POT-Creation-Date: 2021-02-16 14:52+0100\n"
"PO-Revision-Date: 2021-02-16 18:37+0000\n"
"Last-Translator: Jo Vandeginste <jo.vandeginste@gmail.com>, 2021\n"
"Language-Team: Dutch (Netherlands) (https://www.transifex.com/paperless/teams/115905/nl_NL/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -26,64 +26,64 @@ msgstr ""
msgid "Documents"
msgstr "Documenten"
#: documents/models.py:33
#: documents/models.py:32
msgid "Any word"
msgstr "Eender welk woord"
#: documents/models.py:34
#: documents/models.py:33
msgid "All words"
msgstr "Alle woorden"
#: documents/models.py:35
#: documents/models.py:34
msgid "Exact match"
msgstr "Exacte overeenkomst"
#: documents/models.py:36
#: documents/models.py:35
msgid "Regular expression"
msgstr "Reguliere expressie"
#: documents/models.py:37
#: documents/models.py:36
msgid "Fuzzy word"
msgstr "Gelijkaardig woord"
#: documents/models.py:38
#: documents/models.py:37
msgid "Automatic"
msgstr "Automatisch"
#: documents/models.py:42 documents/models.py:352 paperless_mail/models.py:25
#: documents/models.py:41 documents/models.py:364 paperless_mail/models.py:25
#: paperless_mail/models.py:109
msgid "name"
msgstr "naam"
#: documents/models.py:46
#: documents/models.py:45
msgid "match"
msgstr "Overeenkomst"
#: documents/models.py:50
#: documents/models.py:49
msgid "matching algorithm"
msgstr "Algoritme voor het bepalen van de overeenkomst"
#: documents/models.py:56
#: documents/models.py:55
msgid "is insensitive"
msgstr "is niet hoofdlettergevoelig"
#: documents/models.py:75 documents/models.py:135
#: documents/models.py:74 documents/models.py:134
msgid "correspondent"
msgstr "correspondent"
#: documents/models.py:76
#: documents/models.py:75
msgid "correspondents"
msgstr "correspondenten"
#: documents/models.py:98
#: documents/models.py:97
msgid "color"
msgstr "Kleur"
#: documents/models.py:102
#: documents/models.py:101
msgid "is inbox tag"
msgstr "is \"Postvak in\"-etiket"
#: documents/models.py:104
#: documents/models.py:103
msgid ""
"Marks this tag as an inbox tag: All newly consumed documents will be tagged "
"with inbox tags."
@@ -91,39 +91,39 @@ msgstr ""
"Markeer dit etiket als een \"Postvak in\"-etiket: alle nieuw verwerkte "
"documenten krijgen de \"Postvak in\"-etiketten."
#: documents/models.py:109
#: documents/models.py:108
msgid "tag"
msgstr "etiket"
#: documents/models.py:110 documents/models.py:166
#: documents/models.py:109 documents/models.py:165
msgid "tags"
msgstr "etiketten"
#: documents/models.py:116 documents/models.py:148
#: documents/models.py:115 documents/models.py:147
msgid "document type"
msgstr "documenttype"
#: documents/models.py:117
#: documents/models.py:116
msgid "document types"
msgstr "documenttypen"
#: documents/models.py:125
#: documents/models.py:124
msgid "Unencrypted"
msgstr "Niet versleuteld"
#: documents/models.py:126
#: documents/models.py:125
msgid "Encrypted with GNU Privacy Guard"
msgstr "Versleuteld met GNU Privacy Guard"
#: documents/models.py:139
#: documents/models.py:138
msgid "title"
msgstr "titel"
#: documents/models.py:152
#: documents/models.py:151
msgid "content"
msgstr "inhoud"
#: documents/models.py:154
#: documents/models.py:153
msgid ""
"The raw, text-only data of the document. This field is primarily used for "
"searching."
@@ -131,43 +131,43 @@ msgstr ""
"De onbewerkte gegevens van het document. Dit veld wordt voornamelijk "
"gebruikt om te zoeken."
#: documents/models.py:159
#: documents/models.py:158
msgid "mime type"
msgstr "mimetype"
#: documents/models.py:170
#: documents/models.py:169
msgid "checksum"
msgstr "checksum"
#: documents/models.py:174
#: documents/models.py:173
msgid "The checksum of the original document."
msgstr "Het controlecijfer van het originele document."
#: documents/models.py:178
#: documents/models.py:177
msgid "archive checksum"
msgstr "archief checksum"
#: documents/models.py:183
#: documents/models.py:182
msgid "The checksum of the archived document."
msgstr "De checksum van het gearchiveerde document."
#: documents/models.py:187 documents/models.py:330
#: documents/models.py:186 documents/models.py:342
msgid "created"
msgstr "aangemaakt"
#: documents/models.py:191
#: documents/models.py:190
msgid "modified"
msgstr "gewijzigd"
#: documents/models.py:195
#: documents/models.py:194
msgid "storage type"
msgstr "type opslag"
#: documents/models.py:203
#: documents/models.py:202
msgid "added"
msgstr "toegevoegd"
#: documents/models.py:207
#: documents/models.py:206
msgid "filename"
msgstr "bestandsnaam"
@@ -176,178 +176,186 @@ msgid "Current filename in storage"
msgstr "Huidige bestandsnaam in opslag"
#: documents/models.py:216
msgid "archive filename"
msgstr "Bestandsnaam in archief"
#: documents/models.py:222
msgid "Current archive filename in storage"
msgstr "Huidige bestandsnaam in archief"
#: documents/models.py:226
msgid "archive serial number"
msgstr "serienummer in archief"
#: documents/models.py:221
#: documents/models.py:231
msgid "The position of this document in your physical document archive."
msgstr "De positie van dit document in je fysieke documentenarchief."
#: documents/models.py:227
#: documents/models.py:237
msgid "document"
msgstr "document"
#: documents/models.py:228
#: documents/models.py:238
msgid "documents"
msgstr "documenten"
#: documents/models.py:313
#: documents/models.py:325
msgid "debug"
msgstr "debug"
#: documents/models.py:314
#: documents/models.py:326
msgid "information"
msgstr "informatie"
#: documents/models.py:315
#: documents/models.py:327
msgid "warning"
msgstr "waarschuwing"
#: documents/models.py:316
#: documents/models.py:328
msgid "error"
msgstr "fout"
#: documents/models.py:317
#: documents/models.py:329
msgid "critical"
msgstr "kritisch"
#: documents/models.py:321
#: documents/models.py:333
msgid "group"
msgstr "groep"
#: documents/models.py:324
#: documents/models.py:336
msgid "message"
msgstr "bericht"
#: documents/models.py:327
#: documents/models.py:339
msgid "level"
msgstr "niveau"
#: documents/models.py:334
#: documents/models.py:346
msgid "log"
msgstr "bericht"
#: documents/models.py:335
#: documents/models.py:347
msgid "logs"
msgstr "berichten"
#: documents/models.py:346 documents/models.py:396
#: documents/models.py:358 documents/models.py:408
msgid "saved view"
msgstr "opgeslagen view"
#: documents/models.py:347
#: documents/models.py:359
msgid "saved views"
msgstr "opgeslagen views"
#: documents/models.py:350
#: documents/models.py:362
msgid "user"
msgstr "gebruiker"
#: documents/models.py:356
#: documents/models.py:368
msgid "show on dashboard"
msgstr "weergeven op dashboard"
#: documents/models.py:359
#: documents/models.py:371
msgid "show in sidebar"
msgstr "weergeven in zijbalk"
#: documents/models.py:363
#: documents/models.py:375
msgid "sort field"
msgstr "sorteerveld"
#: documents/models.py:366
#: documents/models.py:378
msgid "sort reverse"
msgstr "omgekeerd sorteren"
#: documents/models.py:372
#: documents/models.py:384
msgid "title contains"
msgstr "titel bevat"
#: documents/models.py:373
#: documents/models.py:385
msgid "content contains"
msgstr "inhoud bevat"
#: documents/models.py:374
#: documents/models.py:386
msgid "ASN is"
msgstr "ASN is"
#: documents/models.py:375
#: documents/models.py:387
msgid "correspondent is"
msgstr "correspondent is"
#: documents/models.py:376
#: documents/models.py:388
msgid "document type is"
msgstr "documenttype is"
#: documents/models.py:377
#: documents/models.py:389
msgid "is in inbox"
msgstr "zit in \"Postvak in\""
#: documents/models.py:378
#: documents/models.py:390
msgid "has tag"
msgstr "heeft etiket"
#: documents/models.py:379
#: documents/models.py:391
msgid "has any tag"
msgstr "heeft één van de etiketten"
#: documents/models.py:380
#: documents/models.py:392
msgid "created before"
msgstr "aangemaakt voor"
#: documents/models.py:381
#: documents/models.py:393
msgid "created after"
msgstr "aangemaakt na"
#: documents/models.py:382
#: documents/models.py:394
msgid "created year is"
msgstr "aangemaakt jaar is"
#: documents/models.py:383
#: documents/models.py:395
msgid "created month is"
msgstr "aangemaakte maand is"
#: documents/models.py:384
#: documents/models.py:396
msgid "created day is"
msgstr "aangemaakte dag is"
#: documents/models.py:385
#: documents/models.py:397
msgid "added before"
msgstr "toegevoegd voor"
#: documents/models.py:386
#: documents/models.py:398
msgid "added after"
msgstr "toegevoegd na"
#: documents/models.py:387
#: documents/models.py:399
msgid "modified before"
msgstr "gewijzigd voor"
#: documents/models.py:388
#: documents/models.py:400
msgid "modified after"
msgstr "gewijzigd na"
#: documents/models.py:389
#: documents/models.py:401
msgid "does not have tag"
msgstr "heeft geen etiket"
#: documents/models.py:400
#: documents/models.py:412
msgid "rule type"
msgstr "type regel"
#: documents/models.py:404
#: documents/models.py:416
msgid "value"
msgstr "waarde"
#: documents/models.py:410
#: documents/models.py:422
msgid "filter rule"
msgstr "filterregel"
#: documents/models.py:411
#: documents/models.py:423
msgid "filter rules"
msgstr "filterregels"
#: documents/serialisers.py:383
#: documents/serialisers.py:370
#, python-format
msgid "File type %(type)s not supported"
msgstr "Bestandstype %(type)s niet ondersteund"
@@ -392,19 +400,23 @@ msgstr "Wachtwoord"
msgid "Sign in"
msgstr "Aanmelden"
#: paperless/settings.py:286
msgid "English"
msgstr "Engels"
#: paperless/settings.py:291
msgid "English (US)"
msgstr "Engels (US)"
#: paperless/settings.py:287
#: paperless/settings.py:292
msgid "English (GB)"
msgstr "Engels (Brits)"
#: paperless/settings.py:293
msgid "German"
msgstr "Duits"
#: paperless/settings.py:288
#: paperless/settings.py:294
msgid "Dutch"
msgstr "Nederlands"
#: paperless/settings.py:289
#: paperless/settings.py:295
msgid "French"
msgstr "Frans"

View File

@@ -102,10 +102,11 @@ INSTALLED_APPS = [
"django_q",
"channels",
] + env_apps
if DEBUG:
INSTALLED_APPS.append("channels")
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
@@ -169,16 +170,6 @@ CHANNEL_LAYERS = {
},
}
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": os.getenv("PAPERLESS_REDIS", "redis://localhost:6379"),
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
###############################################################################
# Security #
###############################################################################
@@ -297,7 +288,8 @@ if os.getenv("PAPERLESS_DBHOST"):
LANGUAGE_CODE = 'en-us'
LANGUAGES = [
("en-us", _("English")),
("en-us", _("English (US)")),
("en-gb", _("English (GB)")),
("de", _("German")),
("nl-nl", _("Dutch")),
("fr", _("French"))
@@ -407,8 +399,9 @@ TASK_WORKERS = int(os.getenv("PAPERLESS_TASK_WORKERS", default_task_workers()))
Q_CLUSTER = {
'name': 'paperless',
'catch_up': False,
'recycle': 1,
'workers': TASK_WORKERS,
'django_redis': 'default'
'redis': os.getenv("PAPERLESS_REDIS", "redis://localhost:6379")
}

View File

@@ -1 +1 @@
__version__ = (1, 1, 1)
__version__ = (1, 1, 4)

View File

@@ -2,12 +2,8 @@ import json
import os
import re
import ocrmypdf
import pdftotext
import pikepdf
from PIL import Image
from django.conf import settings
from ocrmypdf import InputFileError, EncryptedPdfError
from documents.parsers import DocumentParser, ParseError, \
make_thumbnail_from_pdf
@@ -22,6 +18,8 @@ class RasterisedDocumentParser(DocumentParser):
logging_name = "paperless.parsing.tesseract"
def extract_metadata(self, document_path, mime_type):
import pikepdf
namespace_pattern = re.compile(r"\{(.*)\}(.*)")
result = []
@@ -91,6 +89,9 @@ class RasterisedDocumentParser(DocumentParser):
return None
def parse(self, document_path, mime_type, file_name=None):
import ocrmypdf
from ocrmypdf import InputFileError, EncryptedPdfError
mode = settings.OCR_MODE
text_original = get_text_from_pdf(document_path)
@@ -223,6 +224,7 @@ def strip_excess_whitespace(text):
def get_text_from_pdf(pdf_file):
import pdftotext
if not os.path.isfile(pdf_file):
return None

View File

@@ -164,17 +164,12 @@ class TestParser(DirectoriesMixin, TestCase):
self.assertRaises(ParseError, f)
@mock.patch("paperless_tesseract.parsers.ocrmypdf.ocr")
def test_image_calc_a4_dpi(self, m):
def test_image_calc_a4_dpi(self):
parser = RasterisedDocumentParser(None)
parser.parse(os.path.join(self.SAMPLE_FILES, "simple-no-dpi.png"), "image/png")
dpi = parser.calculate_a4_dpi(os.path.join(self.SAMPLE_FILES, "simple-no-dpi.png"))
m.assert_called_once()
args, kwargs = m.call_args
self.assertEqual(kwargs['image_dpi'], 62)
self.assertEqual(dpi, 62)
@mock.patch("paperless_tesseract.parsers.RasterisedDocumentParser.calculate_a4_dpi")
def test_image_dpi_fail(self, m):