3
\$\begingroup\$

I have design a model for product apps. This is the first time I am trying to create a product's database schema and then model it in Django. My code handles the following:

  1. A product type can have multiple products and each product type can have certain attributes in common. Examples of product types can be clothing, accessories and bags.
  2. A product can have zero or more attributes, like color, size and material.
  3. Each attribute will have multiple values.
  4. A product can have multiple combination of variants and each variant has their own stock quantity and price.
from django.db import models
from django.utils.text import slugify
from mptt.managers import TreeManager
from mptt.models import MPTTModel

class ProductType(models.Model):
    name = models.CharField(max_length=150, help_text="type of product. ex - accessories, clothing")
    slug = models.SlugField(max_length=150, unique=True)
    is_shipping_required = models.BooleanField(default=True)

    class Meta:
        ordering = ("slug",)
        app_label = "product"

    def __str__(self):
        return self.name

class Category(MPTTModel):
    name = models.CharField(max_length=128)
    slug = models.SlugField(max_length=128)
    description = models.TextField(blank=True)
    # description_json = JSONField(blank=True, default=dict)
    parent = models.ForeignKey(
        "self", null=True, blank=True, related_name="children", on_delete=models.CASCADE
    )

    objects = models.Manager()
    tree = TreeManager()

    class Meta:
        verbose_name = "Category"
        verbose_name_plural = "Categories"

    def __str__(self):
        return self.name


class Product(models.Model):
    product_type = models.ForeignKey(
        ProductType, related_name="products", on_delete=models.CASCADE
    )
    name = models.CharField(max_length=128)
    slug = models.SlugField()
    description = models.TextField(blank=True)
    category = models.ForeignKey(
        Category,
        related_name="products",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )
    article_number = models.CharField(max_length=30)

    class Meta:
      verbose_name = "product"
      verbose_name_plural = "products"
      ordering = ("name",)
      constraints = [
          models.UniqueConstraint(
              fields=["article_number", "slug"], name="unique article number and slug")
      ]

    def __str__(self):
        return f'{self.name} - {self.article_number}'

class Attribute(models.Model):
  name = models.CharField(max_length=200, blank=True)
  description = models.TextField(blank=True)

  def __str__(self):
    return self.name


class AttributeValue(models.Model):
  attribute = models.ForeignKey(Attribute, on_delete=models.CASCADE)
  value = models.CharField(max_length=200, blank=True)
  description = models.TextField(blank=True)

  def __str__(self):
    return self.value


class ProductAttributeValue(models.Model):
  product = models.ForeignKey(Product, on_delete=models.CASCADE)
  attribute_value = models.ForeignKey(AttributeValue, on_delete=models.CASCADE)


class ProductTypeAttributeValue(models.Model):
  product_type = models.ForeignKey(ProductType, on_delete=models.CASCADE)
  attribute_value = models.ForeignKey(AttributeValue, on_delete=models.CASCADE)


class ProductVariant(models.Model):
  sku = models.CharField(max_length=255, unique=True)
  name = models.CharField(max_length=255, blank=True)
  product = models.ForeignKey(Product, on_delete=models.CASCADE)
  track_inventory = models.BooleanField(default=True)

  def __str__(self):
    return self.sku

class ProductVariantValues(models.Model):
  product = models.ForeignKey(Product, on_delete=models.CASCADE)
  variant = models.ForeignKey(ProductVariant, on_delete=models.CASCADE)
  value = models.ForeignKey(ProductAttributeValue, on_delete=models.CASCADE)
  stock = models.PositiveIntegerField(default=0)
  price = models.DecimalField(decimal_places=2, max_digits=6)




class CollectionProduct(models.Model):
    collection = models.ForeignKey(
        "Collection", related_name="collectionproduct", on_delete=models.CASCADE
    )
    product = models.ForeignKey(
        Product, related_name="collectionproduct", on_delete=models.CASCADE
    )

    class Meta:
        unique_together = (("collection", "product"),)

    def get_ordering_queryset(self):
        return self.product.collectionproduct.all()


class Collection(models.Model):
    name = models.CharField(max_length=250, unique=True)
    slug = models.SlugField(max_length=255, unique=True)
    products = models.ManyToManyField(
        Product,
        blank=True,
        related_name="collections",
        through=CollectionProduct,
        through_fields=("collection", "product"),
    )
    description = models.TextField(blank=True)

    class Meta:
        ordering = ("slug",)

    def __str__(self):
        return self.name
    

Due to my lack of experience with databases I am not quite sure if the design is sound. Can anyone verify if there is any room for improvement for the relationships between product, product variants and attributes? I especially would like to see how how it can be designed in a better way. For example relationships, like ForeignKey or ManyToManyField, normalization, query complexity and scalability.

Update

I have this on repl as well

https://repl.it/@tushantk/HumiliatingInterestingMarketing#product/models.py

\$\endgroup\$
  • \$\begingroup\$ Please include your imports \$\endgroup\$ – Reinderien Jun 26 at 20:35
  • \$\begingroup\$ @Reinderien. Thanks for your comment. I have included the import's and also the link where I have this code running. \$\endgroup\$ – Serenity Jun 27 at 0:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.