Files
ai_web/backend/apps/products/models.py
2026-02-04 15:25:04 +08:00

260 lines
8.5 KiB
Python

"""
Product models for categories, websites, products and prices.
"""
from django.db import models
from django.conf import settings
class Category(models.Model):
"""Product categories for navigation."""
id = models.AutoField(primary_key=True)
name = models.CharField('分类名称', max_length=100)
slug = models.CharField('Slug', max_length=100, unique=True)
description = models.TextField('描述', blank=True, null=True)
icon = models.CharField('图标', max_length=100, blank=True, null=True)
parent = models.ForeignKey(
'self',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='children',
verbose_name='父分类'
)
sort_order = models.IntegerField('排序', default=0)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
class Meta:
db_table = 'categories'
verbose_name = '分类'
verbose_name_plural = '分类'
ordering = ['sort_order', 'id']
indexes = [
models.Index(fields=["parent", "sort_order"]),
]
def __str__(self):
return self.name
class Website(models.Model):
"""External shopping/card websites."""
id = models.AutoField(primary_key=True)
name = models.CharField('网站名称', max_length=200)
url = models.CharField('网址', max_length=500)
logo = models.TextField('Logo', blank=True, null=True)
description = models.TextField('描述', blank=True, null=True)
category = models.ForeignKey(
Category,
on_delete=models.CASCADE,
related_name='websites',
verbose_name='分类'
)
rating = models.DecimalField('评分', max_digits=2, decimal_places=1, default=0)
is_verified = models.BooleanField('是否认证', default=False)
sort_order = models.IntegerField('排序', default=0)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
updated_at = models.DateTimeField('更新时间', auto_now=True)
class Meta:
db_table = 'websites'
verbose_name = '网站'
verbose_name_plural = '网站'
ordering = ['sort_order', 'id']
indexes = [
models.Index(fields=["category", "is_verified"]),
]
def __str__(self):
return self.name
class Product(models.Model):
"""Products for price comparison."""
class Status(models.TextChoices):
PENDING = 'pending', '待审核'
APPROVED = 'approved', '已通过'
REJECTED = 'rejected', '已拒绝'
id = models.AutoField(primary_key=True)
name = models.CharField('商品名称', max_length=300)
description = models.TextField('描述', blank=True, null=True)
image = models.TextField('图片', blank=True, null=True)
images = models.JSONField('图片列表', default=list, blank=True)
category = models.ForeignKey(
Category,
on_delete=models.CASCADE,
related_name='products',
verbose_name='分类'
)
status = models.CharField(
'审核状态',
max_length=20,
choices=Status.choices,
default=Status.PENDING
)
submitted_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='submitted_products',
verbose_name='提交者'
)
reject_reason = models.TextField('拒绝原因', blank=True, null=True)
reviewed_at = models.DateTimeField('审核时间', blank=True, null=True)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
updated_at = models.DateTimeField('更新时间', auto_now=True)
class Meta:
db_table = 'products'
verbose_name = '商品'
verbose_name_plural = '商品'
indexes = [
models.Index(fields=["category", "created_at"]),
models.Index(fields=["status", "created_at"]),
models.Index(fields=["submitted_by", "status"]),
]
def __str__(self):
return self.name
class ComparisonTag(models.Model):
"""Admin-curated comparison tags for products."""
id = models.AutoField(primary_key=True)
name = models.CharField('标签名称', max_length=120)
slug = models.CharField('Slug', max_length=120, unique=True)
description = models.TextField('描述', blank=True, null=True)
cover_image = models.TextField('封面图', blank=True, null=True)
icon = models.CharField('图标', max_length=120, blank=True, null=True)
sort_order = models.IntegerField('排序', default=0)
is_active = models.BooleanField('是否启用', default=True)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
updated_at = models.DateTimeField('更新时间', auto_now=True)
class Meta:
db_table = 'comparison_tags'
verbose_name = '比价标签'
verbose_name_plural = '比价标签'
ordering = ['sort_order', 'id']
indexes = [
models.Index(fields=["is_active", "sort_order"]),
]
def __str__(self):
return self.name
class ComparisonTagItem(models.Model):
"""Products included in a comparison tag."""
id = models.AutoField(primary_key=True)
tag = models.ForeignKey(
ComparisonTag,
on_delete=models.CASCADE,
related_name='items',
verbose_name='比价标签',
)
product = models.ForeignKey(
Product,
on_delete=models.CASCADE,
related_name='comparison_items',
verbose_name='商品',
)
sort_order = models.IntegerField('排序', default=0)
is_pinned = models.BooleanField('置顶', default=False)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
class Meta:
db_table = 'comparison_tag_items'
verbose_name = '比价标签商品'
verbose_name_plural = '比价标签商品'
ordering = ['-is_pinned', 'sort_order', 'id']
unique_together = ['tag', 'product']
indexes = [
models.Index(fields=["tag", "sort_order"]),
]
def __str__(self):
return f"{self.tag.name} - {self.product.name}"
class ProductPrice(models.Model):
"""Product prices from different websites."""
id = models.AutoField(primary_key=True)
product = models.ForeignKey(
Product,
on_delete=models.CASCADE,
related_name='prices',
verbose_name='商品'
)
website = models.ForeignKey(
Website,
on_delete=models.CASCADE,
related_name='product_prices',
verbose_name='网站'
)
price = models.DecimalField('价格', max_digits=10, decimal_places=2)
original_price = models.DecimalField(
'原价',
max_digits=10,
decimal_places=2,
blank=True,
null=True
)
currency = models.CharField('货币', max_length=10, default='CNY')
url = models.CharField('商品链接', max_length=500)
in_stock = models.BooleanField('是否有货', default=True)
last_checked = models.DateTimeField('最后检查时间', auto_now=True)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
updated_at = models.DateTimeField('更新时间', auto_now=True)
class Meta:
db_table = 'productPrices'
verbose_name = '商品价格'
verbose_name_plural = '商品价格'
unique_together = ['product', 'website']
indexes = [
models.Index(fields=["product", "website", "last_checked"]),
]
def __str__(self):
return f"{self.product.name} - {self.website.name}: {self.price}"
class ProductPriceHistory(models.Model):
"""Historical price records for products."""
id = models.AutoField(primary_key=True)
product = models.ForeignKey(
Product,
on_delete=models.CASCADE,
related_name='price_history',
verbose_name='商品',
)
website = models.ForeignKey(
Website,
on_delete=models.CASCADE,
related_name='price_history',
verbose_name='网站',
)
price = models.DecimalField('价格', max_digits=10, decimal_places=2)
recorded_at = models.DateTimeField('记录时间', auto_now_add=True)
class Meta:
db_table = 'product_price_history'
verbose_name = '商品价格历史'
verbose_name_plural = '商品价格历史'
ordering = ['-recorded_at']
indexes = [
models.Index(fields=["product", "website", "recorded_at"]),
]
def __str__(self):
return f"{self.product.name} - {self.website.name}: {self.price}"