mirror of
https://github.com/jazzband/django-auditlog.git
synced 2026-03-16 22:20:26 +00:00
Make diffing more robust for polymorphic models (#784)
* Add failing test for diffing polymorphic model instances. * Make diffing more robust for polymorphic models: When working with polymorphic models, where a child model inherits from a parent model, Django's pre_save signal may send model instances in a way where the log_update() handler receives an instance of the child as the `old` model, but an instance of the parent as the `new` model. This leads to a `FieldDoesNotExist` error when a field that only exists on the child was modified, and `get_field_value()` attempts look up that field on the parent. This change makes diffing polymorphic models more robust by considering this case in `get_default_value()`. Changes to those child fields won't be tracked in these cases, but at least `django-auditlog` won't prevent the model from being saved.
This commit is contained in:
parent
074e6aa145
commit
d02ed6b9e0
4 changed files with 29 additions and 2 deletions
|
|
@ -7,6 +7,10 @@
|
||||||
- Add `AUDITLOG_USE_BASE_MANAGER` setting to override default manager use ([#766](https://github.com/jazzband/django-auditlog/pull/766))
|
- Add `AUDITLOG_USE_BASE_MANAGER` setting to override default manager use ([#766](https://github.com/jazzband/django-auditlog/pull/766))
|
||||||
- Drop 'Python 3.9' support ([#773](https://github.com/jazzband/django-auditlog/pull/773))
|
- Drop 'Python 3.9' support ([#773](https://github.com/jazzband/django-auditlog/pull/773))
|
||||||
|
|
||||||
|
#### Fixes
|
||||||
|
|
||||||
|
- Make diffing more robust for polymorphic models ([#784](https://github.com/jazzband/django-auditlog/pull/784))
|
||||||
|
|
||||||
## 3.3.0 (2025-09-18)
|
## 3.3.0 (2025-09-18)
|
||||||
|
|
||||||
#### Improvements
|
#### Improvements
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from collections.abc import Callable
|
||||||
from datetime import timezone
|
from datetime import timezone
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist
|
||||||
from django.db.models import NOT_PROVIDED, DateTimeField, ForeignKey, JSONField, Model
|
from django.db.models import NOT_PROVIDED, DateTimeField, ForeignKey, JSONField, Model
|
||||||
from django.utils import timezone as django_timezone
|
from django.utils import timezone as django_timezone
|
||||||
from django.utils.encoding import smart_str
|
from django.utils.encoding import smart_str
|
||||||
|
|
@ -74,7 +74,7 @@ def get_field_value(obj, field, use_json_for_changes=False):
|
||||||
try:
|
try:
|
||||||
model_field = obj._meta.get_field(field.name)
|
model_field = obj._meta.get_field(field.name)
|
||||||
default = model_field.default
|
default = model_field.default
|
||||||
except AttributeError:
|
except (AttributeError, FieldDoesNotExist):
|
||||||
default = NOT_PROVIDED
|
default = NOT_PROVIDED
|
||||||
|
|
||||||
if default is NOT_PROVIDED:
|
if default is NOT_PROVIDED:
|
||||||
|
|
|
||||||
|
|
@ -487,6 +487,7 @@ auditlog.register(AltPrimaryKeyModel)
|
||||||
auditlog.register(UUIDPrimaryKeyModel)
|
auditlog.register(UUIDPrimaryKeyModel)
|
||||||
auditlog.register(ModelPrimaryKeyModel)
|
auditlog.register(ModelPrimaryKeyModel)
|
||||||
auditlog.register(ProxyModel)
|
auditlog.register(ProxyModel)
|
||||||
|
auditlog.register(RelatedModelParent)
|
||||||
auditlog.register(RelatedModel)
|
auditlog.register(RelatedModel)
|
||||||
auditlog.register(ManyRelatedModel)
|
auditlog.register(ManyRelatedModel)
|
||||||
auditlog.register(ManyRelatedModel.recursive.through)
|
auditlog.register(ManyRelatedModel.recursive.through)
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ from test_app.models import (
|
||||||
NullableJSONModel,
|
NullableJSONModel,
|
||||||
ProxyModel,
|
ProxyModel,
|
||||||
RelatedModel,
|
RelatedModel,
|
||||||
|
RelatedModelParent,
|
||||||
ReusableThroughRelatedModel,
|
ReusableThroughRelatedModel,
|
||||||
SecretM2MModel,
|
SecretM2MModel,
|
||||||
SecretRelatedModel,
|
SecretRelatedModel,
|
||||||
|
|
@ -2131,6 +2132,27 @@ class ModelInstanceDiffTest(TestCase):
|
||||||
model_instance_diff(simple2, simple1)
|
model_instance_diff(simple2, simple1)
|
||||||
model_instance_diff(simple1, simple2)
|
model_instance_diff(simple1, simple2)
|
||||||
|
|
||||||
|
def test_diff_polymorphic_models(self):
|
||||||
|
"""No error is raised when comparing parent/child for polymorphic models."""
|
||||||
|
|
||||||
|
# This tests that when a polymorphic model is compared to its parent,
|
||||||
|
# no FieldDoesNotExist errors are raised because those fields don't exist
|
||||||
|
# on the parent model.
|
||||||
|
|
||||||
|
# relation target
|
||||||
|
simple = SimpleModel()
|
||||||
|
simple.save()
|
||||||
|
|
||||||
|
# the parent model
|
||||||
|
related_parent = RelatedModelParent()
|
||||||
|
related_parent.save()
|
||||||
|
|
||||||
|
# the child model, with some fields that don't exist on the parent
|
||||||
|
related = RelatedModel(related=simple, one_to_one=simple)
|
||||||
|
related.save()
|
||||||
|
|
||||||
|
model_instance_diff(related, related_parent)
|
||||||
|
|
||||||
def test_object_repr_related_deleted(self):
|
def test_object_repr_related_deleted(self):
|
||||||
"""No error is raised when __str__() loads a related object that has been deleted."""
|
"""No error is raised when __str__() loads a related object that has been deleted."""
|
||||||
simple = SimpleModel()
|
simple = SimpleModel()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue