diff --git a/src-ui/src/app/components/audit-log/audit-log.component.html b/src-ui/src/app/components/audit-log/audit-log.component.html
index 497049aa4..65ecaff0a 100644
--- a/src-ui/src/app/components/audit-log/audit-log.component.html
+++ b/src-ui/src/app/components/audit-log/audit-log.component.html
@@ -37,6 +37,12 @@
{{ change.value["objects"].join(', ') }}
}
+ @else if (change.value["type"] === 'custom_field') {
+
+ {{ change.value["field"] }}:
+ {{ change.value["value"] }}
+
+ }
@else {
{{ change.key | titlecase }}:
diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py
index 26930ccec..c7e86a7bf 100644
--- a/src/documents/serialisers.py
+++ b/src/documents/serialisers.py
@@ -5,6 +5,7 @@ import zoneinfo
from decimal import Decimal
import magic
+from auditlog.context import set_actor
from celery import states
from django.conf import settings
from django.contrib.auth.models import Group
@@ -746,7 +747,11 @@ class DocumentSerializer(
for tag in instance.tags.all()
if tag not in inbox_tags_not_being_added
]
- super().update(instance, validated_data)
+ if settings.AUDIT_LOG_ENABLED:
+ with set_actor(self.user):
+ super().update(instance, validated_data)
+ else:
+ super().update(instance, validated_data)
return instance
def __init__(self, *args, **kwargs):
diff --git a/src/documents/tests/test_api_documents.py b/src/documents/tests/test_api_documents.py
index 1ea6f52c4..8ae67a6c0 100644
--- a/src/documents/tests/test_api_documents.py
+++ b/src/documents/tests/test_api_documents.py
@@ -339,6 +339,70 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
{"title": ["First title", "New title"]},
)
+ def test_document_audit_action_w_custom_fields(self):
+ """
+ GIVEN:
+ - Document with custom fields
+ WHEN:
+ - Document is updated
+ THEN:
+ - Audit log contains custom field changes
+ """
+ doc = Document.objects.create(
+ title="First title",
+ checksum="123",
+ mime_type="application/pdf",
+ )
+ custom_field = CustomField.objects.create(
+ name="custom field str",
+ data_type=CustomField.FieldDataType.STRING,
+ )
+ self.client.force_login(user=self.user)
+ self.client.patch(
+ f"/api/documents/{doc.pk}/",
+ data={
+ "custom_fields": [
+ {
+ "field": custom_field.pk,
+ "value": "custom value",
+ },
+ ],
+ },
+ format="json",
+ )
+
+ response = self.client.get(f"/api/documents/{doc.pk}/audit/")
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data[1]["actor"]["id"], self.user.id)
+ self.assertEqual(response.data[1]["action"], "create")
+ self.assertEqual(
+ response.data[1]["changes"],
+ {
+ "custom_fields": {
+ "type": "custom_field",
+ "field": "custom field str",
+ "value": "custom value",
+ },
+ },
+ )
+
+ @override_settings(AUDIT_LOG_ENABLED=False)
+ def test_document_audit_action_disabled(self):
+ doc = Document.objects.create(
+ title="First title",
+ checksum="123",
+ mime_type="application/pdf",
+ )
+ self.client.force_login(user=self.user)
+ self.client.patch(
+ f"/api/documents/{doc.pk}/",
+ {"title": "New title"},
+ format="json",
+ )
+
+ response = self.client.get(f"/api/documents/{doc.pk}/audit/")
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
def test_document_filters(self):
doc1 = Document.objects.create(
title="none1",
diff --git a/src/documents/views.py b/src/documents/views.py
index 48a745092..751d1428b 100644
--- a/src/documents/views.py
+++ b/src/documents/views.py
@@ -18,6 +18,7 @@ import pathvalidate
from django.apps import apps
from django.conf import settings
from django.contrib.auth.models import User
+from django.contrib.contenttypes.models import ContentType
from django.db import connections
from django.db.migrations.loader import MigrationLoader
from django.db.migrations.recorder import MigrationRecorder
@@ -105,6 +106,7 @@ from documents.matching import match_storage_paths
from documents.matching import match_tags
from documents.models import Correspondent
from documents.models import CustomField
+from documents.models import CustomFieldInstance
from documents.models import Document
from documents.models import DocumentType
from documents.models import Note
@@ -743,24 +745,48 @@ class DocumentViewSet(
raise Http404
if request.method == "GET":
+ # documents
entries = [
{
"id": entry.id,
"timestamp": entry.timestamp,
"action": entry.get_action_display(),
"changes": json.loads(entry.changes),
- "remote_addr": entry.remote_addr,
"actor": (
{"id": entry.actor.id, "username": entry.actor.username}
if entry.actor
else None
),
}
- for entry in LogEntry.objects.filter(object_pk=doc.pk).order_by(
- "-timestamp",
- )
+ for entry in LogEntry.objects.filter(object_pk=doc.pk)
]
- return Response(entries)
+
+ # custom fields
+ for entry in LogEntry.objects.filter(
+ object_pk__in=doc.custom_fields.values_list("id", flat=True),
+ content_type=ContentType.objects.get_for_model(CustomFieldInstance),
+ ):
+ entries.append(
+ {
+ "id": entry.id,
+ "timestamp": entry.timestamp,
+ "action": entry.get_action_display(),
+ "changes": {
+ "custom_fields": {
+ "type": "custom_field",
+ "field": str(entry.object_repr).split(":")[0].strip(),
+ "value": str(entry.object_repr).split(":")[1].strip(),
+ },
+ },
+ "actor": (
+ {"id": entry.actor.id, "username": entry.actor.username}
+ if entry.actor
+ else None
+ ),
+ },
+ )
+
+ return Response(sorted(entries, key=lambda x: x["timestamp"], reverse=True))
class SearchResultSerializer(DocumentSerializer, PassUserMixin):