﻿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
35974	Use router.allow_relation in RelatedManager.add and GenericRelatedObjectManager.add	Laurent Szyster	AbhigyaShridhar	"In the `create_reverse_many_to_one_manager` function, the `RelatedManager.add` method will raise an `ValueError` if one of the related objects to add to the many-to-many relation set has not been loaded from the same database connection as the instance to which it is related.

{{{
            if bulk:
                pks = []
                for obj in objs:
                    check_and_update_obj(obj)
                    if obj._state.adding or obj._state.db != db:
                        raise ValueError(
                            ""%r instance isn't saved. Use bulk=False or save ""
                            ""the object first."" % obj
                        )
                    pks.append(obj.pk)
                self.model._base_manager.using(db).filter(pk__in=pks).update(
                    **{
                        self.field.name: self.instance,
                    }
                )
}}}

See: https://github.com/django/django/blob/39cf3c63f3228a04f101f3e62c75a6aae7c6ef0f/django/db/models/fields/related_descriptors.py#L767-L776

This implies that an object loaded from a read-only connection to a database cannot be related to an instance created using a distinct write-enabled connection to the same database.

For instance, assume the following Database Router:

{{{
class DatabaseRouter:
    def db_for_read(self, model, **hints):
        return ""read-only""

    def db_for_write(self, model, **hints):
        return ""default""

    def allow_relation(self, value, instance, **hints):
        if instance._state.db == 'default' and value._state.db in {'default', 'read-only'}:
            return True
        
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        return True
}}}

This router will dispatch read queries to a ""read-only"" database connection and write queries to the ""default"" database connection. Such setup is intended to limit the load on the writer node of a database cluster.

But for such setup to work the router's `allow_relation` method should be used instead of comparing the database connection used to load the related value with the database to write the instance to which we want to add a relation.

Line 771 of `related_descriptor.py`:

{{{
                    if obj._state.adding or obj._state.db != db:
}}}

Should be replaced with:

{{{
                    if obj._state.adding or not (router.allow_relation(obj, self.instance) or obj._state.db == db):
}}}

See: https://docs.djangoproject.com/en/4.2/topics/db/multi-db/#allow_relation"	Bug	assigned	Database layer (models, ORM)	4.2	Normal			Laurent Szyster Tim Graham	Accepted	1	0	0	0	0	0
