132 lines
4.5 KiB
Python
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
|