My article Django behaves differently from other Polymorphic I wrote about how Django's Polymorphic Model is different from the so-called polymorphic model. This time, I'll explain how to implement ** polymorphic related ** in Django.
In the Rails Guide's Polymorphic Associations chapter, polymorphic related is implemented as follows.
class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
end
class Employee < ApplicationRecord
has_many :pictures, as: :imageable
end
class Product < ApplicationRecord
has_many :pictures, as: :imageable
end
The goal is to implement a Model that has the following ER-like attributes.
coontent_type
indicates which table is associated and ʻobject_id` indicates which record is associated.
from django.db import models
from django.contrib.contenttypes.models import ContentType
class Picture(models.Model):
object_id = models.IntegerField(db_index=True)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
file_name = models.CharField()
class Employee(models.Model):
name = models.CharField()
email = models.EmailField()
class Product(models.Model):
name = models.CharField()
price = models.IntegerField()
from django.db import models
from django.contrib.contenttypes.fields import GenericRelation, GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class Picture(models.Model):
object_id = models.IntegerField(db_index=True)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
file_name = models.CharField(max_length=256)
content_object = GenericForeignKey('content_type', 'object_id')
class Imageable(models.Model):
class Meta:
abstract = True
pictures = GenericRelation(Picture)
class Employee(Imageable):
name = models.CharField(max_length=256)
email = models.EmailField()
class Product(Imageable):
name = models.CharField(max_length=256)
price = models.IntegerField()
#migration
$ python manage.py makemigrations polymorphic_associations
$ python manage.py migrate polymorphic_associations
$ python manage.py shell
#Data creation
>>> from polymorphic_associations.models import Employee, Product
>>>
>>> employee = Employee(name='John', email='[email protected]')
>>> employee.save()
>>> employee.pictures.create(file_name='employee.jpg')
<Picture: Picture object (1)>
>>>
>>> product = Product(name='Desk', price=1000)
>>> product.save()
>>> product.pictures.create(file_name='product.jpg')
<Picture: Picture object (2)>
#Data acquisition
>>> employee.pictures.all()
<QuerySet [<Picture: Picture object (1)>]>
>>> employee.pictures.first().file_name
'employee.jpg'
>>>
>>> product.pictures.all()
<QuerySet [<Picture: Picture object (2)>]>
>>> product.pictures.first().file_name
'product.jpg'
#SQL confirmation
>>> str(employee.pictures.all().query)
'SELECT
"polymorphic_associations_picture"."id",
"polymorphic_associations_picture"."object_id",
"polymorphic_associations_picture"."content_type_id",
"polymorphic_associations_picture"."file_name"
FROM
"polymorphic_associations_picture"
WHERE
(
"polymorphic_associations_picture"."content_type_id" = 2
AND "polymorphic_associations_picture"."object_id" = 1
)'
>>>
>>> str(product.pictures.all().query)
'SELECT
"polymorphic_associations_picture"."id",
"polymorphic_associations_picture"."object_id",
"polymorphic_associations_picture"."content_type_id",
"polymorphic_associations_picture"."file_name"
FROM
"polymorphic_associations_picture"
WHERE
(
"polymorphic_associations_picture"."content_type_id" = 3
AND "polymorphic_associations_picture"."object_id" = 1
)'
You can see that the created data can identify the table and record by content_type_id
and ʻobject_id. This allows all tables with images to be implemented quickly by inheriting ʻImageable
. Also, by implementing image-related processing in Imageable, it is possible to prevent logic from being distributed to each model and service.
This source code is posted on Git.
Recommended Posts