django-polymorphic
django-polymorphic copied to clipboard
Missing `using` statement in `PolymorphicModel::accessor_function` causing issues when working with a non-default database
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'