Django multiple databases — read from "readonly" connection; Commit transaction and have all following queries use "default" connection

Viewed 351

I have 2 databases:

# Config
DATABASES = {
    'default': {},
    'readonly': {},
}

In a background task, I would like to pull data from the "readonly" database, then save using the "default" database:

# Tasks
things = Thing.objects.using('readonly').all()


util.do_stuff_with_things(things)  # ideally I don't want to make `using` a required parameter of all my utilities

I could manually specify save(using='default') on all of the utilites, but it's difficult to hunt them down. I would rather commit the transaction, then start a new transaction on the default connection.

What I have attempted:

  1. set_autocommit
transaction.set_autocommit(False, using='readonly')
thing = Thing.objects.using('readonly').first()
transaction.commit(using='readonly')

transaction.set_autocommit(True, using='default')
thing.save()  # `save` still requires `using='default'`, "InternalError: cannot execute UPDATE in a read-only transaction"
  1. pulled within atomic block
with transaction.atomic(using='readonly'):
    thing = Thing.objects.using('readonly').first()


thing.save()  # `save` still requires `using='default'`, "InternalError: cannot execute UPDATE in a read-only transaction"
  1. overwriting _state.db

This works -- but I don't like it :(

thing = Thing.objects.using('readonly').first()

thing._state.db = 'default'

thing.save()

For now, I will probably go with #3, as it's least intrusive to the utilities I am using. But interested to know if there's a better way!

1 Answers

Thanks @arakkal-abu, Database Routers are what I was looking for

class BGTaskDatabaseRouter:
    def db_for_read(self, model, **hints):
        return 'readonly'

    def db_for_write(self, model, **hints):
        return 'default'

in my settings file:

DATABASE_ROUTERS = []
if IS_BACKGROUND_TASK:
    DATABASE_ROUTERS.append('database_routers.BGTaskDatabaseRouter'
Related