Repository: jazzband/django-simple-history

Issue:
Problem to use history = HistoricalRecords(excluded_fields=['description'])
I would like to exclude some fields of the model.

I use the following to exclude a field to the model:
```
class Metabolite(BaseModel):
    met_id = models.AutoField(primary_key=True, db_column="met_id")
    abbreviation = models.CharField(max_length=250, db_index=True, help_text="Unique metabolite identifier")
    fullName = models.TextField(help_text="Name of the metabolite")
    description = models.TextField(null=True, help_text="Description of metabolite in HMDB", blank=True)
    history = HistoricalRecords(excluded_fields=['description'])
```
I made makemigrations
Following file created:
 operations = [
        migrations.RemoveField(
            model_name='historicalmetabolite',
            name='description',
        ),
I run the migration
And when I run python manage.py populate_history --auto
I add the following mistake:

Saving historical records for <class 'reconws.models.Metabolite'>
```
Traceback (most recent call last):
  File "manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/srv/venv/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 364, in execute_from_command_line
    utility.execute()
  File "/srv/venv/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 356, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/srv/venv/local/lib/python2.7/site-packages/django/core/management/base.py", line 283, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/srv/venv/local/lib/python2.7/site-packages/django/core/management/base.py", line 330, in execute
    output = self.handle(*args, **options)
  File "/srv/venv/local/lib/python2.7/site-packages/simple_history/management/commands/populate_history.py", line 65, in handle
    self._process(to_process, batch_size=options['batchsize'])
  File "/srv/venv/local/lib/python2.7/site-packages/simple_history/management/commands/populate_history.py", line 110, in _process
    utils.bulk_history_create(model, history_model, batch_size)
  File "/srv/venv/local/lib/python2.7/site-packages/simple_history/management/commands/_populate_utils.py", line 28, in bulk_history_create
    ) for instance in model.objects.all()]
  File "/srv/venv/local/lib/python2.7/site-packages/django/db/models/base.py", line 572, in __init__
    raise TypeError("'%s' is an invalid keyword argument for this function" % list(kwargs)[0])
TypeError: 'description' is an invalid keyword argument for this function

```



**Environment (please complete the following information):**
 - OS: Ubuntu
 - Django Simple History Version: django-simple-history==2.0
 - Django Version: Django==1.11.10

**Additional context**




Discussion:

Resolving patch:
diff --git a/simple_history/manager.py b/simple_history/manager.py
--- a/simple_history/manager.py
+++ b/simple_history/manager.py
@@ -100,6 +100,7 @@ def bulk_history_create(self, objs, batch_size=None):
                 **{
                     field.attname: getattr(instance, field.attname)
                     for field in instance._meta.fields
+                    if field.name not in self.model._history_excluded_fields
                 }
             ) for instance in objs]
 
diff --git a/simple_history/models.py b/simple_history/models.py
--- a/simple_history/models.py
+++ b/simple_history/models.py
@@ -113,7 +113,10 @@ def create_history_model(self, model, inherited):
         """
         Creates a historical model to associate with the model provided.
         """
-        attrs = {'__module__': self.module}
+        attrs = {
+            '__module__': self.module,
+            '_history_excluded_fields': self.excluded_fields
+        }
 
         app_module = '%s.models' % model._meta.app_label
 
@@ -220,10 +223,20 @@ def revert_url(self):
             )
 
         def get_instance(self):
-            return model(**{
+            attrs = {
                 field.attname: getattr(self, field.attname)
                 for field in fields.values()
-            })
+            }
+            if self._history_excluded_fields:
+                excluded_attnames = [
+                    model._meta.get_field(field).attname
+                    for field in self._history_excluded_fields
+                ]
+                values = model.objects.filter(
+                    pk=getattr(self, model._meta.pk.attname)
+                ).values(*excluded_attnames).get()
+                attrs.update(values)
+            return model(**attrs)
 
         def get_next_record(self):
             """


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
@@ -32,6 +32,14 @@ class PollWithExcludeFields(models.Model):
     history = HistoricalRecords(excluded_fields=['pub_date'])
 
 
+class PollWithExcludedFKField(models.Model):
+    question = models.CharField(max_length=200)
+    pub_date = models.DateTimeField('date published')
+    place = models.ForeignKey('Place', on_delete=models.CASCADE)
+
+    history = HistoricalRecords(excluded_fields=['place'])
+
+
 class Temperature(models.Model):
     location = models.CharField(max_length=200)
     temperature = models.IntegerField()
diff --git a/simple_history/tests/tests/test_commands.py b/simple_history/tests/tests/test_commands.py
--- a/simple_history/tests/tests/test_commands.py
+++ b/simple_history/tests/tests/test_commands.py
@@ -7,7 +7,7 @@
 
 from simple_history import models as sh_models
 from simple_history.management.commands import populate_history
-from ..models import Book, Poll, Restaurant
+from ..models import Book, Poll, PollWithExcludeFields, Restaurant
 
 
 @contextmanager
@@ -142,3 +142,13 @@ def test_stdout_printed_when_verbosity_is_not_specified(self):
                                 stdout=out, stderr=StringIO())
 
         self.assertNotEqual(out.getvalue(), '')
+
+    def test_excluded_fields(self):
+        poll = PollWithExcludeFields.objects.create(
+            question="Will this work?", pub_date=datetime.now())
+        PollWithExcludeFields.history.all().delete()
+        management.call_command(self.command_name,
+                                'tests.pollwithexcludefields', auto=True,
+                                stdout=StringIO(), stderr=StringIO())
+        initial_history_record = PollWithExcludeFields.history.all()[0]
+        self.assertEqual(initial_history_record.question, poll.question)
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
@@ -40,9 +40,11 @@
     Library,
     MultiOneToOne,
     Person,
+    Place,
     Poll,
     PollInfo,
     PollWithExcludeFields,
+    PollWithExcludedFKField,
     Province,
     Restaurant,
     SelfFK,
@@ -946,3 +948,51 @@ def test_custom_table_name_from_register(self):
             self.get_table_name(ContactRegister.history),
             'contacts_register_history',
         )
+
+
+class ExcludeFieldsTest(TestCase):
+    def test_restore_pollwithexclude(self):
+        poll = PollWithExcludeFields.objects.create(question="what's up?",
+                                                    pub_date=today)
+        historical = poll.history.order_by('pk')[0]
+        with self.assertRaises(AttributeError):
+            historical.pub_date
+        original = historical.instance
+        self.assertEqual(original.pub_date, poll.pub_date)
+
+
+class ExcludeForeignKeyTest(TestCase):
+    def setUp(self):
+        self.poll = PollWithExcludedFKField.objects.create(
+            question="Is it?", pub_date=today,
+            place=Place.objects.create(name="Somewhere")
+        )
+
+    def get_first_historical(self):
+        """
+        Retrieve the idx'th HistoricalPoll, ordered by time.
+        """
+        return self.poll.history.order_by('history_date')[0]
+
+    def test_instance_fk_value(self):
+        historical = self.get_first_historical()
+        original = historical.instance
+        self.assertEqual(original.place, self.poll.place)
+
+    def test_history_lacks_fk(self):
+        historical = self.get_first_historical()
+        with self.assertRaises(AttributeError):
+            historical.place
+
+    def test_nb_queries(self):
+        with self.assertNumQueries(2):
+            historical = self.get_first_historical()
+            historical.instance
+
+    def test_changed_value_lost(self):
+        new_place = Place.objects.create(name="More precise")
+        self.poll.place = new_place
+        self.poll.save()
+        historical = self.get_first_historical()
+        instance = historical.instance
+        self.assertEqual(instance.place, new_place)