mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Merge branch 'dev' into feature-nested-tags2
This commit is contained in:
		| @@ -506,6 +506,7 @@ for the possible codes and their meanings. | ||||
| The `localize_date` filter formats a date or datetime object into a localized string using Babel internationalization. | ||||
| This takes into account the provided locale for translation. Since this must be used on a date or datetime object, | ||||
| you must access the field directly, i.e. `document.created`. | ||||
| An ISO string can also be provided to control the output format. | ||||
|  | ||||
| ###### Syntax | ||||
|  | ||||
| @@ -516,7 +517,7 @@ you must access the field directly, i.e. `document.created`. | ||||
|  | ||||
| ###### Parameters | ||||
|  | ||||
| -   `value` (date | datetime): Date or datetime object to format (datetime should be timezone-aware) | ||||
| -   `value` (date | datetime | str): Date, datetime object or ISO string to format (datetime should be timezone-aware) | ||||
| -   `format` (str): Format type - either a Babel preset ('short', 'medium', 'long', 'full') or custom pattern | ||||
| -   `locale` (str): Locale code for localization (e.g., 'en_US', 'fr_FR', 'de_DE') | ||||
|  | ||||
|   | ||||
| @@ -408,7 +408,7 @@ Currently, there are three events that correspond to workflow trigger 'types': | ||||
|    but the document content has been extracted and metadata such as document type, tags, etc. have been set, so these can now | ||||
|    be used for filtering. | ||||
| 3. **Document Updated**: when a document is updated. Similar to 'added' events, triggers can include filtering by content matching, | ||||
|    tags, doc type, or correspondent. | ||||
|    tags, doc type, correspondent or storage path. | ||||
| 4. **Scheduled**: a scheduled trigger that can be used to run workflows at a specific time. The date used can be either the document | ||||
|    added, created, updated date or you can specify a (date) custom field. You can also specify a day offset from the date (positive | ||||
|    offsets will trigger after the date, negative offsets will trigger before). | ||||
| @@ -452,10 +452,11 @@ Workflows allow you to filter by: | ||||
| -   File path, including wildcards. Note that enabling `PAPERLESS_CONSUMER_RECURSIVE` would allow, for | ||||
|     example, automatically assigning documents to different owners based on the upload directory. | ||||
| -   Mail rule. Choosing this option will force 'mail fetch' to be the workflow source. | ||||
| -   Content matching (`Added` and `Updated` triggers only). Filter document content using the matching settings. | ||||
| -   Tags (`Added` and `Updated` triggers only). Filter for documents with any of the specified tags | ||||
| -   Document type (`Added` and `Updated` triggers only). Filter documents with this doc type | ||||
| -   Correspondent (`Added` and `Updated` triggers only). Filter documents with this correspondent | ||||
| -   Content matching (`Added`, `Updated` and `Scheduled` triggers only). Filter document content using the matching settings. | ||||
| -   Tags (`Added`, `Updated` and `Scheduled` triggers only). Filter for documents with any of the specified tags | ||||
| -   Document type (`Added`, `Updated` and `Scheduled` triggers only). Filter documents with this doc type | ||||
| -   Correspondent (`Added`, `Updated` and `Scheduled` triggers only). Filter documents with this correspondent | ||||
| -   Storage path (`Added`, `Updated` and `Scheduled` triggers only). Filter documents with this storage path | ||||
|  | ||||
| ### Workflow Actions | ||||
|  | ||||
| @@ -505,35 +506,52 @@ you may want to adjust these settings to prevent abuse. | ||||
|  | ||||
| #### Workflow placeholders | ||||
|  | ||||
| Some workflow text can include placeholders but the available options differ depending on the type of | ||||
| workflow trigger. This is because at the time of consumption (when the text is to be set), no automatic tags etc. have been | ||||
| applied. You can use the following placeholders with any trigger type: | ||||
| Titles can be assigned by workflows using [Jinja templates](https://jinja.palletsprojects.com/en/3.1.x/templates/). | ||||
| This allows for complex logic to be used to generate the title, including [logical structures](https://jinja.palletsprojects.com/en/3.1.x/templates/#list-of-control-structures) | ||||
| and [filters](https://jinja.palletsprojects.com/en/3.1.x/templates/#id11). | ||||
| The template is provided as a string. | ||||
|  | ||||
| -   `{correspondent}`: assigned correspondent name | ||||
| -   `{document_type}`: assigned document type name | ||||
| -   `{owner_username}`: assigned owner username | ||||
| -   `{added}`: added datetime | ||||
| -   `{added_year}`: added year | ||||
| -   `{added_year_short}`: added year | ||||
| -   `{added_month}`: added month | ||||
| -   `{added_month_name}`: added month name | ||||
| -   `{added_month_name_short}`: added month short name | ||||
| -   `{added_day}`: added day | ||||
| -   `{added_time}`: added time in HH:MM format | ||||
| -   `{original_filename}`: original file name without extension | ||||
| -   `{filename}`: current file name without extension | ||||
| Using Jinja2 Templates is also useful for [Date localization](advanced_usage.md#Date-Localization) in the title. | ||||
|  | ||||
| The available inputs differ depending on the type of workflow trigger. | ||||
| This is because at the time of consumption (when the text is to be set), no automatic tags etc. have been | ||||
| applied. You can use the following placeholders in the template with any trigger type: | ||||
|  | ||||
| -   `{{correspondent}}`: assigned correspondent name | ||||
| -   `{{document_type}}`: assigned document type name | ||||
| -   `{{owner_username}}`: assigned owner username | ||||
| -   `{{added}}`: added datetime | ||||
| -   `{{added_year}}`: added year | ||||
| -   `{{added_year_short}}`: added year | ||||
| -   `{{added_month}}`: added month | ||||
| -   `{{added_month_name}}`: added month name | ||||
| -   `{{added_month_name_short}}`: added month short name | ||||
| -   `{{added_day}}`: added day | ||||
| -   `{{added_time}}`: added time in HH:MM format | ||||
| -   `{{original_filename}}`: original file name without extension | ||||
| -   `{{filename}}`: current file name without extension | ||||
|  | ||||
| The following placeholders are only available for "added" or "updated" triggers | ||||
|  | ||||
| -   `{created}`: created datetime | ||||
| -   `{created_year}`: created year | ||||
| -   `{created_year_short}`: created year | ||||
| -   `{created_month}`: created month | ||||
| -   `{created_month_name}`: created month name | ||||
| -   `{created_month_name_short}`: created month short name | ||||
| -   `{created_day}`: created day | ||||
| -   `{created_time}`: created time in HH:MM format | ||||
| -   `{doc_url}`: URL to the document in the web UI. Requires the `PAPERLESS_URL` setting to be set. | ||||
| -   `{{created}}`: created datetime | ||||
| -   `{{created_year}}`: created year | ||||
| -   `{{created_year_short}}`: created year | ||||
| -   `{{created_month}}`: created month | ||||
| -   `{{created_month_name}}`: created month name | ||||
| -   `{created_month_name_short}}`: created month short name | ||||
| -   `{{created_day}}`: created day | ||||
| -   `{{created_time}}`: created time in HH:MM format | ||||
| -   `{{doc_url}}`: URL to the document in the web UI. Requires the `PAPERLESS_URL` setting to be set. | ||||
|  | ||||
| ##### Examples | ||||
|  | ||||
| ```jinja2 | ||||
| {{ created | localize_date('MMMM', 'en_US') }} | ||||
| <!-- Output: "January" --> | ||||
|  | ||||
| {{ added | localize_date('MMMM', 'de_DE') }} | ||||
| <!-- Output: "Juni" --> # codespell:ignore | ||||
| ``` | ||||
|  | ||||
| ### Workflow permissions | ||||
|  | ||||
|   | ||||
| @@ -103,7 +103,7 @@ testing = [ | ||||
|   "factory-boy~=3.3.1", | ||||
|   "imagehash", | ||||
|   "pytest~=8.4.1", | ||||
|   "pytest-cov~=6.2.1", | ||||
|   "pytest-cov~=7.0.0", | ||||
|   "pytest-django~=4.11.1", | ||||
|   "pytest-env", | ||||
|   "pytest-httpx", | ||||
|   | ||||
| @@ -385,7 +385,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">109</context> | ||||
|           <context context-type="linenumber">113</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1241348629231510663" datatype="html"> | ||||
| @@ -534,7 +534,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">362</context> | ||||
|           <context context-type="linenumber">366</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3768927257183755959" datatype="html"> | ||||
| @@ -593,7 +593,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">355</context> | ||||
|           <context context-type="linenumber">359</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/custom-fields-bulk-edit-dialog/custom-fields-bulk-edit-dialog.component.html</context> | ||||
| @@ -739,7 +739,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">375</context> | ||||
|           <context context-type="linenumber">379</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context> | ||||
| @@ -1197,7 +1197,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">331</context> | ||||
|           <context context-type="linenumber">335</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context> | ||||
| @@ -1291,19 +1291,19 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">209</context> | ||||
|           <context context-type="linenumber">210</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">228</context> | ||||
|           <context context-type="linenumber">229</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">295</context> | ||||
|           <context context-type="linenumber">296</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">314</context> | ||||
|           <context context-type="linenumber">315</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context> | ||||
| @@ -1326,19 +1326,19 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">217</context> | ||||
|           <context context-type="linenumber">218</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">236</context> | ||||
|           <context context-type="linenumber">237</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">303</context> | ||||
|           <context context-type="linenumber">304</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">322</context> | ||||
|           <context context-type="linenumber">323</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context> | ||||
| @@ -1364,11 +1364,11 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">242</context> | ||||
|           <context context-type="linenumber">243</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">328</context> | ||||
|           <context context-type="linenumber">329</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context> | ||||
| @@ -2544,11 +2544,11 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">1019</context> | ||||
|           <context context-type="linenumber">1023</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">1384</context> | ||||
|           <context context-type="linenumber">1388</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
| @@ -3156,7 +3156,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">972</context> | ||||
|           <context context-type="linenumber">976</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
| @@ -3346,7 +3346,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">103</context> | ||||
|           <context context-type="linenumber">107</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/guards/dirty-saved-view.guard.ts</context> | ||||
| @@ -4055,7 +4055,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">196</context> | ||||
|           <context context-type="linenumber">197</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4754802869258527587" datatype="html"> | ||||
| @@ -4073,7 +4073,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">197</context> | ||||
|           <context context-type="linenumber">198</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1519954996184640001" datatype="html"> | ||||
| @@ -4291,7 +4291,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">297</context> | ||||
|           <context context-type="linenumber">301</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8057014866157903311" datatype="html"> | ||||
| @@ -4395,7 +4395,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">88</context> | ||||
|           <context context-type="linenumber">92</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5342432350421167093" datatype="html"> | ||||
| @@ -4739,238 +4739,245 @@ | ||||
|           <context context-type="linenumber">179</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4277260190522078330" datatype="html"> | ||||
|         <source>Has storage path</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">180</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6417103744331194518" datatype="html"> | ||||
|         <source>Action type</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">189</context> | ||||
|           <context context-type="linenumber">190</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6019822389883736115" datatype="html"> | ||||
|         <source>Assign title</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">194</context> | ||||
|           <context context-type="linenumber">195</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1098196422099517191" datatype="html"> | ||||
|         <source>Can include some placeholders, see <a target='_blank' href='https://docs.paperless-ngx.com/usage/#workflows'>documentation</a>.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">194</context> | ||||
|           <context context-type="linenumber">195</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6528897010417701530" datatype="html"> | ||||
|         <source>Assign tags</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">195</context> | ||||
|           <context context-type="linenumber">196</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7198346314713788799" datatype="html"> | ||||
|         <source>Assign storage path</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">198</context> | ||||
|           <context context-type="linenumber">199</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="475685412372379925" datatype="html"> | ||||
|         <source>Assign custom fields</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">199</context> | ||||
|           <context context-type="linenumber">200</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5057200219587080996" datatype="html"> | ||||
|         <source>Assign owner</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">203</context> | ||||
|           <context context-type="linenumber">204</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1749184201773078639" datatype="html"> | ||||
|         <source>Assign view permissions</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">205</context> | ||||
|           <context context-type="linenumber">206</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1744964187586405039" datatype="html"> | ||||
|         <source>Assign edit permissions</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">224</context> | ||||
|           <context context-type="linenumber">225</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6236311670364192011" datatype="html"> | ||||
|         <source>Remove tags</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">251</context> | ||||
|           <context context-type="linenumber">252</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7890599006071681081" datatype="html"> | ||||
|         <source>Remove all</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">252</context> | ||||
|           <context context-type="linenumber">253</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">258</context> | ||||
|           <context context-type="linenumber">259</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">264</context> | ||||
|           <context context-type="linenumber">265</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">270</context> | ||||
|           <context context-type="linenumber">271</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">276</context> | ||||
|           <context context-type="linenumber">277</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">283</context> | ||||
|           <context context-type="linenumber">284</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">289</context> | ||||
|           <context context-type="linenumber">290</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8636414563726517994" datatype="html"> | ||||
|         <source>Remove correspondents</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">257</context> | ||||
|           <context context-type="linenumber">258</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5305293055593064952" datatype="html"> | ||||
|         <source>Remove document types</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">263</context> | ||||
|           <context context-type="linenumber">264</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2400388879708187" datatype="html"> | ||||
|         <source>Remove storage paths</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">269</context> | ||||
|           <context context-type="linenumber">270</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4324304327041955720" datatype="html"> | ||||
|         <source>Remove custom fields</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">275</context> | ||||
|           <context context-type="linenumber">276</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8367536502602515064" datatype="html"> | ||||
|         <source>Remove owners</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">282</context> | ||||
|           <context context-type="linenumber">283</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3393772184866313281" datatype="html"> | ||||
|         <source>Remove permissions</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">288</context> | ||||
|           <context context-type="linenumber">289</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3145629643370481114" datatype="html"> | ||||
|         <source>View permissions</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">291</context> | ||||
|           <context context-type="linenumber">292</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1946660694635960249" datatype="html"> | ||||
|         <source>Edit permissions</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">310</context> | ||||
|           <context context-type="linenumber">311</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8987736563240025468" datatype="html"> | ||||
|         <source>Email subject</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">338</context> | ||||
|           <context context-type="linenumber">339</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8239445959209739142" datatype="html"> | ||||
|         <source>Email body</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">339</context> | ||||
|           <context context-type="linenumber">340</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1222152280703048012" datatype="html"> | ||||
|         <source>Email recipients</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">340</context> | ||||
|           <context context-type="linenumber">341</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7916910101279824329" datatype="html"> | ||||
|         <source>Attach document</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">341</context> | ||||
|           <context context-type="linenumber">342</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5028001922785731600" datatype="html"> | ||||
|         <source>Webhook url</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">349</context> | ||||
|           <context context-type="linenumber">350</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7491983459027245019" datatype="html"> | ||||
|         <source>Use parameters for webhook body</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">351</context> | ||||
|           <context context-type="linenumber">352</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4078214298308732810" datatype="html"> | ||||
|         <source>Send webhook payload as JSON</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">352</context> | ||||
|           <context context-type="linenumber">353</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6806149889743731985" datatype="html"> | ||||
|         <source>Webhook params</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">355</context> | ||||
|           <context context-type="linenumber">356</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7089924379374330" datatype="html"> | ||||
|         <source>Webhook body</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">357</context> | ||||
|           <context context-type="linenumber">358</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3829826512656746316" datatype="html"> | ||||
|         <source>Webhook headers</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">359</context> | ||||
|           <context context-type="linenumber">360</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2114525789021600887" datatype="html"> | ||||
|         <source>Include document</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> | ||||
|           <context context-type="linenumber">360</context> | ||||
|           <context context-type="linenumber">361</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4626030417479279989" datatype="html"> | ||||
| @@ -6012,7 +6019,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">84</context> | ||||
|           <context context-type="linenumber">88</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3429210839568770054" datatype="html"> | ||||
| @@ -6578,11 +6585,18 @@ | ||||
|           <context context-type="linenumber">107</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7049887240439736400" datatype="html"> | ||||
|         <source>Print</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">58</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1418444397960583910" datatype="html"> | ||||
|         <source>More like this</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">58</context> | ||||
|           <context context-type="linenumber">62</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context> | ||||
| @@ -6593,39 +6607,39 @@ | ||||
|         <source>PDF Editor</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">62</context> | ||||
|           <context context-type="linenumber">66</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">1383</context> | ||||
|           <context context-type="linenumber">1387</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6490688569532630280" datatype="html"> | ||||
|         <source>Send</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">80</context> | ||||
|           <context context-type="linenumber">84</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4452427314943113135" datatype="html"> | ||||
|         <source>Previous</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">106</context> | ||||
|           <context context-type="linenumber">110</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5028777105388019087" datatype="html"> | ||||
|         <source>Details</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">119</context> | ||||
|           <context context-type="linenumber">123</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5701618810648052610" datatype="html"> | ||||
|         <source>Title</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">122</context> | ||||
|           <context context-type="linenumber">126</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context> | ||||
| @@ -6648,21 +6662,21 @@ | ||||
|         <source>Archive serial number</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">123</context> | ||||
|           <context context-type="linenumber">127</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5114742157723900905" datatype="html"> | ||||
|         <source>Date created</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">124</context> | ||||
|           <context context-type="linenumber">128</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2691296884221415710" datatype="html"> | ||||
|         <source>Correspondent</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">126</context> | ||||
|           <context context-type="linenumber">130</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context> | ||||
| @@ -6689,7 +6703,7 @@ | ||||
|         <source>Document type</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">128</context> | ||||
|           <context context-type="linenumber">132</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context> | ||||
| @@ -6716,7 +6730,7 @@ | ||||
|         <source>Storage path</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">130</context> | ||||
|           <context context-type="linenumber">134</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context> | ||||
| @@ -6739,7 +6753,7 @@ | ||||
|         <source>Default</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">131</context> | ||||
|           <context context-type="linenumber">135</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context> | ||||
| @@ -6750,14 +6764,14 @@ | ||||
|         <source>Content</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">227</context> | ||||
|           <context context-type="linenumber">231</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="218403386307979629" datatype="html"> | ||||
|         <source>Metadata</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">236</context> | ||||
|           <context context-type="linenumber">240</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/metadata-collapse/metadata-collapse.component.ts</context> | ||||
| @@ -6768,175 +6782,175 @@ | ||||
|         <source>Date modified</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">243</context> | ||||
|           <context context-type="linenumber">247</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6392918669949841614" datatype="html"> | ||||
|         <source>Date added</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">247</context> | ||||
|           <context context-type="linenumber">251</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="146828917013192897" datatype="html"> | ||||
|         <source>Media filename</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">251</context> | ||||
|           <context context-type="linenumber">255</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4500855521601039868" datatype="html"> | ||||
|         <source>Original filename</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">255</context> | ||||
|           <context context-type="linenumber">259</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7985558498848210210" datatype="html"> | ||||
|         <source>Original MD5 checksum</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">259</context> | ||||
|           <context context-type="linenumber">263</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5888243105821763422" datatype="html"> | ||||
|         <source>Original file size</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">263</context> | ||||
|           <context context-type="linenumber">267</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2696647325713149563" datatype="html"> | ||||
|         <source>Original mime type</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">267</context> | ||||
|           <context context-type="linenumber">271</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="342875990758166588" datatype="html"> | ||||
|         <source>Archive MD5 checksum</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">272</context> | ||||
|           <context context-type="linenumber">276</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6033581412811562084" datatype="html"> | ||||
|         <source>Archive file size</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">278</context> | ||||
|           <context context-type="linenumber">282</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6992781481378431874" datatype="html"> | ||||
|         <source>Original document metadata</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">287</context> | ||||
|           <context context-type="linenumber">291</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2846565152091361585" datatype="html"> | ||||
|         <source>Archived document metadata</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">290</context> | ||||
|           <context context-type="linenumber">294</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7206723502037428235" datatype="html"> | ||||
|         <source>Notes <x id="START_BLOCK_IF" equiv-text="@if (document?.notes.length) {"/><x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span class="badge text-bg-secondary ms-1">"/><x id="INTERPOLATION" equiv-text="ngth}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span"/><x id="CLOSE_BLOCK_IF" equiv-text="}"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">309,312</context> | ||||
|           <context context-type="linenumber">313,316</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="186236568870281953" datatype="html"> | ||||
|         <source>History</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">320</context> | ||||
|           <context context-type="linenumber">324</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5129524307369213584" datatype="html"> | ||||
|         <source>Save & next</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">357</context> | ||||
|           <context context-type="linenumber">361</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4910102545766233758" datatype="html"> | ||||
|         <source>Save & close</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">360</context> | ||||
|           <context context-type="linenumber">364</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1309556917227148591" datatype="html"> | ||||
|         <source>Document loading...</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">370</context> | ||||
|           <context context-type="linenumber">374</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8191371354890763172" datatype="html"> | ||||
|         <source>Enter Password</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">424</context> | ||||
|           <context context-type="linenumber">428</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2218903673684131427" datatype="html"> | ||||
|         <source>An error occurred loading content: <x id="PH" equiv-text="err.message ?? err.toString()"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">410,412</context> | ||||
|           <context context-type="linenumber">414,416</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3200733026060976258" datatype="html"> | ||||
|         <source>Document changes detected</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">444</context> | ||||
|           <context context-type="linenumber">448</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2887155916749964" datatype="html"> | ||||
|         <source>The version of this document in your browser session appears older than the existing version.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">445</context> | ||||
|           <context context-type="linenumber">449</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="237142428785956348" datatype="html"> | ||||
|         <source>Saving the document here may overwrite other changes that were made. To restore the existing version, discard your changes or close the document.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">446</context> | ||||
|           <context context-type="linenumber">450</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8720977247725652816" datatype="html"> | ||||
|         <source>Ok</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">448</context> | ||||
|           <context context-type="linenumber">452</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6142395741265832184" datatype="html"> | ||||
|         <source>Next document</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">574</context> | ||||
|           <context context-type="linenumber">578</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="651985345816518480" datatype="html"> | ||||
|         <source>Previous document</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">584</context> | ||||
|           <context context-type="linenumber">588</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2885986061416655600" datatype="html"> | ||||
|         <source>Close document</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">592</context> | ||||
|           <context context-type="linenumber">596</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/services/open-documents.service.ts</context> | ||||
| @@ -6947,67 +6961,67 @@ | ||||
|         <source>Save document</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">599</context> | ||||
|           <context context-type="linenumber">603</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1784543155727940353" datatype="html"> | ||||
|         <source>Save and close / next</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">608</context> | ||||
|           <context context-type="linenumber">612</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5758784066858623886" datatype="html"> | ||||
|         <source>Error retrieving metadata</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">660</context> | ||||
|           <context context-type="linenumber">664</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3456881259945295697" datatype="html"> | ||||
|         <source>Error retrieving suggestions.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">689</context> | ||||
|           <context context-type="linenumber">693</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2194092841814123758" datatype="html"> | ||||
|         <source>Document "<x id="PH" equiv-text="newValues.title"/>" saved successfully.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">861</context> | ||||
|           <context context-type="linenumber">865</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">885</context> | ||||
|           <context context-type="linenumber">889</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6626387786259219838" datatype="html"> | ||||
|         <source>Error saving document "<x id="PH" equiv-text="this.document.title"/>"</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">891</context> | ||||
|           <context context-type="linenumber">895</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="448882439049417053" datatype="html"> | ||||
|         <source>Error saving document</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">941</context> | ||||
|           <context context-type="linenumber">945</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8410796510716511826" datatype="html"> | ||||
|         <source>Do you really want to move the document "<x id="PH" equiv-text="this.document.title"/>" to the trash?</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">973</context> | ||||
|           <context context-type="linenumber">977</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="282586936710748252" datatype="html"> | ||||
|         <source>Documents can be restored prior to permanent deletion.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">974</context> | ||||
|           <context context-type="linenumber">978</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
| @@ -7018,7 +7032,7 @@ | ||||
|         <source>Move to trash</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">976</context> | ||||
|           <context context-type="linenumber">980</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
| @@ -7029,14 +7043,14 @@ | ||||
|         <source>Error deleting document</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">995</context> | ||||
|           <context context-type="linenumber">999</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="619486176823357521" datatype="html"> | ||||
|         <source>Reprocess confirm</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">1015</context> | ||||
|           <context context-type="linenumber">1019</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
| @@ -7047,67 +7061,81 @@ | ||||
|         <source>This operation will permanently recreate the archive file for this document.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">1016</context> | ||||
|           <context context-type="linenumber">1020</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="302054111564709516" datatype="html"> | ||||
|         <source>The archive file will be re-generated with the current settings.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">1017</context> | ||||
|           <context context-type="linenumber">1021</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8251197608401006898" datatype="html"> | ||||
|         <source>Reprocess operation for "<x id="PH" equiv-text="this.document.title"/>" will begin in the background. Close and re-open or reload this document after the operation has completed to see new content.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">1027</context> | ||||
|           <context context-type="linenumber">1031</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4409560272830824468" datatype="html"> | ||||
|         <source>Error executing operation</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">1038</context> | ||||
|           <context context-type="linenumber">1042</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6030453331794586802" datatype="html"> | ||||
|         <source>Error downloading document</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">1087</context> | ||||
|           <context context-type="linenumber">1091</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4458954481601077369" datatype="html"> | ||||
|         <source>Page Fit</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">1164</context> | ||||
|           <context context-type="linenumber">1168</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4663705961777238777" datatype="html"> | ||||
|         <source>PDF edit operation for "<x id="PH" equiv-text="this.document.title"/>" will begin in the background.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">1402</context> | ||||
|           <context context-type="linenumber">1406</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="9043972994040261999" datatype="html"> | ||||
|         <source>Error executing PDF edit operation</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">1414</context> | ||||
|           <context context-type="linenumber">1418</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3740891324955700797" datatype="html"> | ||||
|         <source>Print failed.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">1450</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6457245677384603573" datatype="html"> | ||||
|         <source>Error loading document for printing.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">1458</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6085793215710522488" datatype="html"> | ||||
|         <source>An error occurred loading tiff: <x id="PH" equiv-text="err.toString()"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">1481</context> | ||||
|           <context context-type="linenumber">1523</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">1485</context> | ||||
|           <context context-type="linenumber">1527</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4958946940233632319" datatype="html"> | ||||
|   | ||||
| @@ -177,6 +177,7 @@ | ||||
|           <pngx-input-tags [allowCreate]="false" i18n-title title="Has any of tags" formControlName="filter_has_tags"></pngx-input-tags> | ||||
|           <pngx-input-select i18n-title title="Has correspondent" [items]="correspondents" [allowNull]="true" formControlName="filter_has_correspondent"></pngx-input-select> | ||||
|           <pngx-input-select i18n-title title="Has document type" [items]="documentTypes" [allowNull]="true" formControlName="filter_has_document_type"></pngx-input-select> | ||||
|           <pngx-input-select i18n-title title="Has storage path" [items]="storagePaths" [allowNull]="true" formControlName="filter_has_storage_path"></pngx-input-select> | ||||
|         </div> | ||||
|       } | ||||
|     </div> | ||||
|   | ||||
| @@ -412,6 +412,9 @@ export class WorkflowEditDialogComponent | ||||
|         filter_has_document_type: new FormControl( | ||||
|           trigger.filter_has_document_type | ||||
|         ), | ||||
|         filter_has_storage_path: new FormControl( | ||||
|           trigger.filter_has_storage_path | ||||
|         ), | ||||
|         schedule_offset_days: new FormControl(trigger.schedule_offset_days), | ||||
|         schedule_is_recurring: new FormControl(trigger.schedule_is_recurring), | ||||
|         schedule_recurring_interval_days: new FormControl( | ||||
| @@ -536,6 +539,7 @@ export class WorkflowEditDialogComponent | ||||
|       filter_has_tags: [], | ||||
|       filter_has_correspondent: null, | ||||
|       filter_has_document_type: null, | ||||
|       filter_has_storage_path: null, | ||||
|       matching_algorithm: MATCH_NONE, | ||||
|       match: '', | ||||
|       is_insensitive: true, | ||||
|   | ||||
| @@ -54,6 +54,10 @@ | ||||
|         <i-bs width="1em" height="1em" name="arrow-counterclockwise"></i-bs> <span i18n>Reprocess</span> | ||||
|       </button> | ||||
|  | ||||
|       <button ngbDropdownItem (click)="printDocument()" [hidden]="useNativePdfViewer || isMobile"> | ||||
|         <i-bs width="1em" height="1em" name="printer"></i-bs> <span i18n>Print</span> | ||||
|       </button> | ||||
|  | ||||
|       <button ngbDropdownItem (click)="moreLike()"> | ||||
|         <i-bs width="1em" height="1em" name="diagram-3"></i-bs> <span i18n>More like this</span> | ||||
|       </button> | ||||
|   | ||||
| @@ -1415,4 +1415,151 @@ describe('DocumentDetailComponent', () => { | ||||
|       .flush('fail', { status: 500, statusText: 'Server Error' }) | ||||
|     expect(component.previewText).toContain('An error occurred loading content') | ||||
|   }) | ||||
|  | ||||
|   it('should print document successfully', fakeAsync(() => { | ||||
|     initNormally() | ||||
|  | ||||
|     const appendChildSpy = jest | ||||
|       .spyOn(document.body, 'appendChild') | ||||
|       .mockImplementation((node: Node) => node) | ||||
|     const removeChildSpy = jest | ||||
|       .spyOn(document.body, 'removeChild') | ||||
|       .mockImplementation((node: Node) => node) | ||||
|     const createObjectURLSpy = jest | ||||
|       .spyOn(URL, 'createObjectURL') | ||||
|       .mockReturnValue('blob:mock-url') | ||||
|     const revokeObjectURLSpy = jest | ||||
|       .spyOn(URL, 'revokeObjectURL') | ||||
|       .mockImplementation(() => {}) | ||||
|  | ||||
|     const mockContentWindow = { | ||||
|       focus: jest.fn(), | ||||
|       print: jest.fn(), | ||||
|       onafterprint: null, | ||||
|     } | ||||
|  | ||||
|     const mockIframe = { | ||||
|       style: {}, | ||||
|       src: '', | ||||
|       onload: null, | ||||
|       contentWindow: mockContentWindow, | ||||
|     } | ||||
|  | ||||
|     const createElementSpy = jest | ||||
|       .spyOn(document, 'createElement') | ||||
|       .mockReturnValue(mockIframe as any) | ||||
|  | ||||
|     const blob = new Blob(['test'], { type: 'application/pdf' }) | ||||
|     component.printDocument() | ||||
|  | ||||
|     const req = httpTestingController.expectOne( | ||||
|       `${environment.apiBaseUrl}documents/${doc.id}/download/` | ||||
|     ) | ||||
|     req.flush(blob) | ||||
|  | ||||
|     tick() | ||||
|  | ||||
|     expect(createElementSpy).toHaveBeenCalledWith('iframe') | ||||
|     expect(appendChildSpy).toHaveBeenCalledWith(mockIframe) | ||||
|     expect(createObjectURLSpy).toHaveBeenCalledWith(blob) | ||||
|  | ||||
|     if (mockIframe.onload) { | ||||
|       mockIframe.onload({} as any) | ||||
|     } | ||||
|  | ||||
|     expect(mockContentWindow.focus).toHaveBeenCalled() | ||||
|     expect(mockContentWindow.print).toHaveBeenCalled() | ||||
|  | ||||
|     if (mockIframe.onload) { | ||||
|       mockIframe.onload(new Event('load')) | ||||
|     } | ||||
|  | ||||
|     if (mockContentWindow.onafterprint) { | ||||
|       mockContentWindow.onafterprint(new Event('afterprint')) | ||||
|     } | ||||
|  | ||||
|     expect(removeChildSpy).toHaveBeenCalledWith(mockIframe) | ||||
|     expect(revokeObjectURLSpy).toHaveBeenCalledWith('blob:mock-url') | ||||
|  | ||||
|     createElementSpy.mockRestore() | ||||
|     appendChildSpy.mockRestore() | ||||
|     removeChildSpy.mockRestore() | ||||
|     createObjectURLSpy.mockRestore() | ||||
|     revokeObjectURLSpy.mockRestore() | ||||
|   })) | ||||
|  | ||||
|   it('should show error toast if print document fails', () => { | ||||
|     initNormally() | ||||
|     const toastSpy = jest.spyOn(toastService, 'showError') | ||||
|     component.printDocument() | ||||
|     const req = httpTestingController.expectOne( | ||||
|       `${environment.apiBaseUrl}documents/${doc.id}/download/` | ||||
|     ) | ||||
|     req.error(new ErrorEvent('failed')) | ||||
|     expect(toastSpy).toHaveBeenCalledWith( | ||||
|       'Error loading document for printing.' | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   it('should show error toast if printing throws inside iframe', fakeAsync(() => { | ||||
|     initNormally() | ||||
|  | ||||
|     const appendChildSpy = jest | ||||
|       .spyOn(document.body, 'appendChild') | ||||
|       .mockImplementation((node: Node) => node) | ||||
|     const removeChildSpy = jest | ||||
|       .spyOn(document.body, 'removeChild') | ||||
|       .mockImplementation((node: Node) => node) | ||||
|     const createObjectURLSpy = jest | ||||
|       .spyOn(URL, 'createObjectURL') | ||||
|       .mockReturnValue('blob:mock-url') | ||||
|     const revokeObjectURLSpy = jest | ||||
|       .spyOn(URL, 'revokeObjectURL') | ||||
|       .mockImplementation(() => {}) | ||||
|  | ||||
|     const toastSpy = jest.spyOn(toastService, 'showError') | ||||
|  | ||||
|     const mockContentWindow = { | ||||
|       focus: jest.fn().mockImplementation(() => { | ||||
|         throw new Error('focus failed') | ||||
|       }), | ||||
|       print: jest.fn(), | ||||
|       onafterprint: null, | ||||
|     } | ||||
|  | ||||
|     const mockIframe: any = { | ||||
|       style: {}, | ||||
|       src: '', | ||||
|       onload: null, | ||||
|       contentWindow: mockContentWindow, | ||||
|     } | ||||
|  | ||||
|     const createElementSpy = jest | ||||
|       .spyOn(document, 'createElement') | ||||
|       .mockReturnValue(mockIframe as any) | ||||
|  | ||||
|     const blob = new Blob(['test'], { type: 'application/pdf' }) | ||||
|     component.printDocument() | ||||
|  | ||||
|     const req = httpTestingController.expectOne( | ||||
|       `${environment.apiBaseUrl}documents/${doc.id}/download/` | ||||
|     ) | ||||
|     req.flush(blob) | ||||
|  | ||||
|     tick() | ||||
|  | ||||
|     if (mockIframe.onload) { | ||||
|       mockIframe.onload(new Event('load')) | ||||
|     } | ||||
|  | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|     expect(removeChildSpy).toHaveBeenCalledWith(mockIframe) | ||||
|     expect(revokeObjectURLSpy).toHaveBeenCalledWith('blob:mock-url') | ||||
|  | ||||
|     createElementSpy.mockRestore() | ||||
|     appendChildSpy.mockRestore() | ||||
|     removeChildSpy.mockRestore() | ||||
|     createObjectURLSpy.mockRestore() | ||||
|     revokeObjectURLSpy.mockRestore() | ||||
|   })) | ||||
| }) | ||||
|   | ||||
| @@ -291,6 +291,10 @@ export class DocumentDetailComponent | ||||
|     return this.settings.get(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER) | ||||
|   } | ||||
|  | ||||
|   get isMobile(): boolean { | ||||
|     return this.deviceDetectorService.isMobile() | ||||
|   } | ||||
|  | ||||
|   get archiveContentRenderType(): ContentRenderType { | ||||
|     return this.document?.archived_file_name | ||||
|       ? this.getRenderType('application/pdf') | ||||
| @@ -1419,6 +1423,44 @@ export class DocumentDetailComponent | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   printDocument() { | ||||
|     const printUrl = this.documentsService.getDownloadUrl( | ||||
|       this.document.id, | ||||
|       false | ||||
|     ) | ||||
|     this.http | ||||
|       .get(printUrl, { responseType: 'blob' }) | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe({ | ||||
|         next: (blob) => { | ||||
|           const blobUrl = URL.createObjectURL(blob) | ||||
|           const iframe = document.createElement('iframe') | ||||
|           iframe.style.display = 'none' | ||||
|           iframe.src = blobUrl | ||||
|           document.body.appendChild(iframe) | ||||
|           iframe.onload = () => { | ||||
|             try { | ||||
|               iframe.contentWindow.focus() | ||||
|               iframe.contentWindow.print() | ||||
|               iframe.contentWindow.onafterprint = () => { | ||||
|                 document.body.removeChild(iframe) | ||||
|                 URL.revokeObjectURL(blobUrl) | ||||
|               } | ||||
|             } catch (err) { | ||||
|               this.toastService.showError($localize`Print failed.`, err) | ||||
|               document.body.removeChild(iframe) | ||||
|               URL.revokeObjectURL(blobUrl) | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         error: () => { | ||||
|           this.toastService.showError( | ||||
|             $localize`Error loading document for printing.` | ||||
|           ) | ||||
|         }, | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   public openShareLinks() { | ||||
|     const modal = this.modalService.open(ShareLinksDialogComponent) | ||||
|     modal.componentInstance.documentId = this.document.id | ||||
|   | ||||
| @@ -44,6 +44,8 @@ export interface WorkflowTrigger extends ObjectWithId { | ||||
|  | ||||
|   filter_has_document_type?: number // DocumentType.id | ||||
|  | ||||
|   filter_has_storage_path?: number // StoragePath.id | ||||
|  | ||||
|   schedule_offset_days?: number | ||||
|  | ||||
|   schedule_is_recurring?: boolean | ||||
|   | ||||
| @@ -112,6 +112,7 @@ import { | ||||
|   playFill, | ||||
|   plus, | ||||
|   plusCircle, | ||||
|   printer, | ||||
|   questionCircle, | ||||
|   scissors, | ||||
|   search, | ||||
| @@ -323,6 +324,7 @@ const icons = { | ||||
|   playFill, | ||||
|   plus, | ||||
|   plusCircle, | ||||
|   printer, | ||||
|   questionCircle, | ||||
|   scissors, | ||||
|   search, | ||||
|   | ||||
| @@ -209,6 +209,7 @@ def modify_custom_fields( | ||||
|                 defaults[value_field] = value | ||||
|                 if ( | ||||
|                     custom_field.data_type == CustomField.FieldDataType.DOCUMENTLINK | ||||
|                     and value | ||||
|                     and doc_id in value | ||||
|                 ): | ||||
|                     # Prevent self-linking | ||||
|   | ||||
| @@ -386,6 +386,16 @@ def existing_document_matches_workflow( | ||||
|         ) | ||||
|         trigger_matched = False | ||||
|  | ||||
|     # Document storage_path vs trigger has_storage_path | ||||
|     if ( | ||||
|         trigger.filter_has_storage_path is not None | ||||
|         and document.storage_path != trigger.filter_has_storage_path | ||||
|     ): | ||||
|         reason = ( | ||||
|             f"Document storage path {document.storage_path} does not match {trigger.filter_has_storage_path}", | ||||
|         ) | ||||
|         trigger_matched = False | ||||
|  | ||||
|     # Document original_filename vs trigger filename | ||||
|     if ( | ||||
|         trigger.filter_filename is not None | ||||
| @@ -430,6 +440,11 @@ def prefilter_documents_by_workflowtrigger( | ||||
|             document_type=trigger.filter_has_document_type, | ||||
|         ) | ||||
|  | ||||
|     if trigger.filter_has_storage_path is not None: | ||||
|         documents = documents.filter( | ||||
|             storage_path=trigger.filter_has_storage_path, | ||||
|         ) | ||||
|  | ||||
|     if trigger.filter_filename is not None and len(trigger.filter_filename) > 0: | ||||
|         # the true fnmatch will actually run later so we just want a loose filter here | ||||
|         regex = fnmatch_translate(trigger.filter_filename).lstrip("^").rstrip("$") | ||||
|   | ||||
| @@ -0,0 +1,35 @@ | ||||
| # Generated by Django 5.2.6 on 2025-09-11 17:29 | ||||
|  | ||||
| import django.db.models.deletion | ||||
| from django.db import migrations | ||||
| from django.db import models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|     dependencies = [ | ||||
|         ("documents", "1068_alter_document_created"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name="workflowtrigger", | ||||
|             name="filter_has_storage_path", | ||||
|             field=models.ForeignKey( | ||||
|                 blank=True, | ||||
|                 null=True, | ||||
|                 on_delete=django.db.models.deletion.SET_NULL, | ||||
|                 to="documents.storagepath", | ||||
|                 verbose_name="has this storage path", | ||||
|             ), | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name="workflowaction", | ||||
|             name="assign_title", | ||||
|             field=models.TextField( | ||||
|                 blank=True, | ||||
|                 help_text="Assign a document title, must  be a Jinja2 template, see documentation.", | ||||
|                 null=True, | ||||
|                 verbose_name="assign title", | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
| @@ -1079,6 +1079,14 @@ class WorkflowTrigger(models.Model): | ||||
|         verbose_name=_("has this correspondent"), | ||||
|     ) | ||||
|  | ||||
|     filter_has_storage_path = models.ForeignKey( | ||||
|         StoragePath, | ||||
|         null=True, | ||||
|         blank=True, | ||||
|         on_delete=models.SET_NULL, | ||||
|         verbose_name=_("has this storage path"), | ||||
|     ) | ||||
|  | ||||
|     schedule_offset_days = models.IntegerField( | ||||
|         _("schedule offset days"), | ||||
|         default=0, | ||||
| @@ -1242,14 +1250,12 @@ class WorkflowAction(models.Model): | ||||
|         default=WorkflowActionType.ASSIGNMENT, | ||||
|     ) | ||||
|  | ||||
|     assign_title = models.CharField( | ||||
|     assign_title = models.TextField( | ||||
|         _("assign title"), | ||||
|         max_length=256, | ||||
|         null=True, | ||||
|         blank=True, | ||||
|         help_text=_( | ||||
|             "Assign a document title, can include some placeholders, " | ||||
|             "see documentation.", | ||||
|             "Assign a document title, must  be a Jinja2 template, see documentation.", | ||||
|         ), | ||||
|     ) | ||||
|  | ||||
|   | ||||
| @@ -2085,6 +2085,7 @@ class WorkflowTriggerSerializer(serializers.ModelSerializer): | ||||
|             "filter_has_tags", | ||||
|             "filter_has_correspondent", | ||||
|             "filter_has_document_type", | ||||
|             "filter_has_storage_path", | ||||
|             "schedule_offset_days", | ||||
|             "schedule_is_recurring", | ||||
|             "schedule_recurring_interval_days", | ||||
|   | ||||
							
								
								
									
										27
									
								
								src/documents/templating/environment.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/documents/templating/environment.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| from jinja2.sandbox import SandboxedEnvironment | ||||
|  | ||||
|  | ||||
| class JinjaEnvironment(SandboxedEnvironment): | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super().__init__(*args, **kwargs) | ||||
|         self.undefined_tracker = None | ||||
|  | ||||
|     def is_safe_callable(self, obj): | ||||
|         # Block access to .save() and .delete() methods | ||||
|         if callable(obj) and getattr(obj, "__name__", None) in ( | ||||
|             "save", | ||||
|             "delete", | ||||
|             "update", | ||||
|         ): | ||||
|             return False | ||||
|         # Call the parent method for other cases | ||||
|         return super().is_safe_callable(obj) | ||||
|  | ||||
|  | ||||
| _template_environment = JinjaEnvironment( | ||||
|     trim_blocks=True, | ||||
|     lstrip_blocks=True, | ||||
|     keep_trailing_newline=False, | ||||
|     autoescape=False, | ||||
|     extensions=["jinja2.ext.loopcontrols"], | ||||
| ) | ||||
| @@ -2,22 +2,16 @@ import logging | ||||
| import os | ||||
| import re | ||||
| from collections.abc import Iterable | ||||
| from datetime import date | ||||
| from datetime import datetime | ||||
| from pathlib import PurePath | ||||
|  | ||||
| import pathvalidate | ||||
| from babel import Locale | ||||
| from babel import dates | ||||
| from django.utils import timezone | ||||
| from django.utils.dateparse import parse_date | ||||
| from django.utils.text import slugify as django_slugify | ||||
| from jinja2 import StrictUndefined | ||||
| from jinja2 import Template | ||||
| from jinja2 import TemplateSyntaxError | ||||
| from jinja2 import UndefinedError | ||||
| from jinja2 import make_logging_undefined | ||||
| from jinja2.sandbox import SandboxedEnvironment | ||||
| from jinja2.sandbox import SecurityError | ||||
|  | ||||
| from documents.models import Correspondent | ||||
| @@ -27,39 +21,16 @@ from documents.models import Document | ||||
| from documents.models import DocumentType | ||||
| from documents.models import StoragePath | ||||
| from documents.models import Tag | ||||
| from documents.templating.environment import _template_environment | ||||
| from documents.templating.filters import format_datetime | ||||
| from documents.templating.filters import get_cf_value | ||||
| from documents.templating.filters import localize_date | ||||
|  | ||||
| logger = logging.getLogger("paperless.templating") | ||||
|  | ||||
| _LogStrictUndefined = make_logging_undefined(logger, StrictUndefined) | ||||
|  | ||||
|  | ||||
| class FilePathEnvironment(SandboxedEnvironment): | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super().__init__(*args, **kwargs) | ||||
|         self.undefined_tracker = None | ||||
|  | ||||
|     def is_safe_callable(self, obj): | ||||
|         # Block access to .save() and .delete() methods | ||||
|         if callable(obj) and getattr(obj, "__name__", None) in ( | ||||
|             "save", | ||||
|             "delete", | ||||
|             "update", | ||||
|         ): | ||||
|             return False | ||||
|         # Call the parent method for other cases | ||||
|         return super().is_safe_callable(obj) | ||||
|  | ||||
|  | ||||
| _template_environment = FilePathEnvironment( | ||||
|     trim_blocks=True, | ||||
|     lstrip_blocks=True, | ||||
|     keep_trailing_newline=False, | ||||
|     autoescape=False, | ||||
|     extensions=["jinja2.ext.loopcontrols"], | ||||
|     undefined=_LogStrictUndefined, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class FilePathTemplate(Template): | ||||
|     def render(self, *args, **kwargs) -> str: | ||||
|         def clean_filepath(value: str) -> str: | ||||
| @@ -81,54 +52,7 @@ class FilePathTemplate(Template): | ||||
|         return clean_filepath(original_render) | ||||
|  | ||||
|  | ||||
| def get_cf_value( | ||||
|     custom_field_data: dict[str, dict[str, str]], | ||||
|     name: str, | ||||
|     default: str | None = None, | ||||
| ) -> str | None: | ||||
|     if name in custom_field_data and custom_field_data[name]["value"] is not None: | ||||
|         return custom_field_data[name]["value"] | ||||
|     elif default is not None: | ||||
|         return default | ||||
|     return None | ||||
|  | ||||
|  | ||||
| def format_datetime(value: str | datetime, format: str) -> str: | ||||
|     if isinstance(value, str): | ||||
|         value = parse_date(value) | ||||
|     return value.strftime(format=format) | ||||
|  | ||||
|  | ||||
| def localize_date(value: date | datetime, format: str, locale: str) -> str: | ||||
|     """ | ||||
|     Format a date or datetime object into a localized string using Babel. | ||||
|  | ||||
|     Args: | ||||
|         value (date | datetime): The date or datetime to format. If a datetime | ||||
|             is provided, it should be timezone-aware (e.g., UTC from a Django DB object). | ||||
|         format (str): The format to use. Can be one of Babel's preset formats | ||||
|             ('short', 'medium', 'long', 'full') or a custom pattern string. | ||||
|         locale (str): The locale code (e.g., 'en_US', 'fr_FR') to use for | ||||
|             localization. | ||||
|  | ||||
|     Returns: | ||||
|         str: The localized, formatted date string. | ||||
|  | ||||
|     Raises: | ||||
|         TypeError: If `value` is not a date or datetime instance. | ||||
|     """ | ||||
|     try: | ||||
|         Locale.parse(locale) | ||||
|     except Exception as e: | ||||
|         raise ValueError(f"Invalid locale identifier: {locale}") from e | ||||
|  | ||||
|     if isinstance(value, datetime): | ||||
|         return dates.format_datetime(value, format=format, locale=locale) | ||||
|     elif isinstance(value, date): | ||||
|         return dates.format_date(value, format=format, locale=locale) | ||||
|     else: | ||||
|         raise TypeError(f"Unsupported type {type(value)} for localize_date") | ||||
|  | ||||
| _template_environment.undefined = _LogStrictUndefined | ||||
|  | ||||
| _template_environment.filters["get_cf_value"] = get_cf_value | ||||
|  | ||||
|   | ||||
							
								
								
									
										60
									
								
								src/documents/templating/filters.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/documents/templating/filters.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| from datetime import date | ||||
| from datetime import datetime | ||||
|  | ||||
| from babel import Locale | ||||
| from babel import dates | ||||
| from django.utils.dateparse import parse_date | ||||
| from django.utils.dateparse import parse_datetime | ||||
|  | ||||
|  | ||||
| def localize_date(value: date | datetime | str, format: str, locale: str) -> str: | ||||
|     """ | ||||
|     Format a date, datetime or str object into a localized string using Babel. | ||||
|  | ||||
|     Args: | ||||
|         value (date | datetime | str): The date or datetime to format. If a datetime | ||||
|             is provided, it should be timezone-aware (e.g., UTC from a Django DB object). | ||||
|             if str is provided is is parsed as date. | ||||
|         format (str): The format to use. Can be one of Babel's preset formats | ||||
|             ('short', 'medium', 'long', 'full') or a custom pattern string. | ||||
|         locale (str): The locale code (e.g., 'en_US', 'fr_FR') to use for | ||||
|             localization. | ||||
|  | ||||
|     Returns: | ||||
|         str: The localized, formatted date string. | ||||
|  | ||||
|     Raises: | ||||
|         TypeError: If `value` is not a date, datetime or str instance. | ||||
|     """ | ||||
|     if isinstance(value, str): | ||||
|         value = parse_datetime(value) | ||||
|  | ||||
|     try: | ||||
|         Locale.parse(locale) | ||||
|     except Exception as e: | ||||
|         raise ValueError(f"Invalid locale identifier: {locale}") from e | ||||
|  | ||||
|     if isinstance(value, datetime): | ||||
|         return dates.format_datetime(value, format=format, locale=locale) | ||||
|     elif isinstance(value, date): | ||||
|         return dates.format_date(value, format=format, locale=locale) | ||||
|     else: | ||||
|         raise TypeError(f"Unsupported type {type(value)} for localize_date") | ||||
|  | ||||
|  | ||||
| def format_datetime(value: str | datetime, format: str) -> str: | ||||
|     if isinstance(value, str): | ||||
|         value = parse_date(value) | ||||
|     return value.strftime(format=format) | ||||
|  | ||||
|  | ||||
| def get_cf_value( | ||||
|     custom_field_data: dict[str, dict[str, str]], | ||||
|     name: str, | ||||
|     default: str | None = None, | ||||
| ) -> str | None: | ||||
|     if name in custom_field_data and custom_field_data[name]["value"] is not None: | ||||
|         return custom_field_data[name]["value"] | ||||
|     elif default is not None: | ||||
|         return default | ||||
|     return None | ||||
| @@ -1,7 +1,33 @@ | ||||
| import logging | ||||
| from datetime import date | ||||
| from datetime import datetime | ||||
| from pathlib import Path | ||||
|  | ||||
| from django.utils.text import slugify as django_slugify | ||||
| from jinja2 import StrictUndefined | ||||
| from jinja2 import Template | ||||
| from jinja2 import TemplateSyntaxError | ||||
| from jinja2 import UndefinedError | ||||
| from jinja2 import make_logging_undefined | ||||
| from jinja2.sandbox import SecurityError | ||||
|  | ||||
| from documents.templating.environment import _template_environment | ||||
| from documents.templating.filters import format_datetime | ||||
| from documents.templating.filters import localize_date | ||||
|  | ||||
| logger = logging.getLogger("paperless.templating") | ||||
|  | ||||
| _LogStrictUndefined = make_logging_undefined(logger, StrictUndefined) | ||||
|  | ||||
|  | ||||
| _template_environment.undefined = _LogStrictUndefined | ||||
|  | ||||
| _template_environment.filters["datetime"] = format_datetime | ||||
|  | ||||
| _template_environment.filters["slugify"] = django_slugify | ||||
|  | ||||
| _template_environment.filters["localize_date"] = localize_date | ||||
|  | ||||
|  | ||||
| def parse_w_workflow_placeholders( | ||||
|     text: str, | ||||
| @@ -20,6 +46,7 @@ def parse_w_workflow_placeholders( | ||||
|     e.g. for pre-consumption triggers created will not have been parsed yet, but it will | ||||
|     for added / updated triggers | ||||
|     """ | ||||
|  | ||||
|     formatting = { | ||||
|         "correspondent": correspondent_name, | ||||
|         "document_type": doc_type_name, | ||||
| @@ -52,4 +79,28 @@ def parse_w_workflow_placeholders( | ||||
|         formatting.update({"doc_title": doc_title}) | ||||
|     if doc_url is not None: | ||||
|         formatting.update({"doc_url": doc_url}) | ||||
|     return text.format(**formatting).strip() | ||||
|  | ||||
|     logger.debug(f"Jinja Template is : {text}") | ||||
|     try: | ||||
|         template = _template_environment.from_string( | ||||
|             text, | ||||
|             template_class=Template, | ||||
|         ) | ||||
|         rendered_template = template.render(formatting) | ||||
|  | ||||
|         # We're good! | ||||
|         return rendered_template | ||||
|     except UndefinedError as e: | ||||
|         # The undefined class logs this already for us | ||||
|         raise e | ||||
|     except TemplateSyntaxError as e: | ||||
|         logger.warning(f"Template syntax error in title generation: {e}") | ||||
|     except SecurityError as e: | ||||
|         logger.warning(f"Template attempted restricted operation: {e}") | ||||
|     except Exception as e: | ||||
|         logger.warning(f"Unknown error in title generation: {e}") | ||||
|         logger.warning( | ||||
|             f"Invalid title format '{text}', workflow not applied: {e}", | ||||
|         ) | ||||
|         raise e | ||||
|     return None | ||||
|   | ||||
| @@ -186,6 +186,7 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase): | ||||
|                             "filter_has_tags": [self.t1.id], | ||||
|                             "filter_has_document_type": self.dt.id, | ||||
|                             "filter_has_correspondent": self.c.id, | ||||
|                             "filter_has_storage_path": self.sp.id, | ||||
|                         }, | ||||
|                     ], | ||||
|                     "actions": [ | ||||
|   | ||||
| @@ -304,22 +304,6 @@ class TestConsumer( | ||||
|         self.assertEqual(document.title, "Override Title") | ||||
|         self._assert_first_last_send_progress() | ||||
|  | ||||
|     def testOverrideTitleInvalidPlaceholders(self): | ||||
|         with self.assertLogs("paperless.consumer", level="ERROR") as cm: | ||||
|             with self.get_consumer( | ||||
|                 self.get_test_file(), | ||||
|                 DocumentMetadataOverrides(title="Override {correspondent]"), | ||||
|             ) as consumer: | ||||
|                 consumer.run() | ||||
|  | ||||
|                 document = Document.objects.first() | ||||
|  | ||||
|             self.assertIsNotNone(document) | ||||
|  | ||||
|             self.assertEqual(document.title, "sample") | ||||
|             expected_str = "Error occurred parsing title override 'Override {correspondent]', falling back to original" | ||||
|             self.assertIn(expected_str, cm.output[0]) | ||||
|  | ||||
|     def testOverrideCorrespondent(self): | ||||
|         c = Correspondent.objects.create(name="test") | ||||
|  | ||||
| @@ -437,7 +421,7 @@ class TestConsumer( | ||||
|             DocumentMetadataOverrides( | ||||
|                 correspondent_id=c.pk, | ||||
|                 document_type_id=dt.pk, | ||||
|                 title="{correspondent}{document_type} {added_month}-{added_year_short}", | ||||
|                 title="{{correspondent}}{{document_type}} {{added_month}}-{{added_year_short}}", | ||||
|             ), | ||||
|         ) as consumer: | ||||
|             consumer.run() | ||||
|   | ||||
| @@ -23,7 +23,6 @@ from documents.models import Document | ||||
| from documents.models import DocumentType | ||||
| from documents.models import StoragePath | ||||
| from documents.tasks import empty_trash | ||||
| from documents.templating.filepath import localize_date | ||||
| from documents.tests.factories import DocumentFactory | ||||
| from documents.tests.utils import DirectoriesMixin | ||||
| from documents.tests.utils import FileSystemAssertsMixin | ||||
| @@ -1591,166 +1590,13 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase): | ||||
|             ) | ||||
|  | ||||
|  | ||||
| class TestDateLocalization: | ||||
| class TestPathDateLocalization: | ||||
|     """ | ||||
|     Groups all tests related to the `localize_date` function. | ||||
|     """ | ||||
|  | ||||
|     TEST_DATE = datetime.date(2023, 10, 26) | ||||
|  | ||||
|     TEST_DATETIME = datetime.datetime( | ||||
|         2023, | ||||
|         10, | ||||
|         26, | ||||
|         14, | ||||
|         30, | ||||
|         5, | ||||
|         tzinfo=datetime.timezone.utc, | ||||
|     ) | ||||
|  | ||||
|     @pytest.mark.parametrize( | ||||
|         "value, format_style, locale_str, expected_output", | ||||
|         [ | ||||
|             pytest.param( | ||||
|                 TEST_DATE, | ||||
|                 "EEEE, MMM d, yyyy", | ||||
|                 "en_US", | ||||
|                 "Thursday, Oct 26, 2023", | ||||
|                 id="date-en_US-custom", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 TEST_DATE, | ||||
|                 "dd.MM.yyyy", | ||||
|                 "de_DE", | ||||
|                 "26.10.2023", | ||||
|                 id="date-de_DE-custom", | ||||
|             ), | ||||
|             # German weekday and month name translation | ||||
|             pytest.param( | ||||
|                 TEST_DATE, | ||||
|                 "EEEE", | ||||
|                 "de_DE", | ||||
|                 "Donnerstag", | ||||
|                 id="weekday-de_DE", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 TEST_DATE, | ||||
|                 "MMMM", | ||||
|                 "de_DE", | ||||
|                 "Oktober", | ||||
|                 id="month-de_DE", | ||||
|             ), | ||||
|             # French weekday and month name translation | ||||
|             pytest.param( | ||||
|                 TEST_DATE, | ||||
|                 "EEEE", | ||||
|                 "fr_FR", | ||||
|                 "jeudi", | ||||
|                 id="weekday-fr_FR", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 TEST_DATE, | ||||
|                 "MMMM", | ||||
|                 "fr_FR", | ||||
|                 "octobre", | ||||
|                 id="month-fr_FR", | ||||
|             ), | ||||
|         ], | ||||
|     ) | ||||
|     def test_localize_date_with_date_objects( | ||||
|         self, | ||||
|         value: datetime.date, | ||||
|         format_style: str, | ||||
|         locale_str: str, | ||||
|         expected_output: str, | ||||
|     ): | ||||
|         """ | ||||
|         Tests `localize_date` with `date` objects across different locales and formats. | ||||
|         """ | ||||
|         assert localize_date(value, format_style, locale_str) == expected_output | ||||
|  | ||||
|     @pytest.mark.parametrize( | ||||
|         "value, format_style, locale_str, expected_output", | ||||
|         [ | ||||
|             pytest.param( | ||||
|                 TEST_DATETIME, | ||||
|                 "yyyy.MM.dd G 'at' HH:mm:ss zzz", | ||||
|                 "en_US", | ||||
|                 "2023.10.26 AD at 14:30:05 UTC", | ||||
|                 id="datetime-en_US-custom", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 TEST_DATETIME, | ||||
|                 "dd.MM.yyyy", | ||||
|                 "fr_FR", | ||||
|                 "26.10.2023", | ||||
|                 id="date-fr_FR-custom", | ||||
|             ), | ||||
|             # Spanish weekday and month translation | ||||
|             pytest.param( | ||||
|                 TEST_DATETIME, | ||||
|                 "EEEE", | ||||
|                 "es_ES", | ||||
|                 "jueves", | ||||
|                 id="weekday-es_ES", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 TEST_DATETIME, | ||||
|                 "MMMM", | ||||
|                 "es_ES", | ||||
|                 "octubre", | ||||
|                 id="month-es_ES", | ||||
|             ), | ||||
|             # Italian weekday and month translation | ||||
|             pytest.param( | ||||
|                 TEST_DATETIME, | ||||
|                 "EEEE", | ||||
|                 "it_IT", | ||||
|                 "giovedì", | ||||
|                 id="weekday-it_IT", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 TEST_DATETIME, | ||||
|                 "MMMM", | ||||
|                 "it_IT", | ||||
|                 "ottobre", | ||||
|                 id="month-it_IT", | ||||
|             ), | ||||
|         ], | ||||
|     ) | ||||
|     def test_localize_date_with_datetime_objects( | ||||
|         self, | ||||
|         value: datetime.datetime, | ||||
|         format_style: str, | ||||
|         locale_str: str, | ||||
|         expected_output: str, | ||||
|     ): | ||||
|         # To handle the non-breaking space in French and other locales | ||||
|         result = localize_date(value, format_style, locale_str) | ||||
|         assert result.replace("\u202f", " ") == expected_output.replace("\u202f", " ") | ||||
|  | ||||
|     @pytest.mark.parametrize( | ||||
|         "invalid_value", | ||||
|         [ | ||||
|             "2023-10-26", | ||||
|             1698330605, | ||||
|             None, | ||||
|             [], | ||||
|             {}, | ||||
|         ], | ||||
|     ) | ||||
|     def test_localize_date_raises_type_error_for_invalid_input(self, invalid_value): | ||||
|         with pytest.raises(TypeError) as excinfo: | ||||
|             localize_date(invalid_value, "medium", "en_US") | ||||
|  | ||||
|         assert f"Unsupported type {type(invalid_value)}" in str(excinfo.value) | ||||
|  | ||||
|     def test_localize_date_raises_error_for_invalid_locale(self): | ||||
|         with pytest.raises(ValueError) as excinfo: | ||||
|             localize_date(self.TEST_DATE, "medium", "invalid_locale_code") | ||||
|  | ||||
|         assert "Invalid locale identifier" in str(excinfo.value) | ||||
|  | ||||
|     @pytest.mark.django_db | ||||
|     @pytest.mark.parametrize( | ||||
|         "filename_format,expected_filename", | ||||
|   | ||||
							
								
								
									
										296
									
								
								src/documents/tests/test_filters.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										296
									
								
								src/documents/tests/test_filters.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,296 @@ | ||||
| import datetime | ||||
| from typing import Any | ||||
| from typing import Literal | ||||
|  | ||||
| import pytest | ||||
|  | ||||
| from documents.templating.filters import localize_date | ||||
|  | ||||
|  | ||||
| class TestDateLocalization: | ||||
|     """ | ||||
|     Groups all tests related to the `localize_date` function. | ||||
|     """ | ||||
|  | ||||
|     TEST_DATE = datetime.date(2023, 10, 26) | ||||
|  | ||||
|     TEST_DATETIME = datetime.datetime( | ||||
|         2023, | ||||
|         10, | ||||
|         26, | ||||
|         14, | ||||
|         30, | ||||
|         5, | ||||
|         tzinfo=datetime.timezone.utc, | ||||
|     ) | ||||
|  | ||||
|     TEST_DATETIME_STRING: str = "2023-10-26T14:30:05+00:00" | ||||
|  | ||||
|     TEST_DATE_STRING: str = "2023-10-26" | ||||
|  | ||||
|     @pytest.mark.parametrize( | ||||
|         "value, format_style, locale_str, expected_output", | ||||
|         [ | ||||
|             pytest.param( | ||||
|                 TEST_DATE, | ||||
|                 "EEEE, MMM d, yyyy", | ||||
|                 "en_US", | ||||
|                 "Thursday, Oct 26, 2023", | ||||
|                 id="date-en_US-custom", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 TEST_DATE, | ||||
|                 "dd.MM.yyyy", | ||||
|                 "de_DE", | ||||
|                 "26.10.2023", | ||||
|                 id="date-de_DE-custom", | ||||
|             ), | ||||
|             # German weekday and month name translation | ||||
|             pytest.param( | ||||
|                 TEST_DATE, | ||||
|                 "EEEE", | ||||
|                 "de_DE", | ||||
|                 "Donnerstag", | ||||
|                 id="weekday-de_DE", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 TEST_DATE, | ||||
|                 "MMMM", | ||||
|                 "de_DE", | ||||
|                 "Oktober", | ||||
|                 id="month-de_DE", | ||||
|             ), | ||||
|             # French weekday and month name translation | ||||
|             pytest.param( | ||||
|                 TEST_DATE, | ||||
|                 "EEEE", | ||||
|                 "fr_FR", | ||||
|                 "jeudi", | ||||
|                 id="weekday-fr_FR", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 TEST_DATE, | ||||
|                 "MMMM", | ||||
|                 "fr_FR", | ||||
|                 "octobre", | ||||
|                 id="month-fr_FR", | ||||
|             ), | ||||
|         ], | ||||
|     ) | ||||
|     def test_localize_date_with_date_objects( | ||||
|         self, | ||||
|         value: datetime.date, | ||||
|         format_style: str, | ||||
|         locale_str: str, | ||||
|         expected_output: str, | ||||
|     ): | ||||
|         """ | ||||
|         Tests `localize_date` with `date` objects across different locales and formats. | ||||
|         """ | ||||
|         assert localize_date(value, format_style, locale_str) == expected_output | ||||
|  | ||||
|     @pytest.mark.parametrize( | ||||
|         "value, format_style, locale_str, expected_output", | ||||
|         [ | ||||
|             pytest.param( | ||||
|                 TEST_DATETIME, | ||||
|                 "yyyy.MM.dd G 'at' HH:mm:ss zzz", | ||||
|                 "en_US", | ||||
|                 "2023.10.26 AD at 14:30:05 UTC", | ||||
|                 id="datetime-en_US-custom", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 TEST_DATETIME, | ||||
|                 "dd.MM.yyyy", | ||||
|                 "fr_FR", | ||||
|                 "26.10.2023", | ||||
|                 id="date-fr_FR-custom", | ||||
|             ), | ||||
|             # Spanish weekday and month translation | ||||
|             pytest.param( | ||||
|                 TEST_DATETIME, | ||||
|                 "EEEE", | ||||
|                 "es_ES", | ||||
|                 "jueves", | ||||
|                 id="weekday-es_ES", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 TEST_DATETIME, | ||||
|                 "MMMM", | ||||
|                 "es_ES", | ||||
|                 "octubre", | ||||
|                 id="month-es_ES", | ||||
|             ), | ||||
|             # Italian weekday and month translation | ||||
|             pytest.param( | ||||
|                 TEST_DATETIME, | ||||
|                 "EEEE", | ||||
|                 "it_IT", | ||||
|                 "giovedì", | ||||
|                 id="weekday-it_IT", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 TEST_DATETIME, | ||||
|                 "MMMM", | ||||
|                 "it_IT", | ||||
|                 "ottobre", | ||||
|                 id="month-it_IT", | ||||
|             ), | ||||
|         ], | ||||
|     ) | ||||
|     def test_localize_date_with_datetime_objects( | ||||
|         self, | ||||
|         value: datetime.datetime, | ||||
|         format_style: str, | ||||
|         locale_str: str, | ||||
|         expected_output: str, | ||||
|     ): | ||||
|         # To handle the non-breaking space in French and other locales | ||||
|         result = localize_date(value, format_style, locale_str) | ||||
|         assert result.replace("\u202f", " ") == expected_output.replace("\u202f", " ") | ||||
|  | ||||
|     @pytest.mark.parametrize( | ||||
|         "invalid_value", | ||||
|         [ | ||||
|             1698330605, | ||||
|             None, | ||||
|             [], | ||||
|             {}, | ||||
|         ], | ||||
|     ) | ||||
|     def test_localize_date_raises_type_error_for_invalid_input( | ||||
|         self, | ||||
|         invalid_value: None | list[object] | dict[Any, Any] | Literal[1698330605], | ||||
|     ): | ||||
|         with pytest.raises(TypeError) as excinfo: | ||||
|             localize_date(invalid_value, "medium", "en_US") | ||||
|  | ||||
|         assert f"Unsupported type {type(invalid_value)}" in str(excinfo.value) | ||||
|  | ||||
|     def test_localize_date_raises_error_for_invalid_locale(self): | ||||
|         with pytest.raises(ValueError) as excinfo: | ||||
|             localize_date(self.TEST_DATE, "medium", "invalid_locale_code") | ||||
|  | ||||
|         assert "Invalid locale identifier" in str(excinfo.value) | ||||
|  | ||||
|     @pytest.mark.parametrize( | ||||
|         "value, format_style, locale_str, expected_output", | ||||
|         [ | ||||
|             pytest.param( | ||||
|                 TEST_DATETIME_STRING, | ||||
|                 "EEEE, MMM d, yyyy", | ||||
|                 "en_US", | ||||
|                 "Thursday, Oct 26, 2023", | ||||
|                 id="date-en_US-custom", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 TEST_DATETIME_STRING, | ||||
|                 "dd.MM.yyyy", | ||||
|                 "de_DE", | ||||
|                 "26.10.2023", | ||||
|                 id="date-de_DE-custom", | ||||
|             ), | ||||
|             # German weekday and month name translation | ||||
|             pytest.param( | ||||
|                 TEST_DATETIME_STRING, | ||||
|                 "EEEE", | ||||
|                 "de_DE", | ||||
|                 "Donnerstag", | ||||
|                 id="weekday-de_DE", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 TEST_DATETIME_STRING, | ||||
|                 "MMMM", | ||||
|                 "de_DE", | ||||
|                 "Oktober", | ||||
|                 id="month-de_DE", | ||||
|             ), | ||||
|             # French weekday and month name translation | ||||
|             pytest.param( | ||||
|                 TEST_DATETIME_STRING, | ||||
|                 "EEEE", | ||||
|                 "fr_FR", | ||||
|                 "jeudi", | ||||
|                 id="weekday-fr_FR", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 TEST_DATETIME_STRING, | ||||
|                 "MMMM", | ||||
|                 "fr_FR", | ||||
|                 "octobre", | ||||
|                 id="month-fr_FR", | ||||
|             ), | ||||
|         ], | ||||
|     ) | ||||
|     def test_localize_date_with_datetime_string( | ||||
|         self, | ||||
|         value: str, | ||||
|         format_style: str, | ||||
|         locale_str: str, | ||||
|         expected_output: str, | ||||
|     ): | ||||
|         """ | ||||
|         Tests `localize_date` with `date` string across different locales and formats. | ||||
|         """ | ||||
|         assert localize_date(value, format_style, locale_str) == expected_output | ||||
|  | ||||
|     @pytest.mark.parametrize( | ||||
|         "value, format_style, locale_str, expected_output", | ||||
|         [ | ||||
|             pytest.param( | ||||
|                 TEST_DATE_STRING, | ||||
|                 "EEEE, MMM d, yyyy", | ||||
|                 "en_US", | ||||
|                 "Thursday, Oct 26, 2023", | ||||
|                 id="date-en_US-custom", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 TEST_DATE_STRING, | ||||
|                 "dd.MM.yyyy", | ||||
|                 "de_DE", | ||||
|                 "26.10.2023", | ||||
|                 id="date-de_DE-custom", | ||||
|             ), | ||||
|             # German weekday and month name translation | ||||
|             pytest.param( | ||||
|                 TEST_DATE_STRING, | ||||
|                 "EEEE", | ||||
|                 "de_DE", | ||||
|                 "Donnerstag", | ||||
|                 id="weekday-de_DE", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 TEST_DATE_STRING, | ||||
|                 "MMMM", | ||||
|                 "de_DE", | ||||
|                 "Oktober", | ||||
|                 id="month-de_DE", | ||||
|             ), | ||||
|             # French weekday and month name translation | ||||
|             pytest.param( | ||||
|                 TEST_DATE_STRING, | ||||
|                 "EEEE", | ||||
|                 "fr_FR", | ||||
|                 "jeudi", | ||||
|                 id="weekday-fr_FR", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 TEST_DATE_STRING, | ||||
|                 "MMMM", | ||||
|                 "fr_FR", | ||||
|                 "octobre", | ||||
|                 id="month-fr_FR", | ||||
|             ), | ||||
|         ], | ||||
|     ) | ||||
|     def test_localize_date_with_date_string( | ||||
|         self, | ||||
|         value: str, | ||||
|         format_style: str, | ||||
|         locale_str: str, | ||||
|         expected_output: str, | ||||
|     ): | ||||
|         """ | ||||
|         Tests `localize_date` with `date` string across different locales and formats. | ||||
|         """ | ||||
|         assert localize_date(value, format_style, locale_str) == expected_output | ||||
| @@ -1,6 +1,8 @@ | ||||
| import datetime | ||||
| import shutil | ||||
| import socket | ||||
| from datetime import timedelta | ||||
| from pathlib import Path | ||||
| from typing import TYPE_CHECKING | ||||
| from unittest import mock | ||||
|  | ||||
| @@ -15,6 +17,7 @@ from guardian.shortcuts import get_users_with_perms | ||||
| from httpx import HTTPError | ||||
| from httpx import HTTPStatusError | ||||
| from pytest_httpx import HTTPXMock | ||||
| from rest_framework.test import APIClient | ||||
| from rest_framework.test import APITestCase | ||||
|  | ||||
| from documents.signals.handlers import run_workflows | ||||
| @@ -22,7 +25,7 @@ from documents.signals.handlers import send_webhook | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from django.db.models import QuerySet | ||||
|  | ||||
| from pytest_django.fixtures import SettingsWrapper | ||||
|  | ||||
| from documents import tasks | ||||
| from documents.data_models import ConsumableDocument | ||||
| @@ -122,7 +125,7 @@ class TestWorkflows( | ||||
|             filter_path=f"*/{self.dirs.scratch_dir.parts[-1]}/*", | ||||
|         ) | ||||
|         action = WorkflowAction.objects.create( | ||||
|             assign_title="Doc from {correspondent}", | ||||
|             assign_title="Doc from {{correspondent}}", | ||||
|             assign_correspondent=self.c, | ||||
|             assign_document_type=self.dt, | ||||
|             assign_storage_path=self.sp, | ||||
| @@ -241,7 +244,7 @@ class TestWorkflows( | ||||
|         ) | ||||
|  | ||||
|         action = WorkflowAction.objects.create( | ||||
|             assign_title="Doc from {correspondent}", | ||||
|             assign_title="Doc from {{correspondent}}", | ||||
|             assign_correspondent=self.c, | ||||
|             assign_document_type=self.dt, | ||||
|             assign_storage_path=self.sp, | ||||
| @@ -892,7 +895,7 @@ class TestWorkflows( | ||||
|             filter_filename="*sample*", | ||||
|         ) | ||||
|         action = WorkflowAction.objects.create( | ||||
|             assign_title="Doc created in {created_year}", | ||||
|             assign_title="Doc created in {{created_year}}", | ||||
|             assign_correspondent=self.c2, | ||||
|             assign_document_type=self.dt, | ||||
|             assign_storage_path=self.sp, | ||||
| @@ -1147,6 +1150,38 @@ class TestWorkflows( | ||||
|             expected_str = f"Document correspondent {doc.correspondent} does not match {trigger.filter_has_correspondent}" | ||||
|             self.assertIn(expected_str, cm.output[1]) | ||||
|  | ||||
|     def test_document_added_no_match_storage_path(self): | ||||
|         trigger = WorkflowTrigger.objects.create( | ||||
|             type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_ADDED, | ||||
|             filter_has_storage_path=self.sp, | ||||
|         ) | ||||
|         action = WorkflowAction.objects.create( | ||||
|             assign_title="Doc assign owner", | ||||
|             assign_owner=self.user2, | ||||
|         ) | ||||
|         w = Workflow.objects.create( | ||||
|             name="Workflow 1", | ||||
|             order=0, | ||||
|         ) | ||||
|         w.triggers.add(trigger) | ||||
|         w.actions.add(action) | ||||
|         w.save() | ||||
|  | ||||
|         doc = Document.objects.create( | ||||
|             title="sample test", | ||||
|             original_filename="sample.pdf", | ||||
|         ) | ||||
|  | ||||
|         with self.assertLogs("paperless.matching", level="DEBUG") as cm: | ||||
|             document_consumption_finished.send( | ||||
|                 sender=self.__class__, | ||||
|                 document=doc, | ||||
|             ) | ||||
|             expected_str = f"Document did not match {w}" | ||||
|             self.assertIn(expected_str, cm.output[0]) | ||||
|             expected_str = f"Document storage path {doc.storage_path} does not match {trigger.filter_has_storage_path}" | ||||
|             self.assertIn(expected_str, cm.output[1]) | ||||
|  | ||||
|     def test_document_added_invalid_title_placeholders(self): | ||||
|         """ | ||||
|         GIVEN: | ||||
| @@ -1155,7 +1190,7 @@ class TestWorkflows( | ||||
|         WHEN: | ||||
|             - File that matches is added | ||||
|         THEN: | ||||
|             - Title is not updated, error is output | ||||
|             - Title is updated but the placeholder isn't replaced | ||||
|         """ | ||||
|         trigger = WorkflowTrigger.objects.create( | ||||
|             type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_ADDED, | ||||
| @@ -1181,15 +1216,12 @@ class TestWorkflows( | ||||
|             created=created, | ||||
|         ) | ||||
|  | ||||
|         with self.assertLogs("paperless.handlers", level="ERROR") as cm: | ||||
|             document_consumption_finished.send( | ||||
|                 sender=self.__class__, | ||||
|                 document=doc, | ||||
|             ) | ||||
|             expected_str = f"Error occurred parsing title assignment '{action.assign_title}', falling back to original" | ||||
|             self.assertIn(expected_str, cm.output[0]) | ||||
|         document_consumption_finished.send( | ||||
|             sender=self.__class__, | ||||
|             document=doc, | ||||
|         ) | ||||
|  | ||||
|         self.assertEqual(doc.title, "sample test") | ||||
|         self.assertEqual(doc.title, "Doc {created_year]") | ||||
|  | ||||
|     def test_document_updated_workflow(self): | ||||
|         trigger = WorkflowTrigger.objects.create( | ||||
| @@ -1223,6 +1255,45 @@ class TestWorkflows( | ||||
|  | ||||
|         self.assertEqual(doc.custom_fields.all().count(), 1) | ||||
|  | ||||
|     def test_document_consumption_workflow_month_placeholder_addded(self): | ||||
|         trigger = WorkflowTrigger.objects.create( | ||||
|             type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION, | ||||
|             sources=f"{DocumentSource.ApiUpload}", | ||||
|             filter_filename="simple*", | ||||
|         ) | ||||
|  | ||||
|         action = WorkflowAction.objects.create( | ||||
|             assign_title="Doc added in {{added_month_name_short}}", | ||||
|         ) | ||||
|  | ||||
|         w = Workflow.objects.create( | ||||
|             name="Workflow 1", | ||||
|             order=0, | ||||
|         ) | ||||
|         w.triggers.add(trigger) | ||||
|         w.actions.add(action) | ||||
|         w.save() | ||||
|  | ||||
|         superuser = User.objects.create_superuser("superuser") | ||||
|         self.client.force_authenticate(user=superuser) | ||||
|         test_file = shutil.copy( | ||||
|             self.SAMPLE_DIR / "simple.pdf", | ||||
|             self.dirs.scratch_dir / "simple.pdf", | ||||
|         ) | ||||
|         with mock.patch("documents.tasks.ProgressManager", DummyProgressManager): | ||||
|             tasks.consume_file( | ||||
|                 ConsumableDocument( | ||||
|                     source=DocumentSource.ApiUpload, | ||||
|                     original_file=test_file, | ||||
|                 ), | ||||
|                 None, | ||||
|             ) | ||||
|             document = Document.objects.first() | ||||
|             self.assertRegex( | ||||
|                 document.title, | ||||
|                 r"Doc added in \w{3,}", | ||||
|             )  # Match any 3-letter month name | ||||
|  | ||||
|     def test_document_updated_workflow_existing_custom_field(self): | ||||
|         """ | ||||
|         GIVEN: | ||||
| @@ -1777,6 +1848,7 @@ class TestWorkflows( | ||||
|             filter_filename="*sample*", | ||||
|             filter_has_document_type=self.dt, | ||||
|             filter_has_correspondent=self.c, | ||||
|             filter_has_storage_path=self.sp, | ||||
|         ) | ||||
|         trigger.filter_has_tags.set([self.t1]) | ||||
|         trigger.save() | ||||
| @@ -1797,6 +1869,7 @@ class TestWorkflows( | ||||
|                 title=f"sample test {i}", | ||||
|                 checksum=f"checksum{i}", | ||||
|                 correspondent=self.c, | ||||
|                 storage_path=self.sp, | ||||
|                 original_filename=f"sample_{i}.pdf", | ||||
|                 document_type=self.dt if i % 2 == 0 else None, | ||||
|             ) | ||||
| @@ -2035,7 +2108,7 @@ class TestWorkflows( | ||||
|             filter_filename="*simple*", | ||||
|         ) | ||||
|         action = WorkflowAction.objects.create( | ||||
|             assign_title="Doc from {correspondent}", | ||||
|             assign_title="Doc from {{correspondent}}", | ||||
|             assign_correspondent=self.c, | ||||
|             assign_document_type=self.dt, | ||||
|             assign_storage_path=self.sp, | ||||
| @@ -2614,7 +2687,7 @@ class TestWorkflows( | ||||
|         ) | ||||
|         webhook_action = WorkflowActionWebhook.objects.create( | ||||
|             use_params=False, | ||||
|             body="Test message: {doc_url}", | ||||
|             body="Test message: {{doc_url}}", | ||||
|             url="http://paperless-ngx.com", | ||||
|             include_document=False, | ||||
|         ) | ||||
| @@ -2673,7 +2746,7 @@ class TestWorkflows( | ||||
|         ) | ||||
|         webhook_action = WorkflowActionWebhook.objects.create( | ||||
|             use_params=False, | ||||
|             body="Test message: {doc_url}", | ||||
|             body="Test message: {{doc_url}}", | ||||
|             url="http://paperless-ngx.com", | ||||
|             include_document=True, | ||||
|         ) | ||||
| @@ -3130,3 +3203,234 @@ class TestWebhookSecurity: | ||||
|         req = httpx_mock.get_request() | ||||
|         assert req.headers["Host"] == "paperless-ngx.com" | ||||
|         assert "evil.test" not in req.headers.get("Host", "") | ||||
|  | ||||
|  | ||||
| @pytest.mark.django_db | ||||
| class TestDateWorkflowLocalization( | ||||
|     SampleDirMixin, | ||||
| ): | ||||
|     """Test cases for workflows that use date localization in templates.""" | ||||
|  | ||||
|     TEST_DATETIME = datetime.datetime( | ||||
|         2023, | ||||
|         6, | ||||
|         26, | ||||
|         14, | ||||
|         30, | ||||
|         5, | ||||
|         tzinfo=datetime.timezone.utc, | ||||
|     ) | ||||
|  | ||||
|     @pytest.mark.parametrize( | ||||
|         "title_template,expected_title", | ||||
|         [ | ||||
|             pytest.param( | ||||
|                 "Created at {{ created | localize_date('MMMM', 'es_ES') }}", | ||||
|                 "Created at junio", | ||||
|                 id="spanish_month", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 "Created at {{ created | localize_date('MMMM', 'de_DE') }}", | ||||
|                 "Created at Juni",  # codespell:ignore | ||||
|                 id="german_month", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 "Created at {{ created | localize_date('dd/MM/yyyy', 'en_GB') }}", | ||||
|                 "Created at 26/06/2023", | ||||
|                 id="british_date_format", | ||||
|             ), | ||||
|         ], | ||||
|     ) | ||||
|     def test_document_added_workflow_localization( | ||||
|         self, | ||||
|         title_template: str, | ||||
|         expected_title: str, | ||||
|     ): | ||||
|         """ | ||||
|         GIVEN: | ||||
|             - Document added workflow with title template using localize_date filter | ||||
|         WHEN: | ||||
|             - Document is consumed | ||||
|         THEN: | ||||
|             - Document title is set with localized date | ||||
|         """ | ||||
|         trigger = WorkflowTrigger.objects.create( | ||||
|             type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_ADDED, | ||||
|             filter_filename="*sample*", | ||||
|         ) | ||||
|  | ||||
|         action = WorkflowAction.objects.create( | ||||
|             assign_title=title_template, | ||||
|         ) | ||||
|  | ||||
|         workflow = Workflow.objects.create( | ||||
|             name="Workflow 1", | ||||
|             order=0, | ||||
|         ) | ||||
|         workflow.triggers.add(trigger) | ||||
|         workflow.actions.add(action) | ||||
|         workflow.save() | ||||
|  | ||||
|         doc = Document.objects.create( | ||||
|             title="sample test", | ||||
|             correspondent=None, | ||||
|             original_filename="sample.pdf", | ||||
|             created=self.TEST_DATETIME, | ||||
|         ) | ||||
|  | ||||
|         document_consumption_finished.send( | ||||
|             sender=self.__class__, | ||||
|             document=doc, | ||||
|         ) | ||||
|  | ||||
|         doc.refresh_from_db() | ||||
|         assert doc.title == expected_title | ||||
|  | ||||
|     @pytest.mark.parametrize( | ||||
|         "title_template,expected_title", | ||||
|         [ | ||||
|             pytest.param( | ||||
|                 "Created at {{ created | localize_date('MMMM', 'es_ES') }}", | ||||
|                 "Created at junio", | ||||
|                 id="spanish_month", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 "Created at {{ created | localize_date('MMMM', 'de_DE') }}", | ||||
|                 "Created at Juni",  # codespell:ignore | ||||
|                 id="german_month", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 "Created at {{ created | localize_date('dd/MM/yyyy', 'en_GB') }}", | ||||
|                 "Created at 26/06/2023", | ||||
|                 id="british_date_format", | ||||
|             ), | ||||
|         ], | ||||
|     ) | ||||
|     def test_document_updated_workflow_localization( | ||||
|         self, | ||||
|         title_template: str, | ||||
|         expected_title: str, | ||||
|     ): | ||||
|         """ | ||||
|         GIVEN: | ||||
|             - Document updated workflow with title template using localize_date filter | ||||
|         WHEN: | ||||
|             - Document is updated via API | ||||
|         THEN: | ||||
|             - Document title is set with localized date | ||||
|         """ | ||||
|         # Setup test data | ||||
|         dt = DocumentType.objects.create(name="DocType Name") | ||||
|         c = Correspondent.objects.create(name="Correspondent Name") | ||||
|  | ||||
|         client = APIClient() | ||||
|         superuser = User.objects.create_superuser("superuser") | ||||
|         client.force_authenticate(user=superuser) | ||||
|  | ||||
|         trigger = WorkflowTrigger.objects.create( | ||||
|             type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, | ||||
|             filter_has_document_type=dt, | ||||
|         ) | ||||
|  | ||||
|         doc = Document.objects.create( | ||||
|             title="sample test", | ||||
|             correspondent=c, | ||||
|             original_filename="sample.pdf", | ||||
|             created=self.TEST_DATETIME, | ||||
|         ) | ||||
|  | ||||
|         action = WorkflowAction.objects.create( | ||||
|             assign_title=title_template, | ||||
|         ) | ||||
|  | ||||
|         workflow = Workflow.objects.create( | ||||
|             name="Workflow 1", | ||||
|             order=0, | ||||
|         ) | ||||
|         workflow.triggers.add(trigger) | ||||
|         workflow.actions.add(action) | ||||
|         workflow.save() | ||||
|  | ||||
|         client.patch( | ||||
|             f"/api/documents/{doc.id}/", | ||||
|             {"document_type": dt.id}, | ||||
|             format="json", | ||||
|         ) | ||||
|  | ||||
|         doc.refresh_from_db() | ||||
|         assert doc.title == expected_title | ||||
|  | ||||
|     @pytest.mark.parametrize( | ||||
|         "title_template,expected_title", | ||||
|         [ | ||||
|             pytest.param( | ||||
|                 "Added at {{ added | localize_date('MMMM', 'es_ES') }}", | ||||
|                 "Added at junio", | ||||
|                 id="spanish_month", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 "Added at {{ added | localize_date('MMMM', 'de_DE') }}", | ||||
|                 "Added at Juni",  # codespell:ignore | ||||
|                 id="german_month", | ||||
|             ), | ||||
|             pytest.param( | ||||
|                 "Added at {{ added | localize_date('dd/MM/yyyy', 'en_GB') }}", | ||||
|                 "Added at 26/06/2023", | ||||
|                 id="british_date_format", | ||||
|             ), | ||||
|         ], | ||||
|     ) | ||||
|     def test_document_consumption_workflow_localization( | ||||
|         self, | ||||
|         tmp_path: Path, | ||||
|         settings: SettingsWrapper, | ||||
|         title_template: str, | ||||
|         expected_title: str, | ||||
|     ): | ||||
|         trigger = WorkflowTrigger.objects.create( | ||||
|             type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION, | ||||
|             sources=f"{DocumentSource.ApiUpload}", | ||||
|             filter_filename="simple*", | ||||
|         ) | ||||
|  | ||||
|         test_file = shutil.copy( | ||||
|             self.SAMPLE_DIR / "simple.pdf", | ||||
|             tmp_path / "simple.pdf", | ||||
|         ) | ||||
|  | ||||
|         action = WorkflowAction.objects.create( | ||||
|             assign_title=title_template, | ||||
|         ) | ||||
|  | ||||
|         w = Workflow.objects.create( | ||||
|             name="Workflow 1", | ||||
|             order=0, | ||||
|         ) | ||||
|         w.triggers.add(trigger) | ||||
|         w.actions.add(action) | ||||
|         w.save() | ||||
|  | ||||
|         settings.SCRATCH_DIR = tmp_path / "scratch" | ||||
|         (tmp_path / "scratch").mkdir(parents=True, exist_ok=True) | ||||
|  | ||||
|         # Temporarily override "now" for the environment so templates using | ||||
|         # added/created placeholders behave as if it's a different system date. | ||||
|         with ( | ||||
|             mock.patch( | ||||
|                 "documents.tasks.ProgressManager", | ||||
|                 DummyProgressManager, | ||||
|             ), | ||||
|             mock.patch( | ||||
|                 "django.utils.timezone.now", | ||||
|                 return_value=self.TEST_DATETIME, | ||||
|             ), | ||||
|         ): | ||||
|             tasks.consume_file( | ||||
|                 ConsumableDocument( | ||||
|                     source=DocumentSource.ApiUpload, | ||||
|                     original_file=test_file, | ||||
|                 ), | ||||
|                 None, | ||||
|             ) | ||||
|             document = Document.objects.first() | ||||
|             assert document.title == expected_title | ||||
|   | ||||
| @@ -2,7 +2,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: paperless-ngx\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2025-09-09 20:04+0000\n" | ||||
| "POT-Creation-Date: 2025-09-11 17:43+0000\n" | ||||
| "PO-Revision-Date: 2022-02-17 04:17\n" | ||||
| "Last-Translator: \n" | ||||
| "Language-Team: English\n" | ||||
| @@ -89,7 +89,7 @@ msgstr "" | ||||
| msgid "Automatic" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:62 documents/models.py:423 documents/models.py:1441 | ||||
| #: documents/models.py:62 documents/models.py:423 documents/models.py:1447 | ||||
| #: paperless_mail/models.py:23 paperless_mail/models.py:143 | ||||
| msgid "name" | ||||
| msgstr "" | ||||
| @@ -256,7 +256,7 @@ msgid "The position of this document in your physical document archive." | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:294 documents/models.py:666 documents/models.py:720 | ||||
| #: documents/models.py:1484 | ||||
| #: documents/models.py:1490 | ||||
| msgid "document" | ||||
| msgstr "" | ||||
|  | ||||
| @@ -860,319 +860,322 @@ msgstr "" | ||||
| msgid "has this correspondent" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1048 | ||||
| msgid "schedule offset days" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1051 | ||||
| msgid "The number of days to offset the schedule trigger by." | ||||
| #: documents/models.py:1052 | ||||
| msgid "has this storage path" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1056 | ||||
| msgid "schedule is recurring" | ||||
| msgid "schedule offset days" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1059 | ||||
| msgid "If the schedule should be recurring." | ||||
| msgid "The number of days to offset the schedule trigger by." | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1064 | ||||
| msgid "schedule is recurring" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1067 | ||||
| msgid "If the schedule should be recurring." | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1072 | ||||
| msgid "schedule recurring delay in days" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1068 | ||||
| #: documents/models.py:1076 | ||||
| msgid "The number of days between recurring schedule triggers." | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1073 | ||||
| #: documents/models.py:1081 | ||||
| msgid "schedule date field" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1078 | ||||
| #: documents/models.py:1086 | ||||
| msgid "The field to check for a schedule trigger." | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1087 | ||||
| #: documents/models.py:1095 | ||||
| msgid "schedule date custom field" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1091 | ||||
| #: documents/models.py:1099 | ||||
| msgid "workflow trigger" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1092 | ||||
| #: documents/models.py:1100 | ||||
| msgid "workflow triggers" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1100 | ||||
| #: documents/models.py:1108 | ||||
| msgid "email subject" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1104 | ||||
| #: documents/models.py:1112 | ||||
| msgid "" | ||||
| "The subject of the email, can include some placeholders, see documentation." | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1110 | ||||
| #: documents/models.py:1118 | ||||
| msgid "email body" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1113 | ||||
| #: documents/models.py:1121 | ||||
| msgid "" | ||||
| "The body (message) of the email, can include some placeholders, see " | ||||
| "documentation." | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1119 | ||||
| #: documents/models.py:1127 | ||||
| msgid "emails to" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1122 | ||||
| #: documents/models.py:1130 | ||||
| msgid "The destination email addresses, comma separated." | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1128 | ||||
| #: documents/models.py:1136 | ||||
| msgid "include document in email" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1139 | ||||
| #: documents/models.py:1147 | ||||
| msgid "webhook url" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1142 | ||||
| #: documents/models.py:1150 | ||||
| msgid "The destination URL for the notification." | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1147 | ||||
| #: documents/models.py:1155 | ||||
| msgid "use parameters" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1152 | ||||
| #: documents/models.py:1160 | ||||
| msgid "send as JSON" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1156 | ||||
| #: documents/models.py:1164 | ||||
| msgid "webhook parameters" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1159 | ||||
| #: documents/models.py:1167 | ||||
| msgid "The parameters to send with the webhook URL if body not used." | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1163 | ||||
| #: documents/models.py:1171 | ||||
| msgid "webhook body" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1166 | ||||
| #: documents/models.py:1174 | ||||
| msgid "The body to send with the webhook URL if parameters not used." | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1170 | ||||
| #: documents/models.py:1178 | ||||
| msgid "webhook headers" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1173 | ||||
| #: documents/models.py:1181 | ||||
| msgid "The headers to send with the webhook URL." | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1178 | ||||
| #: documents/models.py:1186 | ||||
| msgid "include document in webhook" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1189 | ||||
| #: documents/models.py:1197 | ||||
| msgid "Assignment" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1193 | ||||
| #: documents/models.py:1201 | ||||
| msgid "Removal" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1197 documents/templates/account/password_reset.html:15 | ||||
| #: documents/models.py:1205 documents/templates/account/password_reset.html:15 | ||||
| msgid "Email" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1201 | ||||
| #: documents/models.py:1209 | ||||
| msgid "Webhook" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1205 | ||||
| #: documents/models.py:1213 | ||||
| msgid "Workflow Action Type" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1211 | ||||
| #: documents/models.py:1219 | ||||
| msgid "assign title" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1216 | ||||
| msgid "" | ||||
| "Assign a document title, can include some placeholders, see documentation." | ||||
| #: documents/models.py:1223 | ||||
| msgid "Assign a document title, must  be a Jinja2 template, see documentation." | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1225 paperless_mail/models.py:274 | ||||
| #: documents/models.py:1231 paperless_mail/models.py:274 | ||||
| msgid "assign this tag" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1234 paperless_mail/models.py:282 | ||||
| #: documents/models.py:1240 paperless_mail/models.py:282 | ||||
| msgid "assign this document type" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1243 paperless_mail/models.py:296 | ||||
| #: documents/models.py:1249 paperless_mail/models.py:296 | ||||
| msgid "assign this correspondent" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1252 | ||||
| #: documents/models.py:1258 | ||||
| msgid "assign this storage path" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1261 | ||||
| #: documents/models.py:1267 | ||||
| msgid "assign this owner" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1268 | ||||
| #: documents/models.py:1274 | ||||
| msgid "grant view permissions to these users" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1275 | ||||
| #: documents/models.py:1281 | ||||
| msgid "grant view permissions to these groups" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1282 | ||||
| #: documents/models.py:1288 | ||||
| msgid "grant change permissions to these users" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1289 | ||||
| #: documents/models.py:1295 | ||||
| msgid "grant change permissions to these groups" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1296 | ||||
| #: documents/models.py:1302 | ||||
| msgid "assign these custom fields" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1300 | ||||
| #: documents/models.py:1306 | ||||
| msgid "custom field values" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1304 | ||||
| #: documents/models.py:1310 | ||||
| msgid "Optional values to assign to the custom fields." | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1313 | ||||
| #: documents/models.py:1319 | ||||
| msgid "remove these tag(s)" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1318 | ||||
| #: documents/models.py:1324 | ||||
| msgid "remove all tags" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1325 | ||||
| #: documents/models.py:1331 | ||||
| msgid "remove these document type(s)" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1330 | ||||
| #: documents/models.py:1336 | ||||
| msgid "remove all document types" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1337 | ||||
| #: documents/models.py:1343 | ||||
| msgid "remove these correspondent(s)" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1342 | ||||
| #: documents/models.py:1348 | ||||
| msgid "remove all correspondents" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1349 | ||||
| #: documents/models.py:1355 | ||||
| msgid "remove these storage path(s)" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1354 | ||||
| #: documents/models.py:1360 | ||||
| msgid "remove all storage paths" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1361 | ||||
| #: documents/models.py:1367 | ||||
| msgid "remove these owner(s)" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1366 | ||||
| #: documents/models.py:1372 | ||||
| msgid "remove all owners" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1373 | ||||
| #: documents/models.py:1379 | ||||
| msgid "remove view permissions for these users" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1380 | ||||
| #: documents/models.py:1386 | ||||
| msgid "remove view permissions for these groups" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1387 | ||||
| #: documents/models.py:1393 | ||||
| msgid "remove change permissions for these users" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1394 | ||||
| #: documents/models.py:1400 | ||||
| msgid "remove change permissions for these groups" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1399 | ||||
| #: documents/models.py:1405 | ||||
| msgid "remove all permissions" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1406 | ||||
| #: documents/models.py:1412 | ||||
| msgid "remove these custom fields" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1411 | ||||
| #: documents/models.py:1417 | ||||
| msgid "remove all custom fields" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1420 | ||||
| #: documents/models.py:1426 | ||||
| msgid "email" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1429 | ||||
| #: documents/models.py:1435 | ||||
| msgid "webhook" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1433 | ||||
| #: documents/models.py:1439 | ||||
| msgid "workflow action" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1434 | ||||
| #: documents/models.py:1440 | ||||
| msgid "workflow actions" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1443 paperless_mail/models.py:145 | ||||
| #: documents/models.py:1449 paperless_mail/models.py:145 | ||||
| msgid "order" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1449 | ||||
| #: documents/models.py:1455 | ||||
| msgid "triggers" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1456 | ||||
| #: documents/models.py:1462 | ||||
| msgid "actions" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1459 paperless_mail/models.py:154 | ||||
| #: documents/models.py:1465 paperless_mail/models.py:154 | ||||
| msgid "enabled" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1470 | ||||
| #: documents/models.py:1476 | ||||
| msgid "workflow" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1474 | ||||
| #: documents/models.py:1480 | ||||
| msgid "workflow trigger type" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1488 | ||||
| #: documents/models.py:1494 | ||||
| msgid "date run" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1494 | ||||
| #: documents/models.py:1500 | ||||
| msgid "workflow run" | ||||
| msgstr "" | ||||
|  | ||||
| #: documents/models.py:1495 | ||||
| #: documents/models.py:1501 | ||||
| msgid "workflow runs" | ||||
| msgstr "" | ||||
|  | ||||
|   | ||||
							
								
								
									
										128
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										128
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							| @@ -471,67 +471,67 @@ wheels = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "coverage" | ||||
| version = "7.10.3" | ||||
| version = "7.10.6" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/f4/2c/253cc41cd0f40b84c1c34c5363e0407d73d4a1cae005fed6db3b823175bd/coverage-7.10.3.tar.gz", hash = "sha256:812ba9250532e4a823b070b0420a36499859542335af3dca8f47fc6aa1a05619", size = 822936, upload-time = "2025-08-10T21:27:39.968Z" } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/14/70/025b179c993f019105b79575ac6edb5e084fb0f0e63f15cdebef4e454fb5/coverage-7.10.6.tar.gz", hash = "sha256:f644a3ae5933a552a29dbb9aa2f90c677a875f80ebea028e5a52a4f429044b90", size = 823736, upload-time = "2025-08-29T15:35:16.668Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/2f/44/e14576c34b37764c821866909788ff7463228907ab82bae188dab2b421f1/coverage-7.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:53808194afdf948c462215e9403cca27a81cf150d2f9b386aee4dab614ae2ffe", size = 215964, upload-time = "2025-08-10T21:25:22.828Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/e6/15/f4f92d9b83100903efe06c9396ee8d8bdba133399d37c186fc5b16d03a87/coverage-7.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f4d1b837d1abf72187a61645dbf799e0d7705aa9232924946e1f57eb09a3bf00", size = 216361, upload-time = "2025-08-10T21:25:25.603Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/e9/3a/c92e8cd5e89acc41cfc026dfb7acedf89661ce2ea1ee0ee13aacb6b2c20c/coverage-7.10.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2a90dd4505d3cc68b847ab10c5ee81822a968b5191664e8a0801778fa60459fa", size = 243115, upload-time = "2025-08-10T21:25:27.09Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/23/53/c1d8c2778823b1d95ca81701bb8f42c87dc341a2f170acdf716567523490/coverage-7.10.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d52989685ff5bf909c430e6d7f6550937bc6d6f3e6ecb303c97a86100efd4596", size = 244927, upload-time = "2025-08-10T21:25:28.77Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/79/41/1e115fd809031f432b4ff8e2ca19999fb6196ab95c35ae7ad5e07c001130/coverage-7.10.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdb558a1d97345bde3a9f4d3e8d11c9e5611f748646e9bb61d7d612a796671b5", size = 246784, upload-time = "2025-08-10T21:25:30.195Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/c7/b2/0eba9bdf8f1b327ae2713c74d4b7aa85451bb70622ab4e7b8c000936677c/coverage-7.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c9e6331a8f09cb1fc8bda032752af03c366870b48cce908875ba2620d20d0ad4", size = 244828, upload-time = "2025-08-10T21:25:31.785Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/1f/cc/74c56b6bf71f2a53b9aa3df8bc27163994e0861c065b4fe3a8ac290bed35/coverage-7.10.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:992f48bf35b720e174e7fae916d943599f1a66501a2710d06c5f8104e0756ee1", size = 242844, upload-time = "2025-08-10T21:25:33.37Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/b6/7b/ac183fbe19ac5596c223cb47af5737f4437e7566100b7e46cc29b66695a5/coverage-7.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c5595fc4ad6a39312c786ec3326d7322d0cf10e3ac6a6df70809910026d67cfb", size = 243721, upload-time = "2025-08-10T21:25:34.939Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/87/04/810e506d7a19889c244d35199cbf3239a2f952b55580aa42ca4287409424/coverage-7.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f2ff2e2afdf0d51b9b8301e542d9c21a8d084fd23d4c8ea2b3a1b3c96f5f7397", size = 216075, upload-time = "2025-08-10T21:25:39.891Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/2e/50/6b3fbab034717b4af3060bdaea6b13dfdc6b1fad44b5082e2a95cd378a9a/coverage-7.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:18ecc5d1b9a8c570f6c9b808fa9a2b16836b3dd5414a6d467ae942208b095f85", size = 216476, upload-time = "2025-08-10T21:25:41.137Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/c7/96/4368c624c1ed92659812b63afc76c492be7867ac8e64b7190b88bb26d43c/coverage-7.10.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1af4461b25fe92889590d438905e1fc79a95680ec2a1ff69a591bb3fdb6c7157", size = 246865, upload-time = "2025-08-10T21:25:42.408Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/34/12/5608f76070939395c17053bf16e81fd6c06cf362a537ea9d07e281013a27/coverage-7.10.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3966bc9a76b09a40dc6063c8b10375e827ea5dfcaffae402dd65953bef4cba54", size = 248800, upload-time = "2025-08-10T21:25:44.098Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/ce/52/7cc90c448a0ad724283cbcdfd66b8d23a598861a6a22ac2b7b8696491798/coverage-7.10.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:205a95b87ef4eb303b7bc5118b47b6b6604a644bcbdb33c336a41cfc0a08c06a", size = 250904, upload-time = "2025-08-10T21:25:45.384Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/e6/70/9967b847063c1c393b4f4d6daab1131558ebb6b51f01e7df7150aa99f11d/coverage-7.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b3801b79fb2ad61e3c7e2554bab754fc5f105626056980a2b9cf3aef4f13f84", size = 248597, upload-time = "2025-08-10T21:25:47.059Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/2d/fe/263307ce6878b9ed4865af42e784b42bb82d066bcf10f68defa42931c2c7/coverage-7.10.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b0dc69c60224cda33d384572da945759756e3f06b9cdac27f302f53961e63160", size = 246647, upload-time = "2025-08-10T21:25:48.334Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/8e/27/d27af83ad162eba62c4eb7844a1de6cf7d9f6b185df50b0a3514a6f80ddd/coverage-7.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a83d4f134bab2c7ff758e6bb1541dd72b54ba295ced6a63d93efc2e20cb9b124", size = 247290, upload-time = "2025-08-10T21:25:49.945Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/b8/62/13c0b66e966c43d7aa64dadc8cd2afa1f5a2bf9bb863bdabc21fb94e8b63/coverage-7.10.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:449c1e2d3a84d18bd204258a897a87bc57380072eb2aded6a5b5226046207b42", size = 216262, upload-time = "2025-08-10T21:25:55.367Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/b5/f0/59fdf79be7ac2f0206fc739032f482cfd3f66b18f5248108ff192741beae/coverage-7.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1d4f9ce50b9261ad196dc2b2e9f1fbbee21651b54c3097a25ad783679fd18294", size = 216496, upload-time = "2025-08-10T21:25:56.759Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/34/b1/bc83788ba31bde6a0c02eb96bbc14b2d1eb083ee073beda18753fa2c4c66/coverage-7.10.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4dd4564207b160d0d45c36a10bc0a3d12563028e8b48cd6459ea322302a156d7", size = 247989, upload-time = "2025-08-10T21:25:58.067Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/0c/29/f8bdf88357956c844bd872e87cb16748a37234f7f48c721dc7e981145eb7/coverage-7.10.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5ca3c9530ee072b7cb6a6ea7b640bcdff0ad3b334ae9687e521e59f79b1d0437", size = 250738, upload-time = "2025-08-10T21:25:59.406Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/ae/df/6396301d332b71e42bbe624670af9376f63f73a455cc24723656afa95796/coverage-7.10.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b6df359e59fa243c9925ae6507e27f29c46698359f45e568fd51b9315dbbe587", size = 251868, upload-time = "2025-08-10T21:26:00.65Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/91/21/d760b2df6139b6ef62c9cc03afb9bcdf7d6e36ed4d078baacffa618b4c1c/coverage-7.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a181e4c2c896c2ff64c6312db3bda38e9ade2e1aa67f86a5628ae85873786cea", size = 249790, upload-time = "2025-08-10T21:26:02.009Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/69/91/5dcaa134568202397fa4023d7066d4318dc852b53b428052cd914faa05e1/coverage-7.10.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a374d4e923814e8b72b205ef6b3d3a647bb50e66f3558582eda074c976923613", size = 247907, upload-time = "2025-08-10T21:26:03.757Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/38/ed/70c0e871cdfef75f27faceada461206c1cc2510c151e1ef8d60a6fedda39/coverage-7.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:daeefff05993e5e8c6e7499a8508e7bd94502b6b9a9159c84fd1fe6bce3151cb", size = 249344, upload-time = "2025-08-10T21:26:05.11Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/0a/ff/239e4de9cc149c80e9cc359fab60592365b8c4cbfcad58b8a939d18c6898/coverage-7.10.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b99e87304ffe0eb97c5308447328a584258951853807afdc58b16143a530518a", size = 216298, upload-time = "2025-08-10T21:26:10.973Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/56/da/28717da68f8ba68f14b9f558aaa8f3e39ada8b9a1ae4f4977c8f98b286d5/coverage-7.10.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4af09c7574d09afbc1ea7da9dcea23665c01f3bc1b1feb061dac135f98ffc53a", size = 216546, upload-time = "2025-08-10T21:26:12.616Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/de/bb/e1ade16b9e3f2d6c323faeb6bee8e6c23f3a72760a5d9af102ef56a656cb/coverage-7.10.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:488e9b50dc5d2aa9521053cfa706209e5acf5289e81edc28291a24f4e4488f46", size = 247538, upload-time = "2025-08-10T21:26:14.455Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/ea/2f/6ae1db51dc34db499bfe340e89f79a63bd115fc32513a7bacdf17d33cd86/coverage-7.10.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:913ceddb4289cbba3a310704a424e3fb7aac2bc0c3a23ea473193cb290cf17d4", size = 250141, upload-time = "2025-08-10T21:26:15.787Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/4f/ed/33efd8819895b10c66348bf26f011dd621e804866c996ea6893d682218df/coverage-7.10.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b1f91cbc78c7112ab84ed2a8defbccd90f888fcae40a97ddd6466b0bec6ae8a", size = 251415, upload-time = "2025-08-10T21:26:17.535Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/26/04/cb83826f313d07dc743359c9914d9bc460e0798da9a0e38b4f4fabc207ed/coverage-7.10.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0bac054d45af7cd938834b43a9878b36ea92781bcb009eab040a5b09e9927e3", size = 249575, upload-time = "2025-08-10T21:26:18.921Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/2d/fd/ae963c7a8e9581c20fa4355ab8940ca272554d8102e872dbb932a644e410/coverage-7.10.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fe72cbdd12d9e0f4aca873fa6d755e103888a7f9085e4a62d282d9d5b9f7928c", size = 247466, upload-time = "2025-08-10T21:26:20.263Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/99/e8/b68d1487c6af370b8d5ef223c6d7e250d952c3acfbfcdbf1a773aa0da9d2/coverage-7.10.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c1e2e927ab3eadd7c244023927d646e4c15c65bb2ac7ae3c3e9537c013700d21", size = 249084, upload-time = "2025-08-10T21:26:21.638Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/fc/26/1c1f450e15a3bf3eaecf053ff64538a2612a23f05b21d79ce03be9ff5903/coverage-7.10.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07009152f497a0464ffdf2634586787aea0e69ddd023eafb23fc38267db94b84", size = 217003, upload-time = "2025-08-10T21:26:27.231Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/29/96/4b40036181d8c2948454b458750960956a3c4785f26a3c29418bbbee1666/coverage-7.10.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dd2ba5f0c7e7e8cc418be2f0c14c4d9e3f08b8fb8e4c0f83c2fe87d03eb655e", size = 217238, upload-time = "2025-08-10T21:26:28.83Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/62/23/8dfc52e95da20957293fb94d97397a100e63095ec1e0ef5c09dd8c6f591a/coverage-7.10.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1ae22b97003c74186e034a93e4f946c75fad8c0ce8d92fbbc168b5e15ee2841f", size = 258561, upload-time = "2025-08-10T21:26:30.475Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/59/95/00e7fcbeda3f632232f4c07dde226afe3511a7781a000aa67798feadc535/coverage-7.10.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb329f1046888a36b1dc35504d3029e1dd5afe2196d94315d18c45ee380f67d5", size = 260735, upload-time = "2025-08-10T21:26:32.333Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/9e/4c/f4666cbc4571804ba2a65b078ff0de600b0b577dc245389e0bc9b69ae7ca/coverage-7.10.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce01048199a91f07f96ca3074b0c14021f4fe7ffd29a3e6a188ac60a5c3a4af8", size = 262960, upload-time = "2025-08-10T21:26:33.701Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/c1/a5/8a9e8a7b12a290ed98b60f73d1d3e5e9ced75a4c94a0d1a671ce3ddfff2a/coverage-7.10.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:08b989a06eb9dfacf96d42b7fb4c9a22bafa370d245dc22fa839f2168c6f9fa1", size = 260515, upload-time = "2025-08-10T21:26:35.16Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/86/11/bb59f7f33b2cac0c5b17db0d9d0abba9c90d9eda51a6e727b43bd5fce4ae/coverage-7.10.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:669fe0d4e69c575c52148511029b722ba8d26e8a3129840c2ce0522e1452b256", size = 258278, upload-time = "2025-08-10T21:26:36.539Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/cc/22/3646f8903743c07b3e53fded0700fed06c580a980482f04bf9536657ac17/coverage-7.10.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3262d19092771c83f3413831d9904b1ccc5f98da5de4ffa4ad67f5b20c7aaf7b", size = 259408, upload-time = "2025-08-10T21:26:37.954Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/2d/84/bb773b51a06edbf1231b47dc810a23851f2796e913b335a0fa364773b842/coverage-7.10.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:bce8b8180912914032785850d8f3aacb25ec1810f5f54afc4a8b114e7a9b55de", size = 216280, upload-time = "2025-08-10T21:26:44.132Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/92/a8/4d8ca9c111d09865f18d56facff64d5fa076a5593c290bd1cfc5dceb8dba/coverage-7.10.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07790b4b37d56608536f7c1079bd1aa511567ac2966d33d5cec9cf520c50a7c8", size = 216557, upload-time = "2025-08-10T21:26:45.598Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/fe/b2/eb668bfc5060194bc5e1ccd6f664e8e045881cfee66c42a2aa6e6c5b26e8/coverage-7.10.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e79367ef2cd9166acedcbf136a458dfe9a4a2dd4d1ee95738fb2ee581c56f667", size = 247598, upload-time = "2025-08-10T21:26:47.081Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/fd/b0/9faa4ac62c8822219dd83e5d0e73876398af17d7305968aed8d1606d1830/coverage-7.10.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:419d2a0f769f26cb1d05e9ccbc5eab4cb5d70231604d47150867c07822acbdf4", size = 250131, upload-time = "2025-08-10T21:26:48.65Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/4e/90/203537e310844d4bf1bdcfab89c1e05c25025c06d8489b9e6f937ad1a9e2/coverage-7.10.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee221cf244757cdc2ac882e3062ab414b8464ad9c884c21e878517ea64b3fa26", size = 251485, upload-time = "2025-08-10T21:26:50.368Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/b9/b2/9d894b26bc53c70a1fe503d62240ce6564256d6d35600bdb86b80e516e7d/coverage-7.10.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c2079d8cdd6f7373d628e14b3357f24d1db02c9dc22e6a007418ca7a2be0435a", size = 249488, upload-time = "2025-08-10T21:26:52.045Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/b4/28/af167dbac5281ba6c55c933a0ca6675d68347d5aee39cacc14d44150b922/coverage-7.10.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:bd8df1f83c0703fa3ca781b02d36f9ec67ad9cb725b18d486405924f5e4270bd", size = 247419, upload-time = "2025-08-10T21:26:53.533Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/f4/1c/9a4ddc9f0dcb150d4cd619e1c4bb39bcf694c6129220bdd1e5895d694dda/coverage-7.10.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6b4e25e0fa335c8aa26e42a52053f3786a61cc7622b4d54ae2dad994aa754fec", size = 248917, upload-time = "2025-08-10T21:26:55.11Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/73/3d/89d65baf1ea39e148ee989de6da601469ba93c1d905b17dfb0b83bd39c96/coverage-7.10.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ebc8791d346410d096818788877d675ca55c91db87d60e8f477bd41c6970ffc6", size = 217019, upload-time = "2025-08-10T21:27:01.242Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/7d/7d/d9850230cd9c999ce3a1e600f85c2fff61a81c301334d7a1faa1a5ba19c8/coverage-7.10.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f4e4d8e75f6fd3c6940ebeed29e3d9d632e1f18f6fb65d33086d99d4d073241", size = 217237, upload-time = "2025-08-10T21:27:03.442Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/36/51/b87002d417202ab27f4a1cd6bd34ee3b78f51b3ddbef51639099661da991/coverage-7.10.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:24581ed69f132b6225a31b0228ae4885731cddc966f8a33fe5987288bdbbbd5e", size = 258735, upload-time = "2025-08-10T21:27:05.124Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/1c/02/1f8612bfcb46fc7ca64a353fff1cd4ed932bb6e0b4e0bb88b699c16794b8/coverage-7.10.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec151569ddfccbf71bac8c422dce15e176167385a00cd86e887f9a80035ce8a5", size = 260901, upload-time = "2025-08-10T21:27:06.68Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/aa/3a/fe39e624ddcb2373908bd922756384bb70ac1c5009b0d1674eb326a3e428/coverage-7.10.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2ae8e7c56290b908ee817200c0b65929b8050bc28530b131fe7c6dfee3e7d86b", size = 263157, upload-time = "2025-08-10T21:27:08.398Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/5e/89/496b6d5a10fa0d0691a633bb2b2bcf4f38f0bdfcbde21ad9e32d1af328ed/coverage-7.10.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fb742309766d7e48e9eb4dc34bc95a424707bc6140c0e7d9726e794f11b92a0", size = 260597, upload-time = "2025-08-10T21:27:10.237Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/b6/a6/8b5bf6a9e8c6aaeb47d5fe9687014148efc05c3588110246d5fdeef9b492/coverage-7.10.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:c65e2a5b32fbe1e499f1036efa6eb9cb4ea2bf6f7168d0e7a5852f3024f471b1", size = 258353, upload-time = "2025-08-10T21:27:11.773Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/c3/6d/ad131be74f8afd28150a07565dfbdc86592fd61d97e2dc83383d9af219f0/coverage-7.10.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d48d2cb07d50f12f4f18d2bb75d9d19e3506c26d96fffabf56d22936e5ed8f7c", size = 259504, upload-time = "2025-08-10T21:27:13.254Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/84/19/e67f4ae24e232c7f713337f3f4f7c9c58afd0c02866fb07c7b9255a19ed7/coverage-7.10.3-py3-none-any.whl", hash = "sha256:416a8d74dc0adfd33944ba2f405897bab87b7e9e84a391e09d241956bd953ce1", size = 207921, upload-time = "2025-08-10T21:27:38.254Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/a8/1d/2e64b43d978b5bd184e0756a41415597dfef30fcbd90b747474bd749d45f/coverage-7.10.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:70e7bfbd57126b5554aa482691145f798d7df77489a177a6bef80de78860a356", size = 217025, upload-time = "2025-08-29T15:32:57.169Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/23/62/b1e0f513417c02cc10ef735c3ee5186df55f190f70498b3702d516aad06f/coverage-7.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e41be6f0f19da64af13403e52f2dec38bbc2937af54df8ecef10850ff8d35301", size = 217419, upload-time = "2025-08-29T15:32:59.908Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/e7/16/b800640b7a43e7c538429e4d7223e0a94fd72453a1a048f70bf766f12e96/coverage-7.10.6-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c61fc91ab80b23f5fddbee342d19662f3d3328173229caded831aa0bd7595460", size = 244180, upload-time = "2025-08-29T15:33:01.608Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/fb/6f/5e03631c3305cad187eaf76af0b559fff88af9a0b0c180d006fb02413d7a/coverage-7.10.6-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10356fdd33a7cc06e8051413140bbdc6f972137508a3572e3f59f805cd2832fd", size = 245992, upload-time = "2025-08-29T15:33:03.239Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/eb/a1/f30ea0fb400b080730125b490771ec62b3375789f90af0bb68bfb8a921d7/coverage-7.10.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80b1695cf7c5ebe7b44bf2521221b9bb8cdf69b1f24231149a7e3eb1ae5fa2fb", size = 247851, upload-time = "2025-08-29T15:33:04.603Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/02/8e/cfa8fee8e8ef9a6bb76c7bef039f3302f44e615d2194161a21d3d83ac2e9/coverage-7.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2e4c33e6378b9d52d3454bd08847a8651f4ed23ddbb4a0520227bd346382bbc6", size = 245891, upload-time = "2025-08-29T15:33:06.176Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/93/a9/51be09b75c55c4f6c16d8d73a6a1d46ad764acca0eab48fa2ffaef5958fe/coverage-7.10.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c8a3ec16e34ef980a46f60dc6ad86ec60f763c3f2fa0db6d261e6e754f72e945", size = 243909, upload-time = "2025-08-29T15:33:07.74Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/e9/a6/ba188b376529ce36483b2d585ca7bdac64aacbe5aa10da5978029a9c94db/coverage-7.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7d79dabc0a56f5af990cc6da9ad1e40766e82773c075f09cc571e2076fef882e", size = 244786, upload-time = "2025-08-29T15:33:08.965Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/d4/16/2bea27e212c4980753d6d563a0803c150edeaaddb0771a50d2afc410a261/coverage-7.10.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c706db3cabb7ceef779de68270150665e710b46d56372455cd741184f3868d8f", size = 217129, upload-time = "2025-08-29T15:33:13.575Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/2a/51/e7159e068831ab37e31aac0969d47b8c5ee25b7d307b51e310ec34869315/coverage-7.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e0c38dc289e0508ef68ec95834cb5d2e96fdbe792eaccaa1bccac3966bbadcc", size = 217532, upload-time = "2025-08-29T15:33:14.872Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/e7/c0/246ccbea53d6099325d25cd208df94ea435cd55f0db38099dd721efc7a1f/coverage-7.10.6-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:752a3005a1ded28f2f3a6e8787e24f28d6abe176ca64677bcd8d53d6fe2ec08a", size = 247931, upload-time = "2025-08-29T15:33:16.142Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/7d/fb/7435ef8ab9b2594a6e3f58505cc30e98ae8b33265d844007737946c59389/coverage-7.10.6-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:689920ecfd60f992cafca4f5477d55720466ad2c7fa29bb56ac8d44a1ac2b47a", size = 249864, upload-time = "2025-08-29T15:33:17.434Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/51/f8/d9d64e8da7bcddb094d511154824038833c81e3a039020a9d6539bf303e9/coverage-7.10.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec98435796d2624d6905820a42f82149ee9fc4f2d45c2c5bc5a44481cc50db62", size = 251969, upload-time = "2025-08-29T15:33:18.822Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/43/28/c43ba0ef19f446d6463c751315140d8f2a521e04c3e79e5c5fe211bfa430/coverage-7.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b37201ce4a458c7a758ecc4efa92fa8ed783c66e0fa3c42ae19fc454a0792153", size = 249659, upload-time = "2025-08-29T15:33:20.407Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/79/3e/53635bd0b72beaacf265784508a0b386defc9ab7fad99ff95f79ce9db555/coverage-7.10.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2904271c80898663c810a6b067920a61dd8d38341244a3605bd31ab55250dad5", size = 247714, upload-time = "2025-08-29T15:33:21.751Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/4c/55/0964aa87126624e8c159e32b0bc4e84edef78c89a1a4b924d28dd8265625/coverage-7.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5aea98383463d6e1fa4e95416d8de66f2d0cb588774ee20ae1b28df826bcb619", size = 248351, upload-time = "2025-08-29T15:33:23.105Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/26/06/263f3305c97ad78aab066d116b52250dd316e74fcc20c197b61e07eb391a/coverage-7.10.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b2dd6059938063a2c9fee1af729d4f2af28fd1a545e9b7652861f0d752ebcea", size = 217324, upload-time = "2025-08-29T15:33:29.06Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/e9/60/1e1ded9a4fe80d843d7d53b3e395c1db3ff32d6c301e501f393b2e6c1c1f/coverage-7.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:388d80e56191bf846c485c14ae2bc8898aa3124d9d35903fef7d907780477634", size = 217560, upload-time = "2025-08-29T15:33:30.748Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/b8/25/52136173c14e26dfed8b106ed725811bb53c30b896d04d28d74cb64318b3/coverage-7.10.6-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:90cb5b1a4670662719591aa92d0095bb41714970c0b065b02a2610172dbf0af6", size = 249053, upload-time = "2025-08-29T15:33:32.041Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/cb/1d/ae25a7dc58fcce8b172d42ffe5313fc267afe61c97fa872b80ee72d9515a/coverage-7.10.6-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:961834e2f2b863a0e14260a9a273aff07ff7818ab6e66d2addf5628590c628f9", size = 251802, upload-time = "2025-08-29T15:33:33.625Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/f5/7a/1f561d47743710fe996957ed7c124b421320f150f1d38523d8d9102d3e2a/coverage-7.10.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf9a19f5012dab774628491659646335b1928cfc931bf8d97b0d5918dd58033c", size = 252935, upload-time = "2025-08-29T15:33:34.909Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/6c/ad/8b97cd5d28aecdfde792dcbf646bac141167a5cacae2cd775998b45fabb5/coverage-7.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99c4283e2a0e147b9c9cc6bc9c96124de9419d6044837e9799763a0e29a7321a", size = 250855, upload-time = "2025-08-29T15:33:36.922Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/33/6a/95c32b558d9a61858ff9d79580d3877df3eb5bc9eed0941b1f187c89e143/coverage-7.10.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:282b1b20f45df57cc508c1e033403f02283adfb67d4c9c35a90281d81e5c52c5", size = 248974, upload-time = "2025-08-29T15:33:38.175Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/0d/9c/8ce95dee640a38e760d5b747c10913e7a06554704d60b41e73fdea6a1ffd/coverage-7.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cdbe264f11afd69841bd8c0d83ca10b5b32853263ee62e6ac6a0ab63895f972", size = 250409, upload-time = "2025-08-29T15:33:39.447Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/bd/e7/917e5953ea29a28c1057729c1d5af9084ab6d9c66217523fd0e10f14d8f6/coverage-7.10.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ffea0575345e9ee0144dfe5701aa17f3ba546f8c3bb48db62ae101afb740e7d6", size = 217351, upload-time = "2025-08-29T15:33:45.438Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/eb/86/2e161b93a4f11d0ea93f9bebb6a53f113d5d6e416d7561ca41bb0a29996b/coverage-7.10.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:95d91d7317cde40a1c249d6b7382750b7e6d86fad9d8eaf4fa3f8f44cf171e80", size = 217600, upload-time = "2025-08-29T15:33:47.269Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/0e/66/d03348fdd8df262b3a7fb4ee5727e6e4936e39e2f3a842e803196946f200/coverage-7.10.6-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e23dd5408fe71a356b41baa82892772a4cefcf758f2ca3383d2aa39e1b7a003", size = 248600, upload-time = "2025-08-29T15:33:48.953Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/73/dd/508420fb47d09d904d962f123221bc249f64b5e56aa93d5f5f7603be475f/coverage-7.10.6-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0f3f56e4cb573755e96a16501a98bf211f100463d70275759e73f3cbc00d4f27", size = 251206, upload-time = "2025-08-29T15:33:50.697Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/e9/1f/9020135734184f439da85c70ea78194c2730e56c2d18aee6e8ff1719d50d/coverage-7.10.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db4a1d897bbbe7339946ffa2fe60c10cc81c43fab8b062d3fcb84188688174a4", size = 252478, upload-time = "2025-08-29T15:33:52.303Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/a4/a4/3d228f3942bb5a2051fde28c136eea23a761177dc4ff4ef54533164ce255/coverage-7.10.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fd7879082953c156d5b13c74aa6cca37f6a6f4747b39538504c3f9c63d043d", size = 250637, upload-time = "2025-08-29T15:33:53.67Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/36/e3/293dce8cdb9a83de971637afc59b7190faad60603b40e32635cbd15fbf61/coverage-7.10.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:28395ca3f71cd103b8c116333fa9db867f3a3e1ad6a084aa3725ae002b6583bc", size = 248529, upload-time = "2025-08-29T15:33:55.022Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/90/26/64eecfa214e80dd1d101e420cab2901827de0e49631d666543d0e53cf597/coverage-7.10.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:61c950fc33d29c91b9e18540e1aed7d9f6787cc870a3e4032493bbbe641d12fc", size = 250143, upload-time = "2025-08-29T15:33:56.386Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/d7/1c/ccccf4bf116f9517275fa85047495515add43e41dfe8e0bef6e333c6b344/coverage-7.10.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c9a8b7a34a4de3ed987f636f71881cd3b8339f61118b1aa311fbda12741bff0b", size = 218059, upload-time = "2025-08-29T15:34:02.91Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/92/97/8a3ceff833d27c7492af4f39d5da6761e9ff624831db9e9f25b3886ddbca/coverage-7.10.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dd5af36092430c2b075cee966719898f2ae87b636cefb85a653f1d0ba5d5393", size = 218287, upload-time = "2025-08-29T15:34:05.106Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/92/d8/50b4a32580cf41ff0423777a2791aaf3269ab60c840b62009aec12d3970d/coverage-7.10.6-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0353b0f0850d49ada66fdd7d0c7cdb0f86b900bb9e367024fd14a60cecc1e27", size = 259625, upload-time = "2025-08-29T15:34:06.575Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/7e/7e/6a7df5a6fb440a0179d94a348eb6616ed4745e7df26bf2a02bc4db72c421/coverage-7.10.6-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d6b9ae13d5d3e8aeca9ca94198aa7b3ebbc5acfada557d724f2a1f03d2c0b0df", size = 261801, upload-time = "2025-08-29T15:34:08.006Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/3a/4c/a270a414f4ed5d196b9d3d67922968e768cd971d1b251e1b4f75e9362f75/coverage-7.10.6-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:675824a363cc05781b1527b39dc2587b8984965834a748177ee3c37b64ffeafb", size = 264027, upload-time = "2025-08-29T15:34:09.806Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/9c/8b/3210d663d594926c12f373c5370bf1e7c5c3a427519a8afa65b561b9a55c/coverage-7.10.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:692d70ea725f471a547c305f0d0fc6a73480c62fb0da726370c088ab21aed282", size = 261576, upload-time = "2025-08-29T15:34:11.585Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/72/d0/e1961eff67e9e1dba3fc5eb7a4caf726b35a5b03776892da8d79ec895775/coverage-7.10.6-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:851430a9a361c7a8484a36126d1d0ff8d529d97385eacc8dfdc9bfc8c2d2cbe4", size = 259341, upload-time = "2025-08-29T15:34:13.159Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/3a/06/d6478d152cd189b33eac691cba27a40704990ba95de49771285f34a5861e/coverage-7.10.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d9369a23186d189b2fc95cc08b8160ba242057e887d766864f7adf3c46b2df21", size = 260468, upload-time = "2025-08-29T15:34:14.571Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/d3/aa/76cf0b5ec00619ef208da4689281d48b57f2c7fde883d14bf9441b74d59f/coverage-7.10.6-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6008a021907be8c4c02f37cdc3ffb258493bdebfeaf9a839f9e71dfdc47b018e", size = 217331, upload-time = "2025-08-29T15:34:20.846Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/65/91/8e41b8c7c505d398d7730206f3cbb4a875a35ca1041efc518051bfce0f6b/coverage-7.10.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5e75e37f23eb144e78940b40395b42f2321951206a4f50e23cfd6e8a198d3ceb", size = 217607, upload-time = "2025-08-29T15:34:22.433Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/87/7f/f718e732a423d442e6616580a951b8d1ec3575ea48bcd0e2228386805e79/coverage-7.10.6-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0f7cb359a448e043c576f0da00aa8bfd796a01b06aa610ca453d4dde09cc1034", size = 248663, upload-time = "2025-08-29T15:34:24.425Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/e6/52/c1106120e6d801ac03e12b5285e971e758e925b6f82ee9b86db3aa10045d/coverage-7.10.6-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c68018e4fc4e14b5668f1353b41ccf4bc83ba355f0e1b3836861c6f042d89ac1", size = 251197, upload-time = "2025-08-29T15:34:25.906Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/3d/ec/3a8645b1bb40e36acde9c0609f08942852a4af91a937fe2c129a38f2d3f5/coverage-7.10.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd4b2b0707fc55afa160cd5fc33b27ccbf75ca11d81f4ec9863d5793fc6df56a", size = 252551, upload-time = "2025-08-29T15:34:27.337Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/a1/70/09ecb68eeb1155b28a1d16525fd3a9b65fbe75337311a99830df935d62b6/coverage-7.10.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cec13817a651f8804a86e4f79d815b3b28472c910e099e4d5a0e8a3b6a1d4cb", size = 250553, upload-time = "2025-08-29T15:34:29.065Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/c6/80/47df374b893fa812e953b5bc93dcb1427a7b3d7a1a7d2db33043d17f74b9/coverage-7.10.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f2a6a8e06bbda06f78739f40bfb56c45d14eb8249d0f0ea6d4b3d48e1f7c695d", size = 248486, upload-time = "2025-08-29T15:34:30.897Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/4a/65/9f98640979ecee1b0d1a7164b589de720ddf8100d1747d9bbdb84be0c0fb/coverage-7.10.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:081b98395ced0d9bcf60ada7661a0b75f36b78b9d7e39ea0790bb4ed8da14747", size = 249981, upload-time = "2025-08-29T15:34:32.365Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/83/c0/1f00caad775c03a700146f55536ecd097a881ff08d310a58b353a1421be0/coverage-7.10.6-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0de434f4fbbe5af4fa7989521c655c8c779afb61c53ab561b64dcee6149e4c65", size = 218080, upload-time = "2025-08-29T15:34:38.919Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/a9/c4/b1c5d2bd7cc412cbeb035e257fd06ed4e3e139ac871d16a07434e145d18d/coverage-7.10.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6e31b8155150c57e5ac43ccd289d079eb3f825187d7c66e755a055d2c85794c6", size = 218293, upload-time = "2025-08-29T15:34:40.425Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/3f/07/4468d37c94724bf6ec354e4ec2f205fda194343e3e85fd2e59cec57e6a54/coverage-7.10.6-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:98cede73eb83c31e2118ae8d379c12e3e42736903a8afcca92a7218e1f2903b0", size = 259800, upload-time = "2025-08-29T15:34:41.996Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/82/d8/f8fb351be5fee31690cd8da768fd62f1cfab33c31d9f7baba6cd8960f6b8/coverage-7.10.6-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f863c08f4ff6b64fa8045b1e3da480f5374779ef187f07b82e0538c68cb4ff8e", size = 261965, upload-time = "2025-08-29T15:34:43.61Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/e8/70/65d4d7cfc75c5c6eb2fed3ee5cdf420fd8ae09c4808723a89a81d5b1b9c3/coverage-7.10.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b38261034fda87be356f2c3f42221fdb4171c3ce7658066ae449241485390d5", size = 264220, upload-time = "2025-08-29T15:34:45.387Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/98/3c/069df106d19024324cde10e4ec379fe2fb978017d25e97ebee23002fbadf/coverage-7.10.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e93b1476b79eae849dc3872faeb0bf7948fd9ea34869590bc16a2a00b9c82a7", size = 261660, upload-time = "2025-08-29T15:34:47.288Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/fc/8a/2974d53904080c5dc91af798b3a54a4ccb99a45595cc0dcec6eb9616a57d/coverage-7.10.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ff8a991f70f4c0cf53088abf1e3886edcc87d53004c7bb94e78650b4d3dac3b5", size = 259417, upload-time = "2025-08-29T15:34:48.779Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/30/38/9616a6b49c686394b318974d7f6e08f38b8af2270ce7488e879888d1e5db/coverage-7.10.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ac765b026c9f33044419cbba1da913cfb82cca1b60598ac1c7a5ed6aac4621a0", size = 260567, upload-time = "2025-08-29T15:34:50.718Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/44/0c/50db5379b615854b5cf89146f8f5bd1d5a9693d7f3a987e269693521c404/coverage-7.10.6-py3-none-any.whl", hash = "sha256:92c4ecf6bf11b2e85fd4d8204814dc26e6a19f0c9d938c207c5cb0eadfcabbe3", size = 208986, upload-time = "2025-08-29T15:35:14.506Z" }, | ||||
| ] | ||||
|  | ||||
| [package.optional-dependencies] | ||||
| @@ -2234,7 +2234,7 @@ dev = [ | ||||
|     { name = "pre-commit", specifier = "~=4.3.0" }, | ||||
|     { name = "pre-commit-uv", specifier = "~=4.1.3" }, | ||||
|     { name = "pytest", specifier = "~=8.4.1" }, | ||||
|     { name = "pytest-cov", specifier = "~=6.2.1" }, | ||||
|     { name = "pytest-cov", specifier = "~=7.0.0" }, | ||||
|     { name = "pytest-django", specifier = "~=4.11.1" }, | ||||
|     { name = "pytest-env" }, | ||||
|     { name = "pytest-httpx" }, | ||||
| @@ -2258,7 +2258,7 @@ testing = [ | ||||
|     { name = "factory-boy", specifier = "~=3.3.1" }, | ||||
|     { name = "imagehash" }, | ||||
|     { name = "pytest", specifier = "~=8.4.1" }, | ||||
|     { name = "pytest-cov", specifier = "~=6.2.1" }, | ||||
|     { name = "pytest-cov", specifier = "~=7.0.0" }, | ||||
|     { name = "pytest-django", specifier = "~=4.11.1" }, | ||||
|     { name = "pytest-env" }, | ||||
|     { name = "pytest-httpx" }, | ||||
| @@ -2731,16 +2731,16 @@ wheels = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "pytest-cov" | ||||
| version = "6.2.1" | ||||
| version = "7.0.0" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| dependencies = [ | ||||
|     { name = "coverage", extra = ["toml"], marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, | ||||
|     { name = "pluggy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, | ||||
|     { name = "pytest", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, | ||||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 shamoon
					shamoon