Wagtail one-to-one object hierarchy with enum type field

I have a wagtail site and I want to create an object hierarchy in a one-to-one matter, but with multiple options. Basically, I want that the database setup look slike this:

CREATE TABLE products (
    id PRIMARY KEY,
    product_type VARCHAR(10) CHECK (product_type IN ('BOOK', 'SHIRT', ...)),
    product_name VARCHAR(255),
    product_description TEXT,
    ...
);
CREATE TABLE product_shirts (
    id PRIMARY KEY,
    product_id integer REFERENCES products (id),
    size varchar(255),
    ...
);
CREATE TABLE product_books (
    id PRIMARY KEY,
    product_id integer REFERENCES products (id),
    author varchar(255),
    ...
);

It is pretty straigt forward to create a regular one-to-one relationship with setting ParentalKey in the derived model. However, I want to also have an enum-type field in the parent model to check which product type we have, so that I can do something like that in my ProductsView:

if object.product_type == 'SHIRT':
    # display additional shirt attributes
elif object.product_type == 'BOOK':
    # display book attributes
else:
    # unknown type, should not happen

I know, that with a one-to-one relationship in wagtail I could just simply call product.shirtwhich would raise an exception, if the product is not a shirt. But it seems very cumbersome to have nested try-catch blocks if I have many different product types...

Any better idea to solve this in a django/wagtail style?

django allows multi-table inheritance, meaning each subclass gets its own table while maintaining a one-to-one relationship with the parent model. You can also use the ContentType framework to link child models dynamically

first of you make a product model.

from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey

class Product(models.Model):
    PRODUCT_TYPES = [
        ('BOOK', 'Book'),
        ('SHIRT', 'Shirt'),
        #add as per your requirements
    ]

    product_name = models.CharField(max_length=255)
    product_description = models.TextField()
    product_type = models.CharField(max_length=10, choices=PRODUCT_TYPES)
    
    # use the framework to form the relation
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, blank=True)
    object_id = models.PositiveIntegerField(null=True, blank=True)
    specific_product = GenericForeignKey('content_type', 'object_id')

    def get_specific_product(self):
       # it returns the specific child product model instance
        if self.specific_product:
            return self.specific_product
        return self

    def __str__(self):
        return f"{self.product_name} ({self.product_type})"

now each product type extends Product using django’s multi-table inheritance each child model will have a one-to-one relationship with Product.

class Book(Product):
    author = models.CharField(max_length=255)

    def save(self, *args, **kwargs):
        if not self.pk:
            self.product_type = 'BOOK'  # Ensure type is set automatically
        super().save(*args, **kwargs)

class Shirt(Product):
    size = models.CharField(max_length=50)

    def save(self, *args, **kwargs):
        if not self.pk:
            self.product_type = 'SHIRT'
        super().save(*args, **kwargs)

Override the save method to ensure that content_type is automatically set.

def save(self, *args, **kwargs):
    if not self.content_type:
        self.content_type = ContentType.objects.get_for_model(self)
    super().save(*args, **kwargs)

retrive the data instances:


product = Product.objects.get(id=some_id)
specific_product = product.get_specific_product()

Hope it helps!

Вернуться на верх