Иерархия объектов «один к одному» с полем типа enum

У меня есть сайт wagtail, и я хочу создать иерархию объектов по принципу «один к одному», но с несколькими вариантами. В принципе, я хочу, чтобы настройка базы данных выглядела следующим образом:

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),
    ...
);

Создать обычные отношения «один к одному» с установкой ParentalKey в производной модели довольно просто. Однако я хочу также иметь поле типа enum в родительской модели, чтобы проверять, какой тип продукта у нас есть, так что я могу сделать что-то подобное в моем ProductsView:

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

Я знаю, что при отношениях «один к одному» в wagtail я мог бы просто вызвать product.shirt, что вызвало бы исключение, если товар не является рубашкой. Но мне кажется очень громоздким иметь вложенные блоки try-catch, если у меня много разных типов товаров...

Есть ли идеи, как решить эту проблему в стиле django/wagtail?

django допускает multi-table inheritance, что означает, что каждый подкласс получает свою собственную таблицу, сохраняя при этом однозначную связь с родительской моделью. Вы также можете использовать фреймворк ContentType для динамического связывания дочерних моделей

сначала создайте модель продукта.

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})"

теперь каждый тип продукта расширяет Product используя многостолбцовое наследование django, каждая дочерняя модель будет иметь однозначную связь с 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)

Переопределите метод сохранения, чтобы обеспечить автоматическую установку content_type.

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

извлеките экземпляры данных:


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

Надеюсь, это поможет!

Есть ли конкретная необходимость в использовании модели "продукт"? Может быть, в вашем случае подойдет абстрактная модель?

class products(models.Model):  # COMM0N 
    product_name = models.CharField(max_length=100) 
    product_id = models.PositiveSmallIntegerField()
    product_desc = models.CharField(max_length=512) 
    product_type = "generic" # will be overwritten by the inheriting model.

    @property
    def product_type(self): # this is untested, might not work
       return self._meta.model_name

    [. other śhared fields and functions]
  
    class Meta: 
        abstract = True`




class shirt(product):
    class Size(models.IntegerChoices):
        S = 1, "SMALL"
        M = 2, "MEDIUM"
        L = 3, "LARGE"
        # (...)

    size = models.PositiveSmallIntegerField(
        choices=Size.choices,
        default=Size.S
    
    )
Вернуться на верх