Наследовать модель Django от другой модели или просто сделать отдельные модели?
В принципе, я подумываю о том, чтобы начать проект электронной коммерции, у меня есть планы, идеи и все такое, но есть одна вещь, с которой я не могу справиться - система Products.
Допустим, у вас есть одна модель, называемая "Телефоны", которая имеет: размер экрана, оперативную память и т.д... и затем у вас есть другая модель, называемая "Ноутбуки", которая имеет: размер экрана, оперативную память и клавиатуру (QWERTZ, QWERTY и т.д.).
Я думал о создании основной модели, которая имеет некоторые основные поля, такие как Имя и Цена, и я могу сделать это в Python, но Django работает по-другому, я полагаю?
Моя основная задача - иметь возможность просто выбрать, хочу ли я зарегистрировать "Ноутбук" или "Телефон", без ненужных полей (например: тип клавиатуры для телефонов, или задняя камера для ноутбуков).
Что было бы лучшей практикой для этого? Имеет ли мой вопрос смысл? Или я должен просто создать поля и оставить их как есть, оставив ненужные пустыми? Потому что в больших масштабах, было бы глупо иметь "RAM" и "Тип клавиатуры" и "Задняя камера mpx", когда вы просто хотите загрузить, скажем, кружку или что-то еще...
Следует ли мне сделать отдельные модели для каждой из них? Но тогда как я могу переместить результаты запросов друг в друга (например, вы ищете "Xiaomi", и он выдает телефоны, ноутбуки, велосипеды, пылесосы и т.д.)...
Посмотрите на абстрактные классы. То, что вы описываете, объясняется в официальной документации: https://docs.djangoproject.com/en/4.1/topics/db/models/#abstract-base-classes
Я не уверен, что является плохой практикой, но я подкину вам несколько потенциальных идей о том, как вы могли бы это сделать:
#1 Абстрактная модель
class BaseProduct(models.Model):
name = models.CharField(max_length=200)
cost = models.DecimalField(max_digits=10, decimal_places=2, default=0)
class Meta:
abstract = True
# all models below will have a name + cost attibute
# django might even throw them in the save table in the backend (not 100% sure)
class Phone(BaseProduct):
rear_camera_mpx = models.CharField(max_length=200)
# ..etc
class Laptop(BaseProduct):
ram = models.CharField(max_length=200)
# ..etc
###############################
# Example Query:
Laptop.objects.filter(name__icontains='MSI', ram='8gb')
# Filter Multiple Products
from itertools import chain
queryset_chain = chain(
Phone.objects.filter(name__icontains=query),
Laptop.objects.filter(name__icontains=query),
)
for i in queryset_chain
if type(i) == Laptop:
print(i.ram)
# elif
# .. etc
#2 Внешний ключ, указывающий назад из атрибутов
class BaseProduct(models.Model):
name = models.CharField(max_length=200)
cost = models.DecimalField(max_digits=10, decimal_places=2, default=0)
# could add a type
product_type = models.CharField(max_length=2, choices=PRODUCTTYPE_CHOICES, default='NA')
# Extra attributes, points back to base
class Phone(models.Model):
product = models.ForeignKey(BaseProduct, on_delete=models.PROTECT)
rear_camera_mpx = models.CharField(max_length=200)
# ..etc
class Laptop(models.Model):
product = models.ForeignKey(BaseProduct, on_delete=models.PROTECT)
ram = models.CharField(max_length=200)
# ..etc
###############################
# Example Query:
Laptop.objects.filter(product__name__icontains='MSI', ram='8gb')
# Search All Products
BaseProduct.objects.filter(name__icontains='MSI')
# then when you click on, use type to grab the correct full class based on "product_type"
if product_type == '01':
return Laptop.objects.filter(product__pk=clickedOnDetailPk).first()
#3 GenericForeign Key Pointing to Attributes
- Примечание: Я нахожу общие клавиши очень неуклюжими и трудными в использовании (это только я так думаю)
class BaseProduct(models.Model):
name = models.CharField(max_length=200)
cost = models.DecimalField(max_digits=10, decimal_places=2, default=0)
# could add a type
product_type = models.CharField(max_length=2, choices=PRODUCTTYPE_CHOICES, default='NA')
# Below the mandatory fields for generic relation
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
# Extra attributes, points back to base
class Phone(models.Model):
rear_camera_mpx = models.CharField(max_length=200)
# ..etc
class Laptop(models.Model):
ram = models.CharField(max_length=200)
# ..etc
###############################
# Example Query:
# Search All Products
l = BaseProduct.objects.filter(name__icontains='MSI')
for i in l:
print(i.name, i.cost)
print('Generic Key Obj:', i.content_object)
print('Generic Key PK:', i.content_id)
print('Generic Key Type:', i.content_type) # is number / can change if re-creating db (not fun)
if i.product_type == '01': # Laptop Type / could also go by content_type with some extra logic
print('RAM:', i.content_object.ram)
# to do stuff like \/ you need extra stuff (never sat down to figure this out)
BaseProduct.objects.filter(content_object__ram='8gb')
#4 Json-поля + запихнуть все в одну таблицу
- Требуется более новая версия DBs + Django .
- Это может быть абстрагировано с помощью
proxy
моделей + менеджеров довольно безумно. Я сделал это для таблицы для аналогичного случая использования, только представьте себе создание ноутбука & включая все компоненты, которые сами являются продуктами :D. Не уверен, что это плохая практика, это ALOT пользовательских вещей, но мне очень понравились мои результаты. .
class BaseProduct(models.Model):
name = models.CharField(max_length=200)
cost = models.DecimalField(max_digits=10, decimal_places=2, default=0)
# could add a type
product_type = models.CharField(max_length=2, choices=PRODUCTTYPE_CHOICES, default='NA')
# cram all the extra stuff as JSON
attr = models.JSONField(null=True)
###############################
# laptop search
l = BaseProduct.objects.filter(name__icontains='MSI', attr__ram='8gb')
for i in l:
print(i.name, i.cost, i.attr['ram'])
Всего
В целом, я думаю, что #1 или #2 - это то, что нужно.
Если только вы не хотите разгуляться и практически все написать, формы, админки и т.д., тогда идите #4
"Абстрактная модель", как упомянул Nealium, работает, это ускользнуло от моего внимания, когда я пробежался по документации.
Также спасибо за понижение голосов за очень общий вопрос, который просил советов, а не точных решений (поскольку я хочу учиться, а не заставлять кого-то исправлять что-то за меня), довольно "повышающий настроение", если вы спросите меня...