15
15
from django .db import models
16
16
from django .db .models import ManyToManyField
17
17
from django .db .models .fields .proxy import OrderWrt
18
- from django .db .models .fields .related import ForeignKey
18
+ from django .db .models .fields .related import ForeignKey , lazy_related_operation
19
19
from django .db .models .fields .related_descriptors import (
20
20
ForwardManyToOneDescriptor ,
21
21
ReverseManyToOneDescriptor ,
@@ -84,6 +84,8 @@ class HistoricalRecords:
84
84
DEFAULT_MODEL_NAME_PREFIX = "Historical"
85
85
86
86
thread = context = LocalContext () # retain thread for backwards compatibility
87
+ # Key is the m2m field and value is a tuple where first entry is the historical m2m
88
+ # model and second is the through model
87
89
m2m_models = {}
88
90
89
91
def __init__ (
@@ -222,13 +224,6 @@ def finalize(self, sender, **kwargs):
222
224
223
225
m2m_fields = self .get_m2m_fields_from_model (sender )
224
226
225
- for field in m2m_fields :
226
- m2m_changed .connect (
227
- partial (self .m2m_changed , attr = field .name ),
228
- sender = field .remote_field .through ,
229
- weak = False ,
230
- )
231
-
232
227
descriptor = HistoryDescriptor (
233
228
history_model ,
234
229
manager = self .history_manager ,
@@ -238,15 +233,29 @@ def finalize(self, sender, **kwargs):
238
233
sender ._meta .simple_history_manager_attribute = self .manager_name
239
234
240
235
for field in m2m_fields :
241
- m2m_model = self .create_history_m2m_model (
242
- history_model , field .remote_field .through
243
- )
244
- self .m2m_models [field ] = m2m_model
245
236
246
- setattr (module , m2m_model .__name__ , m2m_model )
237
+ def resolve_through_model (history_model , through_model ):
238
+ m2m_changed .connect (
239
+ partial (self .m2m_changed , attr = field .name ),
240
+ sender = through_model ,
241
+ weak = False ,
242
+ )
243
+ m2m_model = self .create_history_m2m_model (history_model , through_model )
244
+ # Save the created history model and the resolved through model together
245
+ # for reference later
246
+ self .m2m_models [field ] = (m2m_model , through_model )
247
+
248
+ setattr (module , m2m_model .__name__ , m2m_model )
247
249
248
- m2m_descriptor = HistoryDescriptor (m2m_model )
249
- setattr (history_model , field .name , m2m_descriptor )
250
+ m2m_descriptor = HistoryDescriptor (m2m_model )
251
+ setattr (history_model , field .name , m2m_descriptor )
252
+
253
+ # Lazily generate the historical m2m models for the fields when all of the
254
+ # associated models have been fully loaded. This handles resolving through
255
+ # models referenced as strings. This is how django m2m fields handle this.
256
+ lazy_related_operation (
257
+ resolve_through_model , history_model , field .remote_field .through
258
+ )
250
259
251
260
def get_history_model_name (self , model ):
252
261
if not self .custom_model_name :
@@ -685,9 +694,7 @@ def m2m_changed(self, instance, action, attr, pk_set, reverse, **_):
685
694
686
695
def create_historical_record_m2ms (self , history_instance , instance ):
687
696
for field in history_instance ._history_m2m_fields :
688
- m2m_history_model = self .m2m_models [field ]
689
- original_instance = history_instance .instance
690
- through_model = getattr (original_instance , field .name ).through
697
+ m2m_history_model , through_model = self .m2m_models [field ]
691
698
through_model_field_names = [f .name for f in through_model ._meta .fields ]
692
699
through_model_fk_field_names = [
693
700
f .name for f in through_model ._meta .fields if isinstance (f , ForeignKey )
0 commit comments