mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
Merge remote-tracking branch 'upstream/dev' into fix/issue-267
This commit is contained in:
commit
6a16bdf5fd
1
.gitignore
vendored
1
.gitignore
vendored
@ -85,3 +85,4 @@ scripts/nuke
|
||||
|
||||
# this is where the compiled frontend is moved to.
|
||||
/src/documents/static/frontend/
|
||||
/docs/.vscode/settings.json
|
||||
|
489
Pipfile.lock
generated
489
Pipfile.lock
generated
@ -96,50 +96,40 @@
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
|
||||
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
|
||||
],
|
||||
"markers": "python_version >= '3.1'",
|
||||
"version": "==3.0.4"
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"coloredlogs": {
|
||||
"hashes": [
|
||||
"sha256:346f58aad6afd48444c2468618623638dadab76e4e70d5e10822676f2d32226a",
|
||||
"sha256:a1fab193d2053aa6c0a97608c4342d031f1f93a3d1218432c59322441d31a505",
|
||||
"sha256:b0c2124367d4f72bd739f48e1f61491b4baf145d6bda33b606b4a53cb3f96a97"
|
||||
"sha256:5e78691e2673a8e294499e1832bb13efcfb44a86b92e18109fa18951093218ab",
|
||||
"sha256:b7f630a8297a66984b6bae0f6a1b0e0afb9f2f6838ea3bfa58f50d3d13e133d6"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==14.0"
|
||||
"version": "==15.0"
|
||||
},
|
||||
"cryptography": {
|
||||
"hashes": [
|
||||
"sha256:07ca431b788249af92764e3be9a488aa1d39a0bc3be313d826bbec690417e538",
|
||||
"sha256:13b88a0bd044b4eae1ef40e265d006e34dbcde0c2f1e15eb9896501b2d8f6c6f",
|
||||
"sha256:257dab4f368fae15f378ea9a4d2799bf3696668062de0e9fa0ebb7a738a6917d",
|
||||
"sha256:32434673d8505b42c0de4de86da8c1620651abd24afe91ae0335597683ed1b77",
|
||||
"sha256:3cd75a683b15576cfc822c7c5742b3276e50b21a06672dc3a800a2d5da4ecd1b",
|
||||
"sha256:4e7268a0ca14536fecfdf2b00297d4e407da904718658c1ff1961c713f90fd33",
|
||||
"sha256:545a8550782dda68f8cdc75a6e3bf252017aa8f75f19f5a9ca940772fc0cb56e",
|
||||
"sha256:55d0b896631412b6f0c7de56e12eb3e261ac347fbaa5d5e705291a9016e5f8cb",
|
||||
"sha256:5849d59358547bf789ee7e0d7a9036b2d29e9a4ddf1ce5e06bb45634f995c53e",
|
||||
"sha256:59f7d4cfea9ef12eb9b14b83d79b432162a0a24a91ddc15c2c9bf76a68d96f2b",
|
||||
"sha256:6dc59630ecce8c1f558277ceb212c751d6730bd12c80ea96b4ac65637c4f55e7",
|
||||
"sha256:7117319b44ed1842c617d0a452383a5a052ec6aa726dfbaffa8b94c910444297",
|
||||
"sha256:75e8e6684cf0034f6bf2a97095cb95f81537b12b36a8fedf06e73050bb171c2d",
|
||||
"sha256:7b8d9d8d3a9bd240f453342981f765346c87ade811519f98664519696f8e6ab7",
|
||||
"sha256:a035a10686532b0587d58a606004aa20ad895c60c4d029afa245802347fab57b",
|
||||
"sha256:a4e27ed0b2504195f855b52052eadcc9795c59909c9d84314c5408687f933fc7",
|
||||
"sha256:a733671100cd26d816eed39507e585c156e4498293a907029969234e5e634bc4",
|
||||
"sha256:a75f306a16d9f9afebfbedc41c8c2351d8e61e818ba6b4c40815e2b5740bb6b8",
|
||||
"sha256:bd717aa029217b8ef94a7d21632a3bb5a4e7218a4513d2521c2a2fd63011e98b",
|
||||
"sha256:d25cecbac20713a7c3bc544372d42d8eafa89799f492a43b79e1dfd650484851",
|
||||
"sha256:d26a2557d8f9122f9bf445fc7034242f4375bd4e95ecda007667540270965b13",
|
||||
"sha256:d3545829ab42a66b84a9aaabf216a4dce7f16dbc76eb69be5c302ed6b8f4a29b",
|
||||
"sha256:d3d5e10be0cf2a12214ddee45c6bd203dab435e3d83b4560c03066eda600bfe3",
|
||||
"sha256:efe15aca4f64f3a7ea0c09c87826490e50ed166ce67368a68f315ea0807a20df"
|
||||
"sha256:0003a52a123602e1acee177dc90dd201f9bb1e73f24a070db7d36c588e8f5c7d",
|
||||
"sha256:0e85aaae861d0485eb5a79d33226dd6248d2a9f133b81532c8f5aae37de10ff7",
|
||||
"sha256:594a1db4511bc4d960571536abe21b4e5c3003e8750ab8365fafce71c5d86901",
|
||||
"sha256:69e836c9e5ff4373ce6d3ab311c1a2eed274793083858d3cd4c7d12ce20d5f9c",
|
||||
"sha256:788a3c9942df5e4371c199d10383f44a105d67d401fb4304178020142f020244",
|
||||
"sha256:7e177e4bea2de937a584b13645cab32f25e3d96fc0bc4a4cf99c27dc77682be6",
|
||||
"sha256:83d9d2dfec70364a74f4e7c70ad04d3ca2e6a08b703606993407bf46b97868c5",
|
||||
"sha256:84ef7a0c10c24a7773163f917f1cb6b4444597efd505a8aed0a22e8c4780f27e",
|
||||
"sha256:982f661bffc7a24b6d4f8ebe3291f17cf3833a0941c6f4d9d55c790b9aa2cdb3",
|
||||
"sha256:9e21301f7a1e7c03dbea73e8602905a4ebba641547a462b26dd03451e5769e7c",
|
||||
"sha256:9f6b0492d111b43de5f70052e24c1f0951cb9e6022188ebcb1cc3a3d301469b0",
|
||||
"sha256:a69bd3c68b98298f490e84519b954335154917eaab52cf582fa2c5c7efc6e812",
|
||||
"sha256:b4890d5fb9b7a23e3bf8abf5a8a7da8e228f1e97dc96b30b95685df840b6914a",
|
||||
"sha256:c366df0401d1ec4e548bebe8f91d55ebcc0ec3137900d214dd7aac8427ef3030",
|
||||
"sha256:dc42f645f8f3a489c3dd416730a514e7a91a59510ddaadc09d04224c098d3302"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==3.2.1"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||
"version": "==3.3.1"
|
||||
},
|
||||
"dateparser": {
|
||||
"hashes": [
|
||||
@ -151,19 +141,19 @@
|
||||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:5c866205f15e7a7123f1eec6ab939d22d5bde1416635cab259684af66d8e48a2",
|
||||
"sha256:edb10b5c45e7e9c0fb1dc00b76ec7449aca258a39ffd613dbd078c51d19c9f03"
|
||||
"sha256:2d78425ba74c7a1a74b196058b261b9733a8570782f4e2828974777ccca7edf7",
|
||||
"sha256:efa2ab96b33b20c2182db93147a0c3cd7769d418926f9e9f140a60dca7c64ca9"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.1.4"
|
||||
"version": "==3.1.5"
|
||||
},
|
||||
"django-cors-headers": {
|
||||
"hashes": [
|
||||
"sha256:9322255c296d5f75089571f29e520c83ff9693df17aa3cf9f6a4bea7c6740169",
|
||||
"sha256:db82b2840f667d47872ae3e4a4e0a0d72fbecb42779b8aa233fa8bb965f7836a"
|
||||
"sha256:5665fc1b1aabf1b678885cf6f8f8bd7da36ef0a978375e767d491b48d3055d8f",
|
||||
"sha256:ba898dd478cd4be3a38ebc3d8729fa4d044679f8c91b2684edee41129d7e968a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.5.0"
|
||||
"version": "==3.6.0"
|
||||
},
|
||||
"django-extensions": {
|
||||
"hashes": [
|
||||
@ -230,11 +220,11 @@
|
||||
},
|
||||
"humanfriendly": {
|
||||
"hashes": [
|
||||
"sha256:175ffa628aa76da2c17369a5da5856084562cc66dfe7f82ae93ca3ef175277a6",
|
||||
"sha256:3c9ab8d28e88e6cc998e41963357736dafd555ee5bb666b50e42f6ce28dd3e3d"
|
||||
"sha256:066562956639ab21ff2676d1fda0b5987e985c534fc76700a19bd54bcb81121d",
|
||||
"sha256:d5c731705114b9ad673754f3317d9fa4c23212f36b29bdc4272a892eafc9bc72"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==9.0"
|
||||
"version": "==9.1"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
@ -247,11 +237,11 @@
|
||||
},
|
||||
"imap-tools": {
|
||||
"hashes": [
|
||||
"sha256:72bf46dc135b039a5d5b59f4e079242ac15eac02a30038e8cb2dec7b153cab65",
|
||||
"sha256:75dc1c72dd76d9e577df26a1e0ec3a809b5eebce77678851458dcd2eae127ac9"
|
||||
"sha256:7d2d25b35117a3750c3b561dd93cc2fcb24cdc457830a049796c639f4371e317",
|
||||
"sha256:80088839cd1959f20c44206cdad4463ca1e7647ff67cf5b0e31e810fb6aaa6c4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.33.0"
|
||||
"version": "==0.34.0"
|
||||
},
|
||||
"img2pdf": {
|
||||
"hashes": [
|
||||
@ -262,11 +252,11 @@
|
||||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:6112e21359ef8f344e7178aa5b72dc6e62b38b0d008e6d3cb212c5b84df72013",
|
||||
"sha256:b0c2d3b226157ae4517d9625decf63591461c66b3a808c2666d538946519d170"
|
||||
"sha256:5c5a2720817414a6c41f0a49993908068243ae02c1635a228126519b509c8aed",
|
||||
"sha256:bf792d480abbd5eda85794e4afb09dd538393f7d6e6ffef6e9f03d2014cf9450"
|
||||
],
|
||||
"markers": "python_version < '3.8'",
|
||||
"version": "==3.1.1"
|
||||
"version": "==3.3.0"
|
||||
},
|
||||
"inotify-simple": {
|
||||
"hashes": [
|
||||
@ -286,11 +276,11 @@
|
||||
},
|
||||
"joblib": {
|
||||
"hashes": [
|
||||
"sha256:698c311779f347cf6b7e6b8a39bb682277b8ee4aba8cf9507bc0cf4cd4737b72",
|
||||
"sha256:9e284edd6be6b71883a63c9b7f124738a3c16195513ad940eae7e3438de885d5"
|
||||
"sha256:75ead23f13484a2a414874779d69ade40d4fa1abe62b222a23cd50d4bc822f6f",
|
||||
"sha256:7ad866067ac1fdec27d51c8678ea760601b70e32ff1881d4dc8e1171f2b64b24"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.17.0"
|
||||
"version": "==1.0.0"
|
||||
},
|
||||
"langdetect": {
|
||||
"hashes": [
|
||||
@ -389,26 +379,19 @@
|
||||
},
|
||||
"ocrmypdf": {
|
||||
"hashes": [
|
||||
"sha256:91e7394172cedb3be801a229dbd3d308fb5ae80cbc3a77879fa7954beea407b1",
|
||||
"sha256:e550b8e884150accab7ea41f4a576b5844594cb5cbd6ed514fbf1206720343ad"
|
||||
"sha256:161c9dffb61485d30d4caea07dcb6d1b73ffa43f6e8767504a9128c510cc0c8c",
|
||||
"sha256:404e564d0eac076cc520f0742b3e711f2611ae12a7adbc05f1232a77a81d6d61"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==11.3.4"
|
||||
},
|
||||
"pathtools": {
|
||||
"hashes": [
|
||||
"sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0",
|
||||
"sha256:d77d982475e87f32b82157a43b09f0a5ef3e66c1d8f3c7eb8d2580e783cd8202"
|
||||
],
|
||||
"version": "==0.1.2"
|
||||
"version": "==11.4.4"
|
||||
},
|
||||
"pathvalidate": {
|
||||
"hashes": [
|
||||
"sha256:1697c8ea71ff4c48e7aa0eda72fe4581404be8f41e51a17363ef682dd6824d35",
|
||||
"sha256:32d30dbacb711c16bb188b12ce7e9a46b41785f50a12f64500f747480a4b6ee3"
|
||||
"sha256:378c8b319838a255c00ab37f664686b75f0aabea4444d6c5a34effbec6738285",
|
||||
"sha256:cae8ad5cd9223c5c1f4bc4e2ef0cd4c5e89acd2d698fdb7610ee108b9be654d2"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.3.0"
|
||||
"version": "==2.3.2"
|
||||
},
|
||||
"pdfminer.six": {
|
||||
"hashes": [
|
||||
@ -427,65 +410,65 @@
|
||||
},
|
||||
"pikepdf": {
|
||||
"hashes": [
|
||||
"sha256:0829bd5dacd73bb4a37e7575bae523f49603479755563c92ddb55c206700cab1",
|
||||
"sha256:0d2b631077cd6af6e4d1b396208020705842610a6f13fab489d5f9c47916baa2",
|
||||
"sha256:21c98af08fae4ac9fbcad02b613b6768a4ca300fda4cba867f4a4b6f73c2d04b",
|
||||
"sha256:2240372fed30124ddc35b0c15a613f2b687a426ea2f150091e0a0c58cca7a495",
|
||||
"sha256:2a97f5f1403e058d217d7f6861cf51fca200c5687bce0d052f5f2fa89b5bfa22",
|
||||
"sha256:3faaefca0ae80d19891acec8b0dd5e6235f59f2206d82375eb80d090285e9557",
|
||||
"sha256:48ef45b64882901c0d69af3b85d16a19bd0f3e95b43e614fefb53521d8caf36c",
|
||||
"sha256:5212fe41f2323fc7356ba67caa39737fe13080562cff37bcbb74a8094076c8d0",
|
||||
"sha256:56859c32170663c57bd0658189ce44e180533eebe813853446cd6413810be9eb",
|
||||
"sha256:5f8fd1cb3478c5534222018aca24fbbd2bc74460c899bda988ec76722c13caa9",
|
||||
"sha256:74300a32c41b3d578772f6933f23a88b19f74484185e71e5225ce2f7ea5aea78",
|
||||
"sha256:8cbc946bdd217148f4a9c029fcea62f4ae0f67d5346de4c865f4718cd0ddc37f",
|
||||
"sha256:9ceefd30076f732530cf84a1be2ecb2fa9931af932706ded760a6d37c73b96ad",
|
||||
"sha256:ad69c170fda41b07a4c6b668a3128e7a759f50d9aebcfcde0ccff1358abe0423",
|
||||
"sha256:b715fe182189fb6870fab5b0383bb2fb278c88c46eade346b0f4c1ed8818c09d",
|
||||
"sha256:bb01ecf95083ffcb9ad542dc5342ccc1059e46f1395fd966629d36d9cc766b4a",
|
||||
"sha256:bd6328547219cf48cefb4e0a1bc54442910594de1c5a5feae847d9ff3c629031",
|
||||
"sha256:edb128379bb1dea76b5bdbdacf5657a6e4754bacc2049640762725590d8ed905",
|
||||
"sha256:f8e687900557fcd4c51b4e72b9e337fdae9e2c81049d1d80b624bb2e88b5769d",
|
||||
"sha256:fe0ca120e3347c851c34a91041d574f3c588d832023906d8ae18d66d042e8a52",
|
||||
"sha256:fe8e0152672f24d8bfdecc725f97e9013f2de1b41849150959526ca3562bd3ef"
|
||||
"sha256:05fac9db7d5f5871f7b6598714386ffe56c1589e1d984859fb9e6a4ec8f0ebd0",
|
||||
"sha256:267f76dc2ca107498d9cd90df8b26d36c57faebff933ef4069dffa8d2e14a9e4",
|
||||
"sha256:28d9f436086faf03306d321465a9384aaefe7fb023a46fc177921bc899656c6b",
|
||||
"sha256:2e66e15122f18b1dfbe6f48b90ebfd72c666b16330af5c4849e9b9aa930c8983",
|
||||
"sha256:3147bd0b4f4c6ed42b8dce724aa76d041aa071ebf4b500da302e1b368eb57811",
|
||||
"sha256:385da233cb211f00a154597b437214392b25ba83b88da53124ff01856f4e0753",
|
||||
"sha256:497000a07a1549239a83b3753e38b30257a5978d0c3f1b0ddaf698c2e1722616",
|
||||
"sha256:497c2d9212ec4d08582bdb4bb75d383de9f3d91308092dd23b84fdecffc08fbc",
|
||||
"sha256:62df5bed7aefbfadf29063d1c6bb9d5132bea0f6f40a186b75e068805ba96d45",
|
||||
"sha256:80380933b1423adb25ebee33659614b9e4cd7fdfb655184d5bb8becc2ea5109a",
|
||||
"sha256:8a72fff7adff10f7459670cc7950988cb2863ccfef107460432a7f290d00a9a1",
|
||||
"sha256:a59fe04e67db87a63bc9f3722210e672c0b0577707e51dd121d1480afdec0c28",
|
||||
"sha256:ac163f12a1e07a441976261367e2dfd374e050ec81a199099b9ef01143d3b01b",
|
||||
"sha256:b63b0f6a73df3533181c310af48a5acc6acdb64deb3a36e4082264a7e98f3ca2",
|
||||
"sha256:c3bba19636181cbe9b20dd382eec2c64c1df7ae410089c63ee20aa1d5d14dfa4",
|
||||
"sha256:c8f70fb7453825bcbbe77da56132a22567d4ffbfe8ab8cb801d06fb56b624f6a",
|
||||
"sha256:dd6dd1c15f770da01c03531095b8fbd1932df225297dc13f4987ca1260c2d723",
|
||||
"sha256:e6f5dc7e2a969e73134f7fd7876a7bd2a186e6284e0ed56745d7836626abed15",
|
||||
"sha256:ef8f2935b4380b3ed797bfbb12d143cf01fe62bdec14018813fd4cb029495999",
|
||||
"sha256:f2a75b290f2740ccaad077240ec8d5f963991efd63369b2e4b5d2d046b22632e",
|
||||
"sha256:f81ea51e868f075515bc9f805710105ca759fc01c29ee3cd500186a2d17e21c2"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.2.0"
|
||||
"version": "==2.2.4"
|
||||
},
|
||||
"pillow": {
|
||||
"hashes": [
|
||||
"sha256:006de60d7580d81f4a1a7e9f0173dc90a932e3905cc4d47ea909bc946302311a",
|
||||
"sha256:0a2e8d03787ec7ad71dc18aec9367c946ef8ef50e1e78c71f743bc3a770f9fae",
|
||||
"sha256:0eeeae397e5a79dc088d8297a4c2c6f901f8fb30db47795113a4a605d0f1e5ce",
|
||||
"sha256:11c5c6e9b02c9dac08af04f093eb5a2f84857df70a7d4a6a6ad461aca803fb9e",
|
||||
"sha256:2fb113757a369a6cdb189f8df3226e995acfed0a8919a72416626af1a0a71140",
|
||||
"sha256:4b0ef2470c4979e345e4e0cc1bbac65fda11d0d7b789dbac035e4c6ce3f98adb",
|
||||
"sha256:59e903ca800c8cfd1ebe482349ec7c35687b95e98cefae213e271c8c7fffa021",
|
||||
"sha256:5a3342d34289715928c914ee7f389351eb37fa4857caa9297fc7948f2ed3e53d",
|
||||
"sha256:5abd653a23c35d980b332bc0431d39663b1709d64142e3652890df4c9b6970f6",
|
||||
"sha256:5f9403af9c790cc18411ea398a6950ee2def2a830ad0cfe6dc9122e6d528b302",
|
||||
"sha256:6b4a8fd632b4ebee28282a9fef4c341835a1aa8671e2770b6f89adc8e8c2703c",
|
||||
"sha256:6c1aca8231625115104a06e4389fcd9ec88f0c9befbabd80dc206c35561be271",
|
||||
"sha256:795e91a60f291e75de2e20e6bdd67770f793c8605b553cb6e4387ce0cb302e09",
|
||||
"sha256:7ba0ba61252ab23052e642abdb17fd08fdcfdbbf3b74c969a30c58ac1ade7cd3",
|
||||
"sha256:7c9401e68730d6c4245b8e361d3d13e1035cbc94db86b49dc7da8bec235d0015",
|
||||
"sha256:81f812d8f5e8a09b246515fac141e9d10113229bc33ea073fec11403b016bcf3",
|
||||
"sha256:895d54c0ddc78a478c80f9c438579ac15f3e27bf442c2a9aa74d41d0e4d12544",
|
||||
"sha256:8de332053707c80963b589b22f8e0229f1be1f3ca862a932c1bcd48dafb18dd8",
|
||||
"sha256:92c882b70a40c79de9f5294dc99390671e07fc0b0113d472cbea3fde15db1792",
|
||||
"sha256:95edb1ed513e68bddc2aee3de66ceaf743590bf16c023fb9977adc4be15bd3f0",
|
||||
"sha256:b63d4ff734263ae4ce6593798bcfee6dbfb00523c82753a3a03cbc05555a9cc3",
|
||||
"sha256:bd7bf289e05470b1bc74889d1466d9ad4a56d201f24397557b6f65c24a6844b8",
|
||||
"sha256:cc3ea6b23954da84dbee8025c616040d9aa5eaf34ea6895a0a762ee9d3e12e11",
|
||||
"sha256:cc9ec588c6ef3a1325fa032ec14d97b7309db493782ea8c304666fb10c3bd9a7",
|
||||
"sha256:d3d07c86d4efa1facdf32aa878bd508c0dc4f87c48125cc16b937baa4e5b5e11",
|
||||
"sha256:d8a96747df78cda35980905bf26e72960cba6d355ace4780d4bdde3b217cdf1e",
|
||||
"sha256:e38d58d9138ef972fceb7aeec4be02e3f01d383723965bfcef14d174c8ccd039",
|
||||
"sha256:eb472586374dc66b31e36e14720747595c2b265ae962987261f044e5cce644b5",
|
||||
"sha256:fbd922f702582cb0d71ef94442bfca57624352622d75e3be7a1e7e9360b07e72"
|
||||
"sha256:165c88bc9d8dba670110c689e3cc5c71dbe4bfb984ffa7cbebf1fac9554071d6",
|
||||
"sha256:22d070ca2e60c99929ef274cfced04294d2368193e935c5d6febfd8b601bf865",
|
||||
"sha256:2353834b2c49b95e1313fb34edf18fca4d57446675d05298bb694bca4b194174",
|
||||
"sha256:39725acf2d2e9c17356e6835dccebe7a697db55f25a09207e38b835d5e1bc032",
|
||||
"sha256:3de6b2ee4f78c6b3d89d184ade5d8fa68af0848f9b6b6da2b9ab7943ec46971a",
|
||||
"sha256:47c0d93ee9c8b181f353dbead6530b26980fe4f5485aa18be8f1fd3c3cbc685e",
|
||||
"sha256:5e2fe3bb2363b862671eba632537cd3a823847db4d98be95690b7e382f3d6378",
|
||||
"sha256:604815c55fd92e735f9738f65dabf4edc3e79f88541c221d292faec1904a4b17",
|
||||
"sha256:6c5275bd82711cd3dcd0af8ce0bb99113ae8911fc2952805f1d012de7d600a4c",
|
||||
"sha256:731ca5aabe9085160cf68b2dbef95fc1991015bc0a3a6ea46a371ab88f3d0913",
|
||||
"sha256:7612520e5e1a371d77e1d1ca3a3ee6227eef00d0a9cddb4ef7ecb0b7396eddf7",
|
||||
"sha256:7916cbc94f1c6b1301ac04510d0881b9e9feb20ae34094d3615a8a7c3db0dcc0",
|
||||
"sha256:81c3fa9a75d9f1afafdb916d5995633f319db09bd773cb56b8e39f1e98d90820",
|
||||
"sha256:887668e792b7edbfb1d3c9d8b5d8c859269a0f0eba4dda562adb95500f60dbba",
|
||||
"sha256:8c183b5c60544b49e0a66f924b18c526dfd37774811b627f70836fe01711abd3",
|
||||
"sha256:93a473b53cc6e0b3ce6bf51b1b95b7b1e7e6084be3a07e40f79b42e83503fbf2",
|
||||
"sha256:96d4dc103d1a0fa6d47c6c55a47de5f5dafd5ef0114fa10c85a1fd8e0216284b",
|
||||
"sha256:a3d3e086474ef12ef13d42e5f9b7bbf09d39cf6bd4940f982263d6954b13f6a9",
|
||||
"sha256:b02a0b9f332086657852b1f7cb380f6a42403a6d9c42a4c34a561aa4530d5234",
|
||||
"sha256:b09e10ec453de97f9a23a5aa5e30b334195e8d2ddd1ce76cc32e52ba63c8b31d",
|
||||
"sha256:b6f00ad5ebe846cc91763b1d0c6d30a8042e02b2316e27b05de04fa6ec831ec5",
|
||||
"sha256:bba80df38cfc17f490ec651c73bb37cd896bc2400cfba27d078c2135223c1206",
|
||||
"sha256:c3d911614b008e8a576b8e5303e3db29224b455d3d66d1b2848ba6ca83f9ece9",
|
||||
"sha256:ca20739e303254287138234485579b28cb0d524401f83d5129b5ff9d606cb0a8",
|
||||
"sha256:cb192176b477d49b0a327b2a5a4979552b7a58cd42037034316b8018ac3ebb59",
|
||||
"sha256:cdbbe7dff4a677fb555a54f9bc0450f2a21a93c5ba2b44e09e54fcb72d2bd13d",
|
||||
"sha256:d355502dce85ade85a2511b40b4c61a128902f246504f7de29bbeec1ae27933a",
|
||||
"sha256:dc577f4cfdda354db3ae37a572428a90ffdbe4e51eda7849bf442fb803f09c9b",
|
||||
"sha256:dd9eef866c70d2cbbea1ae58134eaffda0d4bfea403025f4db6859724b18ab3d"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==8.0.1"
|
||||
"version": "==8.1.0"
|
||||
},
|
||||
"pluggy": {
|
||||
"hashes": [
|
||||
@ -590,10 +573,10 @@
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268",
|
||||
"sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"
|
||||
"sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4",
|
||||
"sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"
|
||||
],
|
||||
"version": "==2020.4"
|
||||
"version": "==2020.5"
|
||||
},
|
||||
"redis": {
|
||||
"hashes": [
|
||||
@ -654,50 +637,50 @@
|
||||
},
|
||||
"reportlab": {
|
||||
"hashes": [
|
||||
"sha256:0008b5baa39d7e3a8132c4b47ecae88d6858ad386518e754e5e7b8025ee4722b",
|
||||
"sha256:0ad5a540c336941272fe161ef3a9830da3d4b3a65a195531cebd3cad5db58b2a",
|
||||
"sha256:0c965a5691686d746f558ee1c52aa9c63a01a0e13cba61ffc661573948e32f61",
|
||||
"sha256:0fd568fa5615ae99f76289c52ff230207852ee942d4934f6c893c93d2a79544e",
|
||||
"sha256:1117d905a3404c696869c7aabec9454b43ed6acbbc73f9256c6fcea23e7ae93e",
|
||||
"sha256:1ea7c388e91ad9d823655ad6a13751ff67e8a0e7cf4065cf051b4c931cdd9450",
|
||||
"sha256:26c0ee8f62652cc7fcdc47a1cb3b34775a4d625738025c1a7edb8718bda5a315",
|
||||
"sha256:368c5b3fc3d5a541cb9dcacefa563fdb445365f517e3cbf64b4326631d1cf13c",
|
||||
"sha256:451d42fdcdd7d84587d6d9c8f5d9a7d0e997305efb606705063ca1fe8bcca551",
|
||||
"sha256:47394acba4da8e56ef8e55d8eb483b868521696ba49ab0f0fcf8a1a4a5ac6e49",
|
||||
"sha256:51b16e297f7b937fc530dd151e4b38f1d305b01c9aa10657bc32a5d2901b8ad7",
|
||||
"sha256:51c0cdcf606ded0a7b4b50050400f25125ea797fbfc3c817135993b38f8b764e",
|
||||
"sha256:55c672c579618843e0fd00140fb71f1ffebc4f1c542ac385c4f4999f2f5398d9",
|
||||
"sha256:5c34a96ecfbf595caf16178a06abcd26a5f8720e01fe1285d4c97333382cfaeb",
|
||||
"sha256:61aa89a00754b18c4f2956b8bff831f1fd3affef6476dc63462d92211941605e",
|
||||
"sha256:62234d29c97279917903e4587faf240a5dea4617be250db55386ff268eb5a7c5",
|
||||
"sha256:670f2a8dcc23bf798c39b95c64bf76ee387549b962f76783670821978a226663",
|
||||
"sha256:69387f171f6c7b55109caa6d061b17a18f2f9e724a0212c07cd692aeb369dd19",
|
||||
"sha256:6c5c8871b659f7c2975382d7b61f3c182701fa9eb62cf649c3c73ba8fc5e2595",
|
||||
"sha256:80139ceb3a568f5be908094f1701fd05391b71425e8b69aaed0d30db647ca2aa",
|
||||
"sha256:80661a76d0019b5e2c315ccd3bc7093d754067d6142b36a3a0ec4f416073d23b",
|
||||
"sha256:85a2236f324ae336da7f4b183fa99bed261bcc00ac1255ee91a504e68b086d00",
|
||||
"sha256:89a3acd98bd4478d6bbc5cb32e0665ea546c98bff8b58d5e1014659daa6ef75a",
|
||||
"sha256:8a39119fcab146bde41fd1c6d148f9ee1e2cca10c6f9c2b7eb4dd710a3a2c6ac",
|
||||
"sha256:9c31c2526401da6cc92018f68483f2aac0a731cb98435445ea4b72d46b438c84",
|
||||
"sha256:9e8ae1c3b8a1697147c5c97f00d66ab1c54d88c4615b0cdd9b1a667d7baf3eb7",
|
||||
"sha256:a479c38ab2b997ce05d3bef906783ac20cf4cb224a154e80c9018c5e4d943a35",
|
||||
"sha256:a79aab8d069543d5085d58260f18705a08acd92a4501a41261913fddc2137d46",
|
||||
"sha256:b0a8314383de853599ca531dfe55eaa49bb8d6b0bb663b2f8479b7a0f3385ea2",
|
||||
"sha256:b3d9926e64bd8008007b2d9819d7b30179b069ce95431d5060f71afc36885389",
|
||||
"sha256:c2a9a77ce4f25ffb52d705be82a9f41b47f6b0da23870ebc3587709e7242da30",
|
||||
"sha256:c578dd0799f70fb577474cd383f035c6e1057e4fe837278113f9cfa6eee4b076",
|
||||
"sha256:c5abd9d0023ad20030524ab0d5fa39d77aed025519b1fa426304ab2dd0328b89",
|
||||
"sha256:ced96125525ba21311e9512adf391170b9e149f89e27e45b06ff07b70f97a0b2",
|
||||
"sha256:d692fb88d6ef5e75242b00009b54953a0425eaa8bd3a36db9db8b396785e1f57",
|
||||
"sha256:d70c2104286459658e61388af9eee838b612986bd8a36e1d21ba36152983ac15",
|
||||
"sha256:de47c65c10ac6f0d2addb28f1b1657b1c707aca014d09d01b3b728cf19e8f791",
|
||||
"sha256:e6e7592527791841db0820a72c6afae52655a05b0b6d4df184fd2bafe82ee1ee",
|
||||
"sha256:e8a7e95ee6ea5566291b59ede5b9fadce809dca43ebfbfe11e3ff3d6492c6f0e",
|
||||
"sha256:f041759138b3a95508c4281b3db3bf9bb28636d84c554272a58a5ca7c9f9bbf4",
|
||||
"sha256:f39c7fc1fa2e4a1d9747a3effd70731a9d0e9eb5738247fa089c059eff19d43e",
|
||||
"sha256:f65ac89ee0ba569f5279360eae08783f7f2e95c9810a9846c957fbd5950f4896"
|
||||
"sha256:009fa61710647cdc62eb373345248d8ebb93583a058990f7c4f9be46d90aa5b1",
|
||||
"sha256:04a08d284da86882ec3a41a7c719833362ef891b09ee8e2fbb47cee352aa684a",
|
||||
"sha256:07bff6742fba612da8d1b1f783c436338c6fdc6962828159827d5ca7d2b67935",
|
||||
"sha256:09fb11ab1500e679fc1b01199d2fed24435499856e75043a9ac0d31dd48fd881",
|
||||
"sha256:18a876449c9000c391dd3415ebc8454cd7bb9e488977b894886a2d7d018f16cd",
|
||||
"sha256:18eec161411026dde49767bee4e5e8eeb8014879554811a62581dc7433628d5b",
|
||||
"sha256:19353aead39fc115a4d6c598d6fb9fa26da7e69160a0443ebb49b02903e704e8",
|
||||
"sha256:1b85c20e89c22ae902ca973df2afdd2d64d27dc4ffd2b29ebad8c805a213756b",
|
||||
"sha256:1da3d7a35f918cee905facfa94bd00ae6091cadc06dca1b0b31b69ae02d41d1d",
|
||||
"sha256:1e484ce83dae26cb40fcbd312d45b8ba921de7856a00339d867dd4ecf145a1e7",
|
||||
"sha256:33f3cfdc492575f8af3225701301a7e62fc478358729820c9e0091aff5831378",
|
||||
"sha256:3b0026c1129147befd4e5a8cf25da8dea1096fce371e7b2412e36d7254019c06",
|
||||
"sha256:3d7713dddaa8081ed709a1fa2456a43f6a74b0f07d605da8441fd53fef334f69",
|
||||
"sha256:3e2b4d69763103b9dc9b54c0952dc3cee05cedd06e28c0987fad7f84705b12c0",
|
||||
"sha256:4ca5233a19a5ceca23546290f43addec2345789c7d65bb32f8b2668aa148351f",
|
||||
"sha256:5214a289cf01ebbd65e49bae83709671dd9edb601891cf0ae8abf85f3c0b392f",
|
||||
"sha256:52f8237654acbc78ea2fa6fb4a6a06e5b023b6da93f7889adfe2deba09473fad",
|
||||
"sha256:5ed00894e0f8281c0b7c0494b4d3067c641fd90c8e5cf933089ec4cc9a48e491",
|
||||
"sha256:6191961533d49c9d860964d42bada4d7ac3bb28502d984feb8034093f2012fa8",
|
||||
"sha256:6f3ad2b1afe99c436563cd436d8693d4a12e2c4bd45f70c7705759ff7837fe53",
|
||||
"sha256:739b743b7ca1ba4b4d64c321de6fccb49b562d0507ea06c817d9cc4faed5cd22",
|
||||
"sha256:792efba0c0c6e4ee94f6dc95f305451733ee9230a1c7d51cb8e5301a549e0dfb",
|
||||
"sha256:79d63ca40231ca3860859b39a92daa5219035ba9553da89a5e1b218550744121",
|
||||
"sha256:83b28104edd58ad65748d2d0e60e0d97e3b91b3e90b4573ea6fe60de6811972c",
|
||||
"sha256:85650446538cd2f606ca234634142a7ccd74cb6db7cfec250f76a4242e0f2431",
|
||||
"sha256:8850eba6de6eb813036eb8dce353e40d60c8af48bbce107de82770b10d3aa525",
|
||||
"sha256:9da445cb79e3f740756924c053edc952cde11a65ff5af8acfda3c0a1317136ef",
|
||||
"sha256:9fabd5fbd24f5971085ffe53150d663f158f7d3050b25c95736e29ebf676d454",
|
||||
"sha256:a0c377bc45e73c3f15f55d7de69fab270d174749d5b454ab0de502b15430ec2a",
|
||||
"sha256:a1d3f7022a920d4a5e165d264581f1862e1c1b877ceeabb96fe98cec98125ae5",
|
||||
"sha256:a315edef5c5610b0c75790142f49487e89ea34397fc247ae8aa890fe6d6dd057",
|
||||
"sha256:a755cca2dcf023130b03bb671670301a992157d5c3151d838c0b68ef89894536",
|
||||
"sha256:b1b20208ecdfffd7ca027955c4fe8972b28b30a4b3b80cf25099a08d3b20ed7c",
|
||||
"sha256:b26d6f416891cef93411d6d478a25db275766081a5fb66368248293ef459f3be",
|
||||
"sha256:b4ba4c30af7044ee987e61c88a5ffb76031ca0c53666bc85d823b7de55ddbc75",
|
||||
"sha256:b71faf3b6e4d7058e1af1b8afedaf39a962db4a219affc8177009d8244ec10d4",
|
||||
"sha256:cfa854bea525f8c913cb77e2bda724d94b965a0eb3bcfc4a645a9baa29bb86e2",
|
||||
"sha256:dd9687359e466086b9f6fe6d8069034017f8b6ca3080944fae5709767ca6814e",
|
||||
"sha256:de0c675fc2998a7eaa929c356ba49c84f53a892e9ab25e8ee7d8ebbbdcb2ac16",
|
||||
"sha256:e2b4e33fea2ce9d3a14ea39191b169e41eb2ac995274f54ac8fd27519974bce8",
|
||||
"sha256:f3d4a1a273dc141e03b72a553c11bc14dd7a27ec7654a071edcf83eb04f004bc",
|
||||
"sha256:ff547cf4c1de7e104cad1a378431ff81efcb03e90e40871ee686107da5b91442"
|
||||
],
|
||||
"version": "==3.5.56"
|
||||
"version": "==3.5.59"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
@ -803,11 +786,11 @@
|
||||
},
|
||||
"tqdm": {
|
||||
"hashes": [
|
||||
"sha256:38b658a3e4ecf9b4f6f8ff75ca16221ae3378b2e175d846b6b33ea3a20852cf5",
|
||||
"sha256:d4f413aecb61c9779888c64ddf0c62910ad56dcbe857d8922bb505d4dbff0df1"
|
||||
"sha256:556c55b081bd9aa746d34125d024b73f0e2a0e62d5927ff0e400e20ee0a03b9a",
|
||||
"sha256:b8b46036fd00176d0870307123ef06bb851096964fa7fc578d789f90ce82c3e4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.54.1"
|
||||
"version": "==4.55.1"
|
||||
},
|
||||
"typing-extensions": {
|
||||
"hashes": [
|
||||
@ -835,11 +818,26 @@
|
||||
},
|
||||
"watchdog": {
|
||||
"hashes": [
|
||||
"sha256:3caefdcc8f06a57fdc5ef2d22aa7c0bfda4f55e71a0bee74cbf3176d97536ef3",
|
||||
"sha256:e38bffc89b15bafe2a131f0e1c74924cf07dcec020c2e0a26cccd208831fcd43"
|
||||
"sha256:016b01495b9c55b5d4126ed8ae75d93ea0d99377084107c33162df52887cee18",
|
||||
"sha256:101532b8db506559e52a9b5d75a308729b3f68264d930670e6155c976d0e52a0",
|
||||
"sha256:27d9b4666938d5d40afdcdf2c751781e9ce36320788b70208d0f87f7401caf93",
|
||||
"sha256:2f1ade0d0802503fda4340374d333408831cff23da66d7e711e279ba50fe6c4a",
|
||||
"sha256:376cbc2a35c0392b0fe7ff16fbc1b303fd99d4dd9911ab5581ee9d69adc88982",
|
||||
"sha256:57f05e55aa603c3b053eed7e679f0a83873c540255b88d58c6223c7493833bac",
|
||||
"sha256:5f1f3b65142175366ba94c64d8d4c8f4015825e0beaacee1c301823266b47b9b",
|
||||
"sha256:602dbd9498592eacc42e0632c19781c3df1728ef9cbab555fab6778effc29eeb",
|
||||
"sha256:68744de2003a5ea2dfbb104f9a74192cf381334a9e2c0ed2bbe1581828d50b61",
|
||||
"sha256:85e6574395aa6c1e14e0f030d9d7f35c2340a6cf95d5671354ce876ac3ffdd4d",
|
||||
"sha256:b1d723852ce90a14abf0ec0ca9e80689d9509ee4c9ee27163118d87b564a12ac",
|
||||
"sha256:d948ad9ab9aba705f9836625b32e965b9ae607284811cd98334423f659ea537a",
|
||||
"sha256:e2a531e71be7b5cc3499ae2d1494d51b6a26684bcc7c3146f63c810c00e8a3cc",
|
||||
"sha256:e7c73edef48f4ceeebb987317a67e0080e5c9228601ff67b3c4062fa020403c7",
|
||||
"sha256:ee21aeebe6b3e51e4ba64564c94cee8dbe7438b9cb60f0bb350c4fa70d1b52c2",
|
||||
"sha256:f1d0e878fd69129d0d68b87cee5d9543f20d8018e82998efb79f7e412d42154a",
|
||||
"sha256:f84146f7864339c8addf2c2b9903271df21d18d2c721e9a77f779493234a82b5"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.10.4"
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"wcwidth": {
|
||||
"hashes": [
|
||||
@ -922,53 +920,68 @@
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
|
||||
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
|
||||
],
|
||||
"markers": "python_version >= '3.1'",
|
||||
"version": "==3.0.4"
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"coverage": {
|
||||
"hashes": [
|
||||
"sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516",
|
||||
"sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259",
|
||||
"sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9",
|
||||
"sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097",
|
||||
"sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0",
|
||||
"sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f",
|
||||
"sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7",
|
||||
"sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c",
|
||||
"sha256:3188a7dfd96f734a7498f37cde6598b1e9c084f1ca68bc1aa04e88db31168ab6",
|
||||
"sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5",
|
||||
"sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7",
|
||||
"sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729",
|
||||
"sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978",
|
||||
"sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9",
|
||||
"sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f",
|
||||
"sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9",
|
||||
"sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822",
|
||||
"sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418",
|
||||
"sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82",
|
||||
"sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f",
|
||||
"sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d",
|
||||
"sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221",
|
||||
"sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4",
|
||||
"sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21",
|
||||
"sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709",
|
||||
"sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54",
|
||||
"sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d",
|
||||
"sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270",
|
||||
"sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24",
|
||||
"sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751",
|
||||
"sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a",
|
||||
"sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237",
|
||||
"sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7",
|
||||
"sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636",
|
||||
"sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8",
|
||||
"sha256:ef221855191457fffeb909d5787d1807800ab4d0111f089e6c93ee68f577634d"
|
||||
"sha256:08b3ba72bd981531fd557f67beee376d6700fba183b167857038997ba30dd297",
|
||||
"sha256:262066798d786ad67a13c7ba869e3ce0e39609f99f6d6c80160ad602c4808e32",
|
||||
"sha256:2757fa64e11ec12220968f65d086b7a29b6583d16e9a544c889b22ba98555ef1",
|
||||
"sha256:3102bb2c206700a7d28181dbe04d66b30780cde1d1c02c5f3c165cf3d2489497",
|
||||
"sha256:3498b27d8236057def41de3585f317abae235dd3a11d33e01736ffedb2ef8606",
|
||||
"sha256:378ac77af41350a8c6b8801a66021b52da8a05fd77e578b7380e876c0ce4f528",
|
||||
"sha256:38f16b1317b8dd82df67ed5daa5f5e7c959e46579840d77a67a4ceb9cef0a50b",
|
||||
"sha256:3911c2ef96e5ddc748a3c8b4702c61986628bb719b8378bf1e4a6184bbd48fe4",
|
||||
"sha256:3a3c3f8863255f3c31db3889f8055989527173ef6192a283eb6f4db3c579d830",
|
||||
"sha256:3b14b1da110ea50c8bcbadc3b82c3933974dbeea1832e814aab93ca1163cd4c1",
|
||||
"sha256:535dc1e6e68fad5355f9984d5637c33badbdc987b0c0d303ee95a6c979c9516f",
|
||||
"sha256:6f61319e33222591f885c598e3e24f6a4be3533c1d70c19e0dc59e83a71ce27d",
|
||||
"sha256:723d22d324e7997a651478e9c5a3120a0ecbc9a7e94071f7e1954562a8806cf3",
|
||||
"sha256:76b2775dda7e78680d688daabcb485dc87cf5e3184a0b3e012e1d40e38527cc8",
|
||||
"sha256:782a5c7df9f91979a7a21792e09b34a658058896628217ae6362088b123c8500",
|
||||
"sha256:7e4d159021c2029b958b2363abec4a11db0ce8cd43abb0d9ce44284cb97217e7",
|
||||
"sha256:8dacc4073c359f40fcf73aede8428c35f84639baad7e1b46fce5ab7a8a7be4bb",
|
||||
"sha256:8f33d1156241c43755137288dea619105477961cfa7e47f48dbf96bc2c30720b",
|
||||
"sha256:8ffd4b204d7de77b5dd558cdff986a8274796a1e57813ed005b33fd97e29f059",
|
||||
"sha256:93a280c9eb736a0dcca19296f3c30c720cb41a71b1f9e617f341f0a8e791a69b",
|
||||
"sha256:9a4f66259bdd6964d8cf26142733c81fb562252db74ea367d9beb4f815478e72",
|
||||
"sha256:9a9d4ff06804920388aab69c5ea8a77525cf165356db70131616acd269e19b36",
|
||||
"sha256:a2070c5affdb3a5e751f24208c5c4f3d5f008fa04d28731416e023c93b275277",
|
||||
"sha256:a4857f7e2bc6921dbd487c5c88b84f5633de3e7d416c4dc0bb70256775551a6c",
|
||||
"sha256:a607ae05b6c96057ba86c811d9c43423f35e03874ffb03fbdcd45e0637e8b631",
|
||||
"sha256:a66ca3bdf21c653e47f726ca57f46ba7fc1f260ad99ba783acc3e58e3ebdb9ff",
|
||||
"sha256:ab110c48bc3d97b4d19af41865e14531f300b482da21783fdaacd159251890e8",
|
||||
"sha256:b239711e774c8eb910e9b1ac719f02f5ae4bf35fa0420f438cdc3a7e4e7dd6ec",
|
||||
"sha256:be0416074d7f253865bb67630cf7210cbc14eb05f4099cc0f82430135aaa7a3b",
|
||||
"sha256:c46643970dff9f5c976c6512fd35768c4a3819f01f61169d8cdac3f9290903b7",
|
||||
"sha256:c5ec71fd4a43b6d84ddb88c1df94572479d9a26ef3f150cef3dacefecf888105",
|
||||
"sha256:c6e5174f8ca585755988bc278c8bb5d02d9dc2e971591ef4a1baabdf2d99589b",
|
||||
"sha256:c89b558f8a9a5a6f2cfc923c304d49f0ce629c3bd85cb442ca258ec20366394c",
|
||||
"sha256:cc44e3545d908ecf3e5773266c487ad1877be718d9dc65fc7eb6e7d14960985b",
|
||||
"sha256:cc6f8246e74dd210d7e2b56c76ceaba1cc52b025cd75dbe96eb48791e0250e98",
|
||||
"sha256:cd556c79ad665faeae28020a0ab3bda6cd47d94bec48e36970719b0b86e4dcf4",
|
||||
"sha256:ce6f3a147b4b1a8b09aae48517ae91139b1b010c5f36423fa2b866a8b23df879",
|
||||
"sha256:ceb499d2b3d1d7b7ba23abe8bf26df5f06ba8c71127f188333dddcf356b4b63f",
|
||||
"sha256:cef06fb382557f66d81d804230c11ab292d94b840b3cb7bf4450778377b592f4",
|
||||
"sha256:e448f56cfeae7b1b3b5bcd99bb377cde7c4eb1970a525c770720a352bc4c8044",
|
||||
"sha256:e52d3d95df81c8f6b2a1685aabffadf2d2d9ad97203a40f8d61e51b70f191e4e",
|
||||
"sha256:eb33c4c858d06bd8d79713c7628d3f2b50fb1c62071e2e88cb44876be03eabe1",
|
||||
"sha256:ee2f1d1c223c3d2c24e3afbb2dd38be3f03b1a8d6a83ee3d9eb8c36a52bee899",
|
||||
"sha256:f2c6888eada180814b8583c3e793f3f343a692fc802546eed45f40a001b1169f",
|
||||
"sha256:f51dbba78d68a44e99d484ca8c8f604f17e957c1ca09c3ebc2c7e3bbd9ba0448",
|
||||
"sha256:f54de00baf200b4539a5a092a759f000b5f45fd226d6d25a76b0dff71177a714",
|
||||
"sha256:fa10fee7e32213f5c7b0d6428ea92e3a3fdd6d725590238a3f92c0de1c78b9d2",
|
||||
"sha256:fabeeb121735d47d8eab8671b6b031ce08514c86b7ad8f7d5490a7b6dcd6267d",
|
||||
"sha256:fac3c432851038b3e6afe086f777732bcf7f6ebbfd90951fa04ee53db6d0bcdd",
|
||||
"sha256:fda29412a66099af6d6de0baa6bd7c52674de177ec2ad2630ca264142d69c6c7",
|
||||
"sha256:ff1330e8bc996570221b450e2d539134baa9465f5cb98aff0e0f73f34172e0ae"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
||||
"version": "==5.3"
|
||||
"version": "==5.3.1"
|
||||
},
|
||||
"coveralls": {
|
||||
"hashes": [
|
||||
@ -1010,19 +1023,19 @@
|
||||
},
|
||||
"factory-boy": {
|
||||
"hashes": [
|
||||
"sha256:d8626622550c8ba31392f9e19fdbcef9f139cf1ad643c5923f20490a7b3e2e3d",
|
||||
"sha256:ded73e49135c24bd4d3f45bf1eb168f8d290090f5cf4566b8df3698317dc9c08"
|
||||
"sha256:1d3db4b44b8c8c54cdd8b83ae4bdb9aeb121e464400035f1f03ae0e1eade56a4",
|
||||
"sha256:401cc00ff339a022f84d64a4339503d1689e8263a4478d876e58a3295b155c5b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.1.0"
|
||||
"version": "==3.2.0"
|
||||
},
|
||||
"faker": {
|
||||
"hashes": [
|
||||
"sha256:1fcb415562ee6e2395b041e85fa6901d4708d30b84d54015226fa754ed0822c3",
|
||||
"sha256:e8beccb398ee9b8cc1a91d9295121d66512b6753b4846eb1e7370545d46b3311"
|
||||
"sha256:7b0c4bb678be21a68640007f254259c73d18f7996a3448267716423360519732",
|
||||
"sha256:7e98483fc273ec5cfe1c9efa9b99adaa2de4c6b610fbc62d3767088e4974b0ce"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==5.0.1"
|
||||
"version": "==5.3.0"
|
||||
},
|
||||
"filelock": {
|
||||
"hashes": [
|
||||
@ -1051,19 +1064,19 @@
|
||||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:6112e21359ef8f344e7178aa5b72dc6e62b38b0d008e6d3cb212c5b84df72013",
|
||||
"sha256:b0c2d3b226157ae4517d9625decf63591461c66b3a808c2666d538946519d170"
|
||||
"sha256:5c5a2720817414a6c41f0a49993908068243ae02c1635a228126519b509c8aed",
|
||||
"sha256:bf792d480abbd5eda85794e4afb09dd538393f7d6e6ffef6e9f03d2014cf9450"
|
||||
],
|
||||
"markers": "python_version < '3.8'",
|
||||
"version": "==3.1.1"
|
||||
"version": "==3.3.0"
|
||||
},
|
||||
"importlib-resources": {
|
||||
"hashes": [
|
||||
"sha256:7b51f0106c8ec564b1bef3d9c588bc694ce2b92125bbb6278f4f2f5b54ec3592",
|
||||
"sha256:a3d34a8464ce1d5d7c92b0ea4e921e696d86f2aa212e684451cb1482c8d84ed5"
|
||||
"sha256:0a948d0c8c3f9344de62997e3f73444dbba233b1eaf24352933c2d264b9e4182",
|
||||
"sha256:6b45007a479c4ec21165ae3ffbe37faf35404e2041fac6ae1da684f38530ca73"
|
||||
],
|
||||
"markers": "python_version < '3.7'",
|
||||
"version": "==3.3.0"
|
||||
"version": "==4.1.1"
|
||||
},
|
||||
"iniconfig": {
|
||||
"hashes": [
|
||||
@ -1126,11 +1139,11 @@
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:05af3bb85d320377db281cf254ab050e1a7ebcbf5410685a9a407e18a1f81236",
|
||||
"sha256:eb41423378682dadb7166144a4926e443093863024de508ca5c9737d6bc08376"
|
||||
"sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858",
|
||||
"sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==20.7"
|
||||
"version": "==20.8"
|
||||
},
|
||||
"pluggy": {
|
||||
"hashes": [
|
||||
@ -1142,11 +1155,11 @@
|
||||
},
|
||||
"py": {
|
||||
"hashes": [
|
||||
"sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2",
|
||||
"sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"
|
||||
"sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
|
||||
"sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.9.0"
|
||||
"version": "==1.10.0"
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
@ -1174,11 +1187,11 @@
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe",
|
||||
"sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e"
|
||||
"sha256:1969f797a1a0dbd8ccf0fecc80262312729afea9c17f1d70ebf85c5e76c6f7c8",
|
||||
"sha256:66e419b1899bc27346cb2c993e12c5e5e8daba9073c1fbce33b9807abc95c306"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==6.1.2"
|
||||
"version": "==6.2.1"
|
||||
},
|
||||
"pytest-cov": {
|
||||
"hashes": [
|
||||
@ -1223,11 +1236,11 @@
|
||||
},
|
||||
"pytest-xdist": {
|
||||
"hashes": [
|
||||
"sha256:7c629016b3bb006b88ac68e2b31551e7becf173c76b977768848e2bbed594d90",
|
||||
"sha256:82d938f1a24186520e2d9d3a64ef7d9ac7ecdf1a0659e095d18e596b8cbd0672"
|
||||
"sha256:1d8edbb1a45e8e1f8e44b1260583107fc23f8bc8da6d18cb331ff61d41258ecf",
|
||||
"sha256:f127e11e84ad37cc1de1088cb2990f3c354630d428af3f71282de589c5bb779b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.1.0"
|
||||
"version": "==2.2.0"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
@ -1239,10 +1252,10 @@
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268",
|
||||
"sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"
|
||||
"sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4",
|
||||
"sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"
|
||||
],
|
||||
"version": "==2020.4"
|
||||
"version": "==2020.5"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
@ -1269,11 +1282,11 @@
|
||||
},
|
||||
"sphinx": {
|
||||
"hashes": [
|
||||
"sha256:1e8d592225447104d1172be415bc2972bd1357e3e12fdc76edf2261105db4300",
|
||||
"sha256:d4e59ad4ea55efbb3c05cde3bfc83bfc14f0c95aa95c3d75346fcce186a47960"
|
||||
"sha256:77dec5ac77ca46eee54f59cf477780f4fb23327b3339ef39c8471abb829c1285",
|
||||
"sha256:b8aa4eb5502c53d3b5ca13a07abeedacd887f7770c198952fd5b9530d973e767"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.3.1"
|
||||
"version": "==3.4.2"
|
||||
},
|
||||
"sphinx-rtd-theme": {
|
||||
"hashes": [
|
||||
|
@ -11,6 +11,7 @@ RUN apt-get update \
|
||||
curl \
|
||||
file \
|
||||
fonts-liberation \
|
||||
gettext \
|
||||
ghostscript \
|
||||
gnupg \
|
||||
icc-profiles-free \
|
||||
|
@ -5,6 +5,44 @@
|
||||
Changelog
|
||||
*********
|
||||
|
||||
paperless-ng 0.9.12
|
||||
###################
|
||||
|
||||
* Paperless localization
|
||||
|
||||
* Thanks to the combined efforts of many users, Paperless is now available in English, Dutch, French and German.
|
||||
|
||||
* Thanks to `Jo Vandeginste`_, Paperless has optional support for Office documents such as .docx, .doc, .odt and more.
|
||||
|
||||
* See the :ref:`configuration<configuration-tika>` on how to enable this feature. This feature requires two additional services
|
||||
(one for parsing Office documents and metadata extraction and another for converting Office documents to PDF), and is therefore
|
||||
not enabled on default installations.
|
||||
* As with all other documents, paperless converts Office documents to PDF and stores both the original as well as the archived PDF.
|
||||
|
||||
* Dark mode
|
||||
|
||||
* Thanks to `Michael Shamoon`_, paperless now has a dark mode. Configuration is available in the settings.
|
||||
|
||||
* Other changes and additions
|
||||
|
||||
* The PDF viewer now uses a local copy of some dependencies instead of fetching them from the internet. Thanks to `slorenz`_.
|
||||
* Revamped search bar styling thanks to `Michael Shamoon`_.
|
||||
* Sorting in the document list by clicking on table headers.
|
||||
* A button was added to the document detail page that assigns a new ASN to a document.
|
||||
* Form field validation: When providing invalid input in a form (such as a duplicate ASN or no name), paperless now has visual
|
||||
indicators and clearer error messages about what's wrong.
|
||||
* Paperless disables buttons with network actions (such as save and delete) when a network action is active. This indicates that
|
||||
something is happening and prevents double clicking.
|
||||
* When using "Save & next", the title field is focussed automatically to better support keyboard editing.
|
||||
* E-Mail: Added filter rule parameters to allow inline attachments (watch out for mails with inlined images!) and attachment filename filters
|
||||
with wildcards.
|
||||
|
||||
* Fixes
|
||||
|
||||
* Paperless was unable to save views when "Not assigned" was chosen in one of the filter dropdowns.
|
||||
* Clearer error messages when pre and post consumption scripts do not exist.
|
||||
* The post consumption script is executed later in the consumption process. Before the change, an ID was passed to the script referring to
|
||||
a document that did not yet exist in the database.
|
||||
|
||||
paperless-ng 0.9.11
|
||||
###################
|
||||
@ -966,6 +1004,8 @@ bulk of the work on this big change.
|
||||
|
||||
* Initial release
|
||||
|
||||
.. _slorenz: https://github.com/sisao
|
||||
.. _Jo Vandeginste: https://github.com/jovandeginste
|
||||
.. _zjean: https://github.com/zjean
|
||||
.. _rYR79435: https://github.com/rYR79435
|
||||
.. _Michael Shamoon: https://github.com/shamoon
|
||||
|
@ -162,6 +162,12 @@ PAPERLESS_COOKIE_PREFIX=<str>
|
||||
|
||||
Defaults to ``""``, which does not alter the cookie names.
|
||||
|
||||
PAPERLESS_ENABLE_HTTP_REMOTE_USER=<bool>
|
||||
Allows authentication via HTTP_REMOTE_USER which is used by some SSO
|
||||
applications.
|
||||
|
||||
Defaults to `false` which disables this feature.
|
||||
|
||||
.. _configuration-ocr:
|
||||
|
||||
OCR settings
|
||||
@ -210,20 +216,20 @@ PAPERLESS_OCR_MODE=<mode>
|
||||
into images and puts the OCRed text on top. This works for all documents,
|
||||
however, the resulting document may be significantly larger and text
|
||||
won't appear as sharp when zoomed in.
|
||||
|
||||
|
||||
The default is ``skip``, which only performs OCR when necessary and always
|
||||
creates archived documents.
|
||||
|
||||
PAPERLESS_OCR_OUTPUT_TYPE=<type>
|
||||
Specify the the type of PDF documents that paperless should produce.
|
||||
|
||||
|
||||
* ``pdf``: Modify the PDF document as little as possible.
|
||||
* ``pdfa``: Convert PDF documents into PDF/A-2b documents, which is a
|
||||
subset of the entire PDF specification and meant for storing
|
||||
documents long term.
|
||||
* ``pdfa-1``, ``pdfa-2``, ``pdfa-3`` to specify the exact version of
|
||||
PDF/A you wish to use.
|
||||
|
||||
|
||||
If not specified, ``pdfa`` is used. Remember that paperless also keeps
|
||||
the original input file as well as the archived version.
|
||||
|
||||
@ -275,14 +281,14 @@ PAPERLESS_OCR_USER_ARG=<json>
|
||||
|
||||
.. code:: json
|
||||
|
||||
{"deskew": true, "optimize": 3, "unpaper_args": "--pre-rotate 90"}
|
||||
|
||||
{"deskew": true, "optimize": 3, "unpaper_args": "--pre-rotate 90"}
|
||||
|
||||
.. _configuration-tika:
|
||||
|
||||
Tika settings
|
||||
#############
|
||||
|
||||
Paperless can make use of `Tika <https://tika.apache.org/>`_ and
|
||||
Paperless can make use of `Tika <https://tika.apache.org/>`_ and
|
||||
`Gotenberg <https://thecodingmachine.github.io/gotenberg/>`_ for parsing and
|
||||
converting "Office" documents (such as ".doc", ".xlsx" and ".odt"). If you
|
||||
wish to use this, you must provide a Tika server and a Gotenberg server,
|
||||
@ -306,7 +312,7 @@ PAPERLESS_TIKA_GOTENBERG_ENDPOINT=<url>
|
||||
|
||||
Defaults to "http://localhost:3000".
|
||||
|
||||
|
||||
|
||||
Software tweaks
|
||||
###############
|
||||
|
||||
@ -348,11 +354,14 @@ PAPERLESS_TIME_ZONE=<timezone>
|
||||
Defaults to UTC.
|
||||
|
||||
|
||||
.. _configuration-polling:
|
||||
|
||||
PAPERLESS_CONSUMER_POLLING=<num>
|
||||
If paperless won't find documents added to your consume folder, it might
|
||||
not be able to automatically detect filesystem changes. In that case,
|
||||
specify a polling interval in seconds here, which will then cause paperless
|
||||
to periodically check your consumption directory for changes.
|
||||
to periodically check your consumption directory for changes. This will also
|
||||
disable listening for file system changes with ``inotify``.
|
||||
|
||||
Defaults to 0, which disables polling and uses filesystem notifications.
|
||||
|
||||
@ -438,6 +447,19 @@ PAPERLESS_THUMBNAIL_FONT_NAME=<filename>
|
||||
|
||||
Defaults to ``/usr/share/fonts/liberation/LiberationSerif-Regular.ttf``.
|
||||
|
||||
PAPERLESS_IGNORE_DATES=<string>
|
||||
Paperless parses a documents creation date from filename and file content.
|
||||
You may specify a comma separated list of dates that should be ignored during
|
||||
this process. This is useful for special dates (like date of birth) that appear
|
||||
in documents regularly but are very unlikely to be the documents creation date.
|
||||
|
||||
You may specify dates in a multitude of formats supported by dateparser (see
|
||||
https://dateparser.readthedocs.io/en/latest/#popular-formats) but as the dates
|
||||
need to be comma separated, the options are limited.
|
||||
Example: "2020-12-02,22.04.1999"
|
||||
|
||||
Defaults to an empty string to not ignore any dates.
|
||||
|
||||
|
||||
Binaries
|
||||
########
|
||||
|
@ -179,6 +179,14 @@ Docker Route
|
||||
|
||||
You can use any settings from the file ``paperless.conf`` in this file.
|
||||
Have a look at :ref:`configuration` to see whats available.
|
||||
|
||||
.. caution::
|
||||
|
||||
Certain file systems such as NFS network shares don't support file system
|
||||
notifications with ``inotify``. When storing the consumption directory
|
||||
on such a file system, paperless will be unable to pick up new files
|
||||
with the default configuration. You will need to use ``PAPERLESS_CONSUMER_POLLING``,
|
||||
which will disable inotify. See :ref:`here <configuration-polling>`.
|
||||
|
||||
4. Run ``docker-compose up -d``. This will create and start the necessary
|
||||
containers. This will also build the image of paperless if you grabbed the
|
||||
|
@ -34,6 +34,9 @@ directory at startup, but won't find any other files added later, check out
|
||||
the configuration file and enable filesystem polling with the setting
|
||||
``PAPERLESS_CONSUMER_POLLING``.
|
||||
|
||||
This will disable listening to filesystem changes with inotify and paperless will
|
||||
manually check the consumption directory for changes instead.
|
||||
|
||||
Operation not permitted
|
||||
#######################
|
||||
|
||||
|
@ -31,6 +31,7 @@
|
||||
#PAPERLESS_STATIC_URL=/static/
|
||||
#PAPERLESS_AUTO_LOGIN_USERNAME=
|
||||
#PAPERLESS_COOKIE_PREFIX=
|
||||
#PAPERLESS_ENABLE_HTTP_REMOTE_USER=false
|
||||
|
||||
# OCR settings
|
||||
|
||||
@ -50,11 +51,14 @@
|
||||
#PAPERLESS_TIME_ZONE=UTC
|
||||
#PAPERLESS_CONSUMER_POLLING=10
|
||||
#PAPERLESS_CONSUMER_DELETE_DUPLICATES=false
|
||||
#PAPERLESS_CONSUMER_RECURSIVE=false
|
||||
#PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=false
|
||||
#PAPERLESS_OPTIMIZE_THUMBNAILS=true
|
||||
#PAPERLESS_POST_CONSUME_SCRIPT=/path/to/an/arbitrary/script.sh
|
||||
#PAPERLESS_FILENAME_DATE_ORDER=YMD
|
||||
#PAPERLESS_FILENAME_PARSE_TRANSFORMS=[]
|
||||
#PAPERLESS_THUMBNAIL_FONT_NAME=
|
||||
#PAPERLESS_IGNORE_DATES=
|
||||
|
||||
# Tika settings
|
||||
|
||||
|
@ -57,8 +57,8 @@ pipenv lock --keep-outdated -r > "$PAPERLESS_DIST_APP/requirements.txt"
|
||||
# test if the application works.
|
||||
|
||||
cd "$PAPERLESS_ROOT/src"
|
||||
pipenv run pytest --cov
|
||||
pipenv run pycodestyle
|
||||
#pipenv run pytest --cov
|
||||
#pipenv run pycodestyle
|
||||
|
||||
# make the documentation.
|
||||
|
||||
@ -81,7 +81,7 @@ cp "$PAPERLESS_ROOT/paperless.conf.example" "$PAPERLESS_DIST_APP/paperless.conf"
|
||||
|
||||
# copy python source, templates and static files.
|
||||
cd "$PAPERLESS_ROOT"
|
||||
find src -wholename '*/templates/*' -o -wholename '*/static/*' -o -name '*.py' | cpio -pdm "$PAPERLESS_DIST_APP"
|
||||
find src -wholename '*/locale/*' -o -wholename '*/templates/*' -o -wholename '*/static/*' -o -name '*.py' | cpio -pdm "$PAPERLESS_DIST_APP"
|
||||
|
||||
# build the front end.
|
||||
|
||||
|
@ -17,7 +17,8 @@
|
||||
"sourceLocale": "en-US",
|
||||
"locales": {
|
||||
"de": "src/locale/messages.de.xlf",
|
||||
"nl-NL": "src/locale/messages.nl_NL.xlf"
|
||||
"nl-NL": "src/locale/messages.nl_NL.xlf",
|
||||
"fr": "src/locale/messages.fr.xlf"
|
||||
}
|
||||
},
|
||||
"architect": {
|
||||
|
@ -140,6 +140,13 @@
|
||||
</svg> <ng-container i18n>Logs</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="settings" routerLinkActive="active" (click)="closeMenu()">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#gear"/>
|
||||
</svg> <ng-container i18n>Settings</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="admin/">
|
||||
<svg class="sidebaricon" fill="currentColor">
|
||||
|
@ -9,7 +9,7 @@
|
||||
<p *ngIf="message">{{message}}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-dark" (click)="cancelClicked()" [disabled]="!buttonsEnabled">Cancel</button>
|
||||
<button type="button" class="btn btn-outline-dark" (click)="cancelClicked()" [disabled]="!buttonsEnabled" i18n>Cancel</button>
|
||||
<button type="button" class="btn" [class]="btnClass" (click)="confirmClicked.emit()" [disabled]="!confirmButtonEnabled || !buttonsEnabled">
|
||||
{{btnCaption}}
|
||||
<span *ngIf="!confirmButtonEnabled"> ({{seconds}})</span>
|
||||
|
@ -1,10 +1,13 @@
|
||||
import { Directive, Input, OnInit } from '@angular/core';
|
||||
import { Directive, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import { ControlValueAccessor } from '@angular/forms';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
@Directive()
|
||||
export class AbstractInputComponent<T> implements OnInit, ControlValueAccessor {
|
||||
|
||||
@ViewChild("inputField")
|
||||
inputField: ElementRef
|
||||
|
||||
constructor() { }
|
||||
|
||||
onChange = (newValue: T) => {};
|
||||
@ -24,6 +27,12 @@ export class AbstractInputComponent<T> implements OnInit, ControlValueAccessor {
|
||||
this.disabled = isDisabled;
|
||||
}
|
||||
|
||||
focus() {
|
||||
if (this.inputField && this.inputField.nativeElement) {
|
||||
this.inputField.nativeElement.focus()
|
||||
}
|
||||
}
|
||||
|
||||
@Input()
|
||||
title: string
|
||||
|
||||
|
@ -1,8 +1,14 @@
|
||||
<div class="form-group">
|
||||
<label [for]="inputId">{{title}}</label>
|
||||
<input type="number" class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)">
|
||||
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
|
||||
<div class="input-group" [class.is-invalid]="error">
|
||||
<input type="number" class="form-control" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [class.is-invalid]="error">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" type="button" id="button-addon1" (click)="nextAsn()" [disabled]="value">+1</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
{{error}}
|
||||
</div>
|
||||
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
|
||||
|
||||
</div>
|
@ -1,5 +1,7 @@
|
||||
import { Component, forwardRef } from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { FILTER_ASN_ISNULL } from 'src/app/data/filter-rule-type';
|
||||
import { DocumentService } from 'src/app/services/rest/document.service';
|
||||
import { AbstractInputComponent } from '../abstract-input';
|
||||
|
||||
@Component({
|
||||
@ -14,8 +16,24 @@ import { AbstractInputComponent } from '../abstract-input';
|
||||
})
|
||||
export class NumberComponent extends AbstractInputComponent<number> {
|
||||
|
||||
constructor() {
|
||||
constructor(private documentService: DocumentService) {
|
||||
super()
|
||||
}
|
||||
|
||||
nextAsn() {
|
||||
if (this.value) {
|
||||
return
|
||||
}
|
||||
this.documentService.listFiltered(1, 1, "archive_serial_number", true, [{rule_type: FILTER_ASN_ISNULL, value: "false"}]).subscribe(
|
||||
results => {
|
||||
if (results.count > 0) {
|
||||
this.value = results.results[0].archive_serial_number + 1
|
||||
} else {
|
||||
this.value + 1
|
||||
}
|
||||
this.onChange(this.value)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div class="form-group paperless-input-select paperless-input-tags">
|
||||
<label for="tags">Tags</label>
|
||||
<label for="tags" i18n>Tags</label>
|
||||
|
||||
<div class="input-group flex-nowrap">
|
||||
<ng-select name="tags" [items]="tags" bindLabel="name" bindValue="id" [(ngModel)]="displayValue"
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div class="form-group">
|
||||
<label [for]="inputId">{{title}}</label>
|
||||
<input type="text" class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)">
|
||||
<input #inputField type="text" class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)">
|
||||
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
|
||||
<div class="invalid-feedback">
|
||||
{{error}}
|
||||
|
@ -56,14 +56,14 @@
|
||||
<a ngbNavLink i18n>Details</a>
|
||||
<ng-template ngbNavContent>
|
||||
|
||||
<app-input-text i18n-title title="Title" formControlName="title" [error]="error?.title"></app-input-text>
|
||||
<app-input-text #inputTitle i18n-title title="Title" formControlName="title" [error]="error?.title"></app-input-text>
|
||||
<app-input-number i18n-title title="Archive serial number" [error]="error?.archive_serial_number" formControlName='archive_serial_number'></app-input-number>
|
||||
<app-input-date-time i18n-titleDate titleDate="Date created" formControlName="created"></app-input-date-time>
|
||||
<app-input-select [items]="correspondents" i18n-title title="Correspondent" formControlName="correspondent" [allowNull]="true"
|
||||
(createNew)="createCorrespondent()"></app-input-select>
|
||||
<app-input-select [items]="documentTypes" i18n-title title="Document type" formControlName="document_type" [allowNull]="true"
|
||||
(createNew)="createDocumentType()"></app-input-select>
|
||||
<app-input-tags formControlName="tags" i18n-title title="Tags"></app-input-tags>
|
||||
<app-input-tags formControlName="tags"></app-input-tags>
|
||||
|
||||
</ng-template>
|
||||
</li>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { FormControl, FormGroup } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
@ -17,6 +17,7 @@ import { CorrespondentEditDialogComponent } from '../manage/correspondent-list/c
|
||||
import { DocumentTypeEditDialogComponent } from '../manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component';
|
||||
import { PDFDocumentProxy } from 'ng2-pdf-viewer';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
import { TextComponent } from '../common/input/text/text.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-document-detail',
|
||||
@ -25,6 +26,9 @@ import { ToastService } from 'src/app/services/toast.service';
|
||||
})
|
||||
export class DocumentDetailComponent implements OnInit {
|
||||
|
||||
@ViewChild("inputTitle")
|
||||
titleInput: TextComponent
|
||||
|
||||
expandOriginalMetadata = false
|
||||
expandArchivedMetadata = false
|
||||
|
||||
@ -157,6 +161,7 @@ export class DocumentDetailComponent implements OnInit {
|
||||
if (nextDocId) {
|
||||
this.openDocumentService.closeDocument(this.document)
|
||||
this.router.navigate(['documents', nextDocId])
|
||||
this.titleInput.focus()
|
||||
}
|
||||
}, error => {
|
||||
this.networkActive = false
|
||||
|
@ -83,8 +83,10 @@
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<p i18n *ngIf="list.selected.size > 0">{list.collectionSize, plural, =1 {Selected {{list.selected.size}} of one document} other {Selected {{list.selected.size}} of {{list.collectionSize || 0}} documents}}</p>
|
||||
<p i18n *ngIf="list.selected.size == 0">{list.collectionSize, plural, =1 {One document} other {{{list.collectionSize || 0}} documents}}</p>
|
||||
<p>
|
||||
<span i18n *ngIf="list.selected.size > 0">{list.collectionSize, plural, =1 {Selected {{list.selected.size}} of one document} other {Selected {{list.selected.size}} of {{list.collectionSize || 0}} documents}}</span>
|
||||
<span i18n *ngIf="list.selected.size == 0">{list.collectionSize, plural, =1 {One document} other {{{list.collectionSize || 0}} documents}}</span> <span i18n *ngIf="isFiltered">(filtered)</span>
|
||||
</p>
|
||||
<ngb-pagination [pageSize]="list.currentPageSize" [collectionSize]="list.collectionSize" [(page)]="list.currentPage" [maxSize]="5"
|
||||
[rotate]="true" (pageChange)="list.reload()" aria-label="Default pagination"></ngb-pagination>
|
||||
</div>
|
||||
@ -97,12 +99,42 @@
|
||||
<table class="table table-sm border shadow-sm" *ngIf="displayMode == 'details'">
|
||||
<thead>
|
||||
<th></th>
|
||||
<th class="d-none d-lg-table-cell" i18n>ASN</th>
|
||||
<th class="d-none d-md-table-cell" i18n>Correspondent</th>
|
||||
<th i18n>Title</th>
|
||||
<th class="d-none d-xl-table-cell" i18n>Document type</th>
|
||||
<th i18n>Created</th>
|
||||
<th class="d-none d-xl-table-cell" i18n>Added</th>
|
||||
<th class="d-none d-lg-table-cell"
|
||||
sortable="archive_serial_number"
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>ASN</th>
|
||||
<th class="d-none d-md-table-cell"
|
||||
sortable="correspondent__name"
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Correspondent</th>
|
||||
<th
|
||||
sortable="title"
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Title</th>
|
||||
<th class="d-none d-xl-table-cell"
|
||||
sortable="document_type__name"
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Document type</th>
|
||||
<th
|
||||
sortable="created"
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Created</th>
|
||||
<th class="d-none d-xl-table-cell"
|
||||
sortable="added"
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Added</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let d of list.documents; trackBy: trackByDocumentId" [ngClass]="list.isSelected(d) ? 'table-row-selected' : ''">
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { AfterViewInit, Component, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { PaperlessDocument } from 'src/app/data/paperless-document';
|
||||
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view';
|
||||
import { SortableDirective, SortEvent } from 'src/app/directives/sortable.directive';
|
||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
|
||||
import { DOCUMENT_SORT_FIELDS } from 'src/app/services/rest/document.service';
|
||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service';
|
||||
@ -28,6 +29,8 @@ export class DocumentListComponent implements OnInit {
|
||||
@ViewChild("filterEditor")
|
||||
private filterEditor: FilterEditorComponent
|
||||
|
||||
@ViewChildren(SortableDirective) headers: QueryList<SortableDirective>;
|
||||
|
||||
displayMode = 'smallCards' // largeCards, smallCards, details
|
||||
|
||||
getTitle() {
|
||||
@ -38,6 +41,10 @@ export class DocumentListComponent implements OnInit {
|
||||
return DOCUMENT_SORT_FIELDS
|
||||
}
|
||||
|
||||
onSort(event: SortEvent) {
|
||||
this.list.setSort(event.column, event.reverse)
|
||||
}
|
||||
|
||||
get isBulkEditing(): boolean {
|
||||
return this.list.selected.size > 0
|
||||
}
|
||||
|
@ -9,10 +9,10 @@
|
||||
<table class="table table-striped border shadow">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" sortable="name" (sort)="onSort($event)" i18n>Name</th>
|
||||
<th scope="col" sortable="matching_algorithm" (sort)="onSort($event)" i18n>Matching</th>
|
||||
<th scope="col" sortable="document_count" (sort)="onSort($event)" i18n>Document count</th>
|
||||
<th scope="col" sortable="last_correspondence" (sort)="onSort($event)" i18n>Last correspondence</th>
|
||||
<th scope="col" sortable="name" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Name</th>
|
||||
<th scope="col" sortable="matching_algorithm" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Matching</th>
|
||||
<th scope="col" sortable="document_count" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Document count</th>
|
||||
<th scope="col" sortable="last_correspondence" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Last correspondence</th>
|
||||
<th scope="col" i18n>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -10,9 +10,9 @@
|
||||
<table class="table table-striped border shadow">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" sortable="name" (sort)="onSort($event)" i18n>Name</th>
|
||||
<th scope="col" sortable="matching_algorithm" (sort)="onSort($event)" i18n>Matching</th>
|
||||
<th scope="col" sortable="document_count" (sort)="onSort($event)" i18n>Document count</th>
|
||||
<th scope="col" sortable="name" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Name</th>
|
||||
<th scope="col" sortable="matching_algorithm" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Matching</th>
|
||||
<th scope="col" sortable="document_count" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Document count</th>
|
||||
<th scope="col" i18n>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -26,7 +26,7 @@ export abstract class GenericListComponent<T extends ObjectWithId> implements On
|
||||
public collectionSize = 0
|
||||
|
||||
public sortField: string
|
||||
public sortDirection: string
|
||||
public sortReverse: boolean
|
||||
|
||||
getMatching(o: MatchingModel) {
|
||||
if (o.matching_algorithm == MATCH_AUTO) {
|
||||
@ -39,21 +39,8 @@ export abstract class GenericListComponent<T extends ObjectWithId> implements On
|
||||
}
|
||||
|
||||
onSort(event: SortEvent) {
|
||||
|
||||
if (event.direction && event.direction.length > 0) {
|
||||
this.sortField = event.column
|
||||
this.sortDirection = event.direction
|
||||
} else {
|
||||
this.sortField = null
|
||||
this.sortDirection = null
|
||||
}
|
||||
|
||||
this.headers.forEach(header => {
|
||||
if (header.sortable !== this.sortField) {
|
||||
header.direction = '';
|
||||
}
|
||||
});
|
||||
|
||||
this.sortField = event.column
|
||||
this.sortReverse = event.reverse
|
||||
this.reloadData()
|
||||
}
|
||||
|
||||
@ -62,8 +49,7 @@ export abstract class GenericListComponent<T extends ObjectWithId> implements On
|
||||
}
|
||||
|
||||
reloadData() {
|
||||
// TODO: this is a hack
|
||||
this.service.list(this.page, null, this.sortField, this.sortDirection == 'des').subscribe(c => {
|
||||
this.service.list(this.page, null, this.sortField, this.sortReverse).subscribe(c => {
|
||||
this.data = c.results
|
||||
this.collectionSize = c.count
|
||||
});
|
||||
|
@ -36,7 +36,7 @@
|
||||
<app-input-check i18n-title title="Use system settings" formControlName="darkModeUseSystem" (change)="toggleDarkModeSetting()"></app-input-check>
|
||||
<div class="custom-control custom-switch" *ngIf="!settingsForm.value.darkModeUseSystem">
|
||||
<input type="checkbox" class="custom-control-input" id="darkModeEnabled" formControlName="darkModeEnabled" [checked]="settingsForm.value.darkModeEnabled">
|
||||
<label class="custom-control-label" for="darkModeEnabled">Enabled</label>
|
||||
<label class="custom-control-label" for="darkModeEnabled" i18n>Enable dark mode</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -92,5 +92,5 @@
|
||||
|
||||
<div [ngbNavOutlet]="nav" class="border-left border-right border-bottom p-3 mb-3 shadow"></div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
<button type="submit" class="btn btn-primary" i18n>Save</button>
|
||||
</form>
|
||||
|
@ -6,7 +6,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<app-input-text title="Name" formControlName="name" [error]="error?.name"></app-input-text>
|
||||
<app-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></app-input-text>
|
||||
|
||||
|
||||
<div class="form-group paperless-input-select">
|
||||
|
@ -10,10 +10,10 @@
|
||||
<table class="table table-striped border shadow-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" sortable="name" (sort)="onSort($event)" i18n>Name</th>
|
||||
<th scope="col" sortable="name" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Name</th>
|
||||
<th scope="col" i18n>Color</th>
|
||||
<th scope="col" sortable="matching_algorithm" (sort)="onSort($event)" i18n>Matching</th>
|
||||
<th scope="col" sortable="document_count" (sort)="onSort($event)" i18n>Document count</th>
|
||||
<th scope="col" sortable="matching_algorithm" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Matching</th>
|
||||
<th scope="col" sortable="document_count" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Document count</th>
|
||||
<th scope="col" i18n>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -18,6 +18,8 @@ export const FILTER_MODIFIED_AFTER = 16
|
||||
|
||||
export const FILTER_DOES_NOT_HAVE_TAG = 17
|
||||
|
||||
export const FILTER_ASN_ISNULL = 18
|
||||
|
||||
export const FILTER_RULE_TYPES: FilterRuleType[] = [
|
||||
|
||||
{id: FILTER_TITLE, name: "Title contains", filtervar: "title__icontains", datatype: "string", multi: false, default: ""},
|
||||
@ -45,6 +47,7 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [
|
||||
|
||||
{id: FILTER_MODIFIED_BEFORE, name: "Modified before", filtervar: "modified__date__lt", datatype: "date", multi: false},
|
||||
{id: FILTER_MODIFIED_AFTER, name: "Modified after", filtervar: "modified__date__gt", datatype: "date", multi: false},
|
||||
{id: FILTER_ASN_ISNULL, name: "ASN is null", filtervar: "archive_serial_number__isnull", datatype: "boolean", multi: false}
|
||||
]
|
||||
|
||||
export interface FilterRuleType {
|
||||
|
@ -1,17 +1,15 @@
|
||||
import { Directive, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
export interface SortEvent {
|
||||
column: string;
|
||||
direction: string;
|
||||
column: string
|
||||
reverse: boolean
|
||||
}
|
||||
|
||||
const rotate: {[key: string]: string} = { 'asc': 'des', 'des': '', '': 'asc' };
|
||||
|
||||
@Directive({
|
||||
selector: 'th[sortable]',
|
||||
host: {
|
||||
'[class.asc]': 'direction === "asc"',
|
||||
'[class.des]': 'direction === "des"',
|
||||
'[class.asc]': 'currentSortField == sortable && !currentSortReverse',
|
||||
'[class.des]': 'currentSortField == sortable && currentSortReverse',
|
||||
'(click)': 'rotate()'
|
||||
}
|
||||
})
|
||||
@ -19,12 +17,24 @@ export class SortableDirective {
|
||||
|
||||
constructor() { }
|
||||
|
||||
@Input() sortable: string = '';
|
||||
@Input() direction: string = '';
|
||||
@Input()
|
||||
sortable: string = '';
|
||||
|
||||
@Input()
|
||||
currentSortReverse: boolean = false
|
||||
|
||||
@Input()
|
||||
currentSortField: string
|
||||
|
||||
@Output() sort = new EventEmitter<SortEvent>();
|
||||
|
||||
rotate() {
|
||||
this.direction = rotate[this.direction];
|
||||
this.sort.emit({column: this.sortable, direction: this.direction});
|
||||
if (this.currentSortField != this.sortable) {
|
||||
this.sort.emit({column: this.sortable, reverse: false});
|
||||
} else if (this.currentSortField == this.sortable && !this.currentSortReverse) {
|
||||
this.sort.emit({column: this.currentSortField, reverse: true});
|
||||
} else {
|
||||
this.sort.emit({column: null, reverse: false});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,7 +111,8 @@ export class DocumentListViewService {
|
||||
this.isReloading = false
|
||||
},
|
||||
error => {
|
||||
if (error.error['detail'] == 'Invalid page.') {
|
||||
if (this.currentPage != 1 && error.status == 404) {
|
||||
// this happens when applying a filter: the current page might not be available anymore due to the reduced result set.
|
||||
this.currentPage = 1
|
||||
this.reload()
|
||||
}
|
||||
@ -152,6 +153,13 @@ export class DocumentListViewService {
|
||||
return this.view.sort_reverse
|
||||
}
|
||||
|
||||
setSort(field: string, reverse: boolean) {
|
||||
this.view.sort_field = field
|
||||
this.view.sort_reverse = reverse
|
||||
this.saveDocumentListView()
|
||||
this.reload()
|
||||
}
|
||||
|
||||
private saveDocumentListView() {
|
||||
sessionStorage.setItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG, JSON.stringify(this.documentListView))
|
||||
}
|
||||
@ -259,7 +267,7 @@ export class DocumentListViewService {
|
||||
this.documentListView = null
|
||||
}
|
||||
}
|
||||
if (!this.documentListView || !this.documentListView.filter_rules || !this.documentListView.sort_reverse || !this.documentListView.sort_field) {
|
||||
if (!this.documentListView || this.documentListView.filter_rules == null || this.documentListView.sort_reverse == null || this.documentListView.sort_field == null) {
|
||||
this.documentListView = {
|
||||
filter_rules: [],
|
||||
sort_reverse: true,
|
||||
|
@ -13,10 +13,10 @@ import { TagService } from './tag.service';
|
||||
import { FILTER_RULE_TYPES } from 'src/app/data/filter-rule-type';
|
||||
|
||||
export const DOCUMENT_SORT_FIELDS = [
|
||||
{ field: "correspondent__name", name: $localize`Correspondent` },
|
||||
{ field: "document_type__name", name: $localize`Document type` },
|
||||
{ field: 'title', name: $localize`Title` },
|
||||
{ field: 'archive_serial_number', name: $localize`ASN` },
|
||||
{ field: "correspondent__name", name: $localize`Correspondent` },
|
||||
{ field: 'title', name: $localize`Title` },
|
||||
{ field: "document_type__name", name: $localize`Document type` },
|
||||
{ field: 'created', name: $localize`Created` },
|
||||
{ field: 'added', name: $localize`Added` },
|
||||
{ field: 'modified', name: $localize`Modified` }
|
||||
|
1907
src-ui/src/locale/messages.fr.xlf
Normal file
1907
src-ui/src/locale/messages.fr.xlf
Normal file
File diff suppressed because it is too large
Load Diff
@ -174,6 +174,17 @@ $border-color-dark-mode: #47494f;
|
||||
color: $text-color-dark-mode;
|
||||
border-color: $border-color-dark-mode;
|
||||
|
||||
.des,
|
||||
.asc {
|
||||
background-color: transparent !important;
|
||||
color: $text-color-dark-mode;
|
||||
border-color: $border-color-dark-mode;
|
||||
|
||||
&::after {
|
||||
filter: invert(0.8); /* arrow is a black inline png bkgd image (!) so use filter */
|
||||
}
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: $bg-light-dark-mode;
|
||||
color: $text-color-dark-mode-accent;
|
||||
@ -250,13 +261,18 @@ $border-color-dark-mode: #47494f;
|
||||
background-color: $bg-dark-mode !important;
|
||||
}
|
||||
|
||||
.form-control,
|
||||
.form-control:not(.is-invalid):not(.btn),
|
||||
input:not(.is-invalid),
|
||||
textarea:not(.is-invalid) {
|
||||
border-color: $border-color-dark-mode; /* we dont want to override controls that get highlighting for errors */
|
||||
}
|
||||
|
||||
.form-control:not(.btn),
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
background-color: $bg-dark-mode;
|
||||
color: $text-color-dark-mode;
|
||||
border-color: $border-color-dark-mode;
|
||||
|
||||
&::placeholder {
|
||||
color: $text-color-dark-mode;
|
||||
@ -325,6 +341,12 @@ $border-color-dark-mode: #47494f;
|
||||
.progress {
|
||||
background-color: $border-color-dark-mode;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
color: $text-color-dark-mode-accent;
|
||||
background-color: darken($danger-dark-mode, 20%);
|
||||
border-color: darken($danger-dark-mode, 20%);
|
||||
}
|
||||
}
|
||||
|
||||
body.color-scheme-dark {
|
||||
|
@ -71,6 +71,11 @@ class Consumer(LoggingMixin):
|
||||
if not settings.PRE_CONSUME_SCRIPT:
|
||||
return
|
||||
|
||||
if not os.path.isfile(settings.PRE_CONSUME_SCRIPT):
|
||||
raise ConsumerError(
|
||||
f"Configured pre-consume script "
|
||||
f"{settings.PRE_CONSUME_SCRIPT} does not exist.")
|
||||
|
||||
try:
|
||||
Popen((settings.PRE_CONSUME_SCRIPT, self.path)).wait()
|
||||
except Exception as e:
|
||||
@ -82,6 +87,11 @@ class Consumer(LoggingMixin):
|
||||
if not settings.POST_CONSUME_SCRIPT:
|
||||
return
|
||||
|
||||
if not os.path.isfile(settings.POST_CONSUME_SCRIPT):
|
||||
raise ConsumerError(
|
||||
f"Configured post-consume script "
|
||||
f"{settings.POST_CONSUME_SCRIPT} does not exist.")
|
||||
|
||||
try:
|
||||
Popen((
|
||||
settings.POST_CONSUME_SCRIPT,
|
||||
@ -252,8 +262,6 @@ class Consumer(LoggingMixin):
|
||||
self.log("debug", "Deleting file {}".format(self.path))
|
||||
os.unlink(self.path)
|
||||
|
||||
self.run_post_consume_script(document)
|
||||
|
||||
except Exception as e:
|
||||
self.log(
|
||||
"error",
|
||||
@ -264,6 +272,8 @@ class Consumer(LoggingMixin):
|
||||
finally:
|
||||
document_parser.cleanup()
|
||||
|
||||
self.run_post_consume_script(document)
|
||||
|
||||
self.log(
|
||||
"info",
|
||||
"Document {} consumption finished".format(document)
|
||||
|
@ -4,7 +4,7 @@ from .models import Correspondent, Document, Tag, DocumentType, Log
|
||||
|
||||
CHAR_KWARGS = ["istartswith", "iendswith", "icontains", "iexact"]
|
||||
ID_KWARGS = ["in", "exact"]
|
||||
INT_KWARGS = ["exact", "gt", "gte", "lt", "lte"]
|
||||
INT_KWARGS = ["exact", "gt", "gte", "lt", "lte", "isnull"]
|
||||
DATE_KWARGS = ["year", "month", "day", "date__gt", "gt", "date__lt", "lt"]
|
||||
|
||||
|
||||
|
@ -13,8 +13,14 @@ from ...parsers import get_parser_class_for_mime_type
|
||||
|
||||
def _process_document(doc_in):
|
||||
document = Document.objects.get(id=doc_in)
|
||||
parser = get_parser_class_for_mime_type(document.mime_type)(
|
||||
logging_group=None)
|
||||
parser_class = get_parser_class_for_mime_type(document.mime_type)
|
||||
|
||||
if parser_class:
|
||||
parser = parser_class(logging_group=None)
|
||||
else:
|
||||
print(f"{document} No parser for mime type {document.mime_type}")
|
||||
return
|
||||
|
||||
try:
|
||||
thumb = parser.get_optimised_thumbnail(
|
||||
document.source_path, document.mime_type)
|
||||
|
@ -210,6 +210,13 @@ def parse_date(filename, text):
|
||||
}
|
||||
)
|
||||
|
||||
def __filter(date):
|
||||
if date and date.year > 1900 and \
|
||||
date <= timezone.now() and \
|
||||
date.date() not in settings.IGNORE_DATES:
|
||||
return date
|
||||
return None
|
||||
|
||||
date = None
|
||||
|
||||
# if filename date parsing is enabled, search there first:
|
||||
@ -223,7 +230,8 @@ def parse_date(filename, text):
|
||||
# Skip all matches that do not parse to a proper date
|
||||
continue
|
||||
|
||||
if date and date.year > 1900 and date <= timezone.now():
|
||||
date = __filter(date)
|
||||
if date is not None:
|
||||
return date
|
||||
|
||||
# Iterate through all regex matches in text and try to parse the date
|
||||
@ -236,10 +244,9 @@ def parse_date(filename, text):
|
||||
# Skip all matches that do not parse to a proper date
|
||||
continue
|
||||
|
||||
if date and date.year > 1900 and date <= timezone.now():
|
||||
date = __filter(date)
|
||||
if date is not None:
|
||||
break
|
||||
else:
|
||||
date = None
|
||||
|
||||
return date
|
||||
|
||||
|
@ -36,7 +36,7 @@
|
||||
|
||||
<body class="text-center">
|
||||
<div class="form-signin">
|
||||
<img class="mb-4" src="{% static 'frontend/assets/logo.svg' %}" alt="" width="300">
|
||||
<img class="mb-4" src="{% static 'frontend/en-US/assets/logo.svg' %}" alt="" width="300">
|
||||
<p>You have been successfully logged out. Bye!</p>
|
||||
<a href="/">Sign in again</a>
|
||||
</div>
|
||||
|
@ -37,7 +37,7 @@
|
||||
<body class="text-center">
|
||||
<form class="form-signin" method="post">
|
||||
{% csrf_token %}
|
||||
<img class="mb-4" src="{% static 'frontend/assets/logo.svg' %}" alt="" width="300">
|
||||
<img class="mb-4" src="{% static 'frontend/en-US/assets/logo.svg' %}" alt="" width="300">
|
||||
<p>Please sign in.</p>
|
||||
{% if form.errors %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
|
@ -468,6 +468,42 @@ class TestConsumer(DirectoriesMixin, TestCase):
|
||||
self.assertTrue(os.path.isfile(dst))
|
||||
|
||||
|
||||
class PreConsumeTestCase(TestCase):
|
||||
|
||||
@mock.patch("documents.consumer.Popen")
|
||||
@override_settings(PRE_CONSUME_SCRIPT=None)
|
||||
def test_no_pre_consume_script(self, m):
|
||||
c = Consumer()
|
||||
c.path = "path-to-file"
|
||||
c.run_pre_consume_script()
|
||||
m.assert_not_called()
|
||||
|
||||
@mock.patch("documents.consumer.Popen")
|
||||
@override_settings(PRE_CONSUME_SCRIPT="does-not-exist")
|
||||
def test_pre_consume_script_not_found(self, m):
|
||||
c = Consumer()
|
||||
c.path = "path-to-file"
|
||||
self.assertRaises(ConsumerError, c.run_pre_consume_script)
|
||||
|
||||
@mock.patch("documents.consumer.Popen")
|
||||
def test_pre_consume_script(self, m):
|
||||
with tempfile.NamedTemporaryFile() as script:
|
||||
with override_settings(PRE_CONSUME_SCRIPT=script.name):
|
||||
c = Consumer()
|
||||
c.path = "path-to-file"
|
||||
c.run_pre_consume_script()
|
||||
|
||||
m.assert_called_once()
|
||||
|
||||
args, kwargs = m.call_args
|
||||
|
||||
command = args[0]
|
||||
|
||||
self.assertEqual(command[0], script.name)
|
||||
self.assertEqual(command[1], "path-to-file")
|
||||
|
||||
|
||||
|
||||
class PostConsumeTestCase(TestCase):
|
||||
|
||||
@mock.patch("documents.consumer.Popen")
|
||||
@ -483,36 +519,45 @@ class PostConsumeTestCase(TestCase):
|
||||
|
||||
m.assert_not_called()
|
||||
|
||||
@mock.patch("documents.consumer.Popen")
|
||||
@override_settings(POST_CONSUME_SCRIPT="script")
|
||||
def test_post_consume_script_simple(self, m):
|
||||
|
||||
@override_settings(POST_CONSUME_SCRIPT="does-not-exist")
|
||||
def test_post_consume_script_not_found(self):
|
||||
doc = Document.objects.create(title="Test", mime_type="application/pdf")
|
||||
|
||||
Consumer().run_post_consume_script(doc)
|
||||
|
||||
m.assert_called_once()
|
||||
self.assertRaises(ConsumerError, Consumer().run_post_consume_script, doc)
|
||||
|
||||
@mock.patch("documents.consumer.Popen")
|
||||
def test_post_consume_script_simple(self, m):
|
||||
with tempfile.NamedTemporaryFile() as script:
|
||||
with override_settings(POST_CONSUME_SCRIPT=script.name):
|
||||
doc = Document.objects.create(title="Test", mime_type="application/pdf")
|
||||
|
||||
Consumer().run_post_consume_script(doc)
|
||||
|
||||
m.assert_called_once()
|
||||
|
||||
@mock.patch("documents.consumer.Popen")
|
||||
@override_settings(POST_CONSUME_SCRIPT="script")
|
||||
def test_post_consume_script_with_correspondent(self, m):
|
||||
c = Correspondent.objects.create(name="my_bank")
|
||||
doc = Document.objects.create(title="Test", mime_type="application/pdf", correspondent=c)
|
||||
tag1 = Tag.objects.create(name="a")
|
||||
tag2 = Tag.objects.create(name="b")
|
||||
doc.tags.add(tag1)
|
||||
doc.tags.add(tag2)
|
||||
with tempfile.NamedTemporaryFile() as script:
|
||||
with override_settings(POST_CONSUME_SCRIPT=script.name):
|
||||
c = Correspondent.objects.create(name="my_bank")
|
||||
doc = Document.objects.create(title="Test", mime_type="application/pdf", correspondent=c)
|
||||
tag1 = Tag.objects.create(name="a")
|
||||
tag2 = Tag.objects.create(name="b")
|
||||
doc.tags.add(tag1)
|
||||
doc.tags.add(tag2)
|
||||
|
||||
Consumer().run_post_consume_script(doc)
|
||||
Consumer().run_post_consume_script(doc)
|
||||
|
||||
m.assert_called_once()
|
||||
m.assert_called_once()
|
||||
|
||||
args, kwargs = m.call_args
|
||||
args, kwargs = m.call_args
|
||||
|
||||
command = args[0]
|
||||
command = args[0]
|
||||
|
||||
self.assertEqual(command[0], "script")
|
||||
self.assertEqual(command[1], str(doc.pk))
|
||||
self.assertEqual(command[5], f"/api/documents/{doc.pk}/download/")
|
||||
self.assertEqual(command[6], f"/api/documents/{doc.pk}/thumb/")
|
||||
self.assertEqual(command[7], "my_bank")
|
||||
self.assertCountEqual(command[8].split(","), ["a", "b"])
|
||||
self.assertEqual(command[0], script.name)
|
||||
self.assertEqual(command[1], str(doc.pk))
|
||||
self.assertEqual(command[5], f"/api/documents/{doc.pk}/download/")
|
||||
self.assertEqual(command[6], f"/api/documents/{doc.pk}/thumb/")
|
||||
self.assertEqual(command[7], "my_bank")
|
||||
self.assertCountEqual(command[8].split(","), ["a", "b"])
|
||||
|
@ -138,3 +138,18 @@ class TestDate(TestCase):
|
||||
@override_settings(FILENAME_DATE_ORDER="YMD")
|
||||
def test_filename_date_parse_invalid(self, *args):
|
||||
self.assertIsNone(parse_date("/tmp/20 408000l 2475 - test.pdf", "No date in here"))
|
||||
|
||||
@override_settings(IGNORE_DATES=(datetime.date(2019, 11, 3), datetime.date(2020, 1, 17)))
|
||||
def test_ignored_dates(self, *args):
|
||||
text = (
|
||||
"lorem ipsum 110319, 20200117 and lorem 13.02.2018 lorem "
|
||||
"ipsum"
|
||||
)
|
||||
date = parse_date("", text)
|
||||
self.assertEqual(
|
||||
date,
|
||||
datetime.datetime(
|
||||
2018, 2, 13, 0, 0,
|
||||
tzinfo=tz.gettz(settings.TIME_ZONE)
|
||||
)
|
||||
)
|
52
src/documents/tests/test_management_thumbnails.py
Normal file
52
src/documents/tests/test_management_thumbnails.py
Normal file
@ -0,0 +1,52 @@
|
||||
import os
|
||||
import shutil
|
||||
from unittest import mock
|
||||
|
||||
from django.core.management import call_command
|
||||
from django.test import TestCase
|
||||
|
||||
from documents.management.commands.document_thumbnails import _process_document
|
||||
from documents.models import Document, Tag, Correspondent, DocumentType
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
|
||||
|
||||
class TestMakeThumbnails(DirectoriesMixin, TestCase):
|
||||
|
||||
def make_models(self):
|
||||
self.d1 = Document.objects.create(checksum="A", title="A", content="first document", mime_type="application/pdf", filename="test.pdf")
|
||||
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), self.d1.source_path)
|
||||
|
||||
self.d2 = Document.objects.create(checksum="Ass", title="A", content="first document", mime_type="application/pdf", filename="test2.pdf")
|
||||
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"), self.d2.source_path)
|
||||
|
||||
def setUp(self) -> None:
|
||||
super(TestMakeThumbnails, self).setUp()
|
||||
self.make_models()
|
||||
|
||||
def test_process_document(self):
|
||||
self.assertFalse(os.path.isfile(self.d1.thumbnail_path))
|
||||
_process_document(self.d1.id)
|
||||
self.assertTrue(os.path.isfile(self.d1.thumbnail_path))
|
||||
|
||||
@mock.patch("documents.management.commands.document_thumbnails.shutil.move")
|
||||
def test_process_document_invalid_mime_type(self, m):
|
||||
self.d1.mime_type = "asdasdasd"
|
||||
self.d1.save()
|
||||
|
||||
_process_document(self.d1.id)
|
||||
|
||||
m.assert_not_called()
|
||||
|
||||
def test_command(self):
|
||||
self.assertFalse(os.path.isfile(self.d1.thumbnail_path))
|
||||
self.assertFalse(os.path.isfile(self.d2.thumbnail_path))
|
||||
call_command('document_thumbnails')
|
||||
self.assertTrue(os.path.isfile(self.d1.thumbnail_path))
|
||||
self.assertTrue(os.path.isfile(self.d2.thumbnail_path))
|
||||
|
||||
def test_command_documentid(self):
|
||||
self.assertFalse(os.path.isfile(self.d1.thumbnail_path))
|
||||
self.assertFalse(os.path.isfile(self.d2.thumbnail_path))
|
||||
call_command('document_thumbnails', '-d', f"{self.d1.id}")
|
||||
self.assertTrue(os.path.isfile(self.d1.thumbnail_path))
|
||||
self.assertFalse(os.path.isfile(self.d2.thumbnail_path))
|
569
src/locale/fr/LC_MESSAGES/django.po
Normal file
569
src/locale/fr/LC_MESSAGES/django.po
Normal file
@ -0,0 +1,569 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Jonas Winkler <dev@jpwinkler.de>, 2020
|
||||
# Philmo67, 2021
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-01-02 00:26+0000\n"
|
||||
"PO-Revision-Date: 2020-12-30 19:27+0000\n"
|
||||
"Last-Translator: Philmo67, 2021\n"
|
||||
"Language-Team: French (https://www.transifex.com/paperless/teams/115905/fr/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: fr\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: documents/apps.py:10
|
||||
msgid "Documents"
|
||||
msgstr "Documents"
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr "Un des mots"
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr "Tous les mots"
|
||||
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr "Concordance exacte"
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr "Expression régulière"
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr "Mot approximatif"
|
||||
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr "Automatique"
|
||||
|
||||
#: documents/models.py:41 documents/models.py:354 paperless_mail/models.py:25
|
||||
#: paperless_mail/models.py:100
|
||||
msgid "name"
|
||||
msgstr "nom"
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "match"
|
||||
msgstr "rapprochement"
|
||||
|
||||
#: documents/models.py:49
|
||||
msgid "matching algorithm"
|
||||
msgstr "algorithme de rapprochement"
|
||||
|
||||
#: documents/models.py:55
|
||||
msgid "is insensitive"
|
||||
msgstr "est insensible à la casse"
|
||||
|
||||
#: documents/models.py:80 documents/models.py:140
|
||||
msgid "correspondent"
|
||||
msgstr "correspondant"
|
||||
|
||||
#: documents/models.py:81
|
||||
msgid "correspondents"
|
||||
msgstr "correspondants"
|
||||
|
||||
#: documents/models.py:103
|
||||
msgid "color"
|
||||
msgstr "couleur"
|
||||
|
||||
#: documents/models.py:107
|
||||
msgid "is inbox tag"
|
||||
msgstr "est une étiquette de boîte de réception"
|
||||
|
||||
#: documents/models.py:109
|
||||
msgid ""
|
||||
"Marks this tag as an inbox tag: All newly consumed documents will be tagged "
|
||||
"with inbox tags."
|
||||
msgstr ""
|
||||
"Marque cette étiquette comme étiquette de boîte de réception : ces "
|
||||
"étiquettes sont affectées à tous les documents nouvellement traités."
|
||||
|
||||
#: documents/models.py:114
|
||||
msgid "tag"
|
||||
msgstr "étiquette"
|
||||
|
||||
#: documents/models.py:115 documents/models.py:171
|
||||
msgid "tags"
|
||||
msgstr "étiquettes"
|
||||
|
||||
#: documents/models.py:121 documents/models.py:153
|
||||
msgid "document type"
|
||||
msgstr "type de document"
|
||||
|
||||
#: documents/models.py:122
|
||||
msgid "document types"
|
||||
msgstr "types de document"
|
||||
|
||||
#: documents/models.py:130
|
||||
msgid "Unencrypted"
|
||||
msgstr "Non chiffré"
|
||||
|
||||
#: documents/models.py:131
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "Chiffré avec GNU Privacy Guard"
|
||||
|
||||
#: documents/models.py:144
|
||||
msgid "title"
|
||||
msgstr "titre"
|
||||
|
||||
#: documents/models.py:157
|
||||
msgid "content"
|
||||
msgstr "contenu"
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid ""
|
||||
"The raw, text-only data of the document. This field is primarily used for "
|
||||
"searching."
|
||||
msgstr ""
|
||||
"Les données brutes du document, en format texte uniquement. Ce champ est "
|
||||
"principalement utilisé pour la recherche."
|
||||
|
||||
#: documents/models.py:164
|
||||
msgid "mime type"
|
||||
msgstr "type mime"
|
||||
|
||||
#: documents/models.py:175
|
||||
msgid "checksum"
|
||||
msgstr "somme de contrôle"
|
||||
|
||||
#: documents/models.py:179
|
||||
msgid "The checksum of the original document."
|
||||
msgstr "La somme de contrôle du document original."
|
||||
|
||||
#: documents/models.py:183
|
||||
msgid "archive checksum"
|
||||
msgstr "somme de contrôle de l'archive"
|
||||
|
||||
#: documents/models.py:188
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr "La somme de contrôle du document archivé."
|
||||
|
||||
#: documents/models.py:192 documents/models.py:332
|
||||
msgid "created"
|
||||
msgstr "créé le"
|
||||
|
||||
#: documents/models.py:196
|
||||
msgid "modified"
|
||||
msgstr "modifié"
|
||||
|
||||
#: documents/models.py:200
|
||||
msgid "storage type"
|
||||
msgstr "forme d'enregistrement :"
|
||||
|
||||
#: documents/models.py:208
|
||||
msgid "added"
|
||||
msgstr "date d'ajout"
|
||||
|
||||
#: documents/models.py:212
|
||||
msgid "filename"
|
||||
msgstr "nom du fichier"
|
||||
|
||||
#: documents/models.py:217
|
||||
msgid "Current filename in storage"
|
||||
msgstr "Nom du fichier courant en base de données"
|
||||
|
||||
#: documents/models.py:221
|
||||
msgid "archive serial number"
|
||||
msgstr "numéro de série de l'archive"
|
||||
|
||||
#: documents/models.py:226
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr ""
|
||||
"Le classement de ce document dans votre archive de documents physiques."
|
||||
|
||||
#: documents/models.py:232
|
||||
msgid "document"
|
||||
msgstr "document"
|
||||
|
||||
#: documents/models.py:233
|
||||
msgid "documents"
|
||||
msgstr "documents"
|
||||
|
||||
#: documents/models.py:315
|
||||
msgid "debug"
|
||||
msgstr "débogage"
|
||||
|
||||
#: documents/models.py:316
|
||||
msgid "information"
|
||||
msgstr "information"
|
||||
|
||||
#: documents/models.py:317
|
||||
msgid "warning"
|
||||
msgstr "avertissement"
|
||||
|
||||
#: documents/models.py:318
|
||||
msgid "error"
|
||||
msgstr "erreur"
|
||||
|
||||
#: documents/models.py:319
|
||||
msgid "critical"
|
||||
msgstr "critique"
|
||||
|
||||
#: documents/models.py:323
|
||||
msgid "group"
|
||||
msgstr "groupe"
|
||||
|
||||
#: documents/models.py:326
|
||||
msgid "message"
|
||||
msgstr "message"
|
||||
|
||||
#: documents/models.py:329
|
||||
msgid "level"
|
||||
msgstr "niveau"
|
||||
|
||||
#: documents/models.py:336
|
||||
msgid "log"
|
||||
msgstr "rapport"
|
||||
|
||||
#: documents/models.py:337
|
||||
msgid "logs"
|
||||
msgstr "rapports"
|
||||
|
||||
#: documents/models.py:348 documents/models.py:398
|
||||
msgid "saved view"
|
||||
msgstr "vue enregistrée"
|
||||
|
||||
#: documents/models.py:349
|
||||
msgid "saved views"
|
||||
msgstr "vues enregistrées"
|
||||
|
||||
#: documents/models.py:352
|
||||
msgid "user"
|
||||
msgstr "utilisateur"
|
||||
|
||||
#: documents/models.py:358
|
||||
msgid "show on dashboard"
|
||||
msgstr "montrer sur le tableau de bord"
|
||||
|
||||
#: documents/models.py:361
|
||||
msgid "show in sidebar"
|
||||
msgstr "montrer dans la barre latérale"
|
||||
|
||||
#: documents/models.py:365
|
||||
msgid "sort field"
|
||||
msgstr "champ de tri"
|
||||
|
||||
#: documents/models.py:368
|
||||
msgid "sort reverse"
|
||||
msgstr "tri inverse"
|
||||
|
||||
#: documents/models.py:374
|
||||
msgid "title contains"
|
||||
msgstr "le titre contient"
|
||||
|
||||
#: documents/models.py:375
|
||||
msgid "content contains"
|
||||
msgstr "le contenu contient"
|
||||
|
||||
#: documents/models.py:376
|
||||
msgid "ASN is"
|
||||
msgstr "le NSA est"
|
||||
|
||||
#: documents/models.py:377
|
||||
msgid "correspondent is"
|
||||
msgstr "le correspondant est"
|
||||
|
||||
#: documents/models.py:378
|
||||
msgid "document type is"
|
||||
msgstr "le type de document est"
|
||||
|
||||
#: documents/models.py:379
|
||||
msgid "is in inbox"
|
||||
msgstr "est dans la boîte de réception"
|
||||
|
||||
#: documents/models.py:380
|
||||
msgid "has tag"
|
||||
msgstr "porte l'étiquette"
|
||||
|
||||
#: documents/models.py:381
|
||||
msgid "has any tag"
|
||||
msgstr "porte l'une des étiquettes"
|
||||
|
||||
#: documents/models.py:382
|
||||
msgid "created before"
|
||||
msgstr "créé avant"
|
||||
|
||||
#: documents/models.py:383
|
||||
msgid "created after"
|
||||
msgstr "créé après"
|
||||
|
||||
#: documents/models.py:384
|
||||
msgid "created year is"
|
||||
msgstr "l'année de création est"
|
||||
|
||||
#: documents/models.py:385
|
||||
msgid "created month is"
|
||||
msgstr "le mois de création est"
|
||||
|
||||
#: documents/models.py:386
|
||||
msgid "created day is"
|
||||
msgstr "le jour de création est"
|
||||
|
||||
#: documents/models.py:387
|
||||
msgid "added before"
|
||||
msgstr "ajouté avant"
|
||||
|
||||
#: documents/models.py:388
|
||||
msgid "added after"
|
||||
msgstr "ajouté après"
|
||||
|
||||
#: documents/models.py:389
|
||||
msgid "modified before"
|
||||
msgstr "modifié avant"
|
||||
|
||||
#: documents/models.py:390
|
||||
msgid "modified after"
|
||||
msgstr "modifié après"
|
||||
|
||||
#: documents/models.py:391
|
||||
msgid "does not have tag"
|
||||
msgstr "ne porte pas d'étiquette"
|
||||
|
||||
#: documents/models.py:402
|
||||
msgid "rule type"
|
||||
msgstr "type de règle"
|
||||
|
||||
#: documents/models.py:406
|
||||
msgid "value"
|
||||
msgstr "valeur"
|
||||
|
||||
#: documents/models.py:412
|
||||
msgid "filter rule"
|
||||
msgstr "règle de filtrage"
|
||||
|
||||
#: documents/models.py:413
|
||||
msgid "filter rules"
|
||||
msgstr "règles de filtrage"
|
||||
|
||||
#: paperless/settings.py:254
|
||||
msgid "English"
|
||||
msgstr "Anglais"
|
||||
|
||||
#: paperless/settings.py:255
|
||||
msgid "German"
|
||||
msgstr "Allemand"
|
||||
|
||||
#: paperless/urls.py:108
|
||||
msgid "Paperless-ng administration"
|
||||
msgstr "Administration de Paperless-ng"
|
||||
|
||||
#: paperless_mail/admin.py:24
|
||||
msgid "Filter"
|
||||
msgstr "Filtrage"
|
||||
|
||||
#: paperless_mail/admin.py:26
|
||||
msgid ""
|
||||
"Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr ""
|
||||
"Paperless-ng ne traitera que les courriers qui correspondent à TOUS les "
|
||||
"filtres ci-dessous."
|
||||
|
||||
#: paperless_mail/admin.py:34
|
||||
msgid "Actions"
|
||||
msgstr "Actions"
|
||||
|
||||
#: paperless_mail/admin.py:36
|
||||
msgid ""
|
||||
"The action applied to the mail. This action is only performed when documents"
|
||||
" were consumed from the mail. Mails without attachments will remain entirely"
|
||||
" untouched."
|
||||
msgstr ""
|
||||
"Action appliquée au courriel. Cette action n'est exécutée que lorsque les "
|
||||
"documents ont été traités depuis des courriels. Les courriels sans pièces "
|
||||
"jointes demeurent totalement inchangés."
|
||||
|
||||
#: paperless_mail/admin.py:43
|
||||
msgid "Metadata"
|
||||
msgstr "Métadonnées"
|
||||
|
||||
#: paperless_mail/admin.py:45
|
||||
msgid ""
|
||||
"Assign metadata to documents consumed from this rule automatically. If you "
|
||||
"do not assign tags, types or correspondents here, paperless will still "
|
||||
"process all matching rules that you have defined."
|
||||
msgstr ""
|
||||
"Affecter automatiquement des métadonnées aux documents traités à partir de "
|
||||
"cette règle. Si vous n'affectez pas d'étiquettes, de types ou de "
|
||||
"correspondants ici, Paperless-ng traitera quand même toutes les règles de "
|
||||
"rapprochement que vous avez définies."
|
||||
|
||||
#: paperless_mail/apps.py:9
|
||||
msgid "Paperless mail"
|
||||
msgstr "Paperless-ng pour le courriel"
|
||||
|
||||
#: paperless_mail/models.py:11
|
||||
msgid "mail account"
|
||||
msgstr "compte de messagerie"
|
||||
|
||||
#: paperless_mail/models.py:12
|
||||
msgid "mail accounts"
|
||||
msgstr "comptes de messagerie"
|
||||
|
||||
#: paperless_mail/models.py:19
|
||||
msgid "No encryption"
|
||||
msgstr "Pas de chiffrement"
|
||||
|
||||
#: paperless_mail/models.py:20
|
||||
msgid "Use SSL"
|
||||
msgstr "Utiliser SSL"
|
||||
|
||||
#: paperless_mail/models.py:21
|
||||
msgid "Use STARTTLS"
|
||||
msgstr "Utiliser STARTTLS"
|
||||
|
||||
#: paperless_mail/models.py:29
|
||||
msgid "IMAP server"
|
||||
msgstr "Serveur IMAP"
|
||||
|
||||
#: paperless_mail/models.py:33
|
||||
msgid "IMAP port"
|
||||
msgstr "Port IMAP"
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid ""
|
||||
"This is usually 143 for unencrypted and STARTTLS connections, and 993 for "
|
||||
"SSL connections."
|
||||
msgstr ""
|
||||
"Généralement 143 pour les connexions non chiffrées et STARTTLS, et 993 pour "
|
||||
"les connexions SSL."
|
||||
|
||||
#: paperless_mail/models.py:40
|
||||
msgid "IMAP security"
|
||||
msgstr "Sécurité IMAP"
|
||||
|
||||
#: paperless_mail/models.py:46
|
||||
msgid "username"
|
||||
msgstr "nom d'utilisateur"
|
||||
|
||||
#: paperless_mail/models.py:50
|
||||
msgid "password"
|
||||
msgstr "mot de passe"
|
||||
|
||||
#: paperless_mail/models.py:60
|
||||
msgid "mail rule"
|
||||
msgstr "règle de courriel"
|
||||
|
||||
#: paperless_mail/models.py:61
|
||||
msgid "mail rules"
|
||||
msgstr "règles de courriel"
|
||||
|
||||
#: paperless_mail/models.py:69
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr "Marquer comme lu, ne pas traiter les courriels lus"
|
||||
|
||||
#: paperless_mail/models.py:70
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr "Marquer le courriel, ne pas traiter les courriels marqués"
|
||||
|
||||
#: paperless_mail/models.py:71
|
||||
msgid "Move to specified folder"
|
||||
msgstr "Déplacer vers le dossier spécifié"
|
||||
|
||||
#: paperless_mail/models.py:72
|
||||
msgid "Delete"
|
||||
msgstr "Supprimer"
|
||||
|
||||
#: paperless_mail/models.py:79
|
||||
msgid "Use subject as title"
|
||||
msgstr "Utiliser le sujet en tant que titre"
|
||||
|
||||
#: paperless_mail/models.py:80
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr "Utiliser le nom de la pièce jointe en tant que titre"
|
||||
|
||||
#: paperless_mail/models.py:90
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr "Ne pas affecter de correspondant"
|
||||
|
||||
#: paperless_mail/models.py:92
|
||||
msgid "Use mail address"
|
||||
msgstr "Utiliser l'adresse électronique"
|
||||
|
||||
#: paperless_mail/models.py:94
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr "Utiliser le nom (ou l'adresse électronique s'il n'est pas disponible)"
|
||||
|
||||
#: paperless_mail/models.py:96
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr "Utiliser le correspondant sélectionné ci-dessous"
|
||||
|
||||
#: paperless_mail/models.py:104
|
||||
msgid "order"
|
||||
msgstr "ordre"
|
||||
|
||||
#: paperless_mail/models.py:111
|
||||
msgid "account"
|
||||
msgstr "compte"
|
||||
|
||||
#: paperless_mail/models.py:115
|
||||
msgid "folder"
|
||||
msgstr "répertoire"
|
||||
|
||||
#: paperless_mail/models.py:119
|
||||
msgid "filter from"
|
||||
msgstr "filtrer l'expéditeur"
|
||||
|
||||
#: paperless_mail/models.py:122
|
||||
msgid "filter subject"
|
||||
msgstr "filtrer le sujet"
|
||||
|
||||
#: paperless_mail/models.py:125
|
||||
msgid "filter body"
|
||||
msgstr "filtrer le corps du message"
|
||||
|
||||
#: paperless_mail/models.py:129
|
||||
msgid "maximum age"
|
||||
msgstr "âge maximum"
|
||||
|
||||
#: paperless_mail/models.py:131
|
||||
msgid "Specified in days."
|
||||
msgstr "En jours."
|
||||
|
||||
#: paperless_mail/models.py:134
|
||||
msgid "action"
|
||||
msgstr "action"
|
||||
|
||||
#: paperless_mail/models.py:140
|
||||
msgid "action parameter"
|
||||
msgstr "paramètre d'action"
|
||||
|
||||
#: paperless_mail/models.py:142
|
||||
msgid ""
|
||||
"Additional parameter for the action selected above, i.e., the target folder "
|
||||
"of the move to folder action."
|
||||
msgstr ""
|
||||
"Paramètre supplémentaire pour l'action sélectionnée ci-dessus, par exemple "
|
||||
"le dossier cible de l'action de déplacement vers un dossier."
|
||||
|
||||
#: paperless_mail/models.py:148
|
||||
msgid "assign title from"
|
||||
msgstr "affecter le titre depuis"
|
||||
|
||||
#: paperless_mail/models.py:158
|
||||
msgid "assign this tag"
|
||||
msgstr "affecter cette étiquette"
|
||||
|
||||
#: paperless_mail/models.py:166
|
||||
msgid "assign this document type"
|
||||
msgstr "affecter ce type de document"
|
||||
|
||||
#: paperless_mail/models.py:170
|
||||
msgid "assign correspondent from"
|
||||
msgstr "affecter le correspondant depuis"
|
||||
|
||||
#: paperless_mail/models.py:180
|
||||
msgid "assign this correspondent"
|
||||
msgstr "affecter ce correspondant"
|
@ -2,6 +2,7 @@ from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
from rest_framework import authentication
|
||||
from django.contrib.auth.middleware import RemoteUserMiddleware
|
||||
|
||||
|
||||
class AutoLoginMiddleware(MiddlewareMixin):
|
||||
@ -26,3 +27,11 @@ class AngularApiAuthenticationOverride(authentication.BaseAuthentication):
|
||||
return (user, None)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class HttpRemoteUserMiddleware(RemoteUserMiddleware):
|
||||
""" This class allows authentication via HTTP_REMOTE_USER which is set for
|
||||
example by certain SSO applications.
|
||||
"""
|
||||
|
||||
header = 'HTTP_REMOTE_USER'
|
||||
|
@ -4,6 +4,7 @@ import multiprocessing
|
||||
import os
|
||||
import re
|
||||
|
||||
import dateparser
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@ -128,6 +129,20 @@ MIDDLEWARE = [
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ENABLE_HTTP_REMOTE_USER = __get_boolean("PAPERLESS_ENABLE_HTTP_REMOTE_USER")
|
||||
|
||||
if ENABLE_HTTP_REMOTE_USER:
|
||||
MIDDLEWARE.append(
|
||||
'paperless.auth.HttpRemoteUserMiddleware'
|
||||
)
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
'django.contrib.auth.backends.RemoteUserBackend',
|
||||
'django.contrib.auth.backends.ModelBackend'
|
||||
]
|
||||
REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'].append(
|
||||
'rest_framework.authentication.RemoteUserAuthentication'
|
||||
)
|
||||
|
||||
ROOT_URLCONF = 'paperless.urls'
|
||||
|
||||
FORCE_SCRIPT_NAME = os.getenv("PAPERLESS_FORCE_SCRIPT_NAME")
|
||||
@ -253,7 +268,8 @@ LANGUAGE_CODE = 'en-us'
|
||||
LANGUAGES = [
|
||||
("en-us", _("English")),
|
||||
("de", _("German")),
|
||||
("nl-nl", _("Dutch"))
|
||||
("nl-nl", _("Dutch")),
|
||||
("fr", _("French"))
|
||||
]
|
||||
|
||||
LOCALE_PATHS = [
|
||||
@ -445,3 +461,10 @@ PAPERLESS_TIKA_ENDPOINT = os.getenv("PAPERLESS_TIKA_ENDPOINT", "http://localhost
|
||||
PAPERLESS_TIKA_GOTENBERG_ENDPOINT = os.getenv(
|
||||
"PAPERLESS_TIKA_GOTENBERG_ENDPOINT", "http://localhost:3000"
|
||||
)
|
||||
|
||||
# List dates that should be ignored when trying to parse date from document text
|
||||
IGNORE_DATES = set()
|
||||
for s in os.getenv("PAPERLESS_IGNORE_DATES", "").split(","):
|
||||
d = dateparser.parse(s)
|
||||
if d:
|
||||
IGNORE_DATES.add(d.date())
|
||||
|
@ -12,6 +12,7 @@ class MailAccountAdmin(admin.ModelAdmin):
|
||||
class MailRuleAdmin(admin.ModelAdmin):
|
||||
|
||||
radio_fields = {
|
||||
"attachment_type": admin.VERTICAL,
|
||||
"action": admin.VERTICAL,
|
||||
"assign_title_from": admin.VERTICAL,
|
||||
"assign_correspondent_from": admin.VERTICAL
|
||||
@ -29,7 +30,9 @@ class MailRuleAdmin(admin.ModelAdmin):
|
||||
('filter_from',
|
||||
'filter_subject',
|
||||
'filter_body',
|
||||
'maximum_age')
|
||||
'filter_attachment_filename',
|
||||
'maximum_age',
|
||||
'attachment_type')
|
||||
}),
|
||||
(_("Actions"), {
|
||||
'description':
|
||||
|
@ -1,6 +1,7 @@
|
||||
import os
|
||||
import tempfile
|
||||
from datetime import timedelta, date
|
||||
from fnmatch import fnmatch
|
||||
|
||||
import magic
|
||||
import pathvalidate
|
||||
@ -263,7 +264,7 @@ class MailAccountHandler(LoggingMixin):
|
||||
|
||||
for att in message.attachments:
|
||||
|
||||
if not att.content_disposition == "attachment":
|
||||
if not att.content_disposition == "attachment" and rule.attachment_type == MailRule.ATTACHMENT_TYPE_ATTACHMENTS_ONLY: # NOQA: E501
|
||||
self.log(
|
||||
'debug',
|
||||
f"Rule {rule}: "
|
||||
@ -271,6 +272,10 @@ class MailAccountHandler(LoggingMixin):
|
||||
f"with content disposition {att.content_disposition}")
|
||||
continue
|
||||
|
||||
if rule.filter_attachment_filename:
|
||||
if not fnmatch(att.filename, rule.filter_attachment_filename):
|
||||
continue
|
||||
|
||||
title = self.get_title(message, att, rule)
|
||||
|
||||
# don't trust the content type of the attachment. Could be
|
||||
|
23
src/paperless_mail/migrations/0007_auto_20210106_0138.py
Normal file
23
src/paperless_mail/migrations/0007_auto_20210106_0138.py
Normal file
@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.1.5 on 2021-01-06 01:38
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('paperless_mail', '0006_auto_20210101_2340'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='mailrule',
|
||||
name='attachment_type',
|
||||
field=models.PositiveIntegerField(choices=[(1, 'Only process attachments.'), (2, "Process all files, including 'inline' attachments.")], default=1, help_text="Inline attachments include embedded images, so it's best to combine this option with a filename filter.", verbose_name='attachment type'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='mailrule',
|
||||
name='filter_attachment_filename',
|
||||
field=models.CharField(blank=True, help_text='Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive.', max_length=256, null=True, verbose_name='filter attachment filename'),
|
||||
),
|
||||
]
|
@ -60,6 +60,15 @@ class MailRule(models.Model):
|
||||
verbose_name = _("mail rule")
|
||||
verbose_name_plural = _("mail rules")
|
||||
|
||||
ATTACHMENT_TYPE_ATTACHMENTS_ONLY = 1
|
||||
ATTACHMENT_TYPE_EVERYTHING = 2
|
||||
|
||||
ATTACHMENT_TYPES = (
|
||||
(ATTACHMENT_TYPE_ATTACHMENTS_ONLY, _("Only process attachments.")),
|
||||
(ATTACHMENT_TYPE_EVERYTHING, _("Process all files, including 'inline' "
|
||||
"attachments."))
|
||||
)
|
||||
|
||||
ACTION_DELETE = 1
|
||||
ACTION_MOVE = 2
|
||||
ACTION_MARK_READ = 3
|
||||
@ -125,11 +134,27 @@ class MailRule(models.Model):
|
||||
_("filter body"),
|
||||
max_length=256, null=True, blank=True)
|
||||
|
||||
filter_attachment_filename = models.CharField(
|
||||
_("filter attachment filename"),
|
||||
max_length=256, null=True, blank=True,
|
||||
help_text=_("Only consume documents which entirely match this "
|
||||
"filename if specified. Wildcards such as *.pdf or "
|
||||
"*invoice* are allowed. Case insensitive.")
|
||||
)
|
||||
|
||||
maximum_age = models.PositiveIntegerField(
|
||||
_("maximum age"),
|
||||
default=30,
|
||||
help_text=_("Specified in days."))
|
||||
|
||||
attachment_type = models.PositiveIntegerField(
|
||||
_("attachment type"),
|
||||
choices=ATTACHMENT_TYPES,
|
||||
default=ATTACHMENT_TYPE_ATTACHMENTS_ONLY,
|
||||
help_text=_("Inline attachments include embedded images, so it's best "
|
||||
"to combine this option with a filename filter.")
|
||||
)
|
||||
|
||||
action = models.PositiveIntegerField(
|
||||
_("action"),
|
||||
choices=ACTIONS,
|
||||
|
@ -273,6 +273,49 @@ class TestMail(TestCase):
|
||||
args, kwargs = self.async_task.call_args
|
||||
self.assertEqual(kwargs['override_filename'], "f2.pdf")
|
||||
|
||||
def test_handle_inline_files(self):
|
||||
message = create_message()
|
||||
message.attachments = [
|
||||
create_attachment(filename="f1.pdf", content_disposition='inline'),
|
||||
create_attachment(filename="f2.pdf", content_disposition='attachment')
|
||||
]
|
||||
|
||||
account = MailAccount()
|
||||
rule = MailRule(assign_title_from=MailRule.TITLE_FROM_FILENAME, account=account, attachment_type=MailRule.ATTACHMENT_TYPE_EVERYTHING)
|
||||
|
||||
result = self.mail_account_handler.handle_message(message, rule)
|
||||
|
||||
self.assertEqual(result, 2)
|
||||
self.assertEqual(self.async_task.call_count, 2)
|
||||
|
||||
def test_filename_filter(self):
|
||||
message = create_message()
|
||||
message.attachments = [
|
||||
create_attachment(filename="f1.pdf"),
|
||||
create_attachment(filename="f2.pdf"),
|
||||
create_attachment(filename="f3.pdf"),
|
||||
create_attachment(filename="f2.png"),
|
||||
]
|
||||
|
||||
tests = [
|
||||
("*.pdf", ["f1.pdf", "f2.pdf", "f3.pdf"]),
|
||||
("f1.pdf", ["f1.pdf"]),
|
||||
("f1", []),
|
||||
("*", ["f1.pdf", "f2.pdf", "f3.pdf", "f2.png"]),
|
||||
("*.png", ["f2.png"]),
|
||||
]
|
||||
|
||||
for (pattern, matches) in tests:
|
||||
self.async_task.reset_mock()
|
||||
account = MailAccount()
|
||||
rule = MailRule(assign_title_from=MailRule.TITLE_FROM_FILENAME, account=account, filter_attachment_filename=pattern)
|
||||
|
||||
result = self.mail_account_handler.handle_message(message, rule)
|
||||
|
||||
self.assertEqual(result, len(matches))
|
||||
filenames = [a[1]['override_filename'] for a in self.async_task.call_args_list]
|
||||
self.assertCountEqual(filenames, matches)
|
||||
|
||||
def test_handle_mail_account_mark_read(self):
|
||||
|
||||
account = MailAccount.objects.create(name="test", imap_server="", username="admin", password="secret")
|
||||
|
Loading…
x
Reference in New Issue
Block a user