Feat: Add inline price editing for products in catalog
Implemented inline editing functionality for product prices directly in the catalog view with support for both regular and sale prices. Features: - Click-to-edit price fields with visual hover indicators - Separate editing for price and sale_price fields - Add/remove sale price with validation - Real-time UI updates without page reload - Permission-based access control - Server-side validation for price constraints 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1081,3 +1081,139 @@ def create_category_api(request):
|
||||
'success': False,
|
||||
'error': f'Ошибка при создании категории: {str(e)}'
|
||||
}, status=500)
|
||||
|
||||
|
||||
def update_product_price_api(request, pk):
|
||||
"""
|
||||
AJAX endpoint для изменения цены товара (inline editing в каталоге).
|
||||
|
||||
Принимает JSON:
|
||||
{
|
||||
"field": "price" | "sale_price",
|
||||
"value": "150.50" | null
|
||||
}
|
||||
|
||||
Возвращает JSON:
|
||||
{
|
||||
"success": true,
|
||||
"price": "199.00",
|
||||
"sale_price": "150.00" | null,
|
||||
"actual_price": "150.00"
|
||||
}
|
||||
"""
|
||||
if request.method != 'POST':
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Метод не поддерживается'
|
||||
}, status=405)
|
||||
|
||||
# Проверка прав доступа
|
||||
if not request.user.has_perm('products.change_product'):
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'У вас нет прав для изменения цен товаров'
|
||||
}, status=403)
|
||||
|
||||
try:
|
||||
import json
|
||||
from decimal import Decimal, InvalidOperation
|
||||
|
||||
data = json.loads(request.body)
|
||||
field = data.get('field')
|
||||
value = data.get('value')
|
||||
|
||||
# Валидация поля
|
||||
if field not in ['price', 'sale_price']:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Недопустимое поле. Разрешены: price, sale_price'
|
||||
}, status=400)
|
||||
|
||||
# Получаем товар
|
||||
product = Product.objects.get(pk=pk)
|
||||
|
||||
# Обработка значения
|
||||
if value is None:
|
||||
# Очистка sale_price
|
||||
if field == 'sale_price':
|
||||
product.sale_price = None
|
||||
else:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Основная цена не может быть пустой'
|
||||
}, status=400)
|
||||
else:
|
||||
# Валидация значения
|
||||
try:
|
||||
decimal_value = Decimal(str(value))
|
||||
except (InvalidOperation, ValueError):
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Некорректное числовое значение'
|
||||
}, status=400)
|
||||
|
||||
# Проверка диапазона
|
||||
if decimal_value <= 0:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Цена должна быть положительной'
|
||||
}, status=400)
|
||||
|
||||
if decimal_value > Decimal('999999.99'):
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Цена слишком большая (максимум 999999.99)'
|
||||
}, status=400)
|
||||
|
||||
# Проверка десятичных знаков
|
||||
if decimal_value.as_tuple().exponent < -2:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Максимум 2 знака после запятой'
|
||||
}, status=400)
|
||||
|
||||
# Устанавливаем значение
|
||||
if field == 'price':
|
||||
product.price = decimal_value
|
||||
# Проверка: sale_price должна быть меньше price
|
||||
if product.sale_price and product.sale_price >= decimal_value:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Скидочная цена должна быть меньше обычной цены'
|
||||
}, status=400)
|
||||
else: # sale_price
|
||||
# Проверка: sale_price должна быть меньше price
|
||||
if decimal_value >= product.price:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Скидочная цена должна быть меньше обычной цены'
|
||||
}, status=400)
|
||||
product.sale_price = decimal_value
|
||||
|
||||
# Сохраняем
|
||||
product.save(update_fields=[field])
|
||||
|
||||
# Возвращаем обновлённые данные
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'price': str(product.price),
|
||||
'sale_price': str(product.sale_price) if product.sale_price else None,
|
||||
'actual_price': str(product.actual_price)
|
||||
})
|
||||
|
||||
except Product.DoesNotExist:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Товар не найден'
|
||||
}, status=404)
|
||||
except json.JSONDecodeError:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Некорректный JSON'
|
||||
}, status=400)
|
||||
except Exception as e:
|
||||
logger.error(f'Ошибка при обновлении цены товара: {str(e)}')
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': f'Ошибка при обновлении цены: {str(e)}'
|
||||
}, status=500)
|
||||
|
||||
Reference in New Issue
Block a user