﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
32563	Cannot override database used with RelatedManager	Lucas Gruber	nobody	"I suppose an app with models:
{{{
############################
class Blog(models.Model):
    title = models.CharField(max_length=100)

class Person(models.Model):
    name = models.CharField(max_length=100)
    subscribed_blogs = models.ManyToManyField(Blog, related_name=""subscribers"", through=""Subscription"")
    
class Subscription(models.Model):
    person = models.ForeignKey(Person, related_name=""subscriptions"")
    blog = models.ForeignKey(Blog, related_name=""subscriptions"")
############################
}}}

In thes case we are using multiple databases and we need to create custom migration, we have to write something like
{{{
############################
def custom_migrations(apps, schema_editor):
    db_alias = schema_editor.connection.alias
    
    person = Person.objects.using(db_alias).get(pk=1)
    blog = Blog.objects.using(db_alias).get(pk=1)
    blog.subscribers.set([person])

    blog.save(using=db_alias)

############################
}}}

The line blog.subscribers.set(...) does not permit to add parameter for overriding database to use.
The source code for this function is in django.db.models.fields.related_descriptors when we can see:
{{{
[...]
        def set(self, objs, *, clear=False, through_defaults=None):
            # Force evaluation of `objs` in case it's a queryset whose value
            # could be affected by `manager.clear()`. Refs #19816.
            objs = tuple(objs)

            db = router.db_for_write(self.through, instance=self.instance)
            with transaction.atomic(using=db, savepoint=False):
                if clear:
                    self.clear()
                    self.add(*objs, through_defaults=through_defaults)
                else:
                    old_ids = set(self.using(db).values_list(self.target_field.target_field.attname, flat=True))

                    new_objs = []
                    for obj in objs:
                        fk_val = (
                            self.target_field.get_foreign_related_value(obj)[0]
                            if isinstance(obj, self.model) else obj
                        )
                        if fk_val in old_ids:
                            old_ids.remove(fk_val)
                        else:
                            new_objs.append(obj)

                    self.remove(*old_ids)
                    self.add(*new_objs, through_defaults=through_defaults)
[...]
}}}

Code always calls database router, but in migration process, the router can not find the appropriate database because we just use without request :
{{{
python manage.py migrate --database db2
}}}

I noticed that all the methods of ''RelatedManager'' directly call the router object to find the database while the ''Manager'' objects always exploits the possibility of overriding the database with the call to ''using()'' on the QuerySet or to pass parameter ''using=db'' for save model method for example.

I guess we need to use a mecanism which we can find in Model class with the save method :
{{{
    def save(self, force_insert=False, force_update=False, using=None,
             update_fields=None):
        [...]
        using = using or router.db_for_write(self.__class__, instance=self)
        [...]
}}}

Thus, we will be able to override the database at the time of the save.

Thank you in advance for your answer


"	Bug	new	Database layer (models, ORM)	dev	Normal		RelatedManager mullti databases		Unreviewed	0	0	0	0	0	0
