Opened 4 years ago

Last modified 4 years ago

#32563 closed Bug

Cannot override database used with RelatedManager — at Initial Version

Reported by: Lucas Gruber Owned by: nobody
Component: Database layer (models, ORM) Version: dev
Severity: Normal Keywords: RelatedManager mullti databases
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

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, *, bulk=True, clear=False):
            # Force evaluation of `objs` in case it's a queryset whose value
            # could be affected by `manager.clear()`. Refs #19816.
            objs = tuple(objs)

            if self.field.null:
                db = router.db_for_write(self.model, instance=self.instance)
                with transaction.atomic(using=db, savepoint=False):
                    if clear:
                        self.clear(bulk=bulk)
                        self.add(*objs, bulk=bulk)
                    else:
                        old_objs = set(self.using(db).all())
                        new_objs = []
                        for obj in objs:
                            if obj in old_objs:
                                old_objs.remove(obj)
                            else:
                                new_objs.append(obj)

                        self.remove(*old_objs, bulk=bulk)
                        self.add(*new_objs, bulk=bulk)
            else:
                self.add(*objs, bulk=bulk)
        set.alters_data = True
[...]

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.

Thank you in advance for your answer

Change History (0)

Note: See TracTickets for help on using tickets.
Back to Top