debug: подробные логи для диагностики валидации аватара (JS, DRF, сериализатор)

This commit is contained in:
admin i 2025-12-02 11:28:45 +03:00
parent e240fbf256
commit ae47ab0d90
3 changed files with 153 additions and 10 deletions

View File

@ -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...")

View File

@ -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)

View File

@ -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',