mirror of
https://github.com/Hopiu/django-model-utils.git
synced 2026-03-16 20:00:23 +00:00
The decorator works without an argument as well, but that is an undocumented feature of `unittest.skip()` that is not understood by mypy and pytest. In the case of pytest, it ignored the decorated class during test collection, instead of collecting it and marking it as skipped.
949 lines
34 KiB
Python
949 lines
34 KiB
Python
from unittest import skip
|
|
|
|
from django.core.cache import cache
|
|
from django.core.exceptions import FieldError
|
|
from django.db.models.fields.files import FieldFile
|
|
from django.test import TestCase
|
|
|
|
from model_utils import FieldTracker
|
|
from model_utils.tracker import DescriptorWrapper
|
|
from tests.models import (
|
|
InheritedModelTracked,
|
|
InheritedTracked,
|
|
InheritedTrackedFK,
|
|
ModelTracked,
|
|
ModelTrackedFK,
|
|
ModelTrackedMultiple,
|
|
ModelTrackedNotDefault,
|
|
Tracked,
|
|
TrackedAbstract,
|
|
TrackedFileField,
|
|
TrackedFK,
|
|
TrackedMultiple,
|
|
TrackedNonFieldAttr,
|
|
TrackedNotDefault,
|
|
TrackerTimeStamped,
|
|
)
|
|
|
|
|
|
class FieldTrackerTestCase(TestCase):
|
|
|
|
tracker = None
|
|
|
|
def assertHasChanged(self, **kwargs):
|
|
tracker = kwargs.pop('tracker', self.tracker)
|
|
for field, value in kwargs.items():
|
|
if value is None:
|
|
with self.assertRaises(FieldError):
|
|
tracker.has_changed(field)
|
|
else:
|
|
self.assertEqual(tracker.has_changed(field), value)
|
|
|
|
def assertPrevious(self, **kwargs):
|
|
tracker = kwargs.pop('tracker', self.tracker)
|
|
for field, value in kwargs.items():
|
|
self.assertEqual(tracker.previous(field), value)
|
|
|
|
def assertChanged(self, **kwargs):
|
|
tracker = kwargs.pop('tracker', self.tracker)
|
|
self.assertEqual(tracker.changed(), kwargs)
|
|
|
|
def assertCurrent(self, **kwargs):
|
|
tracker = kwargs.pop('tracker', self.tracker)
|
|
self.assertEqual(tracker.current(), kwargs)
|
|
|
|
def update_instance(self, **kwargs):
|
|
for field, value in kwargs.items():
|
|
setattr(self.instance, field, value)
|
|
self.instance.save()
|
|
|
|
|
|
class FieldTrackerCommonTests:
|
|
|
|
def test_pre_save_previous(self):
|
|
self.assertPrevious(name=None, number=None)
|
|
self.instance.name = 'new age'
|
|
self.instance.number = 8
|
|
self.assertPrevious(name=None, number=None)
|
|
|
|
|
|
class FieldTrackerTests(FieldTrackerTestCase, FieldTrackerCommonTests):
|
|
|
|
tracked_class = Tracked
|
|
|
|
def setUp(self):
|
|
self.instance = self.tracked_class()
|
|
self.tracker = self.instance.tracker
|
|
|
|
def test_descriptor(self):
|
|
self.assertTrue(isinstance(self.tracked_class.tracker, FieldTracker))
|
|
|
|
def test_pre_save_changed(self):
|
|
self.assertChanged(name=None)
|
|
self.instance.name = 'new age'
|
|
self.assertChanged(name=None)
|
|
self.instance.number = 8
|
|
self.assertChanged(name=None, number=None)
|
|
self.instance.name = ''
|
|
self.assertChanged(name=None, number=None)
|
|
self.instance.mutable = [1, 2, 3]
|
|
self.assertChanged(name=None, number=None, mutable=None)
|
|
|
|
def test_pre_save_has_changed(self):
|
|
self.assertHasChanged(name=True, number=False, mutable=False)
|
|
self.instance.name = 'new age'
|
|
self.assertHasChanged(name=True, number=False, mutable=False)
|
|
self.instance.number = 7
|
|
self.assertHasChanged(name=True, number=True)
|
|
self.instance.mutable = [1, 2, 3]
|
|
self.assertHasChanged(name=True, number=True, mutable=True)
|
|
|
|
def test_save_with_args(self):
|
|
self.instance.number = 1
|
|
self.instance.save(False, False, None, None)
|
|
self.assertChanged()
|
|
|
|
def test_first_save(self):
|
|
self.assertHasChanged(name=True, number=False, mutable=False)
|
|
self.assertPrevious(name=None, number=None, mutable=None)
|
|
self.assertCurrent(name='', number=None, id=None, mutable=None)
|
|
self.assertChanged(name=None)
|
|
self.instance.name = 'retro'
|
|
self.instance.number = 4
|
|
self.instance.mutable = [1, 2, 3]
|
|
self.assertHasChanged(name=True, number=True, mutable=True)
|
|
self.assertPrevious(name=None, number=None, mutable=None)
|
|
self.assertCurrent(name='retro', number=4, id=None, mutable=[1, 2, 3])
|
|
self.assertChanged(name=None, number=None, mutable=None)
|
|
|
|
self.instance.save(update_fields=[])
|
|
self.assertHasChanged(name=True, number=True, mutable=True)
|
|
self.assertPrevious(name=None, number=None, mutable=None)
|
|
self.assertCurrent(name='retro', number=4, id=None, mutable=[1, 2, 3])
|
|
self.assertChanged(name=None, number=None, mutable=None)
|
|
with self.assertRaises(ValueError):
|
|
self.instance.save(update_fields=['number'])
|
|
|
|
def test_post_save_has_changed(self):
|
|
self.update_instance(name='retro', number=4, mutable=[1, 2, 3])
|
|
self.assertHasChanged(name=False, number=False, mutable=False)
|
|
self.instance.name = 'new age'
|
|
self.assertHasChanged(name=True, number=False)
|
|
self.instance.number = 8
|
|
self.assertHasChanged(name=True, number=True)
|
|
self.instance.mutable[1] = 4
|
|
self.assertHasChanged(name=True, number=True, mutable=True)
|
|
self.instance.name = 'retro'
|
|
self.assertHasChanged(name=False, number=True, mutable=True)
|
|
|
|
def test_post_save_previous(self):
|
|
self.update_instance(name='retro', number=4, mutable=[1, 2, 3])
|
|
self.instance.name = 'new age'
|
|
self.assertPrevious(name='retro', number=4, mutable=[1, 2, 3])
|
|
self.instance.mutable[1] = 4
|
|
self.assertPrevious(name='retro', number=4, mutable=[1, 2, 3])
|
|
|
|
def test_post_save_changed(self):
|
|
self.update_instance(name='retro', number=4, mutable=[1, 2, 3])
|
|
self.assertChanged()
|
|
self.instance.name = 'new age'
|
|
self.assertChanged(name='retro')
|
|
self.instance.number = 8
|
|
self.assertChanged(name='retro', number=4)
|
|
self.instance.name = 'retro'
|
|
self.assertChanged(number=4)
|
|
self.instance.mutable[1] = 4
|
|
self.assertChanged(number=4, mutable=[1, 2, 3])
|
|
self.instance.mutable = [1, 2, 3]
|
|
self.assertChanged(number=4)
|
|
|
|
def test_current(self):
|
|
self.assertCurrent(id=None, name='', number=None, mutable=None)
|
|
self.instance.name = 'new age'
|
|
self.assertCurrent(id=None, name='new age', number=None, mutable=None)
|
|
self.instance.number = 8
|
|
self.assertCurrent(id=None, name='new age', number=8, mutable=None)
|
|
self.instance.mutable = [1, 2, 3]
|
|
self.assertCurrent(id=None, name='new age', number=8, mutable=[1, 2, 3])
|
|
self.instance.mutable[1] = 4
|
|
self.assertCurrent(id=None, name='new age', number=8, mutable=[1, 4, 3])
|
|
self.instance.save()
|
|
self.assertCurrent(id=self.instance.id, name='new age', number=8, mutable=[1, 4, 3])
|
|
|
|
def test_update_fields(self):
|
|
self.update_instance(name='retro', number=4, mutable=[1, 2, 3])
|
|
self.assertChanged()
|
|
self.instance.name = 'new age'
|
|
self.instance.number = 8
|
|
self.instance.mutable = [4, 5, 6]
|
|
self.assertChanged(name='retro', number=4, mutable=[1, 2, 3])
|
|
self.instance.save(update_fields=[])
|
|
self.assertChanged(name='retro', number=4, mutable=[1, 2, 3])
|
|
self.instance.save(update_fields=['name'])
|
|
in_db = self.tracked_class.objects.get(id=self.instance.id)
|
|
self.assertEqual(in_db.name, self.instance.name)
|
|
self.assertNotEqual(in_db.number, self.instance.number)
|
|
self.assertChanged(number=4, mutable=[1, 2, 3])
|
|
self.instance.save(update_fields=['number'])
|
|
self.assertChanged(mutable=[1, 2, 3])
|
|
self.instance.save(update_fields=['mutable'])
|
|
self.assertChanged()
|
|
in_db = self.tracked_class.objects.get(id=self.instance.id)
|
|
self.assertEqual(in_db.name, self.instance.name)
|
|
self.assertEqual(in_db.number, self.instance.number)
|
|
self.assertEqual(in_db.mutable, self.instance.mutable)
|
|
|
|
def test_refresh_from_db(self):
|
|
self.update_instance(name='retro', number=4, mutable=[1, 2, 3])
|
|
self.tracked_class.objects.filter(pk=self.instance.pk).update(
|
|
name='new age', number=8, mutable=[3, 2, 1])
|
|
self.assertChanged()
|
|
self.instance.name = 'like in db'
|
|
self.instance.number = 8
|
|
self.instance.mutable = [3, 2, 1]
|
|
self.assertChanged(name='retro', number=4, mutable=[1, 2, 3])
|
|
self.instance.refresh_from_db(fields=('name',))
|
|
self.assertChanged(number=4, mutable=[1, 2, 3])
|
|
self.instance.refresh_from_db(fields={'mutable'})
|
|
self.assertChanged(number=4)
|
|
self.instance.refresh_from_db()
|
|
self.assertChanged()
|
|
|
|
def test_with_deferred(self):
|
|
self.instance.name = 'new age'
|
|
self.instance.number = 1
|
|
self.instance.save()
|
|
item = self.tracked_class.objects.only('name').first()
|
|
self.assertTrue(item.get_deferred_fields())
|
|
|
|
# has_changed() returns False for deferred fields, without un-deferring them.
|
|
# Use an if because ModelTracked doesn't support has_changed() in this case.
|
|
if self.tracked_class == Tracked:
|
|
self.assertFalse(item.tracker.has_changed('number'))
|
|
self.assertIsInstance(item.__class__.number, DescriptorWrapper)
|
|
self.assertTrue('number' in item.get_deferred_fields())
|
|
|
|
# previous() un-defers field and returns value
|
|
self.assertEqual(item.tracker.previous('number'), 1)
|
|
self.assertNotIn('number', item.get_deferred_fields())
|
|
|
|
# examining a deferred field un-defers it
|
|
item = self.tracked_class.objects.only('name').first()
|
|
self.assertEqual(item.number, 1)
|
|
self.assertTrue('number' not in item.get_deferred_fields())
|
|
self.assertEqual(item.tracker.previous('number'), 1)
|
|
self.assertFalse(item.tracker.has_changed('number'))
|
|
|
|
# has_changed() returns correct values after deferred field is examined
|
|
self.assertFalse(item.tracker.has_changed('number'))
|
|
item.number = 2
|
|
self.assertTrue(item.tracker.has_changed('number'))
|
|
|
|
# previous() returns correct value after deferred field is examined
|
|
self.assertEqual(item.tracker.previous('number'), 1)
|
|
|
|
# assigning to a deferred field un-defers it
|
|
# Use an if because ModelTracked doesn't handle this case.
|
|
if self.tracked_class == Tracked:
|
|
|
|
item = self.tracked_class.objects.only('name').first()
|
|
item.number = 2
|
|
|
|
# previous() fetches correct value from database after deferred field is assigned
|
|
self.assertEqual(item.tracker.previous('number'), 1)
|
|
|
|
# database fetch of previous() value doesn't affect current value
|
|
self.assertEqual(item.number, 2)
|
|
|
|
# has_changed() returns correct values after deferred field is assigned
|
|
self.assertTrue(item.tracker.has_changed('number'))
|
|
item.number = 1
|
|
self.assertFalse(item.tracker.has_changed('number'))
|
|
|
|
|
|
class FieldTrackerMultipleInstancesTests(TestCase):
|
|
|
|
def test_with_deferred_fields_access_multiple(self):
|
|
Tracked.objects.create(pk=1, name='foo', number=1)
|
|
Tracked.objects.create(pk=2, name='bar', number=2)
|
|
|
|
queryset = Tracked.objects.only('id')
|
|
|
|
for instance in queryset:
|
|
instance.name
|
|
|
|
|
|
class FieldTrackedModelCustomTests(FieldTrackerTestCase,
|
|
FieldTrackerCommonTests):
|
|
|
|
tracked_class = TrackedNotDefault
|
|
|
|
def setUp(self):
|
|
self.instance = self.tracked_class()
|
|
self.tracker = self.instance.name_tracker
|
|
|
|
def test_pre_save_changed(self):
|
|
self.assertChanged(name=None)
|
|
self.instance.name = 'new age'
|
|
self.assertChanged(name=None)
|
|
self.instance.number = 8
|
|
self.assertChanged(name=None)
|
|
self.instance.name = ''
|
|
self.assertChanged(name=None)
|
|
|
|
def test_first_save(self):
|
|
self.assertHasChanged(name=True, number=None)
|
|
self.assertPrevious(name=None, number=None)
|
|
self.assertCurrent(name='')
|
|
self.assertChanged(name=None)
|
|
self.instance.name = 'retro'
|
|
self.instance.number = 4
|
|
self.assertHasChanged(name=True, number=None)
|
|
self.assertPrevious(name=None, number=None)
|
|
self.assertCurrent(name='retro')
|
|
self.assertChanged(name=None)
|
|
|
|
def test_pre_save_has_changed(self):
|
|
self.assertHasChanged(name=True, number=None)
|
|
self.instance.name = 'new age'
|
|
self.assertHasChanged(name=True, number=None)
|
|
self.instance.number = 7
|
|
self.assertHasChanged(name=True, number=None)
|
|
|
|
def test_post_save_has_changed(self):
|
|
self.update_instance(name='retro', number=4)
|
|
self.assertHasChanged(name=False, number=None)
|
|
self.instance.name = 'new age'
|
|
self.assertHasChanged(name=True, number=None)
|
|
self.instance.number = 8
|
|
self.assertHasChanged(name=True, number=None)
|
|
self.instance.name = 'retro'
|
|
self.assertHasChanged(name=False, number=None)
|
|
|
|
def test_post_save_previous(self):
|
|
self.update_instance(name='retro', number=4)
|
|
self.instance.name = 'new age'
|
|
self.assertPrevious(name='retro', number=None)
|
|
|
|
def test_post_save_changed(self):
|
|
self.update_instance(name='retro', number=4)
|
|
self.assertChanged()
|
|
self.instance.name = 'new age'
|
|
self.assertChanged(name='retro')
|
|
self.instance.number = 8
|
|
self.assertChanged(name='retro')
|
|
self.instance.name = 'retro'
|
|
self.assertChanged()
|
|
|
|
def test_current(self):
|
|
self.assertCurrent(name='')
|
|
self.instance.name = 'new age'
|
|
self.assertCurrent(name='new age')
|
|
self.instance.number = 8
|
|
self.assertCurrent(name='new age')
|
|
self.instance.save()
|
|
self.assertCurrent(name='new age')
|
|
|
|
def test_update_fields(self):
|
|
self.update_instance(name='retro', number=4)
|
|
self.assertChanged()
|
|
self.instance.name = 'new age'
|
|
self.instance.number = 8
|
|
self.instance.save(update_fields=['name', 'number'])
|
|
self.assertChanged()
|
|
|
|
|
|
class FieldTrackedModelAttributeTests(FieldTrackerTestCase):
|
|
|
|
tracked_class = TrackedNonFieldAttr
|
|
|
|
def setUp(self):
|
|
self.instance = self.tracked_class()
|
|
self.tracker = self.instance.tracker
|
|
|
|
def test_previous(self):
|
|
self.assertPrevious(rounded=None)
|
|
self.instance.number = 7.5
|
|
self.assertPrevious(rounded=None)
|
|
self.instance.save()
|
|
self.assertPrevious(rounded=8)
|
|
self.instance.number = 7.2
|
|
self.assertPrevious(rounded=8)
|
|
self.instance.save()
|
|
self.assertPrevious(rounded=7)
|
|
|
|
def test_has_changed(self):
|
|
self.assertHasChanged(rounded=False)
|
|
self.instance.number = 7.5
|
|
self.assertHasChanged(rounded=True)
|
|
self.instance.save()
|
|
self.assertHasChanged(rounded=False)
|
|
self.instance.number = 7.2
|
|
self.assertHasChanged(rounded=True)
|
|
self.instance.number = 7.8
|
|
self.assertHasChanged(rounded=False)
|
|
|
|
def test_changed(self):
|
|
self.assertChanged()
|
|
self.instance.number = 7.5
|
|
self.assertPrevious(rounded=None)
|
|
self.instance.save()
|
|
self.assertPrevious()
|
|
self.instance.number = 7.8
|
|
self.assertPrevious()
|
|
self.instance.number = 7.2
|
|
self.assertPrevious(rounded=8)
|
|
self.instance.save()
|
|
self.assertPrevious()
|
|
|
|
def test_current(self):
|
|
self.assertCurrent(rounded=None)
|
|
self.instance.number = 7.5
|
|
self.assertCurrent(rounded=8)
|
|
self.instance.save()
|
|
self.assertCurrent(rounded=8)
|
|
|
|
|
|
class FieldTrackedModelMultiTests(FieldTrackerTestCase,
|
|
FieldTrackerCommonTests):
|
|
|
|
tracked_class = TrackedMultiple
|
|
|
|
def setUp(self):
|
|
self.instance = self.tracked_class()
|
|
self.trackers = [self.instance.name_tracker,
|
|
self.instance.number_tracker]
|
|
|
|
def test_pre_save_changed(self):
|
|
self.tracker = self.instance.name_tracker
|
|
self.assertChanged(name=None)
|
|
self.instance.name = 'new age'
|
|
self.assertChanged(name=None)
|
|
self.instance.number = 8
|
|
self.assertChanged(name=None)
|
|
self.instance.name = ''
|
|
self.assertChanged(name=None)
|
|
self.tracker = self.instance.number_tracker
|
|
self.assertChanged(number=None)
|
|
self.instance.name = 'new age'
|
|
self.assertChanged(number=None)
|
|
self.instance.number = 8
|
|
self.assertChanged(number=None)
|
|
|
|
def test_pre_save_has_changed(self):
|
|
self.tracker = self.instance.name_tracker
|
|
self.assertHasChanged(name=True, number=None)
|
|
self.instance.name = 'new age'
|
|
self.assertHasChanged(name=True, number=None)
|
|
self.tracker = self.instance.number_tracker
|
|
self.assertHasChanged(name=None, number=False)
|
|
self.instance.name = 'new age'
|
|
self.assertHasChanged(name=None, number=False)
|
|
|
|
def test_pre_save_previous(self):
|
|
for tracker in self.trackers:
|
|
self.tracker = tracker
|
|
super().test_pre_save_previous()
|
|
|
|
def test_post_save_has_changed(self):
|
|
self.update_instance(name='retro', number=4)
|
|
self.assertHasChanged(tracker=self.trackers[0], name=False, number=None)
|
|
self.assertHasChanged(tracker=self.trackers[1], name=None, number=False)
|
|
self.instance.name = 'new age'
|
|
self.assertHasChanged(tracker=self.trackers[0], name=True, number=None)
|
|
self.assertHasChanged(tracker=self.trackers[1], name=None, number=False)
|
|
self.instance.number = 8
|
|
self.assertHasChanged(tracker=self.trackers[0], name=True, number=None)
|
|
self.assertHasChanged(tracker=self.trackers[1], name=None, number=True)
|
|
self.instance.name = 'retro'
|
|
self.instance.number = 4
|
|
self.assertHasChanged(tracker=self.trackers[0], name=False, number=None)
|
|
self.assertHasChanged(tracker=self.trackers[1], name=None, number=False)
|
|
|
|
def test_post_save_previous(self):
|
|
self.update_instance(name='retro', number=4)
|
|
self.instance.name = 'new age'
|
|
self.instance.number = 8
|
|
self.assertPrevious(tracker=self.trackers[0], name='retro', number=None)
|
|
self.assertPrevious(tracker=self.trackers[1], name=None, number=4)
|
|
|
|
def test_post_save_changed(self):
|
|
self.update_instance(name='retro', number=4)
|
|
self.assertChanged(tracker=self.trackers[0])
|
|
self.assertChanged(tracker=self.trackers[1])
|
|
self.instance.name = 'new age'
|
|
self.assertChanged(tracker=self.trackers[0], name='retro')
|
|
self.assertChanged(tracker=self.trackers[1])
|
|
self.instance.number = 8
|
|
self.assertChanged(tracker=self.trackers[0], name='retro')
|
|
self.assertChanged(tracker=self.trackers[1], number=4)
|
|
self.instance.name = 'retro'
|
|
self.instance.number = 4
|
|
self.assertChanged(tracker=self.trackers[0])
|
|
self.assertChanged(tracker=self.trackers[1])
|
|
|
|
def test_current(self):
|
|
self.assertCurrent(tracker=self.trackers[0], name='')
|
|
self.assertCurrent(tracker=self.trackers[1], number=None)
|
|
self.instance.name = 'new age'
|
|
self.assertCurrent(tracker=self.trackers[0], name='new age')
|
|
self.assertCurrent(tracker=self.trackers[1], number=None)
|
|
self.instance.number = 8
|
|
self.assertCurrent(tracker=self.trackers[0], name='new age')
|
|
self.assertCurrent(tracker=self.trackers[1], number=8)
|
|
self.instance.save()
|
|
self.assertCurrent(tracker=self.trackers[0], name='new age')
|
|
self.assertCurrent(tracker=self.trackers[1], number=8)
|
|
|
|
|
|
class FieldTrackerForeignKeyTests(FieldTrackerTestCase):
|
|
|
|
fk_class = Tracked
|
|
tracked_class = TrackedFK
|
|
|
|
def setUp(self):
|
|
self.old_fk = self.fk_class.objects.create(number=8)
|
|
self.instance = self.tracked_class.objects.create(fk=self.old_fk)
|
|
|
|
def test_default(self):
|
|
self.tracker = self.instance.tracker
|
|
self.assertChanged()
|
|
self.assertPrevious()
|
|
self.assertCurrent(id=self.instance.id, fk_id=self.old_fk.id)
|
|
self.instance.fk = self.fk_class.objects.create(number=8)
|
|
self.assertChanged(fk_id=self.old_fk.id)
|
|
self.assertPrevious(fk_id=self.old_fk.id)
|
|
self.assertCurrent(id=self.instance.id, fk_id=self.instance.fk_id)
|
|
|
|
def test_custom(self):
|
|
self.tracker = self.instance.custom_tracker
|
|
self.assertChanged()
|
|
self.assertPrevious()
|
|
self.assertCurrent(fk_id=self.old_fk.id)
|
|
self.instance.fk = self.fk_class.objects.create(number=8)
|
|
self.assertChanged(fk_id=self.old_fk.id)
|
|
self.assertPrevious(fk_id=self.old_fk.id)
|
|
self.assertCurrent(fk_id=self.instance.fk_id)
|
|
|
|
def test_custom_without_id(self):
|
|
with self.assertNumQueries(1):
|
|
self.tracked_class.objects.get()
|
|
self.tracker = self.instance.custom_tracker_without_id
|
|
self.assertChanged()
|
|
self.assertPrevious()
|
|
self.assertCurrent(fk=self.old_fk.id)
|
|
self.instance.fk = self.fk_class.objects.create(number=8)
|
|
self.assertChanged(fk=self.old_fk.id)
|
|
self.assertPrevious(fk=self.old_fk.id)
|
|
self.assertCurrent(fk=self.instance.fk_id)
|
|
|
|
|
|
class FieldTrackerForeignKeyPrefetchRelatedTests(FieldTrackerTestCase):
|
|
"""Test that using `prefetch_related` on a tracked field does not raise a ValueError."""
|
|
|
|
fk_class = Tracked
|
|
tracked_class = TrackedFK
|
|
|
|
def setUp(self):
|
|
model_tracked = self.fk_class.objects.create(name="", number=0)
|
|
self.instance = self.tracked_class.objects.create(fk=model_tracked)
|
|
|
|
def test_default(self):
|
|
self.tracker = self.instance.tracker
|
|
self.assertIsNotNone(list(self.tracked_class.objects.prefetch_related("fk")))
|
|
|
|
def test_custom(self):
|
|
self.tracker = self.instance.custom_tracker
|
|
self.assertIsNotNone(list(self.tracked_class.objects.prefetch_related("fk")))
|
|
|
|
def test_custom_without_id(self):
|
|
self.tracker = self.instance.custom_tracker_without_id
|
|
self.assertIsNotNone(list(self.tracked_class.objects.prefetch_related("fk")))
|
|
|
|
|
|
class FieldTrackerTimeStampedTests(FieldTrackerTestCase):
|
|
|
|
fk_class = Tracked
|
|
tracked_class = TrackerTimeStamped
|
|
|
|
def setUp(self):
|
|
self.instance = self.tracked_class.objects.create(name='old', number=1)
|
|
self.tracker = self.instance.tracker
|
|
|
|
def test_set_modified_on_save(self):
|
|
old_modified = self.instance.modified
|
|
self.instance.name = 'new'
|
|
self.instance.save()
|
|
self.assertGreater(self.instance.modified, old_modified)
|
|
self.assertChanged()
|
|
|
|
def test_set_modified_on_save_update_fields(self):
|
|
old_modified = self.instance.modified
|
|
self.instance.name = 'new'
|
|
self.instance.save(update_fields=('name',))
|
|
self.assertGreater(self.instance.modified, old_modified)
|
|
self.assertChanged()
|
|
|
|
|
|
class InheritedFieldTrackerTests(FieldTrackerTests):
|
|
|
|
tracked_class = InheritedTracked
|
|
|
|
def test_child_fields_not_tracked(self):
|
|
self.name2 = 'test'
|
|
self.assertEqual(self.tracker.previous('name2'), None)
|
|
self.assertRaises(FieldError, self.tracker.has_changed, 'name2')
|
|
|
|
|
|
class FieldTrackerInheritedForeignKeyTests(FieldTrackerForeignKeyTests):
|
|
|
|
tracked_class = InheritedTrackedFK
|
|
|
|
|
|
class FieldTrackerFileFieldTests(FieldTrackerTestCase):
|
|
|
|
tracked_class = TrackedFileField
|
|
|
|
def setUp(self):
|
|
self.instance = self.tracked_class()
|
|
self.tracker = self.instance.tracker
|
|
self.some_file = 'something.txt'
|
|
self.another_file = 'another.txt'
|
|
|
|
def test_saved_data_without_instance(self):
|
|
"""
|
|
Tests that instance won't get copied by the Field Tracker.
|
|
|
|
This change was introduced in Django 3.1 with
|
|
https://github.com/django/django/pull/12055
|
|
It results in a dramatic CPU and memory usage of FieldTracker on FileField and
|
|
its subclasses.
|
|
The pickling/deepcopying the instance is useless in the context of FieldTracker
|
|
thus we are skipping it.
|
|
"""
|
|
self.assertEqual(self.tracker.saved_data, {})
|
|
self.update_instance(some_file=self.some_file)
|
|
field_file_copy = self.tracker.saved_data.get('some_file')
|
|
self.assertIsNotNone(field_file_copy)
|
|
self.assertEqual(field_file_copy.__getstate__().get('instance'), None)
|
|
self.assertEqual(self.instance.some_file.instance, self.instance)
|
|
self.assertIsInstance(self.instance.some_file, FieldFile)
|
|
|
|
def test_pre_save_changed(self):
|
|
self.assertChanged(some_file=None)
|
|
self.instance.some_file = self.some_file
|
|
self.assertChanged(some_file=None)
|
|
|
|
def test_pre_save_has_changed(self):
|
|
self.assertHasChanged(some_file=True)
|
|
self.instance.some_file = self.some_file
|
|
self.assertHasChanged(some_file=True)
|
|
|
|
def test_pre_save_previous(self):
|
|
self.assertPrevious(some_file=None)
|
|
self.instance.some_file = self.some_file
|
|
self.assertPrevious(some_file=None)
|
|
|
|
def test_post_save_changed(self):
|
|
self.update_instance(some_file=self.some_file)
|
|
self.assertChanged()
|
|
previous_file = self.instance.some_file
|
|
self.instance.some_file = self.another_file
|
|
self.assertChanged(some_file=previous_file)
|
|
# test deferred file field
|
|
deferred_instance = self.tracked_class.objects.defer('some_file')[0]
|
|
deferred_instance.some_file # access field to fetch from database
|
|
self.assertChanged(tracker=deferred_instance.tracker)
|
|
|
|
previous_file = deferred_instance.some_file
|
|
deferred_instance.some_file = self.another_file
|
|
self.assertChanged(
|
|
tracker=deferred_instance.tracker,
|
|
some_file=previous_file,
|
|
)
|
|
|
|
def test_post_save_has_changed(self):
|
|
self.update_instance(some_file=self.some_file)
|
|
self.assertHasChanged(some_file=False)
|
|
self.instance.some_file = self.another_file
|
|
self.assertHasChanged(some_file=True)
|
|
|
|
# test deferred file field
|
|
deferred_instance = self.tracked_class.objects.defer('some_file')[0]
|
|
deferred_instance.some_file # access field to fetch from database
|
|
self.assertHasChanged(
|
|
tracker=deferred_instance.tracker,
|
|
some_file=False,
|
|
)
|
|
|
|
deferred_instance.some_file = self.another_file
|
|
self.assertHasChanged(
|
|
tracker=deferred_instance.tracker,
|
|
some_file=True,
|
|
)
|
|
|
|
def test_post_save_previous(self):
|
|
self.update_instance(some_file=self.some_file)
|
|
previous_file = self.instance.some_file
|
|
self.instance.some_file = self.another_file
|
|
self.assertPrevious(some_file=previous_file)
|
|
|
|
# test deferred file field
|
|
deferred_instance = self.tracked_class.objects.defer('some_file')[0]
|
|
deferred_instance.some_file # access field to fetch from database
|
|
self.assertPrevious(
|
|
tracker=deferred_instance.tracker,
|
|
some_file=previous_file,
|
|
)
|
|
|
|
deferred_instance.some_file = self.another_file
|
|
self.assertPrevious(
|
|
tracker=deferred_instance.tracker,
|
|
some_file=previous_file,
|
|
)
|
|
|
|
def test_current(self):
|
|
self.assertCurrent(some_file=self.instance.some_file, id=None)
|
|
self.instance.some_file = self.some_file
|
|
self.assertCurrent(some_file=self.instance.some_file, id=None)
|
|
|
|
# test deferred file field
|
|
self.instance.save()
|
|
deferred_instance = self.tracked_class.objects.defer('some_file')[0]
|
|
deferred_instance.some_file # access field to fetch from database
|
|
self.assertCurrent(
|
|
some_file=self.instance.some_file,
|
|
id=self.instance.id,
|
|
)
|
|
|
|
self.instance.some_file = self.another_file
|
|
self.assertCurrent(
|
|
some_file=self.instance.some_file,
|
|
id=self.instance.id,
|
|
)
|
|
|
|
|
|
class ModelTrackerTests(FieldTrackerTests):
|
|
|
|
tracked_class = ModelTracked
|
|
|
|
def test_cache_compatible(self):
|
|
cache.set('key', self.instance)
|
|
instance = cache.get('key')
|
|
instance.number = 1
|
|
instance.name = 'cached'
|
|
instance.save()
|
|
self.assertChanged()
|
|
instance.number = 2
|
|
self.assertHasChanged(number=True)
|
|
|
|
def test_pre_save_changed(self):
|
|
self.assertChanged()
|
|
self.instance.name = 'new age'
|
|
self.assertChanged()
|
|
self.instance.number = 8
|
|
self.assertChanged()
|
|
self.instance.name = ''
|
|
self.assertChanged()
|
|
self.instance.mutable = [1, 2, 3]
|
|
self.assertChanged()
|
|
|
|
def test_first_save(self):
|
|
self.assertHasChanged(name=True, number=True, mutable=True)
|
|
self.assertPrevious(name=None, number=None, mutable=None)
|
|
self.assertCurrent(name='', number=None, id=None, mutable=None)
|
|
self.assertChanged()
|
|
self.instance.name = 'retro'
|
|
self.instance.number = 4
|
|
self.instance.mutable = [1, 2, 3]
|
|
self.assertHasChanged(name=True, number=True, mutable=True)
|
|
self.assertPrevious(name=None, number=None, mutable=None)
|
|
self.assertCurrent(name='retro', number=4, id=None, mutable=[1, 2, 3])
|
|
self.assertChanged()
|
|
|
|
self.instance.save(update_fields=[])
|
|
self.assertHasChanged(name=True, number=True, mutable=True)
|
|
self.assertPrevious(name=None, number=None, mutable=None)
|
|
self.assertCurrent(name='retro', number=4, id=None, mutable=[1, 2, 3])
|
|
self.assertChanged()
|
|
with self.assertRaises(ValueError):
|
|
self.instance.save(update_fields=['number'])
|
|
|
|
def test_pre_save_has_changed(self):
|
|
self.assertHasChanged(name=True, number=True)
|
|
self.instance.name = 'new age'
|
|
self.assertHasChanged(name=True, number=True)
|
|
self.instance.number = 7
|
|
self.assertHasChanged(name=True, number=True)
|
|
|
|
|
|
class ModelTrackedModelCustomTests(FieldTrackedModelCustomTests):
|
|
|
|
tracked_class = ModelTrackedNotDefault
|
|
|
|
def test_first_save(self):
|
|
self.assertHasChanged(name=True, number=True)
|
|
self.assertPrevious(name=None, number=None)
|
|
self.assertCurrent(name='')
|
|
self.assertChanged()
|
|
self.instance.name = 'retro'
|
|
self.instance.number = 4
|
|
self.assertHasChanged(name=True, number=True)
|
|
self.assertPrevious(name=None, number=None)
|
|
self.assertCurrent(name='retro')
|
|
self.assertChanged()
|
|
|
|
def test_pre_save_has_changed(self):
|
|
self.assertHasChanged(name=True, number=True)
|
|
self.instance.name = 'new age'
|
|
self.assertHasChanged(name=True, number=True)
|
|
self.instance.number = 7
|
|
self.assertHasChanged(name=True, number=True)
|
|
|
|
def test_pre_save_changed(self):
|
|
self.assertChanged()
|
|
self.instance.name = 'new age'
|
|
self.assertChanged()
|
|
self.instance.number = 8
|
|
self.assertChanged()
|
|
self.instance.name = ''
|
|
self.assertChanged()
|
|
|
|
|
|
class ModelTrackedModelMultiTests(FieldTrackedModelMultiTests):
|
|
|
|
tracked_class = ModelTrackedMultiple
|
|
|
|
def test_pre_save_has_changed(self):
|
|
self.tracker = self.instance.name_tracker
|
|
self.assertHasChanged(name=True, number=True)
|
|
self.instance.name = 'new age'
|
|
self.assertHasChanged(name=True, number=True)
|
|
self.tracker = self.instance.number_tracker
|
|
self.assertHasChanged(name=True, number=True)
|
|
self.instance.name = 'new age'
|
|
self.assertHasChanged(name=True, number=True)
|
|
|
|
def test_pre_save_changed(self):
|
|
self.tracker = self.instance.name_tracker
|
|
self.assertChanged()
|
|
self.instance.name = 'new age'
|
|
self.assertChanged()
|
|
self.instance.number = 8
|
|
self.assertChanged()
|
|
self.instance.name = ''
|
|
self.assertChanged()
|
|
self.tracker = self.instance.number_tracker
|
|
self.assertChanged()
|
|
self.instance.name = 'new age'
|
|
self.assertChanged()
|
|
self.instance.number = 8
|
|
self.assertChanged()
|
|
|
|
|
|
class ModelTrackerForeignKeyTests(FieldTrackerForeignKeyTests):
|
|
|
|
fk_class = ModelTracked
|
|
tracked_class = ModelTrackedFK
|
|
|
|
def test_custom_without_id(self):
|
|
with self.assertNumQueries(2):
|
|
self.tracked_class.objects.get()
|
|
self.tracker = self.instance.custom_tracker_without_id
|
|
self.assertChanged()
|
|
self.assertPrevious()
|
|
self.assertCurrent(fk=self.old_fk)
|
|
self.instance.fk = self.fk_class.objects.create(number=8)
|
|
self.assertNotEqual(self.instance.fk, self.old_fk)
|
|
self.assertChanged(fk=self.old_fk)
|
|
self.assertPrevious(fk=self.old_fk)
|
|
self.assertCurrent(fk=self.instance.fk)
|
|
|
|
|
|
class InheritedModelTrackerTests(ModelTrackerTests):
|
|
|
|
tracked_class = InheritedModelTracked
|
|
|
|
def test_child_fields_not_tracked(self):
|
|
self.name2 = 'test'
|
|
self.assertEqual(self.tracker.previous('name2'), None)
|
|
self.assertTrue(self.tracker.has_changed('name2'))
|
|
|
|
|
|
@skip("has known failures")
|
|
class AbstractModelTrackerTests(ModelTrackerTests):
|
|
|
|
tracked_class = TrackedAbstract
|
|
|
|
|
|
class TrackerContextDecoratorTests(TestCase):
|
|
|
|
def setUp(self):
|
|
self.instance = Tracked.objects.create(number=1)
|
|
self.tracker = self.instance.tracker
|
|
|
|
def assertChanged(self, *fields):
|
|
for f in fields:
|
|
self.assertTrue(self.tracker.has_changed(f))
|
|
|
|
def assertNotChanged(self, *fields):
|
|
for f in fields:
|
|
self.assertFalse(self.tracker.has_changed(f))
|
|
|
|
def test_context_manager(self):
|
|
with self.tracker:
|
|
with self.tracker:
|
|
self.instance.name = 'new'
|
|
|
|
self.assertChanged('name')
|
|
|
|
self.assertChanged('name')
|
|
|
|
self.assertNotChanged('name')
|
|
|
|
def test_context_manager_fields(self):
|
|
with self.tracker('number'):
|
|
with self.tracker('number', 'name'):
|
|
self.instance.name = 'new'
|
|
self.instance.number += 1
|
|
|
|
self.assertChanged('name', 'number')
|
|
|
|
self.assertChanged('number')
|
|
self.assertNotChanged('name')
|
|
|
|
self.assertNotChanged('number', 'name')
|
|
|
|
def test_tracker_decorator(self):
|
|
|
|
@Tracked.tracker
|
|
def tracked_method(obj):
|
|
obj.name = 'new'
|
|
self.assertChanged('name')
|
|
|
|
tracked_method(self.instance)
|
|
|
|
self.assertNotChanged('name')
|
|
|
|
def test_tracker_decorator_fields(self):
|
|
|
|
@Tracked.tracker(fields=['name'])
|
|
def tracked_method(obj):
|
|
obj.name = 'new'
|
|
obj.number += 1
|
|
self.assertChanged('name', 'number')
|
|
|
|
tracked_method(self.instance)
|
|
|
|
self.assertChanged('number')
|
|
self.assertNotChanged('name')
|
|
|
|
def test_tracker_context_with_save(self):
|
|
|
|
with self.tracker:
|
|
self.instance.name = 'new'
|
|
self.instance.save()
|
|
|
|
self.assertChanged('name')
|
|
|
|
self.assertNotChanged('name')
|