mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Enhancement: shared icon & shared by me filter (#4859)
This commit is contained in:
		| @@ -80,7 +80,7 @@ django_checks() { | |||||||
|  |  | ||||||
| search_index() { | search_index() { | ||||||
|  |  | ||||||
| 	local -r index_version=7 | 	local -r index_version=8 | ||||||
| 	local -r index_version_file=${DATA_DIR}/.index_version | 	local -r index_version_file=${DATA_DIR}/.index_version | ||||||
|  |  | ||||||
| 	if [[ (! -f "${index_version_file}") || $(<"${index_version_file}") != "$index_version" ]]; then | 	if [[ (! -f "${index_version_file}") || $(<"${index_version_file}") != "$index_version" ]]; then | ||||||
|   | |||||||
| @@ -1657,7 +1657,7 @@ | |||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/common/permissions-filter-dropdown/permissions-filter-dropdown.component.html</context> |           <context context-type="sourcefile">src/app/components/common/permissions-filter-dropdown/permissions-filter-dropdown.component.html</context> | ||||||
|           <context context-type="linenumber">68</context> |           <context context-type="linenumber">78</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="2941198503117307737" datatype="html"> |       <trans-unit id="2941198503117307737" datatype="html"> | ||||||
| @@ -1721,7 +1721,7 @@ | |||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context> |           <context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context> | ||||||
|           <context context-type="linenumber">83</context> |           <context context-type="linenumber">89</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/manage/consumption-templates/consumption-templates.component.html</context> |           <context context-type="sourcefile">src/app/components/manage/consumption-templates/consumption-templates.component.html</context> | ||||||
| @@ -3670,18 +3670,25 @@ | |||||||
|           <context context-type="linenumber">38</context> |           <context context-type="linenumber">38</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|  |       <trans-unit id="175385209536581523" datatype="html"> | ||||||
|  |         <source>Shared by me</source> | ||||||
|  |         <context-group purpose="location"> | ||||||
|  |           <context context-type="sourcefile">src/app/components/common/permissions-filter-dropdown/permissions-filter-dropdown.component.html</context> | ||||||
|  |           <context context-type="linenumber">48</context> | ||||||
|  |         </context-group> | ||||||
|  |       </trans-unit> | ||||||
|       <trans-unit id="5151074932731293042" datatype="html"> |       <trans-unit id="5151074932731293042" datatype="html"> | ||||||
|         <source>Unowned</source> |         <source>Unowned</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/common/permissions-filter-dropdown/permissions-filter-dropdown.component.html</context> |           <context context-type="sourcefile">src/app/components/common/permissions-filter-dropdown/permissions-filter-dropdown.component.html</context> | ||||||
|           <context context-type="linenumber">48</context> |           <context context-type="linenumber">58</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="8999708063434507268" datatype="html"> |       <trans-unit id="8999708063434507268" datatype="html"> | ||||||
|         <source>Hide unowned</source> |         <source>Hide unowned</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/common/permissions-filter-dropdown/permissions-filter-dropdown.component.html</context> |           <context context-type="sourcefile">src/app/components/common/permissions-filter-dropdown/permissions-filter-dropdown.component.html</context> | ||||||
|           <context context-type="linenumber">77</context> |           <context context-type="linenumber">87</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="8650499415827640724" datatype="html"> |       <trans-unit id="8650499415827640724" datatype="html"> | ||||||
| @@ -4012,7 +4019,7 @@ | |||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">203</context> |           <context context-type="linenumber">204</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/services/rest/document.service.ts</context> |           <context context-type="sourcefile">src/app/services/rest/document.service.ts</context> | ||||||
| @@ -4073,7 +4080,7 @@ | |||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context> |           <context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context> | ||||||
|           <context context-type="linenumber">99</context> |           <context context-type="linenumber">105</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="872092479747931526" datatype="html"> |       <trans-unit id="872092479747931526" datatype="html"> | ||||||
| @@ -5007,11 +5014,26 @@ | |||||||
|           <context context-type="linenumber">58,59</context> |           <context context-type="linenumber">58,59</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|  |       <trans-unit id="5739581984228459958" datatype="html"> | ||||||
|  |         <source>Shared</source> | ||||||
|  |         <context-group purpose="location"> | ||||||
|  |           <context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context> | ||||||
|  |           <context context-type="linenumber">119</context> | ||||||
|  |         </context-group> | ||||||
|  |         <context-group purpose="location"> | ||||||
|  |           <context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context> | ||||||
|  |           <context context-type="linenumber">84</context> | ||||||
|  |         </context-group> | ||||||
|  |         <context-group purpose="location"> | ||||||
|  |           <context context-type="sourcefile">src/app/pipes/username.pipe.ts</context> | ||||||
|  |           <context context-type="linenumber">33</context> | ||||||
|  |         </context-group> | ||||||
|  |       </trans-unit> | ||||||
|       <trans-unit id="2332107018974972998" datatype="html"> |       <trans-unit id="2332107018974972998" datatype="html"> | ||||||
|         <source>Score:</source> |         <source>Score:</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context> |           <context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context> | ||||||
|           <context context-type="linenumber">116</context> |           <context context-type="linenumber">122</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="3661756380991326939" datatype="html"> |       <trans-unit id="3661756380991326939" datatype="html"> | ||||||
| @@ -5138,7 +5160,7 @@ | |||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">208</context> |           <context context-type="linenumber">209</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/services/rest/document.service.ts</context> |           <context context-type="sourcefile">src/app/services/rest/document.service.ts</context> | ||||||
| @@ -5254,14 +5276,14 @@ | |||||||
|             )?.name"/></source> |             )?.name"/></source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">120,122</context> |           <context context-type="linenumber">121,123</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="8170755470576301659" datatype="html"> |       <trans-unit id="8170755470576301659" datatype="html"> | ||||||
|         <source>Without correspondent</source> |         <source>Without correspondent</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">124</context> |           <context context-type="linenumber">125</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="317796810569008208" datatype="html"> |       <trans-unit id="317796810569008208" datatype="html"> | ||||||
| @@ -5270,14 +5292,14 @@ | |||||||
|             )?.name"/></source> |             )?.name"/></source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">130,132</context> |           <context context-type="linenumber">131,133</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="4362173610367509215" datatype="html"> |       <trans-unit id="4362173610367509215" datatype="html"> | ||||||
|         <source>Without document type</source> |         <source>Without document type</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">134</context> |           <context context-type="linenumber">135</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="232202047340644471" datatype="html"> |       <trans-unit id="232202047340644471" datatype="html"> | ||||||
| @@ -5286,14 +5308,14 @@ | |||||||
|             )?.name"/></source> |             )?.name"/></source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">140,142</context> |           <context context-type="linenumber">141,143</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="1562820715074533164" datatype="html"> |       <trans-unit id="1562820715074533164" datatype="html"> | ||||||
|         <source>Without storage path</source> |         <source>Without storage path</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">144</context> |           <context context-type="linenumber">145</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="8180755793012580465" datatype="html"> |       <trans-unit id="8180755793012580465" datatype="html"> | ||||||
| @@ -5301,112 +5323,112 @@ | |||||||
|             ?.name"/></source> |             ?.name"/></source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">148,149</context> |           <context context-type="linenumber">149,150</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="6494566478302448576" datatype="html"> |       <trans-unit id="6494566478302448576" datatype="html"> | ||||||
|         <source>Without any tag</source> |         <source>Without any tag</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">153</context> |           <context context-type="linenumber">154</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="6523384805359286307" datatype="html"> |       <trans-unit id="6523384805359286307" datatype="html"> | ||||||
|         <source>Title: <x id="PH" equiv-text="rule.value"/></source> |         <source>Title: <x id="PH" equiv-text="rule.value"/></source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">157</context> |           <context context-type="linenumber">158</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="1872523635812236432" datatype="html"> |       <trans-unit id="1872523635812236432" datatype="html"> | ||||||
|         <source>ASN: <x id="PH" equiv-text="rule.value"/></source> |         <source>ASN: <x id="PH" equiv-text="rule.value"/></source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">160</context> |           <context context-type="linenumber">161</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="102674688969746976" datatype="html"> |       <trans-unit id="102674688969746976" datatype="html"> | ||||||
|         <source>Owner: <x id="PH" equiv-text="rule.value"/></source> |         <source>Owner: <x id="PH" equiv-text="rule.value"/></source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">163</context> |           <context context-type="linenumber">164</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="3550877650686009106" datatype="html"> |       <trans-unit id="3550877650686009106" datatype="html"> | ||||||
|         <source>Owner not in: <x id="PH" equiv-text="rule.value"/></source> |         <source>Owner not in: <x id="PH" equiv-text="rule.value"/></source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">166</context> |           <context context-type="linenumber">167</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="1082034558646673343" datatype="html"> |       <trans-unit id="1082034558646673343" datatype="html"> | ||||||
|         <source>Without an owner</source> |         <source>Without an owner</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">169</context> |           <context context-type="linenumber">170</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="3100631071441658964" datatype="html"> |       <trans-unit id="3100631071441658964" datatype="html"> | ||||||
|         <source>Title & content</source> |         <source>Title & content</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">206</context> |           <context context-type="linenumber">207</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="9149498548977462220" datatype="html"> |       <trans-unit id="9149498548977462220" datatype="html"> | ||||||
|         <source>Custom fields</source> |         <source>Custom fields</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">211</context> |           <context context-type="linenumber">212</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="1010505078885609376" datatype="html"> |       <trans-unit id="1010505078885609376" datatype="html"> | ||||||
|         <source>Advanced search</source> |         <source>Advanced search</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">215</context> |           <context context-type="linenumber">216</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="2649431021108393503" datatype="html"> |       <trans-unit id="2649431021108393503" datatype="html"> | ||||||
|         <source>More like</source> |         <source>More like</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">221</context> |           <context context-type="linenumber">222</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="3697582909018473071" datatype="html"> |       <trans-unit id="3697582909018473071" datatype="html"> | ||||||
|         <source>equals</source> |         <source>equals</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">240</context> |           <context context-type="linenumber">241</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="5325481293405718739" datatype="html"> |       <trans-unit id="5325481293405718739" datatype="html"> | ||||||
|         <source>is empty</source> |         <source>is empty</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">244</context> |           <context context-type="linenumber">245</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="6166785695326182482" datatype="html"> |       <trans-unit id="6166785695326182482" datatype="html"> | ||||||
|         <source>is not empty</source> |         <source>is not empty</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">248</context> |           <context context-type="linenumber">249</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="4686622206659266699" datatype="html"> |       <trans-unit id="4686622206659266699" datatype="html"> | ||||||
|         <source>greater than</source> |         <source>greater than</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">252</context> |           <context context-type="linenumber">253</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="8014012170270529279" datatype="html"> |       <trans-unit id="8014012170270529279" datatype="html"> | ||||||
|         <source>less than</source> |         <source>less than</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> |           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||||
|           <context context-type="linenumber">256</context> |           <context context-type="linenumber">257</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="7210076240260527720" datatype="html"> |       <trans-unit id="7210076240260527720" datatype="html"> | ||||||
| @@ -6297,13 +6319,6 @@ | |||||||
|           <context context-type="linenumber">11</context> |           <context context-type="linenumber">11</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="5739581984228459958" datatype="html"> |  | ||||||
|         <source>Shared</source> |  | ||||||
|         <context-group purpose="location"> |  | ||||||
|           <context context-type="sourcefile">src/app/pipes/username.pipe.ts</context> |  | ||||||
|           <context context-type="linenumber">33</context> |  | ||||||
|         </context-group> |  | ||||||
|       </trans-unit> |  | ||||||
|       <trans-unit id="2807800733729323332" datatype="html"> |       <trans-unit id="2807800733729323332" datatype="html"> | ||||||
|         <source>Yes</source> |         <source>Yes</source> | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|   | |||||||
| @@ -38,6 +38,16 @@ | |||||||
|                     <small i18n>Shared with me</small> |                     <small i18n>Shared with me</small> | ||||||
|                 </div> |                 </div> | ||||||
|             </button> |             </button> | ||||||
|  |             <button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="setFilter(OwnerFilterType.SHARED_BY_ME)" [disabled]="disabled"> | ||||||
|  |                 <div class="selected-icon me-1"> | ||||||
|  |                     <svg *ngIf="selectionModel.ownerFilter === OwnerFilterType.SHARED_BY_ME" fill="currentColor" class="buttonicon-sm"> | ||||||
|  |                         <use xlink:href="assets/bootstrap-icons.svg#check"/> | ||||||
|  |                     </svg> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="me-1"> | ||||||
|  |                     <small i18n>Shared by me</small> | ||||||
|  |                 </div> | ||||||
|  |             </button> | ||||||
|             <button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="setFilter(OwnerFilterType.UNOWNED)" [disabled]="disabled"> |             <button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="setFilter(OwnerFilterType.UNOWNED)" [disabled]="disabled"> | ||||||
|                 <div class="selected-icon me-1"> |                 <div class="selected-icon me-1"> | ||||||
|                     <svg *ngIf="selectionModel.ownerFilter === OwnerFilterType.UNOWNED" fill="currentColor" class="buttonicon-sm"> |                     <svg *ngIf="selectionModel.ownerFilter === OwnerFilterType.UNOWNED" fill="currentColor" class="buttonicon-sm"> | ||||||
|   | |||||||
| @@ -145,6 +145,15 @@ describe('PermissionsFilterDropdownComponent', () => { | |||||||
|       userID: null, |       userID: null, | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|  |     component.setFilter(OwnerFilterType.SHARED_BY_ME) | ||||||
|  |     expect(ownerFilterSetResult).toEqual({ | ||||||
|  |       excludeUsers: [], | ||||||
|  |       hideUnowned: false, | ||||||
|  |       includeUsers: [], | ||||||
|  |       ownerFilter: OwnerFilterType.SHARED_BY_ME, | ||||||
|  |       userID: currentUserID, | ||||||
|  |     }) | ||||||
|  |  | ||||||
|     component.setFilter(OwnerFilterType.UNOWNED) |     component.setFilter(OwnerFilterType.UNOWNED) | ||||||
|     expect(ownerFilterSetResult).toEqual({ |     expect(ownerFilterSetResult).toEqual({ | ||||||
|       excludeUsers: [], |       excludeUsers: [], | ||||||
|   | |||||||
| @@ -32,6 +32,7 @@ export enum OwnerFilterType { | |||||||
|   NOT_SELF = 2, |   NOT_SELF = 2, | ||||||
|   OTHERS = 3, |   OTHERS = 3, | ||||||
|   UNOWNED = 4, |   UNOWNED = 4, | ||||||
|  |   SHARED_BY_ME = 5, | ||||||
| } | } | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
| @@ -108,6 +109,13 @@ export class PermissionsFilterDropdownComponent extends ComponentWithPermissions | |||||||
|       this.selectionModel.includeUsers = [] |       this.selectionModel.includeUsers = [] | ||||||
|       this.selectionModel.excludeUsers = [] |       this.selectionModel.excludeUsers = [] | ||||||
|       this.selectionModel.hideUnowned = false |       this.selectionModel.hideUnowned = false | ||||||
|  |     } else if ( | ||||||
|  |       this.selectionModel.ownerFilter === OwnerFilterType.SHARED_BY_ME | ||||||
|  |     ) { | ||||||
|  |       this.selectionModel.userID = this.settingsService.currentUser.id | ||||||
|  |       this.selectionModel.includeUsers = [] | ||||||
|  |       this.selectionModel.excludeUsers = [] | ||||||
|  |       this.selectionModel.hideUnowned = false | ||||||
|     } else if (this.selectionModel.ownerFilter === OwnerFilterType.UNOWNED) { |     } else if (this.selectionModel.ownerFilter === OwnerFilterType.UNOWNED) { | ||||||
|       this.selectionModel.userID = null |       this.selectionModel.userID = null | ||||||
|       this.selectionModel.includeUsers = [] |       this.selectionModel.includeUsers = [] | ||||||
|   | |||||||
| @@ -112,6 +112,12 @@ | |||||||
|               </svg> |               </svg> | ||||||
|               <small>{{document.owner | username}}</small> |               <small>{{document.owner | username}}</small> | ||||||
|             </div> |             </div> | ||||||
|  |             <div *ngIf="document.is_shared_by_requester" class="list-group-item bg-light text-dark p-1 border-0"> | ||||||
|  |               <svg class="metadata-icon me-2 text-muted" fill="currentColor"> | ||||||
|  |                 <use xlink:href="assets/bootstrap-icons.svg#people-fill"/> | ||||||
|  |               </svg> | ||||||
|  |               <small i18n>Shared</small> | ||||||
|  |             </div> | ||||||
|             <div *ngIf="document.__search_hit__?.score" class="list-group-item bg-light text-dark border-0 d-flex p-0 ps-4 search-score"> |             <div *ngIf="document.__search_hit__?.score" class="list-group-item bg-light text-dark border-0 d-flex p-0 ps-4 search-score"> | ||||||
|               <small class="text-muted" i18n>Score:</small> |               <small class="text-muted" i18n>Score:</small> | ||||||
|               <ngb-progressbar [type]="searchScoreClass" [value]="document.__search_hit__.score" class="search-score-bar mx-2 mt-1" [max]="1"></ngb-progressbar> |               <ngb-progressbar [type]="searchScoreClass" [value]="document.__search_hit__.score" class="search-score-bar mx-2 mt-1" [max]="1"></ngb-progressbar> | ||||||
|   | |||||||
| @@ -77,6 +77,12 @@ | |||||||
|           </svg> |           </svg> | ||||||
|           <small>{{document.owner | username}}</small> |           <small>{{document.owner | username}}</small> | ||||||
|         </div> |         </div> | ||||||
|  |         <div *ngIf="document.is_shared_by_requester" class="ps-0 p-1"> | ||||||
|  |           <svg class="metadata-icon me-2 text-muted" fill="currentColor"> | ||||||
|  |             <use xlink:href="assets/bootstrap-icons.svg#people-fill"/> | ||||||
|  |           </svg> | ||||||
|  |           <small i18n>Shared</small> | ||||||
|  |         </div> | ||||||
|       </div> |       </div> | ||||||
|       <div class="d-flex justify-content-between align-items-center"> |       <div class="d-flex justify-content-between align-items-center"> | ||||||
|         <div class="btn-group w-100"> |         <div class="btn-group w-100"> | ||||||
|   | |||||||
| @@ -47,6 +47,7 @@ import { | |||||||
|   FILTER_OWNER_DOES_NOT_INCLUDE, |   FILTER_OWNER_DOES_NOT_INCLUDE, | ||||||
|   FILTER_OWNER_ISNULL, |   FILTER_OWNER_ISNULL, | ||||||
|   FILTER_CUSTOM_FIELDS, |   FILTER_CUSTOM_FIELDS, | ||||||
|  |   FILTER_SHARED_BY_USER, | ||||||
| } from 'src/app/data/filter-rule-type' | } from 'src/app/data/filter-rule-type' | ||||||
| import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent' | import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent' | ||||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type' | import { PaperlessDocumentType } from 'src/app/data/paperless-document-type' | ||||||
| @@ -826,6 +827,16 @@ describe('FilterEditorComponent', () => { | |||||||
|     expect(component.permissionsSelectionModel.hideUnowned).toBeTruthy() |     expect(component.permissionsSelectionModel.hideUnowned).toBeTruthy() | ||||||
|   })) |   })) | ||||||
|  |  | ||||||
|  |   it('should ingest filter rules for shared by me', fakeAsync(() => { | ||||||
|  |     component.filterRules = [ | ||||||
|  |       { | ||||||
|  |         rule_type: FILTER_SHARED_BY_USER, | ||||||
|  |         value: '2', | ||||||
|  |       }, | ||||||
|  |     ] | ||||||
|  |     expect(component.permissionsSelectionModel.userID).toEqual(2) | ||||||
|  |   })) | ||||||
|  |  | ||||||
|   // GET filterRules |   // GET filterRules | ||||||
|  |  | ||||||
|   it('should convert user input to correct filter rules on text field search title + content', fakeAsync(() => { |   it('should convert user input to correct filter rules on text field search title + content', fakeAsync(() => { | ||||||
| @@ -1453,13 +1464,28 @@ describe('FilterEditorComponent', () => { | |||||||
|     ]) |     ]) | ||||||
|   })) |   })) | ||||||
|  |  | ||||||
|   it('should convert user input to correct filter on permissions select unowned', fakeAsync(() => { |   it('should convert user input to correct filter on permissions select shared by me', fakeAsync(() => { | ||||||
|     const permissionsDropdown = fixture.debugElement.query( |     const permissionsDropdown = fixture.debugElement.query( | ||||||
|       By.directive(PermissionsFilterDropdownComponent) |       By.directive(PermissionsFilterDropdownComponent) | ||||||
|     ) |     ) | ||||||
|     const unownedButton = permissionsDropdown.queryAll(By.css('button'))[4] |     const unownedButton = permissionsDropdown.queryAll(By.css('button'))[4] | ||||||
|     unownedButton.triggerEventHandler('click') |     unownedButton.triggerEventHandler('click') | ||||||
|     fixture.detectChanges() |     fixture.detectChanges() | ||||||
|  |     expect(component.filterRules).toEqual([ | ||||||
|  |       { | ||||||
|  |         rule_type: FILTER_SHARED_BY_USER, | ||||||
|  |         value: '1', | ||||||
|  |       }, | ||||||
|  |     ]) | ||||||
|  |   })) | ||||||
|  |  | ||||||
|  |   it('should convert user input to correct filter on permissions select unowned', fakeAsync(() => { | ||||||
|  |     const permissionsDropdown = fixture.debugElement.query( | ||||||
|  |       By.directive(PermissionsFilterDropdownComponent) | ||||||
|  |     ) | ||||||
|  |     const unownedButton = permissionsDropdown.queryAll(By.css('button'))[5] | ||||||
|  |     unownedButton.triggerEventHandler('click') | ||||||
|  |     fixture.detectChanges() | ||||||
|     expect(component.filterRules).toEqual([ |     expect(component.filterRules).toEqual([ | ||||||
|       { |       { | ||||||
|         rule_type: FILTER_OWNER_ISNULL, |         rule_type: FILTER_OWNER_ISNULL, | ||||||
|   | |||||||
| @@ -49,6 +49,7 @@ import { | |||||||
|   FILTER_OWNER_ISNULL, |   FILTER_OWNER_ISNULL, | ||||||
|   FILTER_OWNER_ANY, |   FILTER_OWNER_ANY, | ||||||
|   FILTER_CUSTOM_FIELDS, |   FILTER_CUSTOM_FIELDS, | ||||||
|  |   FILTER_SHARED_BY_USER, | ||||||
| } from 'src/app/data/filter-rule-type' | } from 'src/app/data/filter-rule-type' | ||||||
| import { | import { | ||||||
|   FilterableDropdownSelectionModel, |   FilterableDropdownSelectionModel, | ||||||
| @@ -503,6 +504,12 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | |||||||
|               parseInt(rule.value, 10) |               parseInt(rule.value, 10) | ||||||
|             ) |             ) | ||||||
|           break |           break | ||||||
|  |         case FILTER_SHARED_BY_USER: | ||||||
|  |           this.permissionsSelectionModel.ownerFilter = | ||||||
|  |             OwnerFilterType.SHARED_BY_ME | ||||||
|  |           if (rule.value) | ||||||
|  |             this.permissionsSelectionModel.userID = parseInt(rule.value, 10) | ||||||
|  |           break | ||||||
|         case FILTER_OWNER_ISNULL: |         case FILTER_OWNER_ISNULL: | ||||||
|           if (rule.value === 'true' || rule.value === '1') { |           if (rule.value === 'true' || rule.value === '1') { | ||||||
|             this.permissionsSelectionModel.hideUnowned = false |             this.permissionsSelectionModel.hideUnowned = false | ||||||
| @@ -801,6 +808,13 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | |||||||
|         rule_type: FILTER_OWNER_ANY, |         rule_type: FILTER_OWNER_ANY, | ||||||
|         value: this.permissionsSelectionModel.includeUsers?.join(','), |         value: this.permissionsSelectionModel.includeUsers?.join(','), | ||||||
|       }) |       }) | ||||||
|  |     } else if ( | ||||||
|  |       this.permissionsSelectionModel.ownerFilter == OwnerFilterType.SHARED_BY_ME | ||||||
|  |     ) { | ||||||
|  |       filterRules.push({ | ||||||
|  |         rule_type: FILTER_SHARED_BY_USER, | ||||||
|  |         value: this.permissionsSelectionModel.userID.toString(), | ||||||
|  |       }) | ||||||
|     } else if ( |     } else if ( | ||||||
|       this.permissionsSelectionModel.ownerFilter == OwnerFilterType.UNOWNED |       this.permissionsSelectionModel.ownerFilter == OwnerFilterType.UNOWNED | ||||||
|     ) { |     ) { | ||||||
|   | |||||||
| @@ -45,6 +45,7 @@ export const FILTER_OWNER = 32 | |||||||
| export const FILTER_OWNER_ANY = 33 | export const FILTER_OWNER_ANY = 33 | ||||||
| export const FILTER_OWNER_ISNULL = 34 | export const FILTER_OWNER_ISNULL = 34 | ||||||
| export const FILTER_OWNER_DOES_NOT_INCLUDE = 35 | export const FILTER_OWNER_DOES_NOT_INCLUDE = 35 | ||||||
|  | export const FILTER_SHARED_BY_USER = 37 | ||||||
|  |  | ||||||
| export const FILTER_CUSTOM_FIELDS = 36 | export const FILTER_CUSTOM_FIELDS = 36 | ||||||
|  |  | ||||||
| @@ -273,6 +274,12 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [ | |||||||
|     datatype: 'number', |     datatype: 'number', | ||||||
|     multi: true, |     multi: true, | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     id: FILTER_SHARED_BY_USER, | ||||||
|  |     filtervar: 'shared_by__id', | ||||||
|  |     datatype: 'number', | ||||||
|  |     multi: true, | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     id: FILTER_CUSTOM_FIELDS, |     id: FILTER_CUSTOM_FIELDS, | ||||||
|     filtervar: 'custom_fields__icontains', |     filtervar: 'custom_fields__icontains', | ||||||
|   | |||||||
| @@ -17,4 +17,6 @@ export interface ObjectWithPermissions extends ObjectWithId { | |||||||
|   permissions?: PermissionsObject |   permissions?: PermissionsObject | ||||||
|  |  | ||||||
|   user_can_change?: boolean |   user_can_change?: boolean | ||||||
|  |  | ||||||
|  |   is_shared_by_requester?: boolean | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,7 +1,12 @@ | |||||||
|  | from django.contrib.contenttypes.models import ContentType | ||||||
|  | from django.db.models import Count | ||||||
|  | from django.db.models import OuterRef | ||||||
| from django.db.models import Q | from django.db.models import Q | ||||||
| from django_filters.rest_framework import BooleanFilter | from django_filters.rest_framework import BooleanFilter | ||||||
| from django_filters.rest_framework import Filter | from django_filters.rest_framework import Filter | ||||||
| from django_filters.rest_framework import FilterSet | from django_filters.rest_framework import FilterSet | ||||||
|  | from guardian.utils import get_group_obj_perms_model | ||||||
|  | from guardian.utils import get_user_obj_perms_model | ||||||
| from rest_framework_guardian.filters import ObjectPermissionsFilter | from rest_framework_guardian.filters import ObjectPermissionsFilter | ||||||
|  |  | ||||||
| from documents.models import Correspondent | from documents.models import Correspondent | ||||||
| @@ -101,6 +106,39 @@ class TitleContentFilter(Filter): | |||||||
|             return qs |             return qs | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SharedByUser(Filter): | ||||||
|  |     def filter(self, qs, value): | ||||||
|  |         ctype = ContentType.objects.get_for_model(self.model) | ||||||
|  |         UserObjectPermission = get_user_obj_perms_model() | ||||||
|  |         GroupObjectPermission = get_group_obj_perms_model() | ||||||
|  |         return ( | ||||||
|  |             qs.filter( | ||||||
|  |                 owner_id=value, | ||||||
|  |             ) | ||||||
|  |             .annotate( | ||||||
|  |                 num_shared_users=Count( | ||||||
|  |                     UserObjectPermission.objects.filter( | ||||||
|  |                         content_type=ctype, | ||||||
|  |                         object_pk=OuterRef("pk"), | ||||||
|  |                     ).values("user_id"), | ||||||
|  |                 ), | ||||||
|  |             ) | ||||||
|  |             .annotate( | ||||||
|  |                 num_shared_groups=Count( | ||||||
|  |                     GroupObjectPermission.objects.filter( | ||||||
|  |                         content_type=ctype, | ||||||
|  |                         object_pk=OuterRef("pk"), | ||||||
|  |                     ).values("group_id"), | ||||||
|  |                 ), | ||||||
|  |             ) | ||||||
|  |             .filter( | ||||||
|  |                 Q(num_shared_users__gt=0) | Q(num_shared_groups__gt=0), | ||||||
|  |             ) | ||||||
|  |             if value is not None | ||||||
|  |             else qs | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
| class CustomFieldsFilter(Filter): | class CustomFieldsFilter(Filter): | ||||||
|     def filter(self, qs, value): |     def filter(self, qs, value): | ||||||
|         if value: |         if value: | ||||||
| @@ -144,6 +182,8 @@ class DocumentFilterSet(FilterSet): | |||||||
|  |  | ||||||
|     custom_fields__icontains = CustomFieldsFilter() |     custom_fields__icontains = CustomFieldsFilter() | ||||||
|  |  | ||||||
|  |     shared_by__id = SharedByUser() | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = Document |         model = Document | ||||||
|         fields = { |         fields = { | ||||||
|   | |||||||
| @@ -75,6 +75,7 @@ def get_schema(): | |||||||
|         viewer_id=KEYWORD(commas=True), |         viewer_id=KEYWORD(commas=True), | ||||||
|         checksum=TEXT(), |         checksum=TEXT(), | ||||||
|         original_filename=TEXT(sortable=True), |         original_filename=TEXT(sortable=True), | ||||||
|  |         is_shared=BOOLEAN(), | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -167,6 +168,7 @@ def update_document(writer: AsyncWriter, doc: Document): | |||||||
|         viewer_id=viewer_ids if viewer_ids else None, |         viewer_id=viewer_ids if viewer_ids else None, | ||||||
|         checksum=doc.checksum, |         checksum=doc.checksum, | ||||||
|         original_filename=doc.original_filename, |         original_filename=doc.original_filename, | ||||||
|  |         is_shared=len(viewer_ids) > 0, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -194,6 +196,7 @@ class DelayedQuery: | |||||||
|         "document_type": ("type", ["id", "id__in", "id__none", "isnull"]), |         "document_type": ("type", ["id", "id__in", "id__none", "isnull"]), | ||||||
|         "storage_path": ("path", ["id", "id__in", "id__none", "isnull"]), |         "storage_path": ("path", ["id", "id__in", "id__none", "isnull"]), | ||||||
|         "owner": ("owner", ["id", "id__in", "id__none", "isnull"]), |         "owner": ("owner", ["id", "id__in", "id__none", "isnull"]), | ||||||
|  |         "shared_by": ("shared_by", ["id"]), | ||||||
|         "tags": ("tag", ["id__all", "id__in", "id__none"]), |         "tags": ("tag", ["id__all", "id__in", "id__none"]), | ||||||
|         "added": ("added", ["date__lt", "date__gt"]), |         "added": ("added", ["date__lt", "date__gt"]), | ||||||
|         "created": ("created", ["date__lt", "date__gt"]), |         "created": ("created", ["date__lt", "date__gt"]), | ||||||
| @@ -233,7 +236,11 @@ class DelayedQuery: | |||||||
|                 continue |                 continue | ||||||
|  |  | ||||||
|             if query_filter == "id": |             if query_filter == "id": | ||||||
|                 criterias.append(query.Term(f"{field}_id", value)) |                 if param == "shared_by": | ||||||
|  |                     criterias.append(query.Term("is_shared", True)) | ||||||
|  |                     criterias.append(query.Term("owner_id", value)) | ||||||
|  |                 else: | ||||||
|  |                     criterias.append(query.Term(f"{field}_id", value)) | ||||||
|             elif query_filter == "id__in": |             elif query_filter == "id__in": | ||||||
|                 in_filter = [] |                 in_filter = [] | ||||||
|                 for object_id in value.split(","): |                 for object_id in value.split(","): | ||||||
|   | |||||||
| @@ -0,0 +1,60 @@ | |||||||
|  | # Generated by Django 4.2.7 on 2023-12-09 18:13 | ||||||
|  |  | ||||||
|  | from django.db import migrations | ||||||
|  | from django.db import models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |     dependencies = [ | ||||||
|  |         ("documents", "1042_consumptiontemplate_assign_custom_fields_and_more"), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterField( | ||||||
|  |             model_name="savedviewfilterrule", | ||||||
|  |             name="rule_type", | ||||||
|  |             field=models.PositiveIntegerField( | ||||||
|  |                 choices=[ | ||||||
|  |                     (0, "title contains"), | ||||||
|  |                     (1, "content contains"), | ||||||
|  |                     (2, "ASN is"), | ||||||
|  |                     (3, "correspondent is"), | ||||||
|  |                     (4, "document type is"), | ||||||
|  |                     (5, "is in inbox"), | ||||||
|  |                     (6, "has tag"), | ||||||
|  |                     (7, "has any tag"), | ||||||
|  |                     (8, "created before"), | ||||||
|  |                     (9, "created after"), | ||||||
|  |                     (10, "created year is"), | ||||||
|  |                     (11, "created month is"), | ||||||
|  |                     (12, "created day is"), | ||||||
|  |                     (13, "added before"), | ||||||
|  |                     (14, "added after"), | ||||||
|  |                     (15, "modified before"), | ||||||
|  |                     (16, "modified after"), | ||||||
|  |                     (17, "does not have tag"), | ||||||
|  |                     (18, "does not have ASN"), | ||||||
|  |                     (19, "title or content contains"), | ||||||
|  |                     (20, "fulltext query"), | ||||||
|  |                     (21, "more like this"), | ||||||
|  |                     (22, "has tags in"), | ||||||
|  |                     (23, "ASN greater than"), | ||||||
|  |                     (24, "ASN less than"), | ||||||
|  |                     (25, "storage path is"), | ||||||
|  |                     (26, "has correspondent in"), | ||||||
|  |                     (27, "does not have correspondent in"), | ||||||
|  |                     (28, "has document type in"), | ||||||
|  |                     (29, "does not have document type in"), | ||||||
|  |                     (30, "has storage path in"), | ||||||
|  |                     (31, "does not have storage path in"), | ||||||
|  |                     (32, "owner is"), | ||||||
|  |                     (33, "has owner in"), | ||||||
|  |                     (34, "does not have owner"), | ||||||
|  |                     (35, "does not have owner in"), | ||||||
|  |                     (36, "has custom field value"), | ||||||
|  |                     (37, "is shared by me"), | ||||||
|  |                 ], | ||||||
|  |                 verbose_name="rule type", | ||||||
|  |             ), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -455,6 +455,8 @@ class SavedViewFilterRule(models.Model): | |||||||
|         (33, _("has owner in")), |         (33, _("has owner in")), | ||||||
|         (34, _("does not have owner")), |         (34, _("does not have owner")), | ||||||
|         (35, _("does not have owner in")), |         (35, _("does not have owner in")), | ||||||
|  |         (36, _("has custom field value")), | ||||||
|  |         (37, _("is shared by me")), | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|     saved_view = models.ForeignKey( |     saved_view = models.ForeignKey( | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ from celery import states | |||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.contrib.auth.models import Group | from django.contrib.auth.models import Group | ||||||
| from django.contrib.auth.models import User | from django.contrib.auth.models import User | ||||||
|  | from django.contrib.contenttypes.models import ContentType | ||||||
| from django.core.validators import URLValidator | from django.core.validators import URLValidator | ||||||
| from django.utils.crypto import get_random_string | from django.utils.crypto import get_random_string | ||||||
| from django.utils.text import slugify | from django.utils.text import slugify | ||||||
| @@ -15,6 +16,8 @@ from django.utils.translation import gettext as _ | |||||||
| from drf_writable_nested.serializers import NestedUpdateMixin | from drf_writable_nested.serializers import NestedUpdateMixin | ||||||
| from guardian.core import ObjectPermissionChecker | from guardian.core import ObjectPermissionChecker | ||||||
| from guardian.shortcuts import get_users_with_perms | from guardian.shortcuts import get_users_with_perms | ||||||
|  | from guardian.utils import get_group_obj_perms_model | ||||||
|  | from guardian.utils import get_user_obj_perms_model | ||||||
| from rest_framework import fields | from rest_framework import fields | ||||||
| from rest_framework import serializers | from rest_framework import serializers | ||||||
| from rest_framework.fields import SerializerMethodField | from rest_framework.fields import SerializerMethodField | ||||||
| @@ -160,6 +163,7 @@ class OwnedObjectSerializer(serializers.ModelSerializer, SetPermissionsMixin): | |||||||
|         try: |         try: | ||||||
|             if full_perms: |             if full_perms: | ||||||
|                 self.fields.pop("user_can_change") |                 self.fields.pop("user_can_change") | ||||||
|  |                 self.fields.pop("is_shared_by_requester") | ||||||
|             else: |             else: | ||||||
|                 self.fields.pop("permissions") |                 self.fields.pop("permissions") | ||||||
|         except KeyError: |         except KeyError: | ||||||
| @@ -205,8 +209,26 @@ class OwnedObjectSerializer(serializers.ModelSerializer, SetPermissionsMixin): | |||||||
|             ) |             ) | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  |     def get_is_shared_by_requester(self, obj: Document): | ||||||
|  |         ctype = ContentType.objects.get_for_model(obj) | ||||||
|  |         UserObjectPermission = get_user_obj_perms_model() | ||||||
|  |         GroupObjectPermission = get_group_obj_perms_model() | ||||||
|  |         return obj.owner == self.user and ( | ||||||
|  |             UserObjectPermission.objects.filter( | ||||||
|  |                 content_type=ctype, | ||||||
|  |                 object_pk=obj.pk, | ||||||
|  |             ).count() | ||||||
|  |             > 0 | ||||||
|  |             or GroupObjectPermission.objects.filter( | ||||||
|  |                 content_type=ctype, | ||||||
|  |                 object_pk=obj.pk, | ||||||
|  |             ).count() | ||||||
|  |             > 0 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     permissions = SerializerMethodField(read_only=True) |     permissions = SerializerMethodField(read_only=True) | ||||||
|     user_can_change = SerializerMethodField(read_only=True) |     user_can_change = SerializerMethodField(read_only=True) | ||||||
|  |     is_shared_by_requester = SerializerMethodField(read_only=True) | ||||||
|  |  | ||||||
|     set_permissions = serializers.DictField( |     set_permissions = serializers.DictField( | ||||||
|         label="Set permissions", |         label="Set permissions", | ||||||
| @@ -556,6 +578,7 @@ class DocumentSerializer( | |||||||
|             "owner", |             "owner", | ||||||
|             "permissions", |             "permissions", | ||||||
|             "user_can_change", |             "user_can_change", | ||||||
|  |             "is_shared_by_requester", | ||||||
|             "set_permissions", |             "set_permissions", | ||||||
|             "notes", |             "notes", | ||||||
|             "custom_fields", |             "custom_fields", | ||||||
|   | |||||||
| @@ -594,7 +594,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): | |||||||
|         results = response.data["results"] |         results = response.data["results"] | ||||||
|         self.assertEqual(len(results), 0) |         self.assertEqual(len(results), 0) | ||||||
|  |  | ||||||
|     def test_document_owner_filters(self): |     def test_document_permissions_filters(self): | ||||||
|         """ |         """ | ||||||
|         GIVEN: |         GIVEN: | ||||||
|             - Documents with owners, with and without granted permissions |             - Documents with owners, with and without granted permissions | ||||||
| @@ -686,6 +686,18 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): | |||||||
|             [u1_doc1.id, u1_doc2.id, u2_doc2.id], |             [u1_doc1.id, u1_doc2.id, u2_doc2.id], | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  |         assign_perm("view_document", u2, u1_doc1) | ||||||
|  |  | ||||||
|  |         # Will show only documents shared by user | ||||||
|  |         response = self.client.get(f"/api/documents/?shared_by__id={u1.id}") | ||||||
|  |         self.assertEqual(response.status_code, status.HTTP_200_OK) | ||||||
|  |         results = response.data["results"] | ||||||
|  |         self.assertEqual(len(results), 1) | ||||||
|  |         self.assertCountEqual( | ||||||
|  |             [results[0]["id"]], | ||||||
|  |             [u1_doc1.id], | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def test_pagination_all(self): |     def test_pagination_all(self): | ||||||
|         """ |         """ | ||||||
|         GIVEN: |         GIVEN: | ||||||
|   | |||||||
| @@ -408,10 +408,17 @@ class TestApiAuth(DirectoriesMixin, APITestCase): | |||||||
|             checksum="3", |             checksum="3", | ||||||
|             owner=user2, |             owner=user2, | ||||||
|         ) |         ) | ||||||
|  |         doc4 = Document.objects.create( | ||||||
|  |             title="Test4", | ||||||
|  |             content="content 4", | ||||||
|  |             checksum="4", | ||||||
|  |             owner=user1, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|         assign_perm("view_document", user1, doc2) |         assign_perm("view_document", user1, doc2) | ||||||
|         assign_perm("view_document", user1, doc3) |         assign_perm("view_document", user1, doc3) | ||||||
|         assign_perm("change_document", user1, doc3) |         assign_perm("change_document", user1, doc3) | ||||||
|  |         assign_perm("view_document", user2, doc4) | ||||||
|  |  | ||||||
|         self.client.force_authenticate(user1) |         self.client.force_authenticate(user1) | ||||||
|  |  | ||||||
| @@ -426,9 +433,11 @@ class TestApiAuth(DirectoriesMixin, APITestCase): | |||||||
|  |  | ||||||
|         self.assertNotIn("permissions", resp_data["results"][0]) |         self.assertNotIn("permissions", resp_data["results"][0]) | ||||||
|         self.assertIn("user_can_change", resp_data["results"][0]) |         self.assertIn("user_can_change", resp_data["results"][0]) | ||||||
|         self.assertEqual(resp_data["results"][0]["user_can_change"], True)  # doc1 |         self.assertTrue(resp_data["results"][0]["user_can_change"])  # doc1 | ||||||
|         self.assertEqual(resp_data["results"][1]["user_can_change"], False)  # doc2 |         self.assertFalse(resp_data["results"][0]["is_shared_by_requester"])  # doc1 | ||||||
|         self.assertEqual(resp_data["results"][2]["user_can_change"], True)  # doc3 |         self.assertFalse(resp_data["results"][1]["user_can_change"])  # doc2 | ||||||
|  |         self.assertTrue(resp_data["results"][2]["user_can_change"])  # doc3 | ||||||
|  |         self.assertTrue(resp_data["results"][3]["is_shared_by_requester"])  # doc4 | ||||||
|  |  | ||||||
|         response = self.client.get( |         response = self.client.get( | ||||||
|             "/api/documents/?full_perms=true", |             "/api/documents/?full_perms=true", | ||||||
| @@ -441,6 +450,7 @@ class TestApiAuth(DirectoriesMixin, APITestCase): | |||||||
|  |  | ||||||
|         self.assertIn("permissions", resp_data["results"][0]) |         self.assertIn("permissions", resp_data["results"][0]) | ||||||
|         self.assertNotIn("user_can_change", resp_data["results"][0]) |         self.assertNotIn("user_can_change", resp_data["results"][0]) | ||||||
|  |         self.assertNotIn("is_shared_by_requester", resp_data["results"][0]) | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestApiUser(DirectoriesMixin, APITestCase): | class TestApiUser(DirectoriesMixin, APITestCase): | ||||||
|   | |||||||
| @@ -968,7 +968,7 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase): | |||||||
|         u1.user_permissions.add(*Permission.objects.filter(codename="view_document")) |         u1.user_permissions.add(*Permission.objects.filter(codename="view_document")) | ||||||
|         u2.user_permissions.add(*Permission.objects.filter(codename="view_document")) |         u2.user_permissions.add(*Permission.objects.filter(codename="view_document")) | ||||||
|  |  | ||||||
|         Document.objects.create(checksum="1", content="test 1", owner=u1) |         d1 = Document.objects.create(checksum="1", content="test 1", owner=u1) | ||||||
|         d2 = Document.objects.create(checksum="2", content="test 2", owner=u2) |         d2 = Document.objects.create(checksum="2", content="test 2", owner=u2) | ||||||
|         d3 = Document.objects.create(checksum="3", content="test 3", owner=u2) |         d3 = Document.objects.create(checksum="3", content="test 3", owner=u2) | ||||||
|         Document.objects.create(checksum="4", content="test 4") |         Document.objects.create(checksum="4", content="test 4") | ||||||
| @@ -993,9 +993,10 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase): | |||||||
|  |  | ||||||
|         assign_perm("view_document", u1, d2) |         assign_perm("view_document", u1, d2) | ||||||
|         assign_perm("view_document", u1, d3) |         assign_perm("view_document", u1, d3) | ||||||
|  |         assign_perm("view_document", u2, d1) | ||||||
|  |  | ||||||
|         with AsyncWriter(index.open_index()) as writer: |         with AsyncWriter(index.open_index()) as writer: | ||||||
|             for doc in [d2, d3]: |             for doc in [d1, d2, d3]: | ||||||
|                 index.update_document(writer, doc) |                 index.update_document(writer, doc) | ||||||
|  |  | ||||||
|         self.client.force_authenticate(user=u1) |         self.client.force_authenticate(user=u1) | ||||||
| @@ -1011,6 +1012,8 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase): | |||||||
|         self.assertEqual(r.data["count"], 1) |         self.assertEqual(r.data["count"], 1) | ||||||
|         r = self.client.get("/api/documents/?query=test&owner__isnull=true") |         r = self.client.get("/api/documents/?query=test&owner__isnull=true") | ||||||
|         self.assertEqual(r.data["count"], 1) |         self.assertEqual(r.data["count"], 1) | ||||||
|  |         r = self.client.get(f"/api/documents/?query=test&shared_by__id={u1.id}") | ||||||
|  |         self.assertEqual(r.data["count"], 1) | ||||||
|  |  | ||||||
|     def test_search_sorting(self): |     def test_search_sorting(self): | ||||||
|         u1 = User.objects.create_user("user1") |         u1 = User.objects.create_user("user1") | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ msgid "" | |||||||
| msgstr "" | msgstr "" | ||||||
| "Project-Id-Version: paperless-ngx\n" | "Project-Id-Version: paperless-ngx\n" | ||||||
| "Report-Msgid-Bugs-To: \n" | "Report-Msgid-Bugs-To: \n" | ||||||
| "POT-Creation-Date: 2023-12-05 08:26-0800\n" | "POT-Creation-Date: 2023-12-09 10:53-0800\n" | ||||||
| "PO-Revision-Date: 2022-02-17 04:17\n" | "PO-Revision-Date: 2022-02-17 04:17\n" | ||||||
| "Last-Translator: \n" | "Last-Translator: \n" | ||||||
| "Language-Team: English\n" | "Language-Team: English\n" | ||||||
| @@ -21,7 +21,7 @@ msgstr "" | |||||||
| msgid "Documents" | msgid "Documents" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:36 documents/models.py:734 | #: documents/models.py:36 documents/models.py:736 | ||||||
| msgid "owner" | msgid "owner" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @@ -53,7 +53,7 @@ msgstr "" | |||||||
| msgid "Automatic" | msgid "Automatic" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:62 documents/models.py:402 documents/models.py:895 | #: documents/models.py:62 documents/models.py:402 documents/models.py:897 | ||||||
| #: paperless_mail/models.py:18 paperless_mail/models.py:93 | #: paperless_mail/models.py:18 paperless_mail/models.py:93 | ||||||
| msgid "name" | msgid "name" | ||||||
| msgstr "" | msgstr "" | ||||||
| @@ -132,7 +132,7 @@ msgstr "" | |||||||
| msgid "title" | msgid "title" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:171 documents/models.py:648 | #: documents/models.py:171 documents/models.py:650 | ||||||
| msgid "content" | msgid "content" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @@ -162,8 +162,8 @@ msgstr "" | |||||||
| msgid "The checksum of the archived document." | msgid "The checksum of the archived document." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:205 documents/models.py:385 documents/models.py:654 | #: documents/models.py:205 documents/models.py:385 documents/models.py:656 | ||||||
| #: documents/models.py:692 documents/models.py:762 documents/models.py:799 | #: documents/models.py:694 documents/models.py:764 documents/models.py:801 | ||||||
| msgid "created" | msgid "created" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @@ -211,7 +211,7 @@ msgstr "" | |||||||
| msgid "The position of this document in your physical document archive." | msgid "The position of this document in your physical document archive." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:279 documents/models.py:665 documents/models.py:719 | #: documents/models.py:279 documents/models.py:667 documents/models.py:721 | ||||||
| msgid "document" | msgid "document" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @@ -259,7 +259,7 @@ msgstr "" | |||||||
| msgid "logs" | msgid "logs" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:399 documents/models.py:464 | #: documents/models.py:399 documents/models.py:466 | ||||||
| msgid "saved view" | msgid "saved view" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @@ -427,298 +427,306 @@ msgstr "" | |||||||
| msgid "does not have owner in" | msgid "does not have owner in" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:467 | #: documents/models.py:458 | ||||||
| msgid "rule type" | msgid "has custom field value" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: documents/models.py:459 | ||||||
|  | msgid "is shared by me" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:469 | #: documents/models.py:469 | ||||||
|  | msgid "rule type" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: documents/models.py:471 | ||||||
| msgid "value" | msgid "value" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:472 | #: documents/models.py:474 | ||||||
| msgid "filter rule" | msgid "filter rule" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:473 | #: documents/models.py:475 | ||||||
| msgid "filter rules" | msgid "filter rules" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:584 | #: documents/models.py:586 | ||||||
| msgid "Task ID" | msgid "Task ID" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:585 | #: documents/models.py:587 | ||||||
| msgid "Celery ID for the Task that was run" | msgid "Celery ID for the Task that was run" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:590 | #: documents/models.py:592 | ||||||
| msgid "Acknowledged" | msgid "Acknowledged" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:591 | #: documents/models.py:593 | ||||||
| msgid "If the task is acknowledged via the frontend or API" | msgid "If the task is acknowledged via the frontend or API" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:597 | #: documents/models.py:599 | ||||||
| msgid "Task Filename" | msgid "Task Filename" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:598 | #: documents/models.py:600 | ||||||
| msgid "Name of the file which the Task was run for" | msgid "Name of the file which the Task was run for" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:604 | #: documents/models.py:606 | ||||||
| msgid "Task Name" | msgid "Task Name" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:605 | #: documents/models.py:607 | ||||||
| msgid "Name of the Task which was run" | msgid "Name of the Task which was run" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:612 | #: documents/models.py:614 | ||||||
| msgid "Task State" | msgid "Task State" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:613 | #: documents/models.py:615 | ||||||
| msgid "Current state of the task being run" | msgid "Current state of the task being run" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:618 | #: documents/models.py:620 | ||||||
| msgid "Created DateTime" | msgid "Created DateTime" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:619 | #: documents/models.py:621 | ||||||
| msgid "Datetime field when the task result was created in UTC" | msgid "Datetime field when the task result was created in UTC" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:624 | #: documents/models.py:626 | ||||||
| msgid "Started DateTime" | msgid "Started DateTime" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:625 | #: documents/models.py:627 | ||||||
| msgid "Datetime field when the task was started in UTC" | msgid "Datetime field when the task was started in UTC" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:630 | #: documents/models.py:632 | ||||||
| msgid "Completed DateTime" | msgid "Completed DateTime" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:631 | #: documents/models.py:633 | ||||||
| msgid "Datetime field when the task was completed in UTC" | msgid "Datetime field when the task was completed in UTC" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:636 | #: documents/models.py:638 | ||||||
| msgid "Result Data" | msgid "Result Data" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:638 | #: documents/models.py:640 | ||||||
| msgid "The data returned by the task" | msgid "The data returned by the task" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:650 | #: documents/models.py:652 | ||||||
| msgid "Note for the document" | msgid "Note for the document" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:674 | #: documents/models.py:676 | ||||||
| msgid "user" | msgid "user" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:679 | #: documents/models.py:681 | ||||||
| msgid "note" | msgid "note" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:680 | #: documents/models.py:682 | ||||||
| msgid "notes" | msgid "notes" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:688 | #: documents/models.py:690 | ||||||
| msgid "Archive" | msgid "Archive" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:689 | #: documents/models.py:691 | ||||||
| msgid "Original" | msgid "Original" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:700 | #: documents/models.py:702 | ||||||
| msgid "expiration" | msgid "expiration" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:707 | #: documents/models.py:709 | ||||||
| msgid "slug" | msgid "slug" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:739 | #: documents/models.py:741 | ||||||
| msgid "share link" | msgid "share link" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:740 | #: documents/models.py:742 | ||||||
| msgid "share links" | msgid "share links" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:752 | #: documents/models.py:754 | ||||||
| msgid "String" | msgid "String" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:753 | #: documents/models.py:755 | ||||||
| msgid "URL" | msgid "URL" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:754 | #: documents/models.py:756 | ||||||
| msgid "Date" | msgid "Date" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:755 | #: documents/models.py:757 | ||||||
| msgid "Boolean" | msgid "Boolean" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:756 | #: documents/models.py:758 | ||||||
| msgid "Integer" | msgid "Integer" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:757 | #: documents/models.py:759 | ||||||
| msgid "Float" | msgid "Float" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:758 | #: documents/models.py:760 | ||||||
| msgid "Monetary" | msgid "Monetary" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:759 | #: documents/models.py:761 | ||||||
| msgid "Document Link" | msgid "Document Link" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:771 | #: documents/models.py:773 | ||||||
| msgid "data type" | msgid "data type" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:779 | #: documents/models.py:781 | ||||||
| msgid "custom field" | msgid "custom field" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:780 | #: documents/models.py:782 | ||||||
| msgid "custom fields" | msgid "custom fields" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:842 | #: documents/models.py:844 | ||||||
| msgid "custom field instance" | msgid "custom field instance" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:843 | #: documents/models.py:845 | ||||||
| msgid "custom field instances" | msgid "custom field instances" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:891 | #: documents/models.py:893 | ||||||
| msgid "Consume Folder" | msgid "Consume Folder" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:892 | #: documents/models.py:894 | ||||||
| msgid "Api Upload" | msgid "Api Upload" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:893 | #: documents/models.py:895 | ||||||
| msgid "Mail Fetch" | msgid "Mail Fetch" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:897 paperless_mail/models.py:95 | #: documents/models.py:899 paperless_mail/models.py:95 | ||||||
| msgid "order" | msgid "order" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:906 | #: documents/models.py:908 | ||||||
| msgid "filter path" | msgid "filter path" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:911 | #: documents/models.py:913 | ||||||
| msgid "" | msgid "" | ||||||
| "Only consume documents with a path that matches this if specified. Wildcards " | "Only consume documents with a path that matches this if specified. Wildcards " | ||||||
| "specified as * are allowed. Case insensitive." | "specified as * are allowed. Case insensitive." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:918 | #: documents/models.py:920 | ||||||
| msgid "filter filename" | msgid "filter filename" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:923 paperless_mail/models.py:148 | #: documents/models.py:925 paperless_mail/models.py:148 | ||||||
| msgid "" | msgid "" | ||||||
| "Only consume documents which entirely match this filename if specified. " | "Only consume documents which entirely match this filename if specified. " | ||||||
| "Wildcards such as *.pdf or *invoice* are allowed. Case insensitive." | "Wildcards such as *.pdf or *invoice* are allowed. Case insensitive." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:934 | #: documents/models.py:936 | ||||||
| msgid "filter documents from this mail rule" | msgid "filter documents from this mail rule" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:938 | #: documents/models.py:940 | ||||||
| msgid "assign title" | msgid "assign title" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:943 | #: documents/models.py:945 | ||||||
| msgid "" | msgid "" | ||||||
| "Assign a document title, can include some placeholders, see documentation." | "Assign a document title, can include some placeholders, see documentation." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:951 paperless_mail/models.py:216 | #: documents/models.py:953 paperless_mail/models.py:216 | ||||||
| msgid "assign this tag" | msgid "assign this tag" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:959 paperless_mail/models.py:224 | #: documents/models.py:961 paperless_mail/models.py:224 | ||||||
| msgid "assign this document type" | msgid "assign this document type" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:967 paperless_mail/models.py:238 | #: documents/models.py:969 paperless_mail/models.py:238 | ||||||
| msgid "assign this correspondent" | msgid "assign this correspondent" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:975 | #: documents/models.py:977 | ||||||
| msgid "assign this storage path" | msgid "assign this storage path" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:984 | #: documents/models.py:986 | ||||||
| msgid "assign this owner" | msgid "assign this owner" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:991 | #: documents/models.py:993 | ||||||
| msgid "grant view permissions to these users" | msgid "grant view permissions to these users" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:998 | #: documents/models.py:1000 | ||||||
| msgid "grant view permissions to these groups" | msgid "grant view permissions to these groups" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:1005 | #: documents/models.py:1007 | ||||||
| msgid "grant change permissions to these users" | msgid "grant change permissions to these users" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:1012 | #: documents/models.py:1014 | ||||||
| msgid "grant change permissions to these groups" | msgid "grant change permissions to these groups" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:1019 | #: documents/models.py:1021 | ||||||
| msgid "assign these custom fields" | msgid "assign these custom fields" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:1023 | #: documents/models.py:1025 | ||||||
| msgid "consumption template" | msgid "consumption template" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/models.py:1024 | #: documents/models.py:1026 | ||||||
| msgid "consumption templates" | msgid "consumption templates" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/serialisers.py:102 | #: documents/serialisers.py:105 | ||||||
| #, python-format | #, python-format | ||||||
| msgid "Invalid regular expression: %(error)s" | msgid "Invalid regular expression: %(error)s" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/serialisers.py:377 | #: documents/serialisers.py:399 | ||||||
| msgid "Invalid color." | msgid "Invalid color." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/serialisers.py:842 | #: documents/serialisers.py:865 | ||||||
| #, python-format | #, python-format | ||||||
| msgid "File type %(type)s not supported" | msgid "File type %(type)s not supported" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: documents/serialisers.py:939 | #: documents/serialisers.py:962 | ||||||
| msgid "Invalid variable detected." | msgid "Invalid variable detected." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 shamoon
					shamoon