Skip to content

Commit 493e2e0

Browse files
committed
Added queryset-like functionality to 'as_of' manager method
1 parent 703e985 commit 493e2e0

File tree

4 files changed

+92
-27
lines changed

4 files changed

+92
-27
lines changed

simple_history/manager.py

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -61,26 +61,26 @@ def most_recent(self):
6161

6262
def as_of(self, date):
6363
"""
64-
Returns an instance of the original model with all the attributes set
65-
according to what was present on the object on the date provided.
64+
Returns an instance, or an iterable of the instances, of the
65+
original model with all the attributes set according to what
66+
was present on the object on the date provided.
6667
"""
67-
if not self.instance:
68-
raise TypeError("Can't use as_of() without a %s instance." %
69-
self.model._meta.object_name)
70-
tmp = []
71-
for field in self.instance._meta.fields:
72-
if isinstance(field, models.ForeignKey):
73-
tmp.append(field.name + "_id")
74-
else:
75-
tmp.append(field.name)
76-
fields = tuple(tmp)
77-
qs = self.filter(history_date__lte=date)
78-
try:
79-
values = qs.values_list('history_type', *fields)[0]
80-
except IndexError:
81-
raise self.instance.DoesNotExist("%s had not yet been created." %
82-
self.instance._meta.object_name)
83-
if values[0] == '-':
84-
raise self.instance.DoesNotExist("%s had already been deleted." %
85-
self.instance._meta.object_name)
86-
return self.instance.__class__(*values[1:])
68+
queryset = self.filter(history_date__lte=date)
69+
if self.instance:
70+
try:
71+
history_obj = queryset[0]
72+
except IndexError:
73+
raise self.instance.DoesNotExist(
74+
"%s had not yet been created." %
75+
self.instance._meta.object_name)
76+
if history_obj.history_type == '-':
77+
raise self.instance.DoesNotExist(
78+
"%s had already been deleted." %
79+
self.instance._meta.object_name)
80+
return history_obj.instance
81+
historical_ids = set(
82+
queryset.order_by().values_list('id', flat=True))
83+
return (change.instance for change in (
84+
queryset.filter(id=original_pk).latest('history_date')
85+
for original_pk in historical_ids
86+
) if change.history_type != '-')
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
from .test_models import *
22
from .test_admin import *
33
from .test_commands import *
4+
from .test_manager import *
5+
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from datetime import datetime, timedelta
2+
from django.test import TestCase
3+
try:
4+
from django.contrib.auth import get_user_model
5+
except ImportError:
6+
from django.contrib.auth.models import User
7+
else:
8+
User = get_user_model()
9+
10+
from .. import models
11+
12+
13+
class AsOfTest(TestCase):
14+
model = models.Document
15+
16+
def setUp(self):
17+
user = User.objects.create_user("tester", "[email protected]")
18+
self.now = datetime.now()
19+
self.yesterday = self.now - timedelta(days=1)
20+
self.obj = self.model.objects.create()
21+
self.obj.changed_by = user
22+
self.obj.save()
23+
self.model.objects.all().delete() # allows us to leave PK on instance
24+
self.delete_history, self.change_history, self.create_history = (
25+
self.model.history.all())
26+
self.create_history.history_date = self.now - timedelta(days=2)
27+
self.create_history.save()
28+
self.change_history.history_date = self.now - timedelta(days=1)
29+
self.change_history.save()
30+
self.delete_history.history_date = self.now
31+
self.delete_history.save()
32+
33+
def test_created_after(self):
34+
"""An object created after the 'as of' date should not be
35+
included.
36+
37+
"""
38+
as_of_list = list(
39+
self.model.history.as_of(self.now - timedelta(days=5)))
40+
self.assertFalse(as_of_list)
41+
42+
def test_deleted_before(self):
43+
"""An object deleted before the 'as of' date should not be
44+
included.
45+
46+
"""
47+
as_of_list = list(
48+
self.model.history.as_of(self.now + timedelta(days=1)))
49+
self.assertFalse(as_of_list)
50+
51+
def test_deleted_after(self):
52+
"""An object created before, but deleted after the 'as of'
53+
date should be included.
54+
55+
"""
56+
as_of_list = list(
57+
self.model.history.as_of(self.now - timedelta(days=1)))
58+
self.assertEqual(len(as_of_list), 1)
59+
self.assertEqual(as_of_list[0].pk, self.obj.pk)
60+
61+
def test_modified(self):
62+
"""An object modified before the 'as of' date should reflect
63+
the last version.
64+
65+
"""
66+
as_of_list = list(
67+
self.model.history.as_of(self.now - timedelta(days=1)))
68+
self.assertEqual(as_of_list[0].changed_by, self.obj.changed_by)

simple_history/tests/tests/test_models.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -425,11 +425,6 @@ def test_as_of(self):
425425
self.assertEqual(question_as_of(times[1]), "how's it going?")
426426
self.assertEqual(question_as_of(times[2]), "what's up?")
427427

428-
def test_as_of_on_model_class(self):
429-
Poll.objects.create(question="what's up?", pub_date=today)
430-
time = Poll.history.all()[0].history_date
431-
self.assertRaises(TypeError, Poll.history.as_of, time)
432-
433428
def test_as_of_nonexistant(self):
434429
# Unsaved poll
435430
poll = Poll(question="what's up?", pub_date=today)

0 commit comments

Comments
 (0)