@@ -227,6 +227,7 @@ class Meta:
227227
228228 ATTACH_FIELD_NAMES_AT = "__field_audit_field_names"
229229 ATTACH_INIT_VALUES_AT = "__field_audit_init_values"
230+ ATTACH_INIT_M2M_VALUES_AT = "__field_audit_init_m2m_values"
230231
231232 @classmethod
232233 def attach_field_names (cls , model_class , field_names ):
@@ -246,13 +247,19 @@ def field_names(cls, model_class):
246247 return getattr (model_class , cls .ATTACH_FIELD_NAMES_AT )
247248
248249 @staticmethod
249- def get_field_value (instance , field_name ):
250+ def get_field_value (instance , field_name , bootstrap = False ):
250251 """Returns the database value of a field on ``instance``.
251252
252253 :param instance: an instance of a Django model
253254 :param field_name: name of a field on ``instance``
254255 """
255256 field = instance ._meta .get_field (field_name )
257+
258+ if isinstance (field , models .ManyToManyField ):
259+ # ManyToManyField handled by Django signals
260+ if bootstrap :
261+ return AuditEvent .get_m2m_field_value (instance , field_name )
262+ return []
256263 return field .to_python (field .value_from_object (instance ))
257264
258265 @classmethod
@@ -276,6 +283,44 @@ def attach_initial_values(cls, instance):
276283 init_values = {f : cls .get_field_value (instance , f ) for f in field_names }
277284 setattr (instance , cls .ATTACH_INIT_VALUES_AT , init_values )
278285
286+ @classmethod
287+ def attach_initial_m2m_values (cls , instance , field_name ):
288+ field = instance ._meta .get_field (field_name )
289+ if not isinstance (field , models .ManyToManyField ):
290+ return None
291+
292+ values = cls .get_m2m_field_value (instance , field_name )
293+ init_values = getattr (
294+ instance , cls .ATTACH_INIT_M2M_VALUES_AT , None
295+ ) or {}
296+ init_values .update ({field_name : values })
297+ setattr (instance , cls .ATTACH_INIT_M2M_VALUES_AT , init_values )
298+
299+ @classmethod
300+ def get_initial_m2m_values (cls , instance , field_name ):
301+ init_values = getattr (
302+ instance , cls .ATTACH_INIT_M2M_VALUES_AT , None
303+ ) or {}
304+ return init_values .get (field_name )
305+
306+ @classmethod
307+ def clear_initial_m2m_field_values (cls , instance , field_name ):
308+ init_values = getattr (
309+ instance , cls .ATTACH_INIT_M2M_VALUES_AT , None
310+ ) or {}
311+ init_values .pop (field_name , None )
312+ setattr (instance , cls .ATTACH_INIT_M2M_VALUES_AT , init_values )
313+
314+ @classmethod
315+ def get_m2m_field_value (cls , instance , field_name ):
316+ if instance .pk is None :
317+ # Instance is not saved, return empty list
318+ return []
319+ else :
320+ # Instance is saved, we can access the related objects
321+ related_manager = getattr (instance , field_name )
322+ return list (related_manager .values_list ('pk' , flat = True ))
323+
279324 @classmethod
280325 def reset_initial_values (cls , instance ):
281326 """Returns the previously attached "initial values" and attaches new
@@ -397,7 +442,6 @@ def make_audit_event_from_instance(cls, instance, is_create, is_delete,
397442 object_pk = instance .pk
398443
399444 delta = cls .get_delta_from_instance (instance , is_create , is_delete )
400-
401445 if delta :
402446 return cls .create_audit_event (object_pk , type (instance ), delta ,
403447 is_create , is_delete , request )
@@ -472,7 +516,9 @@ def iter_events():
472516 for instance in iter_records ():
473517 delta = {}
474518 for field_name in field_names :
475- value = cls .get_field_value (instance , field_name )
519+ value = cls .get_field_value (
520+ instance , field_name , bootstrap = True
521+ )
476522 delta [field_name ] = {"new" : value }
477523 yield cls (
478524 object_class_path = object_class_path ,
0 commit comments