diff --git a/CHANGES.rst b/CHANGES.rst index 12d525f..ff9abf0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,7 @@ CHANGES master (unreleased) ------------------- +- Catch `AttributeError` for deferred abstract fields, fixes GH-331. - Update documentation to explain usage of `timeframed` model manager, fixes GH-118 - Honor `OneToOneField.parent_link=False`. - Fix handling of deferred attributes on Django 1.10+, fixes GH-278 @@ -14,7 +15,7 @@ master (unreleased) - Support `reversed` for all kinds of `Choices` objects, fixes GH-309 - Fix Model instance non picklable GH-330 - Fix patched `save` in FieldTracker -- Upgrades test requirements (pytest, pytest-django, pytest-cov) and +- Upgrades test requirements (pytest, pytest-django, pytest-cov) and skips tox test with Python 3.5 and Django (trunk) 3.1.2 (2018.05.09) diff --git a/model_utils/tracker.py b/model_utils/tracker.py index 837e1ce..6f50d10 100644 --- a/model_utils/tracker.py +++ b/model_utils/tracker.py @@ -40,7 +40,10 @@ class DescriptorWrapper(object): if instance is None: return self was_deferred = self.field_name in instance.get_deferred_fields() - value = self.descriptor.__get__(instance, owner) + try: + value = self.descriptor.__get__(instance, owner) + except AttributeError: + value = self.descriptor if was_deferred: tracker_instance = getattr(instance, self.tracker_attname) tracker_instance.saved_data[self.field_name] = deepcopy(value) diff --git a/tests/models.py b/tests/models.py index 4e33a14..ef38626 100644 --- a/tests/models.py +++ b/tests/models.py @@ -224,6 +224,13 @@ class FeaturedManager(models.Manager): return ByAuthorQuerySet(self.model, **kwargs).filter(feature=True) +class AbstractTracked(models.Model): + number = 1 + + class Meta: + abstract = True + + class Tracked(models.Model): name = models.CharField(max_length=20) number = models.IntegerField() @@ -240,6 +247,14 @@ class TrackedFK(models.Model): custom_tracker_without_id = FieldTracker(fields=['fk']) +class TrackedAbstract(AbstractTracked): + name = models.CharField(max_length=20) + number = models.IntegerField() + mutable = MutableField(default=None) + + tracker = FieldTracker() + + class TrackedNotDefault(models.Model): name = models.CharField(max_length=20) number = models.IntegerField() diff --git a/tests/test_fields/test_field_tracker.py b/tests/test_fields/test_field_tracker.py index 5b5d5c2..83cde07 100644 --- a/tests/test_fields/test_field_tracker.py +++ b/tests/test_fields/test_field_tracker.py @@ -8,7 +8,7 @@ from model_utils import FieldTracker from model_utils.tracker import DescriptorWrapper from tests.models import ( Tracked, TrackedFK, InheritedTrackedFK, TrackedNotDefault, TrackedNonFieldAttr, TrackedMultiple, - InheritedTracked, TrackedFileField, + InheritedTracked, TrackedFileField, TrackedAbstract, ModelTracked, ModelTrackedFK, ModelTrackedNotDefault, ModelTrackedMultiple, InheritedModelTracked, ) @@ -785,3 +785,8 @@ class InheritedModelTrackerTests(ModelTrackerTests): self.name2 = 'test' self.assertEqual(self.tracker.previous('name2'), None) self.assertTrue(self.tracker.has_changed('name2')) + + +class AbstractModelTrackerTests(FieldTrackerTestCase): + + tracked_class = TrackedAbstract