@@ -332,3 +332,40 @@ def test_database_router_respected(self):
332332
333333 # Verify it wasn't saved to default database
334334 self .assertFalse (Model2B .objects .filter (pk = obj .pk ).exists ())
335+
336+ def test_save_respects_db_for_write_router (self ):
337+ """
338+ When a DATABASE_ROUTER routes writes to a different database than where
339+ the object was read from, save() should respect db_for_write() rather
340+ than blindly using _state.db. Regression test for issue #865.
341+ """
342+
343+ class ReadWriteSplitRouter :
344+ """Router that sends writes to 'default' regardless of _state.db."""
345+
346+ def db_for_read (self , model , ** hints ):
347+ return None
348+
349+ def db_for_write (self , model , ** hints ):
350+ return "default"
351+
352+ def allow_relation (self , obj1 , obj2 , ** hints ):
353+ return True
354+
355+ def allow_migrate (self , db , app_label , ** hints ):
356+ return True
357+
358+ # Create an object on "secondary", simulating an object loaded from a
359+ # read-replica whose _state.db points at the replica.
360+ obj = Model2B .objects .using ("secondary" ).create (field1 = "test" , field2 = "value" )
361+ self .assertEqual (obj ._state .db , "secondary" )
362+
363+ with self .settings (DATABASE_ROUTERS = [ReadWriteSplitRouter ()]):
364+ # save() without an explicit `using` should consult the router's
365+ # db_for_write() and route to "default", not to _state.db ("secondary").
366+ obj .field1 = "modified"
367+ obj .save ()
368+
369+ # The write should have landed in "default" (the write database).
370+ self .assertTrue (Model2B .objects .using ("default" ).filter (field1 = "modified" ).exists ())
371+ self .assertFalse (Model2B .objects .using ("secondary" ).filter (field1 = "modified" ).exists ())
0 commit comments