Polymorphism in Python and Django
What is polymorphism?
Polymorphism is the ability of an object to take on many forms.
When is polymorphism used?
A polymorphic model is used when a single entity requires different functionality or information.
Difference of Polymorphic and Abstract Classes
Simply put, abstract instances do not exist. An abstract class is just a way of generalizing information for future children classes. Therefore, the only objects you’ll be able to relate are the children instances, never the parent abstract class.
If you want to relate to the parent abstract class, this is where polymorphic classes come in handy in Django.
I’m currently working on a project where I need classes related with a OneToMany relationship with that parent abstract class. If you relate it without polymorphism you’ll get the following error:
abstract_base_model.ModelName.field: (fields.E300) Field defines a relation with model 'ModelName', which is either not installed, or is abstract.
Django Polymorphic models
The good news is that it’s actually really straightforward to use polymorphism in Django! I’ve used the library django-polymorphic
Step 1: Install the library
pip install django-polymorphic
Step 2: Update the settings.py file
INSTALLED_APPS += (
'polymorphic',
'django.contrib.contenttypes',
)
Step 3: Define your polymorphic model
from polymorphic.models import PolymorphicModel
class Block(PolymorphicModel):
code = models.CharField(max_length=20, unique=True)
Step 4: Define your polymorphic children models
class BlockOne(Block):
text = models.CharField(max_length=255)class BlockTwo(Block):
quote = models.CharField(max_length=255)
Step 5: Relate the polymorphic model to other models
from django.db import modelsclass BlockWrapper(models.Model):
block = models.ForeignKey(
Block,
on_delete=models.SET_NULL,
null=True,
blank=False,
)
Step 6: Query the models in the view
You can check what kind of object is the block you get. So for instance:
# get all block wrappers
block_wrappers = blockwrapper_inst.blockwrapperblock_set.all()# iterate through the block wrappers
for block_wrapper in block_wrappers: # now we can check what kind of block it's related to
block_inst = block_wrapper.block
if isinstance(block_inst, BlockOne):
# do stuff
elif isinstance(block_inst, BlockTwo):
# do other stuff
You can also use instance_of
or not_instance_of
for narrowing the result to specific subtypes:
Block.objects.instance_of(BlockOne)
Final notes
Taking this same example, how does this really work in the backend? When you’re getting a BlockOne or a BlockTwo instance, internally Django will have to perform an INNER JOIN to get all the parent’s data. So, as the documentation says, taking performance into account “While django-polymorphic makes subclassed models easy to use in Django, we still encourage to use them with caution”
Resources:
Happy codding! :)