Skip to content

Commit b5e627c

Browse files
committed
Added _select_related_history_tracked_objs()
...to `HistoricalQuerySet`. This convenience method will make it easier to avoid database queries - e.g. when diffing and listing the results, which will be done as part of the upcoming changes to the admin history page adding a "Changes" column. No documentation was added, after the following discussion: #1128 (comment) Also prefixed the method with `_`, to mark it as not being part of the public API.
1 parent 0589ac2 commit b5e627c

File tree

3 files changed

+55
-1
lines changed

3 files changed

+55
-1
lines changed

simple_history/manager.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,18 @@ def latest_of_each(self):
9191
)
9292
return latest_historics
9393

94+
def _select_related_history_tracked_objs(self):
95+
"""
96+
A convenience method that calls ``select_related()`` with all the names of
97+
the model's history-tracked ``ForeignKey`` fields.
98+
"""
99+
field_names = [
100+
field.name
101+
for field in self.model.tracked_fields
102+
if isinstance(field, models.ForeignKey)
103+
]
104+
return self.select_related(*field_names)
105+
94106
def _clone(self):
95107
c = super()._clone()
96108
c._as_instances = self._as_instances

simple_history/tests/tests/test_manager.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from simple_history.manager import SIMPLE_HISTORY_REVERSE_ATTR_NAME
1010

11-
from ..models import Document, Poll, RankedDocument
11+
from ..models import Choice, Document, Poll, RankedDocument
1212

1313
User = get_user_model()
1414

@@ -371,3 +371,37 @@ def test_bulk_history_create_with_change_reason(self):
371371
]
372372
)
373373
)
374+
375+
376+
class PrefetchingMethodsTestCase(TestCase):
377+
def setUp(self):
378+
d = datetime(3021, 1, 1, 10, 0)
379+
self.poll1 = Poll.objects.create(question="why?", pub_date=d)
380+
self.poll2 = Poll.objects.create(question="how?", pub_date=d)
381+
self.choice1 = Choice.objects.create(poll=self.poll1, votes=1)
382+
self.choice2 = Choice.objects.create(poll=self.poll1, votes=2)
383+
self.choice3 = Choice.objects.create(poll=self.poll2, votes=3)
384+
385+
def test__select_related_history_tracked_objs__prefetches_expected_objects(self):
386+
num_choices = Choice.objects.count()
387+
self.assertEqual(num_choices, 3)
388+
389+
def access_related_objs(records):
390+
for record in records:
391+
self.assertIsInstance(record.poll, Poll)
392+
393+
# Without prefetching:
394+
with self.assertNumQueries(1):
395+
historical_records = Choice.history.all()
396+
self.assertEqual(len(historical_records), num_choices)
397+
with self.assertNumQueries(num_choices):
398+
access_related_objs(historical_records)
399+
400+
# With prefetching:
401+
with self.assertNumQueries(1):
402+
historical_records = (
403+
Choice.history.all()._select_related_history_tracked_objs()
404+
)
405+
self.assertEqual(len(historical_records), num_choices)
406+
with self.assertNumQueries(0):
407+
access_related_objs(historical_records)

simple_history/tests/tests/test_models.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,6 +1151,14 @@ def assert_tracked_fields_equal(model, expected_field_names):
11511151
PollWithHistoricalIPAddress,
11521152
["id", "question", "pub_date"],
11531153
)
1154+
assert_tracked_fields_equal(
1155+
PollWithManyToMany,
1156+
["id", "question", "pub_date"],
1157+
)
1158+
assert_tracked_fields_equal(
1159+
Choice,
1160+
["id", "poll", "choice", "votes"],
1161+
)
11541162
assert_tracked_fields_equal(
11551163
ModelWithCustomAttrOneToOneField,
11561164
["id", "poll"],

0 commit comments

Comments
 (0)