mod: валидатор форм, парсера и моделей (28) подготовка миниатюр для списков вынесено в хелпер render_image_thumbnail и применен к ImageMetadataAdmin и ArticleAdmin

This commit is contained in:
2026-07-15 18:23:50 +03:00
parent 9ba5ef2af6
commit b3d47aca52
+73 -30
View File
@@ -2,11 +2,13 @@
# Регистрируем модели с удобным интерфейсом. # Регистрируем модели с удобным интерфейсом.
import etpgrf import etpgrf
import logging
from typing import Any from typing import Any
from django import forms from django import forms
from django.forms import Textarea from django.forms import Textarea
from django.http import HttpRequest from django.http import HttpRequest
from django.contrib import admin from django.contrib import admin
from django.conf import settings
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
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
@@ -17,6 +19,8 @@ from .models import (
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
from .utils import make_slug from .utils import make_slug
logger = logging.getLogger(__name__)
# ============================================================================ # ============================================================================
# ЛОКАЛЬНЫЕ ХЕЛПЕРЫ ДЛЯ АДМИНКИ # ЛОКАЛЬНЫЕ ХЕЛПЕРЫ ДЛЯ АДМИНКИ
@@ -54,6 +58,61 @@ def get_related_article_description(model_class):
f' со&nbsp;всеми SEO-атрибутами и&nbsp;slag, но&nbsp;автоматика несовершенна.<br />&nbsp;') f' со&nbsp;всеми SEO-атрибутами и&nbsp;slag, но&nbsp;автоматика несовершенна.<br />&nbsp;')
def render_image_thumbnail(image_field, size=(40, 40), title='миниатюра'):
"""
Универсальный хелпер для отображения миниатюры изображения в админке.
Используется в ModelAdmin.list_display для показа превью картинок.
Использует easy_thumbnails для автоматического создания и кэширования миниатюр.
Параметры качества и формата берутся из settings (THUMBNAIL_FORMAT, THUMBNAIL_QUALITY).
Args:
image_field: Объект изображения (FilerImageField или ImageFieldFile) или None
size: Кортеж (ширина, высота) для миниатюры. По умолчанию (40, 40)
title: Описание картинки в alt атрибуте (по умолчанию 'миниатюра')
Returns:
str: HTML-строка с тегом img или сообщение об ошибке
Примеры:
# В методе ModelAdmin:
def image_thumbnail(self, obj):
return render_image_thumbnail(obj.image, size=(40, 40), title='Изображение')
"""
if image_field:
try:
# Получаем thumbnailer для картинки через easy_thumbnails
thumbnailer = get_thumbnailer(image_field.file)
# Генерируем или получаем уже созданную миниатюру
# Параметры берутся из django.conf.settings (THUMBNAIL_FORMAT, THUMBNAIL_QUALITY)
# Это гарантирует единообразие со всеми остальными миниатюрами в проекте
thumbnail = thumbnailer.get_thumbnail({
'size': size, # Размер миниатюры
'crop': 'smart', # Умное обрезание для сохранения центра
'quality': getattr(settings, 'THUMBNAIL_QUALITY', 80), # Качество из settings (по умолчанию 80)
})
# Возвращаем HTML тег img с миниатюрой
# format_html автоматически экранирует опасные символы
width, height = size
return format_html(
'<img src="{}" width="{}" height="{}" alt="{}" style="object-fit: cover; "/>',
thumbnail.url, # ← Параметры отдельно
width//2,
height//2,
image_field.name or title
)
except Exception as e:
# Если ошибка при генерации миниатюры (нет файла, ошибка формата и т.д.)
logger.exception(f"Ошибка при генерации миниатюры изображения: {e}")
return mark_safe('<span style="color: #ccc;">(ошибка)</span>')
# Если картинка не привязана
return mark_safe('<img width="20" height="20" alt="" style="background-color: #90909060;"/>')
# ============================================================================ # ============================================================================
# МИКСИНЫ ДЛЯ АДМИНКИ # МИКСИНЫ ДЛЯ АДМИНКИ
# ============================================================================ # ============================================================================
@@ -276,37 +335,12 @@ class ImageMetadataAdmin(admin.ModelAdmin):
def image_thumbnail(self, obj): def image_thumbnail(self, obj):
""" """
Отображает миниатюру картинки (40x40) в списке и в list_display_links. Отображает миниатюру картинки (40x40) в списке и в list_display_links.
Использует easy_thumbnails для автоматического создания и кэширования миниатюр. Использует universal helper render_image_thumbnail().
""" """
if obj and obj.image: return render_image_thumbnail(obj.image if obj else None, size=(40, 40), title='картинка')
try:
# Получаем thumbnailer для картинки через easy_thumbnails
thumbnailer = get_thumbnailer(obj.image.file)
# Генерируем или получаем уже созданную миниатюру размером 40x40
thumbnail = thumbnailer.get_thumbnail({
'size': (40, 40), # Размер миниатюры: 40x40 пикселей
'crop': 'smart', # Умное обрезание для сохранения центра картинки
'quality': 95, # Качество JPEG/WebP (95% для хорошего вида)
})
# Возвращаем HTML тег img с миниатюрой
# format_html автоматически экранирует опасные символы
return format_html(
'<img src="{}" width="40" height="40" alt="{}" '
'style="border-radius: 4px; object-fit: cover; cursor: pointer;"/>',
thumbnail.url,
obj.image.name or 'картинка' # Alt текст для доступности
)
except Exception as e:
# Если ошибка при генерации миниатюры (нет файла, ошибка формата и т.д.)
return mark_safe('<span style="color: #ccc;">(ошибка)</span>')
# Если картинка не привязана
return mark_safe('<span style="color: #ccc;">(нет картинки)</span>')
# Установляем название столбца в админке # Установляем название столбца в админке
image_thumbnail.short_description = 'Миниатюра (40x40)' image_thumbnail.short_description = 'img 40x40'
def _display_alt_text(self, obj): def _display_alt_text(self, obj):
""" """
@@ -1063,8 +1097,8 @@ class ArticleAdmin(RequestInFormMixin, admin.ModelAdmin):
"""Админ для статей с поддержкой передачи request в форму""" """Админ для статей с поддержкой передачи request в форму"""
form = ArticleAdminForm # Используем кастомную форму с CodeMirror form = ArticleAdminForm # Используем кастомную форму с CodeMirror
list_display = ('id', 's_article_title', 'l_article_type', 'b_article_published', 't_article_created') list_display = ('id', 'article_thumbnail', 's_article_title', 'l_article_type', 'b_article_published', 't_article_created')
list_display_links = ('id', 's_article_title',) list_display_links = ('id', 'article_thumbnail', 's_article_title',)
list_filter = ('l_article_type', 'b_article_published', 't_article_created') list_filter = ('l_article_type', 'b_article_published', 't_article_created')
search_fields = ('s_article_title', 'slug') search_fields = ('s_article_title', 'slug')
prepopulated_fields = {'slug': ('s_article_title',)} prepopulated_fields = {'slug': ('s_article_title',)}
@@ -1112,6 +1146,15 @@ class ArticleAdmin(RequestInFormMixin, admin.ModelAdmin):
css = {'all': (*ArticleAdminForm.Media.css['all'], 'css/validation-override.css', )} css = {'all': (*ArticleAdminForm.Media.css['all'], 'css/validation-override.css', )}
js = (*ArticleAdminForm.Media.js, 'js/form-field-watcher.js', ) js = (*ArticleAdminForm.Media.js, 'js/form-field-watcher.js', )
def article_thumbnail(self, obj):
"""
Отображает миниатюру изображения статьи (40x40) в списке.
Использует universal helper render_image_thumbnail().
"""
return render_image_thumbnail(obj.k_article_to_image if obj else None, size=(40, 40), title='статья')
article_thumbnail.short_description = 'img'
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
""" """
Переопределяем save_model для применения типографа. Переопределяем save_model для применения типографа.