132 lines
4.5 KiB
Python

import uuid
from pathlib import Path
from django.db import models
from django.db.models import Q
from django.db.models.functions import Lower
def product_image_upload_to(instance, filename):
return f"commerce/products/{instance.product_id}/original/{Path(filename).name}"
def product_thumbnail_upload_to(instance, filename):
return f"commerce/products/{instance.product_id}/thumbnails/{Path(filename).name}"
class ProductKind(models.TextChoices):
PRODUCT = "product", "Товар"
SERVICE = "service", "Услуга"
BUNDLE = "bundle", "Комплект"
class Product(models.Model):
"""Canonical sellable or stockable SKU used by every data source."""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
parent = models.ForeignKey(
"self",
on_delete=models.PROTECT,
null=True,
blank=True,
related_name="variants",
verbose_name="Родительский товар",
)
kind = models.CharField(
"Тип",
max_length=20,
choices=ProductKind.choices,
default=ProductKind.PRODUCT,
)
sku = models.CharField("Внутренний артикул", max_length=128, unique=True)
name = models.CharField("Название", max_length=255)
barcode = models.CharField("Штрихкод", max_length=64, blank=True, db_index=True)
description = models.TextField("Описание", blank=True)
is_active = models.BooleanField("Активен", default=True)
created_at = models.DateTimeField("Создано", auto_now_add=True)
updated_at = models.DateTimeField("Обновлено", auto_now=True)
class Meta:
verbose_name = "Товар"
verbose_name_plural = "Товары"
ordering = ["name", "sku"]
constraints = [
models.CheckConstraint(
condition=~Q(parent=models.F("id")),
name="commerce_product_not_own_parent",
),
models.UniqueConstraint(
Lower("sku"), name="commerce_product_sku_case_insensitive"
),
]
indexes = [
models.Index(
fields=["is_active", "name"], name="commerce_product_active_name"
),
]
def __str__(self):
return f"{self.sku} — {self.name}"
class ProductImage(models.Model):
"""Locally stored image imported from an external product source."""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
product = models.ForeignKey(
Product,
on_delete=models.CASCADE,
related_name="images",
verbose_name="Товар",
)
source = models.ForeignKey(
"commerce.SourceSystem",
on_delete=models.CASCADE,
related_name="product_images",
verbose_name="Источник",
)
external_id = models.CharField("Внешний ID", max_length=255)
file = models.ImageField(
"Оригинал",
upload_to=product_image_upload_to,
max_length=500,
)
thumbnail = models.ImageField(
"Миниатюра",
upload_to=product_thumbnail_upload_to,
max_length=500,
blank=True,
)
filename = models.CharField("Имя файла", max_length=255, blank=True)
title = models.CharField("Название", max_length=255, blank=True)
source_size = models.PositiveBigIntegerField("Размер в источнике", default=0)
source_updated_at = models.DateTimeField(
"Изменено в источнике",
null=True,
blank=True,
)
position = models.PositiveIntegerField("Позиция", default=0)
is_primary = models.BooleanField("Основное", default=False)
created_at = models.DateTimeField("Создано", auto_now_add=True)
updated_at = models.DateTimeField("Обновлено", auto_now=True)
class Meta:
verbose_name = "Изображение товара"
verbose_name_plural = "Изображения товаров"
ordering = ["position", "created_at"]
constraints = [
models.UniqueConstraint(
fields=["product", "source", "external_id"],
name="commerce_product_image_identity",
)
]
indexes = [
models.Index(
fields=["product", "is_primary", "position"],
name="commerce_product_image_order",
)
]
def __str__(self):
return self.title or self.filename or self.external_id