-
-
Notifications
You must be signed in to change notification settings - Fork 38
TypeError: Can't instantiate abstract class in post_migrate_treenode for proxy models using Python ABCs #215
Description
Description
Python version: 3.14
Django version: 6.0
Package version: 0.23.3
Current behavior (bug description)
The post_migrate_treenode signal handler calls update_tree() on models that are Python-level abstract classes (using abc.ABC / @abstractmethod), even though they are registered as concrete Django models (e.g. proxy models). This causes a TypeError because Python cannot instantiate an abstract class.
Error:
TypeError: Can't instantiate abstract class AbstractBaseFeideFetchTask without an implementation for abstract method '...'
Stack trace:
File ".venv/lib/python3.14/site-packages/treenode/signals.py", line 33, in post_migrate_treenode
sender_model.update_tree()
...
TypeError: Can't instantiate abstract class AbstractBaseFeideFetchTask without an implementation for abstract method '...'
Root cause:
In treenode/signals.py, __is_treenode_model checks isclass() and issubclass(), but does not check inspect.isabstract():
def __is_treenode_model(sender):
from .models import TreeNodeModel
return (
isclass(sender)
and issubclass(sender, TreeNodeModel)
and sender != TreeNodeModel
)This allows abstract proxy models through. Django's get_models() returns all registered models including proxy models, and a proxy model can be a Python ABC if it uses a metaclass combining ABCMeta and Django's ModelBase, and declares @abstractmethod methods. Such models pass the isclass and issubclass checks but cannot be instantiated — so calling update_tree() on them fails.
Note: This is a different issue from #207, which fixed the RuntimeError: dictionary changed size during iteration bug. The list() fix in v0.23.3 solved that problem, but this abstract class issue remains.
Reproduction steps
- Create a Django model inheriting from a
TreeNodeModelsubclass using a metaclass that combinesABCMetawith Django'sModelBase:
from abc import ABCMeta, abstractmethod
from django.db.models.base import ModelBase
from treenode.models import TreeNodeModel
class ABCMetaModelBase(ABCMeta, ModelBase):
"""Metaclass combining ABCMeta and Django's ModelBase."""
pass
class ConcreteTreeNodeModel(TreeNodeModel):
"""A concrete TreeNode model."""
class Meta:
app_label = "myapp"
class AbstractProxyTask(ConcreteTreeNodeModel, metaclass=ABCMetaModelBase):
"""A proxy model that is also a Python ABC — registered in Django but not instantiable."""
class Meta:
proxy = True
@abstractmethod
def my_method(self):
"""Subclasses must implement this."""
...- Run migrations:
python manage.py migrate
- The
post_migrate_treenodesignal fires and callsupdate_tree()onAbstractProxyTask, which raises:
TypeError: Can't instantiate abstract class AbstractProxyTask without an implementation for abstract method 'my_method'
Expected behavior
The post_migrate_treenode signal handler should skip models that are Python abstract classes (inspect.isabstract() returns True), since they cannot be instantiated and therefore update_tree() will fail.
Proposed fix
Add an inspect.isabstract() check in __is_treenode_model:
from inspect import isclass, isabstract
def __is_treenode_model(sender):
from .models import TreeNodeModel
return (
isclass(sender)
and not isabstract(sender)
and issubclass(sender, TreeNodeModel)
and sender != TreeNodeModel
)This skips any model that Python considers abstract (i.e., has unimplemented @abstractmethod methods), preventing the TypeError.
Workaround
Until this is fixed, users can monkey-patch the signal handler in their AppConfig.ready():
import inspect
def _fixed_post_migrate_treenode(sender, **kwargs):
for sender_model in list(sender.get_models()):
if (
hasattr(sender_model, "update_tree")
and not inspect.isabstract(sender_model)
):
sender_model.update_tree()
class MyAppConfig(AppConfig):
def ready(self):
from django.db import models as django_models
from treenode import signals as treenode_signals
django_models.signals.post_migrate.disconnect(
receiver=treenode_signals.post_migrate_treenode,
dispatch_uid="post_migrate_treenode",
)
django_models.signals.post_migrate.connect(
receiver=_fixed_post_migrate_treenode,
dispatch_uid="post_migrate_treenode",
)Impact
This bug affects projects that:
- Use
django-treenodewith proxy models that are also Python ABCs (usingABCMeta+@abstractmethod) - Run migrations (both in production and during test setup)
The fix is minimal (adding isabstract()) and should have no performance impact.