Repository: jazzband/django-simple-history

Issue:
Can't use .prev_record and .next_record when using not default field name
I'm using not default field name for HistoricalRecords:
```
class Record(models.Model)
...
log = HistoricalRecords()
```
When I try to use .prev_record or .next_record property of the history object if fails with a message "AttributeError: 'Record' object has no attribute 'history'". It seems that 'history' attribute is hardcoded now.

**Environment:**
 - OS: Ubuntu 16.04
 - Django Simple History Version: 2.5.1
 - Django Version: 1.11.17
 - Database Version: MySQL 5.7



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
@@ -19,6 +19,7 @@
 from django.utils.timezone import now
 from django.utils.translation import ugettext_lazy as _
 
+from simple_history import utils
 from . import exceptions
 from .manager import HistoryDescriptor
 from .signals import post_create_historical_record, pre_create_historical_record
@@ -262,8 +263,9 @@ def get_next_record(self):
             """
             Get the next history record for the instance. `None` if last.
             """
+            history = utils.get_history_manager_for_model(self.instance)
             return (
-                self.instance.history.filter(Q(history_date__gt=self.history_date))
+                history.filter(Q(history_date__gt=self.history_date))
                 .order_by("history_date")
                 .first()
             )
@@ -272,8 +274,9 @@ def get_prev_record(self):
             """
             Get the previous history record for the instance. `None` if first.
             """
+            history = utils.get_history_manager_for_model(self.instance)
             return (
-                self.instance.history.filter(Q(history_date__lt=self.history_date))
+                history.filter(Q(history_date__lt=self.history_date))
                 .order_by("history_date")
                 .last()
             )


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
@@ -1,16 +1,13 @@
 from __future__ import unicode_literals
 
 import uuid
-
 from django.apps import apps
 from django.conf import settings
 from django.db import models
-from django.dispatch import receiver
 from django.urls import reverse
 
 from simple_history import register
 from simple_history.models import HistoricalRecords
-from simple_history.signals import pre_create_historical_record
 from .custom_user.models import CustomUser as User
 from .external.models.model1 import AbstractExternal
 
@@ -551,3 +548,8 @@ class CharFieldChangeReasonModel(models.Model):
 class CustomNameModel(models.Model):
     name = models.CharField(max_length=15, unique=True)
     history = HistoricalRecords(custom_model_name="MyHistoricalCustomNameModel")
+
+
+class CustomManagerNameModel(models.Model):
+    name = models.CharField(max_length=15)
+    log = HistoricalRecords()
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
@@ -4,13 +4,12 @@
 import uuid
 import warnings
 from datetime import datetime, timedelta
-
 from django.apps import apps
 from django.contrib.auth import get_user_model
 from django.core.files.base import ContentFile
 from django.db import models
 from django.db.models.fields.proxy import OrderWrt
-from django.test import override_settings, TestCase
+from django.test import TestCase, override_settings
 from django.urls import reverse
 
 from simple_history.models import HistoricalRecords, ModelChange
@@ -26,6 +25,7 @@
     BucketData,
     BucketDataRegisterChangedBy,
     BucketMember,
+    CharFieldChangeReasonModel,
     Choice,
     City,
     ConcreteAttr,
@@ -34,7 +34,8 @@
     Contact,
     ContactRegister,
     Country,
-    CustomNameModel,
+    CustomManagerNameModel,
+    DefaultTextFieldChangeReasonModel,
     Document,
     Employee,
     ExternalModel1,
@@ -61,13 +62,11 @@
     SeriesWork,
     State,
     Temperature,
-    UnicodeVerboseName,
-    UUIDModel,
     UUIDDefaultModel,
-    WaterLevel,
-    DefaultTextFieldChangeReasonModel,
+    UUIDModel,
+    UnicodeVerboseName,
     UserTextFieldChangeReasonModel,
-    CharFieldChangeReasonModel,
+    WaterLevel,
 )
 
 get_model = apps.get_model
@@ -537,82 +536,6 @@ def test_user_textfield_history_change_reason(self):
         self.assertTrue(isinstance(field, models.TextField))
         self.assertEqual(history.history_change_reason, reason)
 
-    def test_get_prev_record(self):
-        poll = Poll(question="what's up?", pub_date=today)
-        poll.save()
-        poll.question = "ask questions?"
-        poll.save()
-        poll.question = "eh?"
-        poll.save()
-        poll.question = "one more?"
-        poll.save()
-        first_record = poll.history.filter(question="what's up?").get()
-        second_record = poll.history.filter(question="ask questions?").get()
-        third_record = poll.history.filter(question="eh?").get()
-        fourth_record = poll.history.filter(question="one more?").get()
-        self.assertIsNone(first_record.prev_record)
-
-        def assertRecordsMatch(record_a, record_b):
-            self.assertEqual(record_a, record_b)
-            self.assertEqual(record_a.question, record_b.question)
-
-        assertRecordsMatch(second_record.prev_record, first_record)
-        assertRecordsMatch(third_record.prev_record, second_record)
-        assertRecordsMatch(fourth_record.prev_record, third_record)
-
-    def test_get_prev_record_none_if_only(self):
-        poll = Poll(question="what's up?", pub_date=today)
-        poll.save()
-        self.assertEqual(poll.history.count(), 1)
-        record = poll.history.get()
-        self.assertIsNone(record.prev_record)
-
-    def test_get_prev_record_none_if_earliest(self):
-        poll = Poll(question="what's up?", pub_date=today)
-        poll.save()
-        poll.question = "ask questions?"
-        poll.save()
-        first_record = poll.history.filter(question="what's up?").get()
-        self.assertIsNone(first_record.prev_record)
-
-    def test_get_next_record(self):
-        poll = Poll(question="what's up?", pub_date=today)
-        poll.save()
-        poll.question = "ask questions?"
-        poll.save()
-        poll.question = "eh?"
-        poll.save()
-        poll.question = "one more?"
-        poll.save()
-        first_record = poll.history.filter(question="what's up?").get()
-        second_record = poll.history.filter(question="ask questions?").get()
-        third_record = poll.history.filter(question="eh?").get()
-        fourth_record = poll.history.filter(question="one more?").get()
-        self.assertIsNone(fourth_record.next_record)
-
-        def assertRecordsMatch(record_a, record_b):
-            self.assertEqual(record_a, record_b)
-            self.assertEqual(record_a.question, record_b.question)
-
-        assertRecordsMatch(first_record.next_record, second_record)
-        assertRecordsMatch(second_record.next_record, third_record)
-        assertRecordsMatch(third_record.next_record, fourth_record)
-
-    def test_get_next_record_none_if_only(self):
-        poll = Poll(question="what's up?", pub_date=today)
-        poll.save()
-        self.assertEqual(poll.history.count(), 1)
-        record = poll.history.get()
-        self.assertIsNone(record.next_record)
-
-    def test_get_next_record_none_if_most_recent(self):
-        poll = Poll(question="what's up?", pub_date=today)
-        poll.save()
-        poll.question = "ask questions?"
-        poll.save()
-        recent_record = poll.history.filter(question="ask questions?").get()
-        self.assertIsNone(recent_record.next_record)
-
     def test_history_diff_includes_changed_fields(self):
         p = Poll.objects.create(question="what's up?", pub_date=today)
         p.question = "what's up, man?"
@@ -642,6 +565,90 @@ def test_history_diff_with_incorrect_type(self):
             new_record.diff_against("something")
 
 
+class GetPrevRecordAndNextRecordTestCase(TestCase):
+    def assertRecordsMatch(self, record_a, record_b):
+        self.assertEqual(record_a, record_b)
+        self.assertEqual(record_a.question, record_b.question)
+
+    def setUp(self):
+        self.poll = Poll(question="what's up?", pub_date=today)
+        self.poll.save()
+
+    def test_get_prev_record(self):
+
+        self.poll.question = "ask questions?"
+        self.poll.save()
+        self.poll.question = "eh?"
+        self.poll.save()
+        self.poll.question = "one more?"
+        self.poll.save()
+        first_record = self.poll.history.filter(question="what's up?").get()
+        second_record = self.poll.history.filter(question="ask questions?").get()
+        third_record = self.poll.history.filter(question="eh?").get()
+        fourth_record = self.poll.history.filter(question="one more?").get()
+
+        self.assertRecordsMatch(second_record.prev_record, first_record)
+        self.assertRecordsMatch(third_record.prev_record, second_record)
+        self.assertRecordsMatch(fourth_record.prev_record, third_record)
+
+    def test_get_prev_record_none_if_only(self):
+        self.assertEqual(self.poll.history.count(), 1)
+        record = self.poll.history.get()
+        self.assertIsNone(record.prev_record)
+
+    def test_get_prev_record_none_if_earliest(self):
+        self.poll.question = "ask questions?"
+        self.poll.save()
+        first_record = self.poll.history.filter(question="what's up?").get()
+        self.assertIsNone(first_record.prev_record)
+
+    def get_prev_record_with_custom_manager_name(self):
+        instance = CustomManagerNameModel(name="Test name 1")
+        instance.save()
+        instance.name = "Test name 2"
+        first_record = instance.log.filter(name="Test name").get()
+        second_record = instance.log.filter(name="Test name 2").get()
+
+        self.assertRecordsMatch(second_record.prev_record, first_record)
+
+    def test_get_next_record(self):
+        self.poll.question = "ask questions?"
+        self.poll.save()
+        self.poll.question = "eh?"
+        self.poll.save()
+        self.poll.question = "one more?"
+        self.poll.save()
+        first_record = self.poll.history.filter(question="what's up?").get()
+        second_record = self.poll.history.filter(question="ask questions?").get()
+        third_record = self.poll.history.filter(question="eh?").get()
+        fourth_record = self.poll.history.filter(question="one more?").get()
+        self.assertIsNone(fourth_record.next_record)
+
+        self.assertRecordsMatch(first_record.next_record, second_record)
+        self.assertRecordsMatch(second_record.next_record, third_record)
+        self.assertRecordsMatch(third_record.next_record, fourth_record)
+
+    def test_get_next_record_none_if_only(self):
+        self.assertEqual(self.poll.history.count(), 1)
+        record = self.poll.history.get()
+        self.assertIsNone(record.next_record)
+
+    def test_get_next_record_none_if_most_recent(self):
+        self.poll.question = "ask questions?"
+        self.poll.save()
+        recent_record = self.poll.history.filter(question="ask questions?").get()
+        self.assertIsNone(recent_record.next_record)
+
+    def get_next_record_with_custom_manager_name(self):
+        instance = CustomManagerNameModel(name="Test name 1")
+        instance.save()
+        instance.name = "Test name 2"
+        first_record = instance.log.filter(name="Test name").get()
+        second_record = instance.log.filter(name="Test name 2").get()
+
+        self.assertRecordsMatch(first_record.next_record, second_record)
+
+
 class CreateHistoryModelTests(unittest.TestCase):
     def test_create_history_model_with_one_to_one_field_to_integer_field(self):
         records = HistoricalRecords()