diff --git a/myproject/pos/views.py b/myproject/pos/views.py
index 774dae4..3b868cb 100644
--- a/myproject/pos/views.py
+++ b/myproject/pos/views.py
@@ -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)
diff --git a/myproject/products/utils/storage.py b/myproject/products/utils/storage.py
index 02cb2c2..145e7d4 100644
--- a/myproject/products/utils/storage.py
+++ b/myproject/products/utils/storage.py
@@ -144,24 +144,30 @@ class TenantAwareFileSystemStorage(FileSystemStorage):
Удалить файл, убедившись что он принадлежит текущему тенанту.
Args:
- name (str): Путь к файлу
+ name (str): Путь к файлу (может быть БЕЗ tenant_id)
"""
# Получаем tenant_id для проверки
tenant_id = self._get_tenant_id()
- # Проверяем что файл принадлежит текущему тенанту
- if not name.startswith(f"tenants/{tenant_id}/"):
- logger.warning(
- f"[Storage] Security: Attempted to delete file from different tenant! "
- f"Current tenant: {tenant_id}, file: {name}"
- )
- raise RuntimeError(
- f"Cannot delete file - it belongs to a different tenant. "
- f"Current tenant: {tenant_id}"
- )
+ # Если путь уже содержит tenants/, проверяем принадлежность тенанту
+ if name.startswith("tenants/"):
+ if not name.startswith(f"tenants/{tenant_id}/"):
+ logger.warning(
+ f"[Storage] Security: Attempted to delete file from different tenant! "
+ f"Current tenant: {tenant_id}, file: {name}"
+ )
+ raise RuntimeError(
+ f"Cannot delete file - it belongs to a different tenant. "
+ f"Current tenant: {tenant_id}"
+ )
+ # Если путь уже содержит tenants/, удаляем его как есть
+ logger.debug(f"[Storage] delete: {name} (already has tenant prefix)")
+ return super().delete(name)
- logger.debug(f"[Storage] delete: {name}")
- return super().delete(name)
+ # Иначе добавляем tenant_id перед удалением
+ tenant_aware_name = self._get_tenant_path(name)
+ logger.debug(f"[Storage] delete: {name} → {tenant_aware_name}")
+ return super().delete(tenant_aware_name)
def exists(self, name):
"""
@@ -220,3 +226,76 @@ class TenantAwareFileSystemStorage(FileSystemStorage):
# Иначе добавляем tenant_id
tenant_aware_name = self._get_tenant_path(name)
return super().url(tenant_aware_name)
+
+ def _open(self, name, mode='rb'):
+ """
+ Открыть файл, добавив tenant_id в путь если необходимо.
+ Это критически важно для Celery задач, которые читают файлы из БД.
+
+ Когда ImageProcessor вызывает Image.open(photo_obj.image), Django вызывает
+ это метод. БД содержит путь БЕЗ tenant_id (например, 'products/temp/image.jpg'),
+ но файл находится на диске с tenant_id (tenants/{tenant_id}/products/temp/image.jpg).
+
+ Args:
+ name (str): Путь к файлу (может быть БЕЗ tenant_id)
+ mode (str): Режим открытия файла
+
+ Returns:
+ File-like object
+ """
+ # Получаем tenant_id
+ tenant_id = self._get_tenant_id()
+
+ # Если путь уже содержит tenants/, проверяем принадлежность тенанту
+ if name.startswith("tenants/"):
+ if not name.startswith(f"tenants/{tenant_id}/"):
+ logger.warning(
+ f"[Storage] Security: Attempted to open file from different tenant! "
+ f"Current tenant: {tenant_id}, file: {name}"
+ )
+ raise RuntimeError(
+ f"Cannot open file - it belongs to a different tenant. "
+ f"Current tenant: {tenant_id}"
+ )
+ # Если путь уже содержит tenants/, используем его как есть
+ logger.debug(f"[Storage] _open: {name} (already has tenant prefix)")
+ return super()._open(name, mode)
+
+ # Иначе добавляем tenant_id перед открытием
+ tenant_aware_name = self._get_tenant_path(name)
+ logger.debug(f"[Storage] _open: {name} → {tenant_aware_name}")
+ return super()._open(tenant_aware_name, mode)
+
+ def path(self, name):
+ """
+ Получить полный системный путь к файлу, добавив tenant_id если необходимо.
+ Используется для конвертации 'products/temp/image.jpg' в '/media/tenants/papa/products/temp/image.jpg'
+
+ Args:
+ name (str): Путь к файлу (может быть БЕЗ tenant_id)
+
+ Returns:
+ str: Полный системный путь к файлу
+ """
+ # Получаем tenant_id
+ tenant_id = self._get_tenant_id()
+
+ # Если путь уже содержит tenants/, проверяем принадлежность тенанту
+ if name.startswith("tenants/"):
+ if not name.startswith(f"tenants/{tenant_id}/"):
+ logger.warning(
+ f"[Storage] Security: Attempted to get path for file from different tenant! "
+ f"Current tenant: {tenant_id}, file: {name}"
+ )
+ raise RuntimeError(
+ f"Cannot get path for file - it belongs to a different tenant. "
+ f"Current tenant: {tenant_id}"
+ )
+ # Если путь уже содержит tenants/, используем его как есть
+ logger.debug(f"[Storage] path: {name} (already has tenant prefix)")
+ return super().path(name)
+
+ # Иначе добавляем tenant_id перед получением пути
+ tenant_aware_name = self._get_tenant_path(name)
+ logger.debug(f"[Storage] path: {name} → {tenant_aware_name}")
+ return super().path(tenant_aware_name)