Skip to content

Commit e2894b5

Browse files
committed
Added delete_without_historical_record()
1 parent b36a280 commit e2894b5

File tree

4 files changed

+71
-40
lines changed

4 files changed

+71
-40
lines changed

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Unreleased
88

99
- Made ``skip_history_when_saving`` work when creating an object - not just when
1010
updating an object (gh-1262)
11+
- Added ``delete_without_historical_record()`` to all history-tracked model objects,
12+
which complements ``save_without_historical_record()`` (gh-1387)
1113

1214
**Deprecations:**
1315

docs/disabling_history.rst

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
11
Disable Creating Historical Records
22
===================================
33

4-
Save without creating historical records
5-
----------------------------------------
4+
``save_without_historical_record()`` and ``delete_without_historical_record()``
5+
-------------------------------------------------------------------------------
66

7-
If you want to save model objects without triggering the creation of any historical
8-
records, you can do the following:
7+
These methods are automatically added to a model when registering it for history-tracking
8+
(i.e. defining a ``HistoricalRecords`` manager on the model),
9+
and can be called instead of ``save()`` and ``delete()``, respectively.
10+
11+
Setting the ``skip_history_when_saving`` attribute
12+
--------------------------------------------------
13+
14+
If you want to save or delete model objects without triggering the creation of any
15+
historical records, you can do the following:
916

1017
.. code-block:: python
1118
1219
poll.skip_history_when_saving = True
20+
# It applies both when saving...
1321
poll.save()
22+
# ...and when deleting
23+
poll.delete()
1424
# We recommend deleting the attribute afterward
1525
del poll.skip_history_when_saving
1626
@@ -27,23 +37,10 @@ This also works when creating an object, but only when calling ``save()``:
2737
.. note::
2838
Historical records will always be created when calling the ``create()`` manager method.
2939

30-
Alternatively, call the ``save_without_historical_record()`` method on each object
31-
instead of ``save()``.
32-
This method is automatically added to a model when registering it for history-tracking
33-
(i.e. defining a ``HistoricalRecords`` manager field on the model),
34-
and it looks like this:
35-
36-
.. code-block:: python
37-
38-
def save_without_historical_record(self, *args, **kwargs):
39-
self.skip_history_when_saving = True
40-
try:
41-
ret = self.save(*args, **kwargs)
42-
finally:
43-
del self.skip_history_when_saving
44-
return ret
40+
The ``SIMPLE_HISTORY_ENABLED`` setting
41+
--------------------------------------
4542

46-
Or disable the creation of historical records for *all* models
43+
Disable the creation of historical records for *all* models
4744
by adding the following line to your settings:
4845

4946
.. code-block:: python

simple_history/models.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ def contribute_to_class(self, cls, name):
195195
warnings.warn(msg, UserWarning)
196196

197197
def add_extra_methods(self, cls):
198-
def save_without_historical_record(self, *args, **kwargs):
198+
def save_without_historical_record(self: models.Model, *args, **kwargs):
199199
"""
200200
Save the model instance without creating a historical record.
201201
@@ -208,7 +208,21 @@ def save_without_historical_record(self, *args, **kwargs):
208208
del self.skip_history_when_saving
209209
return ret
210210

211-
setattr(cls, "save_without_historical_record", save_without_historical_record)
211+
def delete_without_historical_record(self: models.Model, *args, **kwargs):
212+
"""
213+
Delete the model instance without creating a historical record.
214+
215+
Make sure you know what you're doing before using this method.
216+
"""
217+
self.skip_history_when_saving = True
218+
try:
219+
ret = self.delete(*args, **kwargs)
220+
finally:
221+
del self.skip_history_when_saving
222+
return ret
223+
224+
cls.save_without_historical_record = save_without_historical_record
225+
cls.delete_without_historical_record = delete_without_historical_record
212226

213227
def finalize(self, sender, **kwargs):
214228
inherited = False
@@ -665,7 +679,9 @@ def get_meta_options(self, model):
665679
)
666680
return meta_fields
667681

668-
def post_save(self, instance, created, using=None, **kwargs):
682+
def post_save(
683+
self, instance: models.Model, created: bool, using: str = None, **kwargs
684+
):
669685
if not getattr(settings, "SIMPLE_HISTORY_ENABLED", True):
670686
return
671687
if hasattr(instance, "skip_history_when_saving"):
@@ -674,9 +690,12 @@ def post_save(self, instance, created, using=None, **kwargs):
674690
if not kwargs.get("raw", False):
675691
self.create_historical_record(instance, created and "+" or "~", using=using)
676692

677-
def post_delete(self, instance, using=None, **kwargs):
693+
def post_delete(self, instance: models.Model, using: str = None, **kwargs):
678694
if not getattr(settings, "SIMPLE_HISTORY_ENABLED", True):
679695
return
696+
if hasattr(instance, "skip_history_when_saving"):
697+
return
698+
680699
if self.cascade_delete_history:
681700
manager = getattr(instance, self.manager_name)
682701
manager.using(using).all().delete()

simple_history/tests/tests/test_models.py

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,14 @@ def test_cascade_delete_history(self):
262262
self.assertEqual(len(thames.history.all()), 1)
263263
self.assertEqual(len(nile.history.all()), 0)
264264

265-
def test_save_without_historical_record(self):
265+
def test_registered_model_has_extra_methods(self):
266+
model = ExternalModelSpecifiedWithAppParam.objects.create(
267+
name="registered model"
268+
)
269+
self.assertTrue(hasattr(model, "save_without_historical_record"))
270+
self.assertTrue(hasattr(model, "delete_without_historical_record"))
271+
272+
def test__save_without_historical_record__creates_no_records(self):
266273
pizza_place = Restaurant.objects.create(name="Pizza Place", rating=3)
267274
pizza_place.rating = 4
268275
pizza_place.save_without_historical_record()
@@ -290,6 +297,26 @@ def test_save_without_historical_record(self):
290297
},
291298
)
292299

300+
def test__delete_without_historical_record__creates_no_records(self):
301+
self.assertEqual(Restaurant.objects.count(), 0)
302+
pizza_place = Restaurant.objects.create(name="Pizza Place", rating=3)
303+
self.assertEqual(Restaurant.objects.count(), 1)
304+
pizza_place.rating = 4
305+
pizza_place.delete_without_historical_record()
306+
self.assertEqual(Restaurant.objects.count(), 0)
307+
308+
(create_record,) = Restaurant.updates.all()
309+
self.assertRecordValues(
310+
create_record,
311+
Restaurant,
312+
{
313+
"name": "Pizza Place",
314+
"rating": 3,
315+
"id": pizza_place.id,
316+
"history_type": "+",
317+
},
318+
)
319+
293320
@override_settings(SIMPLE_HISTORY_ENABLED=False)
294321
def test_save_with_disabled_history(self):
295322
anthony = Person.objects.create(name="Anthony Gillard")
@@ -299,12 +326,6 @@ def test_save_with_disabled_history(self):
299326
anthony.delete()
300327
self.assertEqual(Person.history.count(), 0)
301328

302-
def test_save_without_historical_record_for_registered_model(self):
303-
model = ExternalModelSpecifiedWithAppParam.objects.create(
304-
name="registered model"
305-
)
306-
self.assertTrue(hasattr(model, "save_without_historical_record"))
307-
308329
def test_save_raises_exception(self):
309330
anthony = Person(name="Anthony Gillard")
310331
with self.assertRaises(RuntimeError):
@@ -2142,7 +2163,7 @@ def setUp(self):
21422163
self.poll = self.model.objects.create(question="what's up?", pub_date=today)
21432164

21442165

2145-
class ManyToManyTest(TestCase):
2166+
class ManyToManyTest(HistoricalTestCase):
21462167
def setUp(self):
21472168
self.model = PollWithManyToMany
21482169
self.history_model = self.model.history.model
@@ -2154,14 +2175,6 @@ def setUp(self):
21542175
def assertDatetimesEqual(self, time1, time2):
21552176
self.assertAlmostEqual(time1, time2, delta=timedelta(seconds=2))
21562177

2157-
def assertRecordValues(self, record, klass, values_dict):
2158-
for key, value in values_dict.items():
2159-
self.assertEqual(getattr(record, key), value)
2160-
self.assertEqual(record.history_object.__class__, klass)
2161-
for key, value in values_dict.items():
2162-
if key not in ["history_type", "history_change_reason"]:
2163-
self.assertEqual(getattr(record.history_object, key), value)
2164-
21652178
def test_create(self):
21662179
# There should be 1 history record for our poll, the create from setUp
21672180
self.assertEqual(self.poll.history.all().count(), 1)

0 commit comments

Comments
 (0)