Skip to content

Commit

Permalink
create historical instance with respect to using() for multidb enviro… (
Browse files Browse the repository at this point in the history
#507)

* create historical instance with respect to using() for multidb environment.

* add name to authors

* add name to authors

* code formatting

* update CHANGES

* revert post delete method to current version, add using

* rename second DB to other to be consistent with Django docs

* add section on multiple databases to documentation.

* minor edit to Multiple Databases section in documents

* minor edit to Multiple Databases section in documents
  • Loading branch information
erikvw authored and Ross Mechanic committed Jan 8, 2019
1 parent 65785a0 commit 7d6e84b
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 7 deletions.
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Authors
- David Grochowski (`ThePumpingLemma <https://github.com/ThePumpingLemma>`_)
- David Hite
- Eduardo Cuducos
- Erik van Widenfelt (`erikvw <https://github.com/erikvw>`_)
- Filipe Pina (@fopina)
- Florian Eßer
- Frank Sachsenheim
Expand Down
4 changes: 4 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Changes
=======

Unreleased
----------
- Add support for `using` chained manager method and save/delete keyword argument (gh-507)

2.6.0 (2018-12-12)
------------------
- Add `app` parameter to the constructor of `HistoricalRecords` (gh-486)
Expand Down
32 changes: 32 additions & 0 deletions docs/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,36 @@ be accomplished by setting ``cascade_delete_history=True``.
history = HistoricalRecords(cascade_delete_history=True)
Multiple databases
------------------
`django-simple-history` follows the Django conventions for interacting with multiple databases.
.. code-block:: python
>>> # This will create a new historical record on the 'other' database.
>>> poll = Poll.objects.using('other').create(question='Question 1')
>>> # This will also create a new historical record on the 'other' database.
>>> poll.save(using='other')
When interacting with ``QuerySets``, use ``using()``:
.. code-block:: python
>>> # This will return a QuerySet from the 'other' database.
Poll.history.using('other').all()
When interacting with manager methods, use ``db_manager()``:
.. code-block:: python
>>> # This will call a manager method on the 'other' database.
>>> poll.history.db_manager('other').as_of(datetime(2010, 10, 25, 18, 4, 0))
See the Django documentation for more information on how to interact with multiple databases.
History Diffing
-------------------
Expand Down Expand Up @@ -523,6 +553,8 @@ history_change_reason
Freetext description of the reason for the change
history_user
The user that instigated the change
using
The database alias being used
To connect the signals to your callbacks, you can use the @receiver decorator:
Expand Down
3 changes: 3 additions & 0 deletions runtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
DATABASES={
'default': {
'ENGINE': 'django.db.backends.sqlite3',
},
'other': {
'ENGINE': 'django.db.backends.sqlite3',
}
},
TEMPLATES=[{
Expand Down
16 changes: 9 additions & 7 deletions simple_history/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,20 +349,20 @@ def get_meta_options(self, model):
meta_fields["app_label"] = self.app
return meta_fields

def post_save(self, instance, created, **kwargs):
def post_save(self, instance, created, using=None, **kwargs):
if not created and hasattr(instance, "skip_history_when_saving"):
return
if not kwargs.get("raw", False):
self.create_historical_record(instance, created and "+" or "~")
self.create_historical_record(instance, created and "+" or "~", using=using)

def post_delete(self, instance, **kwargs):
def post_delete(self, instance, using=None, **kwargs):
if self.cascade_delete_history:
manager = getattr(instance, self.manager_name)
manager.all().delete()
manager.using(using).all().delete()
else:
self.create_historical_record(instance, "-")
self.create_historical_record(instance, "-", using=using)

def create_historical_record(self, instance, history_type):
def create_historical_record(self, instance, history_type, using=None):
history_date = getattr(instance, "_history_date", now())
history_user = self.get_history_user(instance)
history_change_reason = getattr(instance, "changeReason", None)
Expand All @@ -387,9 +387,10 @@ def create_historical_record(self, instance, history_type):
history_user=history_user,
history_change_reason=history_change_reason,
history_instance=history_instance,
using=using,
)

history_instance.save()
history_instance.save(using=using)

post_create_historical_record.send(
sender=manager.model,
Expand All @@ -398,6 +399,7 @@ def create_historical_record(self, instance, history_type):
history_date=history_date,
history_user=history_user,
history_change_reason=history_change_reason,
using=using,
)

def get_history_user(self, instance):
Expand Down
2 changes: 2 additions & 0 deletions simple_history/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"history_date",
"history_user",
"history_change_reason",
"using",
]
)
post_create_historical_record = django.dispatch.Signal(
Expand All @@ -17,5 +18,6 @@
"history_date",
"history_user",
"history_change_reason",
"using",
]
)
90 changes: 90 additions & 0 deletions simple_history/tests/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from datetime import datetime, timedelta
from django.apps import apps
from django.contrib.auth import get_user_model
from django.core.exceptions import ObjectDoesNotExist
from django.core.files.base import ContentFile
from django.db import models
from django.db.models.fields.proxy import OrderWrt
Expand All @@ -15,6 +16,7 @@
from simple_history.models import HistoricalRecords, ModelChange
from simple_history.signals import pre_create_historical_record
from simple_history.tests.tests.utils import middleware_override_settings
from simple_history.utils import get_history_model_for_model
from simple_history.utils import update_change_reason
from ..external.models import ExternalModel2, ExternalModel4
from ..models import (
Expand Down Expand Up @@ -1219,3 +1221,91 @@ class Meta:
"(AbstractModelWithInheritFalse) without "
"inherit=True",
)


class MultiDBWithUsingTest(TestCase):

"""Asserts historical manager respects `using()` and the `using`
keyword argument in `save()`.
"""

multi_db = True
db_name = "other"

def test_multidb_with_using_not_on_default(self):
book = Book.objects.using(self.db_name).create(isbn="1-84356-028-1")
self.assertRaises(ObjectDoesNotExist, book.history.get, isbn="1-84356-028-1")

def test_multidb_with_using_is_on_dbtwo(self):
book = Book.objects.using(self.db_name).create(isbn="1-84356-028-1")
try:
book.history.using(self.db_name).get(isbn="1-84356-028-1")
except ObjectDoesNotExist:
self.fail("ObjectDoesNotExist unexpectedly raised.")

def test_multidb_with_using_and_fk_not_on_default(self):
book = Book.objects.using(self.db_name).create(isbn="1-84356-028-1")
library = Library.objects.using(self.db_name).create(book=book)
self.assertRaises(ObjectDoesNotExist, library.history.get, book=book)

def test_multidb_with_using_and_fk_on_dbtwo(self):
book = Book.objects.using(self.db_name).create(isbn="1-84356-028-1")
library = Library.objects.using(self.db_name).create(book=book)
try:
library.history.using(self.db_name).get(book=book)
except ObjectDoesNotExist:
self.fail("ObjectDoesNotExist unexpectedly raised.")

def test_multidb_with_using_keyword_in_save_not_on_default(self):
book = Book(isbn="1-84356-028-1")
book.save(using=self.db_name)
self.assertRaises(ObjectDoesNotExist, book.history.get, isbn="1-84356-028-1")

def test_multidb_with_using_keyword_in_save_on_dbtwo(self):
book = Book(isbn="1-84356-028-1")
book.save(using=self.db_name)
try:
book.history.using(self.db_name).get(isbn="1-84356-028-1")
except ObjectDoesNotExist:
self.fail("ObjectDoesNotExist unexpectedly raised.")

def test_multidb_with_using_keyword_in_save_with_fk(self):
book = Book(isbn="1-84356-028-1")
book.save(using=self.db_name)
library = Library(book=book)
library.save(using=self.db_name)
# assert not created on default
self.assertRaises(ObjectDoesNotExist, library.history.get, book=book)
# assert created on dbtwo
try:
library.history.using(self.db_name).get(book=book)
except ObjectDoesNotExist:
self.fail("ObjectDoesNotExist unexpectedly raised.")

def test_multidb_with_using_keyword_in_save_and_update(self):
book = Book.objects.using(self.db_name).create(isbn="1-84356-028-1")
book.save(using=self.db_name)
self.assertEqual(
["+", "~"],
[
obj.history_type
for obj in book.history.using(self.db_name)
.all()
.order_by("history_date")
],
)

def test_multidb_with_using_keyword_in_save_and_delete(self):
HistoricalBook = get_history_model_for_model(Book)
book = Book.objects.using(self.db_name).create(isbn="1-84356-028-1")
book.save(using=self.db_name)
book.delete(using=self.db_name)
self.assertEqual(
["+", "~", "-"],
[
obj.history_type
for obj in HistoricalBook.objects.using(self.db_name)
.all()
.order_by("history_date")
],
)

0 comments on commit 7d6e84b

Please sign in to comment.