diff --git a/morango/__init__.py b/morango/__init__.py index ee79c68d..c653f9a6 100644 --- a/morango/__init__.py +++ b/morango/__init__.py @@ -3,4 +3,4 @@ from __future__ import unicode_literals default_app_config = "morango.apps.MorangoConfig" -__version__ = "0.4.9" +__version__ = "0.4.11" diff --git a/morango/sync/operations.py b/morango/sync/operations.py index 83403090..965c7057 100644 --- a/morango/sync/operations.py +++ b/morango/sync/operations.py @@ -6,6 +6,8 @@ from django.core import exceptions from django.core.serializers.json import DjangoJSONEncoder from django.db import connection +from django.db import connections +from django.db import router from django.db import transaction from django.db.models import Q from django.db.models import signals @@ -29,6 +31,13 @@ DBBackend = load_backend(connection).SQLWrapper() +# if postgres, get serializable db connection +db_name = router.db_for_write(Store) +USING_DB = db_name +if "postgresql" in transaction.get_connection(USING_DB).vendor: + USING_DB = db_name + '-serializable' + assert USING_DB in connections, "Please add a `default-serializable` database connection in your django settings file, \ + which copies all the configuration settings of the `default` db connection" def _join_with_logical_operator(lst, operator): op = ") {operator} (".format(operator=operator) @@ -70,7 +79,7 @@ def _serialize_into_store(profile, filter=None): # ensure that we write and retrieve the counter in one go for consistency current_id = InstanceIDModel.get_current_instance_and_increment_counter() - with transaction.atomic(): + with transaction.atomic(using=USING_DB): # create Q objects for filtering by prefixes prefix_condition = None if filter: @@ -227,7 +236,7 @@ def _deserialize_from_store(profile): logger.info("Deserializing records") fk_cache = {} - with transaction.atomic(): + with transaction.atomic(using=USING_DB): excluded_list = [] # iterate through classes which are in foreign key dependency order for model in syncable_models.get_models(profile): @@ -316,7 +325,7 @@ def _deserialize_from_store(profile): logger.info("Deserialization complete") -@transaction.atomic() +@transaction.atomic(using=USING_DB) def _queue_into_buffer(transfersession): """ Takes a chunk of data from the store to be put into the buffer to be sent to another morango instance. @@ -399,7 +408,7 @@ def _queue_into_buffer(transfersession): logger.info("Queuing complete") -@transaction.atomic() +@transaction.atomic(using=USING_DB) def _dequeue_into_store(transfersession): """ Takes data from the buffers and merges into the store and record max counters. diff --git a/tests/testapp/testapp/postgres_test.py b/tests/testapp/testapp/postgres_test.py index bcb725c6..c9cb4a6e 100644 --- a/tests/testapp/testapp/postgres_test.py +++ b/tests/testapp/testapp/postgres_test.py @@ -1,7 +1,16 @@ """ A settings module for running tests using a postgres db backend. """ -from __future__ import absolute_import, print_function, unicode_literals +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals + +try: + isolation_level = None + import psycopg2 + isolation_level = psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE +except ImportError: + pass from .settings import * # noqa @@ -15,4 +24,16 @@ 'NAME': 'travis_ci_default' } }, + 'default-serializable': { + 'ENGINE': 'django.db.backends.postgresql', + 'USER': 'postgres', + 'PASSWORD': '', + 'NAME': 'default', # This module should never be used outside of tests -- so this name is irrelevant + 'TEST': { + 'NAME': 'travis_ci_default' + }, + 'OPTIONS': { + 'isolation_level': isolation_level, + } + } }