Skip to content

Commit a3b86f1

Browse files
author
Ross Mechanic
authored
Don't populate excluded fields in populate history (#410)
* Adds tests for two excluded-fields fixes: - tests the management command on an historical record which has excluded fields - tests the excluded fields are retrieved from the current model instance * Adds test for foreign-key excluded field. * Reduces number of queries made by `History-Model.get_instance`. Previously, it was pre-loading directly related objects. * Don't populate excluded fields while populating existing model history * Adjustments * Updated AUTHORS and log CHANGES * Renames excluded_fields -> _history_excluded_fields update_record -> initial_history_record * Fixed accidental deletions to CHANGES.rst and AUTHORS.rst
1 parent ad523eb commit a3b86f1

File tree

7 files changed

+89
-4
lines changed

7 files changed

+89
-4
lines changed

AUTHORS.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ Authors
6262
- Mike Spainhower
6363
- Alexander Anikeev
6464
- Kyle Seever
65+
- Adnan Umer (@uadnan)
66+
- Jonathan Zvesper (@zvesp)
6567

6668
Background
6769
==========

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
- Fixed out-of-memory exception when running populate_history management command (gh-408)
7+
- Fix TypeError on populate_history if excluded_fields are specified (gh-410)
78

89
2.1.0 (2018-06-04)
910
------------------

simple_history/manager.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ def bulk_history_create(self, objs, batch_size=None):
100100
**{
101101
field.attname: getattr(instance, field.attname)
102102
for field in instance._meta.fields
103+
if field.name not in self.model._history_excluded_fields
103104
}
104105
) for instance in objs]
105106

simple_history/models.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,10 @@ def create_history_model(self, model, inherited):
113113
"""
114114
Creates a historical model to associate with the model provided.
115115
"""
116-
attrs = {'__module__': self.module}
116+
attrs = {
117+
'__module__': self.module,
118+
'_history_excluded_fields': self.excluded_fields
119+
}
117120

118121
app_module = '%s.models' % model._meta.app_label
119122

@@ -220,10 +223,20 @@ def revert_url(self):
220223
)
221224

222225
def get_instance(self):
223-
return model(**{
226+
attrs = {
224227
field.attname: getattr(self, field.attname)
225228
for field in fields.values()
226-
})
229+
}
230+
if self._history_excluded_fields:
231+
excluded_attnames = [
232+
model._meta.get_field(field).attname
233+
for field in self._history_excluded_fields
234+
]
235+
values = model.objects.filter(
236+
pk=getattr(self, model._meta.pk.attname)
237+
).values(*excluded_attnames).get()
238+
attrs.update(values)
239+
return model(**attrs)
227240

228241
def get_next_record(self):
229242
"""

simple_history/tests/models.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ class PollWithExcludeFields(models.Model):
3232
history = HistoricalRecords(excluded_fields=['pub_date'])
3333

3434

35+
class PollWithExcludedFKField(models.Model):
36+
question = models.CharField(max_length=200)
37+
pub_date = models.DateTimeField('date published')
38+
place = models.ForeignKey('Place', on_delete=models.CASCADE)
39+
40+
history = HistoricalRecords(excluded_fields=['place'])
41+
42+
3543
class Temperature(models.Model):
3644
location = models.CharField(max_length=200)
3745
temperature = models.IntegerField()

simple_history/tests/tests/test_commands.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from simple_history import models as sh_models
99
from simple_history.management.commands import populate_history
10-
from ..models import Book, Poll, Restaurant
10+
from ..models import Book, Poll, PollWithExcludeFields, Restaurant
1111

1212

1313
@contextmanager
@@ -142,3 +142,13 @@ def test_stdout_printed_when_verbosity_is_not_specified(self):
142142
stdout=out, stderr=StringIO())
143143

144144
self.assertNotEqual(out.getvalue(), '')
145+
146+
def test_excluded_fields(self):
147+
poll = PollWithExcludeFields.objects.create(
148+
question="Will this work?", pub_date=datetime.now())
149+
PollWithExcludeFields.history.all().delete()
150+
management.call_command(self.command_name,
151+
'tests.pollwithexcludefields', auto=True,
152+
stdout=StringIO(), stderr=StringIO())
153+
initial_history_record = PollWithExcludeFields.history.all()[0]
154+
self.assertEqual(initial_history_record.question, poll.question)

simple_history/tests/tests/test_models.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,11 @@
4040
Library,
4141
MultiOneToOne,
4242
Person,
43+
Place,
4344
Poll,
4445
PollInfo,
4546
PollWithExcludeFields,
47+
PollWithExcludedFKField,
4648
Province,
4749
Restaurant,
4850
SelfFK,
@@ -946,3 +948,51 @@ def test_custom_table_name_from_register(self):
946948
self.get_table_name(ContactRegister.history),
947949
'contacts_register_history',
948950
)
951+
952+
953+
class ExcludeFieldsTest(TestCase):
954+
def test_restore_pollwithexclude(self):
955+
poll = PollWithExcludeFields.objects.create(question="what's up?",
956+
pub_date=today)
957+
historical = poll.history.order_by('pk')[0]
958+
with self.assertRaises(AttributeError):
959+
historical.pub_date
960+
original = historical.instance
961+
self.assertEqual(original.pub_date, poll.pub_date)
962+
963+
964+
class ExcludeForeignKeyTest(TestCase):
965+
def setUp(self):
966+
self.poll = PollWithExcludedFKField.objects.create(
967+
question="Is it?", pub_date=today,
968+
place=Place.objects.create(name="Somewhere")
969+
)
970+
971+
def get_first_historical(self):
972+
"""
973+
Retrieve the idx'th HistoricalPoll, ordered by time.
974+
"""
975+
return self.poll.history.order_by('history_date')[0]
976+
977+
def test_instance_fk_value(self):
978+
historical = self.get_first_historical()
979+
original = historical.instance
980+
self.assertEqual(original.place, self.poll.place)
981+
982+
def test_history_lacks_fk(self):
983+
historical = self.get_first_historical()
984+
with self.assertRaises(AttributeError):
985+
historical.place
986+
987+
def test_nb_queries(self):
988+
with self.assertNumQueries(2):
989+
historical = self.get_first_historical()
990+
historical.instance
991+
992+
def test_changed_value_lost(self):
993+
new_place = Place.objects.create(name="More precise")
994+
self.poll.place = new_place
995+
self.poll.save()
996+
historical = self.get_first_historical()
997+
instance = historical.instance
998+
self.assertEqual(instance.place, new_place)

0 commit comments

Comments
 (0)