7

In a view class, you could call self.request.user and perform actions based on that. In my case, I would like to be able to switch databases depending on the current user logged in. Is there anyway to inject a self.request call into the db_for_read() and db_for_write() methods like in the following?

class DataBaseRouter(object):

    def db_for_read(self, model, **hints):
        user = self.request.user
        if user.id == 1:
            return "master"
        return "default"

    def db_for_write(self, model, **hints):
        user = self.request.user
        if user.id == 1:
            return "master"
        return "default"
Lorenzo
  • 133
  • 3
  • 9

3 Answers3

3

You can use a RouterMiddlewear to check if what user is logged in and then redirect all the queries to a particular database of your choice, this would be help full if you are using view based execution of the queries.

class RouterMiddleware (object):

    def process_view( self, request, view_func, args, kwargs ):
        # Check the user logged in
        user = self.request.user
        # Call your functions to set the database by passing the user.id



    def process_response( self, request, response ):
        # Make the database to default here if you wish to use it no longer

        return response


class DataBaseRouter(object):

    def db_for_read(self, model, user_id=None, **hints):
       if user.id == 1:
            return "master"
        return "default"

    def db_for_write(self, model, user_id=None, **hints):
        if user.id == 1:
            return "master"
        return "default"

Here is the link that I have modified for your requirement.

Make sure you add the the RouterMiddleware to your MIDDLEWARE_CLASSES.

kt14
  • 838
  • 15
  • 25
  • @kartheek' s answer would be a simpler solution if you have flexibility to install the package in your project . You can use the Middleware processor if you don't like to check and change the database for each view function. – kt14 Sep 06 '16 at 18:33
  • Hey, thanks for the answer @kt14! I ultimately went with your answer as I found it much easier to implement over kartheek's. I do have one worry though. Is there any chance that multiple requests called at roughly the same time messing this up? I.E. Request1 > user.id =1 Request2 > user.id = 2 Response1 > gets user.id = 2 causing an error – Lorenzo Sep 07 '16 at 15:01
  • I don't think that should be any problem because django will create a new request object for each request the user passes on, no matter if two different users query at the same time, the query passes the context_processor and that particular database would be selected, so there would be no cross-interaction as far as I know. – kt14 Sep 07 '16 at 15:12
  • 3
    @kt14 Hi, i dont really get the point of how we inject user.id into router class. In the linked snippet we use threading.local() to share this but i cant make it work on django 2.1.4. Maybe there were some changes to this? – Alexey Trofimov Jan 14 '19 at 05:57
  • Ok sorry it was because of Django version MIDDLEWARE usage. Added __init__ and __call__ and all good now. – Alexey Trofimov Jan 14 '19 at 06:47
  • 2
    @kt14 How did you pass `user` object inside `db_for_read` method? It is not possible without using `threading` global object (as shown at the link). !That may be shared between sessions!. Please explain connection between `RouterMiddleware` and `DataBaseRouter` – rzlvmp Sep 21 '21 at 05:02
2

Try this django dynamic db router package. its very simple. install it and configure and use as below.

settings.py

DATABASES = {
'default': {
    'ENGINE': 'django.db.backends.postgresql_psycopg2',
    'NAME': 'my_local_database',
    'USER': 'postgres',
    'PASSWORD': 'my-pass',
    'HOST': '127.0.0.1',
    'PORT': '5432',
},
'master': {
    'ENGINE': 'django.db.backends.postgresql_psycopg2',
    'NAME': 'my_master_database',
    'USER': 'postgres',
    'PASSWORD': 'my-pass',
    'HOST': 'example.com',
    'PORT': '5432',
},
}
DATABASE_ROUTERS = ['dynamic_db_router.DynamicDbRouter']

my_app/views.py

from dynamic_db_router import in_database

from my_app.models import MyModel

def index(request):
     #Picking the DB based on logged in user or you can do this in middile ware as well.
     use_db = "default"
     user = self.request.user
     if user.id == 1:
        use_db = "master"
     # Fetching data from selected databases. 
     with in_database(use_db):
         input = MyModel.objects.filter(field_a="okay")
         output = complex_query_function(input)
kartheek
  • 6,434
  • 3
  • 42
  • 41
  • Hey @Kartheek, thanks for the answer! While I appreciate it, I ultimately went with kt14's as I found his answer to be much easier to implement as my project deals with close to a hundred views and thus writing code per view would not be feasible. I thank you for your time though! – Lorenzo Sep 07 '16 at 15:03
  • @Lorenzo I have mentioned inline comment, you can move that logic to middleware. its another way of implementation. Anyway am glad you got solution. – kartheek Sep 07 '16 at 15:26
0

To resolve this, I created a new model with just one userid field. Then in router when specifying db for read and write, I get the data from that model so I don't need any request argument.
And I just added a short ajax code in my base template so whenever my website is opened it gets the userid from request.user and change the model row so current user id is changed like so.

Suraj Rao
  • 29,388
  • 11
  • 94
  • 103