mod: валидатор форм, парсера и моделей (19) автоматически устанавливаем дефолтные метаданные к связанным картинкам. Переопрелен метод save для модели TbArticle и проброс него request из ArticleAdmin через переопределенный save_model...
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -914,10 +914,6 @@ class ItemAdmin(RequestInFormMixin, admin.ModelAdmin):
|
||||
readonly_fields = ('t_item_created', 't_item_updated')
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Остальные ModelAdmin классы
|
||||
# ============================================================================
|
||||
|
||||
# ============================================================================
|
||||
# АДМИНКА СТАТЕЙ
|
||||
#
|
||||
@@ -929,7 +925,7 @@ class ItemAdmin(RequestInFormMixin, admin.ModelAdmin):
|
||||
class ArticleAdminForm(CodeMirrorFormMixin):
|
||||
"""
|
||||
Кастомная форма для админки статей (TbArticle).
|
||||
Добавляет виджеты CodeMirror для текстовых полей
|
||||
Добавляет виджеты CodeMirror для текстовых полей.
|
||||
"""
|
||||
class Meta:
|
||||
model = TbArticle
|
||||
@@ -998,7 +994,30 @@ class ArticleAdmin(admin.ModelAdmin):
|
||||
}),
|
||||
)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
"""
|
||||
Переопределяем save_model чтобы передать username в модель.
|
||||
Username используется в TbArticle.save() для установки IMG_FROM в метаданные картинки.
|
||||
|
||||
Args:
|
||||
request: HTTP-запрос (содержит info о пользователе)
|
||||
obj: инстанция TbArticle для сохранения
|
||||
form: валидированная форма
|
||||
change: True если редактирование, False если создание
|
||||
"""
|
||||
# Сохраняем username текущего пользователя
|
||||
if request and request.user:
|
||||
obj._admin_username = request.user.username
|
||||
|
||||
# Вызываем родительский save_model который вызовет obj.save()
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Остальные ModelAdmin классы
|
||||
# Их еще предстоит обжужукать (натянуть CodeMirror, обвесить валидаторами, переопределить save_model и т.д.)
|
||||
# ============================================================================
|
||||
class OfferAdmin(admin.ModelAdmin):
|
||||
"""Админ для предложений"""
|
||||
|
||||
|
||||
@@ -240,14 +240,22 @@
|
||||
# - M2M использует числовые FK (INT) вместо строк
|
||||
# - Slug'и как UNIQUE indexed fields (не primary_key) для экономии места
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
from django.db import models
|
||||
from django.db.models import F
|
||||
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
|
||||
import datetime
|
||||
import logging
|
||||
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, # Ключ, для заметок о картинке (например, "обложка", "задник", "вкладка" и т.д.)
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -295,15 +303,19 @@ class TbImageMetadata(models.Model):
|
||||
)
|
||||
|
||||
j_img_metadata = models.JSONField(
|
||||
# Гибкие дополнительные данные о изображении
|
||||
# Пример: {"source": "discogs", "reality": "abstract", "confidence": 0.95, ...}
|
||||
default=dict,
|
||||
# Гибкие дополнительные данные об изображении с предзаполненной структурой
|
||||
# Пример: {"IMG_IS": "abstract", "IMG_FROM": "ivan", "IMG_URL": None, "IMG_NOTE": "обложка"}
|
||||
default={
|
||||
KEY_IMAGE_TYPE: VALUE_IMAGE_ABSTRACT,
|
||||
KEY_IMAGE_FROM: VALUE_IMAGE_FROM_USER,
|
||||
KEY_IMAGE_URL: None,
|
||||
KEY_IMAGE_NOTE: '???', # Автогенерация через парсер или админку
|
||||
},
|
||||
blank=True,
|
||||
null=True,
|
||||
null=False, # Изменено с True на False т.к. теперь всегда есть структура default
|
||||
verbose_name='Метаданные',
|
||||
help_text='JSON с дополнительными данными: источник (парсер, админка), тип (реальное/абстрактное), '
|
||||
'достоверность, URL источника и т.д. Гибкое хранилище, позволяет добавлять новые поля '
|
||||
'без миграций БД.',
|
||||
help_text='JSON с дополнительными данными: тип (реальное/абстрактное), источник (парсер/админка), '
|
||||
'URL источника и заметки. Предзаполнено структурой для удобства.',
|
||||
)
|
||||
|
||||
# Связь с TbOffer (часть M2M через эту промежуточную таблицу)
|
||||
@@ -488,6 +500,7 @@ class TbArticle(models.Model):
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
Автоматически генерируем slug на основе заголовка статьи.
|
||||
Если привязана картинка и метаданных нет — создаем TbImageMetadata с тегами IMG_IS и IMG_FROM.
|
||||
Вызывается при каждом сохранении записи (создание или обновление).
|
||||
"""
|
||||
# Если slug не установлен (новая запись) — генерируем его из названия
|
||||
@@ -505,8 +518,39 @@ class TbArticle(models.Model):
|
||||
|
||||
self.slug = slug
|
||||
|
||||
# Сохраняем статью в БД (нужно чтобы потом привязать метаданные картинки)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
# Если картинка привязана и метаданных для неё нет — создаем их
|
||||
if self.k_article_to_image:
|
||||
# Получаем существующие метаданные (если есть)
|
||||
metadata = TbImageMetadata.objects.filter(image=self.k_article_to_image).first()
|
||||
|
||||
adding_metadata = {}
|
||||
if not metadata or not KEY_IMAGE_TYPE in (metadata.j_img_metadata or {}):
|
||||
adding_metadata[KEY_IMAGE_TYPE] = VALUE_IMAGE_ABSTRACT
|
||||
if not metadata or not KEY_IMAGE_FROM in (metadata.j_img_metadata or {}):
|
||||
# Получаем username из контекста админки (установлен в ArticleAdmin.save_model())
|
||||
# Fallback на 'unknown' если username недоступен (например, при программном создании)
|
||||
username = getattr(self, '_admin_username', 'unknown')
|
||||
adding_metadata[KEY_IMAGE_FROM] = username
|
||||
if not metadata or not KEY_IMAGE_URL in (metadata.j_img_metadata or {}):
|
||||
adding_metadata[KEY_IMAGE_URL] = None
|
||||
if not metadata or not KEY_IMAGE_NOTE in (metadata.j_img_metadata or {}):
|
||||
adding_metadata[KEY_IMAGE_NOTE] = None
|
||||
if not metadata:
|
||||
TbImageMetadata.objects.create(
|
||||
image=self.k_article_to_image,
|
||||
i_img_sort=0,
|
||||
j_img_metadata=adding_metadata
|
||||
)
|
||||
else:
|
||||
# Обновляем только недостающие ключи в j_img_metadata
|
||||
if adding_metadata:
|
||||
metadata.j_img_metadata.update(adding_metadata)
|
||||
metadata.save(update_fields=['j_img_metadata'])
|
||||
# Примечание: m_offer остаётся NULL т.к. это статья, а не оффер
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Статья'
|
||||
verbose_name_plural = 'Статьи'
|
||||
|
||||
@@ -320,6 +320,17 @@ SLUG_MAX_LENGTH = 60
|
||||
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' # Ключ для хранения заметки о картинке
|
||||
|
||||
|
||||
# ДЛЯ ВАЛИДАЦИИ (поиска похожих исполнителей, стилей и т.д. (используется в TbLabel.matching_type, TbSeller.matching_type и т.д.)
|
||||
VALIDATE_KEY__MATCH_TYPE = 'MATCH_TYPE'
|
||||
VALIDATE_KEY__MODEL = 'MODEL'
|
||||
|
||||
Reference in New Issue
Block a user