Skip to content

Commit 9deb217

Browse files
author
Ross Mechanic
authored
Allow user to be tracked by explicit ID (#511)
* Allow user to be tracked by explicit ID * Added initial tests to show failing functionality when user in different db * rebase * Initial set of tests * Format * Finished first round of testing * Added documentation * Fixed typo * Updated model name * format
1 parent 2e89360 commit 9deb217

File tree

16 files changed

+422
-174
lines changed

16 files changed

+422
-174
lines changed

CHANGES.rst

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,29 @@ Changes
33

44
Unreleased
55
----------
6-
- Add support for `using` chained manager method and save/delete keyword argument (gh-507)
7-
- Added management command `clean_duplicate_history` to remove duplicate history entries (gh-483)
6+
- Add support for ``using`` chained manager method and save/delete keyword argument (gh-507)
7+
- Added management command ``clean_duplicate_history`` to remove duplicate history entries (gh-483)
88
- Updated most_recent to work with excluded_fields (gh-477)
9-
- Fixed bug that prevented self-referential foreign key from using `'self'` (gh-513)
9+
- Fixed bug that prevented self-referential foreign key from using ``'self'`` (gh-513)
10+
- Added ability to track custom user with explicit custom ``history_user_id_field`` (gh-511)
1011

1112
2.6.0 (2018-12-12)
1213
------------------
13-
- Add `app` parameter to the constructor of `HistoricalRecords` (gh-486)
14-
- Add `custom_model_name` parameter to the constructor of `HistoricalRecords` (gh-451)
14+
- Add ``app`` parameter to the constructor of ``HistoricalRecords`` (gh-486)
15+
- Add ``custom_model_name`` parameter to the constructor of ``HistoricalRecords`` (gh-451)
1516
- Fix header on history pages when custom site_header is used (gh-448)
16-
- Modify `pre_create_historircal_record` to pass `history_instance` for ease of customization (gh-421)
17-
- Raise warning if HistoricalRecords(inherit=False) is in an abstract model (gh-341)
17+
- Modify ``pre_create_historical_record`` to pass ``history_instance`` for ease of customization (gh-421)
18+
- Raise warning if ``HistoricalRecords(inherit=False)`` is in an abstract model (gh-341)
1819
- Ensure custom arguments for fields are included in historical models' fields (gh-431)
1920
- Add german translations (gh-484)
20-
- Add `extra_context` parameter to history_form_view (gh-467)
21-
- Fixed bug that prevented `next_record` and `prev_record` to work with custom manager names (gh-501)
21+
- Add ``extra_context`` parameter to history_form_view (gh-467)
22+
- Fixed bug that prevented ``next_record`` and ``prev_record`` to work with custom manager names (gh-501)
2223

2324
2.5.1 (2018-10-19)
2425
------------------
25-
- Add `'+'` as the `history_type` for each instance in `bulk_history_create` (gh-449)
26-
- Add support for `history_change_reason` for each instance in `bulk_history_create` (gh-449)
27-
- Add `history_change_reason` in the history list view under the `Change reason` display name (gh-458)
26+
- Add ``'+'`` as the ``history_type`` for each instance in ``bulk_history_create`` (gh-449)
27+
- Add support for ``history_change_reason`` for each instance in ``bulk_history_create`` (gh-449)
28+
- Add ``history_change_reason`` in the history list view under the ``Change reason`` display name (gh-458)
2829
- Fix bug that caused failures when using a custom user model (gh-459)
2930

3031
2.5.0 (2018-10-18)
@@ -35,12 +36,12 @@ Unreleased
3536
2.4.0 (2018-09-20)
3637
------------------
3738
- Add pre and post create_historical_record signals (gh-426)
38-
- Remove support for `django_mongodb_engine` when converting AutoFields (gh-432)
39+
- Remove support for ``django_mongodb_engine`` when converting AutoFields (gh-432)
3940
- Add support for Django 2.1 (gh-418)
4041

4142
2.3.0 (2018-07-19)
4243
------------------
43-
- Add ability to diff HistoricalRecords (gh-244)
44+
- Add ability to diff ``HistoricalRecords`` (gh-244)
4445

4546
2.2.0 (2018-07-02)
4647
------------------
@@ -54,22 +55,22 @@ Unreleased
5455

5556
2.1.0 (2018-06-04)
5657
------------------
57-
- Add ability to specify custom history_reason field (gh-379)
58-
- Add ability to specify custom history_id field (gh-368)
59-
- Add HistoricalRecord instance properties `prev_record` and `next_record` (gh-365)
58+
- Add ability to specify custom ``history_reason`` field (gh-379)
59+
- Add ability to specify custom ``history_id`` field (gh-368)
60+
- Add HistoricalRecord instance properties ``prev_record`` and ``next_record`` (gh-365)
6061
- Can set admin methods as attributes on object history change list template (gh-390)
6162
- Fixed compatibility of >= 2.0 versions with old-style middleware (gh-369)
6263

6364
2.0 (2018-04-05)
6465
----------------
6566
- Added Django 2.0 support (gh-330)
6667
- Dropped support for Django<=1.10 (gh-356)
67-
- Fix bug where history_view ignored user permissions (gh-361)
68-
- Fixed HistoryRequestMiddleware which hadn't been working for Django>1.9 (gh-364)
68+
- Fix bug where ``history_view`` ignored user permissions (gh-361)
69+
- Fixed ``HistoryRequestMiddleware`` which hadn't been working for Django>1.9 (gh-364)
6970

7071
1.9.1 (2018-03-30)
7172
------------------
72-
- Use get_queryset rather than model.objects in history_view. (gh-303)
73+
- Use ``get_queryset`` rather ``than model.objects`` in ``history_view``. (gh-303)
7374
- Change ugettext calls in models.py to ugettext_lazy
7475
- Resolve issue where model references itself (gh-278)
7576
- Fix issue with tracking an inherited model (abstract class) (gh-269)
@@ -78,13 +79,13 @@ Unreleased
7879

7980
1.9.0 (2017-06-11)
8081
------------------
81-
- Add --batchsize option to the populate_history management command. (gh-231)
82+
- Add ``--batchsize`` option to the ``populate_history`` management command. (gh-231)
8283
- Add ability to show specific attributes in admin history list view. (gh-256)
8384
- Add Brazilian Portuguese translation file. (gh-279)
8485
- Fix locale file packaging issue. (gh-280)
8586
- Add ability to specify reason for history change. (gh-275)
8687
- Test against Django 1.11 and Python 3.6. (gh-276)
87-
- Add `excluded_fields` option to exclude fields from history. (gh-274)
88+
- Add ``excluded_fields`` option to exclude fields from history. (gh-274)
8889

8990
1.8.2 (2017-01-19)
9091
------------------
@@ -97,18 +98,18 @@ Unreleased
9798

9899
1.8.0 (2016-02-02)
99100
------------------
100-
- History tracking can be inherited by passing `inherit=True`. (gh-63)
101+
- History tracking can be inherited by passing ``inherit=True``. (gh-63)
101102

102103
1.7.0 (2015-12-02)
103104
------------------
104105
- Add ability to list history in admin when the object instance is deleted. (gh-72)
105-
- Add ability to change history through the admin. (Enabled with the `SIMPLE_HISTORY_EDIT` setting.)
106+
- Add ability to change history through the admin. (Enabled with the ``SIMPLE_HISTORY_EDIT`` setting.)
106107
- Add Django 1.9 support.
107108
- Support for custom tables names. (gh-196)
108109

109110
1.6.3 (2015-07-30)
110111
------------------
111-
- Respect `to_field` and `db_column` parameters (gh-182)
112+
- Respect ``to_field`` and ``db_column`` parameters (gh-182)
112113

113114
1.6.2 (2015-07-04)
114115
------------------
@@ -125,7 +126,7 @@ Unreleased
125126
------------------
126127
- Add support for Django 1.8+
127128
- Deprecated use of ``CustomForeignKeyField`` (to be removed)
128-
- Remove default reverse accessor to `auth.User` for historical models (gh-121)
129+
- Remove default reverse accessor to ``auth.User`` for historical models (gh-121)
129130

130131
1.5.4 (2015-01-03)
131132
------------------

docs/multiple_dbs.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
Multiple databases
22
==================
3+
4+
Interacting with Multiple Databases
5+
-----------------------------------
6+
37
`django-simple-history` follows the Django conventions for interacting with multiple databases.
48

59
.. code-block:: python
@@ -26,3 +30,12 @@ When interacting with manager methods, use ``db_manager()``:
2630
>>> poll.history.db_manager('other').as_of(datetime(2010, 10, 25, 18, 4, 0))
2731
2832
See the Django documentation for more information on how to interact with multiple databases.
33+
34+
Tracking User in a Separate Database
35+
------------------------------------
36+
37+
When using ``django-simple-history`` in app with multiple database, you may run into
38+
an issue where you want to track the history on a table that lives in a separate
39+
database to your user model. Since Django does not support cross-database relations,
40+
you will have to manually track the ``history_user`` using an explicit ID. The full
41+
documentation on this feature is in :ref:`Manually Track User Model`.

docs/user_tracking.rst

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ User Tracking
44

55
Recording Which User Changed a Model
66
------------------------------------
7-
There are three documented ways to attach users to a tracked change:
7+
There are four documented ways to attach users to a tracked change:
88

99
1. Use the ``HistoryRequestMiddleware``. The middleware sets the
1010
User instance that made the request as the ``history_user`` on the history
@@ -15,7 +15,15 @@ table.
1515
attach the user to the tracked change by overriding the `save_model` method.
1616

1717
3. Assign a user to the ``_history_user`` attribute of the object as described
18-
below:
18+
in the `_history_user section`_.
19+
20+
4. Track the user using an explicit ``history_user_id``, which is described in
21+
`Manually Track User Model`_. This method is particularly useful when using multiple
22+
databases (where your user model lives in a separate database to your historical model),
23+
or when using a user that doesn't live within the Django app (i.e. a user model retrieved
24+
from an API).
25+
26+
.. _`_history_user section`:
1927

2028
Using ``_history_user`` to Record Which User Changed a Model
2129
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -75,8 +83,65 @@ This is very helpful when using ``register``:
7583
register(Poll, get_user=get_poll_user)
7684
7785
86+
.. _`Manually Track User Model`:
87+
88+
89+
Manually Track User Model
90+
~~~~~~~~~~~~~~~~~~~~~~~~~
91+
92+
Although ``django-simple-history`` tracks the ``history_user`` (the user who changed the
93+
model) using a django foreign key, there are instances where we might want to track this
94+
user but cannot use a Django foreign key.
95+
96+
**Note:** If you want to track a custom user model that is still accessible through a
97+
Django foreign key, refer to `Change User Model`_.
98+
99+
The two most common cases where this feature will be helpful are:
100+
101+
1. You are working on a Django app with multiple databases, and your history table
102+
is in a separate database from the user table.
103+
104+
2. The user model that you want to use for ``history_user`` does not live within the
105+
Django app, but is only accessible elsewhere (i.e. through an API call).
106+
107+
There are three parameters to ``HistoricalRecords`` or ``register`` that facilitate
108+
the ability to manually track a ``history_user``.
109+
110+
111+
:history_user_id_field: An instance of field (i.e. ``IntegerField(null=True)`` or
112+
``UUIDField(default=uuid.uuid4, null=True)`` that will uniquely identify your user
113+
object. This is generally the field type of the primary key on your user object.
114+
115+
:history_user_getter: *optional*. A callable that takes the historical instance of the
116+
model and returns the ``history_user`` object. The default getter is shown below:
117+
118+
.. code-block:: python
119+
120+
def _history_user_getter(historical_instance):
121+
if historical_instance.history_user_id is None:
122+
return None
123+
User = get_user_model()
124+
try:
125+
return User.objects.get(pk=historical_instance.history_user_id)
126+
except User.DoesNotExist:
127+
return None
128+
129+
130+
:history_user_setter: *optional*. A callable that takes the historical instance and
131+
the user instance, and sets ``history_user_id`` on the historical instance. The
132+
default setter is shown below:
133+
134+
.. code-block:: python
135+
136+
def _history_user_setter(historical_instance, user):
137+
if user is not None:
138+
historical_instance.history_user_id = user.pk
139+
140+
141+
.. _`Change User Model`:
142+
78143
Change User Model
79-
------------------------------------
144+
-----------------
80145

81146
If you need to use a different user model then ``settings.AUTH_USER_MODEL``,
82147
pass in the required model to ``user_model``. Doing this requires ``_history_user``

simple_history/admin.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,10 @@ def history_view(self, request, object_id, extra_context=None):
4848
pk_name = opts.pk.attname
4949
history = getattr(model, model._meta.simple_history_manager_attribute)
5050
object_id = unquote(object_id)
51-
action_list = history.filter(**{pk_name: object_id}).select_related(
52-
"history_user"
53-
)
51+
action_list = history.filter(**{pk_name: object_id})
52+
if not isinstance(history.model.history_user, property):
53+
# Only select_related when history_user is a ForeignKey (not a property)
54+
action_list = action_list.select_related("history_user")
5455
history_list_display = getattr(self, "history_list_display", [])
5556
# If no history was found, see whether this object even exists.
5657
try:

0 commit comments

Comments
 (0)