add: модели (draft 09)

This commit is contained in:
2026-06-02 16:06:29 +03:00
parent 61be28786b
commit 76e0350688

View File

@@ -31,7 +31,7 @@ class TbImage(models.Model):
help_text='Файл изображения, загруженный через django_filer.',
)
l_source = models.CharField(
l_img_source = models.CharField(
max_length=10,
choices=ImageSource.choices,
default=ImageSource.MANUAL_UPLOAD,
@@ -40,7 +40,7 @@ class TbImage(models.Model):
' Discogs), предоставлен продавцом и т.д.',
)
l_reality = models.CharField(
l_img_reality = models.CharField(
max_length=10,
choices=ImageReality.choices,
default=ImageReality.ABSTRACT,
@@ -48,7 +48,7 @@ class TbImage(models.Model):
help_text='Реальная фотография товара или картинка из внешнего источника?',
)
s_source_url = models.URLField(
s_img_src_url = models.URLField(
blank=True,
null=True,
verbose_name='URL источника',
@@ -56,7 +56,7 @@ class TbImage(models.Model):
)
# Порядок вывода
i_sort_order = models.IntegerField(
i_img_sort = models.IntegerField(
default=0,
db_index=True,
verbose_name='Cортировка',
@@ -65,7 +65,7 @@ class TbImage(models.Model):
)
# Доверие данным (для парсеров и API)
f_confidence_score = models.FloatField(
f_img_confidence_score = models.FloatField(
null=True,
blank=True,
default=None,
@@ -73,7 +73,7 @@ class TbImage(models.Model):
help_text='0.0 - 1.0, насколько уверены, что это правильное изображение',
)
# Авторские права
s_copyright = models.CharField(
s_img_copyright = models.CharField(
max_length=255,
blank=True,
default='',
@@ -82,11 +82,11 @@ class TbImage(models.Model):
)
# Timestamps
t_created = models.DateTimeField(
t_img_created = models.DateTimeField(
auto_now_add=True,
verbose_name='Дата добавления',
)
t_updated = models.DateTimeField(
t_img_updated = models.DateTimeField(
auto_now=True,
verbose_name='Дата обновления',
)
@@ -94,7 +94,7 @@ class TbImage(models.Model):
class Meta:
verbose_name = 'Изображение'
verbose_name_plural = 'Изображения'
ordering = ('i_sort_order', 't_created')
ordering = ('i_img_sort', 't_img_created')
# ============================================================================
@@ -114,7 +114,8 @@ class TbArtist(models.Model):
null=True,
default='',
verbose_name='Исполнитель (типографированно в HTML)',
help_text='С сохранением типографирования и спецсимволов.',
help_text='С сохранением типографирования и спецсимволов (всякие "метки" и иконки, типа "Иноагент",'
' тоже можно заверстать сюда.',
)
k_artist_to_image = models.OneToOneField(
TbImage,
@@ -126,6 +127,13 @@ class TbArtist(models.Model):
help_text='Изображение исполнителя, например, логотип группы или фотография. Если указано, будет'
' отображаться рядом с именем исполнителя.',
)
s_artist_article_html = models.TextField(
blank=True,
null=True,
default='',
verbose_name='Статья',
help_text='Статья об исполнителе (типографированно в HTML)',
)
s_slug = models.SlugField(
max_length=64,
verbose_name='Слаг',
@@ -137,11 +145,11 @@ class TbArtist(models.Model):
verbose_name='Варианты написания в источниках',
help_text='Список вариантов: ["The Beatles", "Beatles", "Beatles, The"]',
)
t_created = models.DateTimeField(
t_artist_created = models.DateTimeField(
auto_now_add=True,
verbose_name="Дата создания",
)
t_updated = models.DateTimeField(
t_artist_updated = models.DateTimeField(
auto_now=True,
verbose_name="Дата обновления",
)
@@ -171,7 +179,6 @@ class TbItem(models.Model):
verbose_name='Исполнители',
help_text="Один или несколько для коллабораций",
)
s_item_title = models.CharField(
max_length=255, blank=False, null=False, default='',
# db_index=True,
@@ -179,7 +186,6 @@ class TbItem(models.Model):
help_text='Название товара (релиза), как указано на обложке. Например: <tt>Abbey Road</tt>'
' или <tt>TDK, CDing I, 90</tt>.',
)
s_item_title_html = models.TextField(
blank=True,
null=True,
@@ -187,7 +193,6 @@ class TbItem(models.Model):
verbose_name='Название (типографированно)',
help_text='С HTML-тегами для типографирования',
)
s_item_to_image = models.OneToOneField(
TbImage,
on_delete=models.SET_NULL,
@@ -198,6 +203,15 @@ class TbItem(models.Model):
help_text='Изображение товара из каталога, например, обложка альбома. Если указано, будет отображаться'
' рядом с названием товара.',
)
s_item_article_html = models.TextField(
blank=True,
null=True,
default='',
verbose_name='Статья',
help_text='Статья о релизе, например, описание из Википедии или Discogs (а также список композиций,'
' исполнители и т.п.). Для товара это может быть техническое описание и характеристики.'
' Текст сверстан в HTML с сохранением типографирования и спецсимволов.',
)
s_item_slug = models.SlugField(
blank=True,
null=True,
@@ -205,7 +219,6 @@ class TbItem(models.Model):
verbose_name='Слаг',
help_text='Слаг для товара, формируемый на основе названия товара (релиза)',
)
# Год выпуска (важно для коллекционеров)
s_item_date = models.CharField(
max_length=10,
@@ -218,14 +231,12 @@ class TbItem(models.Model):
' релиза, чем t_release_date, так как может содержать неполную дату и/или текстовую информацию.'
' Срабатывает только если t_release_date не указано.'
)
t_item_date = models.DateField(
blank=True,
null=True,
verbose_name='Дата релиза',
help_text='Полная дата если известна, например: 1969-09-26. Если точно известен.',
)
i_discogs_master_id = models.IntegerField(
blank=True,
null=True,
@@ -233,14 +244,12 @@ class TbItem(models.Model):
verbose_name='ID на мастер-релиз Discogs',
help_text='Уникальный идентификатор мастер-релиза на Discogs, если он там есть. Например: <tt>306323</tt>',
)
# Метаданные релиза (страна, жанр, количество треков и т.д.)
j_item_metadata = models.JSONField(
default=dict, blank=True, null=True,
verbose_name='Дополнительные данные',
help_text='Дополнительные данные о релизе в виде JSON-словаря. Сюда же включены варианты написания релиза в источниках',
help_text='Дополнительные данные и метаданные релиза (страна, жанр, количество треков и т.д.) или товавра'
' в виде JSON-словаря. Сюда же включены варианты написания релиза в источниках',
)
t_item_created = models.DateTimeField(
auto_now_add=True,
verbose_name="Дата создания",
@@ -262,7 +271,6 @@ class TbItem(models.Model):
# ============================================================================
# ЛЕЙБЛЫ (производители релизов)
# ============================================================================
class TbLabel(models.Model):
"""
Лейбл или издатель релиза.
@@ -270,7 +278,6 @@ class TbLabel(models.Model):
для кассет под запись это: TDK, AXIA, Maxell, JVC ...
для hi-fi это: Sony, Pioneer, Technics, Marantz ...
"""
s_label = models.CharField(
max_length=128,
blank=False,
@@ -279,7 +286,13 @@ class TbLabel(models.Model):
verbose_name='Название лейбла',
help_text='Например: "Sony Records" или "Мелодия"',
)
s_label_article_html = models.TextField(
blank=True,
null=True,
default='',
verbose_name='Статья',
help_text='Статья о лейбле (типографированно в HTML)',
)
s_label_slug = models.SlugField(
max_length=64,
blank=False,
@@ -287,20 +300,18 @@ class TbLabel(models.Model):
default='',
verbose_name='Слаг',
)
j_label_metadata = models.JSONField(
default=dict,
blank=True,
null=True,
verbose_name='Дополнительные данные',
verbose_name='Метаданные',
help_text='JSON: страна лейбла, официальный сайт и т.д.',
)
t_created = models.DateTimeField(
t_label_created = models.DateTimeField(
auto_now_add=True,
verbose_name="Дата создания",
)
t_updated = models.DateTimeField(
t_tabel_updated = models.DateTimeField(
auto_now=True,
verbose_name="Дата обновления",
)
@@ -320,44 +331,45 @@ class TbLabel(models.Model):
class TbSeller(models.Model):
"""Продавец или магазин, который продаёт товары."""
class SellerType(models.TextChoices):
SELLER = 'seller', 'Продавец'
LABEL = 'label', 'Лейбл (издатель)'
DIY = 'diy', 'Самиздат группы'
CROWD = 'crowdfunding', 'Краудфандинг'
OTHER = '++', 'Другое'
s_seller = models.CharField(
max_length=32, blank=False, null=False, default='',
verbose_name='Название продавца',
help_text='Название продавца или магазина, например: <tt>Клюква Рекодс</tt>',
)
s_seller_article_html = models.TextField(
blank=True,
null=True,
default='',
verbose_name='Статья',
help_text='Статья о продавце (типографированно в HTML)',
)
s_seller_slug = models.SlugField(
max_length=32, blank=False, null=False, default='',
verbose_name='Слаг',
help_text='Слаг для продавца, формируемый на основе названия продавца',
)
l_seller_type = models.CharField(
max_length=9,
default=SellerType.SELLER,
choices=SellerType.choices,
verbose_name='Тип продавца',
)
j_seller_metadata = models.JSONField(
default=dict, blank=True, null=True,
verbose_name='Дополнительные данные',
help_text='Дополнительные данные о продавце в виде JSON-словаря. Телефон, email, адрес, ссылка на сайт и т.д.',
)
t_created = models.DateTimeField(
t_seller_created = models.DateTimeField(
auto_now_add=True,
verbose_name="Дата создания",
)
t_updated = models.DateTimeField(
t_seller_updated = models.DateTimeField(
auto_now=True,
verbose_name="Дата обновления",
)
@@ -373,7 +385,6 @@ class TbSeller(models.Model):
# ============================================================================
# ПРЕДЛОЖЕНИЯ / ОФФЕРЫ
# ============================================================================
class TbOffer(models.Model):
"""
Конкретное предложение от продавца.
@@ -436,13 +447,13 @@ class TbOffer(models.Model):
# Изображения товара (одна картинка может быть у многих офферов, а офер иметь много картинок)
# M2M связь для удобства в админке (filter_horizontal)
# Порядок картинок определяется полем i_sort_order в TbImage
# Порядок картинок определяется полем i_img_sort в TbImage
k_offer_to_image = models.ManyToManyField(
TbImage,
blank=True,
related_name='image_to_offer',
verbose_name='Изображения',
help_text='Картинки этого товара. Порядок определяется полем i_sort_order в ка<D0BA><D0B0>тинке.',
help_text='Картинки этого товара. Порядок определяется полем i_img_sort в ка<D0BA><D0B0>тинке.',
)
# Характеристики
@@ -461,6 +472,14 @@ class TbOffer(models.Model):
help_text='Основной формат носителя (пластинка, CD, кассета и т.п.)'
)
d_offer_date_release = models.DateField(
blank=True,
null=True,
default=None,
verbose_name='Дата релиза',
help_text='Дата релиза, если она известна (например, дата выпуска переиздания)',
)
i_offer_discogs_id = models.IntegerField(
blank=True,default=0,
verbose_name='ID на релиз Discogs',
@@ -502,20 +521,20 @@ class TbOffer(models.Model):
)
i_offer_discount_to_daily_sale = models.IntegerField(
blank=True, default=0,
blank=True,
default=0,
db_index=True,
verbose_name='Скидка',
help_text='Процент скидки, для ежедневной распродажи',
help_text='Процент возможной скидки, если участвует в "ежедневной распродаже" или акции. Если указано'
' <tt>0</tt> то данное предложение не может участвовать в распродажах, спецпредложениях и акциях',
)
# Комментарии
s_offer_comment = models.TextField(
s_offer_comment_html = models.TextField(
blank=True,
default='',
verbose_name='Доп.инфо',
help_text='Дополнительная информация или комментарий к предложению от продавца, например:'
' <tt>"Пластинка запаяна в целлофан, угол обложки замят."</tt>.',
help_text='Дополнительная информация или комментарий к предложению от продавца с сохранением'
' типографирования и спецсимволов.',
)
j_offer_metadata = models.JSONField(
# Метаданные оффера (сырые данные из источника, координаты в Excel и т.д.)
default=dict, null=True,
@@ -549,7 +568,7 @@ class TbSource(models.Model):
"""
Источник данных, из которого был импортирован оффер.
Например, это может быть Excel-файл от продавца или издателя, CSV-файл, URL страницы с данными
(например, HTML-страница с каталогом товаров) и т.д.
(например, HTML-страница с каталогом товаров) и т.д.
"""
class SourceType(models.TextChoices):
EXCEL = 'excel', 'Excel-файл от продавца или издателя'
@@ -579,7 +598,7 @@ class TbSource(models.Model):
verbose_name='Продавец',
)
l_currency = models.CharField(
l_source_currency = models.CharField(
max_length=3,
choices=Currency.choices,
default=Currency.RUB,
@@ -655,13 +674,11 @@ class TbSource(models.Model):
# ============================================================================
# ИСТОРИЯ ИЗМЕНЕНИЙ ОФФЕРОВ
# ============================================================================
class TbOfferHistory(models.Model):
"""
История изменений оффера (снапшот цены, количества, наличия).
Создаётся при каждом импорте, если что-то изменилось.
"""
k_history_to_offer = models.ForeignKey(
TbOffer,
on_delete=models.CASCADE,