mod: модели (13) переделка логики подключения картинок (1)
This commit is contained in:
+29
-31
@@ -9,7 +9,7 @@ from django.contrib import admin
|
|||||||
from django.utils.html import format_html, mark_safe
|
from django.utils.html import format_html, mark_safe
|
||||||
from easy_thumbnails.files import get_thumbnailer
|
from easy_thumbnails.files import get_thumbnailer
|
||||||
from .models import (
|
from .models import (
|
||||||
TbImage, TbArticle, TbArtist, TbItem, TbLabel, TbSeller,
|
TbImageMetadata, TbArticle, TbArtist, TbItem, TbLabel, TbSeller,
|
||||||
TbOffer, TbSource, TbOfferHistory, TbMusicStyle
|
TbOffer, TbSource, TbOfferHistory, TbMusicStyle
|
||||||
)
|
)
|
||||||
from .utils_validators import validate_entity_for_admin_form, generate_admin_save_message
|
from .utils_validators import validate_entity_for_admin_form, generate_admin_save_message
|
||||||
@@ -150,14 +150,14 @@ class CodeMirrorFormMixin(forms.ModelForm):
|
|||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# АДМИНИСТРИРОВАНИЕ TbImage
|
# АДМИНИСТРИРОВАНИЕ TbImageMetadata
|
||||||
#
|
#
|
||||||
# Кастомня форма для админки TbImage
|
# Кастомная форма для админки TbImageMetadata
|
||||||
class TbImageAdminForm(CodeMirrorFormMixin):
|
class TbImageMetadataAdminForm(CodeMirrorFormMixin):
|
||||||
"""
|
"""
|
||||||
Кастомная форма для TbImage в админке.
|
Кастомная форма для TbImageMetadata в админке.
|
||||||
Добавляет виртуальные поля для редактирования метаданных filer_image
|
Добавляет виртуальные поля для редактирования метаданных filer_image
|
||||||
(default_alt_text и default_caption), которые не хранятся в TbImage, но есть в filer_image.
|
(default_alt_text и default_caption), которые не хранятся в TbImageMetadata, но есть в filer_image.
|
||||||
|
|
||||||
Наследует от CodeMirrorFormMixin для поддержки редактора CodeMirror.
|
Наследует от CodeMirrorFormMixin для поддержки редактора CodeMirror.
|
||||||
"""
|
"""
|
||||||
@@ -193,8 +193,8 @@ class TbImageAdminForm(CodeMirrorFormMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = TbImage
|
model = TbImageMetadata
|
||||||
fields = ('image', 'l_img_source', 'l_img_reality', 's_img_src_url', 'i_img_sort', 'f_img_confidence_score')
|
fields = ('image', 'i_img_sort', 'j_img_metadata')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -227,37 +227,39 @@ class TbImageAdminForm(CodeMirrorFormMixin):
|
|||||||
css_class='codemirror-width-m codemirror-no-lines')
|
css_class='codemirror-width-m codemirror-no-lines')
|
||||||
|
|
||||||
# Активируем CodeMirror для реальных полей
|
# Активируем CodeMirror для реальных полей
|
||||||
self.setup_codemirror_field('s_img_src_url', language='url',
|
|
||||||
css_class='codemirror-width-xl codemirror-no-lines')
|
|
||||||
self.setup_codemirror_field('i_img_sort', language='text',
|
self.setup_codemirror_field('i_img_sort', language='text',
|
||||||
css_class='codemirror-width-s codemirror-no-lines')
|
css_class='codemirror-width-s codemirror-no-lines')
|
||||||
self.setup_codemirror_field('f_img_confidence_score', language='text',
|
self.setup_codemirror_field('j_img_metadata', language='json',
|
||||||
css_class='codemirror-width-s codemirror-no-lines')
|
css_class='codemirror-width-xl')
|
||||||
|
|
||||||
|
|
||||||
# Админка для TbImage с кастомной формой
|
# Админка для TbImageMetadata с кастомной формой
|
||||||
class ImageAdmin(admin.ModelAdmin):
|
class ImageMetadataAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
Админ для изображений TbImage с поддержкой редактирования метаданных filer_image.
|
Админ для метаданных изображений TbImageMetadata с поддержкой редактирования метаданных filer_image.
|
||||||
|
|
||||||
Позволяет пользователю заполнить default_alt_text и default_caption для картинки в filer
|
Позволяет пользователю заполнить default_alt_text и default_caption для картинки в filer
|
||||||
прямо в админке TbImage, без необходимости отдельного редактирования filer.
|
прямо в админке TbImageMetadata, без необходимости отдельного редактирования filer.
|
||||||
|
|
||||||
Использует TbImageAdminForm с наследованием от CodeMirrorFormMixin для поддержки CodeMirror.
|
Использует TbImageMetadataAdminForm с наследованием от CodeMirrorFormMixin для поддержки CodeMirror.
|
||||||
"""
|
"""
|
||||||
form = TbImageAdminForm # Используем кастомную форму с виртуальными полями (Media подключится через миксин)
|
form = TbImageMetadataAdminForm # Используем кастомную форму с виртуальными полями (Media подключится через миксин)
|
||||||
|
|
||||||
list_display = ('id', 'image_thumbnail', 'image', '_display_alt_text', 'i_img_sort', 't_img_created')
|
list_display = ('id', 'image_thumbnail', 'image', '_display_alt_text', 'i_img_sort')
|
||||||
list_display_links = ('id', 'image_thumbnail', 'image')
|
list_display_links = ('id', 'image_thumbnail', 'image')
|
||||||
list_filter = ('l_img_source', 'l_img_reality', 't_img_created')
|
list_filter = ('i_img_sort',)
|
||||||
ordering = ('image', 'i_img_sort')
|
ordering = ('image', 'i_img_sort')
|
||||||
readonly_fields = ('t_img_created', 't_img_updated', '_display_alt_text', '_display_title_text')
|
readonly_fields = ('_display_alt_text', '_display_title_text')
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Изображение', {
|
('Изображение', {
|
||||||
'fields': ('image', 'l_img_source', 'l_img_reality', 's_img_src_url', 'i_img_sort',
|
'fields': ('image', 'i_img_sort'),
|
||||||
'f_img_confidence_score'),
|
'description': 'Файл изображения и порядок его отображения (i_img_sort)',
|
||||||
'description': 'Основные данные об изображении и источнике',
|
}),
|
||||||
|
('Метаданные (JSON)', {
|
||||||
|
'fields': ('j_img_metadata',),
|
||||||
|
'description': 'Гибкие дополнительные данные в JSON формате (источник, тип, достоверность и т.д.). '
|
||||||
|
'Пример: {"source": "discogs", "reality": "abstract", "confidence": 0.95}',
|
||||||
}),
|
}),
|
||||||
('Метаданные filer (SEO для картинок)', {
|
('Метаданные filer (SEO для картинок)', {
|
||||||
'fields': ('_display_alt_text', '_display_title_text', 'alt_text', 'title_text',
|
'fields': ('_display_alt_text', '_display_title_text', 'alt_text', 'title_text',
|
||||||
@@ -266,10 +268,6 @@ class ImageAdmin(admin.ModelAdmin):
|
|||||||
' текущие значения останутся без изменений (и не будут заполнены при создании).',
|
' текущие значения останутся без изменений (и не будут заполнены при создании).',
|
||||||
# 'classes': ('collapse',),
|
# 'classes': ('collapse',),
|
||||||
}),
|
}),
|
||||||
('Служебная информация', {
|
|
||||||
'fields': ('t_img_created', 't_img_updated'),
|
|
||||||
'classes': ('collapse',),
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def image_thumbnail(self, obj):
|
def image_thumbnail(self, obj):
|
||||||
@@ -340,7 +338,7 @@ class ImageAdmin(admin.ModelAdmin):
|
|||||||
def save_model(
|
def save_model(
|
||||||
self,
|
self,
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
obj: TbImage,
|
obj: TbImageMetadata,
|
||||||
form: forms.ModelForm,
|
form: forms.ModelForm,
|
||||||
change: bool,
|
change: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -351,7 +349,7 @@ class ImageAdmin(admin.ModelAdmin):
|
|||||||
их значения сохраняются в соответствующие поля filer_image.
|
их значения сохраняются в соответствующие поля filer_image.
|
||||||
Если поля не заполнены, текущие значения в filer остаются без изменений.
|
Если поля не заполнены, текущие значения в filer остаются без изменений.
|
||||||
"""
|
"""
|
||||||
# Сначала сохраняем саму запись TbImage
|
# Сначала сохраняем саму запись TbImageMetadata
|
||||||
super().save_model(request, obj, form, change)
|
super().save_model(request, obj, form, change)
|
||||||
|
|
||||||
# Работаем с meta-данными filer только если картинка привязана
|
# Работаем с meta-данными filer только если картинка привязана
|
||||||
@@ -949,7 +947,7 @@ class OfferHistoryAdmin(admin.ModelAdmin):
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Регистрация моделей в дефолтном admin.site
|
# Регистрация моделей в дефолтном admin.site
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
admin.site.register(TbImage, ImageAdmin)
|
admin.site.register(TbImageMetadata, ImageMetadataAdmin)
|
||||||
admin.site.register(TbArticle, ArticleAdmin)
|
admin.site.register(TbArticle, ArticleAdmin)
|
||||||
admin.site.register(TbMusicStyle, MusicStyleAdmin)
|
admin.site.register(TbMusicStyle, MusicStyleAdmin)
|
||||||
admin.site.register(TbArtist, ArtistAdmin)
|
admin.site.register(TbArtist, ArtistAdmin)
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# Generated by Django 6.0.5 on 2026-07-10 07:47
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import filer.fields.image
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('frontend', '0001_initial'),
|
||||||
|
migrations.swappable_dependency(settings.FILER_IMAGE_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tbarticle',
|
||||||
|
name='k_article_to_image',
|
||||||
|
field=models.ForeignKey(blank=True, help_text='Обложка или иллюстрация статьи из файлового хранилища (django_filer). Метаданные: image.metadata.i_img_sort и image.metadata.j_img_metadata.</br><b>ВАЖНО</b>: Так как статья призывается к исполнителям, лейблам, продавцам, музыкальным стилям, её можно использовать как логотип-пкитограмму или баннер этих сущностей. В этом случае рекомендуется использовать изображение с прозрачным фоном (движок поддерживает SVG, WebP и PNG).', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='article_images', to=settings.FILER_IMAGE_MODEL, verbose_name='Изображение для статьи'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tbartist',
|
||||||
|
name='j_artist_metadata',
|
||||||
|
field=models.JSONField(blank=True, default=dict, help_text='В основном список синонимов/вариантов названия из Discogs, MusicBrainz и т.д. для матчинга. Пример: <tt>{"SYN_EN": ["The Beatles", "Beatles", "Beatles, The"]}</tt>.', null=True, verbose_name='Метаданные JSON'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tbartist',
|
||||||
|
name='k_artist_to_article',
|
||||||
|
field=models.OneToOneField(blank=True, default=None, help_text='Связанная статья об исполнителе (Типографированные заголовок, тизер и текст статьи. Так же через статью может быть получена <b>картинка<b>, seo атрибуты, слаг (обязательно) и т.п.)<br /><b>ОБЯЗАТЕЛЬНО УКАЗЫВАТЬ</b> т.к. через статью получаем слаг для URL артиста.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='article_to_artist', to='frontend.tbarticle', verbose_name='Связанная статья'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tbitem',
|
||||||
|
name='k_item_to_article',
|
||||||
|
field=models.OneToOneField(blank=True, default=None, help_text='Связанная статья об альбоме/релизе/товаре (Типографированные заголовок, тизер и текст статьи. Так же через статью может быть получена <b>картинка</b>, seo атрибуты, слаг (обязательно) и т.п.)<br /><b>ОБЯЗАТЕЛЬНО УКАЗЫВАТЬ</b> т.к. через статью получаем слаг для URL альбома/релиза/товара.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='article_to_item', to='frontend.tbarticle', verbose_name='Связанная статья'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tblabel',
|
||||||
|
name='j_label_metadata',
|
||||||
|
field=models.JSONField(blank=True, default=dict, help_text='JSON: страна лейбла, официальный сайт и т.д. Включая список синонимов/вариантов названия для матчинга. Пример: <tt>{"SYN_EN": ["Island", "Island Records", "Vertigo France"]}</tt>.', null=True, verbose_name='Метаданные'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tblabel',
|
||||||
|
name='k_label_to_article',
|
||||||
|
field=models.OneToOneField(blank=True, default=None, help_text='Связанная статья об лейбле (Типографированные заголовок, тизер и текст статьи. Так же через статью может быть получена <b>картинка</b>, seo атрибуты, слаг (обязательно) и т.п.)<br /><b>ОБЯЗАТЕЛЬНО УКАЗЫВАТЬ</b> т.к. через статью получаем слаг для URL лейбла.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='article_to_label', to='frontend.tbarticle', verbose_name='Связанная статья'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tbmusicstyle',
|
||||||
|
name='j_style_metadata',
|
||||||
|
field=models.JSONField(blank=True, default=dict, help_text='В основном список синонимов/вариантов названия из Discogs, MusicBrainz и т.д. для матчинга. Пример: <tt>{"SYN_EN": ["rock", "Rock Music", "Rock & Roll", "Hard Rock"]}</tt>.', verbose_name='Метаданные'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tboffer',
|
||||||
|
name='k_offer_to_image',
|
||||||
|
field=models.ManyToManyField(blank=True, db_index=True, help_text='Картинки этого товара/предложения. Порядок определяется полем i_img_sort в TbImageMetadata. Доступ к метаданным: image.metadata.i_img_sort и image.metadata.j_img_metadata', related_name='offer_images', to=settings.FILER_IMAGE_MODEL, verbose_name='Изображения'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tbseller',
|
||||||
|
name='k_seller_to_article',
|
||||||
|
field=models.OneToOneField(blank=True, default=None, help_text='Связанная статья о продавце (HTML-готовые заголовок, тизер и текст статьи). Так же через статью может быть получена <b>картинка</b>, seo атрибуты, слаг (обязательно) и т.п.)<br /><b>ОБЯЗАТЕЛЬНО УКАЗЫВАТЬ</b> т.к. через статью получаем слаг для URL продавца.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='article_to_seller', to='frontend.tbarticle', verbose_name='Связанная статья'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TbImageMetadata',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('i_img_sort', models.IntegerField(db_index=True, default=0, help_text='Порядок отображения изображений. Чем меньше число, тем выше в списке. Можно использовать для указания обложки (0), задника (1) и т.д.', verbose_name='Сортировка')),
|
||||||
|
('j_img_metadata', models.JSONField(blank=True, default=dict, help_text='JSON с дополнительными данными: источник (парсер, админка), тип (реальное/абстрактное), достоверность, URL источника и т.д. Гибкое хранилище, позволяет добавлять новые поля без миграций БД.', null=True, verbose_name='Метаданные')),
|
||||||
|
('l_img_source', models.CharField(blank=True, help_text='[DEPRECATED] Использовать j_img_metadata вместо этого поля.', max_length=10, null=True, verbose_name='[DEPRECATED] Источник')),
|
||||||
|
('l_img_reality', models.CharField(blank=True, help_text='[DEPRECATED] Использовать j_img_metadata вместо этого поля.', max_length=10, null=True, verbose_name='[DEPRECATED] Тип снимка')),
|
||||||
|
('s_img_src_url', models.URLField(blank=True, help_text='[DEPRECATED] Использовать j_img_metadata вместо этого поля.', null=True, verbose_name='[DEPRECATED] URL')),
|
||||||
|
('f_img_confidence_score', models.FloatField(blank=True, help_text='[DEPRECATED] Использовать j_img_metadata вместо этого поля.', null=True, verbose_name='[DEPRECATED] Достоверность')),
|
||||||
|
('t_img_created', models.DateTimeField(auto_now_add=True, verbose_name='[DEPRECATED] Дата добавления')),
|
||||||
|
('t_img_updated', models.DateTimeField(auto_now=True, verbose_name='[DEPRECATED] Дата обновления')),
|
||||||
|
('image', filer.fields.image.FilerImageField(help_text='Файл изображения из django_filer.', on_delete=django.db.models.deletion.DO_NOTHING, related_name='metadata', to=settings.FILER_IMAGE_MODEL, verbose_name='Файл изображения')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Метаданные изображения',
|
||||||
|
'verbose_name_plural': 'Метаданные изображений',
|
||||||
|
'ordering': ('i_img_sort',),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='TbImage',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -253,76 +253,87 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# ИЗОБРАЖЕНИЯ
|
# МЕТАДАННЫЕ ИЗОБРАЖЕНИЙ
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
class TbImage(models.Model):
|
class TbImageMetadata(models.Model):
|
||||||
"""
|
"""
|
||||||
Изображение, связанное с релизом, оффером, исполнителем и т.д.
|
Метаданные к изображениям из django_filer.
|
||||||
"""
|
|
||||||
# Источник изображения
|
|
||||||
class ImageSource(models.TextChoices):
|
|
||||||
PARSER_UPLOAD = 'parser', 'Загружено парсером (из Discogs, Meshok или другого сайта)'
|
|
||||||
MANUAL_UPLOAD = 'manual', 'Ручная загрузка пользователем'
|
|
||||||
VENDOR = 'vendor', 'От продавца'
|
|
||||||
OTHER = 'other', 'Другое'
|
|
||||||
|
|
||||||
# Тип изображения (реальное или абстрактное)
|
OneToOne связь с filer.Image. Хранит дополнительные поля:
|
||||||
class ImageReality(models.TextChoices):
|
- i_img_sort: порядок сортировки изображений
|
||||||
REAL_PHOTO = 'real', 'Реальная фотография товара'
|
- j_img_metadata: JSON с гибкими данными (источник, тип, достоверность и т.д.)
|
||||||
ABSTRACT = 'abstract', 'Абстрактное (из внешнего источника)'
|
|
||||||
|
Данные можно заполнять вручную через админку или программно из парсеров.
|
||||||
|
При создании новой filer.Image автоматически создаётся TbImageMetadata
|
||||||
|
с дефолтными значениями (через signal).
|
||||||
|
"""
|
||||||
|
|
||||||
image = FilerImageField(
|
image = FilerImageField(
|
||||||
# Файл через django_filer
|
# Связь OneToOne с filer.Image (через наследование)
|
||||||
null=False,
|
null=False,
|
||||||
blank=False,
|
blank=False,
|
||||||
on_delete=models.DO_NOTHING,
|
on_delete=models.DO_NOTHING,
|
||||||
|
related_name='metadata', # Встречный доступ: filer_image.metadata
|
||||||
verbose_name='Файл изображения',
|
verbose_name='Файл изображения',
|
||||||
help_text='Файл изображения, загруженный через django_filer.',
|
help_text='Файл изображения из django_filer.',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
i_img_sort = models.IntegerField(
|
||||||
|
# Порядок (сортировка) вывода изображений
|
||||||
|
default=0,
|
||||||
|
db_index=True,
|
||||||
|
verbose_name='Сортировка',
|
||||||
|
help_text='Порядок отображения изображений. Чем меньше число, тем выше в списке. '
|
||||||
|
'Можно использовать для указания обложки (0), задника (1) и т.д.',
|
||||||
|
)
|
||||||
|
|
||||||
|
j_img_metadata = models.JSONField(
|
||||||
|
# Гибкие дополнительные данные о изображении
|
||||||
|
# Пример: {"source": "discogs", "reality": "abstract", "confidence": 0.95, ...}
|
||||||
|
default=dict,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name='Метаданные',
|
||||||
|
help_text='JSON с дополнительными данными: источник (парсер, админка), тип (реальное/абстрактное), '
|
||||||
|
'достоверность, URL источника и т.д. Гибкое хранилище, позволяет добавлять новые поля '
|
||||||
|
'без миграций БД.',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Эти поля ПОКА остаются (удалим после миграции данных)
|
||||||
|
# но НЕ используются в новой архитектуре
|
||||||
l_img_source = models.CharField(
|
l_img_source = models.CharField(
|
||||||
max_length=10,
|
max_length=10,
|
||||||
choices=ImageSource.choices,
|
blank=True,
|
||||||
default=ImageSource.MANUAL_UPLOAD,
|
null=True,
|
||||||
verbose_name='Источник',
|
verbose_name='[DEPRECATED] Источник',
|
||||||
help_text='Как был получен этот снимок: загружен вручную, получен парсером из внешнего источника (например,'
|
help_text='[DEPRECATED] Использовать j_img_metadata вместо этого поля.',
|
||||||
' Discogs), предоставлен продавцом и т.д.',
|
|
||||||
)
|
)
|
||||||
l_img_reality = models.CharField(
|
l_img_reality = models.CharField(
|
||||||
max_length=10,
|
max_length=10,
|
||||||
choices=ImageReality.choices,
|
blank=True,
|
||||||
default=ImageReality.ABSTRACT,
|
null=True,
|
||||||
verbose_name='Тип снимка',
|
verbose_name='[DEPRECATED] Тип снимка',
|
||||||
help_text='Реальная фотография товара или картинка из внешнего источника?',
|
help_text='[DEPRECATED] Использовать j_img_metadata вместо этого поля.',
|
||||||
)
|
)
|
||||||
s_img_src_url = models.URLField(
|
s_img_src_url = models.URLField(
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name='URL',
|
verbose_name='[DEPRECATED] URL',
|
||||||
help_text='URL источника, если изображение взято (в том числе и парсером) из внешнего источника (например, Discogs)',
|
help_text='[DEPRECATED] Использовать j_img_metadata вместо этого поля.',
|
||||||
)
|
|
||||||
i_img_sort = models.IntegerField(
|
|
||||||
# Порядок (сортировка) вывода
|
|
||||||
default=0,
|
|
||||||
db_index=True,
|
|
||||||
verbose_name='Сортировка',
|
|
||||||
help_text='Порядок отображения изображений. Чем меньше число, тем выше в списке. Можно использовать'
|
|
||||||
' для указания обложки (0), задника (1) и т.д.',
|
|
||||||
)
|
)
|
||||||
f_img_confidence_score = models.FloatField(
|
f_img_confidence_score = models.FloatField(
|
||||||
# Доверие данным (для парсеров и API)
|
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
default=10.0,
|
verbose_name='[DEPRECATED] Достоверность',
|
||||||
verbose_name='Достоверность',
|
help_text='[DEPRECATED] Использовать j_img_metadata вместо этого поля.',
|
||||||
help_text='Уверенность (для автоматических данных) 0.0 - 10.0, насколько уверены, что это правильное изображение',
|
|
||||||
)
|
)
|
||||||
t_img_created = models.DateTimeField(auto_now_add=True, verbose_name='Дата добавления',)
|
t_img_created = models.DateTimeField(auto_now_add=True, verbose_name='[DEPRECATED] Дата добавления')
|
||||||
t_img_updated = models.DateTimeField(auto_now=True, verbose_name='Дата обновления',)
|
t_img_updated = models.DateTimeField(auto_now=True, verbose_name='[DEPRECATED] Дата обновления')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'Изображение'
|
verbose_name = 'Метаданные изображения'
|
||||||
verbose_name_plural = 'Изображения'
|
verbose_name_plural = 'Метаданные изображений'
|
||||||
ordering = ('-t_img_created', 'i_img_sort',)
|
ordering = ('i_img_sort',)
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -395,13 +406,20 @@ class TbArticle(models.Model):
|
|||||||
' будет отображаться без заголовка.'
|
' будет отображаться без заголовка.'
|
||||||
)
|
)
|
||||||
k_article_to_image = models.ForeignKey(
|
k_article_to_image = models.ForeignKey(
|
||||||
TbImage,
|
# Прямая ссылка на filer.Image (вместо TbImage)
|
||||||
|
# Метаданные изображения (сортировка, источник, тип и т.д.) находятся в TbImageMetadata
|
||||||
|
to='filer.Image',
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
related_name='image_to_article',
|
related_name='article_images',
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
db_index=True, # Принудительно создаем индекс, т.к. SQLite их сам не создаст.
|
db_index=True,
|
||||||
verbose_name='Изображение для статьи',
|
verbose_name='Изображение для статьи',
|
||||||
|
help_text='Обложка или иллюстрация статьи из файлового хранилища (django_filer).'
|
||||||
|
' Метаданные: image.metadata.i_img_sort и image.metadata.j_img_metadata.</br>'
|
||||||
|
'<b>ВАЖНО</b>: Так как статья призывается к исполнителям, лейблам, продавцам, музыкальным стилям,'
|
||||||
|
' её можно использовать как логотип-пкитограмму или баннер этих сущностей. В этом случае '
|
||||||
|
' рекомендуется использовать изображение с прозрачным фоном (движок поддерживает SVG, WebP и PNG).'
|
||||||
)
|
)
|
||||||
s_article_teaser_html = models.TextField(
|
s_article_teaser_html = models.TextField(
|
||||||
blank=True,
|
blank=True,
|
||||||
@@ -426,7 +444,7 @@ class TbArticle(models.Model):
|
|||||||
verbose_name='Число просмотров',
|
verbose_name='Число просмотров',
|
||||||
)
|
)
|
||||||
i_article_favorites = models.IntegerField(
|
i_article_favorites = models.IntegerField(
|
||||||
# Счетчик добавлений в избранное (включая избранное артиста, итема/релиза/товара, лейбла и продавца)
|
# Счетчик добавлений в избранное (включая избранный артист, item/релиз/товар/лейбл/продавец)
|
||||||
default=0,
|
default=0,
|
||||||
db_index=True, # для сортировки "самые добавляемые в избранное"
|
db_index=True, # для сортировки "самые добавляемые в избранное"
|
||||||
verbose_name='Число в избранном',
|
verbose_name='Число в избранном',
|
||||||
@@ -632,7 +650,7 @@ class TbArtist(models.Model):
|
|||||||
blank=True, # <-- Интерфейсное удобство. Связь будет сделана автоматически, и статья создана автоматически.
|
blank=True, # <-- Интерфейсное удобство. Связь будет сделана автоматически, и статья создана автоматически.
|
||||||
verbose_name='Связанная статья',
|
verbose_name='Связанная статья',
|
||||||
help_text='Связанная статья об исполнителе (Типографированные заголовок, тизер и текст статьи. Так же'
|
help_text='Связанная статья об исполнителе (Типографированные заголовок, тизер и текст статьи. Так же'
|
||||||
' через статью может быть получена картинка, seo атрибуты, слаг (обязательно) и т.п.)<br />'
|
' через статью может быть получена <b>картинка<b>, seo атрибуты, слаг (обязательно) и т.п.)<br />'
|
||||||
'<b>ОБЯЗАТЕЛЬНО УКАЗЫВАТЬ</b> т.к. через статью получаем слаг для URL артиста.'
|
'<b>ОБЯЗАТЕЛЬНО УКАЗЫВАТЬ</b> т.к. через статью получаем слаг для URL артиста.'
|
||||||
)
|
)
|
||||||
j_artist_metadata = models.JSONField(
|
j_artist_metadata = models.JSONField(
|
||||||
@@ -731,18 +749,16 @@ class TbItem(models.Model):
|
|||||||
TbArticle,
|
TbArticle,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
related_name='article_to_item',
|
related_name='article_to_item',
|
||||||
db_index=True, # Принудительно создаем индекс, т.к. SQLite их сам не создаст.
|
db_index=True,
|
||||||
default=None,
|
default=None,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True, # <-- Интерфейсное удобство. Связь будет сделана автоматически, и статья создана автоматически.
|
blank=True,
|
||||||
verbose_name='Связанная статья',
|
verbose_name='Связанная статья',
|
||||||
help_text='Связанная статья об альбоме/релизе/товаре (Типографированные заголовок, тизер и текст статьи.'
|
help_text='Связанная статья об альбоме/релизе/товаре (Типографированные заголовок, тизер и текст статьи.'
|
||||||
' Так же через статью может быть получена картинка, seo атрибуты, слаг (обязательно) и т.п.)<br />'
|
' Так же через статью может быть получена <b>картинка</b>, seo атрибуты, слаг (обязательно) и т.п.)<br />'
|
||||||
'<b>ОБЯЗАТЕЛЬНО УКАЗЫВАТЬ</b> т.к. через статью получаем слаг для URL альбома/релиза/товара.'
|
'<b>ОБЯЗАТЕЛЬНО УКАЗЫВАТЬ</b> т.к. через статью получаем слаг для URL альбома/релиза/товара.'
|
||||||
)
|
)
|
||||||
s_item_date = models.CharField(
|
s_item_date = models.CharField(
|
||||||
# Год выпуска (важно для коллекционеров). Так же в будущем можно использовать для "ЮБИЛЕЙНОГО ПРЕДЛОЖЕНИЯ"
|
|
||||||
# и фан-календаря
|
|
||||||
max_length=10,
|
max_length=10,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
@@ -853,7 +869,7 @@ class TbLabel(models.Model):
|
|||||||
blank=True, # <-- Интерфейсное удобство. Связь будет сделана автоматически, и статья создана автоматически.
|
blank=True, # <-- Интерфейсное удобство. Связь будет сделана автоматически, и статья создана автоматически.
|
||||||
verbose_name='Связанная статья',
|
verbose_name='Связанная статья',
|
||||||
help_text='Связанная статья об лейбле (Типографированные заголовок, тизер и текст статьи.'
|
help_text='Связанная статья об лейбле (Типографированные заголовок, тизер и текст статьи.'
|
||||||
' Так же через статью может быть получена картинка, seo атрибуты, слаг (обязательно) и т.п.)<br />'
|
' Так же через статью может быть получена <b>картинка</b>, seo атрибуты, слаг (обязательно) и т.п.)<br />'
|
||||||
'<b>ОБЯЗАТЕЛЬНО УКАЗЫВАТЬ</b> т.к. через статью получаем слаг для URL лейбла.'
|
'<b>ОБЯЗАТЕЛЬНО УКАЗЫВАТЬ</b> т.к. через статью получаем слаг для URL лейбла.'
|
||||||
)
|
)
|
||||||
j_label_metadata = models.JSONField(
|
j_label_metadata = models.JSONField(
|
||||||
@@ -968,7 +984,7 @@ class TbSeller(models.Model):
|
|||||||
blank=True, # <-- Интерфейсное удобство. Связь будет сделана автоматически, и статья создана автоматически.
|
blank=True, # <-- Интерфейсное удобство. Связь будет сделана автоматически, и статья создана автоматически.
|
||||||
verbose_name='Связанная статья',
|
verbose_name='Связанная статья',
|
||||||
help_text='Связанная статья о продавце (HTML-готовые заголовок, тизер и текст статьи).'
|
help_text='Связанная статья о продавце (HTML-готовые заголовок, тизер и текст статьи).'
|
||||||
' Так же через статью может быть получена картинка, seo атрибуты, слаг (обязательно) и т.п.)<br />'
|
' Так же через статью может быть получена <b>картинка</b>, seo атрибуты, слаг (обязательно) и т.п.)<br />'
|
||||||
'<b>ОБЯЗАТЕЛЬНО УКАЗЫВАТЬ</b> т.к. через статью получаем слаг для URL продавца.'
|
'<b>ОБЯЗАТЕЛЬНО УКАЗЫВАТЬ</b> т.к. через статью получаем слаг для URL продавца.'
|
||||||
)
|
)
|
||||||
l_seller_type = models.CharField(
|
l_seller_type = models.CharField(
|
||||||
@@ -1113,15 +1129,17 @@ class TbOffer(models.Model):
|
|||||||
'продавца: offer.k_offer_to_source.k_source_to_seller',
|
'продавца: offer.k_offer_to_source.k_source_to_seller',
|
||||||
)
|
)
|
||||||
k_offer_to_image = models.ManyToManyField(
|
k_offer_to_image = models.ManyToManyField(
|
||||||
# Изображения товара (одна картинка может быть у многих офферов, а оффер иметь много картинок)
|
# Изображения товара из django_filer (одна картинка может быть у многих офферов,
|
||||||
|
# а оффер иметь много картинок)
|
||||||
# M2M связь для удобства в админке (filter_horizontal)
|
# M2M связь для удобства в админке (filter_horizontal)
|
||||||
# Порядок картинок определяется полем i_img_sort в TbImage
|
# Порядок картинок определяется полем i_img_sort в TbImageMetadata
|
||||||
TbImage,
|
to='filer.Image',
|
||||||
blank=True,
|
blank=True,
|
||||||
related_name='image_to_offer',
|
related_name='offer_images',
|
||||||
db_index=True, # Принудительно создаем индекс, т.к. SQLite их сам не создаст.
|
db_index=True,
|
||||||
verbose_name='Изображения',
|
verbose_name='Изображения',
|
||||||
help_text='Картинки этого товара. Порядок определяется полем i_img_sort в ка��тинке.',
|
help_text='Картинки этого товара/предложения. Порядок определяется полем i_img_sort в TbImageMetadata. '
|
||||||
|
'Доступ к метаданным: image.metadata.i_img_sort и image.metadata.j_img_metadata',
|
||||||
)
|
)
|
||||||
# Характеристики
|
# Характеристики
|
||||||
s_offer_catalog_num = models.TextField(
|
s_offer_catalog_num = models.TextField(
|
||||||
|
|||||||
Reference in New Issue
Block a user