Наследовать модель 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, работает, это ускользнуло от моего внимания, когда я пробежался по документации.

Также спасибо за понижение голосов за очень общий вопрос, который просил советов, а не точных решений (поскольку я хочу учиться, а не заставлять кого-то исправлять что-то за меня), довольно "повышающий настроение", если вы спросите меня...

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