Use simple JSON field for display_fields

This commit is contained in:
shamoon 2024-04-20 22:11:50 -07:00
parent 6f546b9cba
commit c07e3f8379
5 changed files with 369 additions and 434 deletions

View File

@ -1,7 +1,6 @@
# Generated by Django 4.2.11 on 2024-04-16 18:35
import django.core.validators
import multiselectfield.db.fields
from django.db import migrations
from django.db import models
@ -41,18 +40,8 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name="savedview",
name="display_fields",
field=multiselectfield.db.fields.MultiSelectField(
field=models.JSONField(
blank=True,
choices=[
("title", "Title"),
("created", "Created"),
("added", "Added"),
("tag", "Tags"),
("documenttype", "Document Type"),
("correspondent", "Correspondent"),
("storagepath", "Storage Path"),
],
max_length=128,
null=True,
verbose_name="Document display fields",
),

View File

@ -393,164 +393,6 @@ class Log(models.Model):
return self.message
class CustomField(models.Model):
"""
Defines the name and type of a custom field
"""
class FieldDataType(models.TextChoices):
STRING = ("string", _("String"))
URL = ("url", _("URL"))
DATE = ("date", _("Date"))
BOOL = ("boolean"), _("Boolean")
INT = ("integer", _("Integer"))
FLOAT = ("float", _("Float"))
MONETARY = ("monetary", _("Monetary"))
DOCUMENTLINK = ("documentlink", _("Document Link"))
created = models.DateTimeField(
_("created"),
default=timezone.now,
db_index=True,
editable=False,
)
name = models.CharField(max_length=128)
data_type = models.CharField(
_("data type"),
max_length=50,
choices=FieldDataType.choices,
editable=False,
)
class Meta:
ordering = ("created",)
verbose_name = _("custom field")
verbose_name_plural = _("custom fields")
constraints = [
models.UniqueConstraint(
fields=["name"],
name="%(app_label)s_%(class)s_unique_name",
),
]
def __str__(self) -> str:
return f"{self.name} : {self.data_type}"
class CustomFieldInstance(models.Model):
"""
A single instance of a field, attached to a CustomField for the name and type
and attached to a single Document to be metadata for it
"""
created = models.DateTimeField(
_("created"),
default=timezone.now,
db_index=True,
editable=False,
)
document = models.ForeignKey(
Document,
blank=False,
null=False,
on_delete=models.CASCADE,
related_name="custom_fields",
editable=False,
)
field = models.ForeignKey(
CustomField,
blank=False,
null=False,
on_delete=models.CASCADE,
related_name="fields",
editable=False,
)
# Actual data storage
value_text = models.CharField(max_length=128, null=True)
value_bool = models.BooleanField(null=True)
value_url = models.URLField(null=True)
value_date = models.DateField(null=True)
value_int = models.IntegerField(null=True)
value_float = models.FloatField(null=True)
value_monetary = models.CharField(null=True, max_length=128)
value_document_ids = models.JSONField(null=True)
class Meta:
ordering = ("created",)
verbose_name = _("custom field instance")
verbose_name_plural = _("custom field instances")
constraints = [
models.UniqueConstraint(
fields=["document", "field"],
name="%(app_label)s_%(class)s_unique_document_field",
),
]
def __str__(self) -> str:
return str(self.field.name) + f" : {self.value}"
@property
def value(self):
"""
Based on the data type, access the actual value the instance stores
A little shorthand/quick way to get what is actually here
"""
if self.field.data_type == CustomField.FieldDataType.STRING:
return self.value_text
elif self.field.data_type == CustomField.FieldDataType.URL:
return self.value_url
elif self.field.data_type == CustomField.FieldDataType.DATE:
return self.value_date
elif self.field.data_type == CustomField.FieldDataType.BOOL:
return self.value_bool
elif self.field.data_type == CustomField.FieldDataType.INT:
return self.value_int
elif self.field.data_type == CustomField.FieldDataType.FLOAT:
return self.value_float
elif self.field.data_type == CustomField.FieldDataType.MONETARY:
return self.value_monetary
elif self.field.data_type == CustomField.FieldDataType.DOCUMENTLINK:
return self.value_document_ids
raise NotImplementedError(self.field.data_type) # pragma: no cover
class DynamicMultiSelectField(MultiSelectField):
"""
A MultiSelectField that can have dynamic choices from a model
"""
def __init__(self, *args, **kwargs):
self.dynamic_choices = kwargs.pop("dyanmic_choices", None)
super().__init__(*args, **kwargs)
def _get_choices(self):
return self._choices
def _set_choices(self, value):
if self.dynamic_choices:
for key, model in self.dynamic_choices:
try:
for obj in model.objects.all(): # pragma: no cover
value.append((key % obj.pk, obj.name))
except Exception:
pass
self._choices = value
choices = property(_get_choices, _set_choices)
class SavedView(ModelWithOwner):
class DisplayMode(models.TextChoices):
TABLE = ("table", _("Table"))
@ -565,9 +407,7 @@ class SavedView(ModelWithOwner):
DOCUMENT_TYPE = ("documenttype", _("Document Type"))
CORRESPONDENT = ("correspondent", _("Correspondent"))
STORAGE_PATH = ("storagepath", _("Storage Path"))
class DynamicDisplayFields:
CUSTOM_FIELD = ("custom_field_%d", CustomField)
CUSTOM_FIELD = ("custom_field_%d", ("Custom Field"))
name = models.CharField(_("name"), max_length=128)
@ -601,11 +441,8 @@ class SavedView(ModelWithOwner):
blank=True,
)
display_fields = DynamicMultiSelectField(
max_length=128,
display_fields = models.JSONField(
verbose_name=_("Document display fields"),
choices=DisplayFields.choices,
dyanmic_choices=[DynamicDisplayFields.CUSTOM_FIELD],
null=True,
blank=True,
)
@ -947,6 +784,139 @@ class ShareLink(models.Model):
return f"Share Link for {self.document.title}"
class CustomField(models.Model):
"""
Defines the name and type of a custom field
"""
class FieldDataType(models.TextChoices):
STRING = ("string", _("String"))
URL = ("url", _("URL"))
DATE = ("date", _("Date"))
BOOL = ("boolean"), _("Boolean")
INT = ("integer", _("Integer"))
FLOAT = ("float", _("Float"))
MONETARY = ("monetary", _("Monetary"))
DOCUMENTLINK = ("documentlink", _("Document Link"))
created = models.DateTimeField(
_("created"),
default=timezone.now,
db_index=True,
editable=False,
)
name = models.CharField(max_length=128)
data_type = models.CharField(
_("data type"),
max_length=50,
choices=FieldDataType.choices,
editable=False,
)
class Meta:
ordering = ("created",)
verbose_name = _("custom field")
verbose_name_plural = _("custom fields")
constraints = [
models.UniqueConstraint(
fields=["name"],
name="%(app_label)s_%(class)s_unique_name",
),
]
def __str__(self) -> str:
return f"{self.name} : {self.data_type}"
class CustomFieldInstance(models.Model):
"""
A single instance of a field, attached to a CustomField for the name and type
and attached to a single Document to be metadata for it
"""
created = models.DateTimeField(
_("created"),
default=timezone.now,
db_index=True,
editable=False,
)
document = models.ForeignKey(
Document,
blank=False,
null=False,
on_delete=models.CASCADE,
related_name="custom_fields",
editable=False,
)
field = models.ForeignKey(
CustomField,
blank=False,
null=False,
on_delete=models.CASCADE,
related_name="fields",
editable=False,
)
# Actual data storage
value_text = models.CharField(max_length=128, null=True)
value_bool = models.BooleanField(null=True)
value_url = models.URLField(null=True)
value_date = models.DateField(null=True)
value_int = models.IntegerField(null=True)
value_float = models.FloatField(null=True)
value_monetary = models.CharField(null=True, max_length=128)
value_document_ids = models.JSONField(null=True)
class Meta:
ordering = ("created",)
verbose_name = _("custom field instance")
verbose_name_plural = _("custom field instances")
constraints = [
models.UniqueConstraint(
fields=["document", "field"],
name="%(app_label)s_%(class)s_unique_document_field",
),
]
def __str__(self) -> str:
return str(self.field.name) + f" : {self.value}"
@property
def value(self):
"""
Based on the data type, access the actual value the instance stores
A little shorthand/quick way to get what is actually here
"""
if self.field.data_type == CustomField.FieldDataType.STRING:
return self.value_text
elif self.field.data_type == CustomField.FieldDataType.URL:
return self.value_url
elif self.field.data_type == CustomField.FieldDataType.DATE:
return self.value_date
elif self.field.data_type == CustomField.FieldDataType.BOOL:
return self.value_bool
elif self.field.data_type == CustomField.FieldDataType.INT:
return self.value_int
elif self.field.data_type == CustomField.FieldDataType.FLOAT:
return self.value_float
elif self.field.data_type == CustomField.FieldDataType.MONETARY:
return self.value_monetary
elif self.field.data_type == CustomField.FieldDataType.DOCUMENTLINK:
return self.value_document_ids
raise NotImplementedError(self.field.data_type)
if settings.AUDIT_LOG_ENABLED:
auditlog.register(
Document,

View File

@ -802,48 +802,8 @@ class SavedViewFilterRuleSerializer(serializers.ModelSerializer):
fields = ["rule_type", "value"]
class DynamicOrderedMultipleChoiceField(fields.MultipleChoiceField):
"""
A MultipleChoiceField that allows for dynamic choices from a model
and preserves the order of the choices.
"""
def __init__(self, **kwargs):
self.dyanmic_choices = kwargs.pop("dyanmic_choices", None)
super().__init__(**kwargs)
def _get_choices(self):
return super()._get_choices()
def _set_choices(self, choices):
if self.dyanmic_choices is not None:
for key, Model in self.dyanmic_choices:
try:
for obj in Model.objects.all():
choices.append((key % obj.pk, obj.name))
except Exception:
pass
return super()._set_choices(choices)
choices = property(_get_choices, _set_choices)
def to_internal_value(self, data):
super().to_internal_value(data)
# MultipleChoiceField doesn't preserve order, so we use an array
return [fields.ChoiceField.to_internal_value(self, item) for item in data]
def to_representation(self, value):
# MultipleChoiceField doesn't preserve order, so we return as array to match the original order
return [self.choice_strings_to_values.get(str(item), item) for item in value]
class SavedViewSerializer(OwnedObjectSerializer):
filter_rules = SavedViewFilterRuleSerializer(many=True)
display_fields = DynamicOrderedMultipleChoiceField(
choices=SavedView.DisplayFields.choices,
dyanmic_choices=[("custom_field_%d", CustomField)],
required=False,
)
class Meta:
model = SavedView
@ -864,6 +824,22 @@ class SavedViewSerializer(OwnedObjectSerializer):
"set_permissions",
]
def validate(self, attrs):
attrs = super().validate(attrs)
if "display_fields" in attrs:
for field in attrs["display_fields"]:
if re.sub(r"\d+", "%d", field) == SavedView.DisplayFields.CUSTOM_FIELD:
field_id = int(re.search(r"\d+", field)[0])
if not CustomField.objects.filter(id=field_id).exists():
raise serializers.ValidationError(
f"Invalid field: {field}",
)
elif field not in SavedView.DisplayFields.values:
raise serializers.ValidationError(
f"Invalid field: {field}",
)
return attrs
def update(self, instance, validated_data):
if "filter_rules" in validated_data:
rules_data = validated_data.pop("filter_rules")

View File

@ -1749,7 +1749,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
"display_fields": [
SavedView.DisplayFields.TITLE,
SavedView.DisplayFields.CREATED,
SavedView.DynamicDisplayFields.CUSTOM_FIELD[0] % custom_field.id,
SavedView.DisplayFields.CUSTOM_FIELD % custom_field.id,
],
},
format="json",
@ -1762,7 +1762,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
[
str(SavedView.DisplayFields.TITLE),
str(SavedView.DisplayFields.CREATED),
SavedView.DynamicDisplayFields.CUSTOM_FIELD[0] % custom_field.id,
SavedView.DisplayFields.CUSTOM_FIELD % custom_field.id,
],
)
@ -1773,7 +1773,7 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
"display_fields": [
SavedView.DisplayFields.TITLE,
SavedView.DisplayFields.CREATED,
SavedView.DynamicDisplayFields.CUSTOM_FIELD[0] % 99,
SavedView.DisplayFields.CUSTOM_FIELD % 99,
],
},
format="json",

View File

@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-19 01:14-0700\n"
"POT-Creation-Date: 2024-04-20 22:11-0700\n"
"PO-Revision-Date: 2022-02-17 04:17\n"
"Last-Translator: \n"
"Language-Team: English\n"
@ -21,31 +21,31 @@ msgstr ""
msgid "Documents"
msgstr ""
#: documents/models.py:36 documents/models.py:938
#: documents/models.py:36 documents/models.py:775
msgid "owner"
msgstr ""
#: documents/models.py:53 documents/models.py:963
#: documents/models.py:53 documents/models.py:933
msgid "None"
msgstr ""
#: documents/models.py:54 documents/models.py:964
#: documents/models.py:54 documents/models.py:934
msgid "Any word"
msgstr ""
#: documents/models.py:55 documents/models.py:965
#: documents/models.py:55 documents/models.py:935
msgid "All words"
msgstr ""
#: documents/models.py:56 documents/models.py:966
#: documents/models.py:56 documents/models.py:936
msgid "Exact match"
msgstr ""
#: documents/models.py:57 documents/models.py:967
#: documents/models.py:57 documents/models.py:937
msgid "Regular expression"
msgstr ""
#: documents/models.py:58 documents/models.py:968
#: documents/models.py:58 documents/models.py:938
msgid "Fuzzy word"
msgstr ""
@ -53,20 +53,20 @@ msgstr ""
msgid "Automatic"
msgstr ""
#: documents/models.py:62 documents/models.py:572 documents/models.py:1284
#: documents/models.py:62 documents/models.py:412 documents/models.py:1254
#: paperless_mail/models.py:18 paperless_mail/models.py:93
msgid "name"
msgstr ""
#: documents/models.py:64 documents/models.py:1024
#: documents/models.py:64 documents/models.py:994
msgid "match"
msgstr ""
#: documents/models.py:67 documents/models.py:1027
#: documents/models.py:67 documents/models.py:997
msgid "matching algorithm"
msgstr ""
#: documents/models.py:72 documents/models.py:1032
#: documents/models.py:72 documents/models.py:1002
msgid "is insensitive"
msgstr ""
@ -132,7 +132,7 @@ msgstr ""
msgid "title"
msgstr ""
#: documents/models.py:171 documents/models.py:852
#: documents/models.py:171 documents/models.py:689
msgid "content"
msgstr ""
@ -162,8 +162,8 @@ msgstr ""
msgid "The checksum of the archived document."
msgstr ""
#: documents/models.py:205 documents/models.py:385 documents/models.py:412
#: documents/models.py:449 documents/models.py:858 documents/models.py:896
#: documents/models.py:205 documents/models.py:385 documents/models.py:695
#: documents/models.py:733 documents/models.py:803 documents/models.py:840
msgid "created"
msgstr ""
@ -211,7 +211,7 @@ msgstr ""
msgid "The position of this document in your physical document archive."
msgstr ""
#: documents/models.py:279 documents/models.py:869 documents/models.py:923
#: documents/models.py:279 documents/models.py:706 documents/models.py:760
msgid "document"
msgstr ""
@ -259,636 +259,636 @@ msgstr ""
msgid "logs"
msgstr ""
#: documents/models.py:402
msgid "String"
msgstr ""
#: documents/models.py:403
msgid "URL"
msgstr ""
#: documents/models.py:404
msgid "Date"
msgstr ""
#: documents/models.py:405
msgid "Boolean"
msgstr ""
#: documents/models.py:406
msgid "Integer"
msgstr ""
#: documents/models.py:407
msgid "Float"
msgstr ""
#: documents/models.py:408
msgid "Monetary"
msgstr ""
#: documents/models.py:409
msgid "Document Link"
msgstr ""
#: documents/models.py:421
msgid "data type"
msgstr ""
#: documents/models.py:429
msgid "custom field"
msgstr ""
#: documents/models.py:430
msgid "custom fields"
msgstr ""
#: documents/models.py:492
msgid "custom field instance"
msgstr ""
#: documents/models.py:493
msgid "custom field instances"
msgstr ""
#: documents/models.py:556
#: documents/models.py:398
msgid "Table"
msgstr ""
#: documents/models.py:557
#: documents/models.py:399
msgid "Small Cards"
msgstr ""
#: documents/models.py:558
#: documents/models.py:400
msgid "Large Cards"
msgstr ""
#: documents/models.py:561
#: documents/models.py:403
msgid "Title"
msgstr ""
#: documents/models.py:562
#: documents/models.py:404
msgid "Created"
msgstr ""
#: documents/models.py:563
#: documents/models.py:405
msgid "Added"
msgstr ""
#: documents/models.py:564
#: documents/models.py:406
msgid "Tags"
msgstr ""
#: documents/models.py:565
#: documents/models.py:407
msgid "Document Type"
msgstr ""
#: documents/models.py:566
#: documents/models.py:408
msgid "Correspondent"
msgstr ""
#: documents/models.py:567
#: documents/models.py:409
msgid "Storage Path"
msgstr ""
#: documents/models.py:575
#: documents/models.py:415
msgid "show on dashboard"
msgstr ""
#: documents/models.py:578
#: documents/models.py:418
msgid "show in sidebar"
msgstr ""
#: documents/models.py:582
#: documents/models.py:422
msgid "sort field"
msgstr ""
#: documents/models.py:587
#: documents/models.py:427
msgid "sort reverse"
msgstr ""
#: documents/models.py:590
#: documents/models.py:430
msgid "View page size"
msgstr ""
#: documents/models.py:598
#: documents/models.py:438
msgid "View display mode"
msgstr ""
#: documents/models.py:606
#: documents/models.py:445
msgid "Document display fields"
msgstr ""
#: documents/models.py:615 documents/models.py:668
#: documents/models.py:452 documents/models.py:505
msgid "saved view"
msgstr ""
#: documents/models.py:616
#: documents/models.py:453
msgid "saved views"
msgstr ""
#: documents/models.py:624
#: documents/models.py:461
msgid "title contains"
msgstr ""
#: documents/models.py:625
#: documents/models.py:462
msgid "content contains"
msgstr ""
#: documents/models.py:626
#: documents/models.py:463
msgid "ASN is"
msgstr ""
#: documents/models.py:627
#: documents/models.py:464
msgid "correspondent is"
msgstr ""
#: documents/models.py:628
#: documents/models.py:465
msgid "document type is"
msgstr ""
#: documents/models.py:629
#: documents/models.py:466
msgid "is in inbox"
msgstr ""
#: documents/models.py:630
#: documents/models.py:467
msgid "has tag"
msgstr ""
#: documents/models.py:631
#: documents/models.py:468
msgid "has any tag"
msgstr ""
#: documents/models.py:632
#: documents/models.py:469
msgid "created before"
msgstr ""
#: documents/models.py:633
#: documents/models.py:470
msgid "created after"
msgstr ""
#: documents/models.py:634
#: documents/models.py:471
msgid "created year is"
msgstr ""
#: documents/models.py:635
#: documents/models.py:472
msgid "created month is"
msgstr ""
#: documents/models.py:636
#: documents/models.py:473
msgid "created day is"
msgstr ""
#: documents/models.py:637
#: documents/models.py:474
msgid "added before"
msgstr ""
#: documents/models.py:638
#: documents/models.py:475
msgid "added after"
msgstr ""
#: documents/models.py:639
#: documents/models.py:476
msgid "modified before"
msgstr ""
#: documents/models.py:640
#: documents/models.py:477
msgid "modified after"
msgstr ""
#: documents/models.py:641
#: documents/models.py:478
msgid "does not have tag"
msgstr ""
#: documents/models.py:642
#: documents/models.py:479
msgid "does not have ASN"
msgstr ""
#: documents/models.py:643
#: documents/models.py:480
msgid "title or content contains"
msgstr ""
#: documents/models.py:644
#: documents/models.py:481
msgid "fulltext query"
msgstr ""
#: documents/models.py:645
#: documents/models.py:482
msgid "more like this"
msgstr ""
#: documents/models.py:646
#: documents/models.py:483
msgid "has tags in"
msgstr ""
#: documents/models.py:647
#: documents/models.py:484
msgid "ASN greater than"
msgstr ""
#: documents/models.py:648
#: documents/models.py:485
msgid "ASN less than"
msgstr ""
#: documents/models.py:649
#: documents/models.py:486
msgid "storage path is"
msgstr ""
#: documents/models.py:650
#: documents/models.py:487
msgid "has correspondent in"
msgstr ""
#: documents/models.py:651
#: documents/models.py:488
msgid "does not have correspondent in"
msgstr ""
#: documents/models.py:652
#: documents/models.py:489
msgid "has document type in"
msgstr ""
#: documents/models.py:653
#: documents/models.py:490
msgid "does not have document type in"
msgstr ""
#: documents/models.py:654
#: documents/models.py:491
msgid "has storage path in"
msgstr ""
#: documents/models.py:655
#: documents/models.py:492
msgid "does not have storage path in"
msgstr ""
#: documents/models.py:656
#: documents/models.py:493
msgid "owner is"
msgstr ""
#: documents/models.py:657
#: documents/models.py:494
msgid "has owner in"
msgstr ""
#: documents/models.py:658
#: documents/models.py:495
msgid "does not have owner"
msgstr ""
#: documents/models.py:659
#: documents/models.py:496
msgid "does not have owner in"
msgstr ""
#: documents/models.py:660
#: documents/models.py:497
msgid "has custom field value"
msgstr ""
#: documents/models.py:661
#: documents/models.py:498
msgid "is shared by me"
msgstr ""
#: documents/models.py:671
#: documents/models.py:508
msgid "rule type"
msgstr ""
#: documents/models.py:673
#: documents/models.py:510
msgid "value"
msgstr ""
#: documents/models.py:676
#: documents/models.py:513
msgid "filter rule"
msgstr ""
#: documents/models.py:677
#: documents/models.py:514
msgid "filter rules"
msgstr ""
#: documents/models.py:788
#: documents/models.py:625
msgid "Task ID"
msgstr ""
#: documents/models.py:789
#: documents/models.py:626
msgid "Celery ID for the Task that was run"
msgstr ""
#: documents/models.py:794
#: documents/models.py:631
msgid "Acknowledged"
msgstr ""
#: documents/models.py:795
#: documents/models.py:632
msgid "If the task is acknowledged via the frontend or API"
msgstr ""
#: documents/models.py:801
#: documents/models.py:638
msgid "Task Filename"
msgstr ""
#: documents/models.py:802
#: documents/models.py:639
msgid "Name of the file which the Task was run for"
msgstr ""
#: documents/models.py:808
#: documents/models.py:645
msgid "Task Name"
msgstr ""
#: documents/models.py:809
#: documents/models.py:646
msgid "Name of the Task which was run"
msgstr ""
#: documents/models.py:816
#: documents/models.py:653
msgid "Task State"
msgstr ""
#: documents/models.py:817
#: documents/models.py:654
msgid "Current state of the task being run"
msgstr ""
#: documents/models.py:822
#: documents/models.py:659
msgid "Created DateTime"
msgstr ""
#: documents/models.py:823
#: documents/models.py:660
msgid "Datetime field when the task result was created in UTC"
msgstr ""
#: documents/models.py:828
#: documents/models.py:665
msgid "Started DateTime"
msgstr ""
#: documents/models.py:829
#: documents/models.py:666
msgid "Datetime field when the task was started in UTC"
msgstr ""
#: documents/models.py:834
#: documents/models.py:671
msgid "Completed DateTime"
msgstr ""
#: documents/models.py:835
#: documents/models.py:672
msgid "Datetime field when the task was completed in UTC"
msgstr ""
#: documents/models.py:840
#: documents/models.py:677
msgid "Result Data"
msgstr ""
#: documents/models.py:842
#: documents/models.py:679
msgid "The data returned by the task"
msgstr ""
#: documents/models.py:854
#: documents/models.py:691
msgid "Note for the document"
msgstr ""
#: documents/models.py:878
#: documents/models.py:715
msgid "user"
msgstr ""
#: documents/models.py:883
#: documents/models.py:720
msgid "note"
msgstr ""
#: documents/models.py:884
#: documents/models.py:721
msgid "notes"
msgstr ""
#: documents/models.py:892
#: documents/models.py:729
msgid "Archive"
msgstr ""
#: documents/models.py:893
#: documents/models.py:730
msgid "Original"
msgstr ""
#: documents/models.py:904
#: documents/models.py:741
msgid "expiration"
msgstr ""
#: documents/models.py:911
#: documents/models.py:748
msgid "slug"
msgstr ""
#: documents/models.py:943
#: documents/models.py:780
msgid "share link"
msgstr ""
#: documents/models.py:944
#: documents/models.py:781
msgid "share links"
msgstr ""
#: documents/models.py:971
#: documents/models.py:793
msgid "String"
msgstr ""
#: documents/models.py:794
msgid "URL"
msgstr ""
#: documents/models.py:795
msgid "Date"
msgstr ""
#: documents/models.py:796
msgid "Boolean"
msgstr ""
#: documents/models.py:797
msgid "Integer"
msgstr ""
#: documents/models.py:798
msgid "Float"
msgstr ""
#: documents/models.py:799
msgid "Monetary"
msgstr ""
#: documents/models.py:800
msgid "Document Link"
msgstr ""
#: documents/models.py:812
msgid "data type"
msgstr ""
#: documents/models.py:820
msgid "custom field"
msgstr ""
#: documents/models.py:821
msgid "custom fields"
msgstr ""
#: documents/models.py:883
msgid "custom field instance"
msgstr ""
#: documents/models.py:884
msgid "custom field instances"
msgstr ""
#: documents/models.py:941
msgid "Consumption Started"
msgstr ""
#: documents/models.py:972
#: documents/models.py:942
msgid "Document Added"
msgstr ""
#: documents/models.py:973
#: documents/models.py:943
msgid "Document Updated"
msgstr ""
#: documents/models.py:976
#: documents/models.py:946
msgid "Consume Folder"
msgstr ""
#: documents/models.py:977
#: documents/models.py:947
msgid "Api Upload"
msgstr ""
#: documents/models.py:978
#: documents/models.py:948
msgid "Mail Fetch"
msgstr ""
#: documents/models.py:981
#: documents/models.py:951
msgid "Workflow Trigger Type"
msgstr ""
#: documents/models.py:993
#: documents/models.py:963
msgid "filter path"
msgstr ""
#: documents/models.py:998
#: documents/models.py:968
msgid ""
"Only consume documents with a path that matches this if specified. Wildcards "
"specified as * are allowed. Case insensitive."
msgstr ""
#: documents/models.py:1005
#: documents/models.py:975
msgid "filter filename"
msgstr ""
#: documents/models.py:1010 paperless_mail/models.py:148
#: documents/models.py:980 paperless_mail/models.py:148
msgid ""
"Only consume documents which entirely match this filename if specified. "
"Wildcards such as *.pdf or *invoice* are allowed. Case insensitive."
msgstr ""
#: documents/models.py:1021
#: documents/models.py:991
msgid "filter documents from this mail rule"
msgstr ""
#: documents/models.py:1037
#: documents/models.py:1007
msgid "has these tag(s)"
msgstr ""
#: documents/models.py:1045
#: documents/models.py:1015
msgid "has this document type"
msgstr ""
#: documents/models.py:1053
#: documents/models.py:1023
msgid "has this correspondent"
msgstr ""
#: documents/models.py:1057
#: documents/models.py:1027
msgid "workflow trigger"
msgstr ""
#: documents/models.py:1058
#: documents/models.py:1028
msgid "workflow triggers"
msgstr ""
#: documents/models.py:1068
#: documents/models.py:1038
msgid "Assignment"
msgstr ""
#: documents/models.py:1072
#: documents/models.py:1042
msgid "Removal"
msgstr ""
#: documents/models.py:1076
#: documents/models.py:1046
msgid "Workflow Action Type"
msgstr ""
#: documents/models.py:1082
#: documents/models.py:1052
msgid "assign title"
msgstr ""
#: documents/models.py:1087
#: documents/models.py:1057
msgid ""
"Assign a document title, can include some placeholders, see documentation."
msgstr ""
#: documents/models.py:1096 paperless_mail/models.py:216
#: documents/models.py:1066 paperless_mail/models.py:216
msgid "assign this tag"
msgstr ""
#: documents/models.py:1105 paperless_mail/models.py:224
#: documents/models.py:1075 paperless_mail/models.py:224
msgid "assign this document type"
msgstr ""
#: documents/models.py:1114 paperless_mail/models.py:238
#: documents/models.py:1084 paperless_mail/models.py:238
msgid "assign this correspondent"
msgstr ""
#: documents/models.py:1123
#: documents/models.py:1093
msgid "assign this storage path"
msgstr ""
#: documents/models.py:1132
#: documents/models.py:1102
msgid "assign this owner"
msgstr ""
#: documents/models.py:1139
#: documents/models.py:1109
msgid "grant view permissions to these users"
msgstr ""
#: documents/models.py:1146
#: documents/models.py:1116
msgid "grant view permissions to these groups"
msgstr ""
#: documents/models.py:1153
#: documents/models.py:1123
msgid "grant change permissions to these users"
msgstr ""
#: documents/models.py:1160
#: documents/models.py:1130
msgid "grant change permissions to these groups"
msgstr ""
#: documents/models.py:1167
#: documents/models.py:1137
msgid "assign these custom fields"
msgstr ""
#: documents/models.py:1174
#: documents/models.py:1144
msgid "remove these tag(s)"
msgstr ""
#: documents/models.py:1179
#: documents/models.py:1149
msgid "remove all tags"
msgstr ""
#: documents/models.py:1186
#: documents/models.py:1156
msgid "remove these document type(s)"
msgstr ""
#: documents/models.py:1191
#: documents/models.py:1161
msgid "remove all document types"
msgstr ""
#: documents/models.py:1198
#: documents/models.py:1168
msgid "remove these correspondent(s)"
msgstr ""
#: documents/models.py:1203
#: documents/models.py:1173
msgid "remove all correspondents"
msgstr ""
#: documents/models.py:1210
#: documents/models.py:1180
msgid "remove these storage path(s)"
msgstr ""
#: documents/models.py:1215
#: documents/models.py:1185
msgid "remove all storage paths"
msgstr ""
#: documents/models.py:1222
#: documents/models.py:1192
msgid "remove these owner(s)"
msgstr ""
#: documents/models.py:1227
#: documents/models.py:1197
msgid "remove all owners"
msgstr ""
#: documents/models.py:1234
#: documents/models.py:1204
msgid "remove view permissions for these users"
msgstr ""
#: documents/models.py:1241
#: documents/models.py:1211
msgid "remove view permissions for these groups"
msgstr ""
#: documents/models.py:1248
#: documents/models.py:1218
msgid "remove change permissions for these users"
msgstr ""
#: documents/models.py:1255
#: documents/models.py:1225
msgid "remove change permissions for these groups"
msgstr ""
#: documents/models.py:1260
#: documents/models.py:1230
msgid "remove all permissions"
msgstr ""
#: documents/models.py:1267
#: documents/models.py:1237
msgid "remove these custom fields"
msgstr ""
#: documents/models.py:1272
#: documents/models.py:1242
msgid "remove all custom fields"
msgstr ""
#: documents/models.py:1276
#: documents/models.py:1246
msgid "workflow action"
msgstr ""
#: documents/models.py:1277
#: documents/models.py:1247
msgid "workflow actions"
msgstr ""
#: documents/models.py:1286 paperless_mail/models.py:95
#: documents/models.py:1256 paperless_mail/models.py:95
msgid "order"
msgstr ""
#: documents/models.py:1292
#: documents/models.py:1262
msgid "triggers"
msgstr ""
#: documents/models.py:1299
#: documents/models.py:1269
msgid "actions"
msgstr ""
#: documents/models.py:1302
#: documents/models.py:1272
msgid "enabled"
msgstr ""
@ -901,12 +901,12 @@ msgstr ""
msgid "Invalid color."
msgstr ""
#: documents/serialisers.py:1186
#: documents/serialisers.py:1162
#, python-format
msgid "File type %(type)s not supported"
msgstr ""
#: documents/serialisers.py:1295
#: documents/serialisers.py:1271
msgid "Invalid variable detected."
msgstr ""