mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Merge remote-tracking branch 'upstream/dev' into feature-consume-eml
This commit is contained in:
		| @@ -76,7 +76,7 @@ | ||||
| 					</g> | ||||
| 				</svg> | ||||
| 				<h6 class="m-auto">{% translate "Paperless-ngx is loading..." %}</h6> | ||||
| 				<p class="warning m-auto mt-3 small fade hide">{% translate "Still here?! Hmm, something might be wrong." %} <a href="https://paperless-ngx.readthedocs.io/en/latest/">{% translate "Here's a link to the docs." %}</a></p> | ||||
| 				<p class="warning m-auto mt-3 small fade hide">{% translate "Still here?! Hmm, something might be wrong." %} <a href="https://docs.paperless-ngx.com">{% translate "Here's a link to the docs." %}</a></p> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</app-root> | ||||
|   | ||||
| @@ -3,7 +3,7 @@ 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-11-09 23:11\n" | ||||
| "PO-Revision-Date: 2022-11-29 08:29\n" | ||||
| "Last-Translator: \n" | ||||
| "Language-Team: Norwegian\n" | ||||
| "Language: no_NO\n" | ||||
| @@ -184,11 +184,11 @@ msgstr "Gjeldende arkiv filnavn i lagring" | ||||
|  | ||||
| #: documents/models.py:221 | ||||
| msgid "original filename" | ||||
| msgstr "" | ||||
| msgstr "opprinnelig filnavn" | ||||
|  | ||||
| #: documents/models.py:227 | ||||
| msgid "The original name of the file when it was uploaded" | ||||
| msgstr "" | ||||
| msgstr "Det opprinnelige filnavnet da den ble lastet opp" | ||||
|  | ||||
| #: documents/models.py:231 | ||||
| msgid "archive serial number" | ||||
| @@ -368,15 +368,15 @@ msgstr "har tags i" | ||||
|  | ||||
| #: documents/models.py:410 | ||||
| msgid "ASN greater than" | ||||
| msgstr "" | ||||
| msgstr "ASN større enn" | ||||
|  | ||||
| #: documents/models.py:411 | ||||
| msgid "ASN less than" | ||||
| msgstr "" | ||||
| msgstr "ASN mindre enn" | ||||
|  | ||||
| #: documents/models.py:412 | ||||
| msgid "storage path is" | ||||
| msgstr "" | ||||
| msgstr "lagringssti er" | ||||
|  | ||||
| #: documents/models.py:422 | ||||
| msgid "rule type" | ||||
| @@ -396,31 +396,31 @@ msgstr "filtrer regler" | ||||
|  | ||||
| #: documents/models.py:536 | ||||
| msgid "Task ID" | ||||
| msgstr "" | ||||
| msgstr "Oppgave ID" | ||||
|  | ||||
| #: documents/models.py:537 | ||||
| msgid "Celery ID for the Task that was run" | ||||
| msgstr "" | ||||
| msgstr "Celery ID for oppgaven som ble kjørt" | ||||
|  | ||||
| #: documents/models.py:542 | ||||
| msgid "Acknowledged" | ||||
| msgstr "" | ||||
| msgstr "Bekreftet" | ||||
|  | ||||
| #: documents/models.py:543 | ||||
| msgid "If the task is acknowledged via the frontend or API" | ||||
| msgstr "" | ||||
| msgstr "Hvis oppgaven bekreftes via frontend eller API" | ||||
|  | ||||
| #: documents/models.py:549 documents/models.py:556 | ||||
| msgid "Task Name" | ||||
| msgstr "" | ||||
| msgstr "Oppgavenavn" | ||||
|  | ||||
| #: documents/models.py:550 | ||||
| msgid "Name of the file which the Task was run for" | ||||
| msgstr "" | ||||
| msgstr "Navn på filen som oppgaven ble kjørt for" | ||||
|  | ||||
| #: documents/models.py:557 | ||||
| msgid "Name of the Task which was run" | ||||
| msgstr "" | ||||
| msgstr "Navn på Oppgaven som ble kjørt" | ||||
|  | ||||
| #: documents/models.py:562 | ||||
| msgid "Task Positional Arguments" | ||||
|   | ||||
| @@ -3,7 +3,7 @@ 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-11-09 23:11\n" | ||||
| "PO-Revision-Date: 2022-11-28 16:30\n" | ||||
| "Last-Translator: \n" | ||||
| "Language-Team: Polish\n" | ||||
| "Language: pl_PL\n" | ||||
| @@ -184,11 +184,11 @@ msgstr "Aktualna nazwa pliku archiwum w pamięci" | ||||
|  | ||||
| #: documents/models.py:221 | ||||
| msgid "original filename" | ||||
| msgstr "" | ||||
| msgstr "oryginalna nazwa pliku" | ||||
|  | ||||
| #: documents/models.py:227 | ||||
| msgid "The original name of the file when it was uploaded" | ||||
| msgstr "" | ||||
| msgstr "Oryginalna nazwa pliku, gdy został przesłany" | ||||
|  | ||||
| #: documents/models.py:231 | ||||
| msgid "archive serial number" | ||||
| @@ -368,15 +368,15 @@ msgstr "ma znaczniki w" | ||||
|  | ||||
| #: documents/models.py:410 | ||||
| msgid "ASN greater than" | ||||
| msgstr "" | ||||
| msgstr "ASN większy niż" | ||||
|  | ||||
| #: documents/models.py:411 | ||||
| msgid "ASN less than" | ||||
| msgstr "" | ||||
| msgstr "ASN mniejszy niż" | ||||
|  | ||||
| #: documents/models.py:412 | ||||
| msgid "storage path is" | ||||
| msgstr "" | ||||
| msgstr "ścieżką przechowywania jest" | ||||
|  | ||||
| #: documents/models.py:422 | ||||
| msgid "rule type" | ||||
| @@ -396,23 +396,23 @@ msgstr "reguły filtrowania" | ||||
|  | ||||
| #: documents/models.py:536 | ||||
| msgid "Task ID" | ||||
| msgstr "" | ||||
| msgstr "ID zadania" | ||||
|  | ||||
| #: documents/models.py:537 | ||||
| msgid "Celery ID for the Task that was run" | ||||
| msgstr "" | ||||
| msgstr "ID Celery dla zadania, które zostało uruchomione" | ||||
|  | ||||
| #: documents/models.py:542 | ||||
| msgid "Acknowledged" | ||||
| msgstr "" | ||||
| msgstr "Potwierdzono" | ||||
|  | ||||
| #: documents/models.py:543 | ||||
| msgid "If the task is acknowledged via the frontend or API" | ||||
| msgstr "" | ||||
| msgstr "Jeśli zadanie jest potwierdzone przez frontend lub API" | ||||
|  | ||||
| #: documents/models.py:549 documents/models.py:556 | ||||
| msgid "Task Name" | ||||
| msgstr "" | ||||
| msgstr "Nazwa zadania" | ||||
|  | ||||
| #: documents/models.py:550 | ||||
| msgid "Name of the file which the Task was run for" | ||||
| @@ -440,7 +440,7 @@ msgstr "" | ||||
|  | ||||
| #: documents/models.py:578 | ||||
| msgid "Task State" | ||||
| msgstr "" | ||||
| msgstr "Stan zadania" | ||||
|  | ||||
| #: documents/models.py:579 | ||||
| msgid "Current state of the task being run" | ||||
| @@ -476,19 +476,19 @@ msgstr "" | ||||
|  | ||||
| #: documents/models.py:604 | ||||
| msgid "The data returned by the task" | ||||
| msgstr "" | ||||
| msgstr "Dane zwrócone przez zadanie" | ||||
|  | ||||
| #: documents/models.py:613 | ||||
| msgid "Comment for the document" | ||||
| msgstr "" | ||||
| msgstr "Komentarz do dokumentu" | ||||
|  | ||||
| #: documents/models.py:642 | ||||
| msgid "comment" | ||||
| msgstr "" | ||||
| msgstr "komentarz" | ||||
|  | ||||
| #: documents/models.py:643 | ||||
| msgid "comments" | ||||
| msgstr "" | ||||
| msgstr "komentarze" | ||||
|  | ||||
| #: documents/serialisers.py:72 | ||||
| #, python-format | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import tempfile | ||||
| from typing import Final | ||||
| from typing import Optional | ||||
| from typing import Set | ||||
| from typing import Tuple | ||||
| from urllib.parse import urlparse | ||||
|  | ||||
| from celery.schedules import crontab | ||||
| @@ -65,6 +66,44 @@ def __get_path(key: str, default: str) -> str: | ||||
|     return os.path.abspath(os.path.normpath(os.environ.get(key, default))) | ||||
|  | ||||
|  | ||||
| def _parse_redis_url(env_redis: Optional[str]) -> Tuple[str]: | ||||
|     """ | ||||
|     Gets the Redis information from the environment or a default and handles | ||||
|     converting from incompatible django_channels and celery formats. | ||||
|  | ||||
|     Returns a tuple of (celery_url, channels_url) | ||||
|     """ | ||||
|  | ||||
|     # Not set, return a compatible default | ||||
|     if env_redis is None: | ||||
|         return ("redis://localhost:6379", "redis://localhost:6379") | ||||
|  | ||||
|     if "unix" in env_redis.lower(): | ||||
|         # channels_redis socket format, looks like: | ||||
|         # "unix:///path/to/redis.sock" | ||||
|         _, path = env_redis.split(":") | ||||
|         # Optionally setting a db number | ||||
|         if "?db=" in env_redis: | ||||
|             path, number = path.split("?db=") | ||||
|             return (f"redis+socket:{path}?virtual_host={number}", env_redis) | ||||
|         else: | ||||
|             return (f"redis+socket:{path}", env_redis) | ||||
|  | ||||
|     elif "+socket" in env_redis.lower(): | ||||
|         # celery socket style, looks like: | ||||
|         # "redis+socket:///path/to/redis.sock" | ||||
|         _, path = env_redis.split(":") | ||||
|         if "?virtual_host=" in env_redis: | ||||
|             # Virtual host (aka db number) | ||||
|             path, number = path.split("?virtual_host=") | ||||
|             return (env_redis, f"unix:{path}?db={number}") | ||||
|         else: | ||||
|             return (env_redis, f"unix:{path}") | ||||
|  | ||||
|     # Not a socket | ||||
|     return (env_redis, env_redis) | ||||
|  | ||||
|  | ||||
| # NEVER RUN WITH DEBUG IN PRODUCTION. | ||||
| DEBUG = __get_boolean("PAPERLESS_DEBUG", "NO") | ||||
|  | ||||
| @@ -182,7 +221,9 @@ ASGI_APPLICATION = "paperless.asgi.application" | ||||
| STATIC_URL = os.getenv("PAPERLESS_STATIC_URL", BASE_URL + "static/") | ||||
| WHITENOISE_STATIC_PREFIX = "/static/" | ||||
|  | ||||
| _REDIS_URL = os.getenv("PAPERLESS_REDIS", "redis://localhost:6379") | ||||
| _CELERY_REDIS_URL, _CHANNELS_REDIS_URL = _parse_redis_url( | ||||
|     os.getenv("PAPERLESS_REDIS", None), | ||||
| ) | ||||
|  | ||||
| # TODO: what is this used for? | ||||
| TEMPLATES = [ | ||||
| @@ -205,7 +246,7 @@ CHANNEL_LAYERS = { | ||||
|     "default": { | ||||
|         "BACKEND": "channels_redis.core.RedisChannelLayer", | ||||
|         "CONFIG": { | ||||
|             "hosts": [_REDIS_URL], | ||||
|             "hosts": [_CHANNELS_REDIS_URL], | ||||
|             "capacity": 2000,  # default 100 | ||||
|             "expiry": 15,  # default 60 | ||||
|         }, | ||||
| @@ -468,7 +509,7 @@ TASK_WORKERS = __get_int("PAPERLESS_TASK_WORKERS", 1) | ||||
|  | ||||
| WORKER_TIMEOUT: Final[int] = __get_int("PAPERLESS_WORKER_TIMEOUT", 1800) | ||||
|  | ||||
| CELERY_BROKER_URL = _REDIS_URL | ||||
| CELERY_BROKER_URL = _CELERY_REDIS_URL | ||||
| CELERY_TIMEZONE = TIME_ZONE | ||||
|  | ||||
| CELERY_WORKER_HIJACK_ROOT_LOGGER = False | ||||
| @@ -513,7 +554,7 @@ CELERY_BEAT_SCHEDULE_FILENAME = os.path.join(DATA_DIR, "celerybeat-schedule.db") | ||||
| CACHES = { | ||||
|     "default": { | ||||
|         "BACKEND": "django.core.cache.backends.redis.RedisCache", | ||||
|         "LOCATION": _REDIS_URL, | ||||
|         "LOCATION": _CHANNELS_REDIS_URL, | ||||
|     }, | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,7 @@ from unittest import mock | ||||
| from unittest import TestCase | ||||
|  | ||||
| from paperless.settings import _parse_ignore_dates | ||||
| from paperless.settings import _parse_redis_url | ||||
| from paperless.settings import default_threads_per_worker | ||||
|  | ||||
|  | ||||
| @@ -82,3 +83,59 @@ class TestIgnoreDateParsing(TestCase): | ||||
|                 self.assertGreaterEqual(default_threads, 1) | ||||
|  | ||||
|                 self.assertLessEqual(default_workers * default_threads, i) | ||||
|  | ||||
|     def test_redis_socket_parsing(self): | ||||
|         """ | ||||
|         GIVEN: | ||||
|             - Various Redis connection URI formats | ||||
|         WHEN: | ||||
|             - The URI is parsed | ||||
|         THEN: | ||||
|             - Socket based URIs are translated | ||||
|             - Non-socket URIs are unchanged | ||||
|             - None provided uses default | ||||
|         """ | ||||
|  | ||||
|         for input, expected in [ | ||||
|             # Nothing is set | ||||
|             (None, ("redis://localhost:6379", "redis://localhost:6379")), | ||||
|             # celery style | ||||
|             ( | ||||
|                 "redis+socket:///run/redis/redis.sock", | ||||
|                 ( | ||||
|                     "redis+socket:///run/redis/redis.sock", | ||||
|                     "unix:///run/redis/redis.sock", | ||||
|                 ), | ||||
|             ), | ||||
|             # redis-py / channels-redis style | ||||
|             ( | ||||
|                 "unix:///run/redis/redis.sock", | ||||
|                 ( | ||||
|                     "redis+socket:///run/redis/redis.sock", | ||||
|                     "unix:///run/redis/redis.sock", | ||||
|                 ), | ||||
|             ), | ||||
|             # celery style with db | ||||
|             ( | ||||
|                 "redis+socket:///run/redis/redis.sock?virtual_host=5", | ||||
|                 ( | ||||
|                     "redis+socket:///run/redis/redis.sock?virtual_host=5", | ||||
|                     "unix:///run/redis/redis.sock?db=5", | ||||
|                 ), | ||||
|             ), | ||||
|             # redis-py / channels-redis style with db | ||||
|             ( | ||||
|                 "unix:///run/redis/redis.sock?db=10", | ||||
|                 ( | ||||
|                     "redis+socket:///run/redis/redis.sock?virtual_host=10", | ||||
|                     "unix:///run/redis/redis.sock?db=10", | ||||
|                 ), | ||||
|             ), | ||||
|             # Just a host with a port | ||||
|             ( | ||||
|                 "redis://myredishost:6379", | ||||
|                 ("redis://myredishost:6379", "redis://myredishost:6379"), | ||||
|             ), | ||||
|         ]: | ||||
|             result = _parse_redis_url(input) | ||||
|             self.assertTupleEqual(expected, result) | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| from typing import Final | ||||
| from typing import Tuple | ||||
|  | ||||
| __version__: Final[Tuple[int, int, int]] = (1, 10, 0) | ||||
| __version__: Final[Tuple[int, int, int]] = (1, 10, 2) | ||||
| # Version string like X.Y.Z | ||||
| __full_version_str__: Final[str] = ".".join(map(str, __version__)) | ||||
| # Version string like X.Y | ||||
|   | ||||
| @@ -16,8 +16,7 @@ def get_tesseract_langs(): | ||||
|     # Decode bytes to string, split on newlines, trim out the header | ||||
|     proc_lines = proc.stdout.decode("utf8", errors="ignore").strip().split("\n")[1:] | ||||
|  | ||||
|     # Replace _ with - to convert two part languages to the expected code | ||||
|     return [x.replace("_", "-") for x in proc_lines] | ||||
|     return [x.strip() for x in proc_lines] | ||||
|  | ||||
|  | ||||
| @register() | ||||
|   | ||||
| @@ -27,3 +27,40 @@ class TestChecks(TestCase): | ||||
|         msgs = check_default_language_available(None) | ||||
|         self.assertEqual(len(msgs), 1) | ||||
|         self.assertEqual(msgs[0].level, ERROR) | ||||
|  | ||||
|     @override_settings(OCR_LANGUAGE="chi_sim") | ||||
|     @mock.patch("paperless_tesseract.checks.get_tesseract_langs") | ||||
|     def test_multi_part_language(self, m): | ||||
|         """ | ||||
|         GIVEN: | ||||
|             - An OCR language which is multi part (ie chi-sim) | ||||
|             - The language is correctly formatted | ||||
|         WHEN: | ||||
|             - Installed packages are checked | ||||
|         THEN: | ||||
|             - No errors are reported | ||||
|         """ | ||||
|         m.return_value = ["chi_sim", "eng"] | ||||
|  | ||||
|         msgs = check_default_language_available(None) | ||||
|  | ||||
|         self.assertEqual(len(msgs), 0) | ||||
|  | ||||
|     @override_settings(OCR_LANGUAGE="chi-sim") | ||||
|     @mock.patch("paperless_tesseract.checks.get_tesseract_langs") | ||||
|     def test_multi_part_language_bad_format(self, m): | ||||
|         """ | ||||
|         GIVEN: | ||||
|             - An OCR language which is multi part (ie chi-sim) | ||||
|             - The language is correctly NOT formatted | ||||
|         WHEN: | ||||
|             - Installed packages are checked | ||||
|         THEN: | ||||
|             - No errors are reported | ||||
|         """ | ||||
|         m.return_value = ["chi_sim", "eng"] | ||||
|  | ||||
|         msgs = check_default_language_available(None) | ||||
|  | ||||
|         self.assertEqual(len(msgs), 1) | ||||
|         self.assertEqual(msgs[0].level, ERROR) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Trenton Holmes
					Trenton Holmes