Support custom fields, include actor for custom fields changes

F
This commit is contained in:
shamoon 2024-04-13 22:37:12 -07:00
parent 42eef35988
commit 25f0036a50
4 changed files with 107 additions and 6 deletions

View File

@ -37,6 +37,12 @@
<code class="text-primary">{{ change.value["objects"].join(', ') }}</code> <code class="text-primary">{{ change.value["objects"].join(', ') }}</code>
</li> </li>
} }
@else if (change.value["type"] === 'custom_field') {
<li>
<span class="text-light">{{ change.value["field"] }}</span>:&nbsp;
<code class="text-primary">{{ change.value["value"] }}</code>
</li>
}
@else { @else {
<li> <li>
<span class="text-light">{{ change.key | titlecase }}</span>:&nbsp; <span class="text-light">{{ change.key | titlecase }}</span>:&nbsp;

View File

@ -5,6 +5,7 @@ import zoneinfo
from decimal import Decimal from decimal import Decimal
import magic import magic
from auditlog.context import set_actor
from celery import states from celery import states
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
@ -746,7 +747,11 @@ class DocumentSerializer(
for tag in instance.tags.all() for tag in instance.tags.all()
if tag not in inbox_tags_not_being_added 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 return instance
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@ -339,6 +339,70 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
{"title": ["First title", "New title"]}, {"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): def test_document_filters(self):
doc1 = Document.objects.create( doc1 = Document.objects.create(
title="none1", title="none1",

View File

@ -18,6 +18,7 @@ import pathvalidate
from django.apps import apps from django.apps import apps
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.db import connections from django.db import connections
from django.db.migrations.loader import MigrationLoader from django.db.migrations.loader import MigrationLoader
from django.db.migrations.recorder import MigrationRecorder 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.matching import match_tags
from documents.models import Correspondent from documents.models import Correspondent
from documents.models import CustomField from documents.models import CustomField
from documents.models import CustomFieldInstance
from documents.models import Document from documents.models import Document
from documents.models import DocumentType from documents.models import DocumentType
from documents.models import Note from documents.models import Note
@ -743,24 +745,48 @@ class DocumentViewSet(
raise Http404 raise Http404
if request.method == "GET": if request.method == "GET":
# documents
entries = [ entries = [
{ {
"id": entry.id, "id": entry.id,
"timestamp": entry.timestamp, "timestamp": entry.timestamp,
"action": entry.get_action_display(), "action": entry.get_action_display(),
"changes": json.loads(entry.changes), "changes": json.loads(entry.changes),
"remote_addr": entry.remote_addr,
"actor": ( "actor": (
{"id": entry.actor.id, "username": entry.actor.username} {"id": entry.actor.id, "username": entry.actor.username}
if entry.actor if entry.actor
else None else None
), ),
} }
for entry in LogEntry.objects.filter(object_pk=doc.pk).order_by( for entry in LogEntry.objects.filter(object_pk=doc.pk)
"-timestamp",
)
] ]
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): class SearchResultSerializer(DocumentSerializer, PassUserMixin):