260 lines
8.5 KiB
Python
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}"
|