Skip to content

Commit 3bf5a96

Browse files
committed
Merge pull request #142 from jwhitlock/order_wrt_included
Fix #140 - Convert OrderWrt to IntegerField
2 parents e3ff030 + 7d3066b commit 3bf5a96

File tree

3 files changed

+140
-1
lines changed

3 files changed

+140
-1
lines changed

simple_history/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
apps = None
99
from django.db import models, router
1010
from django.db.models import loading
11+
from django.db.models.fields.proxy import OrderWrt
1112
from django.db.models.fields.related import RelatedField
1213
from django.db.models.related import RelatedObject
1314
from django.conf import settings
@@ -135,6 +136,9 @@ def copy_fields(self, model):
135136
field.rel.related_name = '+'
136137
field.null = True
137138
field.blank = True
139+
if isinstance(field, OrderWrt):
140+
# OrderWrt is a proxy field, switch to a plain IntegerField
141+
field.__class__ = models.IntegerField
138142
transform_field(field)
139143
fields[field.name] = field
140144
return fields

simple_history/tests/models.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,3 +217,18 @@ class Meta:
217217
class CustomFKError(models.Model):
218218
fk = models.ForeignKey(SecondLevelInheritedModel)
219219
history = HistoricalRecords()
220+
221+
222+
class Series(models.Model):
223+
"""A series of works, like a trilogy of books."""
224+
name = models.CharField(max_length=100)
225+
author = models.CharField(max_length=100)
226+
227+
228+
class SeriesWork(models.Model):
229+
series = models.ForeignKey('Series', related_name='works')
230+
title = models.CharField(max_length=100)
231+
history = HistoricalRecords()
232+
233+
class Meta:
234+
order_with_respect_to = 'series'

simple_history/tests/tests/test_models.py

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from django.contrib.auth.models import User
1515
from django.db import models
1616
from django.db.models.loading import get_model
17+
from django.db.models.fields.proxy import OrderWrt
1718
from django.test import TestCase
1819
from django.core.files.base import ContentFile
1920

@@ -24,7 +25,7 @@
2425
Person, FileModel, Document, Book, HistoricalPoll, Library, State,
2526
AbstractBase, ConcreteAttr, ConcreteUtil, SelfFK, Temperature, WaterLevel,
2627
ExternalModel1, ExternalModel3, UnicodeVerboseName, HistoricalChoice,
27-
HistoricalState, HistoricalCustomFKError
28+
HistoricalState, HistoricalCustomFKError, Series, SeriesWork
2829
)
2930
from ..external.models import ExternalModel2, ExternalModel4
3031

@@ -519,3 +520,122 @@ def test_non_relational(self):
519520
with self.settings(DATABASES={'default': {
520521
'ENGINE': 'django_mongodb_engine'}}):
521522
assert convert_auto_field(self.field) == models.TextField
523+
524+
525+
class TestOrderWrtField(TestCase):
526+
"""Check behaviour of _order field added by Meta.order_with_respect_to.
527+
528+
The Meta.order_with_respect_to option adds an OrderWrt field named
529+
"_order", where OrderWrt is a proxy class for an IntegerField that sets
530+
some default options.
531+
532+
The simple_history strategy is:
533+
- Convert to a plain IntegerField in the historical record
534+
- When restoring a historical instance, add the old value. This may
535+
result in duplicate ordering values and non-deterministic ordering.
536+
"""
537+
538+
def setUp(self):
539+
"""Create works in published order."""
540+
s = self.series = Series.objects.create(
541+
name="The Chronicles of Narnia", author="C.S. Lewis")
542+
self.w_lion = s.works.create(
543+
title="The Lion, the Witch and the Wardrobe")
544+
self.w_caspian = s.works.create(title="Prince Caspian")
545+
self.w_voyage = s.works.create(title="The Voyage of the Dawn Treader")
546+
self.w_chair = s.works.create(title="The Silver Chair")
547+
self.w_horse = s.works.create(title="The Horse and His Boy")
548+
self.w_nephew = s.works.create(title="The Magician's Nephew")
549+
self.w_battle = s.works.create(title="The Last Battle")
550+
551+
def test_order(self):
552+
"""Confirm that works are ordered by creation."""
553+
order = self.series.get_serieswork_order()
554+
expected = [
555+
self.w_lion.pk,
556+
self.w_caspian.pk,
557+
self.w_voyage.pk,
558+
self.w_chair.pk,
559+
self.w_horse.pk,
560+
self.w_nephew.pk,
561+
self.w_battle.pk]
562+
self.assertEqual(order, expected)
563+
self.assertEqual(0, self.w_lion._order)
564+
self.assertEqual(1, self.w_caspian._order)
565+
self.assertEqual(2, self.w_voyage._order)
566+
self.assertEqual(3, self.w_chair._order)
567+
self.assertEqual(4, self.w_horse._order)
568+
self.assertEqual(5, self.w_nephew._order)
569+
self.assertEqual(6, self.w_battle._order)
570+
571+
def test_order_field_in_historical_model(self):
572+
work_order_field = self.w_lion._meta.get_field_by_name('_order')[0]
573+
self.assertEqual(type(work_order_field), OrderWrt)
574+
575+
history = self.w_lion.history.all()[0]
576+
history_order_field = history._meta.get_field_by_name('_order')[0]
577+
self.assertEqual(type(history_order_field), models.IntegerField)
578+
579+
def test_history_object_has_order(self):
580+
history = self.w_lion.history.all()[0]
581+
self.assertEqual(self.w_lion._order, history.history_object._order)
582+
583+
def test_restore_object_with_changed_order(self):
584+
# Change a title
585+
self.w_caspian.title = 'Prince Caspian: The Return to Narnia'
586+
self.w_caspian.save()
587+
self.assertEqual(2, len(self.w_caspian.history.all()))
588+
self.assertEqual(1, self.w_caspian._order)
589+
590+
# Switch to internal chronological order
591+
chronological = [
592+
self.w_nephew.pk,
593+
self.w_lion.pk,
594+
self.w_horse.pk,
595+
self.w_caspian.pk,
596+
self.w_voyage.pk,
597+
self.w_chair.pk,
598+
self.w_battle.pk]
599+
self.series.set_serieswork_order(chronological)
600+
self.assertEqual(self.series.get_serieswork_order(), chronological)
601+
602+
# This uses an update, not a save, so no new history is created
603+
w_caspian = SeriesWork.objects.get(id=self.w_caspian.id)
604+
self.assertEqual(2, len(w_caspian.history.all()))
605+
self.assertEqual(1, w_caspian.history.all()[0]._order)
606+
self.assertEqual(1, w_caspian.history.all()[1]._order)
607+
self.assertEqual(3, w_caspian._order)
608+
609+
# Revert to first title, old order
610+
old = w_caspian.history.all()[1].history_object
611+
old.save()
612+
w_caspian = SeriesWork.objects.get(id=self.w_caspian.id)
613+
self.assertEqual(3, len(w_caspian.history.all()))
614+
self.assertEqual(1, w_caspian.history.all()[0]._order)
615+
self.assertEqual(1, w_caspian.history.all()[1]._order)
616+
self.assertEqual(1, w_caspian.history.all()[2]._order)
617+
self.assertEqual(1, w_caspian._order) # The order changed
618+
w_lion = SeriesWork.objects.get(id=self.w_lion.id)
619+
self.assertEqual(1, w_lion._order) # and is identical to another order
620+
621+
# New order is non-deterministic around identical IDs
622+
series = Series.objects.get(id=self.series.id)
623+
order = series.get_serieswork_order()
624+
self.assertEqual(order[0], self.w_nephew.pk)
625+
self.assertTrue(order[1] in (self.w_lion.pk, self.w_caspian.pk))
626+
self.assertTrue(order[2] in (self.w_lion.pk, self.w_caspian.pk))
627+
self.assertEqual(order[3], self.w_horse.pk)
628+
self.assertEqual(order[4], self.w_voyage.pk)
629+
self.assertEqual(order[5], self.w_chair.pk)
630+
self.assertEqual(order[6], self.w_battle.pk)
631+
632+
@skipUnless(django.get_version() >= "1.7", "Requires 1.7 migrations")
633+
def test_migrations_include_order(self):
634+
from django.db.migrations import state
635+
model_state = state.ModelState.from_model(SeriesWork.history.model)
636+
found = False
637+
for name, field in model_state.fields:
638+
if name == '_order':
639+
found = True
640+
self.assertEqual(type(field), models.IntegerField)
641+
assert found, '_order not in fields ' + repr(model_state.fields)

0 commit comments

Comments
 (0)