Support custom fields, include actor for custom fields changes
F
This commit is contained in:
parent
42eef35988
commit
25f0036a50
@ -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>:
|
||||||
|
<code class="text-primary">{{ change.value["value"] }}</code>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
@else {
|
@else {
|
||||||
<li>
|
<li>
|
||||||
<span class="text-light">{{ change.key | titlecase }}</span>:
|
<span class="text-light">{{ change.key | titlecase }}</span>:
|
||||||
|
@ -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,6 +747,10 @@ 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
|
||||||
]
|
]
|
||||||
|
if settings.AUDIT_LOG_ENABLED:
|
||||||
|
with set_actor(self.user):
|
||||||
|
super().update(instance, validated_data)
|
||||||
|
else:
|
||||||
super().update(instance, validated_data)
|
super().update(instance, validated_data)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
@ -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):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user