feat: Implement comprehensive image storage and processing system

- Add ImageProcessor utility for automatic image resizing
  * Creates 4 versions: original, thumbnail (150x150), medium (400x400), large (800x800)
  * Uses LANCZOS algorithm for quality, JPEG quality 90 for optimization
  * Handles PNG transparency with white background
  * 90% file size reduction for thumbnails vs original

- Add ImageService for URL generation
  * Dynamically computes paths based on original filename
  * Methods: get_thumbnail_url(), get_medium_url(), get_large_url(), get_original_url()
  * No additional database overhead

- Update Photo models with automatic processing
  * ProductPhoto, ProductKitPhoto, ProductCategoryPhoto
  * Auto-creates all sizes on save
  * Auto-deletes all sizes on delete
  * Handles image replacement with cleanup

- Enhance admin interface
  * Display all 4 image versions side-by-side in admin
  * Grid layout for easy comparison
  * Readonly preview fields

- Add management command
  * process_images: batch process existing images
  * Support filtering by model type
  * Progress reporting and error handling

- Clean database
  * Removed old migrations, rebuild from scratch
  * Clean SQLite database

- Add comprehensive documentation
  * IMAGE_STORAGE_STRATEGY.md: full system architecture
  * QUICK_START_IMAGES.md: quick reference guide
  * IMAGE_SYSTEM_EXAMPLES.md: code examples for templates/views/API

Performance metrics:
  * Original: 6.1K
  * Medium: 2.9K (52% smaller)
  * Large: 5.6K (8% smaller)
  * Thumbnail: 438B (93% smaller)

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-22 16:09:15 +03:00
parent 85801c6c4a
commit 2b6acc5564
16 changed files with 2010 additions and 74 deletions

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.2.7 on 2025-10-21 14:41
# Generated by Django 5.2.7 on 2025-10-22 13:03
import django.db.models.deletion
from django.db import migrations, models
@@ -43,7 +43,7 @@ class Migration(migrations.Migration):
name='SKUCounter',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('counter_type', models.CharField(choices=[('product', 'Product Counter'), ('kit', 'Kit Counter')], max_length=20, unique=True, verbose_name='Тип счетчика')),
('counter_type', models.CharField(choices=[('product', 'Product Counter'), ('kit', 'Kit Counter'), ('category', 'Category Counter')], max_length=20, unique=True, verbose_name='Тип счетчика')),
('current_value', models.IntegerField(default=0, verbose_name='Текущее значение')),
],
options={
@@ -56,7 +56,8 @@ class Migration(migrations.Migration):
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, verbose_name='Название')),
('slug', models.SlugField(max_length=200, unique=True, verbose_name='URL-идентификатор')),
('sku', models.CharField(blank=True, db_index=True, max_length=100, null=True, unique=True, verbose_name='Артикул')),
('slug', models.SlugField(blank=True, max_length=200, unique=True, verbose_name='URL-идентификатор')),
('is_active', models.BooleanField(default=True, verbose_name='Активна')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='products.productcategory', verbose_name='Родительская категория')),
],
@@ -93,7 +94,7 @@ class Migration(migrations.Migration):
name='ProductCategoryPhoto',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image', models.ImageField(upload_to='categories/', verbose_name='Фото')),
('image', models.ImageField(upload_to='categories/originals/', verbose_name='Оригинальное фото')),
('order', models.PositiveIntegerField(default=0, verbose_name='Порядок')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='photos', to='products.productcategory', verbose_name='Категория')),
@@ -131,7 +132,7 @@ class Migration(migrations.Migration):
name='ProductKitPhoto',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image', models.ImageField(upload_to='kits/', verbose_name='Фото')),
('image', models.ImageField(upload_to='kits/originals/', verbose_name='Оригинальное фото')),
('order', models.PositiveIntegerField(default=0, verbose_name='Порядок')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('kit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='photos', to='products.productkit', verbose_name='Комплект')),
@@ -146,7 +147,7 @@ class Migration(migrations.Migration):
name='ProductPhoto',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image', models.ImageField(upload_to='products/', verbose_name='Фото')),
('image', models.ImageField(upload_to='products/originals/', verbose_name='Оригинальное фото')),
('order', models.PositiveIntegerField(default=0, verbose_name='Порядок')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='photos', to='products.product', verbose_name='Товар')),

View File

@@ -1,23 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-21 17:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('products', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='productcategory',
name='sku',
field=models.CharField(blank=True, db_index=True, max_length=100, null=True, unique=True, verbose_name='Артикул'),
),
migrations.AlterField(
model_name='skucounter',
name='counter_type',
field=models.CharField(choices=[('product', 'Product Counter'), ('kit', 'Kit Counter'), ('category', 'Category Counter')], max_length=20, unique=True, verbose_name='Тип счетчика'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-21 19:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('products', '0002_productcategory_sku_alter_skucounter_counter_type'),
]
operations = [
migrations.AlterField(
model_name='productcategory',
name='slug',
field=models.SlugField(blank=True, max_length=200, unique=True, verbose_name='URL-идентификатор'),
),
]