Иерархия объектов «один к одному» с полем типа 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
)