From 493b6c212d90d2f8ee5d2b547f829f9450b44d45 Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Sun, 23 Nov 2025 21:49:50 +0300 Subject: [PATCH] Add bulk photo deletion feature for products. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allows selecting and deleting multiple photos at once via checkboxes and an AJAX endpoint. Key features: - Checkboxes next to each photo in edit form - Delete button that shows only when photos are selected - AJAX request with JSON payload and success confirmation - DOM removal and counter update after deletion - Uses existing ImageProcessor cleanup logic Files changed: - product_form.html: Added checkboxes and delete button with JS handler - photo_management.py: Added product_photos_delete_bulk AJAX view - urls.py: Added /product/photos/delete-bulk/ endpoint - views/__init__.py: Exported new function 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../templates/products/product_form.html | 113 +++++++++++++++++- myproject/products/urls.py | 1 + myproject/products/views/__init__.py | 2 + myproject/products/views/photo_management.py | 73 +++++++++++ 4 files changed, 185 insertions(+), 4 deletions(-) diff --git a/myproject/products/templates/products/product_form.html b/myproject/products/templates/products/product_form.html index 46014ea..126040e 100644 --- a/myproject/products/templates/products/product_form.html +++ b/myproject/products/templates/products/product_form.html @@ -182,11 +182,23 @@ {% if object and product_photos %}
-
Текущие фотографии ({{ photos_count }})
-
+
+
Текущие фотографии ({{ photos_count }})
+ +
+
{% for photo in product_photos %} -
-
+
+
+ +
+ +
+
+ + + {% endif %} diff --git a/myproject/products/urls.py b/myproject/products/urls.py index c120ad1..e7f2d91 100644 --- a/myproject/products/urls.py +++ b/myproject/products/urls.py @@ -24,6 +24,7 @@ urlpatterns = [ path('product/photo//set-main/', views.product_photo_set_main, name='product-photo-set-main'), path('product/photo//move-up/', views.product_photo_move_up, name='product-photo-move-up'), path('product/photo//move-down/', views.product_photo_move_down, name='product-photo-move-down'), + path('product/photos/delete-bulk/', views.product_photos_delete_bulk, name='product-photos-delete-bulk'), # CRUD URLs for ProductKit (комплекты/букеты) path('kit/create/', views.ProductKitCreateView.as_view(), name='productkit-create'), diff --git a/myproject/products/views/__init__.py b/myproject/products/views/__init__.py index 25d1285..a49dcbe 100644 --- a/myproject/products/views/__init__.py +++ b/myproject/products/views/__init__.py @@ -12,6 +12,7 @@ from .photo_management import ( product_photo_set_main, product_photo_move_up, product_photo_move_down, + product_photos_delete_bulk, ) # Управление фотографиями (ProductKit) @@ -105,6 +106,7 @@ __all__ = [ 'product_photo_set_main', 'product_photo_move_up', 'product_photo_move_down', + 'product_photos_delete_bulk', # Управление фотографиями ProductKit 'productkit_photo_delete', diff --git a/myproject/products/views/photo_management.py b/myproject/products/views/photo_management.py index a468e53..1b88e5c 100644 --- a/myproject/products/views/photo_management.py +++ b/myproject/products/views/photo_management.py @@ -2,8 +2,13 @@ Универсальные функции для управления фотографиями товаров, комплектов и категорий. Устраняет дублирование кода для операций: delete, set_main, move_up, move_down. """ +import json from django.shortcuts import get_object_or_404, redirect from django.contrib import messages +from django.http import JsonResponse +from django.views.decorators.http import require_http_methods +from django.contrib.auth.decorators import login_required +from django.contrib.auth.mixins import PermissionRequiredMixin from ..models import ProductPhoto, ProductKitPhoto, ProductCategoryPhoto @@ -308,3 +313,71 @@ def category_photo_move_down(request, pk): parent_attr='category', permission='products.change_productcategory' ) + + +# ==================================== +# AJAX Endpoints для массового удаления +# ==================================== + +@require_http_methods(["POST"]) +@login_required +def product_photos_delete_bulk(request): + """ + AJAX endpoint для массового удаления фотографий товара. + + Ожидает JSON: {photo_ids: [1, 2, 3]} + Возвращает JSON: {success: true, deleted: 3} или {success: false, error: "..."} + """ + # Проверка прав доступа + if not request.user.has_perm('products.change_product'): + return JsonResponse({ + 'success': False, + 'error': 'У вас нет прав для удаления фотографий' + }, status=403) + + try: + # Получаем список photo_ids из JSON тела запроса + data = json.loads(request.body) + photo_ids = data.get('photo_ids', []) + + if not photo_ids or not isinstance(photo_ids, list): + return JsonResponse({ + 'success': False, + 'error': 'Неверный формат: требуется список photo_ids' + }, status=400) + + # Удаляем фотографии + deleted_count = 0 + for photo_id in photo_ids: + try: + photo = ProductPhoto.objects.get(pk=photo_id) + photo.delete() # Это вызовет ImageProcessor.delete_all_versions() + deleted_count += 1 + except ProductPhoto.DoesNotExist: + # Если фото не найдена, просто пропускаем + continue + except Exception as e: + # Логируем ошибку но продолжаем удаление остальных + import logging + logger = logging.getLogger(__name__) + logger.error(f"Error deleting photo {photo_id}: {str(e)}", exc_info=True) + continue + + return JsonResponse({ + 'success': True, + 'deleted': deleted_count + }) + + except json.JSONDecodeError: + return JsonResponse({ + 'success': False, + 'error': 'Неверный JSON формат' + }, status=400) + except Exception as e: + import logging + logger = logging.getLogger(__name__) + logger.error(f"Bulk photo deletion error: {str(e)}", exc_info=True) + return JsonResponse({ + 'success': False, + 'error': f'Ошибка сервера: {str(e)}' + }, status=500)