Skip to content

Commit 2e8925b

Browse files
SpainTrainRoss Mechanic
authored andcommitted
feat(HistoryRecords): prev and next attributes (#365)
- New attributes on history model retrieve previous and next records Closes #230
1 parent d11e393 commit 2e8925b

File tree

5 files changed

+118
-0
lines changed

5 files changed

+118
-0
lines changed

AUTHORS.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ Authors
5656
- Shane Engelman
5757
- Ray Logel
5858
- Nathan Villagaray-Carski
59+
- Mike Spainhower
5960

6061
Background
6162
==========

CHANGES.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Changes
44
Unreleased
55
----------
66
- Add ability to specify custom history_id field (gh-368)
7+
- Add HistoricalRecord instance properties `prev_record` and `next_record` (gh-365)
78

89
2.0 (2018-04-05)
910
----------------

docs/usage.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,26 @@ records for all ``Choice`` instances can be queried by using the manager on the
214214
215215
Because the history is model, you can also filter it like regularly QuerySets,
216216
a.k. Choice.history.filter(choice_text='Not Much') will work!
217+
218+
Getting previous and next historical record
219+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
220+
221+
If you have a historical record for an instance and would like to retrieve the previous historical record (older) or next historical record (newer), `prev_record` and `next_record` read-only attributes can be used, respectively.
222+
223+
.. code-block:: pycon
224+
225+
>>> from polls.models import Poll, Choice
226+
>>> from datetime import datetime
227+
>>> poll = Poll.objects.create(question="what's up?", pub_date=datetime.now())
228+
>>>
229+
>>> record = poll.history.first()
230+
>>> record.prev_record
231+
None
232+
>>> record.next_record
233+
None
234+
>>> poll.question = "what is up?"
235+
>>> poll.save()
236+
>>> record.next_record
237+
<HistoricalPoll: Poll object as of 2010-10-25 18:04:13.814128>
238+
239+
If a historical record is the first record, `prev_record` will be `None`. Similarly, if it is the latest record, `next_record` will be `None`

simple_history/models.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from django.conf import settings
1010
from django.contrib import admin
1111
from django.db import models, router
12+
from django.db.models import Q
1213
from django.db.models.fields.proxy import OrderWrt
1314
from django.urls import reverse
1415
from django.utils import six
@@ -222,6 +223,22 @@ def get_instance(self):
222223
for field in fields.values()
223224
})
224225

226+
def get_next_record(self):
227+
"""
228+
Get the next history record for the instance. `None` if last.
229+
"""
230+
return self.instance.history.filter(
231+
Q(history_date__gt=self.history_date)
232+
).order_by('history_date').first()
233+
234+
def get_prev_record(self):
235+
"""
236+
Get the previous history record for the instance. `None` if first.
237+
"""
238+
return self.instance.history.filter(
239+
Q(history_date__lt=self.history_date)
240+
).order_by('history_date').last()
241+
225242
if self.history_id_field:
226243
history_id_field = self.history_id_field
227244
history_id_field.primary_key = True
@@ -252,6 +269,8 @@ def get_instance(self):
252269
),
253270
'instance': property(get_instance),
254271
'instance_type': model,
272+
'next_record': property(get_next_record),
273+
'prev_record': property(get_prev_record),
255274
'revert_url': revert_url,
256275
'__str__': lambda self: '%s as of %s' % (self.history_object,
257276
self.history_date)

simple_history/tests/tests/test_models.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,80 @@ def test_uuid_default_history_id(self):
382382
history = entry.history.all()[0]
383383
self.assertTrue(isinstance(history.history_id, uuid.UUID))
384384

385+
def test_get_prev_record(self):
386+
poll = Poll(question="what's up?", pub_date=today)
387+
poll.save()
388+
poll.question = "ask questions?"
389+
poll.save()
390+
poll.question = "eh?"
391+
poll.save()
392+
poll.question = "one more?"
393+
poll.save()
394+
first_record = poll.history.filter(question="what's up?").get()
395+
second_record = poll.history.filter(question="ask questions?").get()
396+
third_record = poll.history.filter(question="eh?").get()
397+
fourth_record = poll.history.filter(question="one more?").get()
398+
self.assertIsNone(first_record.prev_record)
399+
400+
def assertRecordsMatch(record_a, record_b):
401+
self.assertEqual(record_a, record_b)
402+
self.assertEqual(record_a.question, record_b.question)
403+
assertRecordsMatch(second_record.prev_record, first_record)
404+
assertRecordsMatch(third_record.prev_record, second_record)
405+
assertRecordsMatch(fourth_record.prev_record, third_record)
406+
407+
def test_get_prev_record_none_if_only(self):
408+
poll = Poll(question="what's up?", pub_date=today)
409+
poll.save()
410+
self.assertEqual(poll.history.count(), 1)
411+
record = poll.history.get()
412+
self.assertIsNone(record.prev_record)
413+
414+
def test_get_prev_record_none_if_earliest(self):
415+
poll = Poll(question="what's up?", pub_date=today)
416+
poll.save()
417+
poll.question = "ask questions?"
418+
poll.save()
419+
first_record = poll.history.filter(question="what's up?").get()
420+
self.assertIsNone(first_record.prev_record)
421+
422+
def test_get_next_record(self):
423+
poll = Poll(question="what's up?", pub_date=today)
424+
poll.save()
425+
poll.question = "ask questions?"
426+
poll.save()
427+
poll.question = "eh?"
428+
poll.save()
429+
poll.question = "one more?"
430+
poll.save()
431+
first_record = poll.history.filter(question="what's up?").get()
432+
second_record = poll.history.filter(question="ask questions?").get()
433+
third_record = poll.history.filter(question="eh?").get()
434+
fourth_record = poll.history.filter(question="one more?").get()
435+
self.assertIsNone(fourth_record.next_record)
436+
437+
def assertRecordsMatch(record_a, record_b):
438+
self.assertEqual(record_a, record_b)
439+
self.assertEqual(record_a.question, record_b.question)
440+
assertRecordsMatch(first_record.next_record, second_record)
441+
assertRecordsMatch(second_record.next_record, third_record)
442+
assertRecordsMatch(third_record.next_record, fourth_record)
443+
444+
def test_get_next_record_none_if_only(self):
445+
poll = Poll(question="what's up?", pub_date=today)
446+
poll.save()
447+
self.assertEqual(poll.history.count(), 1)
448+
record = poll.history.get()
449+
self.assertIsNone(record.next_record)
450+
451+
def test_get_next_record_none_if_most_recent(self):
452+
poll = Poll(question="what's up?", pub_date=today)
453+
poll.save()
454+
poll.question = "ask questions?"
455+
poll.save()
456+
recent_record = poll.history.filter(question="ask questions?").get()
457+
self.assertIsNone(recent_record.next_record)
458+
385459

386460
class CreateHistoryModelTests(unittest.TestCase):
387461

0 commit comments

Comments
 (0)