From 6c10248d042afca9bd4d56a96b4754de3446fded Mon Sep 17 00:00:00 2001 From: Jordan Hyatt <38536669+JordanHyatt@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:37:33 -0500 Subject: [PATCH] Deferred fields patch (#1393) * added patch and test * added myself to AUTHORS * adde my change to CHANGES.rst * fixed bug to connect to pre_delete instead of post_delete * implemented suggestion made by @jwaschkau in issue #1064 * added .python-version to the .gitignore * - added check to signal to see if instance is history enabled before loading its deferred fields * check that fields is "truthy" before calling refresh_from_db * added to test to ensure by-pass logic is covered --- .gitignore | 1 + CHANGES.rst | 1 + simple_history/models.py | 18 ++++++++++++++++++ simple_history/tests/tests/test_models.py | 12 ++++++++++++ 4 files changed, 32 insertions(+) diff --git a/.gitignore b/.gitignore index 6263b36f3..325ff5eac 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .idea .tox/ .venv/ +.python-version /.project /.pydevproject /.ve diff --git a/CHANGES.rst b/CHANGES.rst index 073d6c3ad..10fbb752b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,7 @@ Unreleased - Made ``skip_history_when_saving`` work when creating an object - not just when updating an object (gh-1262) - Improved performance of the ``latest_of_each()`` history manager method (gh-1360) +- Fixed issue with deferred fields causing DoesNotExist error (gh-678) - Added HistoricOneToOneField (gh-1394) 3.7.0 (2024-05-29) diff --git a/simple_history/models.py b/simple_history/models.py index 008eea9d8..02b845784 100644 --- a/simple_history/models.py +++ b/simple_history/models.py @@ -221,6 +221,7 @@ def finalize(self, sender, **kwargs): # so the signal handlers can't use weak references. models.signals.post_save.connect(self.post_save, sender=sender, weak=False) models.signals.post_delete.connect(self.post_delete, sender=sender, weak=False) + models.signals.pre_delete.connect(self.pre_delete, sender=sender, weak=False) m2m_fields = self.get_m2m_fields_from_model(sender) @@ -668,6 +669,23 @@ def post_delete(self, instance, using=None, **kwargs): else: self.create_historical_record(instance, "-", using=using) + def pre_delete(self, instance, **kwargs): + """ + pre_delete method to ensure all deferred fileds are loaded on the model + """ + # First check that history is enabled (on model and globally) + if not getattr(settings, "SIMPLE_HISTORY_ENABLED", True): + return + if not hasattr(instance._meta, "simple_history_manager_attribute"): + return + fields = self.fields_included(instance) + field_attrs = {field.attname for field in fields} + deferred_attrs = instance.get_deferred_fields() + # Load all deferred fields that are present in fields_included + fields = field_attrs.intersection(deferred_attrs) + if fields: + instance.refresh_from_db(fields=fields) + def get_change_reason_for_object(self, instance, history_type, using): """ Get change reason for object. diff --git a/simple_history/tests/tests/test_models.py b/simple_history/tests/tests/test_models.py index 224bbabb7..566e2635a 100644 --- a/simple_history/tests/tests/test_models.py +++ b/simple_history/tests/tests/test_models.py @@ -945,6 +945,18 @@ def test_history_with_unknown_field(self): with self.assertNumQueries(0): new_record.diff_against(old_record, excluded_fields=["unknown_field"]) + def test_delete_with_deferred_fields(self): + Poll.objects.create(question="what's up bro?", pub_date=today) + Poll.objects.create(question="what's up sis?", pub_date=today) + Poll.objects.only("id").first().delete() + Poll.objects.defer("question").all().delete() + # Make sure bypass logic runs + Place.objects.create(name="cool place") + Place.objects.defer("name").first().delete() + with self.settings(SIMPLE_HISTORY_ENABLED=False): + Place.objects.create(name="cool place") + Place.objects.defer("name").all().delete() + def test_history_with_custom_queryset(self): PollWithQuerySetCustomizations.objects.create( id=1, pub_date=today, question="Question 1"