Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

URL based database routing

Tags:

python

django

I set up URL based database routing inspired by this answer in order to use the same app for different projects/databases. The projects do not need to share any data, access control is managed by every project on its own and I need an admin site for every project. As in the original post I use a database router and a middleware which determines which database to be used from the request path, e.g. /test/process/1 will be routed to database test and /default/process/2 to database default.

import threading
from django.conf import settings

request_cfg = threading.local()

class RouterMiddleware(object):
    def process_view(self, request, view_func, view_args, view_kwargs):
        path = request.path.lstrip('/').split('/')
        if path[0] in settings.DATABASES:
            request_cfg.db = path[0]

    def process_response(self, request, response):
        if hasattr(request_cfg, 'db'):
            del request_cfg.db
        return response

class DatabaseRouter(object):
    def _default_db(self):
        if hasattr(request_cfg, 'db') and request_cfg.db in settings.DATABASES:
            return request_cfg.db
        else:
            return 'default'

    def db_for_read(self, model, **hints):
        return self._default_db()

    def db_for_write(self, model, **hints):
        return self._default_db()

The url patterns then need to be extended to include the subpath which refers to a specific database. I did this by hardcoding the urls in the project level urls.py like this:

urlpatterns = [
  url(r'^default/admin/', include(admin.site.urls)),  # does not work
  url(r'^test/admin/', include(admin.site.urls)),  # does not work
  url(r'^default/', include('logbook.urls', namespace='anything')),
  url(r'^test/', include('logbook.urls', namespace='anything else'))]

I admit that this is not very nice but I do not expect to have to manage more than a few databases. Interestingly, it does not matter what the namespace argument is but it has to be given. The original namespace of the app was logbook and it is used for url reversing all over the app's views and templates.

Then, in the app level urls.py the app_name has to be defined (and be equal to the original namespace):

app_name = 'logbook'

urlpatterns = [
    url(r'^$', views.redirect_index, name='index'),
    url(r'^(?P<date>[0-9]{4}[0-9]{2})/$', views.Index.as_view(), name='index'),
.....

In the views I added a current_app=request.resolver_match.namespace kwarg to every call of reverse() as explained in the django docs. URL resolving whithin the templates did not need any modifcation.

Overall this works very well with two exceptions:

  • url reversing for any of the admin views will always resolve to the first entry in urls.py
  • I cannot make it work with the django.contrib.auth.middleware.AuthenticationMiddleware mostly, I think because LOGIN and LOGIN_REDIRECT are constants.

I am wondering if this is a clean approach and if there is solution to the two exceptions mentioned above. If not, what would be a better solution?

like image 864
Christian K. Avatar asked Apr 03 '17 16:04

Christian K.


Video Answer


1 Answers

This is the article you are looking for.

Django Multi DB Documentation

It explains how to set up your multiple db, and the admin console to work along with it. As it says you need to make a custom model for your second db(the one which isn't default) and register it using the method given in the documentation.

like image 148
Abhishek Menon Avatar answered Nov 04 '22 15:11

Abhishek Menon