Skip to content

Instantly share code, notes, and snippets.

@gijzelaerr
Created July 18, 2014 09:36
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save gijzelaerr/7a3130c494215a0dd9b2 to your computer and use it in GitHub Desktop.
Save gijzelaerr/7a3130c494215a0dd9b2 to your computer and use it in GitHub Desktop.
"""
Select database based on URL variable
Inspired by this Django snipped:
https://djangosnippets.org/snippets/2037/
It's assumed that any view in the system with a cfg keyword argument passed to
it from the urlconf may be routed to a separate database. for example:
url( r'^(?P<db>\w+)/account/$', 'views.account' )
The middleware and router will select a database whose alias is <db>,
"default" if no db argument is given and raise a 404 exception if not listed in
settings.DATABASES, all completely transparent to the view itself.
"""
import threading
from django.http import Http404
request_cfg = threading.local()
class MultiDbRouterMiddleware(object):
"""
The Multidb router middelware.
he middleware process_view (or process_request) function sets some context
from the URL into thread local storage, and process_response deletes it. In
between, any database operation will call the router, which checks for this
context and returns an appropriate database alias.
Add this to your middleware, for example:
MIDDLEWARE_CLASSES += ['bananaproject.multidb.MultiDbRouterMiddleware']
"""
def process_view(self, request, view_func, args, kwargs):
if 'db' in kwargs:
request_cfg.db = kwargs['db']
request.SELECTED_DATABASE = request_cfg.db
def process_response(self, request, response):
if hasattr(request_cfg, 'db'):
del request_cfg.db
return response
class MultiDbRouter(object):
"""
The multiple database router.
Add this to your Django database router configuration, for example:
DATABASE_ROUTERS += ['bananaproject.multidb.MultiDbRouter']
"""
def _multi_db(self):
from django.conf import settings
if hasattr(request_cfg, 'db'):
if request_cfg.db in settings.DATABASES:
return request_cfg.db
else:
raise Http404
else:
return 'default'
def db_for_read(self, model, **hints):
if model._meta.app_label != 'banana':
return 'default'
return self._multi_db()
db_for_write = db_for_read
def multidb_context_processor(request):
"""
This context processor will add a db_name to the request.
Add this to your Django context processors, for example:
TEMPLATE_CONTEXT_PROCESSORS +=[
'bananaproject.multidb.multidb_context_processor']
"""
if hasattr(request, 'SELECTED_DATABASE'):
return {'db_name': request.SELECTED_DATABASE}
else:
return {}
@d-j-kendall
Copy link

d-j-kendall commented Aug 24, 2020

I am going to implement this in a production environment for different databases with identical models but different data. Not the way I would have done it originally, but I am updating some code and bringing 6 different web applications into one web application. I implemented your code here and it work fantastically for most HTTP requests. It broke the Django admin page, but everything else seems to work.

Are there any possible race conditions while multiple users are querying different database. I see you use a thread local variable to handle this, but I can not find any documentation of he "SELECTED_DATABASE" attribute anywhere.

Any help would be appreciated!

@anselmobd
Copy link

Hi, @dkendall100 .
It's an old question, but here's my little help.
From what I understand from @gijzelaerr's code, "SELECTED_DATABASE" is any variable name used to place in the request the information of selected database, just so that the context_processor receives it and places it in a context variable "db_name".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment