-
-
Notifications
You must be signed in to change notification settings - Fork 483
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
disable_history()
context manager
#1387
base: master
Are you sure you want to change the base?
Changes from all commits
74a2e38
b36a280
e2894b5
c2576e3
23c37dd
78286f6
8ea956c
7755c1e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
Disable Creating Historical Records | ||
=================================== | ||
|
||
``save_without_historical_record()`` and ``delete_without_historical_record()`` | ||
------------------------------------------------------------------------------- | ||
|
||
These methods are automatically added to a model when registering it for history-tracking | ||
(i.e. defining a ``HistoricalRecords`` manager on the model), | ||
and can be called instead of ``save()`` and ``delete()``, respectively. | ||
|
||
Using the ``disable_history()`` context manager | ||
----------------------------------------------- | ||
|
||
``disable_history()`` has three ways of being called: | ||
|
||
#. With no arguments: This will disable all historical record creation | ||
(as if the ``SIMPLE_HISTORY_ENABLED`` setting was set to ``False``; see below) | ||
within the context manager's ``with`` block. | ||
#. With ``only_for_model``: Only disable history creation for the provided model type. | ||
#. With ``instance_predicate``: Only disable history creation for model instances passing | ||
this predicate. | ||
Comment on lines
+20
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This argument name doesn't seem very beginner friendly. The best thing I can come up with is We should also document what arguments this function must take. |
||
|
||
See some examples below: | ||
|
||
.. code-block:: python | ||
|
||
from simple_history.utils import disable_history | ||
|
||
# No historical records are created | ||
with disable_history(): | ||
User.objects.create(...) | ||
Poll.objects.create(...) | ||
|
||
# A historical record is only created for the poll | ||
with disable_history(only_for_model=User): | ||
User.objects.create(...) | ||
Poll.objects.create(...) | ||
|
||
# A historical record is created for the second poll, but not for the first poll | ||
# (remember to check the instance type in the passed function if you expect | ||
# historical records of more than one model to be created inside the `with` block) | ||
with disable_history(instance_predicate=lambda poll: "ignore" in poll.question): | ||
Poll.objects.create(question="ignore this") | ||
Poll.objects.create(question="what's up?") | ||
|
||
Overriding ``create_historical_record()`` | ||
----------------------------------------- | ||
|
||
For even more fine-grained control, you can subclass ``HistoricalRecords`` and override | ||
its ``create_historical_record()`` method, for example like this: | ||
|
||
.. code-block:: python | ||
|
||
class CustomHistoricalRecords(HistoricalRecords): | ||
def create_historical_record( | ||
self, instance: models.Model, history_type: str, *args, **kwargs | ||
) -> None: | ||
# Don't create records for "ignore" polls that are being deleted | ||
if "ignore" in poll.question and history_type == "-": | ||
return | ||
|
||
super().create_historical_record(instance, history_type, *args, **kwargs) | ||
|
||
|
||
class Poll(models.Model): | ||
# ... | ||
history = CustomHistoricalRecords() | ||
|
||
The ``SIMPLE_HISTORY_ENABLED`` setting | ||
-------------------------------------- | ||
|
||
Disable the creation of historical records for *all* models | ||
by adding the following line to your settings: | ||
Comment on lines
+72
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to indicate this defaults to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, this can be ignored. I realized that it's just moving. |
||
|
||
.. code-block:: python | ||
|
||
SIMPLE_HISTORY_ENABLED = False |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,12 +1,8 @@ | ||||||
from django.conf import settings | ||||||
from django.db import models | ||||||
from django.db.models import Exists, OuterRef, Q, QuerySet | ||||||
from django.utils import timezone | ||||||
|
||||||
from simple_history.utils import ( | ||||||
get_app_model_primary_key_name, | ||||||
get_change_reason_from_object, | ||||||
) | ||||||
from . import utils | ||||||
|
||||||
# when converting a historical record to an instance, this attribute is added | ||||||
# to the instance so that code can reverse the instance to its historical record | ||||||
|
@@ -118,16 +114,13 @@ def __init__(self, model, instance=None): | |||||
self.model = model | ||||||
self.instance = instance | ||||||
|
||||||
def get_super_queryset(self): | ||||||
return super().get_queryset() | ||||||
|
||||||
def get_queryset(self): | ||||||
qs = self.get_super_queryset() | ||||||
qs = super().get_queryset() | ||||||
if self.instance is None: | ||||||
return qs | ||||||
|
||||||
key_name = get_app_model_primary_key_name(self.instance) | ||||||
return self.get_super_queryset().filter(**{key_name: self.instance.pk}) | ||||||
pk_name = utils.get_pk_name(self.instance._meta.model) | ||||||
return qs.filter(**{pk_name: self.instance.pk}) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Does that work just as well? |
||||||
|
||||||
def most_recent(self): | ||||||
""" | ||||||
|
@@ -222,15 +215,19 @@ def bulk_history_create( | |||||
If called by bulk_update_with_history, use the update boolean and | ||||||
save the history_type accordingly. | ||||||
""" | ||||||
if not getattr(settings, "SIMPLE_HISTORY_ENABLED", True): | ||||||
return | ||||||
info = utils.DisableHistoryInfo.get() | ||||||
if info.disabled_globally: | ||||||
return [] | ||||||
|
||||||
Comment on lines
+218
to
+220
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a breaking API change and should be documented in the release notes. |
||||||
history_type = "+" | ||||||
if update: | ||||||
history_type = "~" | ||||||
|
||||||
historical_instances = [] | ||||||
for instance in objs: | ||||||
if info.disabled_for(instance): | ||||||
continue | ||||||
|
||||||
history_user = getattr( | ||||||
instance, | ||||||
"_history_user", | ||||||
|
@@ -241,7 +238,7 @@ def bulk_history_create( | |||||
instance, "_history_date", default_date or timezone.now() | ||||||
), | ||||||
history_user=history_user, | ||||||
history_change_reason=get_change_reason_from_object(instance) | ||||||
history_change_reason=utils.get_change_reason_from_object(instance) | ||||||
or default_change_reason, | ||||||
history_type=history_type, | ||||||
**{ | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps this is better as
models
to match the signature ofisinstance
? This would allow people to disable multiple models in a single context manager and it shouldn't be too much additional complexity on our end.