use django-audit-log

This commit is contained in:
Your Name 2023-06-18 23:13:30 -04:00 committed by Henri Dickson
parent db1e28607b
commit 7c5a02bac7
6 changed files with 82 additions and 12 deletions

View file

@ -55,7 +55,7 @@ INSTALLED_APPS = [
"oauth2_provider",
"tz_detect",
"sass_processor",
"simple_history",
"auditlog",
"markdownx",
"polymorphic",
"easy_thumbnails",
@ -85,7 +85,7 @@ MIDDLEWARE = [
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"hijack.middleware.HijackUserMiddleware",
"tz_detect.middleware.TimezoneMiddleware",
"simple_history.middleware.HistoryRequestMiddleware",
"auditlog.middleware.AuditlogMiddleware",
"maintenance_mode.middleware.MaintenanceModeMiddleware", # this should be last
]

View file

@ -10,7 +10,8 @@ class CatalogConfig(AppConfig):
from catalog import models
from catalog import sites
from journal import models as journal_models
from catalog.models import init_catalog_search_models
from catalog.models import init_catalog_search_models, init_catalog_audit_log
from catalog import api
init_catalog_search_models()
init_catalog_audit_log()

View file

@ -1,3 +1,4 @@
from functools import cached_property
from polymorphic.models import PolymorphicModel
from django.db import models
import logging
@ -8,7 +9,6 @@ from django.utils import timezone
from django.core.files.uploadedfile import SimpleUploadedFile
from django.contrib.contenttypes.models import ContentType
from django.utils.baseconv import base62
from simple_history.models import HistoricalRecords
import uuid
from typing import cast
from .utils import DEFAULT_ITEM_COVER, item_cover_path, resource_cover_path
@ -17,6 +17,8 @@ from django.conf import settings
from users.models import User
from django.db import connection
from ninja import Schema
from auditlog.context import disable_auditlog
from auditlog.models import LogEntry, AuditlogHistoryField
_logger = logging.getLogger(__name__)
@ -259,7 +261,6 @@ class Item(SoftDeleteMixin, PolymorphicModel):
created_time = models.DateTimeField(auto_now_add=True)
edited_time = models.DateTimeField(auto_now=True)
is_deleted = models.BooleanField(default=False, db_index=True)
history = HistoricalRecords()
merged_to_item = models.ForeignKey(
"Item",
null=True,
@ -279,6 +280,15 @@ class Item(SoftDeleteMixin, PolymorphicModel):
]
]
_content_type_ids = []
@cached_property
def history(self):
# can't use AuditlogHistoryField bc it will only return history with current content type
return LogEntry.objects.filter(
object_id=self.id, content_type_id__in=self._content_type_ids
)
def clear(self):
self.set_parent_item(None)
self.primary_lookup_id_value = None
@ -348,13 +358,25 @@ class Item(SoftDeleteMixin, PolymorphicModel):
if model not in Item.__subclasses__():
raise ValueError("invalid model to recast to")
ct = ContentType.objects.get_for_model(model)
old_ct = self.polymorphic_ctype
tbl = self.__class__._meta.db_table
obj = model(item_ptr_id=self.pk, polymorphic_ctype=ct)
obj.save_base(raw=True)
obj.save(update_fields=["polymorphic_ctype"])
with connection.cursor() as cursor:
cursor.execute(f"DELETE FROM {tbl} WHERE item_ptr_id = %s", [self.pk])
return model.objects.get(pk=obj.pk)
with disable_auditlog():
# disable audit as serialization won't work here
obj = model(item_ptr_id=self.pk, polymorphic_ctype=ct)
obj.save_base(raw=True)
obj.save(update_fields=["polymorphic_ctype"])
with connection.cursor() as cursor:
cursor.execute(f"DELETE FROM {tbl} WHERE item_ptr_id = %s", [self.pk])
obj = model.objects.get(pk=obj.pk)
LogEntry.objects.log_create(
obj,
action=LogEntry.Action.UPDATE,
changes={
"polymorphic_ctype_id": [old_ct.id, ct.id],
"__model__": [old_ct.model, ct.model],
},
)
return obj
@property
def uuid(self):

View file

@ -24,6 +24,7 @@ from .search.models import Indexer
from django.contrib.contenttypes.models import ContentType
from django.conf import settings
import logging
from auditlog.registry import auditlog
_logger = logging.getLogger(__name__)
@ -86,3 +87,16 @@ def init_catalog_search_models():
Indexer.update_model_indexable(Performance)
# Indexer.update_model_indexable(PerformanceProduction)
# Indexer.update_model_indexable(CatalogCollection)
def init_catalog_audit_log():
for cls in Item.__subclasses__():
auditlog.register(
cls, exclude_fields=["metadata", "created_time", "edited_time"]
)
auditlog.register(
ExternalResource, include_fields=["item", "id_type", "id_value", "url"]
)
Item._content_type_ids = list(all_content_types().values())

View file

@ -53,6 +53,39 @@
onclick="{% if item %}window.location='{{ item.url }}'{% else %}history.go(-1){% endif %}">
</div>
</form>
{% if request.user.is_superuser %}
<div>
<table class="table table-striped table-bordered">
<thead>
<tr style="background:#eee;">
<th>Field</th>
<th style="width:40%;padding-right: 8px;">From</th>
<th style="width:40%;">To</th>
</tr>
</thead>
<tbody>
{% for log in item.history.all %}
<tr>
<td colspan="3" style="padding-top:1em;">
({{ log.id }}) <b>{{ log.actor }} {{ log.get_action_display }} on {{ log.timestamp }}</b>
</td>
</tr>
{% for key, value in log.changes_dict.items %}
<tr {% cycle 'style="background:#eee;"' '' %}>
<td>{{ key }}</td>
<td style="padding-left: 8px">{{ value.0|default:"None" }}</td>
<td>{{ value.1|default:"None" }}</td>
</tr>
{% empty %}
<p>No data.</p>
{% endfor %}
{% empty %}
<p>No history for this item has been logged yet.</p>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
<aside class="grid__aside">
{% include "_sidebar_edit.html" %}

View file

@ -9,7 +9,7 @@ libsass
django-compressor
django-sass-processor
django-rq
django-simple-history
django-auditlog @ git+https://github.com/jazzband/django-auditlog.git@45591463e8192b4ac0095e259cc4dcea0ac2fd6c
django-hijack
django-user-messages
django-slack