Feature: consumption templates (#4196)

* Initial implementation of consumption templates

* Frontend implementation of consumption templates

Testing

* Support consumption template source

* order templates, automatically add permissions

* Support title assignment in consumption templates

* Refactoring, filters to and, show sources on list

Show sources on template list, update some translation strings

Make filters and

minor testing

* Update strings

* Only update django-multiselectfield

* Basic docs, document some methods

* Improve testing coverage, template multi-assignment merges
This commit is contained in:
shamoon
2023-09-22 16:53:13 -07:00
committed by GitHub
parent 86d223fd93
commit 9712ac109d
51 changed files with 3250 additions and 444 deletions

View File

@@ -1,6 +1,6 @@
import dataclasses
import datetime
import enum
from enum import IntEnum
from pathlib import Path
from typing import Optional
@@ -20,19 +20,70 @@ class DocumentMetadataOverrides:
correspondent_id: Optional[int] = None
document_type_id: Optional[int] = None
tag_ids: Optional[list[int]] = None
storage_path_id: Optional[int] = None
created: Optional[datetime.datetime] = None
asn: Optional[int] = None
owner_id: Optional[int] = None
view_users: Optional[list[int]] = None
view_groups: Optional[list[int]] = None
change_users: Optional[list[int]] = None
change_groups: Optional[list[int]] = None
def update(self, other: "DocumentMetadataOverrides") -> "DocumentMetadataOverrides":
"""
Merges two DocumentMetadataOverrides objects such that object B's overrides
are only applied if the property is empty in object A or merged if multiple
are accepted.
The update is an in-place modification of self
"""
# only if empty
if self.title is None:
self.title = other.title
if self.correspondent_id is None:
self.correspondent_id = other.correspondent_id
if self.document_type_id is None:
self.document_type_id = other.document_type_id
if self.storage_path_id is None:
self.storage_path_id = other.storage_path_id
if self.owner_id is None:
self.owner_id = other.owner_id
# merge
# TODO: Handle the case where other is also None
if self.tag_ids is None:
self.tag_ids = other.tag_ids
else:
self.tag_ids.extend(other.tag_ids)
if self.view_users is None:
self.view_users = other.view_users
else:
self.view_users.extend(other.view_users)
if self.view_groups is None:
self.view_groups = other.view_groups
else:
self.view_groups.extend(other.view_groups)
if self.change_users is None:
self.change_users = other.change_users
else:
self.change_users.extend(other.change_users)
if self.change_groups is None:
self.change_groups = other.change_groups
else:
self.change_groups = [
*self.change_groups,
*other.change_groups,
]
return self
class DocumentSource(enum.IntEnum):
class DocumentSource(IntEnum):
"""
The source of an incoming document. May have other uses in the future
"""
ConsumeFolder = enum.auto()
ApiUpload = enum.auto()
MailFetch = enum.auto()
ConsumeFolder = 1
ApiUpload = 2
MailFetch = 3
@dataclasses.dataclass
@@ -44,6 +95,7 @@ class ConsumableDocument:
source: DocumentSource
original_file: Path
mailrule_id: Optional[int] = None
mime_type: str = dataclasses.field(init=False, default=None)
def __post_init__(self):