Repository: jazzband/django-simple-history

Issue:
diff_against doesn't detect changes to parent model attributes when using inherited models
**Describe the bug**
`HistoricalChanges.diff_against` won't detect changes in inherited models (`inherit=True`).

Problem resides at the way `_model_to_dict` serializes fields.

~~~ python
def _model_to_dict(model):
    return json.loads(serialize("json", [model]))[0]["fields"]
~~~
It uses `django.core.serializers.serialize`, but as explained [here](https://docs.djangoproject.com/en/2.2/topics/serialization/#inherited-models):

> if you have a model that uses multi-table inheritance, you also need to serialize all of the base classes for the model. This is because only the fields that are locally defined on the model will be serialized.

So it ignores any fields in the parent class.

**To Reproduce**
Steps to reproduce the behavior:
1. Have inherited models:

~~~ python
class Place(models.Model):
    name = models.CharField(max_length=50)

class Restaurant(Place):
    serves_hot_dogs = models.BooleanField(default=False)
~~~

Then from a `Restaurant` instance change an attribute from the parent class `Place.name`

~~~ python
r = Restaurant.objects.create(name="McDonalds")
r.name = "BurgerKing"
r.save()
~~~

Now try to detect changes:

~~~ python
new_record, old_record = r.history.all()
delta = new_record.diff_against(old_record)
for change in delta.changes:
    print("{} changed from {} to {}".format(change.field, change.old, change.new))
~~~

But it won't detect the `name` field so it doesn't detect any changes and `delta.changes=[]`

**Environment (please complete the following information):**
 - OS: Ubuntu 18.10
 - Django Simple History Version: 2.7.2
 - Django Version: 2.2.2



Discussion:
found same issue, django provide a function called 'model_to_dict' in <django.forms> module, I replace json.loads(serialize("json", [model]))[0]["fields"]  to model_to_dict(model), it works

Resolving patch:
diff --git a/simple_history/models.py b/simple_history/models.py
--- a/simple_history/models.py
+++ b/simple_history/models.py
@@ -16,6 +16,7 @@
 from django.db import models
 from django.db.models import Q
 from django.db.models.fields.proxy import OrderWrt
+from django.forms.models import model_to_dict
 from django.urls import reverse
 from django.utils.encoding import python_2_unicode_compatible, smart_text
 from django.utils.text import format_lazy
@@ -32,10 +33,6 @@
 registered_models = {}
 
 
-def _model_to_dict(model):
-    return json.loads(serialize("json", [model]))[0]["fields"]
-
-
 def _default_get_user(request, **kwargs):
     try:
         return request.user
@@ -576,8 +573,8 @@ def diff_against(self, old_history):
 
         changes = []
         changed_fields = []
-        old_values = _model_to_dict(old_history.instance)
-        current_values = _model_to_dict(self.instance)
+        old_values = model_to_dict(old_history.instance)
+        current_values = model_to_dict(self.instance)
         for field, new_value in current_values.items():
             if field in old_values:
                 old_value = old_values[field]


Test patch:
diff --git a/simple_history/tests/models.py b/simple_history/tests/models.py
--- a/simple_history/tests/models.py
+++ b/simple_history/tests/models.py
@@ -483,6 +483,15 @@ class InheritTracking4(TrackedAbstractBaseA):
     pass
 
 
+class BasePlace(models.Model):
+    name = models.CharField(max_length=50)
+    history = HistoricalRecords(inherit=True)
+
+
+class InheritedRestaurant(BasePlace):
+    serves_hot_dogs = models.BooleanField(default=False)
+
+
 class BucketMember(models.Model):
     name = models.CharField(max_length=30)
     user = models.OneToOneField(
diff --git a/simple_history/tests/tests/test_models.py b/simple_history/tests/tests/test_models.py
--- a/simple_history/tests/tests/test_models.py
+++ b/simple_history/tests/tests/test_models.py
@@ -35,6 +35,7 @@
 from ..models import (
     AbstractBase,
     AdminProfile,
+    BasePlace,
     Book,
     Bookcase,
     BucketData,
@@ -62,6 +63,7 @@
     HistoricalPoll,
     HistoricalPollWithHistoricalIPAddress,
     HistoricalState,
+    InheritedRestaurant,
     OverrideModelNameAsCallable,
     OverrideModelNameUsingBaseModel1,
     MyOverrideModelNameRegisterMethod1,
@@ -594,6 +596,19 @@ def test_history_diff_does_not_include_unchanged_fields(self):
         delta = new_record.diff_against(old_record)
         self.assertNotIn("pub_date", delta.changed_fields)
 
+    def test_history_diff_includes_changed_fields_of_base_model(self):
+        r = InheritedRestaurant.objects.create(name="McDonna", serves_hot_dogs=False)
+        # change base model field
+        r.name = "DonnutsKing"
+        r.save()
+        new_record, old_record = r.history.all()
+        delta = new_record.diff_against(old_record)
+        expected_change = ModelChange("name", "McDonna", "DonnutsKing")
+        self.assertEqual(delta.changed_fields, ["name"])
+        self.assertEqual(delta.old_record, old_record)
+        self.assertEqual(delta.new_record, new_record)
+        self.assertEqual(expected_change.field, delta.changes[0].field)
+
     def test_history_diff_with_incorrect_type(self):
         p = Poll.objects.create(question="what's up?", pub_date=today)
         p.question = "what's up, man?"