debug: подробные логи для диагностики валидации аватара (JS, DRF, сериализатор)
This commit is contained in:
parent
e240fbf256
commit
ae47ab0d90
@ -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...")
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user