diff --git a/CHANGELOG.md b/CHANGELOG.md index fc1c8dc6..5419869b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - feat: Added pre-log and post-log signals. ([#483](https://github.com/jazzband/django-auditlog/pull/483)) - feat: Make timestamp in LogEntry overwritable. ([#476](https://github.com/jazzband/django-auditlog/pull/476)) - feat: Support excluding field names globally when ```AUDITLOG_INCLUDE_ALL_MODELS``` is enabled. ([#498](https://github.com/jazzband/django-auditlog/pull/498)) +- feat: Improved auto model registration to include auto-created models and exclude non-managed models, and automatically register m2m fields for models. ([#550](https://github.com/jazzband/django-auditlog/pull/550)) #### Fixes diff --git a/auditlog/registry.py b/auditlog/registry.py index 3bb9a646..3f1f8f30 100644 --- a/auditlog/registry.py +++ b/auditlog/registry.py @@ -13,7 +13,7 @@ ) from django.apps import apps -from django.db.models import Model +from django.db.models import ManyToManyField, Model from django.db.models.base import ModelBase from django.db.models.signals import ( ModelSignal, @@ -336,12 +336,28 @@ def register_from_settings(self): exclude_models = self._get_exclude_models( settings.AUDITLOG_EXCLUDE_TRACKING_MODELS ) - models = apps.get_models() - for model in models: + for model in apps.get_models(include_auto_created=True): if model in exclude_models: continue - self.register(model) + + meta = model._meta + if not meta.managed: + continue + + m2m_fields = [ + m.name for m in meta.get_fields() if isinstance(m, ManyToManyField) + ] + + exclude_fields = [ + i.related_name + for i in meta.related_objects + if i.related_name and not i.related_model._meta.managed + ] + + self.register( + model=model, m2m_fields=m2m_fields, exclude_fields=exclude_fields + ) self._register_models(settings.AUDITLOG_INCLUDE_TRACKING_MODELS) diff --git a/auditlog_tests/models.py b/auditlog_tests/models.py index 6b69eecd..1d454a84 100644 --- a/auditlog_tests/models.py +++ b/auditlog_tests/models.py @@ -304,6 +304,29 @@ class SerializeNaturalKeyRelatedModel(models.Model): history = AuditlogHistoryField(delete_related=False) +class SimpleNonManagedModel(models.Model): + """ + A simple model with no special things going on. + """ + + text = models.TextField(blank=True) + boolean = models.BooleanField(default=False) + integer = models.IntegerField(blank=True, null=True) + datetime = models.DateTimeField(auto_now=True) + + history = AuditlogHistoryField() + + def __str__(self): + return self.text + + class Meta: + managed = False + + +class AutoManyRelatedModel(models.Model): + related = models.ManyToManyField(SimpleModel) + + auditlog.register(AltPrimaryKeyModel) auditlog.register(UUIDPrimaryKeyModel) auditlog.register(ProxyModel) diff --git a/auditlog_tests/tests.py b/auditlog_tests/tests.py index f5726440..12a47250 100644 --- a/auditlog_tests/tests.py +++ b/auditlog_tests/tests.py @@ -36,6 +36,7 @@ from auditlog_tests.models import ( AdditionalDataIncludedModel, AltPrimaryKeyModel, + AutoManyRelatedModel, CharfieldTextfieldModel, ChoicesFieldModel, DateTimeFieldModel, @@ -55,6 +56,7 @@ SimpleMappingModel, SimpleMaskedModel, SimpleModel, + SimpleNonManagedModel, UUIDPrimaryKeyModel, ) @@ -1136,7 +1138,7 @@ def test_register_models_register_app(self): self.assertTrue(self.test_auditlog.contains(SimpleExcludeModel)) self.assertTrue(self.test_auditlog.contains(ChoicesFieldModel)) - self.assertEqual(len(self.test_auditlog.get_models()), 23) + self.assertEqual(len(self.test_auditlog.get_models()), 25) def test_register_models_register_model_with_attrs(self): self.test_auditlog._register_models( @@ -1330,6 +1332,32 @@ def test_registration_error_if_bad_serialize_params(self): SimpleModel, serialize_kwargs={"fields": ["text", "integer"]} ) + @override_settings(AUDITLOG_INCLUDE_ALL_MODELS=True) + def test_register_from_settings_register_all_models_excluding_non_managed_models( + self, + ): + self.test_auditlog.register_from_settings() + + self.assertFalse(self.test_auditlog.contains(SimpleNonManagedModel)) + + @override_settings(AUDITLOG_INCLUDE_ALL_MODELS=True) + def test_register_from_settings_register_all_models_and_figure_out_m2m_fields(self): + self.test_auditlog.register_from_settings() + + self.assertIn( + "related", self.test_auditlog._registry[AutoManyRelatedModel]["m2m_fields"] + ) + + @override_settings(AUDITLOG_INCLUDE_ALL_MODELS=True) + def test_register_from_settings_register_all_models_including_auto_created_models( + self, + ): + self.test_auditlog.register_from_settings() + + self.assertTrue( + self.test_auditlog.contains(AutoManyRelatedModel.related.through) + ) + class ChoicesFieldModelTest(TestCase): def setUp(self):