mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-03 19:17:13 -05:00 
			
		
		
		
	Merge branch 'dev'
This commit is contained in:
		
						commit
						74afad5976
					
				@ -51,7 +51,7 @@ repos:
 | 
				
			|||||||
          - 'prettier-plugin-organize-imports@4.1.0'
 | 
					          - 'prettier-plugin-organize-imports@4.1.0'
 | 
				
			||||||
  # Python hooks
 | 
					  # Python hooks
 | 
				
			||||||
  - repo: https://github.com/astral-sh/ruff-pre-commit
 | 
					  - repo: https://github.com/astral-sh/ruff-pre-commit
 | 
				
			||||||
    rev: v0.8.6
 | 
					    rev: v0.9.2
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: ruff
 | 
					      - id: ruff
 | 
				
			||||||
      - id: ruff-format
 | 
					      - id: ruff-format
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										277
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										277
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							@ -2913,122 +2913,109 @@
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
        "charset-normalizer": {
 | 
					        "charset-normalizer": {
 | 
				
			||||||
            "hashes": [
 | 
					            "hashes": [
 | 
				
			||||||
                "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621",
 | 
					                "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537",
 | 
				
			||||||
                "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6",
 | 
					                "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa",
 | 
				
			||||||
                "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8",
 | 
					                "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a",
 | 
				
			||||||
                "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912",
 | 
					                "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294",
 | 
				
			||||||
                "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c",
 | 
					                "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b",
 | 
				
			||||||
                "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b",
 | 
					                "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd",
 | 
				
			||||||
                "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d",
 | 
					                "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601",
 | 
				
			||||||
                "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d",
 | 
					                "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd",
 | 
				
			||||||
                "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95",
 | 
					                "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4",
 | 
				
			||||||
                "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e",
 | 
					                "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d",
 | 
				
			||||||
                "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565",
 | 
					                "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2",
 | 
				
			||||||
                "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64",
 | 
					                "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313",
 | 
				
			||||||
                "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab",
 | 
					                "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd",
 | 
				
			||||||
                "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be",
 | 
					                "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa",
 | 
				
			||||||
                "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e",
 | 
					                "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8",
 | 
				
			||||||
                "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907",
 | 
					                "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1",
 | 
				
			||||||
                "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0",
 | 
					                "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2",
 | 
				
			||||||
                "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2",
 | 
					                "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496",
 | 
				
			||||||
                "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62",
 | 
					                "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d",
 | 
				
			||||||
                "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62",
 | 
					                "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b",
 | 
				
			||||||
                "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23",
 | 
					                "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e",
 | 
				
			||||||
                "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc",
 | 
					                "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a",
 | 
				
			||||||
                "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284",
 | 
					                "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4",
 | 
				
			||||||
                "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca",
 | 
					                "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca",
 | 
				
			||||||
                "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455",
 | 
					                "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78",
 | 
				
			||||||
                "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858",
 | 
					                "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408",
 | 
				
			||||||
                "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b",
 | 
					                "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5",
 | 
				
			||||||
                "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594",
 | 
					                "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3",
 | 
				
			||||||
                "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc",
 | 
					                "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f",
 | 
				
			||||||
                "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db",
 | 
					                "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a",
 | 
				
			||||||
                "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b",
 | 
					                "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765",
 | 
				
			||||||
                "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea",
 | 
					                "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6",
 | 
				
			||||||
                "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6",
 | 
					                "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146",
 | 
				
			||||||
                "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920",
 | 
					                "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6",
 | 
				
			||||||
                "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749",
 | 
					                "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9",
 | 
				
			||||||
                "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7",
 | 
					                "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd",
 | 
				
			||||||
                "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd",
 | 
					                "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c",
 | 
				
			||||||
                "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99",
 | 
					                "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f",
 | 
				
			||||||
                "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242",
 | 
					                "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545",
 | 
				
			||||||
                "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee",
 | 
					                "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176",
 | 
				
			||||||
                "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129",
 | 
					                "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770",
 | 
				
			||||||
                "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2",
 | 
					                "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824",
 | 
				
			||||||
                "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51",
 | 
					                "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f",
 | 
				
			||||||
                "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee",
 | 
					                "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf",
 | 
				
			||||||
                "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8",
 | 
					                "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487",
 | 
				
			||||||
                "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b",
 | 
					                "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d",
 | 
				
			||||||
                "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613",
 | 
					                "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd",
 | 
				
			||||||
                "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742",
 | 
					                "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b",
 | 
				
			||||||
                "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe",
 | 
					                "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534",
 | 
				
			||||||
                "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3",
 | 
					                "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f",
 | 
				
			||||||
                "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5",
 | 
					                "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b",
 | 
				
			||||||
                "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631",
 | 
					                "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9",
 | 
				
			||||||
                "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7",
 | 
					                "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd",
 | 
				
			||||||
                "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15",
 | 
					                "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125",
 | 
				
			||||||
                "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c",
 | 
					                "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9",
 | 
				
			||||||
                "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea",
 | 
					                "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de",
 | 
				
			||||||
                "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417",
 | 
					                "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11",
 | 
				
			||||||
                "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250",
 | 
					                "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d",
 | 
				
			||||||
                "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88",
 | 
					                "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35",
 | 
				
			||||||
                "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca",
 | 
					                "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f",
 | 
				
			||||||
                "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa",
 | 
					                "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda",
 | 
				
			||||||
                "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99",
 | 
					                "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7",
 | 
				
			||||||
                "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149",
 | 
					                "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a",
 | 
				
			||||||
                "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41",
 | 
					                "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971",
 | 
				
			||||||
                "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574",
 | 
					                "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8",
 | 
				
			||||||
                "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0",
 | 
					                "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41",
 | 
				
			||||||
                "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f",
 | 
					                "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d",
 | 
				
			||||||
                "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d",
 | 
					                "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f",
 | 
				
			||||||
                "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654",
 | 
					                "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757",
 | 
				
			||||||
                "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3",
 | 
					                "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a",
 | 
				
			||||||
                "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19",
 | 
					                "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886",
 | 
				
			||||||
                "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90",
 | 
					                "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77",
 | 
				
			||||||
                "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578",
 | 
					                "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76",
 | 
				
			||||||
                "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9",
 | 
					                "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247",
 | 
				
			||||||
                "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1",
 | 
					                "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85",
 | 
				
			||||||
                "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51",
 | 
					                "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb",
 | 
				
			||||||
                "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719",
 | 
					                "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7",
 | 
				
			||||||
                "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236",
 | 
					                "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e",
 | 
				
			||||||
                "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a",
 | 
					                "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6",
 | 
				
			||||||
                "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c",
 | 
					                "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037",
 | 
				
			||||||
                "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade",
 | 
					                "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1",
 | 
				
			||||||
                "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944",
 | 
					                "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e",
 | 
				
			||||||
                "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc",
 | 
					                "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807",
 | 
				
			||||||
                "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6",
 | 
					                "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407",
 | 
				
			||||||
                "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6",
 | 
					                "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c",
 | 
				
			||||||
                "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27",
 | 
					                "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12",
 | 
				
			||||||
                "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6",
 | 
					                "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3",
 | 
				
			||||||
                "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2",
 | 
					                "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089",
 | 
				
			||||||
                "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12",
 | 
					                "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd",
 | 
				
			||||||
                "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf",
 | 
					                "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e",
 | 
				
			||||||
                "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114",
 | 
					                "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00",
 | 
				
			||||||
                "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7",
 | 
					                "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"
 | 
				
			||||||
                "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf",
 | 
					 | 
				
			||||||
                "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d",
 | 
					 | 
				
			||||||
                "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b",
 | 
					 | 
				
			||||||
                "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed",
 | 
					 | 
				
			||||||
                "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03",
 | 
					 | 
				
			||||||
                "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4",
 | 
					 | 
				
			||||||
                "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67",
 | 
					 | 
				
			||||||
                "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365",
 | 
					 | 
				
			||||||
                "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a",
 | 
					 | 
				
			||||||
                "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748",
 | 
					 | 
				
			||||||
                "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b",
 | 
					 | 
				
			||||||
                "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079",
 | 
					 | 
				
			||||||
                "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"
 | 
					 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            "markers": "python_full_version >= '3.7.0'",
 | 
					            "markers": "python_version >= '3.7'",
 | 
				
			||||||
            "version": "==3.4.0"
 | 
					            "version": "==3.4.1"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "click": {
 | 
					        "click": {
 | 
				
			||||||
            "hashes": [
 | 
					            "hashes": [
 | 
				
			||||||
                "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28",
 | 
					                "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2",
 | 
				
			||||||
                "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"
 | 
					                "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            "markers": "python_version >= '3.7'",
 | 
					            "markers": "python_version >= '3.7'",
 | 
				
			||||||
            "version": "==8.1.7"
 | 
					            "version": "==8.1.8"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "colorama": {
 | 
					        "colorama": {
 | 
				
			||||||
            "hashes": [
 | 
					            "hashes": [
 | 
				
			||||||
@ -3291,11 +3278,11 @@
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
        "jinja2": {
 | 
					        "jinja2": {
 | 
				
			||||||
            "hashes": [
 | 
					            "hashes": [
 | 
				
			||||||
                "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369",
 | 
					                "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb",
 | 
				
			||||||
                "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"
 | 
					                "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            "markers": "python_version >= '3.7'",
 | 
					            "markers": "python_version >= '3.7'",
 | 
				
			||||||
            "version": "==3.1.4"
 | 
					            "version": "==3.1.5"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "markdown": {
 | 
					        "markdown": {
 | 
				
			||||||
            "hashes": [
 | 
					            "hashes": [
 | 
				
			||||||
@ -3406,12 +3393,12 @@
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
        "mkdocs-material": {
 | 
					        "mkdocs-material": {
 | 
				
			||||||
            "hashes": [
 | 
					            "hashes": [
 | 
				
			||||||
                "sha256:3671bb282b4f53a1c72e08adbe04d2481a98f85fed392530051f80ff94a9621d",
 | 
					                "sha256:ae5fe16f3d7c9ccd05bb6916a7da7420cf99a9ce5e33debd9d40403a090d5825",
 | 
				
			||||||
                "sha256:c3c2d8176b18198435d3a3e119011922f3e11424074645c24019c2dcf08a360e"
 | 
					                "sha256:f24100f234741f4d423a9d672a909d859668a4f404796be3cf035f10d6050385"
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            "index": "pypi",
 | 
					            "index": "pypi",
 | 
				
			||||||
            "markers": "python_version >= '3.8'",
 | 
					            "markers": "python_version >= '3.8'",
 | 
				
			||||||
            "version": "==9.5.49"
 | 
					            "version": "==9.5.50"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "mkdocs-material-extensions": {
 | 
					        "mkdocs-material-extensions": {
 | 
				
			||||||
            "hashes": [
 | 
					            "hashes": [
 | 
				
			||||||
@ -3645,19 +3632,19 @@
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
        "pygments": {
 | 
					        "pygments": {
 | 
				
			||||||
            "hashes": [
 | 
					            "hashes": [
 | 
				
			||||||
                "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199",
 | 
					                "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f",
 | 
				
			||||||
                "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"
 | 
					                "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            "markers": "python_version >= '3.8'",
 | 
					            "markers": "python_version >= '3.8'",
 | 
				
			||||||
            "version": "==2.18.0"
 | 
					            "version": "==2.19.1"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "pymdown-extensions": {
 | 
					        "pymdown-extensions": {
 | 
				
			||||||
            "hashes": [
 | 
					            "hashes": [
 | 
				
			||||||
                "sha256:49f81412242d3527b8b4967b990df395c89563043bc51a3d2d7d500e52123b77",
 | 
					                "sha256:202481f716cc8250e4be8fce997781ebf7917701b59652458ee47f2401f818b5",
 | 
				
			||||||
                "sha256:b0ee1e0b2bef1071a47891ab17003bfe5bf824a398e13f49f8ed653b699369a7"
 | 
					                "sha256:741bd7c4ff961ba40b7528d32284c53bc436b8b1645e8e37c3e57770b8700a34"
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            "markers": "python_version >= '3.8'",
 | 
					            "markers": "python_version >= '3.8'",
 | 
				
			||||||
            "version": "==10.12"
 | 
					            "version": "==10.14"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "pyopenssl": {
 | 
					        "pyopenssl": {
 | 
				
			||||||
            "hashes": [
 | 
					            "hashes": [
 | 
				
			||||||
@ -3974,28 +3961,28 @@
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
        "ruff": {
 | 
					        "ruff": {
 | 
				
			||||||
            "hashes": [
 | 
					            "hashes": [
 | 
				
			||||||
                "sha256:0509e8da430228236a18a677fcdb0c1f102dd26d5520f71f79b094963322ed25",
 | 
					                "sha256:1a605fdcf6e8b2d39f9436d343d1f0ff70c365a1e681546de0104bef81ce88df",
 | 
				
			||||||
                "sha256:0c000a471d519b3e6cfc9c6680025d923b4ca140ce3e4612d1a2ef58e11f11fe",
 | 
					                "sha256:3292c5a22ea9a5f9a185e2d131dc7f98f8534a32fb6d2ee7b9944569239c648d",
 | 
				
			||||||
                "sha256:248b1fb3f739d01d528cc50b35ee9c4812aa58cc5935998e776bf8ed5b251e75",
 | 
					                "sha256:492a5e44ad9b22a0ea98cf72e40305cbdaf27fac0d927f8bc9e1df316dcc96eb",
 | 
				
			||||||
                "sha256:45a56f61b24682f6f6709636949ae8cc82ae229d8d773b4c76c09ec83964a95a",
 | 
					                "sha256:71cbe22e178c5da20e1514e1e01029c73dc09288a8028a5d3446e6bba87a5145",
 | 
				
			||||||
                "sha256:496dd38a53aa173481a7d8866bcd6451bd934d06976a2505028a50583e001b76",
 | 
					                "sha256:80605a039ba1454d002b32139e4970becf84b5fee3a3c3bf1c2af6f61a784347",
 | 
				
			||||||
                "sha256:52d587092ab8df308635762386f45f4638badb0866355b2b86760f6d3c076188",
 | 
					                "sha256:82b35259b0cbf8daa22a498018e300b9bb0174c2bbb7bcba593935158a78054d",
 | 
				
			||||||
                "sha256:54799ca3d67ae5e0b7a7ac234baa657a9c1784b48ec954a094da7c206e0365b1",
 | 
					                "sha256:8b6a9701d1e371bf41dca22015c3f89769da7576884d2add7317ec1ec8cb9c3c",
 | 
				
			||||||
                "sha256:61323159cf21bc3897674e5adb27cd9e7700bab6b84de40d7be28c3d46dc67cf",
 | 
					                "sha256:8efd9da7a1ee314b910da155ca7e8953094a7c10d0c0a39bfde3fcfd2a015684",
 | 
				
			||||||
                "sha256:7ae4478b1471fc0c44ed52a6fb787e641a2ac58b1c1f91763bafbc2faddc5117",
 | 
					                "sha256:9cc53e68b3c5ae41e8faf83a3b89f4a5d7b2cb666dff4b366bb86ed2a85b481f",
 | 
				
			||||||
                "sha256:7d7fc2377a04b6e04ffe588caad613d0c460eb2ecba4c0ccbbfe2bc973cbc162",
 | 
					                "sha256:a1b63fa24149918f8b37cef2ee6fff81f24f0d74b6f0bdc37bc3e1f2143e41c6",
 | 
				
			||||||
                "sha256:91a7ddb221779871cf226100e677b5ea38c2d54e9e2c8ed847450ebbdf99b32d",
 | 
					                "sha256:af1e9e9fe7b1f767264d26b1075ac4ad831c7db976911fa362d09b2d0356426a",
 | 
				
			||||||
                "sha256:9257aa841e9e8d9b727423086f0fa9a86b6b420fbf4bf9e1465d1250ce8e4d8d",
 | 
					                "sha256:b338edc4610142355ccf6b87bd356729b62bf1bc152a2fad5b0c7dc04af77bfe",
 | 
				
			||||||
                "sha256:bc3c083c50390cf69e7e1b5a5a7303898966be973664ec0c4a4acea82c1d4315",
 | 
					                "sha256:b5eceb334d55fae5f316f783437392642ae18e16dcf4f1858d55d3c2a0f8f5d0",
 | 
				
			||||||
                "sha256:dcad24b81b62650b0eb8814f576fc65cfee8674772a6e24c9b747911801eeaa5",
 | 
					                "sha256:b9aab82bb20afd5f596527045c01e6ae25a718ff1784cb92947bff1f83068b00",
 | 
				
			||||||
                "sha256:defed167955d42c68b407e8f2e6f56ba52520e790aba4ca707a9c88619e580e3",
 | 
					                "sha256:c547f7f256aa366834829a08375c297fa63386cbe5f1459efaf174086b564247",
 | 
				
			||||||
                "sha256:e169ea1b9eae61c99b257dc83b9ee6c76f89042752cb2d83486a7d6e48e8f764",
 | 
					                "sha256:c5e1d6abc798419cf46eed03f54f2e0c3adb1ad4b801119dedf23fcaf69b55b5",
 | 
				
			||||||
                "sha256:e88b8f6d901477c41559ba540beeb5a671e14cd29ebd5683903572f4b40a9807",
 | 
					                "sha256:d18bba3d3353ed916e882521bc3e0af403949dbada344c20c16ea78f47af965e",
 | 
				
			||||||
                "sha256:f1d70bef3d16fdc897ee290d7d20da3cbe4e26349f62e8a0274e7a3f4ce7a905"
 | 
					                "sha256:fbd337bac1cfa96be615f6efcd4bc4d077edbc127ef30e2b8ba2a27e18c054d4"
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            "index": "pypi",
 | 
					            "index": "pypi",
 | 
				
			||||||
            "markers": "python_version >= '3.7'",
 | 
					            "markers": "python_version >= '3.7'",
 | 
				
			||||||
            "version": "==0.8.6"
 | 
					            "version": "==0.9.2"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "scipy": {
 | 
					        "scipy": {
 | 
				
			||||||
            "hashes": [
 | 
					            "hashes": [
 | 
				
			||||||
@ -4113,11 +4100,11 @@
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
        "urllib3": {
 | 
					        "urllib3": {
 | 
				
			||||||
            "hashes": [
 | 
					            "hashes": [
 | 
				
			||||||
                "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac",
 | 
					                "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df",
 | 
				
			||||||
                "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"
 | 
					                "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            "markers": "python_version >= '3.8'",
 | 
					            "markers": "python_version >= '3.9'",
 | 
				
			||||||
            "version": "==2.2.3"
 | 
					            "version": "==2.3.0"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "virtualenv": {
 | 
					        "virtualenv": {
 | 
				
			||||||
            "hashes": [
 | 
					            "hashes": [
 | 
				
			||||||
 | 
				
			|||||||
@ -308,7 +308,7 @@ Paperless provides the following variables for use within filenames:
 | 
				
			|||||||
-   `{{ tag_list }}`: A comma separated list of all tags assigned to the
 | 
					-   `{{ tag_list }}`: A comma separated list of all tags assigned to the
 | 
				
			||||||
    document.
 | 
					    document.
 | 
				
			||||||
-   `{{ title }}`: The title of the document.
 | 
					-   `{{ title }}`: The title of the document.
 | 
				
			||||||
-   `{{ created }}`: The full date (ISO format) the document was created.
 | 
					-   `{{ created }}`: The full date (ISO 8601 format, e.g. `2024-03-14`) the document was created.
 | 
				
			||||||
-   `{{ created_year }}`: Year created only, formatted as the year with
 | 
					-   `{{ created_year }}`: Year created only, formatted as the year with
 | 
				
			||||||
    century.
 | 
					    century.
 | 
				
			||||||
-   `{{ created_year_short }}`: Year created only, formatted as the year
 | 
					-   `{{ created_year_short }}`: Year created only, formatted as the year
 | 
				
			||||||
@ -476,7 +476,7 @@ a document with an ASN of 355 would be placed in `somepath/asn-201-400/asn-3xx/T
 | 
				
			|||||||
/{{ title }}
 | 
					/{{ title }}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
For a PDF document, it would result in `pdfs/Title.pdf`, but for a PNG document, the path would be `pngs/Title.pdf`.
 | 
					For a PDF document, it would result in `pdfs/Title.pdf`, but for a PNG document, the path would be `pngs/Title.png`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
To use custom fields:
 | 
					To use custom fields:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -299,7 +299,7 @@ permissions can be granted to limit access to certain parts of the UI (and corre
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#### Superusers
 | 
					#### Superusers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Superusers can access all parts of the front and backend application as well as any and all objects.
 | 
					Superusers can access all parts of the front and backend application as well as any and all objects. Superuser status can only be granted by another superuser.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### Admin Status
 | 
					#### Admin Status
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@ import os
 | 
				
			|||||||
# See https://docs.gunicorn.org/en/stable/settings.html for
 | 
					# See https://docs.gunicorn.org/en/stable/settings.html for
 | 
				
			||||||
# explanations of settings
 | 
					# explanations of settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bind = f'{os.getenv("PAPERLESS_BIND_ADDR", "[::]")}:{os.getenv("PAPERLESS_PORT", 8000)}'
 | 
					bind = f"{os.getenv('PAPERLESS_BIND_ADDR', '[::]')}:{os.getenv('PAPERLESS_PORT', 8000)}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
workers = int(os.getenv("PAPERLESS_WEBSERVER_WORKERS", 1))
 | 
					workers = int(os.getenv("PAPERLESS_WEBSERVER_WORKERS", 1))
 | 
				
			||||||
worker_class = "paperless.workers.ConfigurableWorker"
 | 
					worker_class = "paperless.workers.ConfigurableWorker"
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										7
									
								
								src-ui/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7
									
								
								src-ui/package-lock.json
									
									
									
										generated
									
									
									
								
							@ -18107,10 +18107,11 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/undici": {
 | 
					    "node_modules/undici": {
 | 
				
			||||||
      "version": "5.28.4",
 | 
					      "version": "5.28.5",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.5.tgz",
 | 
				
			||||||
      "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==",
 | 
					      "integrity": "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "license": "MIT",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@fastify/busboy": "^2.0.0"
 | 
					        "@fastify/busboy": "^2.0.0"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 | 
				
			|||||||
@ -160,4 +160,23 @@ describe('UserEditDialogComponent', () => {
 | 
				
			|||||||
    })
 | 
					    })
 | 
				
			||||||
    expect(component.currentUserIsSuperUser).toBeTruthy()
 | 
					    expect(component.currentUserIsSuperUser).toBeTruthy()
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should disable superuser option if current user is not superuser', () => {
 | 
				
			||||||
 | 
					    const control: AbstractControl = component.objectForm.get('is_superuser')
 | 
				
			||||||
 | 
					    permissionsService.initialize([], {
 | 
				
			||||||
 | 
					      id: 99,
 | 
				
			||||||
 | 
					      username: 'user99',
 | 
				
			||||||
 | 
					      is_superuser: false,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    component.ngOnInit()
 | 
				
			||||||
 | 
					    expect(control.disabled).toBeTruthy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    permissionsService.initialize([], {
 | 
				
			||||||
 | 
					      id: 99,
 | 
				
			||||||
 | 
					      username: 'user99',
 | 
				
			||||||
 | 
					      is_superuser: true,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    component.ngOnInit()
 | 
				
			||||||
 | 
					    expect(control.disabled).toBeFalsy()
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
				
			|||||||
@ -60,6 +60,11 @@ export class UserEditDialogComponent
 | 
				
			|||||||
  ngOnInit(): void {
 | 
					  ngOnInit(): void {
 | 
				
			||||||
    super.ngOnInit()
 | 
					    super.ngOnInit()
 | 
				
			||||||
    this.onToggleSuperUser()
 | 
					    this.onToggleSuperUser()
 | 
				
			||||||
 | 
					    if (!this.currentUserIsSuperUser) {
 | 
				
			||||||
 | 
					      this.objectForm.get('is_superuser').disable()
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this.objectForm.get('is_superuser').enable()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getCreateTitle() {
 | 
					  getCreateTitle() {
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,7 @@
 | 
				
			|||||||
          (change)="onChange(value)">
 | 
					          (change)="onChange(value)">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <ng-template ng-label-tmp let-item="item">
 | 
					          <ng-template ng-label-tmp let-item="item">
 | 
				
			||||||
            <button class="tag-wrap btn p-0" (click)="removeTag($event, item.id)" title="Remove tag" i18n-title>
 | 
					            <button class="tag-wrap btn p-0 d-flex align-items-center" (click)="removeTag($event, item.id)" title="Remove tag" i18n-title>
 | 
				
			||||||
              <i-bs name="x" style="margin-inline-end: 1px;"></i-bs>
 | 
					              <i-bs name="x" style="margin-inline-end: 1px;"></i-bs>
 | 
				
			||||||
              @if (item.id && tags) {
 | 
					              @if (item.id && tags) {
 | 
				
			||||||
                <pngx-tag style="background-color: none;" [tag]="getTag(item.id)"></pngx-tag>
 | 
					                <pngx-tag style="background-color: none;" [tag]="getTag(item.id)"></pngx-tag>
 | 
				
			||||||
 | 
				
			|||||||
@ -48,7 +48,6 @@
 | 
				
			|||||||
                  i18n-title
 | 
					                  i18n-title
 | 
				
			||||||
                  buttonClasses=" btn-outline-secondary"
 | 
					                  buttonClasses=" btn-outline-secondary"
 | 
				
			||||||
                  iconName="arrow-repeat"
 | 
					                  iconName="arrow-repeat"
 | 
				
			||||||
                  [disabled]="!hasUsablePassword"
 | 
					 | 
				
			||||||
                  (confirm)="generateAuthToken()">
 | 
					                  (confirm)="generateAuthToken()">
 | 
				
			||||||
                </pngx-confirm-button>
 | 
					                </pngx-confirm-button>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,8 @@
 | 
				
			|||||||
a {
 | 
					a, span {
 | 
				
			||||||
    cursor: pointer;
 | 
					    cursor: pointer;
 | 
				
			||||||
    white-space: normal;
 | 
					    white-space: normal;
 | 
				
			||||||
    word-break: break-word;
 | 
					    word-break: break-word;
 | 
				
			||||||
    text-align: end;
 | 
					    text-align: start;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.private {
 | 
					.private {
 | 
				
			||||||
 | 
				
			|||||||
@ -45,7 +45,7 @@
 | 
				
			|||||||
      <li class="list-group-item">
 | 
					      <li class="list-group-item">
 | 
				
			||||||
        <div class="row fade" [class.show]="showAccounts">
 | 
					        <div class="row fade" [class.show]="showAccounts">
 | 
				
			||||||
          <div class="col d-flex align-items-center">
 | 
					          <div class="col d-flex align-items-center">
 | 
				
			||||||
            <button class="btn btn-link p-0 text-start" type="button" (click)="editMailAccount(account)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.MailAccount)">
 | 
					            <button class="btn btn-link p-0 text-start" type="button" (click)="editMailAccount(account)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.MailAccount) || !userCanEdit(account)">
 | 
				
			||||||
              {{account.name}}@switch (account.account_type) {
 | 
					              {{account.name}}@switch (account.account_type) {
 | 
				
			||||||
                @case (MailAccountType.IMAP) {<i-bs name="envelope-at-fill" class="ms-2"></i-bs>}
 | 
					                @case (MailAccountType.IMAP) {<i-bs name="envelope-at-fill" class="ms-2"></i-bs>}
 | 
				
			||||||
                @case (MailAccountType.Gmail_OAuth) {<i-bs name="google" class="ms-2"></i-bs>}
 | 
					                @case (MailAccountType.Gmail_OAuth) {<i-bs name="google" class="ms-2"></i-bs>}
 | 
				
			||||||
@ -62,10 +62,10 @@
 | 
				
			|||||||
                  <i-bs name="three-dots-vertical"></i-bs>
 | 
					                  <i-bs name="three-dots-vertical"></i-bs>
 | 
				
			||||||
                </button>
 | 
					                </button>
 | 
				
			||||||
                <div ngbDropdownMenu aria-labelledby="actionsMenuMobile">
 | 
					                <div ngbDropdownMenu aria-labelledby="actionsMenuMobile">
 | 
				
			||||||
                  <button (click)="editMailAccount(account)" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailAccount }" ngbDropdownItem i18n>Edit</button>
 | 
					                  <button (click)="editMailAccount(account)" [disabled]="!userCanEdit(account)" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailAccount }" ngbDropdownItem i18n>Edit</button>
 | 
				
			||||||
                  <button (click)="editPermissions(account)" *pngxIfOwner="account" ngbDropdownItem i18n>Permissions</button>
 | 
					                  <button (click)="editPermissions(account)" *pngxIfOwner="account" ngbDropdownItem i18n>Permissions</button>
 | 
				
			||||||
                  <button (click)="deleteMailAccount(account)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.MailAccount }" ngbDropdownItem i18n>Delete</button>
 | 
					                  <button (click)="deleteMailAccount(account)" [disabled]="!userIsOwner(account)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.MailAccount }" ngbDropdownItem i18n>Delete</button>
 | 
				
			||||||
                  <button (click)="processAccount(account)" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailAccount }" ngbDropdownItem i18n>Process Mail</button>
 | 
					                  <button (click)="processAccount(account)" [disabled]="!userIsOwner(account)" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailAccount }" ngbDropdownItem i18n>Process Mail</button>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
@ -82,7 +82,7 @@
 | 
				
			|||||||
                </button>
 | 
					                </button>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
              <div class="btn-group">
 | 
					              <div class="btn-group">
 | 
				
			||||||
                <button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailAccount }" class="btn btn-sm btn-outline-secondary" type="button" (click)="processAccount(account)">
 | 
					                <button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailAccount }" [disabled]="!userIsOwner(account)" class="btn btn-sm btn-outline-secondary" type="button" (click)="processAccount(account)">
 | 
				
			||||||
                  <i-bs width="1em" height="1em" name="arrow-clockwise"></i-bs> <ng-container i18n>Process Mail</ng-container>
 | 
					                  <i-bs width="1em" height="1em" name="arrow-clockwise"></i-bs> <ng-container i18n>Process Mail</ng-container>
 | 
				
			||||||
                </button>
 | 
					                </button>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
@ -126,7 +126,7 @@
 | 
				
			|||||||
    @for (rule of mailRules; track rule) {
 | 
					    @for (rule of mailRules; track rule) {
 | 
				
			||||||
      <li class="list-group-item">
 | 
					      <li class="list-group-item">
 | 
				
			||||||
        <div class="row fade" [class.show]="showRules">
 | 
					        <div class="row fade" [class.show]="showRules">
 | 
				
			||||||
          <div class="col d-flex align-items-center"><button class="btn btn-link p-0 text-start" type="button" (click)="editMailRule(rule)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.MailRule)">{{rule.name}}</button></div>
 | 
					          <div class="col d-flex align-items-center"><button class="btn btn-link p-0 text-start" type="button" (click)="editMailRule(rule)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.MailRule) || !userCanEdit(rule)">{{rule.name}}</button></div>
 | 
				
			||||||
          <div class="col d-flex align-items-center d-none d-sm-flex">{{rule.order}}</div>
 | 
					          <div class="col d-flex align-items-center d-none d-sm-flex">{{rule.order}}</div>
 | 
				
			||||||
          <div class="col d-flex align-items-center">{{(mailAccountService.getCached(rule.account) | async)?.name}}</div>
 | 
					          <div class="col d-flex align-items-center">{{(mailAccountService.getCached(rule.account) | async)?.name}}</div>
 | 
				
			||||||
          <div class="col d-flex align-items-center d-none d-sm-flex">
 | 
					          <div class="col d-flex align-items-center d-none d-sm-flex">
 | 
				
			||||||
@ -144,9 +144,9 @@
 | 
				
			|||||||
                  <i-bs name="three-dots-vertical"></i-bs>
 | 
					                  <i-bs name="three-dots-vertical"></i-bs>
 | 
				
			||||||
                </button>
 | 
					                </button>
 | 
				
			||||||
                <div ngbDropdownMenu aria-labelledby="actionsMenuMobile">
 | 
					                <div ngbDropdownMenu aria-labelledby="actionsMenuMobile">
 | 
				
			||||||
                  <button (click)="editMailRule(rule)" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailRule }" ngbDropdownItem i18n>Edit</button>
 | 
					                  <button (click)="editMailRule(rule)" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailRule }" [disabled]="!userCanEdit(rule)" ngbDropdownItem i18n>Edit</button>
 | 
				
			||||||
                  <button (click)="editPermissions(rule)" *pngxIfOwner="rule" ngbDropdownItem i18n>Permissions</button>
 | 
					                  <button (click)="editPermissions(rule)" *pngxIfOwner="rule" ngbDropdownItem i18n>Permissions</button>
 | 
				
			||||||
                  <button (click)="deleteMailRule(rule)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.MailRule }" ngbDropdownItem i18n>Delete</button>
 | 
					                  <button (click)="deleteMailRule(rule)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.MailRule }" [disabled]="!userIsOwner(rule)" ngbDropdownItem i18n>Delete</button>
 | 
				
			||||||
                  <button (click)="copyMailRule(rule)" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.MailRule }" ngbDropdownItem i18n>Copy</button>
 | 
					                  <button (click)="copyMailRule(rule)" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.MailRule }" ngbDropdownItem i18n>Copy</button>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,9 @@ import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
 | 
				
			|||||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
 | 
					import { provideHttpClientTesting } from '@angular/common/http/testing'
 | 
				
			||||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
					import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
				
			||||||
import { By } from '@angular/platform-browser'
 | 
					import { By } from '@angular/platform-browser'
 | 
				
			||||||
 | 
					import { RouterModule } from '@angular/router'
 | 
				
			||||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
 | 
					import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
 | 
				
			||||||
 | 
					import { routes } from 'src/app/app-routing.module'
 | 
				
			||||||
import { LogoComponent } from '../common/logo/logo.component'
 | 
					import { LogoComponent } from '../common/logo/logo.component'
 | 
				
			||||||
import { NotFoundComponent } from './not-found.component'
 | 
					import { NotFoundComponent } from './not-found.component'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -16,6 +18,7 @@ describe('NotFoundComponent', () => {
 | 
				
			|||||||
        NgxBootstrapIconsModule.pick(allIcons),
 | 
					        NgxBootstrapIconsModule.pick(allIcons),
 | 
				
			||||||
        NotFoundComponent,
 | 
					        NotFoundComponent,
 | 
				
			||||||
        LogoComponent,
 | 
					        LogoComponent,
 | 
				
			||||||
 | 
					        RouterModule.forRoot(routes),
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
      providers: [
 | 
					      providers: [
 | 
				
			||||||
        provideHttpClient(withInterceptorsFromDi()),
 | 
					        provideHttpClient(withInterceptorsFromDi()),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import { Component } from '@angular/core'
 | 
					import { Component } from '@angular/core'
 | 
				
			||||||
 | 
					import { RouterModule } from '@angular/router'
 | 
				
			||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
 | 
					import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
 | 
				
			||||||
import { LogoComponent } from '../common/logo/logo.component'
 | 
					import { LogoComponent } from '../common/logo/logo.component'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -6,7 +7,7 @@ import { LogoComponent } from '../common/logo/logo.component'
 | 
				
			|||||||
  selector: 'pngx-not-found',
 | 
					  selector: 'pngx-not-found',
 | 
				
			||||||
  templateUrl: './not-found.component.html',
 | 
					  templateUrl: './not-found.component.html',
 | 
				
			||||||
  styleUrls: ['./not-found.component.scss'],
 | 
					  styleUrls: ['./not-found.component.scss'],
 | 
				
			||||||
  imports: [LogoComponent, NgxBootstrapIconsModule],
 | 
					  imports: [LogoComponent, NgxBootstrapIconsModule, RouterModule],
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
export class NotFoundComponent {
 | 
					export class NotFoundComponent {
 | 
				
			||||||
  constructor() {}
 | 
					  constructor() {}
 | 
				
			||||||
 | 
				
			|||||||
@ -8381,7 +8381,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
 | 
					          <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
 | 
				
			||||||
          <context context-type="linenumber">285</context>
 | 
					          <context context-type="linenumber">285</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="translated">Ripristina filtri / selezione</target>
 | 
					        <target state="translated">Azzera filtri / selezione</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="4135055128446167640" datatype="html">
 | 
					      <trans-unit id="4135055128446167640" datatype="html">
 | 
				
			||||||
        <source>Open first [selected] document</source>
 | 
					        <source>Open first [selected] document</source>
 | 
				
			||||||
@ -9349,7 +9349,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">4</context>
 | 
					          <context context-type="linenumber">4</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Customize the views of your documents.</target>
 | 
					        <target state="translated">Personalizza la vista dei tuoi documenti.</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="6338800642797811873" datatype="html">
 | 
					      <trans-unit id="6338800642797811873" datatype="html">
 | 
				
			||||||
        <source>Documents page size</source>
 | 
					        <source>Documents page size</source>
 | 
				
			||||||
@ -9421,7 +9421,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.ts</context>
 | 
					          <context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.ts</context>
 | 
				
			||||||
          <context context-type="linenumber">163</context>
 | 
					          <context context-type="linenumber">163</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Error while saving views.</target>
 | 
					        <target state="translated">Errore durante il salvataggio delle viste.</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="5101757640976222639" datatype="html">
 | 
					      <trans-unit id="5101757640976222639" datatype="html">
 | 
				
			||||||
        <source>storage path</source>
 | 
					        <source>storage path</source>
 | 
				
			||||||
 | 
				
			|||||||
@ -452,7 +452,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/app.component.ts</context>
 | 
					          <context context-type="sourcefile">src/app/app.component.ts</context>
 | 
				
			||||||
          <context context-type="linenumber">183</context>
 | 
					          <context context-type="linenumber">183</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="translated">Dra-og-slipp dokumenter hit for å laste dem opp, eller plasser dem i opplastningsmappen. Du kan også dra-og-slippe dokumenter hvor som helst på alle andre sider av nettsiden. Da vil Paperless-ngx starte å trene opp maskinlæringsalgoritmene sine.</target>
 | 
					        <target state="translated">Dra og slipp dokumenter hit for å laste dem opp, eller plasser dem i opplastningsmappen. Du kan også dra og slippe dokumenter hvor som helst på alle andre sider av nettsiden. Da vil Paperless-ngx starte å trene opp maskinlæringsalgoritmene sine.</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="7495498057594070122" datatype="html">
 | 
					      <trans-unit id="7495498057594070122" datatype="html">
 | 
				
			||||||
        <source>The documents list shows all of your documents and allows for filtering as well as bulk-editing. There are three different view styles: list, small cards and large cards. A list of documents currently opened for editing is shown in the sidebar.</source>
 | 
					        <source>The documents list shows all of your documents and allows for filtering as well as bulk-editing. There are three different view styles: list, small cards and large cards. A list of documents currently opened for editing is shown in the sidebar.</source>
 | 
				
			||||||
@ -524,7 +524,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/app.component.ts</context>
 | 
					          <context context-type="sourcefile">src/app/app.component.ts</context>
 | 
				
			||||||
          <context context-type="linenumber">238</context>
 | 
					          <context context-type="linenumber">238</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Check out the settings for various tweaks to the web app.</target>
 | 
					        <target state="translated">Sjekk ut innstillingene for forskjellige endringer du kan gjøre på applikasjonen.</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="7172877665285340082" datatype="html">
 | 
					      <trans-unit id="7172877665285340082" datatype="html">
 | 
				
			||||||
        <source>Thank you! 🙏</source>
 | 
					        <source>Thank you! 🙏</source>
 | 
				
			||||||
@ -572,7 +572,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">25</context>
 | 
					          <context context-type="linenumber">25</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Read the documentation about this setting</target>
 | 
					        <target state="translated">Les dokumentasjonen om denne innstillingen</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="2180291763949669799" datatype="html">
 | 
					      <trans-unit id="2180291763949669799" datatype="html">
 | 
				
			||||||
        <source>Enable</source>
 | 
					        <source>Enable</source>
 | 
				
			||||||
@ -1108,7 +1108,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/common/permissions-select/permissions-select.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/common/permissions-select/permissions-select.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">4</context>
 | 
					          <context context-type="linenumber">4</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">What's this?</target>
 | 
					        <target state="translated">Hva er dette?</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="6226301160429720843" datatype="html">
 | 
					      <trans-unit id="6226301160429720843" datatype="html">
 | 
				
			||||||
        <source> Update checking works by pinging the public GitHub API for the latest release to determine whether a new version is available. Actual updating of the app must still be performed manually. </source>
 | 
					        <source> Update checking works by pinging the public GitHub API for the latest release to determine whether a new version is available. Actual updating of the app must still be performed manually. </source>
 | 
				
			||||||
@ -1724,7 +1724,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">16</context>
 | 
					          <context context-type="linenumber">16</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Filter by</target>
 | 
					        <target state="translated">Filtrer etter</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="8953033926734869941" datatype="html">
 | 
					      <trans-unit id="8953033926734869941" datatype="html">
 | 
				
			||||||
        <source>Name</source>
 | 
					        <source>Name</source>
 | 
				
			||||||
@ -1968,7 +1968,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
 | 
					          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
 | 
				
			||||||
          <context context-type="linenumber">157</context>
 | 
					          <context context-type="linenumber">157</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="translated">Avvis</target>
 | 
					        <target state="translated">Fjern varsel</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="2134950584701094962" datatype="html">
 | 
					      <trans-unit id="2134950584701094962" datatype="html">
 | 
				
			||||||
        <source>Open Document</source>
 | 
					        <source>Open Document</source>
 | 
				
			||||||
@ -2016,7 +2016,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">164,166</context>
 | 
					          <context context-type="linenumber">164,166</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Started<x id="START_BLOCK_IF" equiv-text="@if (tasksService.startedFileTasks.length > 0) {"/><x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span class="badge bg-secondary ms-2">"/><x id="INTERPOLATION" equiv-text="{{tasksService.startedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/><x id="CLOSE_BLOCK_IF" equiv-text="}"/></target>
 | 
					        <target state="translated">Startet<x id="START_BLOCK_IF" equiv-text="@if (tasksService.startedFileTasks.length > 0) {"/><x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span class="badge bg-secondary ms-2">"/><x id="INTERPOLATION" equiv-text="{{tasksService.startedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/><x id="CLOSE_BLOCK_IF" equiv-text="}"/></target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="2341807459308874922" datatype="html">
 | 
					      <trans-unit id="2341807459308874922" datatype="html">
 | 
				
			||||||
        <source>Queued<x id="START_BLOCK_IF" equiv-text="@if (tasksService.queuedFileTasks.length > 0) {"/><x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span class="badge bg-secondary ms-2">"/><x id="INTERPOLATION" equiv-text="{{tasksService.queuedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/><x id="CLOSE_BLOCK_IF" equiv-text="}"/></source>
 | 
					        <source>Queued<x id="START_BLOCK_IF" equiv-text="@if (tasksService.queuedFileTasks.length > 0) {"/><x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span class="badge bg-secondary ms-2">"/><x id="INTERPOLATION" equiv-text="{{tasksService.queuedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/><x id="CLOSE_BLOCK_IF" equiv-text="}"/></source>
 | 
				
			||||||
@ -2024,7 +2024,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">172,174</context>
 | 
					          <context context-type="linenumber">172,174</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Queued<x id="START_BLOCK_IF" equiv-text="@if (tasksService.queuedFileTasks.length > 0) {"/><x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span class="badge bg-secondary ms-2">"/><x id="INTERPOLATION" equiv-text="{{tasksService.queuedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/><x id="CLOSE_BLOCK_IF" equiv-text="}"/></target>
 | 
					        <target state="translated">I kø:<x id="START_BLOCK_IF" equiv-text="@if (tasksService.queuedFileTasks.length > 0) {"/><x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span class="badge bg-secondary ms-2">"/><x id="INTERPOLATION" equiv-text="{{tasksService.queuedFileTasks.length}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span>"/><x id="CLOSE_BLOCK_IF" equiv-text="}"/></target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="2525230676386818985" datatype="html">
 | 
					      <trans-unit id="2525230676386818985" datatype="html">
 | 
				
			||||||
        <source>Result</source>
 | 
					        <source>Result</source>
 | 
				
			||||||
@ -2032,7 +2032,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
 | 
					          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
 | 
				
			||||||
          <context context-type="linenumber">45</context>
 | 
					          <context context-type="linenumber">45</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Result</target>
 | 
					        <target state="translated">Resultat</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="5404910960991552159" datatype="html">
 | 
					      <trans-unit id="5404910960991552159" datatype="html">
 | 
				
			||||||
        <source>Dismiss selected</source>
 | 
					        <source>Dismiss selected</source>
 | 
				
			||||||
@ -2040,7 +2040,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
 | 
					          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
 | 
				
			||||||
          <context context-type="linenumber">104</context>
 | 
					          <context context-type="linenumber">104</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="translated">Avvis valgte</target>
 | 
					        <target state="translated">Fjern valgte varsel</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="8829078752502782653" datatype="html">
 | 
					      <trans-unit id="8829078752502782653" datatype="html">
 | 
				
			||||||
        <source>Dismiss all</source>
 | 
					        <source>Dismiss all</source>
 | 
				
			||||||
@ -2048,7 +2048,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
 | 
					          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
 | 
				
			||||||
          <context context-type="linenumber">105</context>
 | 
					          <context context-type="linenumber">105</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="translated">Avvis alle</target>
 | 
					        <target state="translated">Kvitter ut alle beskjeder</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="1323591410517879795" datatype="html">
 | 
					      <trans-unit id="1323591410517879795" datatype="html">
 | 
				
			||||||
        <source>Confirm Dismiss All</source>
 | 
					        <source>Confirm Dismiss All</source>
 | 
				
			||||||
@ -2056,7 +2056,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
 | 
					          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
 | 
				
			||||||
          <context context-type="linenumber">154</context>
 | 
					          <context context-type="linenumber">154</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="translated">Bekreft avvisning av alle</target>
 | 
					        <target state="translated">Bekreft kvittering av alle</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="4157200209636243740" datatype="html">
 | 
					      <trans-unit id="4157200209636243740" datatype="html">
 | 
				
			||||||
        <source>Dismiss all <x id="PH" equiv-text="tasks.size"/> tasks?</source>
 | 
					        <source>Dismiss all <x id="PH" equiv-text="tasks.size"/> tasks?</source>
 | 
				
			||||||
@ -2064,7 +2064,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
 | 
					          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
 | 
				
			||||||
          <context context-type="linenumber">155</context>
 | 
					          <context context-type="linenumber">155</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Dismiss all <x id="PH" equiv-text="tasks.size"/> tasks?</target>
 | 
					        <target state="translated">Fjern alle <x id="PH" equiv-text="tasks.size"/> oppgaver?</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="9011556615675272238" datatype="html">
 | 
					      <trans-unit id="9011556615675272238" datatype="html">
 | 
				
			||||||
        <source>queued</source>
 | 
					        <source>queued</source>
 | 
				
			||||||
@ -2120,7 +2120,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/admin/trash/trash.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/admin/trash/trash.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">4</context>
 | 
					          <context context-type="linenumber">4</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Manage trashed documents that are pending deletion.</target>
 | 
					        <target state="translated">Administrer forkastede dokumenter som venter på å bli slettet.</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="3186604097120837257" datatype="html">
 | 
					      <trans-unit id="3186604097120837257" datatype="html">
 | 
				
			||||||
        <source>Restore selected</source>
 | 
					        <source>Restore selected</source>
 | 
				
			||||||
@ -2328,7 +2328,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/admin/trash/trash.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/admin/trash/trash.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">94</context>
 | 
					          <context context-type="linenumber">94</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">{VAR_PLURAL, plural, =1 {One document in trash} other {<x id="INTERPOLATION"/> total documents in trash}}</target>
 | 
					        <target state="translated">{VAR_PLURAL, plural, one {}=1 {Ett dokument i papirkurven} other {<x id="INTERPOLATION"/> totalt antall dokumenter i papirkurven}}</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="9021887951960049161" datatype="html">
 | 
					      <trans-unit id="9021887951960049161" datatype="html">
 | 
				
			||||||
        <source>Confirm delete</source>
 | 
					        <source>Confirm delete</source>
 | 
				
			||||||
@ -3912,7 +3912,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">37</context>
 | 
					          <context context-type="linenumber">37</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Use locale</target>
 | 
					        <target state="translated">Bruk nasjonale innstillinger</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="528950215505228201" datatype="html">
 | 
					      <trans-unit id="528950215505228201" datatype="html">
 | 
				
			||||||
        <source>Create new custom field</source>
 | 
					        <source>Create new custom field</source>
 | 
				
			||||||
@ -6462,7 +6462,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context>
 | 
					          <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context>
 | 
				
			||||||
          <context context-type="linenumber">57</context>
 | 
					          <context context-type="linenumber">57</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Hello <x id="PH" equiv-text="this.settingsService.displayName"/>, welcome to <x id="PH_1" equiv-text="environment.appTitle"/></target>
 | 
					        <target state="translated">Hei <x id="PH" equiv-text="this.settingsService.displayName"/> og velkommen til <x id="PH_1" equiv-text="environment.appTitle"/></target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="2901300640157872718" datatype="html">
 | 
					      <trans-unit id="2901300640157872718" datatype="html">
 | 
				
			||||||
        <source>Welcome to <x id="PH" equiv-text="environment.appTitle"/></source>
 | 
					        <source>Welcome to <x id="PH" equiv-text="environment.appTitle"/></source>
 | 
				
			||||||
@ -6634,7 +6634,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">4</context>
 | 
					          <context context-type="linenumber">4</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Drop documents anywhere or</target>
 | 
					        <target state="translated">Slipp dokumenter her, eller</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="8133800334834354642" datatype="html">
 | 
					      <trans-unit id="8133800334834354642" datatype="html">
 | 
				
			||||||
        <source>Browse files</source>
 | 
					        <source>Browse files</source>
 | 
				
			||||||
@ -6651,7 +6651,7 @@
 | 
				
			|||||||
          <context context-type="linenumber">20</context>
 | 
					          <context context-type="linenumber">20</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <note priority="1" from="description">This button dismisses all status messages about processed documents on the dashboard (failed and successful)</note>
 | 
					        <note priority="1" from="description">This button dismisses all status messages about processed documents on the dashboard (failed and successful)</note>
 | 
				
			||||||
        <target state="translated">Avvis fullført</target>
 | 
					        <target state="translated">Kvittert ut alle varsler</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="2330646618997399019" datatype="html">
 | 
					      <trans-unit id="2330646618997399019" datatype="html">
 | 
				
			||||||
        <source>{VAR_PLURAL, plural, =1 {One more document} other {<x id="INTERPOLATION"/> more documents}}</source>
 | 
					        <source>{VAR_PLURAL, plural, =1 {One more document} other {<x id="INTERPOLATION"/> more documents}}</source>
 | 
				
			||||||
 | 
				
			|||||||
@ -1724,7 +1724,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">16</context>
 | 
					          <context context-type="linenumber">16</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Filter by</target>
 | 
					        <target state="translated">Фильтровать по</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="8953033926734869941" datatype="html" approved="yes">
 | 
					      <trans-unit id="8953033926734869941" datatype="html" approved="yes">
 | 
				
			||||||
        <source>Name</source>
 | 
					        <source>Name</source>
 | 
				
			||||||
@ -2032,7 +2032,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
 | 
					          <context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
 | 
				
			||||||
          <context context-type="linenumber">45</context>
 | 
					          <context context-type="linenumber">45</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Result</target>
 | 
					        <target state="translated">Результат</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="5404910960991552159" datatype="html">
 | 
					      <trans-unit id="5404910960991552159" datatype="html">
 | 
				
			||||||
        <source>Dismiss selected</source>
 | 
					        <source>Dismiss selected</source>
 | 
				
			||||||
@ -2152,7 +2152,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/admin/trash/trash.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/admin/trash/trash.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">36</context>
 | 
					          <context context-type="linenumber">36</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Remaining</target>
 | 
					        <target state="translated">Осталось</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="7494361412465596264" datatype="html">
 | 
					      <trans-unit id="7494361412465596264" datatype="html">
 | 
				
			||||||
        <source><x id="INTERPOLATION" equiv-text="{{ getDaysRemaining(document) }}"/> days</source>
 | 
					        <source><x id="INTERPOLATION" equiv-text="{{ getDaysRemaining(document) }}"/> days</source>
 | 
				
			||||||
@ -2440,7 +2440,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/admin/trash/trash.component.ts</context>
 | 
					          <context context-type="sourcefile">src/app/components/admin/trash/trash.component.ts</context>
 | 
				
			||||||
          <context context-type="linenumber">119</context>
 | 
					          <context context-type="linenumber">119</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Document(s) deleted</target>
 | 
					        <target state="translated">Документ(ы) удален(ы)</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="6962724852893361467" datatype="html">
 | 
					      <trans-unit id="6962724852893361467" datatype="html">
 | 
				
			||||||
        <source>Error deleting document(s)</source>
 | 
					        <source>Error deleting document(s)</source>
 | 
				
			||||||
@ -2448,7 +2448,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/admin/trash/trash.component.ts</context>
 | 
					          <context context-type="sourcefile">src/app/components/admin/trash/trash.component.ts</context>
 | 
				
			||||||
          <context context-type="linenumber">126</context>
 | 
					          <context context-type="linenumber">126</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Error deleting document(s)</target>
 | 
					        <target state="translated">Ошибка при удалении документам(ов)</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="7534569062269274401" datatype="html">
 | 
					      <trans-unit id="7534569062269274401" datatype="html">
 | 
				
			||||||
        <source>Document restored</source>
 | 
					        <source>Document restored</source>
 | 
				
			||||||
@ -2472,7 +2472,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/admin/trash/trash.component.ts</context>
 | 
					          <context context-type="sourcefile">src/app/components/admin/trash/trash.component.ts</context>
 | 
				
			||||||
          <context context-type="linenumber">159</context>
 | 
					          <context context-type="linenumber">159</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Document(s) restored</target>
 | 
					        <target state="translated">Документ(ы) восстановлены</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="8405416976953346141" datatype="html">
 | 
					      <trans-unit id="8405416976953346141" datatype="html">
 | 
				
			||||||
        <source>Error restoring document(s)</source>
 | 
					        <source>Error restoring document(s)</source>
 | 
				
			||||||
@ -2480,7 +2480,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/admin/trash/trash.component.ts</context>
 | 
					          <context context-type="sourcefile">src/app/components/admin/trash/trash.component.ts</context>
 | 
				
			||||||
          <context context-type="linenumber">165</context>
 | 
					          <context context-type="linenumber">165</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Error restoring document(s)</target>
 | 
					        <target state="translated">Ошибка при восстановлении документа(ов)</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="8119815638230251386" datatype="html">
 | 
					      <trans-unit id="8119815638230251386" datatype="html">
 | 
				
			||||||
        <source>Users & Groups</source>
 | 
					        <source>Users & Groups</source>
 | 
				
			||||||
@ -3184,7 +3184,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/app-frame/global-search/global-search.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/app-frame/global-search/global-search.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">62</context>
 | 
					          <context context-type="linenumber">62</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Filter documents</target>
 | 
					        <target state="translated">Отфильтровать документы</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="3099741642167775297" datatype="html" approved="yes">
 | 
					      <trans-unit id="3099741642167775297" datatype="html" approved="yes">
 | 
				
			||||||
        <source>Download</source>
 | 
					        <source>Download</source>
 | 
				
			||||||
@ -3228,7 +3228,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/app-frame/global-search/global-search.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/app-frame/global-search/global-search.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">90</context>
 | 
					          <context context-type="linenumber">90</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Documents</target>
 | 
					        <target state="translated">Документы</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="searchResults.saved_views" datatype="html">
 | 
					      <trans-unit id="searchResults.saved_views" datatype="html">
 | 
				
			||||||
        <source>Saved Views</source>
 | 
					        <source>Saved Views</source>
 | 
				
			||||||
@ -3244,7 +3244,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/app-frame/global-search/global-search.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/app-frame/global-search/global-search.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">103</context>
 | 
					          <context context-type="linenumber">103</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Tags</target>
 | 
					        <target state="translated">Теги</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="searchResults.correspondents" datatype="html">
 | 
					      <trans-unit id="searchResults.correspondents" datatype="html">
 | 
				
			||||||
        <source>Correspondents</source>
 | 
					        <source>Correspondents</source>
 | 
				
			||||||
@ -3480,7 +3480,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">32</context>
 | 
					          <context context-type="linenumber">32</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Delete original documents after successful merge</target>
 | 
					        <target state="translated">Удалить оригиналы после успешного объединения</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="5138283234724909648" datatype="html">
 | 
					      <trans-unit id="5138283234724909648" datatype="html">
 | 
				
			||||||
        <source>Note that only PDFs will be included.</source>
 | 
					        <source>Note that only PDFs will be included.</source>
 | 
				
			||||||
@ -3488,7 +3488,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">34</context>
 | 
					          <context context-type="linenumber">34</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Note that only PDFs will be included.</target>
 | 
					        <target state="translated">Только PDF файлы могут быть добавлены.</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="8157388568390631653" datatype="html">
 | 
					      <trans-unit id="8157388568390631653" datatype="html">
 | 
				
			||||||
        <source>Note that only PDFs will be rotated.</source>
 | 
					        <source>Note that only PDFs will be rotated.</source>
 | 
				
			||||||
@ -4240,7 +4240,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">46</context>
 | 
					          <context context-type="linenumber">46</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Include only files matching</target>
 | 
					        <target state="translated">Включить только соответствующие файлы</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="7233407036155150477" datatype="html">
 | 
					      <trans-unit id="7233407036155150477" datatype="html">
 | 
				
			||||||
        <source>Optional. Wildcards e.g. *.pdf or *invoice* allowed. Can be comma-separated list. Case insensitive.</source>
 | 
					        <source>Optional. Wildcards e.g. *.pdf or *invoice* allowed. Can be comma-separated list. Case insensitive.</source>
 | 
				
			||||||
@ -4252,7 +4252,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">47</context>
 | 
					          <context context-type="linenumber">47</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Optional. Wildcards e.g. *.pdf or *invoice* allowed. Can be comma-separated list. Case insensitive.</target>
 | 
					        <target state="translated">Необязательно. Допускается использовать символ подстановки (wildcard), например *.pdf or *invoice*. Может быть список, разделенным через запятую. Нечувствителен к регистру.</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="1546332577833742677" datatype="html">
 | 
					      <trans-unit id="1546332577833742677" datatype="html">
 | 
				
			||||||
        <source>Exclude files matching</source>
 | 
					        <source>Exclude files matching</source>
 | 
				
			||||||
@ -4260,7 +4260,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">47</context>
 | 
					          <context context-type="linenumber">47</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Exclude files matching</target>
 | 
					        <target state="translated">Исключить соответствующие файлы</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="9216117865911519658" datatype="html">
 | 
					      <trans-unit id="9216117865911519658" datatype="html">
 | 
				
			||||||
        <source>Action</source>
 | 
					        <source>Action</source>
 | 
				
			||||||
@ -4276,7 +4276,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">53</context>
 | 
					          <context context-type="linenumber">53</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Only performed if the mail is processed.</target>
 | 
					        <target state="translated">Выполнять только если письмо обработано.</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="1261794314435932203" datatype="html">
 | 
					      <trans-unit id="1261794314435932203" datatype="html">
 | 
				
			||||||
        <source>Action parameter</source>
 | 
					        <source>Action parameter</source>
 | 
				
			||||||
@ -4904,7 +4904,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">143</context>
 | 
					          <context context-type="linenumber">143</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Repeat the trigger every n days.</target>
 | 
					        <target state="translated">Повторять триггер каждые n дней.</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="8727727835543352574" datatype="html">
 | 
					      <trans-unit id="8727727835543352574" datatype="html">
 | 
				
			||||||
        <source>Trigger for documents that match <x id="START_EMPHASISED_TEXT" ctype="x-em" equiv-text="<em>"/>all<x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> filters specified below.</source>
 | 
					        <source>Trigger for documents that match <x id="START_EMPHASISED_TEXT" ctype="x-em" equiv-text="<em>"/>all<x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> filters specified below.</source>
 | 
				
			||||||
@ -4992,7 +4992,7 @@
 | 
				
			|||||||
          <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
 | 
				
			||||||
          <context context-type="linenumber">169</context>
 | 
					          <context context-type="linenumber">169</context>
 | 
				
			||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <target state="needs-translation">Has any of tags</target>
 | 
					        <target state="translated">Содержит любой из тегов</target>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="5281365940563983618" datatype="html">
 | 
					      <trans-unit id="5281365940563983618" datatype="html">
 | 
				
			||||||
        <source>Has correspondent</source>
 | 
					        <source>Has correspondent</source>
 | 
				
			||||||
 | 
				
			|||||||
@ -32,8 +32,7 @@ def changed_password_check(app_configs, **kwargs):
 | 
				
			|||||||
        if not settings.PASSPHRASE:
 | 
					        if not settings.PASSPHRASE:
 | 
				
			||||||
            return [
 | 
					            return [
 | 
				
			||||||
                Error(
 | 
					                Error(
 | 
				
			||||||
                    "The database contains encrypted documents but no password "
 | 
					                    "The database contains encrypted documents but no password is set.",
 | 
				
			||||||
                    "is set.",
 | 
					 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -170,6 +170,7 @@ class DocumentClassifier:
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
            .select_related("document_type", "correspondent", "storage_path")
 | 
					            .select_related("document_type", "correspondent", "storage_path")
 | 
				
			||||||
            .prefetch_related("tags")
 | 
					            .prefetch_related("tags")
 | 
				
			||||||
 | 
					            .order_by("pk")
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # No documents exit to train against
 | 
					        # No documents exit to train against
 | 
				
			||||||
@ -199,11 +200,10 @@ class DocumentClassifier:
 | 
				
			|||||||
            hasher.update(y.to_bytes(4, "little", signed=True))
 | 
					            hasher.update(y.to_bytes(4, "little", signed=True))
 | 
				
			||||||
            labels_correspondent.append(y)
 | 
					            labels_correspondent.append(y)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            tags: list[int] = sorted(
 | 
					            tags: list[int] = list(
 | 
				
			||||||
                tag.pk
 | 
					                doc.tags.filter(matching_algorithm=MatchingModel.MATCH_AUTO)
 | 
				
			||||||
                for tag in doc.tags.filter(
 | 
					                .order_by("pk")
 | 
				
			||||||
                    matching_algorithm=MatchingModel.MATCH_AUTO,
 | 
					                .values_list("pk", flat=True),
 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            for tag in tags:
 | 
					            for tag in tags:
 | 
				
			||||||
                hasher.update(tag.to_bytes(4, "little", signed=True))
 | 
					                hasher.update(tag.to_bytes(4, "little", signed=True))
 | 
				
			||||||
@ -315,8 +315,7 @@ class DocumentClassifier:
 | 
				
			|||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.correspondent_classifier = None
 | 
					            self.correspondent_classifier = None
 | 
				
			||||||
            logger.debug(
 | 
					            logger.debug(
 | 
				
			||||||
                "There are no correspondents. Not training correspondent "
 | 
					                "There are no correspondents. Not training correspondent classifier.",
 | 
				
			||||||
                "classifier.",
 | 
					 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if num_document_types > 0:
 | 
					        if num_document_types > 0:
 | 
				
			||||||
@ -326,8 +325,7 @@ class DocumentClassifier:
 | 
				
			|||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.document_type_classifier = None
 | 
					            self.document_type_classifier = None
 | 
				
			||||||
            logger.debug(
 | 
					            logger.debug(
 | 
				
			||||||
                "There are no document types. Not training document type "
 | 
					                "There are no document types. Not training document type classifier.",
 | 
				
			||||||
                "classifier.",
 | 
					 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if num_storage_paths > 0:
 | 
					        if num_storage_paths > 0:
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,5 @@
 | 
				
			|||||||
from email.encoders import encode_base64
 | 
					from email import message_from_bytes
 | 
				
			||||||
from email.mime.base import MIMEBase
 | 
					 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
from urllib.parse import quote
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from django.core.mail import EmailMessage
 | 
					from django.core.mail import EmailMessage
 | 
				
			||||||
@ -27,35 +25,14 @@ def send_email(
 | 
				
			|||||||
    if attachment:
 | 
					    if attachment:
 | 
				
			||||||
        # Something could be renaming the file concurrently so it can't be attached
 | 
					        # Something could be renaming the file concurrently so it can't be attached
 | 
				
			||||||
        with FileLock(settings.MEDIA_LOCK), attachment.open("rb") as f:
 | 
					        with FileLock(settings.MEDIA_LOCK), attachment.open("rb") as f:
 | 
				
			||||||
            file_content = f.read()
 | 
					            content = f.read()
 | 
				
			||||||
 | 
					            if attachment_mime_type == "message/rfc822":
 | 
				
			||||||
 | 
					                # See https://forum.djangoproject.com/t/using-emailmessage-with-an-attached-email-file-crashes-due-to-non-ascii/37981
 | 
				
			||||||
 | 
					                content = message_from_bytes(f.read())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            main_type, sub_type = (
 | 
					            email.attach(
 | 
				
			||||||
                attachment_mime_type.split("/", 1)
 | 
					                filename=attachment.name,
 | 
				
			||||||
                if attachment_mime_type
 | 
					                content=content,
 | 
				
			||||||
                else ("application", "octet-stream")
 | 
					                mimetype=attachment_mime_type,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            mime_part = MIMEBase(main_type, sub_type)
 | 
					 | 
				
			||||||
            mime_part.set_payload(file_content)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            encode_base64(mime_part)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # see https://github.com/stumpylog/tika-client/blob/f65a2b792fc3cf15b9b119501bba9bddfac15fcc/src/tika_client/_base.py#L46-L57
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                attachment.name.encode("ascii")
 | 
					 | 
				
			||||||
            except UnicodeEncodeError:
 | 
					 | 
				
			||||||
                filename_safed = attachment.name.encode("ascii", "ignore").decode(
 | 
					 | 
				
			||||||
                    "ascii",
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                filepath_quoted = quote(attachment.name, encoding="utf-8")
 | 
					 | 
				
			||||||
                mime_part.add_header(
 | 
					 | 
				
			||||||
                    "Content-Disposition",
 | 
					 | 
				
			||||||
                    f"attachment; filename={filename_safed}; filename*=UTF-8''{filepath_quoted}",
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                mime_part.add_header(
 | 
					 | 
				
			||||||
                    "Content-Disposition",
 | 
					 | 
				
			||||||
                    f"attachment; filename={attachment.name}",
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            email.attach(mime_part)
 | 
					 | 
				
			||||||
    return email.send()
 | 
					    return email.send()
 | 
				
			||||||
 | 
				
			|||||||
@ -18,8 +18,7 @@ class Command(BaseCommand):
 | 
				
			|||||||
        parser.add_argument(
 | 
					        parser.add_argument(
 | 
				
			||||||
            "--passphrase",
 | 
					            "--passphrase",
 | 
				
			||||||
            help=(
 | 
					            help=(
 | 
				
			||||||
                "If PAPERLESS_PASSPHRASE isn't set already, you need to "
 | 
					                "If PAPERLESS_PASSPHRASE isn't set already, you need to specify it here"
 | 
				
			||||||
                "specify it here"
 | 
					 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										63
									
								
								src/documents/tests/samples/eml_with_umlaut.eml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/documents/tests/samples/eml_with_umlaut.eml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
				
			|||||||
 | 
					From: =?UTF-8?Q?My_Name=C3=B6er?= <myaddr@volkswagen.de>
 | 
				
			||||||
 | 
					Return-Path: <myaddr@volkswagen.de>
 | 
				
			||||||
 | 
					X-Original-To: rechnung@domain.de
 | 
				
			||||||
 | 
					Delivered-To: rechnung@domain.de
 | 
				
			||||||
 | 
					DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=domainb.de; s=default;
 | 
				
			||||||
 | 
						t=1736973836; bh=bCUrrHd7c5mrvMbK20=;
 | 
				
			||||||
 | 
						h=Date:To:From:Subject:From;
 | 
				
			||||||
 | 
						b=QPaQKuzx2adfCr0S18KVgA5x01KXZknaaEpQW49Ock2ghScLAvv3ij8xfzUbZewCT
 | 
				
			||||||
 | 
						 CuUAYBmCxbN5ygIztJXfgWpl1Cx5FsVQNpdZ/6Ns=
 | 
				
			||||||
 | 
					Received: by mail.domain.de (Postfix, from userid 121)
 | 
				
			||||||
 | 
						id 407BCE078A; Wed, 15 Jan 2025 21:43:56 +0100 (CET)
 | 
				
			||||||
 | 
					X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on imail.domain.de
 | 
				
			||||||
 | 
					X-Spam-Level:
 | 
				
			||||||
 | 
					X-Spam-Status: No, score=-3.0 required=1.7 tests=ALL_TRUSTED,BAYES_00,
 | 
				
			||||||
 | 
						DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU autolearn=ham autolearn_force=no
 | 
				
			||||||
 | 
						version=4.0.0
 | 
				
			||||||
 | 
					DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=domain.de; s=default;
 | 
				
			||||||
 | 
						t=1736973835; bh=bCUrrHvn+Hd7c5mrvMbK20=;
 | 
				
			||||||
 | 
						h=Date:To:From:Subject:From;
 | 
				
			||||||
 | 
						b=AjGxzFALRR0AixC1uRhFuQkb4MoBqju1NInlUzx9w+toniNx3ifgkXpGxiV7+JJsr
 | 
				
			||||||
 | 
						 Z+jNZxck3D3M05ETYnrGInO+vDlosfFU2WqnZn+E=
 | 
				
			||||||
 | 
					Received: from [192.168.8.154] (unknown [1.1.1.1])
 | 
				
			||||||
 | 
						(using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits)
 | 
				
			||||||
 | 
						 key-exchange X25519 server-signature ECDSA (prime256v1) server-digest
 | 
				
			||||||
 | 
					 SHA256)
 | 
				
			||||||
 | 
						(No client certificate requested)
 | 
				
			||||||
 | 
						(Authenticated sender: myuser)
 | 
				
			||||||
 | 
						by mail.domain.de (Postfix) with ESMTPSA id C8BC6DF926
 | 
				
			||||||
 | 
						for <rechnung@domain.de>; Wed, 15 Jan 2025 21:43:55 +0100 (CET)
 | 
				
			||||||
 | 
					Message-ID: <da0c12c1-58c3-4f3d-ab89-9ae04@domain.de>
 | 
				
			||||||
 | 
					Date: Wed, 15 Jan 2025 21:43:52 +0100
 | 
				
			||||||
 | 
					MIME-Version: 1.0
 | 
				
			||||||
 | 
					User-Agent: Mozilla Thunderbird
 | 
				
			||||||
 | 
					Content-Language: de-DE
 | 
				
			||||||
 | 
					To: rechnung@domain.de
 | 
				
			||||||
 | 
					Subject: No Umlauts here
 | 
				
			||||||
 | 
					Autocrypt: addr=myaddr@domain.de; keydata=
 | 
				
			||||||
 | 
					 xsDiBEK4/dERBACj7Kn2Skjnyq/Q69FKLSd9WJg/7Ta3aZwWiaizzAnB/avBoN9/NPkVCQbB
 | 
				
			||||||
 | 
					 jeJ8G/uOtYDCgjmxeBNMVM3DOMTu4QfLnl0BoQz811bxiaPqQ6YLRA4MZrawZwerIOS2oSk2
 | 
				
			||||||
 | 
					 FDGKsZvAYCG439QK102XPlSPC7c4/oQ+3fwkeqFpEwCg4XYOfTNzis6CZPgkQqyVrpaYR5kD
 | 
				
			||||||
 | 
					 /j1HIDd1B75eeCb8ifoyWoWHB+cVHR+kEuMw1FMZt7UQ6Pb5nfQTcpEvrH9BTc0GKmTzj1N3
 | 
				
			||||||
 | 
					 ExOPaNaGtsc7FAST+5dYflfL1+WVzsNJWgIp6PoAL1XoCZ6l63/qOrHtnp6l42IO8Rg2lDcc
 | 
				
			||||||
 | 
					 25YdfiRSlTWuKvleT/okyc6jHioEA/9bUPbpdmUyR5kWRkdRBTjjCipl+o8rSlparnnk+7jh
 | 
				
			||||||
 | 
					 1cvOHJlNJ/MYP9vcgDGYFIv+38sY4+UuBBoNmSS7yN5yKpT+XIsSgMEvyRPP6lr1GJ76aT2v
 | 
				
			||||||
 | 
					 dIvcozHdC9g+nu6AlKgywdWW3hq5IjqRqnmVQfUN/1dL/D1ZImclEJoZR80lQ2hyaXN0aWFu
 | 
				
			||||||
 | 
					 IFZvZWxrZXIgPGN2b2Vsa2VyQGtuZWJiLmRlPsJ3BBMRCAA3AhsjBgsJCAcDAgQVAggDBBYC
 | 
				
			||||||
 | 
					 AwECHgECF4AWIQQl96acg1HmEUgEtrfRc0hiUBebOwUCXwQmvwAKCRDRc0hiUBebOxeiAJ43
 | 
				
			||||||
 | 
					 bk2DCMuEVho3wRUqEyhNk0/mwQCaA60n1eTn+6bs2WXttTVGkBJGadzOwU0EQrj92RAIAKJz
 | 
				
			||||||
 | 
					 rvwheohL4D327LEpy1AkIjUJotYUt9fPW+MVDSsoyj67HFTRz1WcK51+/8Fi6jedKxmR3hAi
 | 
				
			||||||
 | 
					 GlZRvpsJ2chOuaynMac0Uv42rnSGHcLZf0KxLG+r7HOPSEAnSrbDAhWbuqyV994vCIfG9LDz
 | 
				
			||||||
 | 
					 RDocaUEyJ7M+QV4VGS6Z3PPgxm78kCJ5TGHXRA96ponSptkyfIxvKHBa2TyrhMoLj4TmW4CO
 | 
				
			||||||
 | 
					 SHmQD2e3EVIYlhERdPEQ5DmCljeO19ZopjNOLcAx4eOyguwvjpdeLUQJdaryWo56USWKbrmU
 | 
				
			||||||
 | 
					 VrK4OodWkgcUvaagvey0MkABZkY0RMRKrfMuGb+Vw2nH9OGaRysAAwYH/AxC/+/m+OTA6tmA
 | 
				
			||||||
 | 
					 AXd31vpMNUdVoPjyO+FQ7f8mwXa3SjPZeQLvpA1RfYFdDtSfr16RI8s41xtL12IYZr4nyRG/
 | 
				
			||||||
 | 
					 wYPmM2WvcTUp3vWVizzHSERlarONc7aaCGXghg6Trpbz7+tv2MOpRLMfJd+6kyCz5pRSGeuX
 | 
				
			||||||
 | 
					 z0iIxWSny1+Vc9uGgxyjJ21FFuvYPR8xmjfCGXvsnWLhKxTPNdhIG6/im/1/uTznzlfGUvgx
 | 
				
			||||||
 | 
					 eNuzVphaVSPzP5DBVxJbKZzZYKOydQLx0Z79YF2xCGmz80EsSajpQNMvNYuNQXuH1ogFIP7e
 | 
				
			||||||
 | 
					 PNOoaoakYuLE1YMhWL+AJzYRRevW8k/VLBgsYvbCRgQYEQIABgUCQrj92QAKCRDRc0hiUBeb
 | 
				
			||||||
 | 
					 O/HXAJ0WAbB0sQ0SBVF+2Nlabw4HICAiKwCg4Fe9VjcfR4+ZJqq3Mx1c+IAE65c=
 | 
				
			||||||
 | 
					Content-Type: text/plain; charset=UTF-8; format=flowed
 | 
				
			||||||
 | 
					Content-Transfer-Encoding: 8bit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					But here: üöäüäö
 | 
				
			||||||
@ -1,4 +1,7 @@
 | 
				
			|||||||
 | 
					import types
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.contrib.admin.sites import AdminSite
 | 
					from django.contrib.admin.sites import AdminSite
 | 
				
			||||||
 | 
					from django.contrib.auth.models import User
 | 
				
			||||||
from django.test import TestCase
 | 
					from django.test import TestCase
 | 
				
			||||||
from django.utils import timezone
 | 
					from django.utils import timezone
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -6,6 +9,7 @@ from documents import index
 | 
				
			|||||||
from documents.admin import DocumentAdmin
 | 
					from documents.admin import DocumentAdmin
 | 
				
			||||||
from documents.models import Document
 | 
					from documents.models import Document
 | 
				
			||||||
from documents.tests.utils import DirectoriesMixin
 | 
					from documents.tests.utils import DirectoriesMixin
 | 
				
			||||||
 | 
					from paperless.admin import PaperlessUserAdmin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestDocumentAdmin(DirectoriesMixin, TestCase):
 | 
					class TestDocumentAdmin(DirectoriesMixin, TestCase):
 | 
				
			||||||
@ -64,3 +68,39 @@ class TestDocumentAdmin(DirectoriesMixin, TestCase):
 | 
				
			|||||||
            created=timezone.make_aware(timezone.datetime(2020, 4, 12)),
 | 
					            created=timezone.make_aware(timezone.datetime(2020, 4, 12)),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self.assertEqual(self.doc_admin.created_(doc), "2020-04-12")
 | 
					        self.assertEqual(self.doc_admin.created_(doc), "2020-04-12")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestPaperlessAdmin(DirectoriesMixin, TestCase):
 | 
				
			||||||
 | 
					    def setUp(self) -> None:
 | 
				
			||||||
 | 
					        super().setUp()
 | 
				
			||||||
 | 
					        self.user_admin = PaperlessUserAdmin(model=User, admin_site=AdminSite())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_request_is_passed_to_form(self):
 | 
				
			||||||
 | 
					        user = User.objects.create(username="test", is_superuser=False)
 | 
				
			||||||
 | 
					        non_superuser = User.objects.create(username="requestuser")
 | 
				
			||||||
 | 
					        request = types.SimpleNamespace(user=non_superuser)
 | 
				
			||||||
 | 
					        formType = self.user_admin.get_form(request)
 | 
				
			||||||
 | 
					        form = formType(data={}, instance=user)
 | 
				
			||||||
 | 
					        self.assertEqual(form.request, request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_only_superuser_can_change_superuser(self):
 | 
				
			||||||
 | 
					        superuser = User.objects.create_superuser(username="superuser", password="test")
 | 
				
			||||||
 | 
					        non_superuser = User.objects.create(username="requestuser")
 | 
				
			||||||
 | 
					        user = User.objects.create(username="test", is_superuser=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        data = {
 | 
				
			||||||
 | 
					            "username": "test",
 | 
				
			||||||
 | 
					            "is_superuser": True,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        form = self.user_admin.form(data, instance=user)
 | 
				
			||||||
 | 
					        form.request = types.SimpleNamespace(user=non_superuser)
 | 
				
			||||||
 | 
					        self.assertFalse(form.is_valid())
 | 
				
			||||||
 | 
					        self.assertEqual(
 | 
				
			||||||
 | 
					            form.errors.get("__all__"),
 | 
				
			||||||
 | 
					            ["Superuser status can only be changed by a superuser"],
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        form = self.user_admin.form(data, instance=user)
 | 
				
			||||||
 | 
					        form.request = types.SimpleNamespace(user=superuser)
 | 
				
			||||||
 | 
					        self.assertTrue(form.is_valid())
 | 
				
			||||||
 | 
					        self.assertEqual({}, form.errors)
 | 
				
			||||||
 | 
				
			|||||||
@ -681,6 +681,80 @@ class TestApiUser(DirectoriesMixin, APITestCase):
 | 
				
			|||||||
        )
 | 
					        )
 | 
				
			||||||
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
 | 
					        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_only_superusers_can_create_or_alter_superuser_status(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        GIVEN:
 | 
				
			||||||
 | 
					            - Existing user account
 | 
				
			||||||
 | 
					        WHEN:
 | 
				
			||||||
 | 
					            - API request is made to add a user account with superuser status
 | 
				
			||||||
 | 
					            - API request is made to change superuser status
 | 
				
			||||||
 | 
					        THEN:
 | 
				
			||||||
 | 
					            - Only superusers can change superuser status
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        user1 = User.objects.create_user(username="user1")
 | 
				
			||||||
 | 
					        user1.user_permissions.add(*Permission.objects.all())
 | 
				
			||||||
 | 
					        user2 = User.objects.create_superuser(username="user2")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.client.force_authenticate(user1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        response = self.client.patch(
 | 
				
			||||||
 | 
					            f"{self.ENDPOINT}{user1.pk}/",
 | 
				
			||||||
 | 
					            json.dumps(
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "is_superuser": True,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            content_type="application/json",
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        response = self.client.post(
 | 
				
			||||||
 | 
					            f"{self.ENDPOINT}",
 | 
				
			||||||
 | 
					            json.dumps(
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "username": "user3",
 | 
				
			||||||
 | 
					                    "is_superuser": True,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            content_type="application/json",
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.client.force_authenticate(user2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        response = self.client.patch(
 | 
				
			||||||
 | 
					            f"{self.ENDPOINT}{user1.pk}/",
 | 
				
			||||||
 | 
					            json.dumps(
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "is_superuser": True,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            content_type="application/json",
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(response.status_code, status.HTTP_200_OK)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        returned_user1 = User.objects.get(pk=user1.pk)
 | 
				
			||||||
 | 
					        self.assertEqual(returned_user1.is_superuser, True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        response = self.client.patch(
 | 
				
			||||||
 | 
					            f"{self.ENDPOINT}{user1.pk}/",
 | 
				
			||||||
 | 
					            json.dumps(
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "is_superuser": False,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            content_type="application/json",
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(response.status_code, status.HTTP_200_OK)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        returned_user1 = User.objects.get(pk=user1.pk)
 | 
				
			||||||
 | 
					        self.assertEqual(returned_user1.is_superuser, False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestApiGroup(DirectoriesMixin, APITestCase):
 | 
					class TestApiGroup(DirectoriesMixin, APITestCase):
 | 
				
			||||||
    ENDPOINT = "/api/groups/"
 | 
					    ENDPOINT = "/api/groups/"
 | 
				
			||||||
 | 
				
			|||||||
@ -96,7 +96,7 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase):
 | 
				
			|||||||
                doc = Document.objects.create(
 | 
					                doc = Document.objects.create(
 | 
				
			||||||
                    checksum=str(i),
 | 
					                    checksum=str(i),
 | 
				
			||||||
                    pk=i + 1,
 | 
					                    pk=i + 1,
 | 
				
			||||||
                    title=f"Document {i+1}",
 | 
					                    title=f"Document {i + 1}",
 | 
				
			||||||
                    content="content",
 | 
					                    content="content",
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                index.update_document(writer, doc)
 | 
					                index.update_document(writer, doc)
 | 
				
			||||||
@ -131,7 +131,7 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase):
 | 
				
			|||||||
                doc = Document.objects.create(
 | 
					                doc = Document.objects.create(
 | 
				
			||||||
                    checksum=str(i),
 | 
					                    checksum=str(i),
 | 
				
			||||||
                    pk=i + 1,
 | 
					                    pk=i + 1,
 | 
				
			||||||
                    title=f"Document {i+1}",
 | 
					                    title=f"Document {i + 1}",
 | 
				
			||||||
                    content="content",
 | 
					                    content="content",
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                index.update_document(writer, doc)
 | 
					                index.update_document(writer, doc)
 | 
				
			||||||
@ -630,8 +630,8 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase):
 | 
				
			|||||||
                doc = Document.objects.create(
 | 
					                doc = Document.objects.create(
 | 
				
			||||||
                    checksum=str(i),
 | 
					                    checksum=str(i),
 | 
				
			||||||
                    pk=i + 1,
 | 
					                    pk=i + 1,
 | 
				
			||||||
                    title=f"Document {i+1}",
 | 
					                    title=f"Document {i + 1}",
 | 
				
			||||||
                    content=f"Things document {i+1}",
 | 
					                    content=f"Things document {i + 1}",
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                index.update_document(writer, doc)
 | 
					                index.update_document(writer, doc)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -2149,9 +2149,8 @@ class TestWorkflows(
 | 
				
			|||||||
        EMAIL_ENABLED=True,
 | 
					        EMAIL_ENABLED=True,
 | 
				
			||||||
        PAPERLESS_URL="http://localhost:8000",
 | 
					        PAPERLESS_URL="http://localhost:8000",
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    @mock.patch("httpx.post")
 | 
					 | 
				
			||||||
    @mock.patch("django.core.mail.message.EmailMessage.send")
 | 
					    @mock.patch("django.core.mail.message.EmailMessage.send")
 | 
				
			||||||
    def test_workflow_email_include_file(self, mock_email_send, mock_post):
 | 
					    def test_workflow_email_include_file(self, mock_email_send):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        GIVEN:
 | 
					        GIVEN:
 | 
				
			||||||
            - Document updated workflow with email action
 | 
					            - Document updated workflow with email action
 | 
				
			||||||
@ -2199,6 +2198,24 @@ class TestWorkflows(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        mock_email_send.assert_called_once()
 | 
					        mock_email_send.assert_called_once()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mock_email_send.reset_mock()
 | 
				
			||||||
 | 
					        # test with .eml file
 | 
				
			||||||
 | 
					        test_file2 = shutil.copy(
 | 
				
			||||||
 | 
					            self.SAMPLE_DIR / "eml_with_umlaut.eml",
 | 
				
			||||||
 | 
					            self.dirs.scratch_dir / "eml_with_umlaut.eml",
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        doc2 = Document.objects.create(
 | 
				
			||||||
 | 
					            title="sample eml",
 | 
				
			||||||
 | 
					            checksum="123456",
 | 
				
			||||||
 | 
					            filename=test_file2,
 | 
				
			||||||
 | 
					            mime_type="message/rfc822",
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        run_workflows(WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, doc2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mock_email_send.assert_called_once()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @override_settings(
 | 
					    @override_settings(
 | 
				
			||||||
        EMAIL_ENABLED=False,
 | 
					        EMAIL_ENABLED=False,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
				
			|||||||
@ -295,9 +295,9 @@ class TestMigrations(TransactionTestCase):
 | 
				
			|||||||
    def setUp(self):
 | 
					    def setUp(self):
 | 
				
			||||||
        super().setUp()
 | 
					        super().setUp()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert (
 | 
					        assert self.migrate_from and self.migrate_to, (
 | 
				
			||||||
            self.migrate_from and self.migrate_to
 | 
					            f"TestCase '{type(self).__name__}' must define migrate_from and migrate_to properties"
 | 
				
			||||||
        ), f"TestCase '{type(self).__name__}' must define migrate_from and migrate_to properties"
 | 
					        )
 | 
				
			||||||
        self.migrate_from = [(self.app, self.migrate_from)]
 | 
					        self.migrate_from = [(self.app, self.migrate_from)]
 | 
				
			||||||
        if self.dependencies is not None:
 | 
					        if self.dependencies is not None:
 | 
				
			||||||
            self.migrate_from.extend(self.dependencies)
 | 
					            self.migrate_from.extend(self.dependencies)
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@ msgstr ""
 | 
				
			|||||||
"Project-Id-Version: paperless-ngx\n"
 | 
					"Project-Id-Version: paperless-ngx\n"
 | 
				
			||||||
"Report-Msgid-Bugs-To: \n"
 | 
					"Report-Msgid-Bugs-To: \n"
 | 
				
			||||||
"POT-Creation-Date: 2024-12-02 23:04-0800\n"
 | 
					"POT-Creation-Date: 2024-12-02 23:04-0800\n"
 | 
				
			||||||
"PO-Revision-Date: 2025-01-16 00:30\n"
 | 
					"PO-Revision-Date: 2025-01-21 00:29\n"
 | 
				
			||||||
"Last-Translator: \n"
 | 
					"Last-Translator: \n"
 | 
				
			||||||
"Language-Team: French\n"
 | 
					"Language-Team: French\n"
 | 
				
			||||||
"Language: fr_FR\n"
 | 
					"Language: fr_FR\n"
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@ msgstr ""
 | 
				
			|||||||
"Project-Id-Version: paperless-ngx\n"
 | 
					"Project-Id-Version: paperless-ngx\n"
 | 
				
			||||||
"Report-Msgid-Bugs-To: \n"
 | 
					"Report-Msgid-Bugs-To: \n"
 | 
				
			||||||
"POT-Creation-Date: 2024-12-02 23:04-0800\n"
 | 
					"POT-Creation-Date: 2024-12-02 23:04-0800\n"
 | 
				
			||||||
"PO-Revision-Date: 2025-01-18 20:24\n"
 | 
					"PO-Revision-Date: 2025-01-21 00:29\n"
 | 
				
			||||||
"Last-Translator: \n"
 | 
					"Last-Translator: \n"
 | 
				
			||||||
"Language-Team: Italian\n"
 | 
					"Language-Team: Italian\n"
 | 
				
			||||||
"Language: it_IT\n"
 | 
					"Language: it_IT\n"
 | 
				
			||||||
@ -31,7 +31,7 @@ msgstr "Campo personalizzato della query non valido"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#: documents/filters.py:365
 | 
					#: documents/filters.py:365
 | 
				
			||||||
msgid "Invalid expression list. Must be nonempty."
 | 
					msgid "Invalid expression list. Must be nonempty."
 | 
				
			||||||
msgstr ""
 | 
					msgstr "Elenco delle espressioni non valido. Deve essere non vuoto."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: documents/filters.py:386
 | 
					#: documents/filters.py:386
 | 
				
			||||||
msgid "Invalid logical operator {op!r}"
 | 
					msgid "Invalid logical operator {op!r}"
 | 
				
			||||||
@ -39,7 +39,7 @@ msgstr "Operatore logico non valido {op!r}"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#: documents/filters.py:400
 | 
					#: documents/filters.py:400
 | 
				
			||||||
msgid "Maximum number of query conditions exceeded."
 | 
					msgid "Maximum number of query conditions exceeded."
 | 
				
			||||||
msgstr ""
 | 
					msgstr "Numero massimo delle condizioni della jQuery superato."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: documents/filters.py:465
 | 
					#: documents/filters.py:465
 | 
				
			||||||
msgid "{name!r} is not a valid custom field."
 | 
					msgid "{name!r} is not a valid custom field."
 | 
				
			||||||
@ -47,7 +47,7 @@ msgstr "{name!r} non è un campo personalizzato valido."
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#: documents/filters.py:502
 | 
					#: documents/filters.py:502
 | 
				
			||||||
msgid "{data_type} does not support query expr {expr!r}."
 | 
					msgid "{data_type} does not support query expr {expr!r}."
 | 
				
			||||||
msgstr ""
 | 
					msgstr "{data_type} Non supporta la jQuery Expo {Expo!r}."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: documents/filters.py:610
 | 
					#: documents/filters.py:610
 | 
				
			||||||
msgid "Maximum nesting depth exceeded."
 | 
					msgid "Maximum nesting depth exceeded."
 | 
				
			||||||
@ -794,7 +794,7 @@ msgstr "Campo personalizzato"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#: documents/models.py:1037
 | 
					#: documents/models.py:1037
 | 
				
			||||||
msgid "Workflow Trigger Type"
 | 
					msgid "Workflow Trigger Type"
 | 
				
			||||||
msgstr ""
 | 
					msgstr "Tipo Frigger Del Workshop"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: documents/models.py:1049
 | 
					#: documents/models.py:1049
 | 
				
			||||||
msgid "filter path"
 | 
					msgid "filter path"
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@ msgstr ""
 | 
				
			|||||||
"Project-Id-Version: paperless-ngx\n"
 | 
					"Project-Id-Version: paperless-ngx\n"
 | 
				
			||||||
"Report-Msgid-Bugs-To: \n"
 | 
					"Report-Msgid-Bugs-To: \n"
 | 
				
			||||||
"POT-Creation-Date: 2024-12-02 23:04-0800\n"
 | 
					"POT-Creation-Date: 2024-12-02 23:04-0800\n"
 | 
				
			||||||
"PO-Revision-Date: 2024-12-03 07:05\n"
 | 
					"PO-Revision-Date: 2025-01-19 12:10\n"
 | 
				
			||||||
"Last-Translator: \n"
 | 
					"Last-Translator: \n"
 | 
				
			||||||
"Language-Team: Norwegian\n"
 | 
					"Language-Team: Norwegian\n"
 | 
				
			||||||
"Language: no_NO\n"
 | 
					"Language: no_NO\n"
 | 
				
			||||||
@ -23,7 +23,7 @@ msgstr "Dokumenter"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#: documents/filters.py:336
 | 
					#: documents/filters.py:336
 | 
				
			||||||
msgid "Value must be valid JSON."
 | 
					msgid "Value must be valid JSON."
 | 
				
			||||||
msgstr ""
 | 
					msgstr "Verdien må være en gyldig JSON."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: documents/filters.py:355
 | 
					#: documents/filters.py:355
 | 
				
			||||||
msgid "Invalid custom field query expression"
 | 
					msgid "Invalid custom field query expression"
 | 
				
			||||||
@ -1149,7 +1149,7 @@ msgstr "Ugyldig variabel oppdaget."
 | 
				
			|||||||
#: documents/templates/account/email/base_message.txt:1
 | 
					#: documents/templates/account/email/base_message.txt:1
 | 
				
			||||||
#, python-format
 | 
					#, python-format
 | 
				
			||||||
msgid "Hello from %(site_name)s!"
 | 
					msgid "Hello from %(site_name)s!"
 | 
				
			||||||
msgstr ""
 | 
					msgstr "Hei fra %(site_name)s!"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: documents/templates/account/email/base_message.txt:5
 | 
					#: documents/templates/account/email/base_message.txt:5
 | 
				
			||||||
#, python-format
 | 
					#, python-format
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@ msgstr ""
 | 
				
			|||||||
"Project-Id-Version: paperless-ngx\n"
 | 
					"Project-Id-Version: paperless-ngx\n"
 | 
				
			||||||
"Report-Msgid-Bugs-To: \n"
 | 
					"Report-Msgid-Bugs-To: \n"
 | 
				
			||||||
"POT-Creation-Date: 2024-12-02 23:04-0800\n"
 | 
					"POT-Creation-Date: 2024-12-02 23:04-0800\n"
 | 
				
			||||||
"PO-Revision-Date: 2024-12-31 00:30\n"
 | 
					"PO-Revision-Date: 2025-01-19 00:32\n"
 | 
				
			||||||
"Last-Translator: \n"
 | 
					"Last-Translator: \n"
 | 
				
			||||||
"Language-Team: Russian\n"
 | 
					"Language-Team: Russian\n"
 | 
				
			||||||
"Language: ru_RU\n"
 | 
					"Language: ru_RU\n"
 | 
				
			||||||
@ -374,7 +374,7 @@ msgstr "обратная сортировка"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#: documents/models.py:451
 | 
					#: documents/models.py:451
 | 
				
			||||||
msgid "View page size"
 | 
					msgid "View page size"
 | 
				
			||||||
msgstr ""
 | 
					msgstr "Посмотреть размер страницы"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: documents/models.py:459
 | 
					#: documents/models.py:459
 | 
				
			||||||
msgid "View display mode"
 | 
					msgid "View display mode"
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										53
									
								
								src/paperless/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/paperless/admin.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,53 @@
 | 
				
			|||||||
 | 
					from django import forms
 | 
				
			||||||
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					from django.contrib.auth.admin import UserAdmin
 | 
				
			||||||
 | 
					from django.contrib.auth.models import User
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PaperlessUserForm(forms.ModelForm):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Custom form for the User model that adds validation to prevent non-superusers
 | 
				
			||||||
 | 
					    from changing the superuser status of a user.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        model = User
 | 
				
			||||||
 | 
					        fields = [
 | 
				
			||||||
 | 
					            "username",
 | 
				
			||||||
 | 
					            "first_name",
 | 
				
			||||||
 | 
					            "last_name",
 | 
				
			||||||
 | 
					            "email",
 | 
				
			||||||
 | 
					            "is_staff",
 | 
				
			||||||
 | 
					            "is_active",
 | 
				
			||||||
 | 
					            "is_superuser",
 | 
				
			||||||
 | 
					            "groups",
 | 
				
			||||||
 | 
					            "user_permissions",
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def clean(self):
 | 
				
			||||||
 | 
					        cleaned_data = super().clean()
 | 
				
			||||||
 | 
					        user_being_edited = self.instance
 | 
				
			||||||
 | 
					        is_superuser = cleaned_data.get("is_superuser")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					            not self.request.user.is_superuser
 | 
				
			||||||
 | 
					            and is_superuser != user_being_edited.is_superuser
 | 
				
			||||||
 | 
					        ):
 | 
				
			||||||
 | 
					            raise forms.ValidationError(
 | 
				
			||||||
 | 
					                "Superuser status can only be changed by a superuser",
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return cleaned_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PaperlessUserAdmin(UserAdmin):
 | 
				
			||||||
 | 
					    form = PaperlessUserForm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_form(self, request, obj=None, **kwargs):
 | 
				
			||||||
 | 
					        form = super().get_form(request, obj, **kwargs)
 | 
				
			||||||
 | 
					        form.request = request
 | 
				
			||||||
 | 
					        return form
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					admin.site.unregister(User)
 | 
				
			||||||
 | 
					admin.site.register(User, PaperlessUserAdmin)
 | 
				
			||||||
@ -109,6 +109,25 @@ class UserViewSet(ModelViewSet):
 | 
				
			|||||||
    filterset_class = UserFilterSet
 | 
					    filterset_class = UserFilterSet
 | 
				
			||||||
    ordering_fields = ("username",)
 | 
					    ordering_fields = ("username",)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def create(self, request, *args, **kwargs):
 | 
				
			||||||
 | 
					        if not request.user.is_superuser and request.data.get("is_superuser") is True:
 | 
				
			||||||
 | 
					            return HttpResponseForbidden(
 | 
				
			||||||
 | 
					                "Superuser status can only be granted by a superuser",
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        return super().create(request, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update(self, request, *args, **kwargs):
 | 
				
			||||||
 | 
					        user_to_update: User = self.get_object()
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					            not request.user.is_superuser
 | 
				
			||||||
 | 
					            and request.data.get("is_superuser") is not None
 | 
				
			||||||
 | 
					            and request.data.get("is_superuser") != user_to_update.is_superuser
 | 
				
			||||||
 | 
					        ):
 | 
				
			||||||
 | 
					            return HttpResponseForbidden(
 | 
				
			||||||
 | 
					                "Superuser status can only be changed by a superuser",
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        return super().update(request, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @action(detail=True, methods=["post"])
 | 
					    @action(detail=True, methods=["post"])
 | 
				
			||||||
    def deactivate_totp(self, request, pk=None):
 | 
					    def deactivate_totp(self, request, pk=None):
 | 
				
			||||||
        request_user = request.user
 | 
					        request_user = request.user
 | 
				
			||||||
 | 
				
			|||||||
@ -552,8 +552,7 @@ class MailAccountHandler(LoggingMixin):
 | 
				
			|||||||
                mailbox_login(M, account)
 | 
					                mailbox_login(M, account)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                self.log.debug(
 | 
					                self.log.debug(
 | 
				
			||||||
                    f"Account {account}: Processing "
 | 
					                    f"Account {account}: Processing {account.rules.count()} rule(s)",
 | 
				
			||||||
                    f"{account.rules.count()} rule(s)",
 | 
					 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                for rule in account.rules.order_by("order"):
 | 
					                for rule in account.rules.order_by("order"):
 | 
				
			||||||
 | 
				
			|||||||
@ -129,9 +129,11 @@ class TestParserLive:
 | 
				
			|||||||
        assert thumb.exists()
 | 
					        assert thumb.exists()
 | 
				
			||||||
        assert thumb.is_file()
 | 
					        assert thumb.is_file()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert (
 | 
					        assert self.imagehash(thumb) == self.imagehash(
 | 
				
			||||||
            self.imagehash(thumb) == self.imagehash(simple_txt_email_thumbnail_file)
 | 
					            simple_txt_email_thumbnail_file,
 | 
				
			||||||
        ), f"Created Thumbnail {thumb} differs from expected file {simple_txt_email_thumbnail_file}"
 | 
					        ), (
 | 
				
			||||||
 | 
					            f"Created Thumbnail {thumb} differs from expected file {simple_txt_email_thumbnail_file}"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_tika_parse_successful(self, mail_parser: MailDocumentParser):
 | 
					    def test_tika_parse_successful(self, mail_parser: MailDocumentParser):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
@ -226,6 +228,6 @@ class TestParserLive:
 | 
				
			|||||||
        # The created pdf is not reproducible. But the converted image should always look the same.
 | 
					        # The created pdf is not reproducible. But the converted image should always look the same.
 | 
				
			||||||
        expected_hash = self.imagehash(html_email_thumbnail_file)
 | 
					        expected_hash = self.imagehash(html_email_thumbnail_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert (
 | 
					        assert generated_thumbnail_hash == expected_hash, (
 | 
				
			||||||
            generated_thumbnail_hash == expected_hash
 | 
					            f"PDF looks different. Check if {generated_thumbnail} looks weird."
 | 
				
			||||||
        ), f"PDF looks different. Check if {generated_thumbnail} looks weird."
 | 
					        )
 | 
				
			||||||
 | 
				
			|||||||
@ -455,8 +455,7 @@ class RasterisedDocumentParser(DocumentParser):
 | 
				
			|||||||
                self.text = text_original
 | 
					                self.text = text_original
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                self.log.warning(
 | 
					                self.log.warning(
 | 
				
			||||||
                    f"No text was found in {document_path}, the content will "
 | 
					                    f"No text was found in {document_path}, the content will be empty.",
 | 
				
			||||||
                    f"be empty.",
 | 
					 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                self.text = ""
 | 
					                self.text = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user