From 194286ecd53a0f85d64143a7fd84a155df5fbfb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B3=D0=BE=D1=80=D1=8C=20=D0=9D=D0=B0=D0=B4=D0=B5?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Tue, 2 Dec 2025 11:28:45 +0300 Subject: [PATCH] =?UTF-8?q?debug:=20=D0=BF=D0=BE=D0=B4=D1=80=D0=BE=D0=B1?= =?UTF-8?q?=D0=BD=D1=8B=D0=B5=20=D0=BB=D0=BE=D0=B3=D0=B8=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=B4=D0=B8=D0=B0=D0=B3=D0=BD=D0=BE=D1=81=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=B2=D0=B0=D0=BB=D0=B8=D0=B4=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=B0=D0=B2=D0=B0=D1=82=D0=B0=D1=80=D0=B0=20(JS,=20DR?= =?UTF-8?q?F,=20=D1=81=D0=B5=D1=80=D0=B8=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=D1=82=D0=BE=D1=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/v1/employees/views.py | 50 +++++++++++-- backend/api/v1/serializers.py | 72 ++++++++++++++++++- .../js/components/employeeFormHandler.js | 41 +++++++++++ 3 files changed, 153 insertions(+), 10 deletions(-) diff --git a/backend/api/v1/employees/views.py b/backend/api/v1/employees/views.py index d01b5c9b..f8aa021d 100644 --- a/backend/api/v1/employees/views.py +++ b/backend/api/v1/employees/views.py @@ -1689,25 +1689,61 @@ class EmployeeViewSet(viewsets.ModelViewSet): logger.info(f"[ME PATCH] request.data keys: {list(request.data.keys())}") logger.info(f"[ME PATCH] request.FILES keys: {list(request.FILES.keys())}") logger.info(f"[ME PATCH] Content-Type: {request.content_type}") + + # ДЕТАЛЬНАЯ ДИАГНОСТИКА АВАТАРА if 'avatar' in request.data: avatar_data = request.data['avatar'] - logger.info(f"[ME PATCH] avatar in request.data: type={type(avatar_data)}, length={len(str(avatar_data)) if avatar_data else 0}") + avatar_type = type(avatar_data).__name__ + logger.info(f"[ME PATCH] ⚠️ avatar В request.data:") + logger.info(f" - type: {avatar_type}") + logger.info(f" - value: {repr(avatar_data)[:200]}") + logger.info(f" - is empty string: {avatar_data == ''}") + logger.info(f" - is None: {avatar_data is None}") + logger.info(f" - bool(avatar_data): {bool(avatar_data)}") + if hasattr(avatar_data, '__len__'): + logger.info(f" - length: {len(avatar_data) if avatar_data else 0}") + else: + logger.info(f"[ME PATCH] ✓ avatar НЕТ в request.data") + if 'avatar' in request.FILES: avatar_file = request.FILES['avatar'] - logger.info(f"[ME PATCH] avatar in request.FILES: name={avatar_file.name}, size={avatar_file.size}") + logger.info(f"[ME PATCH] ⚠️ avatar В request.FILES:") + logger.info(f" - name: {avatar_file.name}") + logger.info(f" - size: {avatar_file.size}") + logger.info(f" - content_type: {avatar_file.content_type}") + else: + logger.info(f"[ME PATCH] ✓ avatar НЕТ в request.FILES") logger.info("[ME PATCH] Step 1: Validating serializer...") - ser = self.get_serializer(instance, data=request.data, partial=True) - ser.is_valid(raise_exception=True) + logger.info(f"[ME PATCH] Step 1: Передаем в сериализатор data с ключами: {list(request.data.keys())}") + + try: + ser = self.get_serializer(instance, data=request.data, partial=True) + ser.is_valid(raise_exception=True) + logger.info("[ME PATCH] Step 1: ✓ Serializer validated successfully") + except ValidationError as ve: + logger.error(f"[ME PATCH] Step 1: ❌ Serializer validation FAILED:") + logger.error(f" - error detail: {ve.detail}") + logger.error(f" - error detail type: {type(ve.detail)}") + if hasattr(ve.detail, 'items'): + for field, errors in ve.detail.items(): + logger.error(f" - field '{field}': {errors}") + raise + vd = dict(ser.validated_data) - logger.info("[ME PATCH] Step 1: Serializer validated successfully") logger.info(f"[ME PATCH] Step 2: validated_data keys: {list(vd.keys())}") if 'avatar' in vd: avatar_obj = vd.get('avatar') - logger.info(f"[ME PATCH] avatar in validated_data: type={type(avatar_obj)}, hasattr read={hasattr(avatar_obj, 'read')}") + logger.info(f"[ME PATCH] Step 2: avatar В validated_data:") + logger.info(f" - type: {type(avatar_obj)}") + logger.info(f" - hasattr read: {hasattr(avatar_obj, 'read')}") + logger.info(f" - repr: {repr(avatar_obj)[:200]}") if avatar_obj: - logger.info(f"[ME PATCH] avatar object: name={getattr(avatar_obj, 'name', None)}, size={getattr(avatar_obj, 'size', None)}") + logger.info(f" - name: {getattr(avatar_obj, 'name', None)}") + logger.info(f" - size: {getattr(avatar_obj, 'size', None)}") + else: + logger.info(f"[ME PATCH] Step 2: ✓ avatar НЕТ в validated_data") # Проверяем изменение email logger.info("[ME PATCH] Step 3: Checking email change...") diff --git a/backend/api/v1/serializers.py b/backend/api/v1/serializers.py index d31b81a1..d9ed2066 100644 --- a/backend/api/v1/serializers.py +++ b/backend/api/v1/serializers.py @@ -39,25 +39,91 @@ class Base64ImageField(serializers.ImageField): return self.default_ext, "image/jpeg" def to_internal_value(self, data): + import logging + logger = logging.getLogger(__name__) + + logger.info("[Base64ImageField] to_internal_value вызван:") + logger.info(f" - data type: {type(data).__name__}") + logger.info(f" - data repr: {repr(data)[:200]}") + logger.info(f" - data is None: {data is None}") + logger.info(f" - data == '': {data == ''}") + logger.info(f" - data == b'': {data == b''}") + # Принимаем None/пустое if data in (None, "", b""): + logger.info( + "[Base64ImageField] Пустое значение, передаем в parent" + ) return super().to_internal_value(data) if isinstance(data, str): + logger.info( + f"[Base64ImageField] Обрабатываем строку, " + f"length: {len(data)}" + ) + # Срезаем префикс data:image/...;base64, если есть if data.startswith("data:image"): + logger.info( + "[Base64ImageField] Найден data URI префикс" + ) try: _, data = data.split(";base64,", 1) + logger.info( + f"[Base64ImageField] Префикс обрезан, " + f"осталось: {len(data)} символов" + ) except ValueError: - raise serializers.ValidationError("Некорректный формат data URI.") + logger.error( + "[Base64ImageField] Некорректный формат data URI" + ) + raise serializers.ValidationError( + "Некорректный формат data URI." + ) + try: + logger.info("[Base64ImageField] Декодируем base64...") decoded = base64.b64decode(data) - except (TypeError, ValueError, binascii.Error): + logger.info( + f"[Base64ImageField] Base64 декодирован, " + f"размер: {len(decoded)} байт" + ) + except (TypeError, ValueError, binascii.Error) as e: + logger.error( + f"[Base64ImageField] Ошибка декодирования " + f"base64: {e}" + ) raise serializers.ValidationError("Невалидный base64.") + logger.info( + "[Base64ImageField] Определяем формат изображения..." + ) ext, _ = self._detect_ext_mime(decoded) + logger.info(f"[Base64ImageField] Формат определен: {ext}") + file = ContentFile(decoded, name=f"upload.{ext}") - return super().to_internal_value(file) + logger.info( + "[Base64ImageField] ContentFile создан, " + "передаем в parent ImageField" + ) + + result = super().to_internal_value(file) + logger.info( + f"[Base64ImageField] Parent validation passed, " + f"result type: {type(result)}" + ) + return result + + # Если это не строка (например, InMemoryUploadedFile) + logger.info( + f"[Base64ImageField] Не строка, передаем в parent as-is" + ) + result = super().to_internal_value(data) + logger.info( + f"[Base64ImageField] Parent validation passed, " + f"result type: {type(result)}" + ) + return result return super().to_internal_value(data) diff --git a/backend/static/js/components/employeeFormHandler.js b/backend/static/js/components/employeeFormHandler.js index e8dd3337..dfaaf2c8 100644 --- a/backend/static/js/components/employeeFormHandler.js +++ b/backend/static/js/components/employeeFormHandler.js @@ -34,7 +34,48 @@ export function initEmployeeForm(options = {}) { const endpoint = form.dataset.endpoint || form.action; const formData = new FormData(form); + // ДЕТАЛЬНЫЕ ЛОГИ: проверяем все поля в FormData console.log('[EmployeeForm] Отправка данных на:', endpoint); + console.log('[EmployeeForm] Все поля в FormData:'); + for (let [key, value] of formData.entries()) { + if (value instanceof File) { + console.log(` ${key}: [File] name="${value.name}", size=${value.size}, type="${value.type}"`); + } else { + console.log(` ${key}: "${value}" (length: ${String(value).length})`); + } + } + + // Проверяем поле avatar особенно внимательно + const avatarInput = form.querySelector('input[name="avatar"]'); + if (avatarInput) { + console.log('[EmployeeForm] Avatar input найден:'); + console.log(' - type:', avatarInput.type); + console.log(' - files.length:', avatarInput.files?.length || 0); + if (avatarInput.files && avatarInput.files.length > 0) { + console.log(' - files[0]:', avatarInput.files[0]); + } else { + console.log(' - НЕТ выбранных файлов'); + } + console.log(' - value:', avatarInput.value); + + // КРИТИЧНО: если файл не выбран, удаляем поле avatar из FormData + if (!avatarInput.files || avatarInput.files.length === 0) { + console.warn('[EmployeeForm] Аватар не выбран - УДАЛЯЕМ поле avatar из FormData'); + formData.delete('avatar'); + } + } else { + console.log('[EmployeeForm] Avatar input НЕ НАЙДЕН в форме'); + } + + // Логируем финальное состояние FormData после очистки + console.log('[EmployeeForm] Финальный FormData (после очистки):'); + for (let [key, value] of formData.entries()) { + if (value instanceof File) { + console.log(` ${key}: [File] name="${value.name}", size=${value.size}`); + } else { + console.log(` ${key}: "${value}"`); + } + } const response = await fetch(endpoint, { method: 'PATCH',