Skip to content

Commit 9896652

Browse files
committed
Make historical records' m2m fields type-compatible with non-historical
Fixes #1186
1 parent b5eadd5 commit 9896652

File tree

3 files changed

+48
-15
lines changed

3 files changed

+48
-15
lines changed

simple_history/manager.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,35 @@ def __get__(self, instance, owner):
127127
return HistoryManager.from_queryset(HistoricalQuerySet)(self.model, instance)
128128

129129

130+
class HistoryManyToManyDescriptor:
131+
def __init__(self, model, rel):
132+
self.rel = rel
133+
self.model = model
134+
135+
def __get__(self, instance, owner):
136+
return HistoryManyRelatedManager.from_queryset(QuerySet)(
137+
self.model, self.rel, instance
138+
)
139+
140+
141+
class HistoryManyRelatedManager(models.Manager):
142+
def __init__(self, through, rel, instance=None):
143+
super().__init__()
144+
self.model = rel.model
145+
self.through = through
146+
self.instance = instance
147+
self._m2m_through_field_name = rel.field.m2m_reverse_field_name()
148+
149+
def get_queryset(self):
150+
qs = super().get_queryset()
151+
through_qs = HistoryManager.from_queryset(HistoricalQuerySet)(
152+
self.through, self.instance
153+
)
154+
return qs.filter(
155+
pk__in=through_qs.all().values_list(self._m2m_through_field_name, flat=True)
156+
)
157+
158+
130159
class HistoryManager(models.Manager):
131160
def __init__(self, model, instance=None):
132161
super().__init__()

simple_history/models.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@
3131
from simple_history import utils
3232

3333
from . import exceptions
34-
from .manager import SIMPLE_HISTORY_REVERSE_ATTR_NAME, HistoryDescriptor
34+
from .manager import (
35+
SIMPLE_HISTORY_REVERSE_ATTR_NAME,
36+
HistoryDescriptor,
37+
HistoryManyToManyDescriptor,
38+
)
3539
from .signals import (
3640
post_create_historical_m2m_records,
3741
post_create_historical_record,
@@ -227,7 +231,7 @@ def finalize(self, sender, **kwargs):
227231

228232
setattr(module, m2m_model.__name__, m2m_model)
229233

230-
m2m_descriptor = HistoryDescriptor(m2m_model)
234+
m2m_descriptor = HistoryManyToManyDescriptor(m2m_model, field.remote_field)
231235
setattr(history_model, field.name, m2m_descriptor)
232236

233237
def get_history_model_name(self, model):

simple_history/tests/tests/test_models.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1788,12 +1788,12 @@ def test_separation(self):
17881788
self.assertEqual(book.restaurants.all().count(), 0)
17891789
self.assertEqual(book.books.all().count(), 1)
17901790
self.assertEqual(book.places.all().count(), 1)
1791-
self.assertEqual(book.books.first().book, self.book)
1791+
self.assertEqual(book.books.first(), self.book)
17921792

17931793
self.assertEqual(place.restaurants.all().count(), 0)
17941794
self.assertEqual(place.books.all().count(), 0)
17951795
self.assertEqual(place.places.all().count(), 1)
1796-
self.assertEqual(place.places.first().place, self.place)
1796+
self.assertEqual(place.places.first(), self.place)
17971797

17981798
self.assertEqual(add.restaurants.all().count(), 0)
17991799
self.assertEqual(add.books.all().count(), 0)
@@ -1829,11 +1829,11 @@ def test_separation(self):
18291829

18301830
self.assertEqual(book.books.all().count(), 1)
18311831
self.assertEqual(book.places.all().count(), 1)
1832-
self.assertEqual(book.books.first().book, self.book)
1832+
self.assertEqual(book.books.first(), self.book)
18331833

18341834
self.assertEqual(place.books.all().count(), 0)
18351835
self.assertEqual(place.places.all().count(), 1)
1836-
self.assertEqual(place.places.first().place, self.place)
1836+
self.assertEqual(place.places.first(), self.place)
18371837

18381838
self.assertEqual(add.books.all().count(), 0)
18391839
self.assertEqual(add.places.all().count(), 0)
@@ -1842,11 +1842,11 @@ def test_separation(self):
18421842

18431843
self.assertEqual(restaurant.restaurants.all().count(), 1)
18441844
self.assertEqual(restaurant.places.all().count(), 1)
1845-
self.assertEqual(restaurant.restaurants.first().restaurant, self.restaurant)
1845+
self.assertEqual(restaurant.restaurants.first(), self.restaurant)
18461846

18471847
self.assertEqual(place.restaurants.all().count(), 0)
18481848
self.assertEqual(place.places.all().count(), 1)
1849-
self.assertEqual(place.places.first().place, self.place)
1849+
self.assertEqual(place.places.first(), self.place)
18501850

18511851
self.assertEqual(add.restaurants.all().count(), 0)
18521852
self.assertEqual(add.places.all().count(), 0)
@@ -1964,7 +1964,7 @@ def test_create(self):
19641964

19651965
# And the historical place is the correct one
19661966
historical_place = m2m_record.places.first()
1967-
self.assertEqual(historical_place.place, self.place)
1967+
self.assertEqual(historical_place, self.place)
19681968

19691969
def test_remove(self):
19701970
# Add and remove a many-to-many child
@@ -1984,7 +1984,7 @@ def test_remove(self):
19841984

19851985
# And the previous row still has the correct one
19861986
historical_place = previous_m2m_record.places.first()
1987-
self.assertEqual(historical_place.place, self.place)
1987+
self.assertEqual(historical_place, self.place)
19881988

19891989
def test_clear(self):
19901990
# Add some places
@@ -2036,7 +2036,7 @@ def test_delete_child(self):
20362036
# Place instance cannot be created...
20372037
historical_place = m2m_record.places.first()
20382038
with self.assertRaises(ObjectDoesNotExist):
2039-
historical_place.place.id
2039+
historical_place.id
20402040

20412041
# But the values persist
20422042
historical_place_values = m2m_record.places.all().values()[0]
@@ -2066,7 +2066,7 @@ def test_delete_parent(self):
20662066

20672067
# And it is the correct one
20682068
historical_place = prev_record.places.first()
2069-
self.assertEqual(historical_place.place, self.place)
2069+
self.assertEqual(historical_place, self.place)
20702070

20712071
def test_update_child(self):
20722072
self.poll.places.add(self.place)
@@ -2084,7 +2084,7 @@ def test_update_child(self):
20842084
m2m_record = self.poll.history.all()[0]
20852085
self.assertEqual(m2m_record.places.count(), 1)
20862086
historical_place = m2m_record.places.first()
2087-
self.assertEqual(historical_place.place.name, "Updated")
2087+
self.assertEqual(historical_place.name, "Updated")
20882088

20892089
def test_update_parent(self):
20902090
self.poll.places.add(self.place)
@@ -2102,7 +2102,7 @@ def test_update_parent(self):
21022102
m2m_record = self.poll.history.all()[0]
21032103
self.assertEqual(m2m_record.places.count(), 1)
21042104
historical_place = m2m_record.places.first()
2105-
self.assertEqual(historical_place.place, self.place)
2105+
self.assertEqual(historical_place, self.place)
21062106

21072107
def test_bulk_add_remove(self):
21082108
# Add some places
@@ -2134,7 +2134,7 @@ def test_bulk_add_remove(self):
21342134
self.assertEqual(m2m_record.places.all().count(), 1)
21352135

21362136
historical_place = m2m_record.places.first()
2137-
self.assertEqual(historical_place.place, self.place)
2137+
self.assertEqual(historical_place, self.place)
21382138

21392139
def test_m2m_relation(self):
21402140
# Ensure only the correct M2Ms are saved and returned for history objects

0 commit comments

Comments
 (0)