Repository: jazzband/django-simple-history

Issue:
Add support for custom history manager
**Problem Statement**
I have a model that makes heavy use of annotations. I want to be able to fetch the historical records for a model and have the annotations already included. In order to do this, I need to extend the history manager, but there's no straightforward way to actually inject it and use it.

**Describe the solution you'd like**
`HistoricalRecords` could accept an argument for the manager class. 

```python
from simple_history.manager import HistoryManager, HistoricalQuerySet

class MyModelHistoricalManager(HistoryManager):
  ...

class MyModel:
  history = HistoricalRecords(manager_cls=MyModelHistoricalManager)
  # or even better
  history = HistoricalRecords(manager_cls=MyModelHistoricalManager.from_queryset(HistoricalQuerySet))
```

Extend source model's QuerySet manager to Historical model?
Getting ahead of myself on this great unreleased feature, but..

Based on the great work added in #866, is it at all possible to extend the object manager from the source model to the `Historical` model? The idea here is that now that `as_of()` returns a `HistoricalQuerySet` most of the filtering I want to do on the historical models is based on filtering I would do on live objects.

Since a custom `ModelManager` or `QuerySetManager` that provides chainable filters would work on historical models, to avoid repeating them again, is there a way to use the same manager on the `HistoricalModel`?


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
@@ -119,14 +119,6 @@ def _instanceize(self):
                 setattr(historic, "_as_of", self._as_of)
 
 
-class HistoryDescriptor:
-    def __init__(self, model):
-        self.model = model
-
-    def __get__(self, instance, owner):
-        return HistoryManager.from_queryset(HistoricalQuerySet)(self.model, instance)
-
-
 class HistoryManager(models.Manager):
     def __init__(self, model, instance=None):
         super().__init__()
@@ -272,3 +264,15 @@ def bulk_history_create(
         return self.model.objects.bulk_create(
             historical_instances, batch_size=batch_size
         )
+
+
+class HistoryDescriptor:
+    def __init__(self, model, manager=HistoryManager, queryset=HistoricalQuerySet):
+        self.model = model
+        self.queryset_class = queryset
+        self.manager_class = manager
+
+    def __get__(self, instance, owner):
+        return self.manager_class.from_queryset(self.queryset_class)(
+            self.model, instance
+        )
diff --git a/simple_history/models.py b/simple_history/models.py
--- a/simple_history/models.py
+++ b/simple_history/models.py
@@ -31,7 +31,12 @@
 from simple_history import utils
 
 from . import exceptions
-from .manager import SIMPLE_HISTORY_REVERSE_ATTR_NAME, HistoryDescriptor
+from .manager import (
+    SIMPLE_HISTORY_REVERSE_ATTR_NAME,
+    HistoricalQuerySet,
+    HistoryDescriptor,
+    HistoryManager,
+)
 from .signals import (
     post_create_historical_m2m_records,
     post_create_historical_record,
@@ -100,6 +105,8 @@ def __init__(
         user_db_constraint=True,
         no_db_index=list(),
         excluded_field_kwargs=None,
+        history_manager=HistoryManager,
+        historical_queryset=HistoricalQuerySet,
         m2m_fields=(),
         m2m_fields_model_field_name="_history_m2m_fields",
         m2m_bases=(models.Model,),
@@ -122,6 +129,8 @@ def __init__(
         self.user_setter = history_user_setter
         self.related_name = related_name
         self.use_base_model_db = use_base_model_db
+        self.history_manager = history_manager
+        self.historical_queryset = historical_queryset
         self.m2m_fields = m2m_fields
         self.m2m_fields_model_field_name = m2m_fields_model_field_name
 
@@ -215,7 +224,11 @@ def finalize(self, sender, **kwargs):
                 weak=False,
             )
 
-        descriptor = HistoryDescriptor(history_model)
+        descriptor = HistoryDescriptor(
+            history_model,
+            manager=self.history_manager,
+            queryset=self.historical_queryset,
+        )
         setattr(sender, self.manager_name, descriptor)
         sender._meta.simple_history_manager_attribute = self.manager_name
 


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
@@ -9,6 +9,7 @@
 from django.urls import reverse
 
 from simple_history import register
+from simple_history.manager import HistoricalQuerySet, HistoryManager
 from simple_history.models import HistoricalRecords, HistoricForeignKey
 
 from .custom_user.models import CustomUser as User
@@ -155,6 +156,25 @@ class PollWithManyToManyCustomHistoryID(models.Model):
     )
 
 
+class PollQuerySet(HistoricalQuerySet):
+    def questions(self):
+        return self.filter(question__startswith="Question ")
+
+
+class PollManager(HistoryManager):
+    def low_ids(self):
+        return self.filter(id__lte=3)
+
+
+class PollWithQuerySetCustomizations(models.Model):
+    question = models.CharField(max_length=200)
+    pub_date = models.DateTimeField("date published")
+
+    history = HistoricalRecords(
+        history_manager=PollManager, historical_queryset=PollQuerySet
+    )
+
+
 class HistoricalRecordsWithExtraFieldM2M(HistoricalRecords):
     def get_extra_fields_m2m(self, model, through_model, fields):
         extra_fields = super().get_extra_fields_m2m(model, through_model, fields)
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
@@ -103,6 +103,7 @@
     PollWithManyToManyCustomHistoryID,
     PollWithManyToManyWithIPAddress,
     PollWithNonEditableField,
+    PollWithQuerySetCustomizations,
     PollWithSelfManyToMany,
     PollWithSeveralManyToMany,
     Province,
@@ -800,6 +801,42 @@ def test_history_with_unknown_field(self):
         with self.assertNumQueries(0):
             new_record.diff_against(old_record, excluded_fields=["unknown_field"])
 
+    def test_history_with_custom_queryset(self):
+        PollWithQuerySetCustomizations.objects.create(
+            id=1, pub_date=today, question="Question 1"
+        )
+        PollWithQuerySetCustomizations.objects.create(
+            id=2, pub_date=today, question="Low Id"
+        )
+        PollWithQuerySetCustomizations.objects.create(
+            id=10, pub_date=today, question="Random"
+        )
+
+        self.assertEqual(
+            set(
+                PollWithQuerySetCustomizations.history.low_ids().values_list(
+                    "question", flat=True
+                )
+            ),
+            {"Question 1", "Low Id"},
+        )
+        self.assertEqual(
+            set(
+                PollWithQuerySetCustomizations.history.questions().values_list(
+                    "question", flat=True
+                )
+            ),
+            {"Question 1"},
+        )
+        self.assertEqual(
+            set(
+                PollWithQuerySetCustomizations.history.low_ids()
+                .questions()
+                .values_list("question", flat=True)
+            ),
+            {"Question 1"},
+        )
+
 
 class GetPrevRecordAndNextRecordTestCase(TestCase):
     def assertRecordsMatch(self, record_a, record_b):