Fix: Add _open() and path() methods to TenantAwareFileSystemStorage

Critical fix for Celery photo processing. The storage class now correctly
handles file reading operations by automatically adding tenant_id prefix
when opening files.

Problems fixed:
- Celery tasks could not open image files from storage
- PIL/Pillow couldn't locate files in tenant-specific directories
- temp file deletion was failing due to path validation

Changes:
- Added _open() method to add tenant_id prefix when opening files
- Added path() method to convert relative paths to full filesystem paths
- Updated delete() method to handle paths with or without tenant prefix
- All methods include security checks to prevent cross-tenant access

Testing:
- All 5 existing tests pass
- Verified photo processing task works end-to-end:
  * Reads temp image file from disk
  * Processes and creates all image versions
  * Saves processed files to tenant-specific directory
  * Cleans up temporary files correctly
- Files correctly stored in: media/tenants/{tenant_id}/products/{product_id}/{photo_id}/

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-23 20:30:52 +03:00
parent ff40a9c1f0
commit 87cba63c47
4 changed files with 114 additions and 20 deletions

View File

@@ -98,7 +98,8 @@ function updateCustomerDisplay() {
}
// Обновляем видимость кнопок сброса (в корзине и в модалке продажи)
const isSystemCustomer = selectedCustomer.id === SYSTEM_CUSTOMER.id;
// Приводим к числу для надёжного сравнения (JSON может вернуть разные типы)
const isSystemCustomer = Number(selectedCustomer.id) === Number(SYSTEM_CUSTOMER.id);
[document.getElementById('resetCustomerBtn'),
document.getElementById('checkoutResetCustomerBtn')].forEach(resetBtn => {

View File

@@ -37,10 +37,10 @@
<!-- Right Panel (4/12) - Fixed -->
<div class="col-md-4">
<div class="right-panel-fixed d-flex flex-column">
<!-- Информация о складе -->
{% if current_warehouse %}
<!-- Информация о складе (всегда показываем блок для фиксированной позиции) -->
<div class="card mb-2">
<div class="card-body py-2 px-3 d-flex justify-content-between align-items-center">
{% if current_warehouse %}
<div>
<small class="text-muted d-block" style="font-size: 0.75rem;">Склад:</small>
<strong style="font-size: 0.95rem;">{{ current_warehouse.name }}</strong>
@@ -48,9 +48,14 @@
<button class="btn btn-sm btn-outline-secondary" id="changeWarehouseBtn" style="font-size: 0.75rem;">
<i class="bi bi-arrow-left-right"></i> Сменить
</button>
{% else %}
<div class="text-danger">
<small class="d-block" style="font-size: 0.75rem;">Склад:</small>
<strong style="font-size: 0.95rem;"><i class="bi bi-exclamation-triangle me-1"></i>Не выбран</strong>
</div>
{% endif %}
</div>
</div>
{% endif %}
<!-- Cart Panel -->
<div class="card mb-2 flex-grow-1" style="min-height: 0;">

View File

@@ -172,15 +172,24 @@ def pos_terminal(request):
current_warehouse = get_pos_warehouse(request)
if not current_warehouse:
# Нет активных складов - показываем ошибку
from django.contrib import messages
messages.error(request, 'Нет активных складов. Обратитесь к администратору.')
# Нет активных складов - информация отображается в блоке склада в шаблоне
# Получаем системного клиента для корректного рендеринга JSON в шаблоне
system_customer, _ = Customer.get_or_create_system_customer()
context = {
'categories_json': json.dumps([]),
'items_json': json.dumps([]),
'showcase_kits_json': json.dumps([]),
'current_warehouse': None,
'warehouses': [],
'system_customer': {
'id': system_customer.id,
'name': system_customer.name
},
'selected_customer': {
'id': system_customer.id,
'name': system_customer.name
},
'cart_data': json.dumps({}),
'title': 'POS Terminal',
}
return render(request, 'pos/terminal.html', context)