mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
Merge branch 'dev' into feature-permissions
This commit is contained in:
commit
a4d96061de
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@ -149,11 +149,11 @@ jobs:
|
||||
name: Tests
|
||||
run: |
|
||||
cd src/
|
||||
pipenv run pytest -rfEp
|
||||
pipenv run pytest -ra
|
||||
-
|
||||
name: Get changed files
|
||||
id: changed-files-specific
|
||||
uses: tj-actions/changed-files@v34
|
||||
uses: tj-actions/changed-files@v35
|
||||
with:
|
||||
files: |
|
||||
src/**
|
||||
|
4
Pipfile
4
Pipfile
@ -76,17 +76,13 @@ channels-redis = "==3.4.1"
|
||||
[dev-packages]
|
||||
coveralls = "*"
|
||||
factory-boy = "*"
|
||||
pycodestyle = "*"
|
||||
pytest = "*"
|
||||
pytest-cov = "*"
|
||||
pytest-django = "*"
|
||||
pytest-env = "*"
|
||||
pytest-sugar = "*"
|
||||
pytest-xdist = "*"
|
||||
tox = "*"
|
||||
black = "*"
|
||||
pre-commit = "*"
|
||||
sphinx-autobuild = "*"
|
||||
myst-parser = "*"
|
||||
imagehash = "*"
|
||||
mkdocs-material = "*"
|
||||
|
274
Pipfile.lock
generated
274
Pipfile.lock
generated
@ -1962,13 +1962,6 @@
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"alabaster": {
|
||||
"hashes": [
|
||||
"sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359",
|
||||
"sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"
|
||||
],
|
||||
"version": "==0.7.12"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836",
|
||||
@ -1977,14 +1970,6 @@
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==22.2.0"
|
||||
},
|
||||
"babel": {
|
||||
"hashes": [
|
||||
"sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe",
|
||||
"sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2.11.0"
|
||||
},
|
||||
"black": {
|
||||
"hashes": [
|
||||
"sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320",
|
||||
@ -2003,14 +1988,6 @@
|
||||
"index": "pypi",
|
||||
"version": "==22.12.0"
|
||||
},
|
||||
"cachetools": {
|
||||
"hashes": [
|
||||
"sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757",
|
||||
"sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"
|
||||
],
|
||||
"markers": "python_version ~= '3.7'",
|
||||
"version": "==5.2.0"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3",
|
||||
@ -2027,14 +2004,6 @@
|
||||
"markers": "python_full_version >= '3.6.1'",
|
||||
"version": "==3.3.1"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:0d62712b956bc154f85fb0a266e2a3c5913c2967e00348701b32411d6def31e5",
|
||||
"sha256:362777fb014af596ad31334fde1e8c327dfdb076e1960d1694662d46a6917ab9"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==5.1.0"
|
||||
},
|
||||
"charset-normalizer": {
|
||||
"hashes": [
|
||||
"sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
|
||||
@ -2136,14 +2105,6 @@
|
||||
],
|
||||
"version": "==0.6.2"
|
||||
},
|
||||
"docutils": {
|
||||
"hashes": [
|
||||
"sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6",
|
||||
"sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==0.19"
|
||||
},
|
||||
"exceptiongroup": {
|
||||
"hashes": [
|
||||
"sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e",
|
||||
@ -2215,14 +2176,6 @@
|
||||
"index": "pypi",
|
||||
"version": "==4.3.1"
|
||||
},
|
||||
"imagesize": {
|
||||
"hashes": [
|
||||
"sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b",
|
||||
"sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.4.1"
|
||||
},
|
||||
"iniconfig": {
|
||||
"hashes": [
|
||||
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
|
||||
@ -2238,13 +2191,6 @@
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==3.1.2"
|
||||
},
|
||||
"livereload": {
|
||||
"hashes": [
|
||||
"sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869",
|
||||
"sha256:ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4"
|
||||
],
|
||||
"version": "==2.6.3"
|
||||
},
|
||||
"markdown": {
|
||||
"hashes": [
|
||||
"sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874",
|
||||
@ -2253,14 +2199,6 @@
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.3.7"
|
||||
},
|
||||
"markdown-it-py": {
|
||||
"hashes": [
|
||||
"sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27",
|
||||
"sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==2.1.0"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
"sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003",
|
||||
@ -2307,22 +2245,6 @@
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==2.1.1"
|
||||
},
|
||||
"mdit-py-plugins": {
|
||||
"hashes": [
|
||||
"sha256:36d08a29def19ec43acdcd8ba471d3ebab132e7879d442760d963f19913e04b9",
|
||||
"sha256:5cfd7e7ac582a594e23ba6546a2f406e94e42eb33ae596d0734781261c251260"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==0.3.3"
|
||||
},
|
||||
"mdurl": {
|
||||
"hashes": [
|
||||
"sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8",
|
||||
"sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==0.1.2"
|
||||
},
|
||||
"mergedeep": {
|
||||
"hashes": [
|
||||
"sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8",
|
||||
@ -2362,14 +2284,6 @@
|
||||
],
|
||||
"version": "==0.4.3"
|
||||
},
|
||||
"myst-parser": {
|
||||
"hashes": [
|
||||
"sha256:61b275b85d9f58aa327f370913ae1bec26ebad372cc99f3ab85c8ec3ee8d9fb8",
|
||||
"sha256:79317f4bb2c13053dd6e64f9da1ba1da6cd9c40c8a430c447a7b146a594c246d"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.18.1"
|
||||
},
|
||||
"nodeenv": {
|
||||
"hashes": [
|
||||
"sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e",
|
||||
@ -2519,14 +2433,6 @@
|
||||
"index": "pypi",
|
||||
"version": "==2.21.0"
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
"sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053",
|
||||
"sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.10.0"
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
"sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1",
|
||||
@ -2543,14 +2449,6 @@
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==9.9"
|
||||
},
|
||||
"pyproject-api": {
|
||||
"hashes": [
|
||||
"sha256:093c047d192ceadcab7afd6b501276bf2ce44adf41cb9c313234518cddd20818",
|
||||
"sha256:155d5623453173b7b4e9379a3146ccef2d52335234eb2d03d6ba730e7dad179c"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.2.1"
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71",
|
||||
@ -2607,13 +2505,6 @@
|
||||
"index": "pypi",
|
||||
"version": "==2.8.2"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:7ccfae7b4b2c067464a6733c6261673fdb8fd1be905460396b97a073e9fa683a",
|
||||
"sha256:93007def75ae22f7cd991c84e02d434876818661f8df9ad5df9e950ff4e52cfd"
|
||||
],
|
||||
"version": "==2022.7"
|
||||
},
|
||||
"pywavelets": {
|
||||
"hashes": [
|
||||
"sha256:030670a213ee8fefa56f6387b0c8e7d970c7f7ad6850dc048bd7c89364771b9b",
|
||||
@ -2698,6 +2589,100 @@
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.1"
|
||||
},
|
||||
"regex": {
|
||||
"hashes": [
|
||||
"sha256:052b670fafbe30966bbe5d025e90b2a491f85dfe5b2583a163b5e60a85a321ad",
|
||||
"sha256:0653d012b3bf45f194e5e6a41df9258811ac8fc395579fa82958a8b76286bea4",
|
||||
"sha256:0a069c8483466806ab94ea9068c34b200b8bfc66b6762f45a831c4baaa9e8cdd",
|
||||
"sha256:0cf0da36a212978be2c2e2e2d04bdff46f850108fccc1851332bcae51c8907cc",
|
||||
"sha256:131d4be09bea7ce2577f9623e415cab287a3c8e0624f778c1d955ec7c281bd4d",
|
||||
"sha256:144486e029793a733e43b2e37df16a16df4ceb62102636ff3db6033994711066",
|
||||
"sha256:1ddf14031a3882f684b8642cb74eea3af93a2be68893901b2b387c5fd92a03ec",
|
||||
"sha256:1eba476b1b242620c266edf6325b443a2e22b633217a9835a52d8da2b5c051f9",
|
||||
"sha256:20f61c9944f0be2dc2b75689ba409938c14876c19d02f7585af4460b6a21403e",
|
||||
"sha256:22960019a842777a9fa5134c2364efaed5fbf9610ddc5c904bd3a400973b0eb8",
|
||||
"sha256:22e7ebc231d28393dfdc19b185d97e14a0f178bedd78e85aad660e93b646604e",
|
||||
"sha256:23cbb932cc53a86ebde0fb72e7e645f9a5eec1a5af7aa9ce333e46286caef783",
|
||||
"sha256:29c04741b9ae13d1e94cf93fca257730b97ce6ea64cfe1eba11cf9ac4e85afb6",
|
||||
"sha256:2bde29cc44fa81c0a0c8686992c3080b37c488df167a371500b2a43ce9f026d1",
|
||||
"sha256:2cdc55ca07b4e70dda898d2ab7150ecf17c990076d3acd7a5f3b25cb23a69f1c",
|
||||
"sha256:370f6e97d02bf2dd20d7468ce4f38e173a124e769762d00beadec3bc2f4b3bc4",
|
||||
"sha256:395161bbdbd04a8333b9ff9763a05e9ceb4fe210e3c7690f5e68cedd3d65d8e1",
|
||||
"sha256:44136355e2f5e06bf6b23d337a75386371ba742ffa771440b85bed367c1318d1",
|
||||
"sha256:44a6c2f6374e0033873e9ed577a54a3602b4f609867794c1a3ebba65e4c93ee7",
|
||||
"sha256:4919899577ba37f505aaebdf6e7dc812d55e8f097331312db7f1aab18767cce8",
|
||||
"sha256:4b4b1fe58cd102d75ef0552cf17242705ce0759f9695334a56644ad2d83903fe",
|
||||
"sha256:4bdd56ee719a8f751cf5a593476a441c4e56c9b64dc1f0f30902858c4ef8771d",
|
||||
"sha256:4bf41b8b0a80708f7e0384519795e80dcb44d7199a35d52c15cc674d10b3081b",
|
||||
"sha256:4cac3405d8dda8bc6ed499557625585544dd5cbf32072dcc72b5a176cb1271c8",
|
||||
"sha256:4fe7fda2fe7c8890d454f2cbc91d6c01baf206fbc96d89a80241a02985118c0c",
|
||||
"sha256:50921c140561d3db2ab9f5b11c5184846cde686bb5a9dc64cae442926e86f3af",
|
||||
"sha256:5217c25229b6a85049416a5c1e6451e9060a1edcf988641e309dbe3ab26d3e49",
|
||||
"sha256:5352bea8a8f84b89d45ccc503f390a6be77917932b1c98c4cdc3565137acc714",
|
||||
"sha256:542e3e306d1669b25936b64917285cdffcd4f5c6f0247636fec037187bd93542",
|
||||
"sha256:543883e3496c8b6d58bd036c99486c3c8387c2fc01f7a342b760c1ea3158a318",
|
||||
"sha256:586b36ebda81e6c1a9c5a5d0bfdc236399ba6595e1397842fd4a45648c30f35e",
|
||||
"sha256:597f899f4ed42a38df7b0e46714880fb4e19a25c2f66e5c908805466721760f5",
|
||||
"sha256:5a260758454580f11dd8743fa98319bb046037dfab4f7828008909d0aa5292bc",
|
||||
"sha256:5aefb84a301327ad115e9d346c8e2760009131d9d4b4c6b213648d02e2abe144",
|
||||
"sha256:5e6a5567078b3eaed93558842346c9d678e116ab0135e22eb72db8325e90b453",
|
||||
"sha256:5ff525698de226c0ca743bfa71fc6b378cda2ddcf0d22d7c37b1cc925c9650a5",
|
||||
"sha256:61edbca89aa3f5ef7ecac8c23d975fe7261c12665f1d90a6b1af527bba86ce61",
|
||||
"sha256:659175b2144d199560d99a8d13b2228b85e6019b6e09e556209dfb8c37b78a11",
|
||||
"sha256:6a9a19bea8495bb419dc5d38c4519567781cd8d571c72efc6aa959473d10221a",
|
||||
"sha256:6b30bddd61d2a3261f025ad0f9ee2586988c6a00c780a2fb0a92cea2aa702c54",
|
||||
"sha256:6ffd55b5aedc6f25fd8d9f905c9376ca44fcf768673ffb9d160dd6f409bfda73",
|
||||
"sha256:702d8fc6f25bbf412ee706bd73019da5e44a8400861dfff7ff31eb5b4a1276dc",
|
||||
"sha256:74bcab50a13960f2a610cdcd066e25f1fd59e23b69637c92ad470784a51b1347",
|
||||
"sha256:75f591b2055523fc02a4bbe598aa867df9e953255f0b7f7715d2a36a9c30065c",
|
||||
"sha256:763b64853b0a8f4f9cfb41a76a4a85a9bcda7fdda5cb057016e7706fde928e66",
|
||||
"sha256:76c598ca73ec73a2f568e2a72ba46c3b6c8690ad9a07092b18e48ceb936e9f0c",
|
||||
"sha256:78d680ef3e4d405f36f0d6d1ea54e740366f061645930072d39bca16a10d8c93",
|
||||
"sha256:7b280948d00bd3973c1998f92e22aa3ecb76682e3a4255f33e1020bd32adf443",
|
||||
"sha256:7db345956ecce0c99b97b042b4ca7326feeec6b75facd8390af73b18e2650ffc",
|
||||
"sha256:7dbdce0c534bbf52274b94768b3498abdf675a691fec5f751b6057b3030f34c1",
|
||||
"sha256:7ef6b5942e6bfc5706301a18a62300c60db9af7f6368042227ccb7eeb22d0892",
|
||||
"sha256:7f5a3ffc731494f1a57bd91c47dc483a1e10048131ffb52d901bfe2beb6102e8",
|
||||
"sha256:8a45b6514861916c429e6059a55cf7db74670eaed2052a648e3e4d04f070e001",
|
||||
"sha256:8ad241da7fac963d7573cc67a064c57c58766b62a9a20c452ca1f21050868dfa",
|
||||
"sha256:8b0886885f7323beea6f552c28bff62cbe0983b9fbb94126531693ea6c5ebb90",
|
||||
"sha256:8ca88da1bd78990b536c4a7765f719803eb4f8f9971cc22d6ca965c10a7f2c4c",
|
||||
"sha256:8e0caeff18b96ea90fc0eb6e3bdb2b10ab5b01a95128dfeccb64a7238decf5f0",
|
||||
"sha256:957403a978e10fb3ca42572a23e6f7badff39aa1ce2f4ade68ee452dc6807692",
|
||||
"sha256:9af69f6746120998cd9c355e9c3c6aec7dff70d47247188feb4f829502be8ab4",
|
||||
"sha256:9c94f7cc91ab16b36ba5ce476f1904c91d6c92441f01cd61a8e2729442d6fcf5",
|
||||
"sha256:a37d51fa9a00d265cf73f3de3930fa9c41548177ba4f0faf76e61d512c774690",
|
||||
"sha256:a3a98921da9a1bf8457aeee6a551948a83601689e5ecdd736894ea9bbec77e83",
|
||||
"sha256:a3c1ebd4ed8e76e886507c9eddb1a891673686c813adf889b864a17fafcf6d66",
|
||||
"sha256:a5f9505efd574d1e5b4a76ac9dd92a12acb2b309551e9aa874c13c11caefbe4f",
|
||||
"sha256:a8ff454ef0bb061e37df03557afda9d785c905dab15584860f982e88be73015f",
|
||||
"sha256:a9d0b68ac1743964755ae2d89772c7e6fb0118acd4d0b7464eaf3921c6b49dd4",
|
||||
"sha256:aa62a07ac93b7cb6b7d0389d8ef57ffc321d78f60c037b19dfa78d6b17c928ee",
|
||||
"sha256:ac741bf78b9bb432e2d314439275235f41656e189856b11fb4e774d9f7246d81",
|
||||
"sha256:ae1e96785696b543394a4e3f15f3f225d44f3c55dafe3f206493031419fedf95",
|
||||
"sha256:b683e5fd7f74fb66e89a1ed16076dbab3f8e9f34c18b1979ded614fe10cdc4d9",
|
||||
"sha256:b7a8b43ee64ca8f4befa2bea4083f7c52c92864d8518244bfa6e88c751fa8fff",
|
||||
"sha256:b8e38472739028e5f2c3a4aded0ab7eadc447f0d84f310c7a8bb697ec417229e",
|
||||
"sha256:bfff48c7bd23c6e2aec6454aaf6edc44444b229e94743b34bdcdda2e35126cf5",
|
||||
"sha256:c14b63c9d7bab795d17392c7c1f9aaabbffd4cf4387725a0ac69109fb3b550c6",
|
||||
"sha256:c27cc1e4b197092e50ddbf0118c788d9977f3f8f35bfbbd3e76c1846a3443df7",
|
||||
"sha256:c28d3309ebd6d6b2cf82969b5179bed5fefe6142c70f354ece94324fa11bf6a1",
|
||||
"sha256:c670f4773f2f6f1957ff8a3962c7dd12e4be54d05839b216cb7fd70b5a1df394",
|
||||
"sha256:ce6910b56b700bea7be82c54ddf2e0ed792a577dfaa4a76b9af07d550af435c6",
|
||||
"sha256:d0213671691e341f6849bf33cd9fad21f7b1cb88b89e024f33370733fec58742",
|
||||
"sha256:d03fe67b2325cb3f09be029fd5da8df9e6974f0cde2c2ac6a79d2634e791dd57",
|
||||
"sha256:d0e5af9a9effb88535a472e19169e09ce750c3d442fb222254a276d77808620b",
|
||||
"sha256:d243b36fbf3d73c25e48014961e83c19c9cc92530516ce3c43050ea6276a2ab7",
|
||||
"sha256:d26166acf62f731f50bdd885b04b38828436d74e8e362bfcb8df221d868b5d9b",
|
||||
"sha256:d403d781b0e06d2922435ce3b8d2376579f0c217ae491e273bab8d092727d244",
|
||||
"sha256:d8716f82502997b3d0895d1c64c3b834181b1eaca28f3f6336a71777e437c2af",
|
||||
"sha256:e4f781ffedd17b0b834c8731b75cce2639d5a8afe961c1e58ee7f1f20b3af185",
|
||||
"sha256:e613a98ead2005c4ce037c7b061f2409a1a4e45099edb0ef3200ee26ed2a69a8",
|
||||
"sha256:ef4163770525257876f10e8ece1cf25b71468316f61451ded1a6f44273eedeb5"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2022.10.31"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983",
|
||||
@ -2751,77 +2736,6 @@
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.16.0"
|
||||
},
|
||||
"snowballstemmer": {
|
||||
"hashes": [
|
||||
"sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1",
|
||||
"sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"
|
||||
],
|
||||
"version": "==2.2.0"
|
||||
},
|
||||
"sphinx": {
|
||||
"hashes": [
|
||||
"sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d",
|
||||
"sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==5.3.0"
|
||||
},
|
||||
"sphinx-autobuild": {
|
||||
"hashes": [
|
||||
"sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac",
|
||||
"sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2021.3.14"
|
||||
},
|
||||
"sphinxcontrib-applehelp": {
|
||||
"hashes": [
|
||||
"sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a",
|
||||
"sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"sphinxcontrib-devhelp": {
|
||||
"hashes": [
|
||||
"sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e",
|
||||
"sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"sphinxcontrib-htmlhelp": {
|
||||
"hashes": [
|
||||
"sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07",
|
||||
"sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2.0.0"
|
||||
},
|
||||
"sphinxcontrib-jsmath": {
|
||||
"hashes": [
|
||||
"sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178",
|
||||
"sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==1.0.1"
|
||||
},
|
||||
"sphinxcontrib-qthelp": {
|
||||
"hashes": [
|
||||
"sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72",
|
||||
"sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==1.0.3"
|
||||
},
|
||||
"sphinxcontrib-serializinghtml": {
|
||||
"hashes": [
|
||||
"sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd",
|
||||
"sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==1.1.5"
|
||||
},
|
||||
"termcolor": {
|
||||
"hashes": [
|
||||
"sha256:67cee2009adc6449c650f6bcf3bdeed00c8ba53a8cda5362733c53e0a39fb70b",
|
||||
|
@ -16,6 +16,7 @@
|
||||
"i18n": {
|
||||
"sourceLocale": "en-US",
|
||||
"locales": {
|
||||
"ar-AR": "src/locale/messages.ar_AR.xlf",
|
||||
"be-BY": "src/locale/messages.be_BY.xlf",
|
||||
"cs-CZ": "src/locale/messages.cs_CZ.xlf",
|
||||
"da-DK": "src/locale/messages.da_DK.xlf",
|
||||
|
@ -44,7 +44,7 @@ describe('document-detail', () => {
|
||||
})
|
||||
|
||||
cy.viewport(1024, 1024)
|
||||
cy.visit('/documents/1/')
|
||||
cy.visit('/documents/1/').wait('@ui-settings')
|
||||
})
|
||||
|
||||
it('should activate / deactivate save button when changes are saved', () => {
|
||||
@ -66,8 +66,21 @@ describe('document-detail', () => {
|
||||
cy.contains('You have unsaved changes').should('not.exist')
|
||||
})
|
||||
|
||||
it('should show a mobile preview', () => {
|
||||
cy.viewport(440, 1000)
|
||||
cy.get('a')
|
||||
.contains('Preview')
|
||||
.scrollIntoView({ offset: { top: 150, left: 0 } })
|
||||
.click()
|
||||
cy.get('pdf-viewer').should('be.visible')
|
||||
})
|
||||
|
||||
it('should show a list of comments', () => {
|
||||
cy.wait(1000).get('a').contains('Comments').click().wait(1000)
|
||||
cy.wait(1000)
|
||||
.get('a')
|
||||
.contains('Comments')
|
||||
.click({ force: true })
|
||||
.wait(1000)
|
||||
cy.get('app-document-comments').find('.card').its('length').should('eq', 3)
|
||||
})
|
||||
|
||||
|
@ -52,6 +52,10 @@ describe('documents-list', () => {
|
||||
|
||||
req.reply(response)
|
||||
})
|
||||
|
||||
cy.intercept('http://localhost:8000/api/documents/selection_data/', {
|
||||
fixture: 'documents/selection_data.json',
|
||||
}).as('selection-data')
|
||||
})
|
||||
|
||||
cy.viewport(1280, 1024)
|
||||
@ -76,6 +80,28 @@ describe('documents-list', () => {
|
||||
cy.get('app-document-card-large')
|
||||
})
|
||||
|
||||
it('should show partial tag selection', () => {
|
||||
cy.get('app-document-card-small:nth-child(1)').click()
|
||||
cy.get('app-document-card-small:nth-child(4)').click()
|
||||
cy.get('app-bulk-editor button')
|
||||
.contains('Tags')
|
||||
.click()
|
||||
.wait('@selection-data')
|
||||
cy.get('svg.bi-dash').should('be.visible')
|
||||
cy.get('svg.bi-check').should('be.visible')
|
||||
})
|
||||
|
||||
it('should allow bulk removal', () => {
|
||||
cy.get('app-document-card-small:nth-child(1)').click()
|
||||
cy.get('app-document-card-small:nth-child(4)').click()
|
||||
cy.get('app-bulk-editor').within(() => {
|
||||
cy.get('button').contains('Tags').click().wait('@selection-data')
|
||||
cy.get('button').contains('Another Sample Tag').click()
|
||||
cy.get('button').contains('Apply').click()
|
||||
})
|
||||
cy.contains('operation will remove the tag')
|
||||
})
|
||||
|
||||
it('should filter tags', () => {
|
||||
cy.get('app-filter-editor app-filterable-dropdown[title="Tags"]').within(
|
||||
() => {
|
||||
|
@ -35,16 +35,58 @@ describe('settings', () => {
|
||||
req.reply(response)
|
||||
}
|
||||
).as('savedViews')
|
||||
})
|
||||
|
||||
cy.intercept('http://localhost:8000/api/mail_accounts/*', {
|
||||
fixture: 'mail_accounts/mail_accounts.json',
|
||||
})
|
||||
cy.intercept('http://localhost:8000/api/mail_rules/*', {
|
||||
fixture: 'mail_rules/mail_rules.json',
|
||||
}).as('mailRules')
|
||||
cy.intercept('http://localhost:8000/api/tasks/', {
|
||||
fixture: 'tasks/tasks.json',
|
||||
})
|
||||
this.newMailAccounts = []
|
||||
|
||||
cy.intercept(
|
||||
'POST',
|
||||
'http://localhost:8000/api/mail_accounts/',
|
||||
(req) => {
|
||||
const newRule = req.body
|
||||
newRule.id = 3
|
||||
this.newMailAccounts.push(newRule) // store this for later
|
||||
req.reply({ result: 'OK' })
|
||||
}
|
||||
).as('saveAccount')
|
||||
|
||||
cy.fixture('mail_accounts/mail_accounts.json').then(
|
||||
(mailAccountsJson) => {
|
||||
cy.intercept(
|
||||
'GET',
|
||||
'http://localhost:8000/api/mail_accounts/*',
|
||||
(req) => {
|
||||
console.log(req, this.newMailAccounts)
|
||||
|
||||
let response = { ...mailAccountsJson }
|
||||
if (this.newMailAccounts.length) {
|
||||
response.results = response.results.concat(this.newMailAccounts)
|
||||
}
|
||||
|
||||
req.reply(response)
|
||||
}
|
||||
).as('getAccounts')
|
||||
}
|
||||
)
|
||||
|
||||
this.newMailRules = []
|
||||
|
||||
cy.intercept('POST', 'http://localhost:8000/api/mail_rules/', (req) => {
|
||||
const newRule = req.body
|
||||
newRule.id = 2
|
||||
this.newMailRules.push(newRule) // store this for later
|
||||
req.reply({ result: 'OK' })
|
||||
}).as('saveRule')
|
||||
|
||||
cy.fixture('mail_rules/mail_rules.json').then((mailRulesJson) => {
|
||||
cy.intercept('GET', 'http://localhost:8000/api/mail_rules/*', (req) => {
|
||||
let response = { ...mailRulesJson }
|
||||
if (this.newMailRules.length) {
|
||||
response.results = response.results.concat(this.newMailRules)
|
||||
}
|
||||
|
||||
req.reply(response)
|
||||
}).as('getRules')
|
||||
})
|
||||
|
||||
cy.fixture('documents/documents.json').then((documentsJson) => {
|
||||
@ -99,4 +141,42 @@ describe('settings', () => {
|
||||
cy.visit('/dashboard')
|
||||
cy.get('app-saved-view-widget').contains('Inbox').should('not.exist')
|
||||
})
|
||||
|
||||
it('should show a list of mail accounts & rules & support creation', () => {
|
||||
cy.contains('a', 'Mail').click()
|
||||
cy.get('app-settings .tab-content ul li').its('length').should('eq', 5) // 2 headers, 2 accounts, 1 rule
|
||||
cy.contains('button', 'Add Account').click()
|
||||
cy.contains('Create new mail account')
|
||||
cy.get('app-input-text[formcontrolname="name"]').type(
|
||||
'Example Mail Account'
|
||||
)
|
||||
cy.get('app-input-text[formcontrolname="imap_server"]').type(
|
||||
'mail.example.com'
|
||||
)
|
||||
cy.get('app-input-text[formcontrolname="imap_port"]').type('993')
|
||||
cy.get('app-input-text[formcontrolname="username"]').type('username')
|
||||
cy.get('app-input-password[formcontrolname="password"]').type('pass')
|
||||
cy.contains('app-mail-account-edit-dialog button', 'Save')
|
||||
.click()
|
||||
.wait('@saveAccount')
|
||||
.wait('@getAccounts')
|
||||
cy.contains('Saved account')
|
||||
|
||||
cy.wait(1000)
|
||||
cy.contains('button', 'Add Rule').click()
|
||||
cy.contains('Create new mail rule')
|
||||
cy.get('app-input-text[formcontrolname="name"]').type('Example Rule')
|
||||
cy.get('app-input-select[formcontrolname="account"]').type('Example{enter}')
|
||||
cy.get('app-input-number[formcontrolname="maximum_age"]').type('30')
|
||||
cy.get('app-input-text[formcontrolname="filter_subject"]').type(
|
||||
'[paperless]'
|
||||
)
|
||||
cy.contains('app-mail-rule-edit-dialog button', 'Save')
|
||||
.click()
|
||||
.wait('@saveRule')
|
||||
.wait('@getRules')
|
||||
cy.contains('Saved rule').wait(1000)
|
||||
|
||||
cy.get('app-settings .tab-content ul li').its('length').should('eq', 7)
|
||||
})
|
||||
})
|
||||
|
293
src-ui/cypress/fixtures/documents/selection_data.json
Normal file
293
src-ui/cypress/fixtures/documents/selection_data.json
Normal file
@ -0,0 +1,293 @@
|
||||
{
|
||||
"selected_correspondents": [
|
||||
{
|
||||
"id": 62,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 75,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 55,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 56,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 73,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 58,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 44,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 42,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 74,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 54,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 71,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 68,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 82,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 41,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 51,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 46,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 40,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 43,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 80,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 70,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 52,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 67,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 53,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 63,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 35,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 45,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 38,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 79,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 48,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 72,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 78,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 39,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 57,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 61,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 81,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 77,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 69,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 36,
|
||||
"document_count": 3
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 50,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 49,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 60,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 47,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 66,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 37,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 59,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 76,
|
||||
"document_count": 0
|
||||
}
|
||||
],
|
||||
"selected_tags": [
|
||||
{
|
||||
"id": 4,
|
||||
"document_count": 2
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"document_count": 1
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"document_count": 1
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"document_count": 0
|
||||
}
|
||||
],
|
||||
"selected_document_types": [
|
||||
{
|
||||
"id": 4,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"document_count": 2
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"document_count": 0
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"document_count": 1
|
||||
}
|
||||
],
|
||||
"selected_storage_paths": []
|
||||
}
|
@ -23,7 +23,8 @@
|
||||
"assign_correspondent": 2,
|
||||
"assign_document_type": null,
|
||||
"order": 0,
|
||||
"attachment_type": 2
|
||||
"attachment_type": 2,
|
||||
"consumption_scope": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
beforeEach(() => {
|
||||
cy.intercept('http://localhost:8000/api/ui_settings/', {
|
||||
fixture: 'ui_settings/settings.json',
|
||||
})
|
||||
}).as('ui-settings')
|
||||
|
||||
cy.intercept('http://localhost:8000/api/users/*', {
|
||||
fixture: 'users/users.json',
|
||||
@ -37,6 +37,10 @@ beforeEach(() => {
|
||||
fixture: 'storage_paths/storage_paths.json',
|
||||
})
|
||||
|
||||
cy.intercept('http://localhost:8000/api/tasks/', {
|
||||
fixture: 'tasks/tasks.json',
|
||||
})
|
||||
|
||||
cy.intercept('http://localhost:8000/api/documents/1/metadata/', {
|
||||
fixture: 'documents/1/metadata.json',
|
||||
})
|
||||
|
@ -89,6 +89,7 @@ import { PermissionsGroupComponent } from './components/common/input/permissions
|
||||
import { IfOwnerDirective } from './directives/if-owner.directive'
|
||||
import { IfObjectPermissionsDirective } from './directives/if-object-permissions.directive'
|
||||
|
||||
import localeAr from '@angular/common/locales/ar'
|
||||
import localeBe from '@angular/common/locales/be'
|
||||
import localeCs from '@angular/common/locales/cs'
|
||||
import localeDa from '@angular/common/locales/da'
|
||||
@ -111,6 +112,7 @@ import localeZh from '@angular/common/locales/zh'
|
||||
import { PermissionsDialogComponent } from './components/common/permissions-dialog/permissions-dialog.component'
|
||||
import { PermissionsFormComponent } from './components/common/input/permissions/permissions-form/permissions-form.component'
|
||||
|
||||
registerLocaleData(localeAr)
|
||||
registerLocaleData(localeBe)
|
||||
registerLocaleData(localeCs)
|
||||
registerLocaleData(localeDa)
|
||||
|
@ -149,7 +149,7 @@
|
||||
|
||||
<li [ngbNavItem]="4" class="d-md-none">
|
||||
<a ngbNavLink>Preview</a>
|
||||
<ng-template ngbNavContent *ngIf="pdfPreview.offsetParent === undefined">
|
||||
<ng-template ngbNavContent *ngIf="!pdfPreview.offsetParent">
|
||||
<div class="position-relative">
|
||||
<ng-container *ngIf="getContentType() === 'application/pdf'">
|
||||
<div class="preview-sticky pdf-viewer-container" *ngIf="!useNativePdfViewer ; else nativePdfViewer">
|
||||
@ -191,9 +191,9 @@
|
||||
<div [ngbNavOutlet]="nav" class="mt-2"></div>
|
||||
|
||||
<ng-container>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="discard()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) === false">Discard</button>
|
||||
<button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) === false || error">Save & next</button>
|
||||
<button type="submit" class="btn btn-primary" *appIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) === false || error">Save</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="discard()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Discard</button>
|
||||
<button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true || error">Save & next</button>
|
||||
<button type="submit" class="btn btn-primary" *appIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true || error">Save</button>
|
||||
</ng-container>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -25,7 +25,13 @@
|
||||
</h5>
|
||||
</div>
|
||||
<p class="card-text">
|
||||
<span *ngIf="document.__search_hit__" [innerHtml]="document.__search_hit__.highlights"></span>
|
||||
<span *ngIf="document.__search_hit__ && document.__search_hit__.highlights" [innerHtml]="document.__search_hit__.highlights"></span>
|
||||
<span *ngIf="document.__search_hit__ && document.__search_hit__.comment_highlights">
|
||||
<svg width="1em" height="1em" fill="currentColor" class="me-2">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#chat-left-text"/>
|
||||
</svg>
|
||||
<span [innerHtml]="document.__search_hit__.comment_highlights"></span>
|
||||
</span>
|
||||
<span *ngIf="!document.__search_hit__" class="result-content">{{contentTrimmed}}</span>
|
||||
</p>
|
||||
|
||||
|
@ -10,6 +10,7 @@ export interface SearchHit {
|
||||
rank?: number
|
||||
|
||||
highlights?: string
|
||||
comment_highlights?: string
|
||||
}
|
||||
|
||||
export interface PaperlessDocument extends ObjectWithPermissions {
|
||||
|
@ -163,6 +163,12 @@ export class SettingsService {
|
||||
englishName: 'English (US)',
|
||||
dateInputFormat: 'mm/dd/yyyy',
|
||||
},
|
||||
{
|
||||
code: 'ar-ar',
|
||||
name: $localize`Arabic`,
|
||||
englishName: 'Arabic',
|
||||
dateInputFormat: 'yyyy-mm-dd',
|
||||
},
|
||||
{
|
||||
code: 'be-by',
|
||||
name: $localize`Belarusian`,
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@ from contextlib import contextmanager
|
||||
|
||||
from dateutil.parser import isoparse
|
||||
from django.conf import settings
|
||||
from documents.models import Comment
|
||||
from documents.models import Document
|
||||
from guardian.shortcuts import get_users_with_perms
|
||||
from whoosh import classify
|
||||
@ -50,6 +51,7 @@ def get_schema():
|
||||
path=TEXT(sortable=True),
|
||||
path_id=NUMERIC(),
|
||||
has_path=BOOLEAN(),
|
||||
comments=TEXT(),
|
||||
owner=TEXT(),
|
||||
owner_id=NUMERIC(),
|
||||
has_owner=BOOLEAN(),
|
||||
@ -95,6 +97,7 @@ def open_index_searcher():
|
||||
def update_document(writer, doc):
|
||||
tags = ",".join([t.name for t in doc.tags.all()])
|
||||
tags_ids = ",".join([str(t.id) for t in doc.tags.all()])
|
||||
comments = ",".join([str(c.comment) for c in Comment.objects.filter(document=doc)])
|
||||
users_with_perms = get_users_with_perms(
|
||||
doc,
|
||||
only_with_perms_in=["view_document"],
|
||||
@ -120,6 +123,7 @@ def update_document(writer, doc):
|
||||
path=doc.storage_path.name if doc.storage_path else None,
|
||||
path_id=doc.storage_path.id if doc.storage_path else None,
|
||||
has_path=doc.storage_path is not None,
|
||||
comments=comments,
|
||||
owner=doc.owner.username if doc.owner else None,
|
||||
owner_id=doc.owner.id if doc.owner else None,
|
||||
has_owner=doc.owner is not None,
|
||||
@ -276,7 +280,7 @@ class DelayedFullTextQuery(DelayedQuery):
|
||||
def _get_query(self):
|
||||
q_str = self.query_params["query"]
|
||||
qp = MultifieldParser(
|
||||
["content", "title", "correspondent", "tag", "type"],
|
||||
["content", "title", "correspondent", "tag", "type", "comments"],
|
||||
self.searcher.ixreader.schema,
|
||||
)
|
||||
qp.add_plugin(DateParserPlugin())
|
||||
|
@ -6,12 +6,12 @@ import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from functools import cache
|
||||
from typing import Iterator
|
||||
from typing import Match
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
|
||||
import magic
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from documents.loggers import LoggingMixin
|
||||
@ -45,11 +45,20 @@ DATE_REGEX = re.compile(
|
||||
logger = logging.getLogger("paperless.parsing")
|
||||
|
||||
|
||||
def is_mime_type_supported(mime_type) -> bool:
|
||||
@cache
|
||||
def is_mime_type_supported(mime_type: str) -> bool:
|
||||
"""
|
||||
Returns True if the mime type is supported, False otherwise
|
||||
"""
|
||||
return get_parser_class_for_mime_type(mime_type) is not None
|
||||
|
||||
|
||||
def get_default_file_extension(mime_type) -> str:
|
||||
@cache
|
||||
def get_default_file_extension(mime_type: str) -> str:
|
||||
"""
|
||||
Returns the default file extension for a mimetype, or
|
||||
an empty string if it could not be determined
|
||||
"""
|
||||
for response in document_consumer_declaration.send(None):
|
||||
parser_declaration = response[1]
|
||||
supported_mime_types = parser_declaration["mime_types"]
|
||||
@ -64,7 +73,12 @@ def get_default_file_extension(mime_type) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
def is_file_ext_supported(ext) -> bool:
|
||||
@cache
|
||||
def is_file_ext_supported(ext: str) -> bool:
|
||||
"""
|
||||
Returns True if the file extension is supported, False otherwise
|
||||
TODO: Investigate why this really exists, why not use mimetype
|
||||
"""
|
||||
if ext:
|
||||
return ext.lower() in get_supported_file_extensions()
|
||||
else:
|
||||
@ -79,11 +93,19 @@ def get_supported_file_extensions() -> Set[str]:
|
||||
|
||||
for mime_type in supported_mime_types:
|
||||
extensions.update(mimetypes.guess_all_extensions(mime_type))
|
||||
# Python's stdlib might be behind, so also add what the parser
|
||||
# says is the default extension
|
||||
# This makes image/webp supported on Python < 3.11
|
||||
extensions.add(supported_mime_types[mime_type])
|
||||
|
||||
return extensions
|
||||
|
||||
|
||||
def get_parser_class_for_mime_type(mime_type):
|
||||
def get_parser_class_for_mime_type(mime_type: str) -> Optional["DocumentParser"]:
|
||||
"""
|
||||
Returns the best parser (by weight) for the given mimetype or
|
||||
None if no parser exists
|
||||
"""
|
||||
|
||||
options = []
|
||||
|
||||
@ -103,16 +125,6 @@ def get_parser_class_for_mime_type(mime_type):
|
||||
return sorted(options, key=lambda _: _["weight"], reverse=True)[0]["parser"]
|
||||
|
||||
|
||||
def get_parser_class(path):
|
||||
"""
|
||||
Determine the appropriate parser class based on the file
|
||||
"""
|
||||
|
||||
mime_type = magic.from_file(path, mime=True)
|
||||
|
||||
return get_parser_class_for_mime_type(mime_type)
|
||||
|
||||
|
||||
def run_convert(
|
||||
input_file,
|
||||
output_file,
|
||||
|
@ -1,5 +1,3 @@
|
||||
from unittest import mock
|
||||
|
||||
from django.contrib.admin.sites import AdminSite
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
@ -35,7 +35,6 @@ from documents.models import SavedView
|
||||
from documents.models import StoragePath
|
||||
from documents.models import Tag
|
||||
from documents.models import Comment
|
||||
from documents.models import StoragePath
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
from paperless import version
|
||||
from rest_framework.test import APITestCase
|
||||
@ -484,7 +483,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
||||
self.assertNotIn(result["id"], seen_ids)
|
||||
seen_ids.append(result["id"])
|
||||
|
||||
response = self.client.get(f"/api/documents/?query=content&page=6&page_size=10")
|
||||
response = self.client.get("/api/documents/?query=content&page=6&page_size=10")
|
||||
results = response.data["results"]
|
||||
self.assertEqual(response.data["count"], 55)
|
||||
self.assertEqual(len(results), 5)
|
||||
@ -504,9 +503,9 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
||||
)
|
||||
index.update_document(writer, doc)
|
||||
|
||||
response = self.client.get(f"/api/documents/?query=content&page=0&page_size=10")
|
||||
response = self.client.get("/api/documents/?query=content&page=0&page_size=10")
|
||||
self.assertEqual(response.status_code, 404)
|
||||
response = self.client.get(f"/api/documents/?query=content&page=3&page_size=10")
|
||||
response = self.client.get("/api/documents/?query=content&page=3&page_size=10")
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
@mock.patch("documents.index.autocomplete")
|
||||
@ -1084,7 +1083,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
||||
self.assertEqual(meta["archive_size"], os.stat(archive_file).st_size)
|
||||
|
||||
def test_get_metadata_invalid_doc(self):
|
||||
response = self.client.get(f"/api/documents/34576/metadata/")
|
||||
response = self.client.get("/api/documents/34576/metadata/")
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_get_metadata_no_archive(self):
|
||||
@ -1149,7 +1148,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
||||
)
|
||||
|
||||
def test_get_suggestions_invalid_doc(self):
|
||||
response = self.client.get(f"/api/documents/34676/suggestions/")
|
||||
response = self.client.get("/api/documents/34676/suggestions/")
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
@mock.patch("documents.views.match_storage_paths")
|
||||
|
@ -401,7 +401,7 @@ class TestBarcode(DirectoriesMixin, TestCase):
|
||||
self.assertEqual(
|
||||
cm.output,
|
||||
[
|
||||
f"WARNING:paperless.barcodes:No pages to split on!",
|
||||
"WARNING:paperless.barcodes:No pages to split on!",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import textwrap
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from django.core.checks import Error
|
||||
|
@ -1,5 +1,6 @@
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
@ -27,6 +28,9 @@ def dummy_preprocess(content: str):
|
||||
|
||||
|
||||
class TestClassifier(DirectoriesMixin, TestCase):
|
||||
|
||||
SAMPLE_MODEL_FILE = os.path.join(os.path.dirname(__file__), "data", "model.pickle")
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.classifier = DocumentClassifier()
|
||||
@ -213,13 +217,14 @@ class TestClassifier(DirectoriesMixin, TestCase):
|
||||
# self.classifier.train()
|
||||
# self.classifier.save()
|
||||
|
||||
@override_settings(
|
||||
MODEL_FILE=os.path.join(os.path.dirname(__file__), "data", "model.pickle"),
|
||||
)
|
||||
def test_load_and_classify(self):
|
||||
# Generate test data, train and save to the model file
|
||||
# This ensures the model file sklearn version matches
|
||||
# and eliminates a warning
|
||||
shutil.copy(
|
||||
self.SAMPLE_MODEL_FILE,
|
||||
os.path.join(self.dirs.data_dir, "classification_model.pickle"),
|
||||
)
|
||||
self.generate_test_data()
|
||||
self.classifier.train()
|
||||
self.classifier.save()
|
||||
@ -230,9 +235,6 @@ class TestClassifier(DirectoriesMixin, TestCase):
|
||||
|
||||
self.assertCountEqual(new_classifier.predict_tags(self.doc2.content), [45, 12])
|
||||
|
||||
@override_settings(
|
||||
MODEL_FILE=os.path.join(os.path.dirname(__file__), "data", "model.pickle"),
|
||||
)
|
||||
@mock.patch("documents.classifier.pickle.load")
|
||||
def test_load_corrupt_file(self, patched_pickle_load):
|
||||
"""
|
||||
@ -243,6 +245,10 @@ class TestClassifier(DirectoriesMixin, TestCase):
|
||||
THEN:
|
||||
- The ClassifierModelCorruptError is raised
|
||||
"""
|
||||
shutil.copy(
|
||||
self.SAMPLE_MODEL_FILE,
|
||||
os.path.join(self.dirs.data_dir, "classification_model.pickle"),
|
||||
)
|
||||
# First load is the schema version
|
||||
patched_pickle_load.side_effect = [DocumentClassifier.FORMAT_VERSION, OSError()]
|
||||
|
||||
|
@ -4,7 +4,6 @@ import re
|
||||
import shutil
|
||||
import stat
|
||||
import tempfile
|
||||
from subprocess import CalledProcessError
|
||||
from unittest import mock
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
|
@ -9,7 +9,6 @@ from django.test import override_settings
|
||||
from django.test import TestCase
|
||||
from documents.parsers import parse_date
|
||||
from documents.parsers import parse_date_generator
|
||||
from paperless.settings import DATE_ORDER
|
||||
|
||||
|
||||
class TestDate(TestCase):
|
||||
|
@ -88,10 +88,10 @@ class TestArchiver(DirectoriesMixin, TestCase):
|
||||
mime_type="application/pdf",
|
||||
filename="document_01.pdf",
|
||||
)
|
||||
shutil.copy(sample_file, os.path.join(self.dirs.originals_dir, f"document.pdf"))
|
||||
shutil.copy(sample_file, os.path.join(self.dirs.originals_dir, "document.pdf"))
|
||||
shutil.copy(
|
||||
sample_file,
|
||||
os.path.join(self.dirs.originals_dir, f"document_01.pdf"),
|
||||
os.path.join(self.dirs.originals_dir, "document_01.pdf"),
|
||||
)
|
||||
|
||||
update_document_archive_file(doc2.pk)
|
||||
@ -150,7 +150,7 @@ class TestDecryptDocuments(TestCase):
|
||||
"samples",
|
||||
"documents",
|
||||
"thumbnails",
|
||||
f"0000004.webp.gpg",
|
||||
"0000004.webp.gpg",
|
||||
),
|
||||
os.path.join(thumb_dir, f"{doc.id:07}.webp.gpg"),
|
||||
)
|
||||
|
@ -5,10 +5,7 @@ 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 Correspondent
|
||||
from documents.models import Document
|
||||
from documents.models import DocumentType
|
||||
from documents.models import Tag
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
|
||||
|
||||
|
@ -7,7 +7,6 @@ from typing import Union
|
||||
from unittest import mock
|
||||
|
||||
from django.test import override_settings
|
||||
from documents.tests.test_migration_archive_files import thumbnail_path
|
||||
from documents.tests.utils import TestMigrations
|
||||
|
||||
|
||||
|
@ -1,14 +1,8 @@
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from tempfile import TemporaryDirectory
|
||||
from unittest import mock
|
||||
|
||||
from django.test import override_settings
|
||||
from django.test import TestCase
|
||||
from documents.parsers import DocumentParser
|
||||
from documents.parsers import get_default_file_extension
|
||||
from documents.parsers import get_parser_class
|
||||
from documents.parsers import get_parser_class_for_mime_type
|
||||
from documents.parsers import get_supported_file_extensions
|
||||
from documents.parsers import is_file_ext_supported
|
||||
@ -16,21 +10,18 @@ from paperless_tesseract.parsers import RasterisedDocumentParser
|
||||
from paperless_text.parsers import TextDocumentParser
|
||||
|
||||
|
||||
def fake_magic_from_file(file, mime=False):
|
||||
|
||||
if mime:
|
||||
if os.path.splitext(file)[1] == ".pdf":
|
||||
return "application/pdf"
|
||||
else:
|
||||
return "unknown"
|
||||
else:
|
||||
return "A verbose string that describes the contents of the file"
|
||||
|
||||
|
||||
@mock.patch("documents.parsers.magic.from_file", fake_magic_from_file)
|
||||
class TestParserDiscovery(TestCase):
|
||||
@mock.patch("documents.parsers.document_consumer_declaration.send")
|
||||
def test__get_parser_class_1_parser(self, m, *args):
|
||||
def test_get_parser_class_1_parser(self, m, *args):
|
||||
"""
|
||||
GIVEN:
|
||||
- Parser declared for a given mimetype
|
||||
WHEN:
|
||||
- Attempt to get parser for the mimetype
|
||||
THEN:
|
||||
- Declared parser class is returned
|
||||
"""
|
||||
|
||||
class DummyParser:
|
||||
pass
|
||||
|
||||
@ -45,10 +36,20 @@ class TestParserDiscovery(TestCase):
|
||||
),
|
||||
)
|
||||
|
||||
self.assertEqual(get_parser_class("doc.pdf"), DummyParser)
|
||||
self.assertEqual(get_parser_class_for_mime_type("application/pdf"), DummyParser)
|
||||
|
||||
@mock.patch("documents.parsers.document_consumer_declaration.send")
|
||||
def test__get_parser_class_n_parsers(self, m, *args):
|
||||
def test_get_parser_class_n_parsers(self, m, *args):
|
||||
"""
|
||||
GIVEN:
|
||||
- Two parsers declared for a given mimetype
|
||||
- Second parser has a higher weight
|
||||
WHEN:
|
||||
- Attempt to get parser for the mimetype
|
||||
THEN:
|
||||
- Second parser class is returned
|
||||
"""
|
||||
|
||||
class DummyParser1:
|
||||
pass
|
||||
|
||||
@ -74,30 +75,77 @@ class TestParserDiscovery(TestCase):
|
||||
),
|
||||
)
|
||||
|
||||
self.assertEqual(get_parser_class("doc.pdf"), DummyParser2)
|
||||
self.assertEqual(
|
||||
get_parser_class_for_mime_type("application/pdf"),
|
||||
DummyParser2,
|
||||
)
|
||||
|
||||
@mock.patch("documents.parsers.document_consumer_declaration.send")
|
||||
def test__get_parser_class_0_parsers(self, m, *args):
|
||||
def test_get_parser_class_0_parsers(self, m, *args):
|
||||
"""
|
||||
GIVEN:
|
||||
- No parsers are declared
|
||||
WHEN:
|
||||
- Attempt to get parser for the mimetype
|
||||
THEN:
|
||||
- No parser class is returned
|
||||
"""
|
||||
m.return_value = []
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
self.assertIsNone(get_parser_class("doc.pdf"))
|
||||
self.assertIsNone(get_parser_class_for_mime_type("application/pdf"))
|
||||
|
||||
@mock.patch("documents.parsers.document_consumer_declaration.send")
|
||||
def test_get_parser_class_no_valid_parser(self, m, *args):
|
||||
"""
|
||||
GIVEN:
|
||||
- No parser declared for a given mimetype
|
||||
- Parser declared for a different mimetype
|
||||
WHEN:
|
||||
- Attempt to get parser for the given mimetype
|
||||
THEN:
|
||||
- No parser class is returned
|
||||
"""
|
||||
|
||||
def fake_get_thumbnail(self, path, mimetype, file_name):
|
||||
return os.path.join(os.path.dirname(__file__), "examples", "no-text.png")
|
||||
class DummyParser:
|
||||
pass
|
||||
|
||||
m.return_value = (
|
||||
(
|
||||
None,
|
||||
{
|
||||
"weight": 0,
|
||||
"parser": DummyParser,
|
||||
"mime_types": {"application/pdf": ".pdf"},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
self.assertIsNone(get_parser_class_for_mime_type("image/tiff"))
|
||||
|
||||
|
||||
class TestParserAvailability(TestCase):
|
||||
def test_file_extensions(self):
|
||||
|
||||
for ext in [".pdf", ".jpe", ".jpg", ".jpeg", ".txt", ".csv"]:
|
||||
self.assertIn(ext, get_supported_file_extensions())
|
||||
self.assertEqual(get_default_file_extension("application/pdf"), ".pdf")
|
||||
self.assertEqual(get_default_file_extension("image/png"), ".png")
|
||||
self.assertEqual(get_default_file_extension("image/jpeg"), ".jpg")
|
||||
self.assertEqual(get_default_file_extension("text/plain"), ".txt")
|
||||
self.assertEqual(get_default_file_extension("text/csv"), ".csv")
|
||||
supported_mimes_and_exts = [
|
||||
("application/pdf", ".pdf"),
|
||||
("image/png", ".png"),
|
||||
("image/jpeg", ".jpg"),
|
||||
("image/tiff", ".tif"),
|
||||
("image/webp", ".webp"),
|
||||
("text/plain", ".txt"),
|
||||
("text/csv", ".csv"),
|
||||
]
|
||||
|
||||
supported_exts = get_supported_file_extensions()
|
||||
|
||||
for mime_type, ext in supported_mimes_and_exts:
|
||||
self.assertIn(ext, supported_exts)
|
||||
self.assertEqual(get_default_file_extension(mime_type), ext)
|
||||
|
||||
# Test no parser declared still returns a an extension
|
||||
self.assertEqual(get_default_file_extension("application/zip"), ".zip")
|
||||
|
||||
# Test invalid mimetype returns no extension
|
||||
self.assertEqual(get_default_file_extension("aasdasd/dgfgf"), "")
|
||||
|
||||
self.assertIsInstance(
|
||||
@ -108,7 +156,7 @@ class TestParserAvailability(TestCase):
|
||||
get_parser_class_for_mime_type("text/plain")(logging_group=None),
|
||||
TextDocumentParser,
|
||||
)
|
||||
self.assertEqual(get_parser_class_for_mime_type("text/sdgsdf"), None)
|
||||
self.assertIsNone(get_parser_class_for_mime_type("text/sdgsdf"))
|
||||
|
||||
self.assertTrue(is_file_ext_supported(".pdf"))
|
||||
self.assertFalse(is_file_ext_supported(".hsdfh"))
|
||||
|
@ -494,10 +494,19 @@ class DocumentViewSet(
|
||||
class SearchResultSerializer(DocumentSerializer, PassUserMixin):
|
||||
def to_representation(self, instance):
|
||||
doc = Document.objects.get(id=instance["id"])
|
||||
commentTerm = instance.results.q.subqueries[0]
|
||||
comments = ",".join(
|
||||
[
|
||||
str(c.comment)
|
||||
for c in Comment.objects.filter(document=instance["id"])
|
||||
if commentTerm.text in c.comment
|
||||
],
|
||||
)
|
||||
r = super().to_representation(doc)
|
||||
r["__search_hit__"] = {
|
||||
"score": instance.score,
|
||||
"highlights": instance.highlights("content", text=doc.content)
|
||||
"highlights": instance.highlights("content", text=doc.content),
|
||||
"comment_highlights": instance.highlights("content", text=comments)
|
||||
if doc
|
||||
else None,
|
||||
"rank": instance.rank,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,878 +0,0 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-11-09 21:50+0000\n"
|
||||
"PO-Revision-Date: 2022-12-09 07:39\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Arabic\n"
|
||||
"Language: ar_SA\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
|
||||
"X-Crowdin-Project: paperless-ngx\n"
|
||||
"X-Crowdin-Project-ID: 500308\n"
|
||||
"X-Crowdin-Language: ar\n"
|
||||
"X-Crowdin-File: /dev/src/locale/en_US/LC_MESSAGES/django.po\n"
|
||||
"X-Crowdin-File-ID: 14\n"
|
||||
|
||||
#: documents/apps.py:9
|
||||
msgid "Documents"
|
||||
msgstr "المستندات"
|
||||
|
||||
#: documents/models.py:32
|
||||
msgid "Any word"
|
||||
msgstr "أي كلمة"
|
||||
|
||||
#: documents/models.py:33
|
||||
msgid "All words"
|
||||
msgstr "كل الكلمات"
|
||||
|
||||
#: documents/models.py:34
|
||||
msgid "Exact match"
|
||||
msgstr "تطابق تام"
|
||||
|
||||
#: documents/models.py:35
|
||||
msgid "Regular expression"
|
||||
msgstr "التعابير النظامية"
|
||||
|
||||
#: documents/models.py:36
|
||||
msgid "Fuzzy word"
|
||||
msgstr "كلمة غامضة"
|
||||
|
||||
#: documents/models.py:37
|
||||
msgid "Automatic"
|
||||
msgstr "تلقائي"
|
||||
|
||||
#: documents/models.py:40 documents/models.py:367 paperless_mail/models.py:16
|
||||
#: paperless_mail/models.py:80
|
||||
msgid "name"
|
||||
msgstr "اسم"
|
||||
|
||||
#: documents/models.py:42
|
||||
msgid "match"
|
||||
msgstr "تطابق"
|
||||
|
||||
#: documents/models.py:45
|
||||
msgid "matching algorithm"
|
||||
msgstr "خوارزمية مطابقة"
|
||||
|
||||
#: documents/models.py:50
|
||||
msgid "is insensitive"
|
||||
msgstr "غير حساس"
|
||||
|
||||
#: documents/models.py:63 documents/models.py:118
|
||||
msgid "correspondent"
|
||||
msgstr "مراسل"
|
||||
|
||||
#: documents/models.py:64
|
||||
msgid "correspondents"
|
||||
msgstr "مراسلون"
|
||||
|
||||
#: documents/models.py:69
|
||||
msgid "color"
|
||||
msgstr "لون"
|
||||
|
||||
#: documents/models.py:72
|
||||
msgid "is inbox tag"
|
||||
msgstr "علامة علبة الوارد"
|
||||
|
||||
#: documents/models.py:75
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr "ضع علامة على هذه السمة كعلامة علبة الوارد: سيتم وضع علامة على جميع المستندات المستهلكة حديثا مع علامات صندوق الواردات."
|
||||
|
||||
#: documents/models.py:81
|
||||
msgid "tag"
|
||||
msgstr "علامة"
|
||||
|
||||
#: documents/models.py:82 documents/models.py:156
|
||||
msgid "tags"
|
||||
msgstr "علامات"
|
||||
|
||||
#: documents/models.py:87 documents/models.py:138
|
||||
msgid "document type"
|
||||
msgstr "نوع المستند"
|
||||
|
||||
#: documents/models.py:88
|
||||
msgid "document types"
|
||||
msgstr "أنواع المستندات"
|
||||
|
||||
#: documents/models.py:93
|
||||
msgid "path"
|
||||
msgstr "مسار"
|
||||
|
||||
#: documents/models.py:99 documents/models.py:127
|
||||
msgid "storage path"
|
||||
msgstr "مسار التخزين"
|
||||
|
||||
#: documents/models.py:100
|
||||
msgid "storage paths"
|
||||
msgstr "مسارات التخزين"
|
||||
|
||||
#: documents/models.py:108
|
||||
msgid "Unencrypted"
|
||||
msgstr "دون تشفير"
|
||||
|
||||
#: documents/models.py:109
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr "مشفر باستخدام حارس خصوصية غنو"
|
||||
|
||||
#: documents/models.py:130
|
||||
msgid "title"
|
||||
msgstr "عنوان"
|
||||
|
||||
#: documents/models.py:142 documents/models.py:611
|
||||
msgid "content"
|
||||
msgstr "محتوى"
|
||||
|
||||
#: documents/models.py:145
|
||||
msgid "The raw, text-only data of the document. This field is primarily used for searching."
|
||||
msgstr "الخام, فقط النص من المستند. يستخدم هذا الحقل أساسا للبحث."
|
||||
|
||||
#: documents/models.py:150
|
||||
msgid "mime type"
|
||||
msgstr "MIME type"
|
||||
|
||||
#: documents/models.py:160
|
||||
msgid "checksum"
|
||||
msgstr "بصمة الملف"
|
||||
|
||||
#: documents/models.py:164
|
||||
msgid "The checksum of the original document."
|
||||
msgstr "بصمة الملف للمستند الأصلي."
|
||||
|
||||
#: documents/models.py:168
|
||||
msgid "archive checksum"
|
||||
msgstr "بصمة الملف للربيدة"
|
||||
|
||||
#: documents/models.py:173
|
||||
msgid "The checksum of the archived document."
|
||||
msgstr "بصمة الملف للمستند الربيدة."
|
||||
|
||||
#: documents/models.py:176 documents/models.py:348 documents/models.py:617
|
||||
msgid "created"
|
||||
msgstr "أُنشئ"
|
||||
|
||||
#: documents/models.py:179
|
||||
msgid "modified"
|
||||
msgstr "مُعدّل"
|
||||
|
||||
#: documents/models.py:186
|
||||
msgid "storage type"
|
||||
msgstr "نوع التخزين"
|
||||
|
||||
#: documents/models.py:194
|
||||
msgid "added"
|
||||
msgstr "أضيف"
|
||||
|
||||
#: documents/models.py:201
|
||||
msgid "filename"
|
||||
msgstr "اسم الملف"
|
||||
|
||||
#: documents/models.py:207
|
||||
msgid "Current filename in storage"
|
||||
msgstr "اسم الملف الحالي في التخزين"
|
||||
|
||||
#: documents/models.py:211
|
||||
msgid "archive filename"
|
||||
msgstr "اسم الربيدة"
|
||||
|
||||
#: documents/models.py:217
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr "اسم ملف الربيدة الحالي في التخزين"
|
||||
|
||||
#: documents/models.py:221
|
||||
msgid "original filename"
|
||||
msgstr "اسم الملف الأصلي"
|
||||
|
||||
#: documents/models.py:227
|
||||
msgid "The original name of the file when it was uploaded"
|
||||
msgstr "اسم الملف الأصلي عند تحميله"
|
||||
|
||||
#: documents/models.py:231
|
||||
msgid "archive serial number"
|
||||
msgstr "الرقم التسلسلي للربيدة"
|
||||
|
||||
#: documents/models.py:237
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr "موقع هذا المستند في ربيدة المستند الفيزيائي."
|
||||
|
||||
#: documents/models.py:243 documents/models.py:628
|
||||
msgid "document"
|
||||
msgstr "مستند"
|
||||
|
||||
#: documents/models.py:244
|
||||
msgid "documents"
|
||||
msgstr "المستندات"
|
||||
|
||||
#: documents/models.py:331
|
||||
msgid "debug"
|
||||
msgstr "تصحيح الأخطاء"
|
||||
|
||||
#: documents/models.py:332
|
||||
msgid "information"
|
||||
msgstr "معلومات"
|
||||
|
||||
#: documents/models.py:333
|
||||
msgid "warning"
|
||||
msgstr "تحذير"
|
||||
|
||||
#: documents/models.py:334
|
||||
msgid "error"
|
||||
msgstr "خطأ"
|
||||
|
||||
#: documents/models.py:335
|
||||
msgid "critical"
|
||||
msgstr "الحرجة"
|
||||
|
||||
#: documents/models.py:338
|
||||
msgid "group"
|
||||
msgstr "مجموعة"
|
||||
|
||||
#: documents/models.py:340
|
||||
msgid "message"
|
||||
msgstr "رسالة"
|
||||
|
||||
#: documents/models.py:343
|
||||
msgid "level"
|
||||
msgstr "المستوى"
|
||||
|
||||
#: documents/models.py:352
|
||||
msgid "log"
|
||||
msgstr "سجل"
|
||||
|
||||
#: documents/models.py:353
|
||||
msgid "logs"
|
||||
msgstr "السجلات"
|
||||
|
||||
#: documents/models.py:363 documents/models.py:419
|
||||
msgid "saved view"
|
||||
msgstr "العرض المحفوظ"
|
||||
|
||||
#: documents/models.py:364
|
||||
msgid "saved views"
|
||||
msgstr "العروض المحفوظة"
|
||||
|
||||
#: documents/models.py:366 documents/models.py:637
|
||||
msgid "user"
|
||||
msgstr "المستخدم"
|
||||
|
||||
#: documents/models.py:370
|
||||
msgid "show on dashboard"
|
||||
msgstr "عرض على لوحة التحكم"
|
||||
|
||||
#: documents/models.py:373
|
||||
msgid "show in sidebar"
|
||||
msgstr "عرض على الشريط الجانبي"
|
||||
|
||||
#: documents/models.py:377
|
||||
msgid "sort field"
|
||||
msgstr "فرز الحقل"
|
||||
|
||||
#: documents/models.py:382
|
||||
msgid "sort reverse"
|
||||
msgstr "فرز بالعكس"
|
||||
|
||||
#: documents/models.py:387
|
||||
msgid "title contains"
|
||||
msgstr "العنوان يحتوي"
|
||||
|
||||
#: documents/models.py:388
|
||||
msgid "content contains"
|
||||
msgstr "المحتوى يحتوي"
|
||||
|
||||
#: documents/models.py:389
|
||||
msgid "ASN is"
|
||||
msgstr "ASN هو"
|
||||
|
||||
#: documents/models.py:390
|
||||
msgid "correspondent is"
|
||||
msgstr "المراسل هو"
|
||||
|
||||
#: documents/models.py:391
|
||||
msgid "document type is"
|
||||
msgstr "نوع المستند"
|
||||
|
||||
#: documents/models.py:392
|
||||
msgid "is in inbox"
|
||||
msgstr "موجود في علبة الوارد"
|
||||
|
||||
#: documents/models.py:393
|
||||
msgid "has tag"
|
||||
msgstr "لديه علامة"
|
||||
|
||||
#: documents/models.py:394
|
||||
msgid "has any tag"
|
||||
msgstr "لديه أي وسم"
|
||||
|
||||
#: documents/models.py:395
|
||||
msgid "created before"
|
||||
msgstr "أنشئت قبل"
|
||||
|
||||
#: documents/models.py:396
|
||||
msgid "created after"
|
||||
msgstr "أنشئت بعد"
|
||||
|
||||
#: documents/models.py:397
|
||||
msgid "created year is"
|
||||
msgstr "أنشئت سنة"
|
||||
|
||||
#: documents/models.py:398
|
||||
msgid "created month is"
|
||||
msgstr "أنشئت شهر"
|
||||
|
||||
#: documents/models.py:399
|
||||
msgid "created day is"
|
||||
msgstr "أنشئت يوم"
|
||||
|
||||
#: documents/models.py:400
|
||||
msgid "added before"
|
||||
msgstr "أضيف قبل"
|
||||
|
||||
#: documents/models.py:401
|
||||
msgid "added after"
|
||||
msgstr "أضيف بعد"
|
||||
|
||||
#: documents/models.py:402
|
||||
msgid "modified before"
|
||||
msgstr "عُدِّل قبل"
|
||||
|
||||
#: documents/models.py:403
|
||||
msgid "modified after"
|
||||
msgstr "عُدِّل بعد"
|
||||
|
||||
#: documents/models.py:404
|
||||
msgid "does not have tag"
|
||||
msgstr "ليس لديه علامة"
|
||||
|
||||
#: documents/models.py:405
|
||||
msgid "does not have ASN"
|
||||
msgstr "ليس لديه ASN"
|
||||
|
||||
#: documents/models.py:406
|
||||
msgid "title or content contains"
|
||||
msgstr "العنوان أو المحتوى يحتوي"
|
||||
|
||||
#: documents/models.py:407
|
||||
msgid "fulltext query"
|
||||
msgstr "استعلام كامل النص"
|
||||
|
||||
#: documents/models.py:408
|
||||
msgid "more like this"
|
||||
msgstr "أخرى مثلها"
|
||||
|
||||
#: documents/models.py:409
|
||||
msgid "has tags in"
|
||||
msgstr "لديه علامات في"
|
||||
|
||||
#: documents/models.py:410
|
||||
msgid "ASN greater than"
|
||||
msgstr "ASN أكبر من"
|
||||
|
||||
#: documents/models.py:411
|
||||
msgid "ASN less than"
|
||||
msgstr "ASN أقل من"
|
||||
|
||||
#: documents/models.py:412
|
||||
msgid "storage path is"
|
||||
msgstr "مسار التخزين"
|
||||
|
||||
#: documents/models.py:422
|
||||
msgid "rule type"
|
||||
msgstr "نوع القاعدة"
|
||||
|
||||
#: documents/models.py:424
|
||||
msgid "value"
|
||||
msgstr "قيمة"
|
||||
|
||||
#: documents/models.py:427
|
||||
msgid "filter rule"
|
||||
msgstr "تصفية القاعدة"
|
||||
|
||||
#: documents/models.py:428
|
||||
msgid "filter rules"
|
||||
msgstr "تصفية القواعد"
|
||||
|
||||
#: documents/models.py:536
|
||||
msgid "Task ID"
|
||||
msgstr "الرمز التعريفي للمهمة"
|
||||
|
||||
#: documents/models.py:537
|
||||
msgid "Celery ID for the Task that was run"
|
||||
msgstr "رمز المعرف للمهمة التي كانت تعمل"
|
||||
|
||||
#: documents/models.py:542
|
||||
msgid "Acknowledged"
|
||||
msgstr "مُعترف"
|
||||
|
||||
#: documents/models.py:543
|
||||
msgid "If the task is acknowledged via the frontend or API"
|
||||
msgstr "إذا عرف على المهمة عبر الواجهة الأمامية أو API"
|
||||
|
||||
#: documents/models.py:549 documents/models.py:556
|
||||
msgid "Task Name"
|
||||
msgstr "اسم المهمة"
|
||||
|
||||
#: documents/models.py:550
|
||||
msgid "Name of the file which the Task was run for"
|
||||
msgstr "اسم الملف الذي وكل بالمهمة"
|
||||
|
||||
#: documents/models.py:557
|
||||
msgid "Name of the Task which was run"
|
||||
msgstr "اسم المهمة التي كانت تعمل"
|
||||
|
||||
#: documents/models.py:562
|
||||
msgid "Task Positional Arguments"
|
||||
msgstr "مهمة قيمة المعاملات الموضعية"
|
||||
|
||||
#: documents/models.py:564
|
||||
msgid "JSON representation of the positional arguments used with the task"
|
||||
msgstr "تمثيل JSON لقيمة المعاملات الموضعية المستخدمة في المهمة"
|
||||
|
||||
#: documents/models.py:569
|
||||
msgid "Task Named Arguments"
|
||||
msgstr "مهمة قيمة المعامل المسمى"
|
||||
|
||||
#: documents/models.py:571
|
||||
msgid "JSON representation of the named arguments used with the task"
|
||||
msgstr "تمثيل JSON لقيمة المعاملات المسمية المستخدمة في المهمة"
|
||||
|
||||
#: documents/models.py:578
|
||||
msgid "Task State"
|
||||
msgstr "حالة المهمة"
|
||||
|
||||
#: documents/models.py:579
|
||||
msgid "Current state of the task being run"
|
||||
msgstr "الحالة الراهنة للمهمة قيد العمل"
|
||||
|
||||
#: documents/models.py:584
|
||||
msgid "Created DateTime"
|
||||
msgstr "تاريخ و وقت الإنشاء"
|
||||
|
||||
#: documents/models.py:585
|
||||
msgid "Datetime field when the task result was created in UTC"
|
||||
msgstr "حقل التاريخ والوقت عند إنشاء نتيجة المهمة في UTC"
|
||||
|
||||
#: documents/models.py:590
|
||||
msgid "Started DateTime"
|
||||
msgstr "تاريخ و وقت البداية"
|
||||
|
||||
#: documents/models.py:591
|
||||
msgid "Datetime field when the task was started in UTC"
|
||||
msgstr "حقل التاريخ والوقت عند بدء المهمة في UTC"
|
||||
|
||||
#: documents/models.py:596
|
||||
msgid "Completed DateTime"
|
||||
msgstr "التاريخ و الوقت المكتمل"
|
||||
|
||||
#: documents/models.py:597
|
||||
msgid "Datetime field when the task was completed in UTC"
|
||||
msgstr "حقل التاريخ و الوقت عند اكتمال المهمة في UTC"
|
||||
|
||||
#: documents/models.py:602
|
||||
msgid "Result Data"
|
||||
msgstr "نتائج البيانات"
|
||||
|
||||
#: documents/models.py:604
|
||||
msgid "The data returned by the task"
|
||||
msgstr "البيانات المستردة من قبل المهمة"
|
||||
|
||||
#: documents/models.py:613
|
||||
msgid "Comment for the document"
|
||||
msgstr "التعليق على المستند"
|
||||
|
||||
#: documents/models.py:642
|
||||
msgid "comment"
|
||||
msgstr "تعليق"
|
||||
|
||||
#: documents/models.py:643
|
||||
msgid "comments"
|
||||
msgstr "التعليقات"
|
||||
|
||||
#: documents/serialisers.py:72
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr "التعبير النظامي خاطىء: %(error)s"
|
||||
|
||||
#: documents/serialisers.py:193
|
||||
msgid "Invalid color."
|
||||
msgstr "لون خاطئ."
|
||||
|
||||
#: documents/serialisers.py:518
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr "نوع الملف %(type)s غير مدعوم"
|
||||
|
||||
#: documents/serialisers.py:599
|
||||
msgid "Invalid variable detected."
|
||||
msgstr "اكتشاف متغير خاطئ."
|
||||
|
||||
#: documents/templates/index.html:78
|
||||
msgid "Paperless-ngx is loading..."
|
||||
msgstr "تحميل Paperless-ngx..."
|
||||
|
||||
#: documents/templates/index.html:79
|
||||
msgid "Still here?! Hmm, something might be wrong."
|
||||
msgstr "مازلت هنا؟! همم، قد يكون هناك خطأ ما."
|
||||
|
||||
#: documents/templates/index.html:79
|
||||
msgid "Here's a link to the docs."
|
||||
msgstr "إليك رابط المستندات."
|
||||
|
||||
#: documents/templates/registration/logged_out.html:14
|
||||
msgid "Paperless-ngx signed out"
|
||||
msgstr "تسجيل الخروج Paperless-ngx"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:59
|
||||
msgid "You have been successfully logged out. Bye!"
|
||||
msgstr "تم تسجيل خروجك بنجاح. مع السلامة!"
|
||||
|
||||
#: documents/templates/registration/logged_out.html:60
|
||||
msgid "Sign in again"
|
||||
msgstr "تسجيل الدخول مرة أخرى"
|
||||
|
||||
#: documents/templates/registration/login.html:15
|
||||
msgid "Paperless-ngx sign in"
|
||||
msgstr "تسجيل الدخول Paperless-ngx"
|
||||
|
||||
#: documents/templates/registration/login.html:61
|
||||
msgid "Please sign in."
|
||||
msgstr "الرجاء تسجيل الدخول."
|
||||
|
||||
#: documents/templates/registration/login.html:64
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr "اسم المستخدم وكلمة المرور غير متطابقين. حاول مرة أخرى."
|
||||
|
||||
#: documents/templates/registration/login.html:67
|
||||
msgid "Username"
|
||||
msgstr "اسم المستخدم"
|
||||
|
||||
#: documents/templates/registration/login.html:68
|
||||
msgid "Password"
|
||||
msgstr "كلمة المرور"
|
||||
|
||||
#: documents/templates/registration/login.html:73
|
||||
msgid "Sign in"
|
||||
msgstr "تسجيل الدخول"
|
||||
|
||||
#: paperless/settings.py:378
|
||||
msgid "English (US)"
|
||||
msgstr "الإنجليزية (الولايات المتحدة)"
|
||||
|
||||
#: paperless/settings.py:379
|
||||
msgid "Belarusian"
|
||||
msgstr "البيلاروسية"
|
||||
|
||||
#: paperless/settings.py:380
|
||||
msgid "Czech"
|
||||
msgstr "التشيكية"
|
||||
|
||||
#: paperless/settings.py:381
|
||||
msgid "Danish"
|
||||
msgstr "الدانماركية"
|
||||
|
||||
#: paperless/settings.py:382
|
||||
msgid "German"
|
||||
msgstr "الألمانية"
|
||||
|
||||
#: paperless/settings.py:383
|
||||
msgid "English (GB)"
|
||||
msgstr "الإنجليزية (المملكة المتحدة)"
|
||||
|
||||
#: paperless/settings.py:384
|
||||
msgid "Spanish"
|
||||
msgstr "الإسبانية"
|
||||
|
||||
#: paperless/settings.py:385
|
||||
msgid "French"
|
||||
msgstr "الفرنسية"
|
||||
|
||||
#: paperless/settings.py:386
|
||||
msgid "Italian"
|
||||
msgstr "الإيطالية"
|
||||
|
||||
#: paperless/settings.py:387
|
||||
msgid "Luxembourgish"
|
||||
msgstr "اللوكسمبرجية"
|
||||
|
||||
#: paperless/settings.py:388
|
||||
msgid "Dutch"
|
||||
msgstr "الهولندية"
|
||||
|
||||
#: paperless/settings.py:389
|
||||
msgid "Polish"
|
||||
msgstr "البولندية"
|
||||
|
||||
#: paperless/settings.py:390
|
||||
msgid "Portuguese (Brazil)"
|
||||
msgstr "البرتغالية (البرازيل)"
|
||||
|
||||
#: paperless/settings.py:391
|
||||
msgid "Portuguese"
|
||||
msgstr "البرتغالية"
|
||||
|
||||
#: paperless/settings.py:392
|
||||
msgid "Romanian"
|
||||
msgstr "الرومانية"
|
||||
|
||||
#: paperless/settings.py:393
|
||||
msgid "Russian"
|
||||
msgstr "الروسية"
|
||||
|
||||
#: paperless/settings.py:394
|
||||
msgid "Slovenian"
|
||||
msgstr "السلوفانية"
|
||||
|
||||
#: paperless/settings.py:395
|
||||
msgid "Serbian"
|
||||
msgstr "الصربية"
|
||||
|
||||
#: paperless/settings.py:396
|
||||
msgid "Swedish"
|
||||
msgstr "السويدية"
|
||||
|
||||
#: paperless/settings.py:397
|
||||
msgid "Turkish"
|
||||
msgstr "التركية"
|
||||
|
||||
#: paperless/settings.py:398
|
||||
msgid "Chinese Simplified"
|
||||
msgstr "الصينية المبسطة"
|
||||
|
||||
#: paperless/urls.py:161
|
||||
msgid "Paperless-ngx administration"
|
||||
msgstr "Paperless-ngx الإدارة"
|
||||
|
||||
#: paperless_mail/admin.py:29
|
||||
msgid "Authentication"
|
||||
msgstr "المصادقة"
|
||||
|
||||
#: paperless_mail/admin.py:30
|
||||
msgid "Advanced settings"
|
||||
msgstr "الإعدادات المتقدمة"
|
||||
|
||||
#: paperless_mail/admin.py:47
|
||||
msgid "Filter"
|
||||
msgstr "تصفية"
|
||||
|
||||
#: paperless_mail/admin.py:50
|
||||
msgid "Paperless will only process mails that match ALL of the filters given below."
|
||||
msgstr "Paperless يقوم فقط بمعالجة البُرُد التي تتطابق جميع التصفيات المقدمة أدناه."
|
||||
|
||||
#: paperless_mail/admin.py:64
|
||||
msgid "Actions"
|
||||
msgstr "إجراءات"
|
||||
|
||||
#: paperless_mail/admin.py:67
|
||||
msgid "The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched."
|
||||
msgstr "الإجراء المطبق على البريد. ينفذ هذا الإجراء فقط عندما تستهلك المستندات من البريد. ستبقى البُرٌد التي لا تحتوي على مرفقات ستبقى كما هي."
|
||||
|
||||
#: paperless_mail/admin.py:75
|
||||
msgid "Metadata"
|
||||
msgstr "البيانات الوصفية"
|
||||
|
||||
#: paperless_mail/admin.py:78
|
||||
msgid "Assign metadata to documents consumed from this rule automatically. If you do not assign tags, types or correspondents here, paperless will still process all matching rules that you have defined."
|
||||
msgstr "تعيين بيانات التعريف للمستندات المستهلكة من هذه القاعدة تِلْقائيًا. إذا لم تعين العلامات أو الأنواع أو المراسلين هنا، سيظل paperless يعالج جميع قواعد المطابقة التي حددتها."
|
||||
|
||||
#: paperless_mail/apps.py:8
|
||||
msgid "Paperless mail"
|
||||
msgstr "بريد paperless"
|
||||
|
||||
#: paperless_mail/models.py:8
|
||||
msgid "mail account"
|
||||
msgstr "حساب البريد"
|
||||
|
||||
#: paperless_mail/models.py:9
|
||||
msgid "mail accounts"
|
||||
msgstr "حساب البُرُد"
|
||||
|
||||
#: paperless_mail/models.py:12
|
||||
msgid "No encryption"
|
||||
msgstr "دون تشفير"
|
||||
|
||||
#: paperless_mail/models.py:13
|
||||
msgid "Use SSL"
|
||||
msgstr "استخدم SSL"
|
||||
|
||||
#: paperless_mail/models.py:14
|
||||
msgid "Use STARTTLS"
|
||||
msgstr "استخدم STARTTLS"
|
||||
|
||||
#: paperless_mail/models.py:18
|
||||
msgid "IMAP server"
|
||||
msgstr "خادم IMAP"
|
||||
|
||||
#: paperless_mail/models.py:21
|
||||
msgid "IMAP port"
|
||||
msgstr "منفذ IMAP"
|
||||
|
||||
#: paperless_mail/models.py:25
|
||||
msgid "This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections."
|
||||
msgstr "عادة ما يكون 143 للغير مشفر و اتصالات STARTTLS و 993 للاتصالات SSL."
|
||||
|
||||
#: paperless_mail/models.py:31
|
||||
msgid "IMAP security"
|
||||
msgstr "أمان IMAP"
|
||||
|
||||
#: paperless_mail/models.py:36
|
||||
msgid "username"
|
||||
msgstr "اسم المستخدم"
|
||||
|
||||
#: paperless_mail/models.py:38
|
||||
msgid "password"
|
||||
msgstr "كلمة المرور"
|
||||
|
||||
#: paperless_mail/models.py:41
|
||||
msgid "character set"
|
||||
msgstr "نوع ترميز المحارف"
|
||||
|
||||
#: paperless_mail/models.py:45
|
||||
msgid "The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
msgstr "ترميز المحارف المستخدمة عند التواصل مع خادم البريد، مثل 'UTF-8' أو 'US-ASCII'."
|
||||
|
||||
#: paperless_mail/models.py:56
|
||||
msgid "mail rule"
|
||||
msgstr "قاعدة البريد"
|
||||
|
||||
#: paperless_mail/models.py:57
|
||||
msgid "mail rules"
|
||||
msgstr "قواعد البريد"
|
||||
|
||||
#: paperless_mail/models.py:60
|
||||
msgid "Only process attachments."
|
||||
msgstr "معالجة المرفقات فقط."
|
||||
|
||||
#: paperless_mail/models.py:61
|
||||
msgid "Process all files, including 'inline' attachments."
|
||||
msgstr "معالجة جميع الملفات، بما في ذلك المرفقات المضمنة."
|
||||
|
||||
#: paperless_mail/models.py:64
|
||||
msgid "Delete"
|
||||
msgstr "حذف"
|
||||
|
||||
#: paperless_mail/models.py:65
|
||||
msgid "Move to specified folder"
|
||||
msgstr "نقل إلى مجلد محدد"
|
||||
|
||||
#: paperless_mail/models.py:66
|
||||
msgid "Mark as read, don't process read mails"
|
||||
msgstr "وضع علامة كمقروءة، لا تعالج الرسائل المقروءة"
|
||||
|
||||
#: paperless_mail/models.py:67
|
||||
msgid "Flag the mail, don't process flagged mails"
|
||||
msgstr "علم الرسالة، لا تعالج الرسائل المعلمة"
|
||||
|
||||
#: paperless_mail/models.py:68
|
||||
msgid "Tag the mail with specified tag, don't process tagged mails"
|
||||
msgstr "علم الرسالة بعلامة محددة، لا تعالج الرسائل المُعلمة"
|
||||
|
||||
#: paperless_mail/models.py:71
|
||||
msgid "Use subject as title"
|
||||
msgstr "استخدم الموضوع كعنوان"
|
||||
|
||||
#: paperless_mail/models.py:72
|
||||
msgid "Use attachment filename as title"
|
||||
msgstr "استخدم اسم الملف المرفق كعنوان"
|
||||
|
||||
#: paperless_mail/models.py:75
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr "لا تعيّن مراسل"
|
||||
|
||||
#: paperless_mail/models.py:76
|
||||
msgid "Use mail address"
|
||||
msgstr "استخدم عنوان البريد"
|
||||
|
||||
#: paperless_mail/models.py:77
|
||||
msgid "Use name (or mail address if not available)"
|
||||
msgstr "استخدم الاسم (أو عنوان البريد إذا لم يكن متاحا)"
|
||||
|
||||
#: paperless_mail/models.py:78
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr "استخدم المراسل المحدد أدناه"
|
||||
|
||||
#: paperless_mail/models.py:82
|
||||
msgid "order"
|
||||
msgstr "الطلب"
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "account"
|
||||
msgstr "الحساب"
|
||||
|
||||
#: paperless_mail/models.py:92
|
||||
msgid "folder"
|
||||
msgstr "مجلد"
|
||||
|
||||
#: paperless_mail/models.py:96
|
||||
msgid "Subfolders must be separated by a delimiter, often a dot ('.') or slash ('/'), but it varies by mail server."
|
||||
msgstr "يجب فصل المجلدات الفرعية باستخدام محدد، غالبا نقطة ('.') أو خط مائل ('/')، لكنها تختلف حسب خادم البريد."
|
||||
|
||||
#: paperless_mail/models.py:102
|
||||
msgid "filter from"
|
||||
msgstr "تصفية من"
|
||||
|
||||
#: paperless_mail/models.py:108
|
||||
msgid "filter subject"
|
||||
msgstr "تصفية الموضوع"
|
||||
|
||||
#: paperless_mail/models.py:114
|
||||
msgid "filter body"
|
||||
msgstr "تصفية الجسم"
|
||||
|
||||
#: paperless_mail/models.py:121
|
||||
msgid "filter attachment filename"
|
||||
msgstr "تصفية اسم الملف المرفق"
|
||||
|
||||
#: paperless_mail/models.py:126
|
||||
msgid "Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
|
||||
msgstr "فقط المستندات التي تتطابق تماما مع اسم هذا الملف إذا تم تحديدها. المحارف البديلة مثل *.pdf أو *الفواتير* مسموح بها. لأنها غير حساسة."
|
||||
|
||||
#: paperless_mail/models.py:133
|
||||
msgid "maximum age"
|
||||
msgstr "أقصى عُمُر"
|
||||
|
||||
#: paperless_mail/models.py:135
|
||||
msgid "Specified in days."
|
||||
msgstr "محدد بالأيام."
|
||||
|
||||
#: paperless_mail/models.py:139
|
||||
msgid "attachment type"
|
||||
msgstr "نوع المرفق"
|
||||
|
||||
#: paperless_mail/models.py:143
|
||||
msgid "Inline attachments include embedded images, so it's best to combine this option with a filename filter."
|
||||
msgstr "تتضمن المرفقات المضمنة صورا مضمنة، لذا من الأفضل دمج هذا الخِيار مع تصفية اسم الملف."
|
||||
|
||||
#: paperless_mail/models.py:149
|
||||
msgid "action"
|
||||
msgstr "إجراء"
|
||||
|
||||
#: paperless_mail/models.py:155
|
||||
msgid "action parameter"
|
||||
msgstr "إجراء المعامل"
|
||||
|
||||
#: paperless_mail/models.py:160
|
||||
msgid "Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots."
|
||||
msgstr "معامل إضافي للإجراء المحدد أعلاه، مثال: المجلد المستهدف للانتقال إلى إجراء مجلد. يجب أن تكون المجلدات الفرعية مفصولة بنقاط."
|
||||
|
||||
#: paperless_mail/models.py:168
|
||||
msgid "assign title from"
|
||||
msgstr "تعيين العنوان من"
|
||||
|
||||
#: paperless_mail/models.py:176
|
||||
msgid "assign this tag"
|
||||
msgstr "تعيين هذه العلامة"
|
||||
|
||||
#: paperless_mail/models.py:184
|
||||
msgid "assign this document type"
|
||||
msgstr "تعيين نوع هذا المستند"
|
||||
|
||||
#: paperless_mail/models.py:188
|
||||
msgid "assign correspondent from"
|
||||
msgstr "تعيين مراسل من"
|
||||
|
||||
#: paperless_mail/models.py:198
|
||||
msgid "assign this correspondent"
|
||||
msgstr "تعيين هذا المراسل"
|
||||
|
@ -423,6 +423,7 @@ LANGUAGE_CODE = "en-us"
|
||||
|
||||
LANGUAGES = [
|
||||
("en-us", _("English (US)")), # needs to be first to act as fallback language
|
||||
("ar-ar", _("Arabic")),
|
||||
("be-by", _("Belarusian")),
|
||||
("cs-cz", _("Czech")),
|
||||
("da-dk", _("Danish")),
|
||||
|
@ -69,7 +69,7 @@ class BogusClient:
|
||||
if message.uid == args[0]:
|
||||
flag = args[2]
|
||||
if flag == "processed":
|
||||
message._raw_flag_data.append(f"+FLAGS (processed)".encode())
|
||||
message._raw_flag_data.append(b"+FLAGS (processed)")
|
||||
MailMessage.flags.fget.cache_clear()
|
||||
|
||||
|
||||
@ -153,7 +153,7 @@ class BogusMailBox(ContextManager):
|
||||
if flag == MailMessageFlags.SEEN:
|
||||
message.seen = value
|
||||
if flag == "processed":
|
||||
message._raw_flag_data.append(f"+FLAGS (processed)".encode())
|
||||
message._raw_flag_data.append(b"+FLAGS (processed)")
|
||||
MailMessage.flags.fget.cache_clear()
|
||||
|
||||
def move(self, uid_list, folder):
|
||||
@ -223,7 +223,7 @@ def create_message(
|
||||
imap_msg.seen = seen
|
||||
imap_msg.flagged = flagged
|
||||
if processed:
|
||||
imap_msg._raw_flag_data.append(f"+FLAGS (processed)".encode())
|
||||
imap_msg._raw_flag_data.append(b"+FLAGS (processed)")
|
||||
MailMessage.flags.fget.cache_clear()
|
||||
|
||||
return imap_msg
|
||||
|
@ -6,7 +6,6 @@ from urllib.request import urlopen
|
||||
|
||||
import pytest
|
||||
from django.test import TestCase
|
||||
from documents.parsers import ParseError
|
||||
from documents.parsers import run_convert
|
||||
from imagehash import average_hash
|
||||
from paperless_mail.parsers import MailDocumentParser
|
||||
|
@ -1,6 +1,7 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
@ -79,6 +80,17 @@ class RasterisedDocumentParser(DocumentParser):
|
||||
with Image.open(image) as im:
|
||||
return im.mode in ("RGBA", "LA")
|
||||
|
||||
def remove_alpha(self, image_path: str):
|
||||
subprocess.run(
|
||||
[
|
||||
settings.CONVERT_BINARY,
|
||||
"-alpha",
|
||||
"off",
|
||||
image_path,
|
||||
image_path,
|
||||
],
|
||||
)
|
||||
|
||||
def get_dpi(self, image):
|
||||
try:
|
||||
with Image.open(image) as im:
|
||||
@ -230,11 +242,7 @@ class RasterisedDocumentParser(DocumentParser):
|
||||
f"Removing alpha layer from {input_file} "
|
||||
"for compatibility with img2pdf",
|
||||
)
|
||||
with Image.open(input_file) as im:
|
||||
background = Image.new("RGBA", im.size, (255, 255, 255))
|
||||
background.alpha_composite(im)
|
||||
background = background.convert("RGB")
|
||||
background.save(input_file, format=im.format)
|
||||
self.remove_alpha(input_file)
|
||||
|
||||
if dpi:
|
||||
self.log("debug", f"Detected DPI for image {input_file}: {dpi}")
|
||||
|
Binary file not shown.
Binary file not shown.
BIN
src/paperless_tesseract/tests/samples/multi-page-images.tiff
Normal file
BIN
src/paperless_tesseract/tests/samples/multi-page-images.tiff
Normal file
Binary file not shown.
@ -542,6 +542,78 @@ class TestParser(DirectoriesMixin, TestCase):
|
||||
],
|
||||
)
|
||||
|
||||
def test_multi_page_tiff(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Multi-page TIFF image
|
||||
WHEN:
|
||||
- Image is parsed
|
||||
THEN:
|
||||
- Text from all pages extracted
|
||||
"""
|
||||
parser = RasterisedDocumentParser(None)
|
||||
parser.parse(
|
||||
os.path.join(self.SAMPLE_FILES, "multi-page-images.tiff"),
|
||||
"image/tiff",
|
||||
)
|
||||
self.assertTrue(os.path.isfile(parser.archive_path))
|
||||
self.assertContainsStrings(
|
||||
parser.get_text().lower(),
|
||||
["page 1", "page 2", "page 3"],
|
||||
)
|
||||
|
||||
def test_multi_page_tiff_alpha(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Multi-page TIFF image
|
||||
- Image include an alpha channel
|
||||
WHEN:
|
||||
- Image is parsed
|
||||
THEN:
|
||||
- Text from all pages extracted
|
||||
"""
|
||||
parser = RasterisedDocumentParser(None)
|
||||
sample_file = os.path.join(self.SAMPLE_FILES, "multi-page-images-alpha.tiff")
|
||||
with tempfile.NamedTemporaryFile() as tmp_file:
|
||||
shutil.copy(sample_file, tmp_file.name)
|
||||
parser.parse(
|
||||
tmp_file.name,
|
||||
"image/tiff",
|
||||
)
|
||||
self.assertTrue(os.path.isfile(parser.archive_path))
|
||||
self.assertContainsStrings(
|
||||
parser.get_text().lower(),
|
||||
["page 1", "page 2", "page 3"],
|
||||
)
|
||||
|
||||
def test_multi_page_tiff_alpha_srgb(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Multi-page TIFF image
|
||||
- Image include an alpha channel
|
||||
- Image is srgb colorspace
|
||||
WHEN:
|
||||
- Image is parsed
|
||||
THEN:
|
||||
- Text from all pages extracted
|
||||
"""
|
||||
parser = RasterisedDocumentParser(None)
|
||||
sample_file = os.path.join(
|
||||
self.SAMPLE_FILES,
|
||||
"multi-page-images-alpha-rgb.tiff",
|
||||
)
|
||||
with tempfile.NamedTemporaryFile() as tmp_file:
|
||||
shutil.copy(sample_file, tmp_file.name)
|
||||
parser.parse(
|
||||
tmp_file.name,
|
||||
"image/tiff",
|
||||
)
|
||||
self.assertTrue(os.path.isfile(parser.archive_path))
|
||||
self.assertContainsStrings(
|
||||
parser.get_text().lower(),
|
||||
["page 1", "page 2", "page 3"],
|
||||
)
|
||||
|
||||
def test_ocrmypdf_parameters(self):
|
||||
parser = RasterisedDocumentParser(None)
|
||||
params = parser.construct_ocrmypdf_parameters(
|
||||
|
@ -5,7 +5,6 @@ from typing import Final
|
||||
|
||||
import pytest
|
||||
from django.test import TestCase
|
||||
from documents.parsers import ParseError
|
||||
from paperless_tika.parsers import TikaDocumentParser
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user