Proper data type enum

This commit is contained in:
shamoon 2024-04-03 00:07:49 -07:00
parent 117b0b44b3
commit 8cbb15bd90
5 changed files with 91 additions and 60 deletions

View File

@ -30,10 +30,10 @@
(click)="primaryAction(type, item); $event.stopPropagation()" (click)="primaryAction(type, item); $event.stopPropagation()"
[disabled]="disablePrimaryButton(type, item)" [disabled]="disablePrimaryButton(type, item)"
(mouseenter)="onButtonHover($event)"> (mouseenter)="onButtonHover($event)">
@if (type === 'document') { @if (type === DataType.Document) {
<i-bs width="1em" height="1em" name="pencil"></i-bs> <i-bs width="1em" height="1em" name="pencil"></i-bs>
<span>&nbsp;<ng-container i18n>Open</ng-container></span> <span>&nbsp;<ng-container i18n>Open</ng-container></span>
} @else if (type === 'workflow' || type === 'customField' || type === 'group' || type === 'user') { } @else if (type === DataType.Workflow || type === DataType.CustomField || type === DataType.Group || type === DataType.User) {
<i-bs width="1em" height="1em" name="pencil"></i-bs> <i-bs width="1em" height="1em" name="pencil"></i-bs>
<span>&nbsp;<ng-container i18n>Edit</ng-container></span> <span>&nbsp;<ng-container i18n>Edit</ng-container></span>
} @else { } @else {
@ -41,12 +41,12 @@
<span>&nbsp;<ng-container i18n>Filter documents</ng-container></span> <span>&nbsp;<ng-container i18n>Filter documents</ng-container></span>
} }
</button> </button>
@if (type !== 'workflow' && type !== 'customField' && type !== 'group' && type !== 'user') { @if (type !== DataType.Workflow && type !== DataType.CustomField && type !== DataType.Group && type !== DataType.User) {
<button #secondaryButton type="button" class="btn btn-sm btn-outline-primary d-flex" <button #secondaryButton type="button" class="btn btn-sm btn-outline-primary d-flex"
(click)="secondaryAction(type, item); $event.stopPropagation()" (click)="secondaryAction(type, item); $event.stopPropagation()"
[disabled]="disableSecondaryButton(type, item)" [disabled]="disableSecondaryButton(type, item)"
(mouseenter)="onButtonHover($event)"> (mouseenter)="onButtonHover($event)">
@if (type === 'document') { @if (type === DataType.Document) {
<i-bs width="1em" height="1em" name="download"></i-bs> <i-bs width="1em" height="1em" name="download"></i-bs>
<span>&nbsp;<ng-container i18n>Download</ng-container></span> <span>&nbsp;<ng-container i18n>Download</ng-container></span>
} @else { } @else {
@ -66,63 +66,63 @@
@if (searchResults?.documents.length) { @if (searchResults?.documents.length) {
<h6 class="dropdown-header" i18n="@@searchResults.documents">Documents</h6> <h6 class="dropdown-header" i18n="@@searchResults.documents">Documents</h6>
@for (document of searchResults.documents; track document.id) { @for (document of searchResults.documents; track document.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: document, nameProp: 'title', type: 'document', icon: 'file-text', date: document.added}"></ng-container> <ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: document, nameProp: 'title', type: DataType.Document, icon: 'file-text', date: document.added}"></ng-container>
} }
} }
@if (searchResults?.tags.length) { @if (searchResults?.tags.length) {
<h6 class="dropdown-header" i18n="@@searchResults.tags">Tags</h6> <h6 class="dropdown-header" i18n="@@searchResults.tags">Tags</h6>
@for (tag of searchResults.tags; track tag.id) { @for (tag of searchResults.tags; track tag.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: tag, nameProp: 'name', type: 'tag', icon: 'tag'}"></ng-container> <ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: tag, nameProp: 'name', type: DataType.Tag, icon: 'tag'}"></ng-container>
} }
} }
@if (searchResults?.correspondents.length) { @if (searchResults?.correspondents.length) {
<h6 class="dropdown-header" i18n="@@searchResults.correspondents">Correspondents</h6> <h6 class="dropdown-header" i18n="@@searchResults.correspondents">Correspondents</h6>
@for (correspondent of searchResults.correspondents; track correspondent.id) { @for (correspondent of searchResults.correspondents; track correspondent.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: correspondent, nameProp: 'name', type: 'correspondent', icon: 'person'}"></ng-container> <ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: correspondent, nameProp: 'name', type: DataType.Correspondent, icon: 'person'}"></ng-container>
} }
} }
@if (searchResults?.document_types.length) { @if (searchResults?.document_types.length) {
<h6 class="dropdown-header" i18n="@@searchResults.documentTypes">Document types</h6> <h6 class="dropdown-header" i18n="@@searchResults.documentTypes">Document types</h6>
@for (documentType of searchResults.document_types; track documentType.id) { @for (documentType of searchResults.document_types; track documentType.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: documentType, nameProp: 'name', type: 'documentType', icon: 'file-earmark'}"></ng-container> <ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: documentType, nameProp: 'name', type: DataType.DocumentType, icon: 'file-earmark'}"></ng-container>
} }
} }
@if (searchResults?.storage_paths.length) { @if (searchResults?.storage_paths.length) {
<h6 class="dropdown-header" i18n="@@searchResults.storagePaths">Storage paths</h6> <h6 class="dropdown-header" i18n="@@searchResults.storagePaths">Storage paths</h6>
@for (storagePath of searchResults.storage_paths; track storagePath.id) { @for (storagePath of searchResults.storage_paths; track storagePath.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: storagePath, nameProp: 'name', type: 'storagePath', icon: 'folder'}"></ng-container> <ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: storagePath, nameProp: 'name', type: DataType.StoragePath, icon: 'folder'}"></ng-container>
} }
} }
@if (searchResults?.users.length) { @if (searchResults?.users.length) {
<h6 class="dropdown-header" i18n="@@searchResults.users">Users</h6> <h6 class="dropdown-header" i18n="@@searchResults.users">Users</h6>
@for (user of searchResults.users; track user.id) { @for (user of searchResults.users; track user.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: user, nameProp: 'username', type: 'user', icon: 'person-square'}"></ng-container> <ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: user, nameProp: 'username', type: DataType.User, icon: 'person-square'}"></ng-container>
} }
} }
@if (searchResults?.groups.length) { @if (searchResults?.groups.length) {
<h6 class="dropdown-header" i18n="@@searchResults.groups">Groups</h6> <h6 class="dropdown-header" i18n="@@searchResults.groups">Groups</h6>
@for (group of searchResults.groups; track group.id) { @for (group of searchResults.groups; track group.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: group, nameProp: 'name', type: 'group', icon: 'people'}"></ng-container> <ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: group, nameProp: 'name', type: DataType.Group, icon: 'people'}"></ng-container>
} }
} }
@if (searchResults?.custom_fields.length) { @if (searchResults?.custom_fields.length) {
<h6 class="dropdown-header" i18n="@@searchResults.customFields">Custom fields</h6> <h6 class="dropdown-header" i18n="@@searchResults.customFields">Custom fields</h6>
@for (customField of searchResults.custom_fields; track customField.id) { @for (customField of searchResults.custom_fields; track customField.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: customField, nameProp: 'name', type: 'customField', icon: 'ui-radios'}"></ng-container> <ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: customField, nameProp: 'name', type: DataType.CustomField, icon: 'ui-radios'}"></ng-container>
} }
} }
@if (searchResults?.workflows.length) { @if (searchResults?.workflows.length) {
<h6 class="dropdown-header" i18n="@@searchResults.workflows">Workflows</h6> <h6 class="dropdown-header" i18n="@@searchResults.workflows">Workflows</h6>
@for (workflow of searchResults.workflows; track workflow.id) { @for (workflow of searchResults.workflows; track workflow.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: workflow, nameProp: 'name', type: 'workflow', icon: 'boxes'}"></ng-container> <ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: workflow, nameProp: 'name', type: DataType.Workflow, icon: 'boxes'}"></ng-container>
} }
} }
} }

View File

@ -34,6 +34,7 @@ import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-
import { WorkflowEditDialogComponent } from '../../common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component' import { WorkflowEditDialogComponent } from '../../common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component'
import { ElementRef } from '@angular/core' import { ElementRef } from '@angular/core'
import { ToastService } from 'src/app/services/toast.service' import { ToastService } from 'src/app/services/toast.service'
import { DataType } from 'src/app/data/datatype'
const searchResults = { const searchResults = {
total: 11, total: 11,
@ -229,55 +230,55 @@ describe('GlobalSearchComponent', () => {
let modal: NgbModalRef let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
component.primaryAction('document', object) component.primaryAction(DataType.Document, object)
expect(routerSpy).toHaveBeenCalledWith(['/documents', object.id]) expect(routerSpy).toHaveBeenCalledWith(['/documents', object.id])
component.primaryAction('correspondent', object) component.primaryAction(DataType.Correspondent, object)
expect(qfSpy).toHaveBeenCalledWith([ expect(qfSpy).toHaveBeenCalledWith([
{ rule_type: FILTER_HAS_CORRESPONDENT_ANY, value: object.id.toString() }, { rule_type: FILTER_HAS_CORRESPONDENT_ANY, value: object.id.toString() },
]) ])
component.primaryAction('documentType', object) component.primaryAction(DataType.DocumentType, object)
expect(qfSpy).toHaveBeenCalledWith([ expect(qfSpy).toHaveBeenCalledWith([
{ rule_type: FILTER_HAS_DOCUMENT_TYPE_ANY, value: object.id.toString() }, { rule_type: FILTER_HAS_DOCUMENT_TYPE_ANY, value: object.id.toString() },
]) ])
component.primaryAction('storagePath', object) component.primaryAction(DataType.StoragePath, object)
expect(qfSpy).toHaveBeenCalledWith([ expect(qfSpy).toHaveBeenCalledWith([
{ rule_type: FILTER_HAS_STORAGE_PATH_ANY, value: object.id.toString() }, { rule_type: FILTER_HAS_STORAGE_PATH_ANY, value: object.id.toString() },
]) ])
component.primaryAction('tag', object) component.primaryAction(DataType.Tag, object)
expect(qfSpy).toHaveBeenCalledWith([ expect(qfSpy).toHaveBeenCalledWith([
{ rule_type: FILTER_HAS_ANY_TAG, value: object.id.toString() }, { rule_type: FILTER_HAS_ANY_TAG, value: object.id.toString() },
]) ])
component.primaryAction('user', object) component.primaryAction(DataType.User, object)
expect(modalSpy).toHaveBeenCalledWith(UserEditDialogComponent, { expect(modalSpy).toHaveBeenCalledWith(UserEditDialogComponent, {
size: 'lg', size: 'lg',
}) })
component.primaryAction('group', object) component.primaryAction(DataType.Group, object)
expect(modalSpy).toHaveBeenCalledWith(GroupEditDialogComponent, { expect(modalSpy).toHaveBeenCalledWith(GroupEditDialogComponent, {
size: 'lg', size: 'lg',
}) })
component.primaryAction('mailAccount', object) component.primaryAction(DataType.MailAccount, object)
expect(modalSpy).toHaveBeenCalledWith(MailAccountEditDialogComponent, { expect(modalSpy).toHaveBeenCalledWith(MailAccountEditDialogComponent, {
size: 'xl', size: 'xl',
}) })
component.primaryAction('mailRule', object) component.primaryAction(DataType.MailRule, object)
expect(modalSpy).toHaveBeenCalledWith(MailRuleEditDialogComponent, { expect(modalSpy).toHaveBeenCalledWith(MailRuleEditDialogComponent, {
size: 'xl', size: 'xl',
}) })
component.primaryAction('customField', object) component.primaryAction(DataType.CustomField, object)
expect(modalSpy).toHaveBeenCalledWith(CustomFieldEditDialogComponent, { expect(modalSpy).toHaveBeenCalledWith(CustomFieldEditDialogComponent, {
size: 'md', size: 'md',
}) })
component.primaryAction('workflow', object) component.primaryAction(DataType.Workflow, object)
expect(modalSpy).toHaveBeenCalledWith(WorkflowEditDialogComponent, { expect(modalSpy).toHaveBeenCalledWith(WorkflowEditDialogComponent, {
size: 'xl', size: 'xl',
}) })
@ -307,22 +308,28 @@ describe('GlobalSearchComponent', () => {
let modal: NgbModalRef let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
component.secondaryAction('correspondent', correspondent) component.secondaryAction(DataType.Correspondent, correspondent)
expect(modalSpy).toHaveBeenCalledWith(CorrespondentEditDialogComponent, { expect(modalSpy).toHaveBeenCalledWith(CorrespondentEditDialogComponent, {
size: 'md', size: 'md',
}) })
component.secondaryAction('documentType', searchResults.document_types[0]) component.secondaryAction(
DataType.DocumentType,
searchResults.document_types[0]
)
expect(modalSpy).toHaveBeenCalledWith(CorrespondentEditDialogComponent, { expect(modalSpy).toHaveBeenCalledWith(CorrespondentEditDialogComponent, {
size: 'md', size: 'md',
}) })
component.secondaryAction('storagePath', searchResults.storage_paths[0]) component.secondaryAction(
DataType.StoragePath,
searchResults.storage_paths[0]
)
expect(modalSpy).toHaveBeenCalledWith(CorrespondentEditDialogComponent, { expect(modalSpy).toHaveBeenCalledWith(CorrespondentEditDialogComponent, {
size: 'md', size: 'md',
}) })
component.secondaryAction('tag', searchResults.tags[0]) component.secondaryAction(DataType.Tag, searchResults.tags[0])
expect(modalSpy).toHaveBeenCalledWith(CorrespondentEditDialogComponent, { expect(modalSpy).toHaveBeenCalledWith(CorrespondentEditDialogComponent, {
size: 'md', size: 'md',
}) })

View File

@ -15,6 +15,7 @@ import {
FILTER_HAS_STORAGE_PATH_ANY, FILTER_HAS_STORAGE_PATH_ANY,
FILTER_HAS_ANY_TAG, FILTER_HAS_ANY_TAG,
} from 'src/app/data/filter-rule-type' } from 'src/app/data/filter-rule-type'
import { DataType } from 'src/app/data/datatype'
import { ObjectWithId } from 'src/app/data/object-with-id' import { ObjectWithId } from 'src/app/data/object-with-id'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { import {
@ -45,6 +46,7 @@ import { WorkflowEditDialogComponent } from '../../common/edit-dialog/workflow-e
styleUrl: './global-search.component.scss', styleUrl: './global-search.component.scss',
}) })
export class GlobalSearchComponent { export class GlobalSearchComponent {
public DataType = DataType
public query: string public query: string
public queryDebounce: Subject<string> public queryDebounce: Subject<string>
public searchResults: GlobalSearchResult public searchResults: GlobalSearchResult
@ -130,41 +132,41 @@ export class GlobalSearchComponent {
let editDialogComponent: any let editDialogComponent: any
let size: string = 'md' let size: string = 'md'
switch (type) { switch (type) {
case 'document': case DataType.Document:
this.router.navigate(['/documents', object.id]) this.router.navigate(['/documents', object.id])
return return
case 'correspondent': case DataType.Correspondent:
filterRuleType = FILTER_HAS_CORRESPONDENT_ANY filterRuleType = FILTER_HAS_CORRESPONDENT_ANY
break break
case 'documentType': case DataType.DocumentType:
filterRuleType = FILTER_HAS_DOCUMENT_TYPE_ANY filterRuleType = FILTER_HAS_DOCUMENT_TYPE_ANY
break break
case 'storagePath': case DataType.StoragePath:
filterRuleType = FILTER_HAS_STORAGE_PATH_ANY filterRuleType = FILTER_HAS_STORAGE_PATH_ANY
break break
case 'tag': case DataType.Tag:
filterRuleType = FILTER_HAS_ANY_TAG filterRuleType = FILTER_HAS_ANY_TAG
break break
case 'user': case DataType.User:
editDialogComponent = UserEditDialogComponent editDialogComponent = UserEditDialogComponent
size = 'lg' size = 'lg'
break break
case 'group': case DataType.Group:
editDialogComponent = GroupEditDialogComponent editDialogComponent = GroupEditDialogComponent
size = 'lg' size = 'lg'
break break
case 'mailAccount': case DataType.MailAccount:
editDialogComponent = MailAccountEditDialogComponent editDialogComponent = MailAccountEditDialogComponent
size = 'xl' size = 'xl'
break break
case 'mailRule': case DataType.MailRule:
editDialogComponent = MailRuleEditDialogComponent editDialogComponent = MailRuleEditDialogComponent
size = 'xl' size = 'xl'
break break
case 'customField': case DataType.CustomField:
editDialogComponent = CustomFieldEditDialogComponent editDialogComponent = CustomFieldEditDialogComponent
break break
case 'workflow': case DataType.Workflow:
editDialogComponent = WorkflowEditDialogComponent editDialogComponent = WorkflowEditDialogComponent
size = 'xl' size = 'xl'
break break
@ -195,19 +197,19 @@ export class GlobalSearchComponent {
let editDialogComponent: any let editDialogComponent: any
let size: string = 'md' let size: string = 'md'
switch (type) { switch (type) {
case 'document': case DataType.Document:
window.open(this.documentService.getDownloadUrl(object.id)) window.open(this.documentService.getDownloadUrl(object.id))
break break
case 'correspondent': case DataType.Correspondent:
editDialogComponent = CorrespondentEditDialogComponent editDialogComponent = CorrespondentEditDialogComponent
break break
case 'documentType': case DataType.DocumentType:
editDialogComponent = DocumentTypeEditDialogComponent editDialogComponent = DocumentTypeEditDialogComponent
break break
case 'storagePath': case DataType.StoragePath:
editDialogComponent = StoragePathEditDialogComponent editDialogComponent = StoragePathEditDialogComponent
break break
case 'tag': case DataType.Tag:
editDialogComponent = TagEditDialogComponent editDialogComponent = TagEditDialogComponent
break break
} }
@ -287,8 +289,15 @@ export class GlobalSearchComponent {
} }
} }
public disablePrimaryButton(type: string, object: ObjectWithId): boolean { public disablePrimaryButton(type: DataType, object: ObjectWithId): boolean {
if (['workflow', 'customField', 'group', 'user'].includes(type)) { if (
[
DataType.Workflow,
DataType.CustomField,
DataType.Group,
DataType.User,
].includes(type)
) {
return !this.permissionsService.currentUserHasObjectPermissions( return !this.permissionsService.currentUserHasObjectPermissions(
PermissionAction.Change, PermissionAction.Change,
object object
@ -298,8 +307,8 @@ export class GlobalSearchComponent {
return false return false
} }
public disableSecondaryButton(type: string, object: ObjectWithId): boolean { public disableSecondaryButton(type: DataType, object: ObjectWithId): boolean {
if ('document' === type) { if (DataType.Document === type) {
return false return false
} }

View File

@ -0,0 +1,13 @@
export enum DataType {
Document = 'document',
Correspondent = 'correspondent',
DocumentType = 'document_type',
StoragePath = 'storage_path',
Tag = 'tag',
User = 'user',
Group = 'group',
MailAccount = 'mail_account',
MailRule = 'mail_rule',
CustomField = 'custom_field',
Workflow = 'workflow',
}

View File

@ -1,3 +1,5 @@
import { DataType } from './datatype'
// These correspond to src/documents/models.py and changes here require a DB migration (and vice versa) // These correspond to src/documents/models.py and changes here require a DB migration (and vice versa)
export const FILTER_TITLE = 0 export const FILTER_TITLE = 0
export const FILTER_CONTENT = 1 export const FILTER_CONTENT = 1
@ -74,57 +76,57 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [
id: FILTER_CORRESPONDENT, id: FILTER_CORRESPONDENT,
filtervar: 'correspondent__id', filtervar: 'correspondent__id',
isnull_filtervar: 'correspondent__isnull', isnull_filtervar: 'correspondent__isnull',
datatype: 'correspondent', datatype: DataType.Correspondent,
multi: false, multi: false,
}, },
{ {
id: FILTER_HAS_CORRESPONDENT_ANY, id: FILTER_HAS_CORRESPONDENT_ANY,
filtervar: 'correspondent__id__in', filtervar: 'correspondent__id__in',
datatype: 'correspondent', datatype: DataType.Correspondent,
multi: true, multi: true,
}, },
{ {
id: FILTER_DOES_NOT_HAVE_CORRESPONDENT, id: FILTER_DOES_NOT_HAVE_CORRESPONDENT,
filtervar: 'correspondent__id__none', filtervar: 'correspondent__id__none',
datatype: 'correspondent', datatype: DataType.Correspondent,
multi: true, multi: true,
}, },
{ {
id: FILTER_STORAGE_PATH, id: FILTER_STORAGE_PATH,
filtervar: 'storage_path__id', filtervar: 'storage_path__id',
isnull_filtervar: 'storage_path__isnull', isnull_filtervar: 'storage_path__isnull',
datatype: 'storage_path', datatype: DataType.StoragePath,
multi: false, multi: false,
}, },
{ {
id: FILTER_HAS_STORAGE_PATH_ANY, id: FILTER_HAS_STORAGE_PATH_ANY,
filtervar: 'storage_path__id__in', filtervar: 'storage_path__id__in',
datatype: 'storage_path', datatype: DataType.StoragePath,
multi: true, multi: true,
}, },
{ {
id: FILTER_DOES_NOT_HAVE_STORAGE_PATH, id: FILTER_DOES_NOT_HAVE_STORAGE_PATH,
filtervar: 'storage_path__id__none', filtervar: 'storage_path__id__none',
datatype: 'storage_path', datatype: DataType.StoragePath,
multi: true, multi: true,
}, },
{ {
id: FILTER_DOCUMENT_TYPE, id: FILTER_DOCUMENT_TYPE,
filtervar: 'document_type__id', filtervar: 'document_type__id',
isnull_filtervar: 'document_type__isnull', isnull_filtervar: 'document_type__isnull',
datatype: 'document_type', datatype: DataType.DocumentType,
multi: false, multi: false,
}, },
{ {
id: FILTER_HAS_DOCUMENT_TYPE_ANY, id: FILTER_HAS_DOCUMENT_TYPE_ANY,
filtervar: 'document_type__id__in', filtervar: 'document_type__id__in',
datatype: 'document_type', datatype: DataType.DocumentType,
multi: true, multi: true,
}, },
{ {
id: FILTER_DOES_NOT_HAVE_DOCUMENT_TYPE, id: FILTER_DOES_NOT_HAVE_DOCUMENT_TYPE,
filtervar: 'document_type__id__none', filtervar: 'document_type__id__none',
datatype: 'document_type', datatype: DataType.DocumentType,
multi: true, multi: true,
}, },
{ {
@ -137,19 +139,19 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [
{ {
id: FILTER_HAS_TAGS_ALL, id: FILTER_HAS_TAGS_ALL,
filtervar: 'tags__id__all', filtervar: 'tags__id__all',
datatype: 'tag', datatype: DataType.Tag,
multi: true, multi: true,
}, },
{ {
id: FILTER_HAS_TAGS_ANY, id: FILTER_HAS_TAGS_ANY,
filtervar: 'tags__id__in', filtervar: 'tags__id__in',
datatype: 'tag', datatype: DataType.Tag,
multi: true, multi: true,
}, },
{ {
id: FILTER_DOES_NOT_HAVE_TAG, id: FILTER_DOES_NOT_HAVE_TAG,
filtervar: 'tags__id__none', filtervar: 'tags__id__none',
datatype: 'tag', datatype: DataType.Tag,
multi: true, multi: true,
}, },
{ {