add: перенесены и расширены специфичные для проекта настройки-константы в constants.py и добавлены миграции
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 6.0.5 on 2026-10-11 18:23
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('frontend', '0005_remove_tboffer_s_offer_skip32_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='tbarticle',
|
||||
name='l_article_type',
|
||||
field=models.CharField(blank=True, choices=[('artist', 'Artist: артист, группа или бренд'), ('style', 'Style: музыкальный стиль'), ('item', 'Item: Альбом, релиз или товар (кассета, hifi, аксессуар)'), ('label', 'Label: Лейбл, издатель или компания'), ('offer', 'Offer: конкретное предложение от продавца'), ('seller', 'Seller: продавец или магазин'), ('blog', 'Blog: Новость или блог'), ('action', 'Action: Спецпредложение, акция, распродажа и т.п.'), ('adv', 'Adv: Реклама или баннер'), ('HUB', 'HUB: СТАТЬЯ-ХАБ (использует DSL для отображения контента)'), ('info', 'Info: Текстовый контент (для FAQ, условий конфиденциальности, правил/соглашений и т.д.)'), ('--', '?¿?: Другое')], db_index=True, default='--', max_length=7, verbose_name='Тип статьи'),
|
||||
),
|
||||
]
|
||||
+37
-107
@@ -172,15 +172,34 @@ from filer.fields.image import FilerImageField
|
||||
from filer.fields.file import FilerFileField
|
||||
from frontend.utils import make_slug, update_synonyms_in_metadata, create_or_get_related_article
|
||||
from frontend.utils_validators import validate_and_raise_for_duplicates
|
||||
from lpon_site.constants import (
|
||||
KEY_IMAGE_TYPE,
|
||||
VALUE_IMAGE_ABSTRACT,
|
||||
KEY_IMAGE_FROM,
|
||||
VALUE_IMAGE_FROM_USER,
|
||||
KEY_IMAGE_URL,
|
||||
KEY_IMAGE_NOTE,
|
||||
KEY_OFFER_NOTE,
|
||||
KEY_OFFER_ALL_MEDIA,
|
||||
get_image_metadata_default,
|
||||
get_offer_metadata_default,
|
||||
get_item_metadata_default,
|
||||
ArticleType,
|
||||
SellerType,
|
||||
Currency,
|
||||
Condition,
|
||||
Format,
|
||||
SourceType,
|
||||
MediaType,
|
||||
Market,
|
||||
TapeType,
|
||||
MediaLength,
|
||||
ShellColor,
|
||||
WriteProtectTabs,
|
||||
InlayCondition,
|
||||
Packaging,
|
||||
)
|
||||
from lpon_site.settings import (
|
||||
KEY_IMAGE_TYPE, # Ключ, о том, какой тип изображения (реальное или абстрактное)
|
||||
VALUE_IMAGE_ABSTRACT, # Реальная картинка (актуально, для фото товаров в TbOffer)
|
||||
KEY_IMAGE_FROM, # Ключ, о том, как получено изображение
|
||||
VALUE_IMAGE_FROM_USER, # Картинка загружена пользователем через админку
|
||||
KEY_IMAGE_URL, # Ключ, о том, откуда получена картинка (URL источника)
|
||||
KEY_IMAGE_NOTE, # Ключ, для заметок о картинке (например, "обложка", "задник", "вкладка" и т.д.)
|
||||
KEY_OFFER_NOTE, # Ключ, для заметок о коммерческом предложении (например, "замят угол конверта" и т.д.)
|
||||
KEY_OFFER_ALL_MEDIA, # Ключ, для хранения информации о том, какие носители входят в оффер (например, CD+2LP)
|
||||
OFFER_HASHIDS_SALT, # Соль для криптографического кодирования ID оферов
|
||||
OFFER_HASHIDS_MIN_LENGTH, # Минимальная длина кода
|
||||
)
|
||||
@@ -188,34 +207,6 @@ from lpon_site.settings import (
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ДЛЯ DEFAULTS В МОДЕЛЯХ
|
||||
# ============================================================================
|
||||
|
||||
def get_image_metadata_default():
|
||||
"""
|
||||
Возвращает значение по умолчанию для j_img_metadata.
|
||||
Используется как callable default в Django моделях (Django не может сериализовать lambda).
|
||||
"""
|
||||
return {
|
||||
KEY_IMAGE_TYPE: VALUE_IMAGE_ABSTRACT,
|
||||
KEY_IMAGE_FROM: VALUE_IMAGE_FROM_USER,
|
||||
KEY_IMAGE_URL: None,
|
||||
KEY_IMAGE_NOTE: '???',
|
||||
}
|
||||
|
||||
|
||||
def get_offer_metadata_default():
|
||||
"""
|
||||
Возвращает значение по умолчанию для j_offer_metadata.
|
||||
Используется как callable default в Django моделях (Django не может сериализовать lambda).
|
||||
"""
|
||||
return {
|
||||
KEY_OFFER_NOTE: '',
|
||||
KEY_OFFER_ALL_MEDIA: [],
|
||||
}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# МЕТАДАННЫЕ ИЗОБРАЖЕНИЙ
|
||||
# ============================================================================
|
||||
@@ -317,30 +308,8 @@ class TbArticle(models.Model):
|
||||
• Соединитель для Artist, Item, Label, Seller, Style (все 1:1→article)
|
||||
• Хранит текстовый контент, SEO, изображения справочников
|
||||
"""
|
||||
class ArticleType(models.TextChoices):
|
||||
"""
|
||||
Типы статей и контентных разделов.
|
||||
|
||||
ВАЖНО ДЛЯ РОУТИНГА И URL:
|
||||
Значение каждого элемента (например, 'info', 'blog', 'artist', 'item' и т.д.)
|
||||
используется напрямую как первый сегмент канонического URL-пути статьи:
|
||||
/<l_article_type>/<slug>/ (например: /info/privacy-policy/, /blog/new-release-2026-september/)
|
||||
|
||||
Исключения:
|
||||
- HUB ('HUB'): статьи-хабы публикуются напрямую в корне сайта (/<slug>/).
|
||||
"""
|
||||
ARTIST = 'artist', 'Artist: артист, группа или бренд'
|
||||
STYLE = 'style', 'Style: музыкальный стиль'
|
||||
ITEM = 'item', 'Item: Альбом, релиз или товар (кассета, hifi, аксессуар)'
|
||||
LABEL = 'label', 'Label: Лейбл, издатель или компания'
|
||||
OFFER = 'offer', 'Offer: конкретное предложение от продавца'
|
||||
SELLER = 'seller', 'Seller: продавец или магазин'
|
||||
BLOG = 'blog', 'Blog: Новость или блог'
|
||||
ACTION = 'action', 'Action: Спецпредложение, акция, распродажа и т.п.'
|
||||
ADV = 'adv', 'Adv: Реклама или баннер'
|
||||
HUB = 'HUB', 'HUB: СТАТЬЯ-ХАБ (использует DSL для отображения контента)'
|
||||
INFO = 'info', 'Info: Текстовый контент (для FAQ, условий конфиденциальности, правил/соглашений и т.д.)'
|
||||
OTHER = '--', '?¿?: Другое'
|
||||
# Алиас на перечисление типов статей из constants.py (для обратной совместимости)
|
||||
ArticleType = ArticleType
|
||||
|
||||
s_article_title = models.CharField(
|
||||
max_length=255,
|
||||
@@ -1081,25 +1050,9 @@ class TbSeller(models.Model):
|
||||
• save(): создаёт связанную статью автоматически (синонимы не используются)
|
||||
- Если статья не привязана: создаёт новую через create_or_get_related_article()
|
||||
"""
|
||||
class SellerType(models.TextChoices):
|
||||
SELLER = 'seller', 'Продавец'
|
||||
LABEL = 'label', 'Лейбл (издатель)'
|
||||
DIY = 'diy', 'Самиздат группы'
|
||||
CROWD = 'crowd', 'Краудфандинг'
|
||||
OTHER = '???', 'Другое'
|
||||
|
||||
class Currency(models.TextChoices):
|
||||
RUB = 'rub', 'RUB: российский рубль'
|
||||
USD = 'usd', 'USD: американский доллар'
|
||||
EUR = 'eur', 'EUR: евро'
|
||||
AMD = 'amd', 'AMD: армянских драм'
|
||||
TRY = 'try', 'TRY: турецкая лира'
|
||||
JPY = 'jpy', 'JPY: японская иена'
|
||||
GBP = 'gbp', 'GBP: британский фунт'
|
||||
CNY = 'cny', 'CNY: китайский юань'
|
||||
BYN = 'byn', 'BYN: белорусский рубль'
|
||||
TON = 'ton', 'TON: криптовалюта TON'
|
||||
OTHER = '??', 'Other'
|
||||
# Алиасы на перечисления из constants.py (для обратной совместимости)
|
||||
SellerType = SellerType
|
||||
Currency = Currency
|
||||
|
||||
# Используем SmallAutoField для оптимизации (макс ~32k)
|
||||
# Продавцов обычно до 1000-10000, поэтому достаточно
|
||||
@@ -1215,29 +1168,9 @@ class TbOffer(models.Model):
|
||||
|
||||
В админке M2M выглядит как список картинок с сортировкой и метаданными.
|
||||
"""
|
||||
class Condition(models.TextChoices):
|
||||
S = 's', 'Still Sealed (новое, запечатано)'
|
||||
M = 'm', 'Mint (новое, распакованное)'
|
||||
NM = 'nm', 'Near Mint (почти новое)'
|
||||
VG = 'vg', 'Very Good (очень хорошее)'
|
||||
G = 'g', 'Good (хорошее)'
|
||||
F = 'f', 'Fair (удовлетворительное)'
|
||||
P = 'p', 'Poor (плохое)'
|
||||
OTHER = '??', 'Other'
|
||||
|
||||
class Format(models.TextChoices):
|
||||
LP = 'lp', 'Vinyl Long-Play (12")'
|
||||
EP = 'ep', 'Vinyl Extended-Play (12", 10", 7")'
|
||||
V45 = '45', 'Vinyl 7" (45 rpm)'
|
||||
CD = 'cd', 'Compact Disc'
|
||||
LD = 'ld', 'LaserDisc'
|
||||
REC_MD = 'md', 'MiniDisc Record'
|
||||
USE_MD = 'ms', 'Used MiniDisc (для записи)'
|
||||
REC_CS = 'cs', 'Cassette Record'
|
||||
USE_CS = 'uc', 'Used Cassette (для записи)'
|
||||
REC_RR = 'tp', 'Tape Reel Record'
|
||||
USE_RR = 'ur', 'Used Tape Reel (для записи)'
|
||||
OTHER = '??', 'Other'
|
||||
# Алиасы на перечисления из constants.py (для обратной совместимости)
|
||||
Condition = Condition
|
||||
Format = Format
|
||||
|
||||
s_offer = models.CharField(
|
||||
max_length=128,
|
||||
@@ -1535,11 +1468,8 @@ class TbSource(models.Model):
|
||||
МЕТОДЫ:
|
||||
• Нет переопределённых методов save(). Используется стандартное поведение Django.
|
||||
"""
|
||||
class SourceType(models.TextChoices):
|
||||
EXCEL = 'excel', 'Excel-файл от продавца или издателя'
|
||||
CSV = 'csv', 'CSV-файл от продавца или издателя'
|
||||
URL = 'url', 'URL страницы с данными (например, HTML-страница с каталогом товаров)'
|
||||
OTHER = '??', 'Другое (включая ручной ввод)'
|
||||
# Алиас на перечисление из constants.py (для обратной совместимости)
|
||||
SourceType = SourceType
|
||||
|
||||
# Используем SmallAutoField для оптимизации (макс ~32k)
|
||||
# Источников обычно до 1000, достаточно
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
"""
|
||||
Константы, перечисления и ключи метаданных проекта LPON.
|
||||
|
||||
Данный модуль является единым источником правды для:
|
||||
• Ключей JSON-метаданных (TbItem.j_item_metadata, TbOffer.j_offer_metadata и др.)
|
||||
• Контролируемых словарей и перечислений (типы носителей, типы статей, продавцов, валют)
|
||||
• Функций генерации дефолтных JSON-структур для моделей
|
||||
• Валидационных настроек и системных констант
|
||||
"""
|
||||
|
||||
from enum import IntEnum
|
||||
from django.db import models
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# СИСТЕМНЫЕ НАСТРОЙКИ И ОГРАНИЧЕНИЯ
|
||||
# ============================================================================
|
||||
|
||||
# Максимальная длина slug: Весь URL оптимально иметь до 70 символов (методичка Google).
|
||||
# SLUG_MAX_LENGTH задает ограничение для слагов, создаваемых автоматически.
|
||||
SLUG_MAX_LENGTH = 60
|
||||
|
||||
# Минимальная длина слова для поиска совпадений схожести в синонимах
|
||||
MIN_SYNONYM_WORD_LENGTH = 4
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# КЛЮЧИ МЕТАДАННЫХ ДЛЯ ОБЩИХ ТАБЛИЦ (TbLabel, TbSeller, TbArtist, TbMusicStyle)
|
||||
# ============================================================================
|
||||
|
||||
KEY_SYNONYM_EN = 'SYN_EN' # Синонимы на английском языке (для поиска и парсеров)
|
||||
KEY_SYNONYM_RU = 'SYN_RU' # Синонимы на русском языке (для отображения на фронтенде)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# КЛЮЧИ И ЗНАЧЕНИЯ ДЛЯ МЕТАДАННЫХ ИЗОБРАЖЕНИЙ (TbImageMetadata)
|
||||
# ============================================================================
|
||||
|
||||
KEY_IMAGE_TYPE = 'IMG_IS' # Ключ для хранения типа картинки
|
||||
VALUE_IMAGE_ABSTRACT = 'abstract' # Абстрактная картинка (логотипы, промо, типовые обложки)
|
||||
VALUE_IMAGE_REAL = 'real' # Реальная фотография физического лота
|
||||
|
||||
KEY_IMAGE_FROM = 'IMG_FROM' # Источник происхождения изображения
|
||||
VALUE_IMAGE_FROM_USER = '_adm' # Загружено пользователем через панель управления
|
||||
VALUE_IMAGE_FROM_PARSE = 'parse' # Получено автоматически через парсер (Discogs, Bandcamp и т.д.)
|
||||
|
||||
KEY_IMAGE_URL = 'IMG_URL' # URL оригинального источника изображения
|
||||
KEY_IMAGE_NOTE = 'IMG_NOTE' # Текстовая заметка или подпись к изображению
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# КЛЮЧИ МЕТАДАННЫХ СТАТЕЙ (TbArticle)
|
||||
# ============================================================================
|
||||
|
||||
KEY_ARTICLE_HUB = 'HUB' # Флаг того, что статья является хабом/сборником
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# КЛЮЧИ МЕТАДАННЫХ ДЛЯ ТОВАРОВ И МОДЕЛЕЙ НОСИТЕЛЕЙ (TbItem.j_item_metadata)
|
||||
# ============================================================================
|
||||
|
||||
KEY_ITEM_MEDIA_TYPE = 'media_type' # Тип сущности: музыкальный альбом, модель носителя и т.д.
|
||||
KEY_ITEM_MEDIA_FORMAT = 'media_format' # Формат физического носителя (кассета, MD, винил, CD)
|
||||
KEY_ITEM_TAPE_TYPE = 'tape_type' # Тип магнитной ленты (для кассет: Type I, II, III, IV)
|
||||
KEY_ITEM_LENGTH = 'length' # Хронометраж носителя (46, 60, 74, 90 мин)
|
||||
KEY_ITEM_SHELL_COLOR = 'shell_color' # Цвет корпуса кассеты или минидиска
|
||||
KEY_ITEM_RELEASE_YEAR = 'release_year' # Год или диапазон годов производства модели/релиза
|
||||
KEY_ITEM_MARKET = 'market' # Регион дистрибуции (JP, US, EU, GLOBAL)
|
||||
KEY_ITEM_GALLERY_MODEL = 'gallery_model' # Список ID эталонных изображений модели в django-filer
|
||||
KEY_ITEM_CATALOG_NUM = 'catalog_num' # Оригинальный каталожный номер производителя
|
||||
KEY_ITEM_BARCODE = 'barcode' # Штрихкод (EAN/UPC)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# КЛЮЧИ МЕТАДАННЫХ ДЛЯ КОММЕРЧЕСКИХ ПРЕДЛОЖЕНИЙ (TbOffer.j_offer_metadata)
|
||||
# ============================================================================
|
||||
|
||||
KEY_OFFER_NOTE = 'OFR_NT' # Текстовая заметка по лоту (дефекты, нюансы)
|
||||
KEY_OFFER_ALL_MEDIA = 'OFR_MF' # Состав комплекта носителей (например, {"lp": 2, "cd": 1})
|
||||
KEY_OFFER_IS_RECORDED = 'is_recorded' # Флаг наличия уникальной аудиозаписи на носителе
|
||||
KEY_OFFER_RECORDING_INFO = 'recording_info' # Описание записанного материала (эфир, альбом, бутлег)
|
||||
KEY_OFFER_TABS = 'write_protect_tabs' # Состояние язычков защиты от записи (INTACT, BROKEN)
|
||||
KEY_OFFER_INLAY = 'inlay_condition' # Состояние бумажного вкладыша J-Card (CLEAN, PENCIL, WRITTEN)
|
||||
KEY_OFFER_SERIES_TAG = 'series_tag' # Слаг/метка серии или коллекции
|
||||
KEY_OFFER_PACKAGING = 'packaging' # Сохранность упаковки (SEALED, OPENED)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ГЕНЕРАЦИИ ДЕФОЛТНЫХ ЗНАЧЕНИЙ JSON (CALLABLE DEFAULTS)
|
||||
# ============================================================================
|
||||
|
||||
def get_image_metadata_default() -> dict:
|
||||
"""
|
||||
Возвращает значение по умолчанию для TbImageMetadata.j_img_metadata.
|
||||
Используется как callable default в Django моделях и миграциях.
|
||||
"""
|
||||
return {
|
||||
KEY_IMAGE_TYPE: VALUE_IMAGE_ABSTRACT,
|
||||
KEY_IMAGE_FROM: VALUE_IMAGE_FROM_USER,
|
||||
KEY_IMAGE_URL: None,
|
||||
KEY_IMAGE_NOTE: '???',
|
||||
}
|
||||
|
||||
|
||||
def get_offer_metadata_default() -> dict:
|
||||
"""
|
||||
Возвращает значение по умолчанию для TbOffer.j_offer_metadata.
|
||||
Используется как callable default в Django моделях и миграциях.
|
||||
"""
|
||||
return {
|
||||
KEY_OFFER_NOTE: '',
|
||||
KEY_OFFER_ALL_MEDIA: [],
|
||||
}
|
||||
|
||||
|
||||
def get_item_metadata_default() -> dict:
|
||||
"""
|
||||
Возвращает значение по умолчанию для TbItem.j_item_metadata.
|
||||
"""
|
||||
return {
|
||||
KEY_ITEM_MEDIA_TYPE: 'blank_media',
|
||||
KEY_ITEM_MEDIA_FORMAT: 'other',
|
||||
}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# КОНТРОЛИРУЕМЫЕ ПЕРЕЧИСЛЕНИЯ (ENUMS / CHOICES ДЛЯ МОДЕЛЕЙ И JSON)
|
||||
# ============================================================================
|
||||
|
||||
class ArticleType(models.TextChoices):
|
||||
"""
|
||||
Типы статей и контентных разделов (TbArticle.l_article_type).
|
||||
|
||||
ВАЖНО ДЛЯ РОУТИНГА И URL:
|
||||
Значение каждого элемента (например, 'info', 'blog', 'artist', 'item' и т.д.)
|
||||
используется напрямую как первый сегмент канонического URL-пути статьи:
|
||||
/<l_article_type>/<slug>/ (например: /info/privacy-policy/, /blog/new-release-2026-september/)
|
||||
|
||||
Исключения:
|
||||
- HUB ('HUB'): статьи-хабы публикуются напрямую в корне сайта (/<slug>/).
|
||||
"""
|
||||
ARTIST = 'artist', 'Artist: артист, группа или бренд'
|
||||
STYLE = 'style', 'Style: музыкальный стиль'
|
||||
ITEM = 'item', 'Item: Альбом, релиз или товар (кассета, hifi, аксессуар)'
|
||||
LABEL = 'label', 'Label: Лейбл, издатель или компания'
|
||||
OFFER = 'offer', 'Offer: конкретное предложение от продавца'
|
||||
SELLER = 'seller', 'Seller: продавец или магазин'
|
||||
BLOG = 'blog', 'Blog: Новость или блог'
|
||||
ACTION = 'action', 'Action: Спецпредложение, акция, распродажа и т.п.'
|
||||
ADV = 'adv', 'Adv: Реклама или баннер'
|
||||
HUB = 'HUB', 'HUB: СТАТЬЯ-ХАБ (использует DSL для отображения контента)'
|
||||
INFO = 'info', 'Info: Текстовый контент (для FAQ, условий конфиденциальности, правил/соглашений и т.д.)'
|
||||
OTHER = '--', '?¿?: Другое'
|
||||
|
||||
|
||||
class SellerType(models.TextChoices):
|
||||
"""Тип продавца или магазина (TbSeller.l_seller_type)."""
|
||||
SELLER = 'seller', 'Продавец'
|
||||
LABEL = 'label', 'Лейбл (издатель)'
|
||||
DIY = 'diy', 'Самиздат группы'
|
||||
CROWD = 'crowd', 'Краудфандинг'
|
||||
OTHER = '???', 'Другое'
|
||||
|
||||
|
||||
class Currency(models.TextChoices):
|
||||
"""Валюта источника/продавца (TbSeller.l_seller_currency)."""
|
||||
RUB = 'rub', 'RUB: российский рубль'
|
||||
USD = 'usd', 'USD: американский доллар'
|
||||
EUR = 'eur', 'EUR: евро'
|
||||
AMD = 'amd', 'AMD: армянских драм'
|
||||
TRY = 'try', 'TRY: турецкая лира'
|
||||
JPY = 'jpy', 'JPY: японская иена'
|
||||
GBP = 'gbp', 'GBP: британский фунт'
|
||||
CNY = 'cny', 'CNY: китайский юань'
|
||||
BYN = 'byn', 'BYN: белорусский рубль'
|
||||
TON = 'ton', 'TON: криптовалюта TON'
|
||||
OTHER = '??', 'Other'
|
||||
|
||||
|
||||
class Condition(models.TextChoices):
|
||||
"""
|
||||
Международный грейдинг состояния носителя, обложки, вкладышей, плакатов и т.п. (Goldmine Standard).
|
||||
Используется в TbOffer.l_offer_condition_media и TbOffer.l_offer_condition_sleeve.
|
||||
"""
|
||||
S = 's', 'Still Sealed (новое, запечатано)'
|
||||
M = 'm', 'Mint (новое, распакованное)'
|
||||
NM = 'nm', 'Near Mint (почти новое)'
|
||||
VG = 'vg', 'Very Good (очень хорошее)'
|
||||
G = 'g', 'Good (хорошее)'
|
||||
F = 'f', 'Fair (удовлетворительное)'
|
||||
P = 'p', 'Poor (плохое)'
|
||||
OTHER = '??', 'Other'
|
||||
|
||||
|
||||
class Format(models.TextChoices):
|
||||
"""Физический формат носителя оффера (TbOffer.l_offer_to_format)."""
|
||||
LP = 'lp', 'Vinyl Long-Play (12")'
|
||||
EP = 'ep', 'Vinyl Extended-Play (12", 10", 7")'
|
||||
V45 = '45', 'Vinyl 7" (45 rpm)'
|
||||
CD = 'cd', 'Compact Disc'
|
||||
LD = 'ld', 'LaserDisc'
|
||||
REC_MD = 'md', 'MiniDisc Record'
|
||||
USE_MD = 'ms', 'Used MiniDisc (для записи)'
|
||||
REC_CS = 'cs', 'Cassette Record'
|
||||
USE_CS = 'uc', 'Used Cassette (для записи)'
|
||||
REC_RR = 'tp', 'Tape Reel Record'
|
||||
USE_RR = 'ur', 'Used Tape Reel (для записи)'
|
||||
OTHER = '??', 'Other'
|
||||
|
||||
|
||||
class SourceType(models.TextChoices):
|
||||
"""Тип источника данных о товарах (TbSource.l_source_type)."""
|
||||
EXCEL = 'excel', 'Excel-файл от продавца или издателя'
|
||||
CSV = 'csv', 'CSV-файл от продавца или издателя'
|
||||
URL = 'url', 'URL страницы с данными (например, HTML-страница с каталогом товаров)'
|
||||
OTHER = '??', 'Другое (включая ручной ввод)'
|
||||
|
||||
|
||||
class MediaType(models.TextChoices):
|
||||
"""Тип сущности товара в каталоге."""
|
||||
M_ALBUM = 'music_album', 'Музыкальный альбом / релиз (кассета, CD, винил, MD и т.д.)'
|
||||
M_MEDIA = 'blank_media', 'Носитель под запись (чистый или б/у модель)'
|
||||
M_RARE = 'rare_recording', 'Раритетная аудиозапись или бутлег'
|
||||
M_EQUIPMENT = 'equipment', 'Аудиотехника и аксессуары'
|
||||
|
||||
|
||||
class Market(models.TextChoices):
|
||||
"""Регион дистрибуции релиза или модели носителя."""
|
||||
JAPAN = 'jp', 'Япония (Japan / NTSC-J)'
|
||||
USA = 'us', 'США / Северная Америка (USA / NTSC-U)'
|
||||
EUROPE = 'eu', 'Европа (Europe / PAL)'
|
||||
UK = 'uk', 'Великобритания (United Kingdom)'
|
||||
USSR = 'su', 'СССР / Мелодия'
|
||||
RU = 'ru', 'Россия'
|
||||
GLOBAL = 'global', 'Международный / Global'
|
||||
|
||||
|
||||
class TapeType(models.TextChoices):
|
||||
"""Тип магнитной ленты для аудиокассет."""
|
||||
TYPE_1_NORMAL = 'type_1', 'Type I: Normal Position (Fe / Fe2O3)'
|
||||
TYPE_2_HIGH = 'type_2', 'Type II: High Position (CrO2 / Chrome / Cobalt)'
|
||||
TYPE_3_FECR = 'type_3', 'Type III: Ferro-Chrome (FeCr)'
|
||||
TYPE_4_METAL = 'type_4', 'Type IV: Metal Position (Metal Tape)'
|
||||
|
||||
|
||||
class MediaLength(models.IntegerChoices):
|
||||
"""Стандартные хронометражи кассет и минидисков в минутах."""
|
||||
L10 = 10, '10 минут'
|
||||
L20 = 20, '20 минут'
|
||||
L30 = 30, '30 минут'
|
||||
L40 = 40, '40 минут'
|
||||
L46 = 46, '46 минут'
|
||||
L50 = 50, '50 минут'
|
||||
L54 = 54, '54 минуты'
|
||||
L60 = 60, '60 минут'
|
||||
L74 = 74, '74 минуты (стандарт MiniDisc)'
|
||||
L80 = 80, '80 минут'
|
||||
L90 = 90, '90 минут'
|
||||
L100 = 100, '100 минут'
|
||||
L110 = 110, '110 минут'
|
||||
L120 = 120, '120 минут'
|
||||
L150 = 150, '150 минут'
|
||||
|
||||
|
||||
class ShellColor(models.TextChoices):
|
||||
"""Цвет корпуса компакт-кассеты или минидиска."""
|
||||
AMBER = 'amber', 'Янтарный (Amber / Прозрачный оранжевый)'
|
||||
SMOKE = 'smoke', 'Дымчатый (Smoke / Полупрозрачный серый)'
|
||||
CLEAR = 'clear', 'Прозрачный (Clear)'
|
||||
BLACK = 'black', 'Черный (Solid Black)'
|
||||
WHITE = 'white', 'Белый (Solid White)'
|
||||
GOLD = 'gold', 'Золотой / Металлик (Gold Metallic)'
|
||||
BLUE = 'blue', 'Синий / Голубой (Blue / Cyan)'
|
||||
RED = 'red', 'Красный / Рубиновый (Red / Ruby)'
|
||||
GREEN = 'green', 'Зеленый (Green)'
|
||||
|
||||
|
||||
class WriteProtectTabs(models.TextChoices):
|
||||
"""Состояние язычков защиты от записи на компакт-кассетах."""
|
||||
INTACT = 'intact', 'Целы (Intact / Запись возможна)'
|
||||
BROKEN = 'broken', 'Выломаны (Broken / Защита активна)'
|
||||
|
||||
|
||||
class InlayCondition(models.TextChoices):
|
||||
"""Состояние бумажного вкладыша (J-Card)."""
|
||||
CLEAN = 'clean', 'Чистый (Без надписей / как новый)'
|
||||
PENCIL = 'pencil', 'Надписи простым карандашом (стираемые)'
|
||||
WRITTEN = 'written', 'Исписан ручкой / штампы / наклейки'
|
||||
MISSING = 'missing', 'Вкладыш отсутствует'
|
||||
|
||||
|
||||
class Packaging(models.TextChoices):
|
||||
"""Тип сохранности упаковки физического экземпляра."""
|
||||
SEALED = 'sealed', 'Заводская запечатка (Still Sealed)'
|
||||
OPENED = 'opened', 'Вскрыт / б/у (Opened)'
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# КОНСТАНТЫ ДЛЯ ПОИСКА ДУБЛИКАТОВ И ВАЛИДАЦИИ
|
||||
# ============================================================================
|
||||
|
||||
VALIDATE_KEY__MATCH_TYPE = 'MATCH_TYPE'
|
||||
VALIDATE_KEY__MODEL = 'MODEL'
|
||||
VALIDATE_KEY__VALUE = 'MATCH_VALUE'
|
||||
|
||||
|
||||
class ValidateMatchType(IntEnum):
|
||||
"""Типы совпадений при поиске дубликатов."""
|
||||
IS_DUPLICATE = 1 # Точное совпадение основного поля (s_label, s_artist и т.д.)
|
||||
FIND_IN_SYNONYM = 2 # Точное совпадение в синонимах
|
||||
EXACT_SYNONYM_MATCH = 3 # Точное совпадение синонимов с синонимами остальных записей
|
||||
PARTIAL_MATCH = 4 # Схожесть: частичное совпадение
|
||||
PARTIAL_MATCH__RISK_SHORT_WORDS = 5 # Схожесть: совпадение по коротким словам (риск ложных срабатываний)
|
||||
@@ -316,58 +316,7 @@ LOGGING = {
|
||||
|
||||
# ===================================================
|
||||
# СПЕЦИФИЧНЫЕ ДЛЯ ПРОЕКТА КОНСТАНТЫ И НАСТРОЙКИ
|
||||
|
||||
# Максимальная длина slug: Весь URL оптимально иметь до 70 символов (методичка Google, так как такой URL виден целиком
|
||||
# на страничке поисковой выдачи, вызывает больше доверия и кликов). С учетом того, что
|
||||
# в URL входит имя домена и slug-и разделов, оптимально устанавливать slug даже короче
|
||||
# (20-30 символов максимум).
|
||||
# SLUG_MAX_LENGTH задает ограничение для slug-ов созданных автоматически. Вручную в admin можно задать до 255 символов.
|
||||
SLUG_MAX_LENGTH = 60
|
||||
|
||||
# Ключи для типовых параметров в мета-полях (для TbLabel, TbSeller, TbArtist, TbMusicStyle и т.д.)
|
||||
KEY_SYNONYM_EN = 'SYN_EN' # Ключ для хранения синонимов на английском языке (для поиска по синонимам) и ДЛЯ ПАРСЕРОВ
|
||||
KEY_SYNONYM_RU = 'SYN_RU' # Ключ для хранения синонимов на русском языке (для представления на фронтенд)
|
||||
|
||||
# Ключи и значения для метаданных картинок
|
||||
KEY_IMAGE_TYPE = 'IMG_IS' # Ключ для хранения типа картинки
|
||||
VALUE_IMAGE_ABSTRACT = 'abstract' # Абстрактная картинка (лого лейбла, фото исполнителя, типовая обложка альбома...)
|
||||
VALUE_IMAGE_REAL = 'real' # Реальная картинка (актуально, для фото товаров в TbOffer)
|
||||
KEY_IMAGE_FROM = 'IMG_FROM' # Ключ, о том, как получено изображение
|
||||
VALUE_IMAGE_FROM_USER = '_adm' # Картинка загружена пользователем через админку
|
||||
VALUE_IMAGE_FROM_PARSE = 'parse' # Картинка получена через парсер (например, с сайта Discogs, Bandcamp и т.д.)
|
||||
KEY_IMAGE_URL = 'IMG_URL' # Ключ для хранения URL оригинальной картинки (обычно это должен прописать парсер)
|
||||
KEY_IMAGE_NOTE = 'IMG_NOTE' # Ключ для хранения заметки о картинке
|
||||
|
||||
# Ключи и значения для коммерческих предложений (офферов, см. TbOffer и OfferAdmin)
|
||||
KEY_OFFER_NOTE = 'OFR_NT' # Ключ для заметок по офферу (например, "замят угол конверта" и т.д.)
|
||||
KEY_OFFER_ALL_MEDIA = 'OFR_MF' # Ключ для хранения информации о том, какие носители входят в оффер (например, CD+2LP)
|
||||
|
||||
# Ключи метанных статей (TbArticle).
|
||||
KEY_ARTICLE_HUB = 'HUB' # Ключ для хранения информации о том, что статья является хабом (содержит ссылки на другие статьи)
|
||||
|
||||
|
||||
# ДЛЯ ВАЛИДАЦИИ (поиска похожих исполнителей, стилей и т.д. (используется в TbLabel.matching_type, TbSeller.matching_type и т.д.)
|
||||
VALIDATE_KEY__MATCH_TYPE = 'MATCH_TYPE'
|
||||
VALIDATE_KEY__MODEL = 'MODEL'
|
||||
VALIDATE_KEY__VALUE = 'MATCH_VALUE'
|
||||
|
||||
# Типы совпадений как Enum (более информативно чем просто числа, работает в match-case)
|
||||
class ValidateMatchType(IntEnum):
|
||||
"""Типы совпадений при поиске дубликатов."""
|
||||
IS_DUPLICATE = 1 # Точное совпадение основного поля (s_label, s_artist и т.д.)
|
||||
FIND_IN_SYNONYM = 2 # Точное совпадение в синонимах
|
||||
EXACT_SYNONYM_MATCH = 3 # Точное совпадение синонимов проверяемой записи с синонимами остальных записей
|
||||
PARTIAL_MATCH = 4 # Схожесть: частичное совпадение (например, часть названия артиста или лейбла в других записях).
|
||||
# Основного поля и синонимами проверяемой записи и всех остальных записей.
|
||||
PARTIAL_MATCH__RISK_SHORT_WORDS = 5 # Схожесть: Частичное совпадение, но с короткими словами (например,
|
||||
# Японская группа "B'Z" или лейбл "XL"), что может вызвать ложные срабатывания
|
||||
# схожести. Нужны быть аккуратнее, проверять вручную (особенно если короткие
|
||||
# слова среди синонимов внутри метаданных других записей). Довольно частое
|
||||
# явление для распростарннеых "суффиксов" MC, DJ, артиклей, инициалов
|
||||
# исполнителей и т.п.
|
||||
|
||||
|
||||
# Минимальная длина слова для поиска совпадений схожести в синонимах (например, в "The Beatles": "The" не учитывается,
|
||||
# а "Beatles" учитывается)
|
||||
MIN_SYNONYM_WORD_LENGTH = 4
|
||||
# Вынесены в constants.py для изоляции бизнес-логики и чистоты настроек
|
||||
# ===================================================
|
||||
from .constants import *
|
||||
|
||||
|
||||
Reference in New Issue
Block a user