0">
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.html b/src-ui/src/app/components/document-detail/document-detail.component.html
index 8b50e6f2e..ea14b750d 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.html
+++ b/src-ui/src/app/components/document-detail/document-detail.component.html
@@ -131,6 +131,7 @@
+
diff --git a/src-ui/src/app/data/paperless-custom-field.ts b/src-ui/src/app/data/paperless-custom-field.ts
index 663e1507f..93bd34e33 100644
--- a/src-ui/src/app/data/paperless-custom-field.ts
+++ b/src-ui/src/app/data/paperless-custom-field.ts
@@ -8,6 +8,7 @@ export enum PaperlessCustomFieldDataType {
Integer = 'integer',
Float = 'float',
Monetary = 'monetary',
+ DocumentLink = 'documentlink',
}
export const DATA_TYPE_LABELS = [
@@ -39,6 +40,10 @@ export const DATA_TYPE_LABELS = [
id: PaperlessCustomFieldDataType.Url,
name: $localize`Url`,
},
+ {
+ id: PaperlessCustomFieldDataType.DocumentLink,
+ name: $localize`Document Link`,
+ },
]
export interface PaperlessCustomField extends ObjectWithId {
diff --git a/src/documents/migrations/1042_consumptiontemplate_assign_custom_fields.py b/src/documents/migrations/1042_consumptiontemplate_assign_custom_fields.py
deleted file mode 100644
index 08d6062ea..000000000
--- a/src/documents/migrations/1042_consumptiontemplate_assign_custom_fields.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Generated by Django 4.2.7 on 2023-11-30 17:44
-
-from django.db import migrations
-from django.db import models
-
-
-class Migration(migrations.Migration):
- dependencies = [
- ("documents", "1041_alter_consumptiontemplate_sources"),
- ]
-
- operations = [
- migrations.AddField(
- model_name="consumptiontemplate",
- name="assign_custom_fields",
- field=models.ManyToManyField(
- blank=True,
- related_name="+",
- to="documents.customfield",
- verbose_name="assign these custom fields",
- ),
- ),
- ]
diff --git a/src/documents/migrations/1042_consumptiontemplate_assign_custom_fields_and_more.py b/src/documents/migrations/1042_consumptiontemplate_assign_custom_fields_and_more.py
new file mode 100644
index 000000000..150b4c2af
--- /dev/null
+++ b/src/documents/migrations/1042_consumptiontemplate_assign_custom_fields_and_more.py
@@ -0,0 +1,52 @@
+# Generated by Django 4.2.7 on 2023-12-04 04:03
+
+import django.core.validators
+from django.db import migrations
+from django.db import models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("documents", "1041_alter_consumptiontemplate_sources"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="consumptiontemplate",
+ name="assign_custom_fields",
+ field=models.ManyToManyField(
+ blank=True,
+ related_name="+",
+ to="documents.customfield",
+ verbose_name="assign these custom fields",
+ ),
+ ),
+ migrations.AddField(
+ model_name="customfieldinstance",
+ name="value_document_ids",
+ field=models.CharField(
+ max_length=128,
+ null=True,
+ validators=[django.core.validators.int_list_validator],
+ ),
+ ),
+ migrations.AlterField(
+ model_name="customfield",
+ name="data_type",
+ field=models.CharField(
+ choices=[
+ ("string", "String"),
+ ("url", "URL"),
+ ("date", "Date"),
+ ("boolean", "Boolean"),
+ ("integer", "Integer"),
+ ("float", "Float"),
+ ("monetary", "Monetary"),
+ ("documentlink", "Document Link"),
+ ],
+ editable=False,
+ max_length=50,
+ verbose_name="data type",
+ ),
+ ),
+ ]
diff --git a/src/documents/models.py b/src/documents/models.py
index d688253de..a0da315a0 100644
--- a/src/documents/models.py
+++ b/src/documents/models.py
@@ -15,6 +15,7 @@ from django.contrib.auth.models import Group
from django.contrib.auth.models import User
from django.core.validators import MaxValueValidator
from django.core.validators import MinValueValidator
+from django.core.validators import int_list_validator
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
@@ -756,6 +757,7 @@ class CustomField(models.Model):
INT = ("integer", _("Integer"))
FLOAT = ("float", _("Float"))
MONETARY = ("monetary", _("Monetary"))
+ DOCUMENTLINK = ("documentlink", _("Document Link"))
created = models.DateTimeField(
_("created"),
@@ -834,6 +836,12 @@ class CustomFieldInstance(models.Model):
value_monetary = models.DecimalField(null=True, decimal_places=2, max_digits=12)
+ value_document_ids = models.CharField(
+ validators=[int_list_validator],
+ max_length=128,
+ null=True,
+ )
+
class Meta:
ordering = ("created",)
verbose_name = _("custom field instance")
@@ -868,6 +876,8 @@ class CustomFieldInstance(models.Model):
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)
diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py
index 2373a25dd..e0ecdd01a 100644
--- a/src/documents/serialisers.py
+++ b/src/documents/serialisers.py
@@ -1,4 +1,5 @@
import datetime
+import json
import math
import re
import zoneinfo
@@ -440,6 +441,7 @@ class CustomFieldInstanceSerializer(serializers.ModelSerializer):
CustomField.FieldDataType.INT: "value_int",
CustomField.FieldDataType.FLOAT: "value_float",
CustomField.FieldDataType.MONETARY: "value_monetary",
+ CustomField.FieldDataType.DOCUMENTLINK: "value_document_ids",
}
# An instance is attached to a document
document: Document = validated_data["document"]
@@ -458,7 +460,11 @@ class CustomFieldInstanceSerializer(serializers.ModelSerializer):
return instance
def get_value(self, obj: CustomFieldInstance):
- return obj.value
+ return (
+ obj.value
+ if (obj.field.data_type != CustomField.FieldDataType.DOCUMENTLINK)
+ else json.loads(obj.value)
+ )
def validate(self, data):
"""
diff --git a/src/documents/tests/test_api_custom_fields.py b/src/documents/tests/test_api_custom_fields.py
index 725bd9254..cde5f302c 100644
--- a/src/documents/tests/test_api_custom_fields.py
+++ b/src/documents/tests/test_api_custom_fields.py
@@ -34,7 +34,9 @@ class TestCustomField(DirectoriesMixin, APITestCase):
("date", "Invoiced Date"),
("integer", "Invoice #"),
("boolean", "Is Active"),
- ("float", "Total Paid"),
+ ("float", "Average Value"),
+ ("monetary", "Total Paid"),
+ ("documentlink", "Related Documents"),
]:
resp = self.client.post(
self.ENDPOINT,
@@ -96,6 +98,10 @@ class TestCustomField(DirectoriesMixin, APITestCase):
name="Test Custom Field Monetary",
data_type=CustomField.FieldDataType.MONETARY,
)
+ custom_field_documentlink = CustomField.objects.create(
+ name="Test Custom Field Doc Link",
+ data_type=CustomField.FieldDataType.DOCUMENTLINK,
+ )
date_value = date.today()
@@ -131,6 +137,10 @@ class TestCustomField(DirectoriesMixin, APITestCase):
"field": custom_field_monetary.id,
"value": 11.10,
},
+ {
+ "field": custom_field_documentlink.id,
+ "value": [1, 2, 3],
+ },
],
},
format="json",
@@ -150,11 +160,12 @@ class TestCustomField(DirectoriesMixin, APITestCase):
{"field": custom_field_url.id, "value": "https://example.com"},
{"field": custom_field_float.id, "value": 12.3456},
{"field": custom_field_monetary.id, "value": 11.10},
+ {"field": custom_field_documentlink.id, "value": [1, 2, 3]},
],
)
doc.refresh_from_db()
- self.assertEqual(len(doc.custom_fields.all()), 7)
+ self.assertEqual(len(doc.custom_fields.all()), 8)
def test_change_custom_field_instance_value(self):
"""