Skip to content

Commit 01fb6d9

Browse files
rwlogelRoss Mechanic
authored andcommitted
Provide history_id_field to change the field type for history_id (#368)
* Provide history_id_field to change the field type for history_id * Updates based on review comments * Add SIMPLE_HISTORY_HISTORY_ID_DEFAULT_FIELD setting * Change setting to SIMPLE_HISTORY_HISTORY_ID_USE_UUID * Update CHANGES
1 parent a72e8d0 commit 01fb6d9

File tree

6 files changed

+105
-2
lines changed

6 files changed

+105
-2
lines changed

CHANGES.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
Changes
22
=======
33

4+
Unreleased
5+
----------
6+
- Add ability to specify custom history_id field (gh-368)
7+
48
2.0 (2018-04-05)
59
----------------
610
- Added Django 2.0 support (gh-330)

docs/advanced.rst

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,37 @@ referencing the ``changed_by`` field:
127127
128128
Admin integration requires that you use a ``_history_user.setter`` attribute with your custom ``_history_user`` property (see :ref:`admin_integration`).
129129

130+
Custom ``history_id``
131+
---------------------
132+
133+
By default, the historical table of a model will use an ``AutoField`` for the table's
134+
``history_id`` (the history table's primary key). However, you can specify a different
135+
type of field for ``history_id`` buy passing a different field to ``history_id_field``
136+
parameter.
137+
138+
A common use case for this would be to use a ``UUIDField``. If you want to use a ``UUIDField``
139+
as the default for all classes set ``SIMPLE_HISTORY_HISTORY_ID_USE_UUID=True`` in the settings.
140+
This setting can still be overridden using the ``history_id_field`` parameter on a per model basis.
141+
142+
You can use the ``history_id_field`` parameter with both ``HistoricalRecords()`` or
143+
``register()`` to change this behavior.
144+
145+
Note: regardless of what field type you specify as your history_id field, that field will
146+
automatically set ``primary_key=True`` and ``editable=False``.
147+
148+
.. code-block:: python
149+
150+
import uuid
151+
from django.db import models
152+
from simple_history.models import HistoricalRecords
153+
154+
class Poll(models.Model):
155+
question = models.CharField(max_length=200)
156+
pub_date = models.DateTimeField('date published')
157+
history = HistoricalRecords(
158+
history_id_field=models.UUIDField(default=uuid.uuid4)
159+
)
160+
130161
131162
Custom ``history_date``
132163
-----------------------

simple_history/models.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import copy
44
import importlib
55
import threading
6+
import uuid
67

78
from django.apps import apps
89
from django.conf import settings
@@ -27,11 +28,13 @@ class HistoricalRecords(object):
2728

2829
def __init__(self, verbose_name=None, bases=(models.Model,),
2930
user_related_name='+', table_name=None, inherit=False,
31+
history_id_field=None,
3032
excluded_fields=None):
3133
self.user_set_verbose_name = verbose_name
3234
self.user_related_name = user_related_name
3335
self.table_name = table_name
3436
self.inherit = inherit
37+
self.history_id_field = history_id_field
3538
if excluded_fields is None:
3639
excluded_fields = []
3740
self.excluded_fields = excluded_fields
@@ -219,8 +222,19 @@ def get_instance(self):
219222
for field in fields.values()
220223
})
221224

225+
if self.history_id_field:
226+
history_id_field = self.history_id_field
227+
history_id_field.primary_key = True
228+
history_id_field.editable = False
229+
elif getattr(settings, 'SIMPLE_HISTORY_HISTORY_ID_USE_UUID', False):
230+
history_id_field = models.UUIDField(
231+
primary_key=True, default=uuid.uuid4, editable=False
232+
)
233+
else:
234+
history_id_field = models.AutoField(primary_key=True)
235+
222236
return {
223-
'history_id': models.AutoField(primary_key=True),
237+
'history_id': history_id_field,
224238
'history_date': models.DateTimeField(),
225239
'history_change_reason': models.CharField(max_length=100,
226240
null=True),

simple_history/registry_tests/tests.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import unicode_literals
22

33
import unittest
4+
import uuid
45
from datetime import datetime, timedelta
56

67
from django.apps import apps
@@ -15,7 +16,7 @@
1516
Restaurant, TrackedAbstractBaseA,
1617
TrackedAbstractBaseB, TrackedWithAbstractBase,
1718
TrackedWithConcreteBase, UserAccessorDefault,
18-
UserAccessorOverride, Voter)
19+
UserAccessorOverride, UUIDRegisterModel, Voter)
1920

2021
get_model = apps.get_model
2122
User = get_user_model()
@@ -56,6 +57,13 @@ def test_register_custome_records(self):
5657
self.assertEqual(expected,
5758
str(voter.history.all()[0])[:len(expected)])
5859

60+
def test_register_history_id_field(self):
61+
self.assertEqual(len(UUIDRegisterModel.history.all()), 0)
62+
entry = UUIDRegisterModel.objects.create()
63+
self.assertEqual(len(entry.history.all()), 1)
64+
history = entry.history.all()[0]
65+
self.assertTrue(isinstance(history.history_id, uuid.UUID))
66+
5967

6068
class TestUserAccessor(unittest.TestCase):
6169

simple_history/tests/models.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from __future__ import unicode_literals
22

3+
import uuid
4+
35
from django.apps import apps
6+
from django.conf import settings
47
from django.db import models
58
from django.urls import reverse
69

@@ -401,3 +404,31 @@ class InheritTracking3(BaseInheritTracking3):
401404

402405
class InheritTracking4(TrackedAbstractBaseA):
403406
pass
407+
408+
409+
class UUIDModel(models.Model):
410+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
411+
history = HistoricalRecords(
412+
history_id_field=models.UUIDField(default=uuid.uuid4)
413+
)
414+
415+
416+
class UUIDRegisterModel(models.Model):
417+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
418+
419+
420+
register(UUIDRegisterModel,
421+
history_id_field=models.UUIDField(default=uuid.uuid4))
422+
423+
424+
# Set the SIMPLE_HISTORY_HISTORY_ID_USE_UUID
425+
setattr(settings, 'SIMPLE_HISTORY_HISTORY_ID_USE_UUID', True)
426+
427+
428+
class UUIDDefaultModel(models.Model):
429+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
430+
history = HistoricalRecords()
431+
432+
433+
# Clear the SIMPLE_HISTORY_HISTORY_ID_USE_UUID
434+
delattr(settings, 'SIMPLE_HISTORY_HISTORY_ID_USE_UUID')

simple_history/tests/tests/test_models.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import unicode_literals
22

33
import unittest
4+
import uuid
45
import warnings
56
from datetime import datetime, timedelta
67

@@ -50,6 +51,8 @@
5051
State,
5152
Temperature,
5253
UnicodeVerboseName,
54+
UUIDModel,
55+
UUIDDefaultModel,
5356
WaterLevel
5457
)
5558

@@ -367,6 +370,18 @@ def test_model_with_excluded_fields(self):
367370
self.assertIn('question', all_fields_names)
368371
self.assertNotIn('pub_date', all_fields_names)
369372

373+
def test_uuid_history_id(self):
374+
entry = UUIDModel.objects.create()
375+
376+
history = entry.history.all()[0]
377+
self.assertTrue(isinstance(history.history_id, uuid.UUID))
378+
379+
def test_uuid_default_history_id(self):
380+
entry = UUIDDefaultModel.objects.create()
381+
382+
history = entry.history.all()[0]
383+
self.assertTrue(isinstance(history.history_id, uuid.UUID))
384+
370385

371386
class CreateHistoryModelTests(unittest.TestCase):
372387

0 commit comments

Comments
 (0)