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:
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
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.
So, with this in mind, my questions are:
Tool
? If yes, then do I have to create a *ToolGroup
model for each subclass?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.
Ok, so I think I'm going to answer my own questions...
Is this a good case for 'multi-table' inheritance?
It appears so. Although there are a few places that recommend against 'multi-table' inheritance (listed here for example), some counterpoints are:
@Bruno Desthuilliers points out that these opinions are not from the 'official' documentation, and by extension, he implies that 'multi-table' is a perfectly good feature available for one to use.
My reading of @dhke's links and comments is that you have to choose one option, and that the 'multi-table' option is the only way databases truly support inheritance. I.e. Even with the polymorphic tricks of tools like Hibernate or SQLAlchemy, you are still choosing whether to JOIN tables ('multi-table' option) for object lookup or to UNION tables ('abstract base' / 'polymorphic' options) for set creation.
@dhke also points out that it is probably better to use the 'multi-table' option and tell a library like django-polymorphic not to do subclass resolution when looking up the 'whole set' rather than have the database do a UNION over all of the tables (as would be required for 'whole set' lookup with the 'abstract base class' option).
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?
No, it doesn't seem that way. The two uses of the Tool
interface that I presented have different needs:
The ToolGroup
/ hierarchical-grouping use case is a good one for retaining the inherited Tool
class. This would get very ugly if you had to create a type-specific set of classes for every type of tool
The ToolAttribute
also makes a good case for the super class, except if you are able to use things like the HSTORE field type (provided by Postgres, I'm not sure about other backends). This link gives a good rundown, and it is probably what I will do here (Thanks to @nigel222 for the research that went into the question!).
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.
This is now an irrelevant question. Basically they don't worry about it.
Is the polymorphic solution (possibly via SQLAlchemy) an option? Is this being considered for Django 1.9+?
Not that I can tell.
The case that led me to this question is a model like this:
class PurchasableItem(models.Model):
class Meta:
abstract = True
class Cheesecake(PurchasableItem):
pass
class Coffee(PurchasableItem):
pass
The workaround I used is turning the parent class into an attribute:
class PurchasableItem(models.Model):
class Meta:
abstract = False
class Cheesecake(models.Model):
purchasable_item = models.OneToOneField(PurchasableItem, on_delete=models.CASCADE)
class Coffee(models.Model):
purchasable_item = models.OneToOneField(PurchasableItem, on_delete=models.CASCADE)
This way, I can get both the behavior and querying functionality.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With