Allow filtering on multiple correspondents, doctypes, storage paths

Preserve 'Not assigned' option
Fix default logical operator
Update frontend strings
Fix radio button name overlaps
Use include / exclude with multi-select for OneToOne objects
This commit is contained in:
shamoon 2023-03-10 14:53:32 -08:00
parent 4383550d98
commit 00e17f4d69
15 changed files with 483 additions and 188 deletions

View File

@ -770,7 +770,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/permissions-dialog/permissions-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/permissions-dialog/permissions-dialog.component.html</context>
<context context-type="linenumber">17</context> <context context-type="linenumber">21</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@ -1015,7 +1015,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/permissions-dialog/permissions-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/permissions-dialog/permissions-dialog.component.html</context>
<context context-type="linenumber">16</context> <context context-type="linenumber">20</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/select-dialog/select-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/select-dialog/select-dialog.component.html</context>
@ -1498,7 +1498,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
<context context-type="linenumber">213</context> <context context-type="linenumber">208</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context> <context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
@ -1787,25 +1787,39 @@
<context context-type="linenumber">18</context> <context context-type="linenumber">18</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6381578200008167206" datatype="html">
<source>Include</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/filterable-dropdown/filterable-dropdown.component.html</context>
<context context-type="linenumber">24</context>
</context-group>
</trans-unit>
<trans-unit id="5668077948386857930" datatype="html">
<source>Exclude</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/filterable-dropdown/filterable-dropdown.component.html</context>
<context context-type="linenumber">26</context>
</context-group>
</trans-unit>
<trans-unit id="4391289919356861627" datatype="html"> <trans-unit id="4391289919356861627" datatype="html">
<source>Apply</source> <source>Apply</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/filterable-dropdown/filterable-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/common/filterable-dropdown/filterable-dropdown.component.html</context>
<context context-type="linenumber">32</context> <context context-type="linenumber">40</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7780041345210191160" datatype="html"> <trans-unit id="7780041345210191160" datatype="html">
<source>Click again to exclude items.</source> <source>Click again to exclude items.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/filterable-dropdown/filterable-dropdown.component.html</context> <context context-type="sourcefile">src/app/components/common/filterable-dropdown/filterable-dropdown.component.html</context>
<context context-type="linenumber">38</context> <context context-type="linenumber">46</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7593728289020204896" datatype="html"> <trans-unit id="7593728289020204896" datatype="html">
<source>Not assigned</source> <source>Not assigned</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts</context> <context context-type="sourcefile">src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts</context>
<context context-type="linenumber">262</context> <context context-type="linenumber">321</context>
</context-group> </context-group>
<note priority="1" from="description">Filter drop down element to filter for documents with no correspondent/type/tag assigned</note> <note priority="1" from="description">Filter drop down element to filter for documents with no correspondent/type/tag assigned</note>
</trans-unit> </trans-unit>
@ -1974,6 +1988,45 @@
<context context-type="linenumber">12</context> <context context-type="linenumber">12</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3894950702316166331" datatype="html">
<source>Loading...</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/permissions-dialog/permissions-dialog.component.html</context>
<context context-type="linenumber">18</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/dashboard.component.html</context>
<context context-type="linenumber">26</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html</context>
<context context-type="linenumber">7</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">95</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">230</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">320</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">406</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
<context context-type="linenumber">19</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit id="8105421668262723483" datatype="html"> <trans-unit id="8105421668262723483" datatype="html">
<source>Set Permissions</source> <source>Set Permissions</source>
<context-group purpose="location"> <context-group purpose="location">
@ -1985,7 +2038,7 @@
<source>Note that permissions set here will override any existing permissions</source> <source>Note that permissions set here will override any existing permissions</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/permissions-dialog/permissions-dialog.component.ts</context> <context context-type="sourcefile">src/app/components/common/permissions-dialog/permissions-dialog.component.ts</context>
<context context-type="linenumber">41</context> <context context-type="linenumber">43</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8650499415827640724" datatype="html"> <trans-unit id="8650499415827640724" datatype="html">
@ -2049,41 +2102,6 @@
<context context-type="linenumber">20</context> <context context-type="linenumber">20</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3894950702316166331" datatype="html">
<source>Loading...</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/dashboard.component.html</context>
<context context-type="linenumber">26</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html</context>
<context context-type="linenumber">7</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">95</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">230</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">320</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">406</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
<context context-type="linenumber">19</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context>
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit id="1865646076514070962" datatype="html"> <trans-unit id="1865646076514070962" datatype="html">
<source>Hello <x id="PH" equiv-text="this.settingsService.displayName"/>, welcome to Paperless-ngx</source> <source>Hello <x id="PH" equiv-text="this.settingsService.displayName"/>, welcome to Paperless-ngx</source>
<context-group purpose="location"> <context-group purpose="location">
@ -2148,7 +2166,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">162</context> <context context-type="linenumber">172</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>
@ -2649,64 +2667,64 @@
<source>Error retrieving suggestions</source> <source>Error retrieving suggestions</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">319</context> <context context-type="linenumber">325</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="448882439049417053" datatype="html"> <trans-unit id="448882439049417053" datatype="html">
<source>Error saving document</source> <source>Error saving document</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">432</context> <context context-type="linenumber">439</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">476</context> <context context-type="linenumber">483</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9021887951960049161" datatype="html"> <trans-unit id="9021887951960049161" datatype="html">
<source>Confirm delete</source> <source>Confirm delete</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">505</context> <context context-type="linenumber">512</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
<context context-type="linenumber">209</context> <context context-type="linenumber">204</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5382975254277698192" datatype="html"> <trans-unit id="5382975254277698192" datatype="html">
<source>Do you really want to delete document &quot;<x id="PH" equiv-text="this.document.title"/>&quot;?</source> <source>Do you really want to delete document &quot;<x id="PH" equiv-text="this.document.title"/>&quot;?</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">506</context> <context context-type="linenumber">513</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6691075929777935948" datatype="html"> <trans-unit id="6691075929777935948" datatype="html">
<source>The files for this document will be deleted permanently. This operation cannot be undone.</source> <source>The files for this document will be deleted permanently. This operation cannot be undone.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">507</context> <context context-type="linenumber">514</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="719892092227206532" datatype="html"> <trans-unit id="719892092227206532" datatype="html">
<source>Delete document</source> <source>Delete document</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">509</context> <context context-type="linenumber">516</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1844801255494293730" datatype="html"> <trans-unit id="1844801255494293730" datatype="html">
<source>Error deleting document: <x id="PH" equiv-text="JSON.stringify(error)"/></source> <source>Error deleting document: <x id="PH" equiv-text="JSON.stringify(error)"/></source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">525</context> <context context-type="linenumber">532</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7362691899087997122" datatype="html"> <trans-unit id="7362691899087997122" datatype="html">
<source>Redo OCR confirm</source> <source>Redo OCR confirm</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">545</context> <context context-type="linenumber">552</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@ -2717,14 +2735,14 @@
<source>This operation will permanently redo OCR for this document.</source> <source>This operation will permanently redo OCR for this document.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">546</context> <context context-type="linenumber">553</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5641451190833696892" datatype="html"> <trans-unit id="5641451190833696892" datatype="html">
<source>This operation cannot be undone.</source> <source>This operation cannot be undone.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">547</context> <context context-type="linenumber">554</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@ -2755,7 +2773,7 @@
<source>Proceed</source> <source>Proceed</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">549</context> <context context-type="linenumber">556</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
@ -2782,7 +2800,7 @@
<source>Redo OCR operation will begin in the background. Close and re-open or reload this document after the operation has completed to see new content.</source> <source>Redo OCR operation 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-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">557</context> <context context-type="linenumber">564</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8008978164775353960" datatype="html"> <trans-unit id="8008978164775353960" datatype="html">
@ -2791,7 +2809,7 @@
)"/></source> )"/></source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">568,570</context> <context context-type="linenumber">575,577</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6857598786757174736" datatype="html"> <trans-unit id="6857598786757174736" datatype="html">
@ -3305,7 +3323,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">167</context> <context context-type="linenumber">177</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>
@ -3359,112 +3377,112 @@
<source>Correspondent: <x id="PH" equiv-text="this.correspondents.find((c) =&gt; c.id == +rule.value)?.name"/></source> <source>Correspondent: <x id="PH" equiv-text="this.correspondents.find((c) =&gt; c.id == +rule.value)?.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">98,100</context> <context context-type="linenumber">108,110</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">102</context> <context context-type="linenumber">112</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8705701325879965907" datatype="html"> <trans-unit id="8705701325879965907" datatype="html">
<source>Type: <x id="PH" equiv-text="this.documentTypes.find((dt) =&gt; dt.id == +rule.value)?.name"/></source> <source>Type: <x id="PH" equiv-text="this.documentTypes.find((dt) =&gt; dt.id == +rule.value)?.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">107,109</context> <context context-type="linenumber">117,119</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">111</context> <context context-type="linenumber">121</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8180755793012580465" datatype="html"> <trans-unit id="8180755793012580465" datatype="html">
<source>Tag: <x id="PH" equiv-text="this.tags.find((t) =&gt; t.id == +rule.value)?.name"/></source> <source>Tag: <x id="PH" equiv-text="this.tags.find((t) =&gt; t.id == +rule.value)?.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">115,117</context> <context context-type="linenumber">125,127</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">121</context> <context context-type="linenumber">131</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">125</context> <context context-type="linenumber">135</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">128</context> <context context-type="linenumber">138</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3100631071441658964" datatype="html"> <trans-unit id="3100631071441658964" datatype="html">
<source>Title &amp; content</source> <source>Title &amp; 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">165</context> <context context-type="linenumber">175</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">170</context> <context context-type="linenumber">180</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">176</context> <context context-type="linenumber">186</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">195</context> <context context-type="linenumber">205</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">199</context> <context context-type="linenumber">209</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">203</context> <context context-type="linenumber">213</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">207</context> <context context-type="linenumber">217</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">211</context> <context context-type="linenumber">221</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7210076240260527720" datatype="html"> <trans-unit id="7210076240260527720" datatype="html">
@ -3699,50 +3717,50 @@
<source>Error occurred while creating <x id="PH" equiv-text="this.typeName"/> : <x id="PH_1" equiv-text="activeModal.componentInstance.error"/>.</source> <source>Error occurred while creating <x id="PH" equiv-text="this.typeName"/> : <x id="PH_1" equiv-text="activeModal.componentInstance.error"/>.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
<context context-type="linenumber">142,144</context> <context context-type="linenumber">142</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
<context context-type="linenumber">155,157</context> <context context-type="linenumber">153,155</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="211408744872436427" datatype="html"> <trans-unit id="211408744872436427" datatype="html">
<source>Successfully created <x id="PH" equiv-text="this.typeName"/>.</source> <source>Successfully created <x id="PH" equiv-text="this.typeName"/>.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
<context context-type="linenumber">149</context> <context context-type="linenumber">147</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6151710751857751783" datatype="html"> <trans-unit id="6151710751857751783" datatype="html">
<source>Error occurred while saving <x id="PH" equiv-text="this.typeName"/> : <x id="PH_1" equiv-text="activeModal.componentInstance.error"/>.</source> <source>Error occurred while saving <x id="PH" equiv-text="this.typeName"/> : <x id="PH_1" equiv-text="activeModal.componentInstance.error"/>.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
<context context-type="linenumber">173,175</context> <context context-type="linenumber">171</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
<context context-type="linenumber">187,189</context> <context context-type="linenumber">182,184</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2541368547549828690" datatype="html"> <trans-unit id="2541368547549828690" datatype="html">
<source>Successfully updated <x id="PH" equiv-text="this.typeName"/>.</source> <source>Successfully updated <x id="PH" equiv-text="this.typeName"/>.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
<context context-type="linenumber">181</context> <context context-type="linenumber">176</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4012132330507560812" datatype="html"> <trans-unit id="4012132330507560812" datatype="html">
<source>Do you really want to delete the <x id="PH" equiv-text="this.typeName"/>?</source> <source>Do you really want to delete the <x id="PH" equiv-text="this.typeName"/>?</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
<context context-type="linenumber">196</context> <context context-type="linenumber">191</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8371896857609524947" datatype="html"> <trans-unit id="8371896857609524947" datatype="html">
<source>Associated documents will not be deleted.</source> <source>Associated documents will not be deleted.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
<context context-type="linenumber">211</context> <context context-type="linenumber">206</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5467489005440577210" datatype="html"> <trans-unit id="5467489005440577210" datatype="html">
@ -3751,7 +3769,7 @@
)"/></source> )"/></source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
<context context-type="linenumber">224,226</context> <context context-type="linenumber">219,221</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1685061484835793745" datatype="html"> <trans-unit id="1685061484835793745" datatype="html">

View File

@ -1,21 +1,29 @@
<div class="btn-group w-100" ngbDropdown role="group" (openChange)="dropdownOpenChange($event)" #dropdown="ngbDropdown"> <div class="btn-group w-100" ngbDropdown role="group" (openChange)="dropdownOpenChange($event)" #dropdown="ngbDropdown">
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="!editing && selectionModel.selectionSize() > 0 ? 'btn-primary' : 'btn-outline-primary'" [disabled]="disabled"> <button class="btn btn-sm" id="dropdown_{{name}}" ngbDropdownToggle [ngClass]="!editing && selectionModel.selectionSize() > 0 ? 'btn-primary' : 'btn-outline-primary'" [disabled]="disabled">
<svg class="toolbaricon" fill="currentColor"> <svg class="toolbaricon" fill="currentColor">
<use attr.xlink:href="assets/bootstrap-icons.svg#{{icon}}" /> <use attr.xlink:href="assets/bootstrap-icons.svg#{{icon}}" />
</svg> </svg>
<div class="d-none d-sm-inline">&nbsp;{{title}}</div> <div class="d-none d-sm-inline">&nbsp;{{title}}</div>
<ng-container *ngIf="!editing && selectionModel.totalCount > 0"> <ng-container *ngIf="!editing && selectionModel.totalCount > 0">
<app-clearable-badge [number]="multiple ? selectionModel.totalCount : undefined" [selected]="!multiple && selectionModel.selectionSize() > 0" (cleared)="reset()"></app-clearable-badge> <app-clearable-badge [number]="selectionModel.totalCount" [selected]="selectionModel.selectionSize() > 0" (cleared)="reset()"></app-clearable-badge>
</ng-container> </ng-container>
</button> </button>
<div class="dropdown-menu py-0 shadow" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}"> <div class="dropdown-menu py-0 shadow" ngbDropdownMenu attr.aria-labelledby="dropdown_{{name}}">
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
<div *ngIf="!editing && multiple" class="list-group-item d-flex"> <div *ngIf="!editing && manyToOne" class="list-group-item d-flex">
<div class="btn-group btn-group-xs flex-fill"> <div class="btn-group btn-group-xs flex-fill" role="group">
<input [(ngModel)]="selectionModel.logicalOperator" [disabled]="!operatorToggleEnabled" (ngModelChange)="selectionModel.toggleOperator()" type="radio" class="btn-check" id="logicalOperatorAnd" value="and"> <input [(ngModel)]="selectionModel.logicalOperator" [disabled]="!modifierToggleEnabled" (ngModelChange)="selectionModel.toggleOperator()" type="radio" class="btn-check" id="logicalOperatorAnd_{{name}}" name="logicalOperatorAnd_{{name}}" value="and">
<label class="btn btn-outline-primary" for="logicalOperatorAnd" i18n>All</label> <label class="btn btn-outline-primary" for="logicalOperatorAnd_{{name}}" i18n>All</label>
<input [(ngModel)]="selectionModel.logicalOperator" [disabled]="!operatorToggleEnabled" (ngModelChange)="selectionModel.toggleOperator()" type="radio" class="btn-check" id="logicalOperatorOr" value="or"> <input [(ngModel)]="selectionModel.logicalOperator" [disabled]="!modifierToggleEnabled" (ngModelChange)="selectionModel.toggleOperator()" type="radio" class="btn-check" id="logicalOperatorOr_{{name}}" name="logicalOperatorOr_{{name}}" value="or">
<label class="btn btn-outline-primary" for="logicalOperatorOr" i18n>Any</label> <label class="btn btn-outline-primary" for="logicalOperatorOr_{{name}}" i18n>Any</label>
</div>
</div>
<div *ngIf="!editing && !manyToOne" class="list-group-item d-flex">
<div class="btn-group btn-group-xs flex-fill" role="group">
<input [(ngModel)]="selectionModel.intersection" [disabled]="!modifierToggleEnabled" (ngModelChange)="selectionModel.toggleIntersection()" type="radio" class="btn-check" id="intersectionInclude_{{name}}" name="intersectionInclude_{{name}}" value="include">
<label class="btn btn-outline-primary" for="intersectionInclude_{{name}}" i18n>Include</label>
<input [(ngModel)]="selectionModel.intersection" [disabled]="!modifierToggleEnabled" (ngModelChange)="selectionModel.toggleIntersection()" type="radio" class="btn-check" id="intersectionExclude_{{name}}" name="intersectionExclude_{{name}}" value="exclude">
<label class="btn btn-outline-primary" for="intersectionExclude_{{name}}" i18n>Exclude</label>
</div> </div>
</div> </div>
<div class="list-group-item"> <div class="list-group-item">
@ -34,7 +42,7 @@
<use xlink:href="assets/bootstrap-icons.svg#arrow-right" /> <use xlink:href="assets/bootstrap-icons.svg#arrow-right" />
</svg> </svg>
</button> </button>
<div *ngIf="!editing && multiple" class="list-group-item list-group-item-note pt-1 pb-2"> <div *ngIf="!editing && manyToOne" class="list-group-item list-group-item-note pt-1 pb-2">
<small i18n>Click again to exclude items.</small> <small i18n>Click again to exclude items.</small>
</div> </div>
</div> </div>

View File

@ -18,12 +18,25 @@ export interface ChangedItems {
itemsToRemove: MatchingModel[] itemsToRemove: MatchingModel[]
} }
export enum LogicalOperator {
And = 'and',
Or = 'or',
}
export enum Intersection {
Include = 'include',
Exclude = 'exclude',
}
export class FilterableDropdownSelectionModel { export class FilterableDropdownSelectionModel {
changed = new Subject<FilterableDropdownSelectionModel>() changed = new Subject<FilterableDropdownSelectionModel>()
multiple = false manyToOne = false
private _logicalOperator = 'and' singleSelect = false
temporaryLogicalOperator = this._logicalOperator private _logicalOperator: LogicalOperator = LogicalOperator.And
temporaryLogicalOperator: LogicalOperator = this._logicalOperator
private _intersection: Intersection = Intersection.Include
temporaryIntersection: Intersection = this._intersection
items: MatchingModel[] = [] items: MatchingModel[] = []
@ -86,21 +99,36 @@ export class FilterableDropdownSelectionModel {
(state != ToggleableItemState.Selected && (state != ToggleableItemState.Selected &&
state != ToggleableItemState.Excluded) state != ToggleableItemState.Excluded)
) { ) {
if (this.manyToOne || this.singleSelect) {
this.temporarySelectionStates.set(id, ToggleableItemState.Selected) this.temporarySelectionStates.set(id, ToggleableItemState.Selected)
} else if (
state == ToggleableItemState.Selected ||
state == ToggleableItemState.Excluded
) {
this.temporarySelectionStates.delete(id)
}
if (!this.multiple) { if (this.singleSelect) {
for (let key of this.temporarySelectionStates.keys()) { for (let key of this.temporarySelectionStates.keys()) {
if (key != id) { if (key != id) {
this.temporarySelectionStates.delete(key) this.temporarySelectionStates.delete(key)
} }
} }
} }
} else {
let newState =
this.intersection == Intersection.Include
? ToggleableItemState.Selected
: ToggleableItemState.Excluded
if (!id) newState = ToggleableItemState.Selected
if (
state == ToggleableItemState.Excluded &&
this.intersection == Intersection.Exclude
) {
newState = ToggleableItemState.NotSelected
}
this.temporarySelectionStates.set(id, newState)
}
} else if (
state == ToggleableItemState.Selected ||
state == ToggleableItemState.Excluded
) {
this.temporarySelectionStates.delete(id)
}
if (!id) { if (!id) {
for (let key of this.temporarySelectionStates.keys()) { for (let key of this.temporarySelectionStates.keys()) {
@ -119,20 +147,37 @@ export class FilterableDropdownSelectionModel {
exclude(id: number, fireEvent: boolean = true) { exclude(id: number, fireEvent: boolean = true) {
let state = this.temporarySelectionStates.get(id) let state = this.temporarySelectionStates.get(id)
if (state == null || state != ToggleableItemState.Excluded) { if (id && (state == null || state != ToggleableItemState.Excluded)) {
this.temporarySelectionStates.set(id, ToggleableItemState.Excluded) this.temporaryLogicalOperator = this._logicalOperator = this.manyToOne
this.temporaryLogicalOperator = this._logicalOperator = 'and' ? LogicalOperator.And
} else if (state == ToggleableItemState.Excluded) { : LogicalOperator.Or
this.temporarySelectionStates.delete(id)
}
if (!this.multiple) { if (this.manyToOne || this.singleSelect) {
this.temporarySelectionStates.set(id, ToggleableItemState.Excluded)
if (this.singleSelect) {
for (let key of this.temporarySelectionStates.keys()) { for (let key of this.temporarySelectionStates.keys()) {
if (key != id) { if (key != id) {
this.temporarySelectionStates.delete(key) this.temporarySelectionStates.delete(key)
} }
} }
} }
} else {
let newState =
this.intersection == Intersection.Include
? ToggleableItemState.Selected
: ToggleableItemState.Excluded
if (
state == ToggleableItemState.Selected &&
this.intersection == Intersection.Include
) {
newState = ToggleableItemState.NotSelected
}
this.temporarySelectionStates.set(id, newState)
}
} else if (!id || state == ToggleableItemState.Excluded) {
this.temporarySelectionStates.delete(id)
}
if (fireEvent) { if (fireEvent) {
this.changed.next(this) this.changed.next(this)
@ -143,11 +188,11 @@ export class FilterableDropdownSelectionModel {
return this.selectionStates.get(id) || ToggleableItemState.NotSelected return this.selectionStates.get(id) || ToggleableItemState.NotSelected
} }
get logicalOperator(): string { get logicalOperator(): LogicalOperator {
return this.temporaryLogicalOperator return this.temporaryLogicalOperator
} }
set logicalOperator(operator: string) { set logicalOperator(operator: LogicalOperator) {
this.temporaryLogicalOperator = operator this.temporaryLogicalOperator = operator
} }
@ -155,6 +200,26 @@ export class FilterableDropdownSelectionModel {
this.changed.next(this) this.changed.next(this)
} }
get intersection(): Intersection {
return this.temporaryIntersection
}
set intersection(intersection: Intersection) {
this.temporaryIntersection = intersection
}
toggleIntersection() {
if (this.temporarySelectionStates.size === 0) return
let newState =
this.intersection == Intersection.Include
? ToggleableItemState.Selected
: ToggleableItemState.Excluded
this.temporarySelectionStates.forEach((state, key) => {
this.temporarySelectionStates.set(key, newState)
})
this.changed.next(this)
}
get(id: number) { get(id: number) {
return ( return (
this.temporarySelectionStates.get(id) || ToggleableItemState.NotSelected this.temporarySelectionStates.get(id) || ToggleableItemState.NotSelected
@ -171,7 +236,8 @@ export class FilterableDropdownSelectionModel {
clear(fireEvent = true) { clear(fireEvent = true) {
this.temporarySelectionStates.clear() this.temporarySelectionStates.clear()
this.temporaryLogicalOperator = this._logicalOperator = 'and' this.temporaryLogicalOperator = this._logicalOperator = LogicalOperator.And
this.temporaryIntersection = this._intersection = Intersection.Include
if (fireEvent) { if (fireEvent) {
this.changed.next(this) this.changed.next(this)
} }
@ -194,6 +260,8 @@ export class FilterableDropdownSelectionModel {
return true return true
} else if (this.temporaryLogicalOperator !== this._logicalOperator) { } else if (this.temporaryLogicalOperator !== this._logicalOperator) {
return true return true
} else if (this.temporaryIntersection !== this._intersection) {
return true
} else { } else {
return false return false
} }
@ -217,14 +285,19 @@ export class FilterableDropdownSelectionModel {
this.selectionStates.set(key, value) this.selectionStates.set(key, value)
}) })
this._logicalOperator = this.temporaryLogicalOperator this._logicalOperator = this.temporaryLogicalOperator
this._intersection = this.temporaryIntersection
} }
reset() { reset(complete: boolean = false) {
this.temporarySelectionStates.clear() this.temporarySelectionStates.clear()
if (complete) {
this.selectionStates.clear()
} else {
this.selectionStates.forEach((value, key) => { this.selectionStates.forEach((value, key) => {
this.temporarySelectionStates.set(key, value) this.temporarySelectionStates.set(key, value)
}) })
} }
}
diff(): ChangedItems { diff(): ChangedItems {
return { return {
@ -269,14 +342,16 @@ export class FilterableDropdownComponent {
return this._selectionModel.items return this._selectionModel.items
} }
_selectionModel = new FilterableDropdownSelectionModel() _selectionModel: FilterableDropdownSelectionModel =
new FilterableDropdownSelectionModel()
@Input() @Input()
set selectionModel(model: FilterableDropdownSelectionModel) { set selectionModel(model: FilterableDropdownSelectionModel) {
if (this.selectionModel) { if (this.selectionModel) {
this.selectionModel.changed.complete() this.selectionModel.changed.complete()
model.items = this.selectionModel.items model.items = this.selectionModel.items
model.multiple = this.selectionModel.multiple model.manyToOne = this.selectionModel.manyToOne
model.singleSelect = this.editing && !this.selectionModel.manyToOne
} }
model.changed.subscribe((updatedModel) => { model.changed.subscribe((updatedModel) => {
this.selectionModelChange.next(updatedModel) this.selectionModelChange.next(updatedModel)
@ -292,12 +367,12 @@ export class FilterableDropdownComponent {
selectionModelChange = new EventEmitter<FilterableDropdownSelectionModel>() selectionModelChange = new EventEmitter<FilterableDropdownSelectionModel>()
@Input() @Input()
set multiple(value: boolean) { set manyToOne(manyToOne: boolean) {
this.selectionModel.multiple = value this.selectionModel.manyToOne = manyToOne
} }
get multiple() { get manyToOne() {
return this.selectionModel.multiple return this.selectionModel.manyToOne
} }
@Input() @Input()
@ -327,16 +402,20 @@ export class FilterableDropdownComponent {
@Output() @Output()
opened = new EventEmitter() opened = new EventEmitter()
get operatorToggleEnabled(): boolean { get modifierToggleEnabled(): boolean {
return ( return this.manyToOne
this.selectionModel.selectionSize() > 1 && ? this.selectionModel.selectionSize() > 1 &&
this.selectionModel.getExcludedItems().length == 0 this.selectionModel.getExcludedItems().length == 0
) : !this.selectionModel.isNoneSelected()
} }
@Input() @Input()
documentCounts: SelectionDataItem[] documentCounts: SelectionDataItem[]
get name(): string {
return this.title ? this.title.replace(/\s/g, '_').toLowerCase() : null
}
getUpdatedDocumentCount(id: number) { getUpdatedDocumentCount(id: number) {
if (this.documentCounts) { if (this.documentCounts) {
return this.documentCounts.find((c) => c.id === id)?.document_count return this.documentCounts.find((c) => c.id === id)?.document_count
@ -346,7 +425,6 @@ export class FilterableDropdownComponent {
modelIsDirty: boolean = false modelIsDirty: boolean = false
constructor(private filterPipe: FilterPipe) { constructor(private filterPipe: FilterPipe) {
this.selectionModel = new FilterableDropdownSelectionModel()
this.selectionModelChange.subscribe((updatedModel) => { this.selectionModelChange.subscribe((updatedModel) => {
this.modelIsDirty = updatedModel.isDirty() this.modelIsDirty = updatedModel.isDirty()
}) })
@ -400,7 +478,7 @@ export class FilterableDropdownComponent {
} }
reset() { reset() {
this.selectionModel.reset() this.selectionModel.reset(true)
this.selectionModelChange.emit(this.selectionModel) this.selectionModelChange.emit(this.selectionModel)
} }
} }

View File

@ -30,7 +30,7 @@
[items]="tags" [items]="tags"
[disabled]="!userCanEditAll" [disabled]="!userCanEditAll"
[editing]="true" [editing]="true"
[multiple]="true" [manyToOne]="true"
[applyOnClose]="applyOnClose" [applyOnClose]="applyOnClose"
(opened)="openTagsDropdown()" (opened)="openTagsDropdown()"
[(selectionModel)]="tagSelectionModel" [(selectionModel)]="tagSelectionModel"

View File

@ -14,19 +14,19 @@
</div> </div>
</div> </div>
<div class="btn-group flex-fill" role="group"> <div class="btn-group flex-fill" role="group">
<input type="radio" class="btn-check" [(ngModel)]="displayMode" value="details" (ngModelChange)="saveDisplayMode()" id="displayModeDetails"> <input type="radio" class="btn-check" [(ngModel)]="displayMode" value="details" (ngModelChange)="saveDisplayMode()" id="displayModeDetails" name="displayModeDetails">
<label for="displayModeDetails" class="btn btn-outline-primary btn-sm"> <label for="displayModeDetails" class="btn btn-outline-primary btn-sm">
<svg class="toolbaricon" fill="currentColor"> <svg class="toolbaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#list-ul" /> <use xlink:href="assets/bootstrap-icons.svg#list-ul" />
</svg> </svg>
</label> </label>
<input type="radio" class="btn-check" [(ngModel)]="displayMode" value="smallCards" (ngModelChange)="saveDisplayMode()" id="displayModeSmall"> <input type="radio" class="btn-check" [(ngModel)]="displayMode" value="smallCards" (ngModelChange)="saveDisplayMode()" id="displayModeSmall" name="displayModeSmall">
<label for="displayModeSmall" class="btn btn-outline-primary btn-sm"> <label for="displayModeSmall" class="btn btn-outline-primary btn-sm">
<svg class="toolbaricon" fill="currentColor"> <svg class="toolbaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#grid" /> <use xlink:href="assets/bootstrap-icons.svg#grid" />
</svg> </svg>
</label> </label>
<input type="radio" class="btn-check" [(ngModel)]="displayMode" value="largeCards" (ngModelChange)="saveDisplayMode()" id="displayModeLarge"> <input type="radio" class="btn-check" [(ngModel)]="displayMode" value="largeCards" (ngModelChange)="saveDisplayMode()" id="displayModeLarge" name="displayModeLarge">
<label for="displayModeLarge" class="btn btn-outline-primary btn-sm"> <label for="displayModeLarge" class="btn btn-outline-primary btn-sm">
<svg class="toolbaricon" fill="currentColor"> <svg class="toolbaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#hdd-stack" /> <use xlink:href="assets/bootstrap-icons.svg#hdd-stack" />

View File

@ -27,7 +27,7 @@
<app-filterable-dropdown class="flex-fill" title="Tags" icon="tag-fill" i18n-title <app-filterable-dropdown class="flex-fill" title="Tags" icon="tag-fill" i18n-title
filterPlaceholder="Filter tags" i18n-filterPlaceholder filterPlaceholder="Filter tags" i18n-filterPlaceholder
[items]="tags" [items]="tags"
[multiple]="true" [manyToOne]="true"
[(selectionModel)]="tagSelectionModel" [(selectionModel)]="tagSelectionModel"
(selectionModelChange)="updateRules()" (selectionModelChange)="updateRules()"
(opened)="onTagsDropdownOpen()" (opened)="onTagsDropdownOpen()"

View File

@ -21,10 +21,10 @@ import {
FILTER_ADDED_AFTER, FILTER_ADDED_AFTER,
FILTER_ADDED_BEFORE, FILTER_ADDED_BEFORE,
FILTER_ASN, FILTER_ASN,
FILTER_CORRESPONDENT, FILTER_HAS_CORRESPONDENT_ANY,
FILTER_CREATED_AFTER, FILTER_CREATED_AFTER,
FILTER_CREATED_BEFORE, FILTER_CREATED_BEFORE,
FILTER_DOCUMENT_TYPE, FILTER_HAS_DOCUMENT_TYPE_ANY,
FILTER_FULLTEXT_MORELIKE, FILTER_FULLTEXT_MORELIKE,
FILTER_FULLTEXT_QUERY, FILTER_FULLTEXT_QUERY,
FILTER_HAS_ANY_TAG, FILTER_HAS_ANY_TAG,
@ -33,12 +33,22 @@ import {
FILTER_DOES_NOT_HAVE_TAG, FILTER_DOES_NOT_HAVE_TAG,
FILTER_TITLE, FILTER_TITLE,
FILTER_TITLE_CONTENT, FILTER_TITLE_CONTENT,
FILTER_STORAGE_PATH, FILTER_HAS_STORAGE_PATH_ANY,
FILTER_ASN_ISNULL, FILTER_ASN_ISNULL,
FILTER_ASN_GT, FILTER_ASN_GT,
FILTER_ASN_LT, FILTER_ASN_LT,
FILTER_DOES_NOT_HAVE_CORRESPONDENT,
FILTER_DOES_NOT_HAVE_DOCUMENT_TYPE,
FILTER_DOES_NOT_HAVE_STORAGE_PATH,
FILTER_DOCUMENT_TYPE,
FILTER_CORRESPONDENT,
FILTER_STORAGE_PATH,
} from 'src/app/data/filter-rule-type' } from 'src/app/data/filter-rule-type'
import { FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component' import {
FilterableDropdownSelectionModel,
Intersection,
LogicalOperator,
} from '../../common/filterable-dropdown/filterable-dropdown.component'
import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component' import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'
import { import {
DocumentService, DocumentService,
@ -93,7 +103,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
if (this.filterRules.length == 1) { if (this.filterRules.length == 1) {
let rule = this.filterRules[0] let rule = this.filterRules[0]
switch (this.filterRules[0].rule_type) { switch (this.filterRules[0].rule_type) {
case FILTER_CORRESPONDENT: case FILTER_HAS_CORRESPONDENT_ANY:
if (rule.value) { if (rule.value) {
return $localize`Correspondent: ${ return $localize`Correspondent: ${
this.correspondents.find((c) => c.id == +rule.value)?.name this.correspondents.find((c) => c.id == +rule.value)?.name
@ -102,7 +112,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
return $localize`Without correspondent` return $localize`Without correspondent`
} }
case FILTER_DOCUMENT_TYPE: case FILTER_HAS_DOCUMENT_TYPE_ANY:
if (rule.value) { if (rule.value) {
return $localize`Type: ${ return $localize`Type: ${
this.documentTypes.find((dt) => dt.id == +rule.value)?.name this.documentTypes.find((dt) => dt.id == +rule.value)?.name
@ -335,6 +345,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
this.dateAddedBefore = rule.value this.dateAddedBefore = rule.value
break break
case FILTER_HAS_TAGS_ALL: case FILTER_HAS_TAGS_ALL:
this.tagSelectionModel.logicalOperator = LogicalOperator.And
this.tagSelectionModel.set( this.tagSelectionModel.set(
rule.value ? +rule.value : null, rule.value ? +rule.value : null,
ToggleableItemState.Selected, ToggleableItemState.Selected,
@ -342,7 +353,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
) )
break break
case FILTER_HAS_TAGS_ANY: case FILTER_HAS_TAGS_ANY:
this.tagSelectionModel.logicalOperator = 'or' this.tagSelectionModel.logicalOperator = LogicalOperator.Or
this.tagSelectionModel.set( this.tagSelectionModel.set(
rule.value ? +rule.value : null, rule.value ? +rule.value : null,
ToggleableItemState.Selected, ToggleableItemState.Selected,
@ -360,26 +371,59 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
) )
break break
case FILTER_CORRESPONDENT: case FILTER_CORRESPONDENT:
case FILTER_HAS_CORRESPONDENT_ANY:
this.correspondentSelectionModel.logicalOperator = LogicalOperator.Or
this.correspondentSelectionModel.intersection = Intersection.Include
this.correspondentSelectionModel.set( this.correspondentSelectionModel.set(
rule.value ? +rule.value : null, rule.value ? +rule.value : null,
ToggleableItemState.Selected, ToggleableItemState.Selected,
false false
) )
break break
case FILTER_DOES_NOT_HAVE_CORRESPONDENT:
this.correspondentSelectionModel.intersection = Intersection.Exclude
this.correspondentSelectionModel.set(
rule.value ? +rule.value : null,
ToggleableItemState.Excluded,
false
)
break
case FILTER_DOCUMENT_TYPE: case FILTER_DOCUMENT_TYPE:
case FILTER_HAS_DOCUMENT_TYPE_ANY:
this.documentTypeSelectionModel.logicalOperator = LogicalOperator.Or
this.documentTypeSelectionModel.intersection = Intersection.Include
this.documentTypeSelectionModel.set( this.documentTypeSelectionModel.set(
rule.value ? +rule.value : null, rule.value ? +rule.value : null,
ToggleableItemState.Selected, ToggleableItemState.Selected,
false false
) )
break break
case FILTER_DOES_NOT_HAVE_DOCUMENT_TYPE:
this.documentTypeSelectionModel.intersection = Intersection.Exclude
this.documentTypeSelectionModel.set(
rule.value ? +rule.value : null,
ToggleableItemState.Excluded,
false
)
break
case FILTER_STORAGE_PATH: case FILTER_STORAGE_PATH:
case FILTER_HAS_STORAGE_PATH_ANY:
this.storagePathSelectionModel.logicalOperator = LogicalOperator.Or
this.storagePathSelectionModel.intersection = Intersection.Include
this.storagePathSelectionModel.set( this.storagePathSelectionModel.set(
rule.value ? +rule.value : null, rule.value ? +rule.value : null,
ToggleableItemState.Selected, ToggleableItemState.Selected,
false false
) )
break break
case FILTER_DOES_NOT_HAVE_STORAGE_PATH:
this.storagePathSelectionModel.intersection = Intersection.Exclude
this.storagePathSelectionModel.set(
rule.value ? +rule.value : null,
ToggleableItemState.Excluded,
false
)
break
case FILTER_ASN_ISNULL: case FILTER_ASN_ISNULL:
this.textFilterTarget = TEXT_FILTER_TARGET_ASN this.textFilterTarget = TEXT_FILTER_TARGET_ASN
this.textFilterModifier = this.textFilterModifier =
@ -469,7 +513,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
filterRules.push({ rule_type: FILTER_HAS_ANY_TAG, value: 'false' }) filterRules.push({ rule_type: FILTER_HAS_ANY_TAG, value: 'false' })
} else { } else {
const tagFilterType = const tagFilterType =
this.tagSelectionModel.logicalOperator == 'and' this.tagSelectionModel.logicalOperator == LogicalOperator.And
? FILTER_HAS_TAGS_ALL ? FILTER_HAS_TAGS_ALL
: FILTER_HAS_TAGS_ANY : FILTER_HAS_TAGS_ANY
this.tagSelectionModel this.tagSelectionModel
@ -491,28 +535,66 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
}) })
}) })
} }
if (this.correspondentSelectionModel.isNoneSelected()) {
filterRules.push({ rule_type: FILTER_CORRESPONDENT, value: null })
} else {
this.correspondentSelectionModel this.correspondentSelectionModel
.getSelectedItems() .getSelectedItems()
.forEach((correspondent) => { .forEach((correspondent) => {
filterRules.push({ filterRules.push({
rule_type: FILTER_CORRESPONDENT, rule_type: FILTER_HAS_CORRESPONDENT_ANY,
value: correspondent.id?.toString(), value: correspondent.id?.toString(),
}) })
}) })
this.correspondentSelectionModel
.getExcludedItems()
.forEach((correspondent) => {
filterRules.push({
rule_type: FILTER_DOES_NOT_HAVE_CORRESPONDENT,
value: correspondent.id?.toString(),
})
})
}
if (this.documentTypeSelectionModel.isNoneSelected()) {
filterRules.push({ rule_type: FILTER_DOCUMENT_TYPE, value: null })
} else {
this.documentTypeSelectionModel this.documentTypeSelectionModel
.getSelectedItems() .getSelectedItems()
.forEach((documentType) => { .forEach((documentType) => {
filterRules.push({ filterRules.push({
rule_type: FILTER_DOCUMENT_TYPE, rule_type: FILTER_HAS_DOCUMENT_TYPE_ANY,
value: documentType.id?.toString(), value: documentType.id?.toString(),
}) })
}) })
this.storagePathSelectionModel.getSelectedItems().forEach((storagePath) => { this.documentTypeSelectionModel
.getExcludedItems()
.forEach((documentType) => {
filterRules.push({ filterRules.push({
rule_type: FILTER_STORAGE_PATH, rule_type: FILTER_DOES_NOT_HAVE_DOCUMENT_TYPE,
value: documentType.id?.toString(),
})
})
}
if (this.storagePathSelectionModel.isNoneSelected()) {
filterRules.push({ rule_type: FILTER_STORAGE_PATH, value: null })
} else {
this.storagePathSelectionModel
.getSelectedItems()
.forEach((storagePath) => {
filterRules.push({
rule_type: FILTER_HAS_STORAGE_PATH_ANY,
value: storagePath.id?.toString(), value: storagePath.id?.toString(),
}) })
}) })
this.storagePathSelectionModel
.getExcludedItems()
.forEach((storagePath) => {
filterRules.push({
rule_type: FILTER_DOES_NOT_HAVE_STORAGE_PATH,
value: storagePath.id?.toString(),
})
})
}
if (this.dateCreatedBefore) { if (this.dateCreatedBefore) {
filterRules.push({ filterRules.push({
rule_type: FILTER_CREATED_BEFORE, rule_type: FILTER_CREATED_BEFORE,

View File

@ -1,6 +1,6 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { FILTER_CORRESPONDENT } from 'src/app/data/filter-rule-type' import { FILTER_HAS_CORRESPONDENT_ANY } from 'src/app/data/filter-rule-type'
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent' import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
@ -35,7 +35,7 @@ export class CorrespondentListComponent extends ManagementListComponent<Paperles
toastService, toastService,
documentListViewService, documentListViewService,
permissionsService, permissionsService,
FILTER_CORRESPONDENT, FILTER_HAS_CORRESPONDENT_ANY,
$localize`correspondent`, $localize`correspondent`,
$localize`correspondents`, $localize`correspondents`,
PermissionType.Correspondent, PermissionType.Correspondent,

View File

@ -1,6 +1,6 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { FILTER_DOCUMENT_TYPE } from 'src/app/data/filter-rule-type' import { FILTER_HAS_DOCUMENT_TYPE_ANY } from 'src/app/data/filter-rule-type'
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type' import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { import {
@ -32,7 +32,7 @@ export class DocumentTypeListComponent extends ManagementListComponent<Paperless
toastService, toastService,
documentListViewService, documentListViewService,
permissionsService, permissionsService,
FILTER_DOCUMENT_TYPE, FILTER_HAS_DOCUMENT_TYPE_ANY,
$localize`document type`, $localize`document type`,
$localize`document types`, $localize`document types`,
PermissionType.DocumentType, PermissionType.DocumentType,

View File

@ -1,6 +1,6 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { FILTER_STORAGE_PATH } from 'src/app/data/filter-rule-type' import { FILTER_HAS_STORAGE_PATH_ANY } from 'src/app/data/filter-rule-type'
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path' import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { import {
@ -32,7 +32,7 @@ export class StoragePathListComponent extends ManagementListComponent<PaperlessS
toastService, toastService,
documentListViewService, documentListViewService,
permissionsService, permissionsService,
FILTER_STORAGE_PATH, FILTER_HAS_STORAGE_PATH_ANY,
$localize`storage path`, $localize`storage path`,
$localize`storage paths`, $localize`storage paths`,
PermissionType.StoragePath, PermissionType.StoragePath,

View File

@ -8,8 +8,12 @@ export const FILTER_ASN_GT = 23
export const FILTER_ASN_LT = 24 export const FILTER_ASN_LT = 24
export const FILTER_CORRESPONDENT = 3 export const FILTER_CORRESPONDENT = 3
export const FILTER_HAS_CORRESPONDENT_ANY = 26
export const FILTER_DOES_NOT_HAVE_CORRESPONDENT = 27
export const FILTER_DOCUMENT_TYPE = 4 export const FILTER_DOCUMENT_TYPE = 4
export const FILTER_HAS_DOCUMENT_TYPE_ANY = 28
export const FILTER_DOES_NOT_HAVE_DOCUMENT_TYPE = 29
export const FILTER_IS_IN_INBOX = 5 export const FILTER_IS_IN_INBOX = 5
export const FILTER_HAS_TAGS_ALL = 6 export const FILTER_HAS_TAGS_ALL = 6
@ -18,6 +22,8 @@ export const FILTER_DOES_NOT_HAVE_TAG = 17
export const FILTER_HAS_TAGS_ANY = 22 export const FILTER_HAS_TAGS_ANY = 22
export const FILTER_STORAGE_PATH = 25 export const FILTER_STORAGE_PATH = 25
export const FILTER_HAS_STORAGE_PATH_ANY = 30
export const FILTER_DOES_NOT_HAVE_STORAGE_PATH = 31
export const FILTER_CREATED_BEFORE = 8 export const FILTER_CREATED_BEFORE = 8
export const FILTER_CREATED_AFTER = 9 export const FILTER_CREATED_AFTER = 9
@ -63,6 +69,18 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [
datatype: 'correspondent', datatype: 'correspondent',
multi: false, multi: false,
}, },
{
id: FILTER_HAS_CORRESPONDENT_ANY,
filtervar: 'correspondent__id__in',
datatype: 'correspondent',
multi: true,
},
{
id: FILTER_DOES_NOT_HAVE_CORRESPONDENT,
filtervar: 'correspondent__id__none',
datatype: 'correspondent',
multi: true,
},
{ {
id: FILTER_STORAGE_PATH, id: FILTER_STORAGE_PATH,
filtervar: 'storage_path__id', filtervar: 'storage_path__id',
@ -70,6 +88,18 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [
datatype: 'storage_path', datatype: 'storage_path',
multi: false, multi: false,
}, },
{
id: FILTER_HAS_STORAGE_PATH_ANY,
filtervar: 'storage_path__id__in',
datatype: 'storage_path',
multi: true,
},
{
id: FILTER_DOES_NOT_HAVE_STORAGE_PATH,
filtervar: 'storage_path__id__none',
datatype: 'storage_path',
multi: true,
},
{ {
id: FILTER_DOCUMENT_TYPE, id: FILTER_DOCUMENT_TYPE,
filtervar: 'document_type__id', filtervar: 'document_type__id',
@ -77,6 +107,18 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [
datatype: 'document_type', datatype: 'document_type',
multi: false, multi: false,
}, },
{
id: FILTER_HAS_DOCUMENT_TYPE_ANY,
filtervar: 'document_type__id__in',
datatype: 'document_type',
multi: true,
},
{
id: FILTER_DOES_NOT_HAVE_DOCUMENT_TYPE,
filtervar: 'document_type__id__none',
datatype: 'document_type',
multi: true,
},
{ {
id: FILTER_IS_IN_INBOX, id: FILTER_IS_IN_INBOX,
filtervar: 'is_in_inbox', filtervar: 'is_in_inbox',

View File

@ -86,12 +86,12 @@ export function queryParamsFromFilterRules(filterRules: FilterRule[]): Params {
let params = {} let params = {}
for (let rule of filterRules) { for (let rule of filterRules) {
let ruleType = FILTER_RULE_TYPES.find((t) => t.id == rule.rule_type) let ruleType = FILTER_RULE_TYPES.find((t) => t.id == rule.rule_type)
if (ruleType.multi) { if (ruleType.isnull_filtervar && rule.value == null) {
params[ruleType.isnull_filtervar] = 1
} else if (ruleType.multi) {
params[ruleType.filtervar] = params[ruleType.filtervar] params[ruleType.filtervar] = params[ruleType.filtervar]
? params[ruleType.filtervar] + ',' + rule.value ? params[ruleType.filtervar] + ',' + rule.value
: rule.value : rule.value
} else if (ruleType.isnull_filtervar && rule.value == null) {
params[ruleType.isnull_filtervar] = 1
} else { } else {
params[ruleType.filtervar] = rule.value params[ruleType.filtervar] = rule.value
if (ruleType.datatype == 'boolean') if (ruleType.datatype == 'boolean')

View File

@ -36,29 +36,30 @@ class DocumentTypeFilterSet(FilterSet):
fields = {"name": CHAR_KWARGS} fields = {"name": CHAR_KWARGS}
class TagsFilter(Filter): class ObjectFilter(Filter):
def __init__(self, exclude=False, in_list=False): def __init__(self, exclude=False, in_list=False, field_name=""):
super().__init__() super().__init__()
self.exclude = exclude self.exclude = exclude
self.in_list = in_list self.in_list = in_list
self.field_name = field_name
def filter(self, qs, value): def filter(self, qs, value):
if not value: if not value:
return qs return qs
try: try:
tag_ids = [int(x) for x in value.split(",")] object_ids = [int(x) for x in value.split(",")]
except ValueError: except ValueError:
return qs return qs
if self.in_list: if self.in_list:
qs = qs.filter(tags__id__in=tag_ids).distinct() qs = qs.filter(**{f"{self.field_name}__id__in": object_ids}).distinct()
else: else:
for tag_id in tag_ids: for obj_id in object_ids:
if self.exclude: if self.exclude:
qs = qs.exclude(tags__id=tag_id) qs = qs.exclude(**{f"{self.field_name}__id": obj_id})
else: else:
qs = qs.filter(tags__id=tag_id) qs = qs.filter(**{f"{self.field_name}__id": obj_id})
return qs return qs
@ -90,11 +91,17 @@ class DocumentFilterSet(FilterSet):
exclude=True, exclude=True,
) )
tags__id__all = TagsFilter() tags__id__all = ObjectFilter(field_name="tags")
tags__id__none = TagsFilter(exclude=True) tags__id__none = ObjectFilter(field_name="tags", exclude=True)
tags__id__in = TagsFilter(in_list=True) tags__id__in = ObjectFilter(field_name="tags", in_list=True)
correspondent__id__none = ObjectFilter(field_name="correspondent", exclude=True)
document_type__id__none = ObjectFilter(field_name="document_type", exclude=True)
storage_path__id__none = ObjectFilter(field_name="storage_path", exclude=True)
is_in_inbox = InboxFilter() is_in_inbox = InboxFilter()

View File

@ -0,0 +1,54 @@
# Generated by Django 4.1.5 on 2023-03-15 07:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("documents", "1033_alter_documenttype_options_alter_tag_options_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"),
],
verbose_name="rule type",
),
),
]

View File

@ -447,6 +447,12 @@ class SavedViewFilterRule(models.Model):
(23, _("ASN greater than")), (23, _("ASN greater than")),
(24, _("ASN less than")), (24, _("ASN less than")),
(25, _("storage path is")), (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")),
] ]
saved_view = models.ForeignKey( saved_view = models.ForeignKey(