diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bf16d8..32e8637 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ via `AUDITLOG_MASK_TRACKING_FIELDS` setting. ([#702](https://github.com/jazzband - fix: Use sender instead of receiver for `m2m_changed` signal ID to prevent duplicate entries for models that share a related model. ([#686](https://github.com/jazzband/django-auditlog/pull/686)) - Fixed a problem when setting `Value(None)` in `JSONField` ([#646](https://github.com/jazzband/django-auditlog/pull/646)) - Fixed a problem when setting `django.db.models.functions.Now()` in `DateTimeField` ([#635](https://github.com/jazzband/django-auditlog/pull/635)) +- Use the [default manager](https://docs.djangoproject.com/en/5.1/topics/db/managers/#default-managers) instead of `objects` to support custom model managers. ([#705](https://github.com/jazzband/django-auditlog/pull/705)) ## 3.0.0 (2024-04-12) diff --git a/auditlog/models.py b/auditlog/models.py index 45e0d7f..e03206f 100644 --- a/auditlog/models.py +++ b/auditlog/models.py @@ -542,7 +542,7 @@ class LogEntry(models.Model): return value # Attempt to return the string representation of the object try: - return smart_str(field.related_model.objects.get(pk=pk_value)) + return smart_str(field.related_model._default_manager.get(pk=pk_value)) # ObjectDoesNotExist will be raised if the object was deleted. except ObjectDoesNotExist: return f"Deleted '{field.related_model.__name__}' ({value})" diff --git a/auditlog/receivers.py b/auditlog/receivers.py index 02405d5..77e60f5 100644 --- a/auditlog/receivers.py +++ b/auditlog/receivers.py @@ -55,7 +55,7 @@ def log_update(sender, instance, **kwargs): """ if not instance._state.adding: update_fields = kwargs.get("update_fields", None) - old = sender.objects.filter(pk=instance.pk).first() + old = sender._default_manager.filter(pk=instance.pk).first() _create_log_entry( action=LogEntry.Action.UPDATE, instance=instance, @@ -156,9 +156,11 @@ def make_log_m2m_changes(field_name): return if action == "post_clear": - changed_queryset = kwargs["model"].objects.all() + changed_queryset = kwargs["model"]._default_manager.all() else: - changed_queryset = kwargs["model"].objects.filter(pk__in=kwargs["pk_set"]) + changed_queryset = kwargs["model"]._default_manager.filter( + pk__in=kwargs["pk_set"] + ) if action in ["post_add"]: LogEntry.objects.log_m2m_changes( diff --git a/auditlog_tests/models.py b/auditlog_tests/models.py index 819556b..38d6966 100644 --- a/auditlog_tests/models.py +++ b/auditlog_tests/models.py @@ -407,6 +407,19 @@ class SimpleNonManagedModel(models.Model): managed = False +class SecretManager(models.Manager): + def get_queryset(self): + return super().get_queryset().filter(is_secret=False) + + +@auditlog.register() +class SwappedManagerModel(models.Model): + is_secret = models.BooleanField(default=False) + name = models.CharField(max_length=255) + + objects = SecretManager() + + class AutoManyRelatedModel(models.Model): related = models.ManyToManyField(SimpleModel) diff --git a/auditlog_tests/tests.py b/auditlog_tests/tests.py index 1904059..5c8ab65 100644 --- a/auditlog_tests/tests.py +++ b/auditlog_tests/tests.py @@ -64,6 +64,7 @@ from auditlog_tests.models import ( SimpleMaskedModel, SimpleModel, SimpleNonManagedModel, + SwappedManagerModel, UUIDPrimaryKeyModel, ) @@ -1270,7 +1271,7 @@ class RegisterModelSettingsTest(TestCase): self.assertTrue(self.test_auditlog.contains(SimpleExcludeModel)) self.assertTrue(self.test_auditlog.contains(ChoicesFieldModel)) - self.assertEqual(len(self.test_auditlog.get_models()), 31) + self.assertEqual(len(self.test_auditlog.get_models()), 32) def test_register_models_register_model_with_attrs(self): self.test_auditlog._register_models( @@ -2828,3 +2829,28 @@ class MissingModelTest(TestCase): history = self.obj.history.latest() self.assertEqual(history.changes_dict["text"][1], self.obj.text) self.assertEqual(history.changes_display_dict["text"][1], self.obj.text) + + +class ModelManagerTest(TestCase): + """ + This does not directly assert the configured manager, but its behaviour. + The "secret" object should not be accessible, as the queryset is overridden. + """ + + def setUp(self): + self.secret = SwappedManagerModel.objects.create(is_secret=True, name="Secret") + self.public = SwappedManagerModel.objects.create(is_secret=False, name="Public") + + def test_update_secret(self): + self.secret.name = "Updated" + self.secret.save() + log = LogEntry.objects.get_for_object(self.secret).first() + self.assertEqual(log.action, LogEntry.Action.UPDATE) + self.assertEqual(log.changes_dict["name"], ["None", "Updated"]) + + def test_update_public(self): + self.public.name = "Updated" + self.public.save() + log = LogEntry.objects.get_for_object(self.public).first() + self.assertEqual(log.action, LogEntry.Action.UPDATE) + self.assertEqual(log.changes_dict["name"], ["Public", "Updated"])