Repository: jazzband/django-simple-history

Issue:
Add option to exclude indices from being created for history tables
**Problem Statement**
We have some rather large tables where we use django-simple-history. The total size of the history tables is quite large but also the indices created. We do not use (most of) the indexes in the history table. It would be great if we could exclude them from being created.

This will save performance while updating the history table but especially will save us disk space. Expected disk space to be saved in our case would be over 30Gb.

**Describe the solution you'd like**
An option to exclude indices like we exclude fields. Not sure what best solution would be.

**Describe alternatives you've considered**
None.

**Additional context**
NA.



Discussion:

Resolving patch:
diff --git a/simple_history/models.py b/simple_history/models.py
--- a/simple_history/models.py
+++ b/simple_history/models.py
@@ -80,6 +80,7 @@ def __init__(
         related_name=None,
         use_base_model_db=False,
         user_db_constraint=True,
+        no_db_index=list(),
         excluded_field_kwargs=None,
     ):
         self.user_set_verbose_name = verbose_name
@@ -101,6 +102,10 @@ def __init__(
         self.related_name = related_name
         self.use_base_model_db = use_base_model_db
 
+        if isinstance(no_db_index, str):
+            no_db_index = [no_db_index]
+        self.no_db_index = no_db_index
+
         if excluded_fields is None:
             excluded_fields = []
         self.excluded_fields = excluded_fields
@@ -305,6 +310,11 @@ def copy_fields(self, model):
                 field.name = old_field.name
             else:
                 transform_field(field)
+
+            # drop db index
+            if field.name in self.no_db_index:
+                field.db_index = False
+
             fields[field.name] = field
         return fields
 


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
@@ -770,3 +770,21 @@ class ModelWithExcludedManyToMany(models.Model):
     name = models.CharField(max_length=15, unique=True)
     other = models.ManyToManyField(ManyToManyModelOther)
     history = HistoricalRecords(excluded_fields=["other"])
+
+
+class ModelWithSingleNoDBIndexUnique(models.Model):
+    name = models.CharField(max_length=15, unique=True, db_index=True)
+    name_keeps_index = models.CharField(max_length=15, unique=True, db_index=True)
+    history = HistoricalRecords(no_db_index="name")
+
+
+class ModelWithMultipleNoDBIndex(models.Model):
+    name = models.CharField(max_length=15, db_index=True)
+    name_keeps_index = models.CharField(max_length=15, db_index=True)
+    fk = models.ForeignKey(
+        "Library", on_delete=models.CASCADE, null=True, related_name="+"
+    )
+    fk_keeps_index = models.ForeignKey(
+        "Library", on_delete=models.CASCADE, null=True, related_name="+"
+    )
+    history = HistoricalRecords(no_db_index=["name", "fk", "other"])
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
@@ -70,6 +70,8 @@
     ModelWithFkToModelWithHistoryUsingBaseModelDb,
     ModelWithHistoryInDifferentDb,
     ModelWithHistoryUsingBaseModelDb,
+    ModelWithMultipleNoDBIndex,
+    ModelWithSingleNoDBIndexUnique,
     MultiOneToOne,
     MyOverrideModelNameRegisterMethod1,
     OverrideModelNameAsCallable,
@@ -1822,3 +1824,35 @@ def test_history_model_saved_in_separate_db_on_delete(self):
         self.assertEqual(
             0, ModelWithHistoryInDifferentDb.objects.using("other").count()
         )
+
+
+class ModelWithMultipleNoDBIndexTest(TestCase):
+    def setUp(self):
+        self.model = ModelWithMultipleNoDBIndex
+        self.history_model = self.model.history.model
+
+    def test_field_indices(self):
+        for field in ["name", "fk"]:
+            # dropped index
+            self.assertTrue(self.model._meta.get_field(field).db_index)
+            self.assertFalse(self.history_model._meta.get_field(field).db_index)
+
+            # keeps index
+            keeps_index = "%s_keeps_index" % field
+            self.assertTrue(self.model._meta.get_field(keeps_index).db_index)
+            self.assertTrue(self.history_model._meta.get_field(keeps_index).db_index)
+
+
+class ModelWithSingleNoDBIndexUniqueTest(TestCase):
+    def setUp(self):
+        self.model = ModelWithSingleNoDBIndexUnique
+        self.history_model = self.model.history.model
+
+    def test_unique_field_index(self):
+        # Ending up with deferred fields (dont know why), using work around
+        self.assertTrue(self.model._meta.get_field("name").db_index)
+        self.assertFalse(self.history_model._meta.get_field("name").db_index)
+
+        # keeps index
+        self.assertTrue(self.model._meta.get_field("name_keeps_index").db_index)
+        self.assertTrue(self.history_model._meta.get_field("name_keeps_index").db_index)