Skip to content

Commit a3db81a

Browse files
author
Ross Mechanic
authored
Merge pull request #355 from rossmechanic/inheritedmodule
Fixes issue with tracking an inherited model
2 parents 29da7a5 + e3c844c commit a3db81a

File tree

6 files changed

+57
-12
lines changed

6 files changed

+57
-12
lines changed

AUTHORS.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Authors
4444
- Jesse Shapiro
4545
- Kevin Foster
4646
- Shane Engelman
47+
- Ray Logel
4748

4849
Background
4950
==========

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Unreleased
66
- Use get_queryset rather than model.objects in history_view. (gh-303)
77
- Change ugettext calls in models.py to ugettext_lazy
88
- Resolve issue where model references itself (gh-278)
9+
- Fix issue with tracking an inherited model (abstract class) (gh-269)
10+
- Resolve issue where model references itself (gh-278)
911

1012
1.9.0 (2017-06-11)
1113
------------------

simple_history/models.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,15 @@ def save_without_historical_record(self, *args, **kwargs):
7373
save_without_historical_record)
7474

7575
def finalize(self, sender, **kwargs):
76+
inherited = False
7677
try:
7778
hint_class = self.cls
7879
except AttributeError: # called via `register`
7980
pass
8081
else:
8182
if hint_class is not sender: # set in concrete
82-
if not (self.inherit and issubclass(sender, hint_class)):
83+
inherited = (self.inherit and issubclass(sender, hint_class))
84+
if not inherited:
8385
return # set in abstract
8486
if hasattr(sender._meta, 'simple_history_manager_attribute'):
8587
raise exceptions.MultipleRegistrationsError(
@@ -88,8 +90,12 @@ def finalize(self, sender, **kwargs):
8890
sender._meta.object_name,
8991
)
9092
)
91-
history_model = self.create_history_model(sender)
92-
module = importlib.import_module(self.module)
93+
history_model = self.create_history_model(sender, inherited)
94+
if inherited:
95+
# Make sure history model is in same module as concrete model
96+
module = importlib.import_module(history_model.__module__)
97+
else:
98+
module = importlib.import_module(self.module)
9399
setattr(module, history_model.__name__, history_model)
94100

95101
# The HistoricalRecords object will be discarded,
@@ -103,14 +109,18 @@ def finalize(self, sender, **kwargs):
103109
setattr(sender, self.manager_name, descriptor)
104110
sender._meta.simple_history_manager_attribute = self.manager_name
105111

106-
def create_history_model(self, model):
112+
def create_history_model(self, model, inherited):
107113
"""
108114
Creates a historical model to associate with the model provided.
109115
"""
110116
attrs = {'__module__': self.module}
111117

112118
app_module = '%s.models' % model._meta.app_label
113-
if model.__module__ != self.module:
119+
120+
if inherited:
121+
# inherited use models module
122+
attrs['__module__'] = model.__module__
123+
elif model.__module__ != self.module:
114124
# registered under different app
115125
attrs['__module__'] = self.module
116126
elif app_module != self.module:

simple_history/registry_tests/tests.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,21 @@ def test_accessor_override(self):
7171
assert hasattr(User, 'my_history_model_accessor')
7272

7373

74+
class TestInheritedModule(TestCase):
75+
76+
def test_using_app_label(self):
77+
try:
78+
from ..tests.models import HistoricalConcreteExternal
79+
except ImportError:
80+
self.fail("HistoricalConcreteExternal is in wrong module")
81+
82+
def test_default(self):
83+
try:
84+
from ..tests.models import HistoricalConcreteExternal2
85+
except ImportError:
86+
self.fail("HistoricalConcreteExternal2 is in wrong module")
87+
88+
7489
class TestTrackingInheritance(TestCase):
7590

7691
def test_tracked_abstract_base(self):

simple_history/tests/models.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,13 @@ class Meta:
355355
app_label = 'tests'
356356

357357

358+
class ConcreteExternal2(AbstractExternal):
359+
name = models.CharField(max_length=50)
360+
361+
class Meta:
362+
pass # Don't set app_label to test inherited module path
363+
364+
358365
class TrackedWithAbstractBase(TrackedAbstractBaseA):
359366
pass
360367

simple_history/tests/tests/test_models.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,25 @@
44
import warnings
55
from datetime import datetime, timedelta
66

7-
import django
7+
from django.apps import apps
88
from django.contrib.auth import get_user_model
99
from django.core.files.base import ContentFile
1010
from django.db import models
1111
from django.db.models.fields.proxy import OrderWrt
1212
from django.test import TestCase
13+
1314
from simple_history.models import HistoricalRecords, convert_auto_field
1415
from simple_history.utils import update_change_reason
15-
1616
from ..external.models import ExternalModel2, ExternalModel4
1717
from ..models import (AbstractBase, AdminProfile, Book, Bookcase, Choice, City,
18-
ConcreteAttr, ConcreteUtil, Contact, ContactRegister,
18+
ConcreteAttr, ConcreteExternal, ConcreteUtil, Contact, ContactRegister,
1919
Country, Document, Employee, ExternalModel1,
2020
ExternalModel3, FileModel, HistoricalChoice,
2121
HistoricalCustomFKError, HistoricalPoll, HistoricalState,
2222
Library, MultiOneToOne, Person, Poll, PollInfo,
2323
PollWithExcludeFields, Province, Restaurant, SelfFK,
2424
Series, SeriesWork, State, Temperature,
2525
UnicodeVerboseName, WaterLevel)
26-
from django.apps import apps
2726

2827
get_model = apps.get_model
2928
User = get_user_model()
@@ -345,7 +344,7 @@ def test_create_history_model_with_one_to_one_field_to_integer_field(self):
345344
records = HistoricalRecords()
346345
records.module = AdminProfile.__module__
347346
try:
348-
records.create_history_model(AdminProfile)
347+
records.create_history_model(AdminProfile, False)
349348
except:
350349
self.fail("SimpleHistory should handle foreign keys to one to one"
351350
"fields to integer fields without throwing an exception")
@@ -354,7 +353,7 @@ def test_create_history_model_with_one_to_one_field_to_char_field(self):
354353
records = HistoricalRecords()
355354
records.module = Bookcase.__module__
356355
try:
357-
records.create_history_model(Bookcase)
356+
records.create_history_model(Bookcase, False)
358357
except:
359358
self.fail("SimpleHistory should handle foreign keys to one to one"
360359
"fields to char fields without throwing an exception.")
@@ -363,7 +362,7 @@ def test_create_history_model_with_multiple_one_to_ones(self):
363362
records = HistoricalRecords()
364363
records.module = MultiOneToOne.__module__
365364
try:
366-
records.create_history_model(MultiOneToOne)
365+
records.create_history_model(MultiOneToOne, False)
367366
except:
368367
self.fail("SimpleHistory should handle foreign keys to one to one"
369368
"fields to one to one fields without throwing an "
@@ -396,6 +395,10 @@ def test_register_app_label(self):
396395
'external_externalmodel4')
397396
self.assertEqual(self.get_table_name(ExternalModel4.histories),
398397
'tests_historicalexternalmodel4')
398+
self.assertEqual(self.get_table_name(ConcreteExternal.objects),
399+
'tests_concreteexternal')
400+
self.assertEqual(self.get_table_name(ConcreteExternal.history),
401+
'tests_historicalconcreteexternal')
399402

400403
def test_get_model(self):
401404
self.assertEqual(get_model('external', 'ExternalModel1'),
@@ -418,6 +421,13 @@ def test_get_model(self):
418421
self.assertEqual(get_model('tests', 'HistoricalExternalModel4'),
419422
ExternalModel4.histories.model)
420423

424+
# Test that historical model is defined within app of concrete
425+
# model rather than abstract base model
426+
self.assertEqual(get_model('tests', 'ConcreteExternal'),
427+
ConcreteExternal)
428+
self.assertEqual(get_model('tests', 'HistoricalConcreteExternal'),
429+
ConcreteExternal.history.model)
430+
421431

422432
class HistoryManagerTest(TestCase):
423433
def test_most_recent(self):

0 commit comments

Comments
 (0)