Skip to content

Commit 9b41414

Browse files
committed
Initial
1 parent 22dcbc4 commit 9b41414

File tree

15 files changed

+94
-22
lines changed

15 files changed

+94
-22
lines changed

auditlog/__init__.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,26 @@
11
from importlib.metadata import version
22

3+
from django.apps import apps as django_apps
4+
from django.conf import settings
5+
from django.core.exceptions import ImproperlyConfigured
6+
37
__version__ = version("django-auditlog")
8+
9+
10+
def get_logentry_model():
11+
"""
12+
Return the LogEntry model that is active in this project.
13+
"""
14+
try:
15+
return django_apps.get_model(
16+
settings.AUDITLOG_LOGENTRY_MODEL, require_ready=False
17+
)
18+
except ValueError:
19+
raise ImproperlyConfigured(
20+
"AUDITLOG_LOGENTRY_MODEL must be of the form 'app_label.model_name'"
21+
)
22+
except LookupError:
23+
raise ImproperlyConfigured(
24+
"AUDITLOG_LOGENTRY_MODEL refers to model '%s' that has not been installed"
25+
% settings.AUDITLOG_LOGENTRY_MODEL
26+
)

auditlog/admin.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
from django.contrib.auth import get_user_model
55
from django.utils.translation import gettext_lazy as _
66

7+
from auditlog import get_logentry_model
78
from auditlog.filters import CIDFilter, ResourceTypeFilter
89
from auditlog.mixins import LogEntryAdminMixin
9-
from auditlog.models import LogEntry
10+
11+
LogEntry = get_logentry_model()
1012

1113

1214
@admin.register(LogEntry)

auditlog/conf.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,8 @@
4545
settings.AUDITLOG_DISABLE_REMOTE_ADDR = getattr(
4646
settings, "AUDITLOG_DISABLE_REMOTE_ADDR", False
4747
)
48+
49+
# Swap default model
50+
settings.AUDITLOG_LOGENTRY_MODEL = getattr(
51+
settings, "AUDITLOG_LOGENTRY_MODEL", "auditlog.LogEntry"
52+
)

auditlog/context.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@
66
from django.contrib.auth import get_user_model
77
from django.db.models.signals import pre_save
88

9-
from auditlog.models import LogEntry
9+
from auditlog import get_logentry_model
1010

1111
auditlog_value = ContextVar("auditlog_value")
1212
auditlog_disabled = ContextVar("auditlog_disabled", default=False)
1313

1414

15+
LogEntry = get_logentry_model()
16+
17+
1518
@contextlib.contextmanager
1619
def set_actor(actor, remote_addr=None):
1720
"""Connect a signal receiver with current user attached."""

auditlog/diff.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ def track_field(field):
2020
:return: Whether the given field should be tracked.
2121
:rtype: bool
2222
"""
23-
from auditlog.models import LogEntry
23+
from auditlog import get_logentry_model
24+
25+
LogEntry = get_logentry_model()
2426

2527
# Do not track many to many relations
2628
if field.many_to_many:

auditlog/management/commands/auditlogflush.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
from django.core.management.base import BaseCommand
44

5-
from auditlog.models import LogEntry
5+
from auditlog import get_logentry_model
6+
7+
LogEntry = get_logentry_model()
68

79

810
class Command(BaseCommand):

auditlog/management/commands/auditlogmigratejson.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
from django.core.management import CommandError, CommandParser
55
from django.core.management.base import BaseCommand
66

7-
from auditlog.models import LogEntry
7+
from auditlog import get_logentry_model
8+
9+
LogEntry = get_logentry_model()
810

911

1012
class Command(BaseCommand):

auditlog/migrations/0001_initial.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class Migration(migrations.Migration):
6767
),
6868
],
6969
options={
70+
"swappable": "AUDITLOG_LOGENTRY_MODEL",
7071
"ordering": ["-timestamp"],
7172
"get_latest_by": "timestamp",
7273
"verbose_name": "log entry",

auditlog/mixins.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010
from django.utils.timezone import is_aware, localtime
1111
from django.utils.translation import gettext_lazy as _
1212

13-
from auditlog.models import LogEntry
13+
from auditlog import get_logentry_model
1414
from auditlog.registry import auditlog
1515
from auditlog.signals import accessed
1616

1717
MAX = 75
1818

19+
LogEntry = get_logentry_model()
20+
1921

2022
class LogEntryAdminMixin:
2123
request: HttpRequest

auditlog/models.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -302,17 +302,7 @@ def _mask_serialized_fields(
302302
return data
303303

304304

305-
class LogEntry(models.Model):
306-
"""
307-
Represents an entry in the audit log. The content type is saved along with the textual and numeric
308-
(if available) primary key, as well as the textual representation of the object when it was saved.
309-
It holds the action performed and the fields that were changed in the transaction.
310-
311-
If AuditlogMiddleware is used, the actor will be set automatically. Keep in mind that
312-
editing / re-saving LogEntry instances may set the actor to a wrong value - editing LogEntry
313-
instances is not recommended (and it should not be necessary).
314-
"""
315-
305+
class AbstractLogEntry(models.Model):
316306
class Action:
317307
"""
318308
The actions that Auditlog distinguishes: creating, updating and deleting objects. Viewing objects
@@ -385,6 +375,7 @@ class Action:
385375
objects = LogEntryManager()
386376

387377
class Meta:
378+
abstract = True
388379
get_latest_by = "timestamp"
389380
ordering = ["-timestamp"]
390381
verbose_name = _("log entry")
@@ -544,6 +535,21 @@ def _get_changes_display_for_fk_field(
544535
return f"Deleted '{field.related_model.__name__}' ({value})"
545536

546537

538+
class LogEntry(AbstractLogEntry):
539+
"""
540+
Represents an entry in the audit log. The content type is saved along with the textual and numeric
541+
(if available) primary key, as well as the textual representation of the object when it was saved.
542+
It holds the action performed and the fields that were changed in the transaction.
543+
544+
If AuditlogMiddleware is used, the actor will be set automatically. Keep in mind that
545+
editing / re-saving LogEntry instances may set the actor to a wrong value - editing LogEntry
546+
instances is not recommended (and it should not be necessary).
547+
"""
548+
549+
class Meta(AbstractLogEntry.Meta):
550+
swappable = "AUDITLOG_LOGENTRY_MODEL"
551+
552+
547553
class AuditlogHistoryField(GenericRelation):
548554
"""
549555
A subclass of py:class:`django.contrib.contenttypes.fields.GenericRelation` that sets some default

auditlog/receivers.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
from django.conf import settings
44

5+
from auditlog import get_logentry_model
56
from auditlog.context import auditlog_disabled
67
from auditlog.diff import model_instance_diff
7-
from auditlog.models import LogEntry
88
from auditlog.signals import post_log, pre_log
99

10+
LogEntry = get_logentry_model()
11+
1012

1113
def check_disable(signal_handler):
1214
"""

auditlog/registry.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class AuditlogModelRegistry:
3838
A registry that keeps track of the models that use Auditlog to track changes.
3939
"""
4040

41-
DEFAULT_EXCLUDE_MODELS = ("auditlog.LogEntry", "admin.LogEntry")
41+
DEFAULT_EXCLUDE_MODELS = (settings.AUDITLOG_LOGENTRY_MODEL, "admin.LogEntry")
4242

4343
def __init__(
4444
self,

auditlog_tests/models.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from django.core.serializers.json import DjangoJSONEncoder
55
from django.db import models
66

7-
from auditlog.models import AuditlogHistoryField
7+
from auditlog.models import AbstractLogEntry, AuditlogHistoryField
88
from auditlog.registry import AuditlogModelRegistry, auditlog
99

1010
m2m_only_auditlog = AuditlogModelRegistry(create=False, update=False, delete=False)
@@ -356,6 +356,10 @@ class AutoManyRelatedModel(models.Model):
356356
related = models.ManyToManyField(SimpleModel)
357357

358358

359+
class CustomLogEntryModel(AbstractLogEntry):
360+
pass
361+
362+
359363
auditlog.register(AltPrimaryKeyModel)
360364
auditlog.register(UUIDPrimaryKeyModel)
361365
auditlog.register(ModelPrimaryKeyModel)

auditlog_tests/test_two_step_json_migration.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
from django.core.management import CommandError, call_command
66
from django.test import TestCase, override_settings
77

8-
from auditlog.models import LogEntry
8+
from auditlog import get_logentry_model
99
from auditlog_tests.models import SimpleModel
1010

11+
LogEntry = get_logentry_model()
12+
1113

1214
class TwoStepMigrationTest(TestCase):
1315
def test_use_text_changes_first(self):

auditlog_tests/tests.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@
2727
from django.utils.encoding import smart_str
2828
from django.utils.translation import gettext_lazy as _
2929

30+
from auditlog import get_logentry_model
3031
from auditlog.admin import LogEntryAdmin
3132
from auditlog.cid import get_cid
3233
from auditlog.context import disable_auditlog, set_actor
3334
from auditlog.diff import model_instance_diff
3435
from auditlog.middleware import AuditlogMiddleware
35-
from auditlog.models import DEFAULT_OBJECT_REPR, LogEntry
36+
from auditlog.models import DEFAULT_OBJECT_REPR
3637
from auditlog.registry import AuditlogModelRegistry, AuditLogRegistrationError, auditlog
3738
from auditlog.signals import post_log, pre_log
3839
from auditlog_tests.fixtures.custom_get_cid import get_cid as custom_get_cid
@@ -42,6 +43,7 @@
4243
AutoManyRelatedModel,
4344
CharfieldTextfieldModel,
4445
ChoicesFieldModel,
46+
CustomLogEntryModel,
4547
DateTimeFieldModel,
4648
JSONModel,
4749
ManyRelatedModel,
@@ -65,6 +67,8 @@
6567
UUIDPrimaryKeyModel,
6668
)
6769

70+
LogEntry = get_logentry_model()
71+
6872

6973
class SimpleModelTest(TestCase):
7074
def setUp(self):
@@ -2733,3 +2737,15 @@ def test_get_changes_for_missing_model(self):
27332737
history = self.obj.history.latest()
27342738
self.assertEqual(history.changes_dict["text"][1], self.obj.text)
27352739
self.assertEqual(history.changes_display_dict["text"][1], self.obj.text)
2740+
2741+
2742+
class SwappableLogEntryModelTest(TestCase):
2743+
2744+
def test_log_changes(self):
2745+
with override_settings(
2746+
AUDITLOG_LOGENTRY_MODEL="auditlog_tests.CustomLogEntryModel"
2747+
):
2748+
self.assertIsInstance(get_logentry_model(), CustomLogEntryModel)
2749+
obj = SimpleModel.objects.create(text="Hi!")
2750+
history = obj.history.latest()
2751+
self.assertEqual(history, CustomLogEntryModel.objects.first())

0 commit comments

Comments
 (0)