This question is about model inheritance in Django.
Almost everything I have read (including the Django documentation itself) strongly recommends doing 'abstract base class' inheritance rather than 'multi-table' inheritance. I agree with the reasoning and as a result am fully behind the recommendation. However, Django does not appear to have support for:
- polymorphic querying, or
- model linking (i.e. I cannot create a ForeignKey field to the abstract base class from another model).
Situation
For example, I have some models that implement the 'abstract base class' inheritance pattern:
class Tool(models.Model):
name = models.CharField(max_length=30)
group = models.ManyToManyField(ToolGroup, blank=True) # Link to 'ToolGroup' MUST be placed on abstract class
attributes = models.ManyToManyField(ToolAttributeValue, blank=True) # Link to 'ToolAttributeValue' MUST be placed on abstract class
class Meta:
abstract = True # Almost everything I read strongly recommends against making this its own table
class HandheldTool(Tool):
electrical_safe = models.BooleanField(default=False)
class PowerTool(Tool):
compliance_expiry_date = models.DateTimeField()
class ConsumableTool(Tool):
combustible = models.BooleanField(default=False)
best_before = models.DateTimeField(null=True)
I also have some grouping and information classes related to tools in general:
# Grouping related structures
#
# ToolHierarchy > ToolGroup (n times) > Tool
#
# "Tool Boxes" > "Day Shift" > "Builders" > HandheldTool[Hammer]
# > HandheldTool[Screwdriver - SAFE]
# > PowerTool[Drill]
#
# > "Demo Team" > HandheldTool[Sledgehammer 1]
# > PowerTool[Jackhammer]
# > ConsumableTool[Dynamite]
#
# > "Night Shift" > "Rock Breakers" > HandheldTool[Hammer]
# > HandheldTool[Sledgehammer 2]
# > PowerTool[Rocksaw]
class ToolHierarchy(models.Model):
name = models.CharField(blank=True, max_length=30)
class ToolGroup(models.Model):
name = models.CharField(blank=True, max_length=30)
parent = models.ForeignKey('self', related_name='children', null=True, blank=True)
hierarchy = models.ForeignKey(ToolHierarchy, null=True, blank=True, related_name='top_level_tools')
# tools = models.ManyToManyField(Tool) # CANNOT MAKE LINK, as 'Tool' is abstract
# 'Extra-info' structures
#
# ToolAttribute > ToolAttributeValue > Tool
#
# 'Brand' > 'Stanley' > HandheldTool[Hammer]
# > HandheldTool[Sledgehammer 1]
# > 'ACME' > HandheldTool[Sledgehammer 2]
# > ConsumableTool[Dynamite]
#
# 'Supplier' > 'Bash Brothers' > HandheldTool[Hammer]
# > HandheldTool[Sledgehammer 1]
# > HandheldTool[Sledgehammer 2]
class ToolAttribute(models.Model):
name = models.CharField(max_length=30)
data_type = models.CharField(max_length=30) # e.g. "STRING", "INT", "DATE" "FLOAT" -- Actually done with enum
unit_of_measure = models.CharField(max_length=30, blank=True)
class ToolAttributeValue(models.Model):
attribute = models.ForeignKey(ToolAttribute)
value = models.CharField(blank=True, max_length=30)
# tool = models.ForeignKey(Tool) # CANNOT MAKE LINK, as 'Tool' is abstract
Problem
Ideally this inheritance model would be implemented via a polymorphic relationship, however the Django ORM does not support it. This is possible with SQLAlchemy and other ORMs like Hibernate.
With the Django ORM, because the Tool
class is abstract I cannot create the links like:
ToolAttributeValue.tool -> tool_obj
orToolGroup.tools -> [tool_obj_1, tool_obj_2]
.
Instead I am forced to create the inverse link on the abstract class, despite it modelling a slightly different thing! This then results in all sorts of ugliness on the ToolAttributeValue
and ToolGroup
objects, which then no longer have a .tools
attribute but instead have RelatedManager
fields for each subtype. i.e.:
tool_group_obj.handheldtool_set.all()
tool_group_obj.powertool_set.all()
...etc, for every subtype of Tool
Which pretty much destroys the usefulness of the abstract class.
Questions
So, with this in mind, my questions are:
- Is this a good case for 'multi-table' inheritance?
- Am I trying too hard to force the type inheritance? Should I just get rid of
Tool
? If yes, then do I have to create a*ToolGroup
model for each subclass? - What is the current (Django 1.8) accepted way around this? Surely I am not the first person to build a relational system in Django ;-) And the fact that other ORMs have considered this problem suggest it is a common design choice.
- Is the polymorphic solution (possibly via SQLAlchemy) an option? Is this being considered for Django 1.9+?
NB: I have read the docs for and tested https://github.com/chrisglass/django_polymorphic, but it does not appear to work for 'abstract base class' inheritance (i.e. it is only for multi-table). If I did choose to do multi-table inheritance then django-polymorphic would be good for the query side of my problem and I guess my model-linking problem would disappear.
NB: This is a similar question to this one, but provides more detail.