#35974: create_reverse_many_to_one_manager: use router.allow_relation instead of
comparing objects' _state.db in RelatedManager.add
-------------------------------------+-------------------------------------
     Reporter:  Laurent Szyster      |                     Type:  Bug
       Status:  new                  |                Component:  Database
                                     |  layer (models, ORM)
      Version:  4.2                  |                 Severity:  Normal
     Keywords:                       |             Triage Stage:
                                     |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
 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
-- 
Ticket URL: <https://code.djangoproject.com/ticket/35974>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/django-updates/0107019396a02a13-7c78f047-7e62-407b-8244-8f97a3d49759-000000%40eu-central-1.amazonses.com.

Reply via email to