Project description

Aim & purpose

It logs information about objects’ manipulations (additions, changes, deletions).
When synchronization is launched, all objects logged from the last checkpoint are synced to another database.

Important note: This app doesn’t log detailed information about changes (e.g. which fields were updated),
just that such manipulation occured. When the synchronization is performed, the objects are synced with their newest, actual values.
(however, you can specify some fields to be skipped during synchronization, see below).

Example 1

Consider scenario:

there is one production project deployed on the web

and the same project is deployed on some office computer in case of main server failure

Assuming that the local database is regularly synced (eg. once a day the main database is exported and imported into the local system),
in case of a long main server downtime the staff may use the local project (inserting objects etc.).

After the server is up again, the local changes (from the point of the last checkpoint) can be painlessly synchronized to the remote server.

Example 2

You can also synchronize databases both ways, not only in the slave-master model like in the previous example.

However, it is probably better (if possible) to have a common database rather than to have
one for every project deployment and to perform synchronization between them.

Requirements

The app is tested to work with Django 1.7 - 1.11. If you want to use app in older versions of Django,
use the 0.6 release.

The app needs django-dbsettings to store the time of last synchronization.

Installation

Install app (note: django-dbsettings is required and please view its install notes,
such as cache backend important remarks):

$ pip install django-synchro

or download it manually along with dependencies and put in python path.

Configure DATABASES.

Add synchro and dbsettings to INSTALLED_APPS.

Specify in your settings.py what is remote database name and which models should be watched and synchronized:

Natural keys

For efficient objects finding, it is highly suggested to provide natural_key object method
and get_by_natural_key manager method.
This will allow easy finding whether the synchronized object exists in REMOTE and to prevent duplicating.

Although adding natural_key to model definition is relatively quick, extending a manager may
require extra work in cases when the default manager is used:

Side note: in fact invoking NaturalManager creates a new class being NaturalManager’s subclass.

The purpose of a natural key is to uniquely distinguish among model instances;
however, there are situations where it is impossible. You can choose such fields that will cause
get_by_natural_key to find more than one object. In such a situation, it will raise
MultipleObjectsReturned exception and the synchronization will fail.

But you can tell NaturalManager that you are aware of such a situation and that it
should just take the first object found:

Previously, there were natural_manager function that was used instead of NaturalManager
- however, it’s deprecated.

Skipping fields

If your model has some fields that should not be synchronized, like computed fields
(eg. field with payment balances, which is updated on every order save - in order.post_save signal),
you can exclude them from synchronization:

class MyModel(models.Model):
...
SYNCHRO_SKIP = ('balance',)

When a new object is synchronized, all its skipped fields will be reset to default values on REMOTE.
Of course, the LOCAL object will stay untouched.

If using the decorator, be sure to place it after connecting to the signal, not before - otherwise it won’t work.

Update issue again

One can benefit from the fact that objects.update is not logged and use it in signal handlers instead of DisableSynchroLog.

Signal handlers for multi-db

Just a reminder note.

When a synchronization is performed, signal handlers are invoked for created/updated/deleted REMOTE objects.
And those signals are of course handled on the LOCAL machine.

That means: signal handlers (and probably other part of project code) must be ready to handle both LOCAL
and REMOTE objects. It must use using(...) clause or db_manager(...) to ensure that the proper database
is used: