From 959786ebe36408f3a43b814c43ee867ca3c66592 Mon Sep 17 00:00:00 2001 From: Nick Sandford Date: Wed, 29 Aug 2018 11:24:51 +1000 Subject: [PATCH] Catch deferred attribute exception --- CHANGES.rst | 3 ++- model_utils/tracker.py | 5 ++++- tests/models.py | 15 +++++++++++++++ tests/test_fields/test_field_tracker.py | 7 ++++++- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 12d525f5..ff9abf0e 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 837e1ce9..6f50d10d 100644 --- a/model_utils/tracker.py +++ b/model_utils/tracker.py @@ -40,7 +40,10 @@ def __get__(self, instance, owner): 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 4e33a14a..ef386265 100644 --- a/tests/models.py +++ b/tests/models.py @@ -224,6 +224,13 @@ def get_queryset(self): 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 5b5d5c23..83cde073 100644 --- a/tests/test_fields/test_field_tracker.py +++ b/tests/test_fields/test_field_tracker.py @@ -8,7 +8,7 @@ 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 @@ def test_child_fields_not_tracked(self): self.name2 = 'test' self.assertEqual(self.tracker.previous('name2'), None) self.assertTrue(self.tracker.has_changed('name2')) + + +class AbstractModelTrackerTests(FieldTrackerTestCase): + + tracked_class = TrackedAbstract