django-polymorphic icon indicating copy to clipboard operation
django-polymorphic copied to clipboard

Missing `using` statement in `PolymorphicModel::accessor_function` causing issues when working with a non-default database

Open SebastianRehfeldt opened this issue 3 years ago • 0 comments

I just ran into a cascading bug with polymorphic models when using a non-default/remote database. My goal was to collect and print all entities of the polymorphic models in the from_database and then copy them into another to_database. I also wanted to clear the to_database before the transfer. The following code pieces resulted in errors:

# Case 1: Deletion failed when using a non-default database

PolymorphicModel.objects.using("to_database").delete()


# Case 2: Collect and print entities failed too

from django.contrib.admin.utils import NestedObjects

polymorphic_entities = PolymorphicModel.objects.using("to_database").all()
collector = NestedObjects(using="to_database")
collector.collect(polymorphic_entities)
collector.nested()

Both snippets raised a DoesNotExist exception:

DoesNotExist                              Traceback (most recent call last)
<ipython-input-9-ca16ff08e5cb> in <module>
      6 collector = NestedObjects(using='to_database')
----> 7 collector.collect(polymorphic_entities)
      8 collector.nested()

/opt/pysetup/.venv/lib/python3.8/site-packages/django/contrib/admin/utils.py in collect(self, objs, source, source_attr, **kwargs)
    180             self.model_objs[obj._meta.model].add(obj)
    181         try:
--> 182             return super().collect(objs, source_attr=source_attr, **kwargs)
    183         except models.ProtectedError as e:
    184             self.protected.update(e.protected_objects)

/opt/pysetup/.venv/lib/python3.8/site-packages/django/db/models/deletion.py in collect(self, objs, source, nullable, collect_related, source_attr, reverse_dependency, keep_parents, fail_on_restricted)
    254             for ptr in concrete_model._meta.parents.values():
    255                 if ptr:
--> 256                     parent_objs = [getattr(obj, ptr.name) for obj in new_objs]
    257                     self.collect(parent_objs, source=model,
    258                                  source_attr=ptr.remote_field.related_name,

/opt/pysetup/.venv/lib/python3.8/site-packages/django/db/models/deletion.py in <listcomp>(.0)
    254             for ptr in concrete_model._meta.parents.values():
    255                 if ptr:
--> 256                     parent_objs = [getattr(obj, ptr.name) for obj in new_objs]
    257                     self.collect(parent_objs, source=model,
    258                                  source_attr=ptr.remote_field.related_name,

/opt/pysetup/.venv/lib/python3.8/site-packages/polymorphic/models.py in accessor_function(self)
    204         def create_accessor_function_for_model(model, accessor_name):
    205             def accessor_function(self):


# I guess this is missing a using statement to also use the non-default db
--> 206                 attr = model._base_objects.get(pk=self.pk)

    207                 return attr
    208 

/opt/pysetup/.venv/lib/python3.8/site-packages/django/db/models/manager.py in manager_method(self, *args, **kwargs)
     83         def create_method(name, method):
     84             def manager_method(self, *args, **kwargs):
---> 85                 return getattr(self.get_queryset(), name)(*args, **kwargs)
     86             manager_method.__name__ = method.__name__
     87             manager_method.__doc__ = method.__doc__

/opt/pysetup/.venv/lib/python3.8/site-packages/django/db/models/query.py in get(self, *args, **kwargs)
    427             return clone._result_cache[0]
    428         if not num:
--> 429             raise self.model.DoesNotExist(
    430                 "%s matching query does not exist." %
    431                 self.model._meta.object_name

DoesNotExist: PolymorphicModel matching query does not exist.

During debugging I noticed that for some queries the default database was used and not the to_database. Therefore, some queries failed as related rows could not be found in the default database.

It looks to me that the issue is in polymorphic/models.py in accessor_function(self): attr = model._base_objects.get(pk=self.pk).

The get is querying the default database which could be fixed when replacing with attr = model._base_objects.using(self._state.db).get(pk=self.pk).

Additional Notes

The polymorphic models have a foreign key to a parent model on which I actually called the delete() and which I wanted to use for the collection of entities. So first, I expected some issue in the NON_POLYMORPHIC_CASCADE. Seems to me now that this is entirely related to the missing using statement.

I have the following versions installed:

  • Django: '3.1.6'
  • Django-polymorphic: '3.0.0'

SebastianRehfeldt avatar May 01 '21 14:05 SebastianRehfeldt