diff --git a/AUTHORS.rst b/AUTHORS.rst index 16602a25..a7a340e9 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -147,6 +147,7 @@ Authors - `Sridhar Marella `_ - `Mattia Fantoni `_ - `Trent Holliday `_ +- Raja Rehan Ahmed (`rajarehanahmed `_) Background ========== diff --git a/CHANGES.rst b/CHANGES.rst index 45eee1b1..375a0fc2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,8 @@ Unreleased ---------- - Tests are no longer bundled in released wheels (gh-1478) +- ``no_db_index`` in RecordModels model now supports ```"__all__"`` in order to drop all + indices in the historical model (gh-1491) 3.9.0 (2025-01-26) ------------------ diff --git a/docs/historical_model.rst b/docs/historical_model.rst index 32447f18..0ae3cd01 100644 --- a/docs/historical_model.rst +++ b/docs/historical_model.rst @@ -519,6 +519,28 @@ And you don't want to create database index for ``question``, it is necessary to history = HistoricalRecords(no_db_index=['question']) +A single field can also be passed as a string instead of a list: + +.. code-block:: python + + class PollWithExcludeFields(models.Model): + question = models.CharField(max_length=200, db_index=True) + + history = HistoricalRecords(no_db_index='question') + + +It is also possible to exclude all fields from having database indices in the historical table +by using __all__: + +.. code-block:: python + + class PollWithExcludeFields(models.Model): + question = models.CharField(max_length=200, db_index=True) + published_at = models.DateTimeField(auto_now_add=True, db_index=True) + + history = HistoricalRecords(no_db_index='__all__') + + By default, django-simple-history keeps all indices. and even forces them on unique fields and relations. WARNING: This will drop performance on historical lookups diff --git a/simple_history/models.py b/simple_history/models.py index b007efbf..6e7c8f03 100644 --- a/simple_history/models.py +++ b/simple_history/models.py @@ -83,6 +83,9 @@ def _history_user_setter(historical_instance, user): historical_instance.history_user_id = user.pk +ALL_FIELDS = "__all__" + + class HistoricalRecords: DEFAULT_MODEL_NAME_PREFIX = "Historical" @@ -142,7 +145,7 @@ def __init__( self.m2m_fields = m2m_fields self.m2m_fields_model_field_name = m2m_fields_model_field_name - if isinstance(no_db_index, str): + if isinstance(no_db_index, str) and no_db_index != ALL_FIELDS: no_db_index = [no_db_index] self.no_db_index = no_db_index @@ -402,7 +405,7 @@ def copy_fields(self, model): transform_field(field) # drop db index - if field.name in self.no_db_index: + if self.no_db_index == ALL_FIELDS or field.name in self.no_db_index: field.db_index = False fields[field.name] = field diff --git a/simple_history/tests/models.py b/simple_history/tests/models.py index c6771302..f4ce4105 100644 --- a/simple_history/tests/models.py +++ b/simple_history/tests/models.py @@ -925,6 +925,15 @@ class ModelWithMultipleNoDBIndex(models.Model): history = HistoricalRecords(no_db_index=["name", "fk", "other"]) +class ModelWithAllNoDBIndex(models.Model): + name = models.CharField(max_length=15, db_index=True) + name_no_index = models.CharField(max_length=15) + fk = models.ForeignKey( + "Library", on_delete=models.CASCADE, null=True, related_name="+" + ) + history = HistoricalRecords(no_db_index="__all__") + + class TestOrganization(models.Model): name = models.CharField(max_length=15, unique=True) diff --git a/simple_history/tests/tests/test_models.py b/simple_history/tests/tests/test_models.py index 566e2635..63dea204 100644 --- a/simple_history/tests/tests/test_models.py +++ b/simple_history/tests/tests/test_models.py @@ -73,6 +73,7 @@ InheritedRestaurant, Library, ManyToManyModelOther, + ModelWithAllNoDBIndex, ModelWithCustomAttrOneToOneField, ModelWithExcludedManyToMany, ModelWithFkToModelWithHistoryUsingBaseModelDb, @@ -2711,6 +2712,27 @@ def test_unique_field_index(self): self.assertTrue(self.history_model._meta.get_field("name_keeps_index").db_index) +class ModelWithAllNoDBIndexTest(TestCase): + def setUp(self): + self.model = ModelWithAllNoDBIndex + self.history_model = self.model.history.model + + def test_field_indices(self): + """ + All the indexed fields in the original model should be + non-indexed in the history model. + """ + 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) + + # no index + no_index = "name_no_index" + self.assertFalse(self.model._meta.get_field(no_index).db_index) + self.assertFalse(self.history_model._meta.get_field(no_index).db_index) + + class HistoricForeignKeyTest(TestCase): """ Tests chasing foreign keys across time points naturally with