diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5a99927c3..e2b380bf6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -51,7 +51,7 @@ repos: - 'prettier-plugin-organize-imports@4.1.0' # Python hooks - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.6 + rev: v0.9.2 hooks: - id: ruff - id: ruff-format diff --git a/Pipfile.lock b/Pipfile.lock index 1e1ec9823..17098c211 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -2913,122 +2913,109 @@ }, "charset-normalizer": { "hashes": [ - "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621", - "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", - "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", - "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912", - "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", - "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b", - "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d", - "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d", - "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95", - "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e", - "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", - "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", - "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab", - "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be", - "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", - "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907", - "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0", - "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2", - "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62", - "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62", - "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", - "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", - "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", - "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca", - "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455", - "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858", - "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", - "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", - "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", - "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", - "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", - "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea", - "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6", - "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", - "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749", - "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", - "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd", - "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99", - "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242", - "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee", - "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", - "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", - "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51", - "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", - "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8", - "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", - "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613", - "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742", - "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe", - "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3", - "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5", - "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", - "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7", - "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", - "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c", - "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", - "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417", - "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", - "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", - "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca", - "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa", - "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99", - "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149", - "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41", - "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574", - "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0", - "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f", - "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", - "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654", - "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3", - "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19", - "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", - "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578", - "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", - "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", - "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51", - "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", - "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", - "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a", - "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", - "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade", - "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", - "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc", - "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6", - "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", - "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", - "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6", - "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2", - "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12", - "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf", - "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", - "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7", - "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" + "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", + "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa", + "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a", + "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", + "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b", + "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", + "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", + "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", + "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", + "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", + "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", + "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", + "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", + "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", + "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", + "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", + "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", + "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", + "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", + "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", + "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e", + "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a", + "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4", + "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca", + "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", + "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", + "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", + "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", + "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", + "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", + "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", + "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", + "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", + "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", + "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", + "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd", + "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c", + "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", + "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", + "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", + "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", + "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824", + "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", + "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf", + "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487", + "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d", + "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd", + "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", + "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534", + "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", + "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", + "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", + "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd", + "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", + "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9", + "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", + "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", + "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d", + "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", + "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", + "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", + "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", + "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", + "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", + "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8", + "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", + "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", + "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", + "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", + "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", + "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", + "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", + "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", + "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", + "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", + "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", + "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", + "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e", + "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6", + "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", + "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", + "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e", + "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", + "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", + "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c", + "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", + "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", + "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089", + "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", + "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e", + "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", + "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616" ], - "markers": "python_full_version >= '3.7.0'", - "version": "==3.4.0" + "markers": "python_version >= '3.7'", + "version": "==3.4.1" }, "click": { "hashes": [ - "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", - "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" + "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", + "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a" ], "markers": "python_version >= '3.7'", - "version": "==8.1.7" + "version": "==8.1.8" }, "colorama": { "hashes": [ @@ -3291,11 +3278,11 @@ }, "jinja2": { "hashes": [ - "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", - "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d" + "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", + "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb" ], "markers": "python_version >= '3.7'", - "version": "==3.1.4" + "version": "==3.1.5" }, "markdown": { "hashes": [ @@ -3406,12 +3393,12 @@ }, "mkdocs-material": { "hashes": [ - "sha256:3671bb282b4f53a1c72e08adbe04d2481a98f85fed392530051f80ff94a9621d", - "sha256:c3c2d8176b18198435d3a3e119011922f3e11424074645c24019c2dcf08a360e" + "sha256:ae5fe16f3d7c9ccd05bb6916a7da7420cf99a9ce5e33debd9d40403a090d5825", + "sha256:f24100f234741f4d423a9d672a909d859668a4f404796be3cf035f10d6050385" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==9.5.49" + "version": "==9.5.50" }, "mkdocs-material-extensions": { "hashes": [ @@ -3645,19 +3632,19 @@ }, "pygments": { "hashes": [ - "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", - "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a" + "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", + "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c" ], "markers": "python_version >= '3.8'", - "version": "==2.18.0" + "version": "==2.19.1" }, "pymdown-extensions": { "hashes": [ - "sha256:49f81412242d3527b8b4967b990df395c89563043bc51a3d2d7d500e52123b77", - "sha256:b0ee1e0b2bef1071a47891ab17003bfe5bf824a398e13f49f8ed653b699369a7" + "sha256:202481f716cc8250e4be8fce997781ebf7917701b59652458ee47f2401f818b5", + "sha256:741bd7c4ff961ba40b7528d32284c53bc436b8b1645e8e37c3e57770b8700a34" ], "markers": "python_version >= '3.8'", - "version": "==10.12" + "version": "==10.14" }, "pyopenssl": { "hashes": [ @@ -3974,28 +3961,28 @@ }, "ruff": { "hashes": [ - "sha256:0509e8da430228236a18a677fcdb0c1f102dd26d5520f71f79b094963322ed25", - "sha256:0c000a471d519b3e6cfc9c6680025d923b4ca140ce3e4612d1a2ef58e11f11fe", - "sha256:248b1fb3f739d01d528cc50b35ee9c4812aa58cc5935998e776bf8ed5b251e75", - "sha256:45a56f61b24682f6f6709636949ae8cc82ae229d8d773b4c76c09ec83964a95a", - "sha256:496dd38a53aa173481a7d8866bcd6451bd934d06976a2505028a50583e001b76", - "sha256:52d587092ab8df308635762386f45f4638badb0866355b2b86760f6d3c076188", - "sha256:54799ca3d67ae5e0b7a7ac234baa657a9c1784b48ec954a094da7c206e0365b1", - "sha256:61323159cf21bc3897674e5adb27cd9e7700bab6b84de40d7be28c3d46dc67cf", - "sha256:7ae4478b1471fc0c44ed52a6fb787e641a2ac58b1c1f91763bafbc2faddc5117", - "sha256:7d7fc2377a04b6e04ffe588caad613d0c460eb2ecba4c0ccbbfe2bc973cbc162", - "sha256:91a7ddb221779871cf226100e677b5ea38c2d54e9e2c8ed847450ebbdf99b32d", - "sha256:9257aa841e9e8d9b727423086f0fa9a86b6b420fbf4bf9e1465d1250ce8e4d8d", - "sha256:bc3c083c50390cf69e7e1b5a5a7303898966be973664ec0c4a4acea82c1d4315", - "sha256:dcad24b81b62650b0eb8814f576fc65cfee8674772a6e24c9b747911801eeaa5", - "sha256:defed167955d42c68b407e8f2e6f56ba52520e790aba4ca707a9c88619e580e3", - "sha256:e169ea1b9eae61c99b257dc83b9ee6c76f89042752cb2d83486a7d6e48e8f764", - "sha256:e88b8f6d901477c41559ba540beeb5a671e14cd29ebd5683903572f4b40a9807", - "sha256:f1d70bef3d16fdc897ee290d7d20da3cbe4e26349f62e8a0274e7a3f4ce7a905" + "sha256:1a605fdcf6e8b2d39f9436d343d1f0ff70c365a1e681546de0104bef81ce88df", + "sha256:3292c5a22ea9a5f9a185e2d131dc7f98f8534a32fb6d2ee7b9944569239c648d", + "sha256:492a5e44ad9b22a0ea98cf72e40305cbdaf27fac0d927f8bc9e1df316dcc96eb", + "sha256:71cbe22e178c5da20e1514e1e01029c73dc09288a8028a5d3446e6bba87a5145", + "sha256:80605a039ba1454d002b32139e4970becf84b5fee3a3c3bf1c2af6f61a784347", + "sha256:82b35259b0cbf8daa22a498018e300b9bb0174c2bbb7bcba593935158a78054d", + "sha256:8b6a9701d1e371bf41dca22015c3f89769da7576884d2add7317ec1ec8cb9c3c", + "sha256:8efd9da7a1ee314b910da155ca7e8953094a7c10d0c0a39bfde3fcfd2a015684", + "sha256:9cc53e68b3c5ae41e8faf83a3b89f4a5d7b2cb666dff4b366bb86ed2a85b481f", + "sha256:a1b63fa24149918f8b37cef2ee6fff81f24f0d74b6f0bdc37bc3e1f2143e41c6", + "sha256:af1e9e9fe7b1f767264d26b1075ac4ad831c7db976911fa362d09b2d0356426a", + "sha256:b338edc4610142355ccf6b87bd356729b62bf1bc152a2fad5b0c7dc04af77bfe", + "sha256:b5eceb334d55fae5f316f783437392642ae18e16dcf4f1858d55d3c2a0f8f5d0", + "sha256:b9aab82bb20afd5f596527045c01e6ae25a718ff1784cb92947bff1f83068b00", + "sha256:c547f7f256aa366834829a08375c297fa63386cbe5f1459efaf174086b564247", + "sha256:c5e1d6abc798419cf46eed03f54f2e0c3adb1ad4b801119dedf23fcaf69b55b5", + "sha256:d18bba3d3353ed916e882521bc3e0af403949dbada344c20c16ea78f47af965e", + "sha256:fbd337bac1cfa96be615f6efcd4bc4d077edbc127ef30e2b8ba2a27e18c054d4" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==0.8.6" + "version": "==0.9.2" }, "scipy": { "hashes": [ @@ -4113,11 +4100,11 @@ }, "urllib3": { "hashes": [ - "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", - "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9" + "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", + "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d" ], - "markers": "python_version >= '3.8'", - "version": "==2.2.3" + "markers": "python_version >= '3.9'", + "version": "==2.3.0" }, "virtualenv": { "hashes": [ diff --git a/docs/advanced_usage.md b/docs/advanced_usage.md index 75bbe05a9..f7b31c919 100644 --- a/docs/advanced_usage.md +++ b/docs/advanced_usage.md @@ -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 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 century. - `{{ 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 }} ``` -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: diff --git a/docs/usage.md b/docs/usage.md index 2b6858337..027aa4e73 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -299,7 +299,7 @@ permissions can be granted to limit access to certain parts of the UI (and corre #### 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 diff --git a/gunicorn.conf.py b/gunicorn.conf.py index 70c5707e1..0e3878f8b 100644 --- a/gunicorn.conf.py +++ b/gunicorn.conf.py @@ -3,7 +3,7 @@ import os # See https://docs.gunicorn.org/en/stable/settings.html for # 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)) worker_class = "paperless.workers.ConfigurableWorker" diff --git a/src-ui/package-lock.json b/src-ui/package-lock.json index 417ae6cd0..c09875247 100644 --- a/src-ui/package-lock.json +++ b/src-ui/package-lock.json @@ -18107,10 +18107,11 @@ } }, "node_modules/undici": { - "version": "5.28.4", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", - "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "version": "5.28.5", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.5.tgz", + "integrity": "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==", "dev": true, + "license": "MIT", "dependencies": { "@fastify/busboy": "^2.0.0" }, diff --git a/src-ui/src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.spec.ts b/src-ui/src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.spec.ts index 560a259f4..9ffa1ea95 100644 --- a/src-ui/src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.spec.ts +++ b/src-ui/src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.spec.ts @@ -160,4 +160,23 @@ describe('UserEditDialogComponent', () => { }) 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() + }) }) diff --git a/src-ui/src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts b/src-ui/src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts index 819d075b5..7ba0f5ceb 100644 --- a/src-ui/src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts +++ b/src-ui/src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts @@ -60,6 +60,11 @@ export class UserEditDialogComponent ngOnInit(): void { super.ngOnInit() this.onToggleSuperUser() + if (!this.currentUserIsSuperUser) { + this.objectForm.get('is_superuser').disable() + } else { + this.objectForm.get('is_superuser').enable() + } } getCreateTitle() { diff --git a/src-ui/src/app/components/common/input/tags/tags.component.html b/src-ui/src/app/components/common/input/tags/tags.component.html index d5d5c4a7d..23c680dd0 100644 --- a/src-ui/src/app/components/common/input/tags/tags.component.html +++ b/src-ui/src/app/components/common/input/tags/tags.component.html @@ -17,7 +17,7 @@ (change)="onChange(value)"> -
- + - - + +
@@ -82,7 +82,7 @@
-
@@ -126,7 +126,7 @@ @for (rule of mailRules; track rule) {
  • -
    +
    {{rule.order}}
    {{(mailAccountService.getCached(rule.account) | async)?.name}}
    @@ -144,9 +144,9 @@
    - + - +
    diff --git a/src-ui/src/app/components/not-found/not-found.component.spec.ts b/src-ui/src/app/components/not-found/not-found.component.spec.ts index 58861da44..219c6c1f9 100644 --- a/src-ui/src/app/components/not-found/not-found.component.spec.ts +++ b/src-ui/src/app/components/not-found/not-found.component.spec.ts @@ -2,7 +2,9 @@ import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' import { provideHttpClientTesting } from '@angular/common/http/testing' import { ComponentFixture, TestBed } from '@angular/core/testing' import { By } from '@angular/platform-browser' +import { RouterModule } from '@angular/router' import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' +import { routes } from 'src/app/app-routing.module' import { LogoComponent } from '../common/logo/logo.component' import { NotFoundComponent } from './not-found.component' @@ -16,6 +18,7 @@ describe('NotFoundComponent', () => { NgxBootstrapIconsModule.pick(allIcons), NotFoundComponent, LogoComponent, + RouterModule.forRoot(routes), ], providers: [ provideHttpClient(withInterceptorsFromDi()), diff --git a/src-ui/src/app/components/not-found/not-found.component.ts b/src-ui/src/app/components/not-found/not-found.component.ts index dc381d265..44cd103ec 100644 --- a/src-ui/src/app/components/not-found/not-found.component.ts +++ b/src-ui/src/app/components/not-found/not-found.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core' +import { RouterModule } from '@angular/router' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { LogoComponent } from '../common/logo/logo.component' @@ -6,7 +7,7 @@ import { LogoComponent } from '../common/logo/logo.component' selector: 'pngx-not-found', templateUrl: './not-found.component.html', styleUrls: ['./not-found.component.scss'], - imports: [LogoComponent, NgxBootstrapIconsModule], + imports: [LogoComponent, NgxBootstrapIconsModule, RouterModule], }) export class NotFoundComponent { constructor() {} diff --git a/src-ui/src/locale/messages.it_IT.xlf b/src-ui/src/locale/messages.it_IT.xlf index 1dbe29d91..67a55b0b5 100644 --- a/src-ui/src/locale/messages.it_IT.xlf +++ b/src-ui/src/locale/messages.it_IT.xlf @@ -8381,7 +8381,7 @@ src/app/components/document-list/document-list.component.ts 285 - Ripristina filtri / selezione + Azzera filtri / selezione Open first [selected] document @@ -9349,7 +9349,7 @@ src/app/components/manage/saved-views/saved-views.component.html 4 - Customize the views of your documents. + Personalizza la vista dei tuoi documenti. Documents page size @@ -9421,7 +9421,7 @@ src/app/components/manage/saved-views/saved-views.component.ts 163 - Error while saving views. + Errore durante il salvataggio delle viste. storage path diff --git a/src-ui/src/locale/messages.no_NO.xlf b/src-ui/src/locale/messages.no_NO.xlf index 36b0273c8..7e59e2814 100644 --- a/src-ui/src/locale/messages.no_NO.xlf +++ b/src-ui/src/locale/messages.no_NO.xlf @@ -452,7 +452,7 @@ src/app/app.component.ts 183 - 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. + 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. 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. @@ -524,7 +524,7 @@ src/app/app.component.ts 238 - Check out the settings for various tweaks to the web app. + Sjekk ut innstillingene for forskjellige endringer du kan gjøre på applikasjonen. Thank you! 🙏 @@ -572,7 +572,7 @@ src/app/components/admin/config/config.component.html 25 - Read the documentation about this setting + Les dokumentasjonen om denne innstillingen Enable @@ -1108,7 +1108,7 @@ src/app/components/common/permissions-select/permissions-select.component.html 4 - What's this? + Hva er dette? 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. @@ -1724,7 +1724,7 @@ src/app/components/admin/tasks/tasks.component.html 16 - Filter by + Filtrer etter Name @@ -1968,7 +1968,7 @@ src/app/components/admin/tasks/tasks.component.ts 157 - Avvis + Fjern varsel Open Document @@ -2016,7 +2016,7 @@ src/app/components/admin/tasks/tasks.component.html 164,166 - Started + Startet Queued @@ -2024,7 +2024,7 @@ src/app/components/admin/tasks/tasks.component.html 172,174 - Queued + I kø: Result @@ -2032,7 +2032,7 @@ src/app/components/admin/tasks/tasks.component.ts 45 - Result + Resultat Dismiss selected @@ -2040,7 +2040,7 @@ src/app/components/admin/tasks/tasks.component.ts 104 - Avvis valgte + Fjern valgte varsel Dismiss all @@ -2048,7 +2048,7 @@ src/app/components/admin/tasks/tasks.component.ts 105 - Avvis alle + Kvitter ut alle beskjeder Confirm Dismiss All @@ -2056,7 +2056,7 @@ src/app/components/admin/tasks/tasks.component.ts 154 - Bekreft avvisning av alle + Bekreft kvittering av alle Dismiss all tasks? @@ -2064,7 +2064,7 @@ src/app/components/admin/tasks/tasks.component.ts 155 - Dismiss all tasks? + Fjern alle oppgaver? queued @@ -2120,7 +2120,7 @@ src/app/components/admin/trash/trash.component.html 4 - Manage trashed documents that are pending deletion. + Administrer forkastede dokumenter som venter på å bli slettet. Restore selected @@ -2328,7 +2328,7 @@ src/app/components/admin/trash/trash.component.html 94 - {VAR_PLURAL, plural, =1 {One document in trash} other { total documents in trash}} + {VAR_PLURAL, plural, one {}=1 {Ett dokument i papirkurven} other { totalt antall dokumenter i papirkurven}} Confirm delete @@ -3912,7 +3912,7 @@ src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html 37 - Use locale + Bruk nasjonale innstillinger Create new custom field @@ -6462,7 +6462,7 @@ src/app/components/dashboard/dashboard.component.ts 57 - Hello , welcome to + Hei og velkommen til Welcome to @@ -6634,7 +6634,7 @@ src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html 4 - Drop documents anywhere or + Slipp dokumenter her, eller Browse files @@ -6651,7 +6651,7 @@ 20 This button dismisses all status messages about processed documents on the dashboard (failed and successful) - Avvis fullført + Kvittert ut alle varsler {VAR_PLURAL, plural, =1 {One more document} other { more documents}} diff --git a/src-ui/src/locale/messages.ru_RU.xlf b/src-ui/src/locale/messages.ru_RU.xlf index c12ab2dd9..a0b485f86 100644 --- a/src-ui/src/locale/messages.ru_RU.xlf +++ b/src-ui/src/locale/messages.ru_RU.xlf @@ -1724,7 +1724,7 @@ src/app/components/admin/tasks/tasks.component.html 16 - Filter by + Фильтровать по Name @@ -2032,7 +2032,7 @@ src/app/components/admin/tasks/tasks.component.ts 45 - Result + Результат Dismiss selected @@ -2152,7 +2152,7 @@ src/app/components/admin/trash/trash.component.html 36 - Remaining + Осталось days @@ -2440,7 +2440,7 @@ src/app/components/admin/trash/trash.component.ts 119 - Document(s) deleted + Документ(ы) удален(ы) Error deleting document(s) @@ -2448,7 +2448,7 @@ src/app/components/admin/trash/trash.component.ts 126 - Error deleting document(s) + Ошибка при удалении документам(ов) Document restored @@ -2472,7 +2472,7 @@ src/app/components/admin/trash/trash.component.ts 159 - Document(s) restored + Документ(ы) восстановлены Error restoring document(s) @@ -2480,7 +2480,7 @@ src/app/components/admin/trash/trash.component.ts 165 - Error restoring document(s) + Ошибка при восстановлении документа(ов) Users & Groups @@ -3184,7 +3184,7 @@ src/app/components/app-frame/global-search/global-search.component.html 62 - Filter documents + Отфильтровать документы Download @@ -3228,7 +3228,7 @@ src/app/components/app-frame/global-search/global-search.component.html 90 - Documents + Документы Saved Views @@ -3244,7 +3244,7 @@ src/app/components/app-frame/global-search/global-search.component.html 103 - Tags + Теги Correspondents @@ -3480,7 +3480,7 @@ src/app/components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component.html 32 - Delete original documents after successful merge + Удалить оригиналы после успешного объединения Note that only PDFs will be included. @@ -3488,7 +3488,7 @@ src/app/components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component.html 34 - Note that only PDFs will be included. + Только PDF файлы могут быть добавлены. Note that only PDFs will be rotated. @@ -4240,7 +4240,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 46 - Include only files matching + Включить только соответствующие файлы Optional. Wildcards e.g. *.pdf or *invoice* allowed. Can be comma-separated list. Case insensitive. @@ -4252,7 +4252,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 47 - Optional. Wildcards e.g. *.pdf or *invoice* allowed. Can be comma-separated list. Case insensitive. + Необязательно. Допускается использовать символ подстановки (wildcard), например *.pdf or *invoice*. Может быть список, разделенным через запятую. Нечувствителен к регистру. Exclude files matching @@ -4260,7 +4260,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 47 - Exclude files matching + Исключить соответствующие файлы Action @@ -4276,7 +4276,7 @@ src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html 53 - Only performed if the mail is processed. + Выполнять только если письмо обработано. Action parameter @@ -4904,7 +4904,7 @@ src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html 143 - Repeat the trigger every n days. + Повторять триггер каждые n дней. Trigger for documents that match all filters specified below. @@ -4992,7 +4992,7 @@ src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html 169 - Has any of tags + Содержит любой из тегов Has correspondent diff --git a/src/documents/checks.py b/src/documents/checks.py index a97c517aa..8f8fbf4f9 100644 --- a/src/documents/checks.py +++ b/src/documents/checks.py @@ -32,8 +32,7 @@ def changed_password_check(app_configs, **kwargs): if not settings.PASSPHRASE: return [ Error( - "The database contains encrypted documents but no password " - "is set.", + "The database contains encrypted documents but no password is set.", ), ] diff --git a/src/documents/classifier.py b/src/documents/classifier.py index 4c36dc5e0..72bf1f16c 100644 --- a/src/documents/classifier.py +++ b/src/documents/classifier.py @@ -170,6 +170,7 @@ class DocumentClassifier: ) .select_related("document_type", "correspondent", "storage_path") .prefetch_related("tags") + .order_by("pk") ) # No documents exit to train against @@ -199,11 +200,10 @@ class DocumentClassifier: hasher.update(y.to_bytes(4, "little", signed=True)) labels_correspondent.append(y) - tags: list[int] = sorted( - tag.pk - for tag in doc.tags.filter( - matching_algorithm=MatchingModel.MATCH_AUTO, - ) + tags: list[int] = list( + doc.tags.filter(matching_algorithm=MatchingModel.MATCH_AUTO) + .order_by("pk") + .values_list("pk", flat=True), ) for tag in tags: hasher.update(tag.to_bytes(4, "little", signed=True)) @@ -315,8 +315,7 @@ class DocumentClassifier: else: self.correspondent_classifier = None logger.debug( - "There are no correspondents. Not training correspondent " - "classifier.", + "There are no correspondents. Not training correspondent classifier.", ) if num_document_types > 0: @@ -326,8 +325,7 @@ class DocumentClassifier: else: self.document_type_classifier = None logger.debug( - "There are no document types. Not training document type " - "classifier.", + "There are no document types. Not training document type classifier.", ) if num_storage_paths > 0: diff --git a/src/documents/mail.py b/src/documents/mail.py index 5183b1bae..12a1c0aa0 100644 --- a/src/documents/mail.py +++ b/src/documents/mail.py @@ -1,7 +1,5 @@ -from email.encoders import encode_base64 -from email.mime.base import MIMEBase +from email import message_from_bytes from pathlib import Path -from urllib.parse import quote from django.conf import settings from django.core.mail import EmailMessage @@ -27,35 +25,14 @@ def send_email( if attachment: # Something could be renaming the file concurrently so it can't be attached 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 = ( - attachment_mime_type.split("/", 1) - if attachment_mime_type - else ("application", "octet-stream") + email.attach( + filename=attachment.name, + content=content, + 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() diff --git a/src/documents/management/commands/decrypt_documents.py b/src/documents/management/commands/decrypt_documents.py index aa8e7d184..793cac4bb 100644 --- a/src/documents/management/commands/decrypt_documents.py +++ b/src/documents/management/commands/decrypt_documents.py @@ -18,8 +18,7 @@ class Command(BaseCommand): parser.add_argument( "--passphrase", help=( - "If PAPERLESS_PASSPHRASE isn't set already, you need to " - "specify it here" + "If PAPERLESS_PASSPHRASE isn't set already, you need to specify it here" ), ) diff --git a/src/documents/tests/samples/eml_with_umlaut.eml b/src/documents/tests/samples/eml_with_umlaut.eml new file mode 100644 index 000000000..5a23cb1c0 --- /dev/null +++ b/src/documents/tests/samples/eml_with_umlaut.eml @@ -0,0 +1,63 @@ +From: =?UTF-8?Q?My_Name=C3=B6er?= +Return-Path: +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 ; Wed, 15 Jan 2025 21:43:55 +0100 (CET) +Message-ID: +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: üöäüäö diff --git a/src/documents/tests/test_admin.py b/src/documents/tests/test_admin.py index a32a31adf..ab32562a8 100644 --- a/src/documents/tests/test_admin.py +++ b/src/documents/tests/test_admin.py @@ -1,4 +1,7 @@ +import types + from django.contrib.admin.sites import AdminSite +from django.contrib.auth.models import User from django.test import TestCase from django.utils import timezone @@ -6,6 +9,7 @@ from documents import index from documents.admin import DocumentAdmin from documents.models import Document from documents.tests.utils import DirectoriesMixin +from paperless.admin import PaperlessUserAdmin class TestDocumentAdmin(DirectoriesMixin, TestCase): @@ -64,3 +68,39 @@ class TestDocumentAdmin(DirectoriesMixin, TestCase): created=timezone.make_aware(timezone.datetime(2020, 4, 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) diff --git a/src/documents/tests/test_api_permissions.py b/src/documents/tests/test_api_permissions.py index ef50c55f7..5de1887b2 100644 --- a/src/documents/tests/test_api_permissions.py +++ b/src/documents/tests/test_api_permissions.py @@ -681,6 +681,80 @@ class TestApiUser(DirectoriesMixin, APITestCase): ) 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): ENDPOINT = "/api/groups/" diff --git a/src/documents/tests/test_api_search.py b/src/documents/tests/test_api_search.py index e524e7b91..118862979 100644 --- a/src/documents/tests/test_api_search.py +++ b/src/documents/tests/test_api_search.py @@ -96,7 +96,7 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase): doc = Document.objects.create( checksum=str(i), pk=i + 1, - title=f"Document {i+1}", + title=f"Document {i + 1}", content="content", ) index.update_document(writer, doc) @@ -131,7 +131,7 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase): doc = Document.objects.create( checksum=str(i), pk=i + 1, - title=f"Document {i+1}", + title=f"Document {i + 1}", content="content", ) index.update_document(writer, doc) @@ -630,8 +630,8 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase): doc = Document.objects.create( checksum=str(i), pk=i + 1, - title=f"Document {i+1}", - content=f"Things document {i+1}", + title=f"Document {i + 1}", + content=f"Things document {i + 1}", ) index.update_document(writer, doc) diff --git a/src/documents/tests/test_workflows.py b/src/documents/tests/test_workflows.py index cb5a132af..b9205d4bb 100644 --- a/src/documents/tests/test_workflows.py +++ b/src/documents/tests/test_workflows.py @@ -2149,9 +2149,8 @@ class TestWorkflows( EMAIL_ENABLED=True, PAPERLESS_URL="http://localhost:8000", ) - @mock.patch("httpx.post") @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: - Document updated workflow with email action @@ -2199,6 +2198,24 @@ class TestWorkflows( 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( EMAIL_ENABLED=False, ) diff --git a/src/documents/tests/utils.py b/src/documents/tests/utils.py index cd4db84e6..739433bb6 100644 --- a/src/documents/tests/utils.py +++ b/src/documents/tests/utils.py @@ -295,9 +295,9 @@ class TestMigrations(TransactionTestCase): def setUp(self): super().setUp() - assert ( - self.migrate_from and self.migrate_to - ), f"TestCase '{type(self).__name__}' must define migrate_from and migrate_to properties" + assert self.migrate_from and self.migrate_to, ( + f"TestCase '{type(self).__name__}' must define migrate_from and migrate_to properties" + ) self.migrate_from = [(self.app, self.migrate_from)] if self.dependencies is not None: self.migrate_from.extend(self.dependencies) diff --git a/src/locale/fr_FR/LC_MESSAGES/django.po b/src/locale/fr_FR/LC_MESSAGES/django.po index 8d569240d..2df3301e8 100644 --- a/src/locale/fr_FR/LC_MESSAGES/django.po +++ b/src/locale/fr_FR/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: paperless-ngx\n" "Report-Msgid-Bugs-To: \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" "Language-Team: French\n" "Language: fr_FR\n" diff --git a/src/locale/it_IT/LC_MESSAGES/django.po b/src/locale/it_IT/LC_MESSAGES/django.po index d31dbc1af..6a87ffe1b 100644 --- a/src/locale/it_IT/LC_MESSAGES/django.po +++ b/src/locale/it_IT/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: paperless-ngx\n" "Report-Msgid-Bugs-To: \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" "Language-Team: Italian\n" "Language: it_IT\n" @@ -31,7 +31,7 @@ msgstr "Campo personalizzato della query non valido" #: documents/filters.py:365 msgid "Invalid expression list. Must be nonempty." -msgstr "" +msgstr "Elenco delle espressioni non valido. Deve essere non vuoto." #: documents/filters.py:386 msgid "Invalid logical operator {op!r}" @@ -39,7 +39,7 @@ msgstr "Operatore logico non valido {op!r}" #: documents/filters.py:400 msgid "Maximum number of query conditions exceeded." -msgstr "" +msgstr "Numero massimo delle condizioni della jQuery superato." #: documents/filters.py:465 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 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 msgid "Maximum nesting depth exceeded." @@ -794,7 +794,7 @@ msgstr "Campo personalizzato" #: documents/models.py:1037 msgid "Workflow Trigger Type" -msgstr "" +msgstr "Tipo Frigger Del Workshop" #: documents/models.py:1049 msgid "filter path" diff --git a/src/locale/no_NO/LC_MESSAGES/django.po b/src/locale/no_NO/LC_MESSAGES/django.po index 825dee24f..12ee531f7 100644 --- a/src/locale/no_NO/LC_MESSAGES/django.po +++ b/src/locale/no_NO/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: paperless-ngx\n" "Report-Msgid-Bugs-To: \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" "Language-Team: Norwegian\n" "Language: no_NO\n" @@ -23,7 +23,7 @@ msgstr "Dokumenter" #: documents/filters.py:336 msgid "Value must be valid JSON." -msgstr "" +msgstr "Verdien må være en gyldig JSON." #: documents/filters.py:355 msgid "Invalid custom field query expression" @@ -1149,7 +1149,7 @@ msgstr "Ugyldig variabel oppdaget." #: documents/templates/account/email/base_message.txt:1 #, python-format msgid "Hello from %(site_name)s!" -msgstr "" +msgstr "Hei fra %(site_name)s!" #: documents/templates/account/email/base_message.txt:5 #, python-format diff --git a/src/locale/ru_RU/LC_MESSAGES/django.po b/src/locale/ru_RU/LC_MESSAGES/django.po index d30263875..2f9942db0 100644 --- a/src/locale/ru_RU/LC_MESSAGES/django.po +++ b/src/locale/ru_RU/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: paperless-ngx\n" "Report-Msgid-Bugs-To: \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" "Language-Team: Russian\n" "Language: ru_RU\n" @@ -374,7 +374,7 @@ msgstr "обратная сортировка" #: documents/models.py:451 msgid "View page size" -msgstr "" +msgstr "Посмотреть размер страницы" #: documents/models.py:459 msgid "View display mode" diff --git a/src/paperless/admin.py b/src/paperless/admin.py new file mode 100644 index 000000000..89575fe2e --- /dev/null +++ b/src/paperless/admin.py @@ -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) diff --git a/src/paperless/views.py b/src/paperless/views.py index b5142ed62..03721adf2 100644 --- a/src/paperless/views.py +++ b/src/paperless/views.py @@ -109,6 +109,25 @@ class UserViewSet(ModelViewSet): filterset_class = UserFilterSet 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"]) def deactivate_totp(self, request, pk=None): request_user = request.user diff --git a/src/paperless_mail/mail.py b/src/paperless_mail/mail.py index 7809c7389..e25c4f227 100644 --- a/src/paperless_mail/mail.py +++ b/src/paperless_mail/mail.py @@ -552,8 +552,7 @@ class MailAccountHandler(LoggingMixin): mailbox_login(M, account) self.log.debug( - f"Account {account}: Processing " - f"{account.rules.count()} rule(s)", + f"Account {account}: Processing {account.rules.count()} rule(s)", ) for rule in account.rules.order_by("order"): diff --git a/src/paperless_mail/tests/test_parsers_live.py b/src/paperless_mail/tests/test_parsers_live.py index 9e13ad25e..e1febb1e5 100644 --- a/src/paperless_mail/tests/test_parsers_live.py +++ b/src/paperless_mail/tests/test_parsers_live.py @@ -129,9 +129,11 @@ class TestParserLive: assert thumb.exists() assert thumb.is_file() - assert ( - self.imagehash(thumb) == self.imagehash(simple_txt_email_thumbnail_file) - ), f"Created Thumbnail {thumb} differs from expected file {simple_txt_email_thumbnail_file}" + assert self.imagehash(thumb) == self.imagehash( + 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): """ @@ -226,6 +228,6 @@ class TestParserLive: # The created pdf is not reproducible. But the converted image should always look the same. expected_hash = self.imagehash(html_email_thumbnail_file) - assert ( - generated_thumbnail_hash == expected_hash - ), f"PDF looks different. Check if {generated_thumbnail} looks weird." + assert generated_thumbnail_hash == expected_hash, ( + f"PDF looks different. Check if {generated_thumbnail} looks weird." + ) diff --git a/src/paperless_tesseract/parsers.py b/src/paperless_tesseract/parsers.py index 28b052614..e7968a61e 100644 --- a/src/paperless_tesseract/parsers.py +++ b/src/paperless_tesseract/parsers.py @@ -455,8 +455,7 @@ class RasterisedDocumentParser(DocumentParser): self.text = text_original else: self.log.warning( - f"No text was found in {document_path}, the content will " - f"be empty.", + f"No text was found in {document_path}, the content will be empty.", ) self.text = ""