🐍 Solución: Error decimal.InvalidOperation y fallo de login en Django con MariaDB 11.4
Si tu aplicación Django ha dejado de funcionar repentinamente tras una actualización del servidor a MariaDB 11.4, mostrando errores como decimal.InvalidOperation, fallas al iniciar sesión o un loop infinito en el login, tu base de datos está intacta.
⚠️ Aviso importante: Esta solución es de nivel avanzado y requiere acceso SSH al servidor y conocimientos de Python/Django. Si no te sientes cómodo realizando estos cambios por tu cuenta, abre un ticket de soporte y te asistimos directamente.
Este problema se debe a que MariaDB 11.4 cambió cómo retorna ciertos tipos de datos internos, lo que genera una cadena de incompatibilidades con las librerías Python que utiliza Django.
🔍 Síntomas: ¿Cómo saber si este es mi problema?
- Error al cargar cualquier página:
decimal.InvalidOperation: [<class 'decimal.ConversionSyntax'>] - Error al autenticar usuarios:
AttributeError: 'str' object has no attribute 'utcoffset' - Loop infinito en el login: Al ingresar credenciales correctas, el sistema redirige de vuelta al login sin mostrar error.
- El problema apareció de un día para otro, sin cambios en el código de la aplicación.
🧐 ¿Por qué ocurre esto?
MariaDB 11.4 cambió el type_code con el que retorna varios tipos de datos. Por ejemplo, campos que antes llegaban como enteros o datetime ahora llegan con type_code 0 (DECIMAL), y campos de texto largo llegan como bytes en lugar de str. Esto confunde a la librería mysqlclient de Python, que no puede procesarlos correctamente.
Los efectos en cadena son:
- Django no puede conectarse a la base de datos →
decimal.InvalidOperation - Los campos
datetimellegan como strings → errorutcoffset - Los datos de sesión llegan como
bytes→ Django no puede leer la sesión → loop en el login
1️⃣ Paso 1: Actualizar mysqlclient
La versión 2.2.4 de mysqlclient tiene bugs de compatibilidad con MariaDB 11.4. Debes actualizarla a 2.2.8 o superior. Ejecuta esto en la terminal dentro del virtualenv de tu proyecto:
source ~/virtualenv/TU_APP/3.12/bin/activate
MYSQLCLIENT_CFLAGS="-I/usr/include/mysql" \
MYSQLCLIENT_LDFLAGS="-L/usr/lib64 -lmariadb" \
PKG_CONFIG_PATH=/usr/lib64/pkgconfig \
pip install --upgrade mysqlclient --no-cache-dir
# Verificar versión instalada
python -c "import MySQLdb; print(MySQLdb.version_info)"
# Debe mostrar: (2, 2, 8, 'final', 0)
2️⃣ Paso 2: Actualizar Django
Django 5.1.x tiene un bug en el backend MySQL que impide la inicialización correcta con MariaDB 11.4. Actualiza a Django 5.2:
pip install "django==5.2" --no-cache-dir
# Verificar
python -c "import django; print(django.__version__)"
# Debe mostrar: 5.2
3️⃣ Paso 3: Agregar conversor de tipos en settings.py
Este es el cambio principal. Debes agregar un conversor de tipos personalizado al inicio de tu archivo settings.py, antes del bloque DATABASES, y luego referenciar ese conversor en las opciones de conexión.
Abre el archivo settings.py de tu proyecto y agrega el siguiente bloque justo antes de la sección DATABASES:
# ---- FIX MARIADB 11.4 (PremiumHosting) ----
import MySQLdb.converters
import datetime as _dt
def _smart_converter(val):
"""Convierte valores que MariaDB 11.4 retorna con type_code 0 (DECIMAL)
pero que en realidad pueden ser enteros o datetimes."""
if isinstance(val, (bytes, bytearray)):
val = val.decode('ascii')
if isinstance(val, str):
# Intentar datetime primero
try:
return _dt.datetime.fromisoformat(val)
except (ValueError, TypeError):
pass
# Intentar entero
try:
return int(float(val))
except (ValueError, TypeError):
pass
return val
if isinstance(val, _dt.datetime):
return val
try:
return int(float(val))
except (ValueError, TypeError):
return val
def _blob_to_str(val):
"""Convierte campos BLOB/TEXT que llegan como bytes a string UTF-8."""
if isinstance(val, (bytes, bytearray)):
return val.decode('utf-8', errors='replace')
return val
_conv = MySQLdb.converters.conversions.copy()
_conv[MySQLdb.constants.FIELD_TYPE.DECIMAL] = _smart_converter
_conv[MySQLdb.constants.FIELD_TYPE.NEWDECIMAL] = _smart_converter
_conv[0] = _smart_converter # type_code genérico que usa MariaDB 11.4
_conv[7] = _smart_converter # TIMESTAMP
_conv[12] = _smart_converter # DATETIME
_conv[255] = _blob_to_str # BLOB
_conv[252] = _blob_to_str # BLOB medium
_conv[251] = _blob_to_str # BLOB long
_conv[250] = _blob_to_str # BLOB tiny
# ---- FIN FIX MARIADB 11.4 ----
Luego, modifica el bloque DATABASES para usar el conversor y agregar el LOGIN_URL correcto. Tu configuración debe quedar similar a esta:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'tu_base_de_datos',
'USER': 'tu_usuario',
'PASSWORD': 'tu_password',
'HOST': 'localhost',
'PORT': '3306',
'OPTIONS': {
'conv': _conv, # <-- Aquí se aplica el conversor
},
}
}
# Ajuste necesario con MariaDB 11.4 y USE_TZ
USE_TZ = False
# Asegúrate de que LOGIN_URL apunta a tu vista de login real
LOGIN_URL = '/tu-ruta-de-login/'
⚠️ Nota sobre USE_TZ: Cambiar USE_TZ = False deshabilita el soporte de zonas horarias en Django. Si tu aplicación maneja usuarios en múltiples zonas horarias, evalúa el impacto antes de aplicar este cambio.
4️⃣ Paso 4: Limpiar sesiones y caché
Las sesiones antiguas guardadas con Django 5.1 no pueden ser leídas por Django 5.2 debido a un cambio en el algoritmo de firma. Debes eliminarlas:
cd ~/tu_proyecto
source ~/virtualenv/TU_APP/3.12/bin/activate
python manage.py shell -c "
from django.contrib.sessions.models import Session
count = Session.objects.all().delete()
print('Sesiones eliminadas:', count)
"
# Eliminar caché de Python
find . -name "*.pyc" -delete
find . -name "__pycache__" -exec rm -rf {} + 2>/dev/null
5️⃣ Paso 5: Reiniciar la aplicación
Si tu aplicación usa Passenger (lo más común en cPanel con Python), reiníciala tocando el archivo restart.txt:
touch ~/tu_proyecto/tmp/restart.txt
Si usas otro servidor WSGI, reinicia el proceso correspondiente.
✅ Verificación final
Puedes verificar que todo funciona correctamente ejecutando este comando en el virtualenv de tu proyecto:
python manage.py shell -c "
from django.db import connection
cursor = connection.cursor()
cursor.execute('SELECT VERSION()')
print('Conexión OK:', cursor.fetchone())
from django.contrib.auth import authenticate
user = authenticate(username='tu_usuario', password='tu_password')
print('Login OK:', user)
"
Si ambas líneas retornan valores correctos, tu aplicación está lista.
📋 Resumen de cambios
- mysqlclient: 2.2.4 → 2.2.8
- Django: 5.1.x → 5.2
- settings.py: Conversor de tipos personalizado para compatibilidad con MariaDB 11.4
- settings.py:
USE_TZ = Falsepara evitar errores de timezone con datetimes - settings.py:
LOGIN_URLapuntando a la ruta correcta de login de la aplicación - Base de datos: Sesiones antiguas eliminadas por incompatibilidad entre versiones de Django
⚠️ ¿Necesitas ayuda? Si los pasos anteriores te parecen complejos o tu aplicación tiene una configuración diferente (múltiples bases de datos, timezone awareness, Gunicorn, uWSGI, etc.), abre un ticket de soporte con el nombre de tu dominio y te asistimos directamente en tu entorno. Cada aplicación Django puede tener particularidades que requieren ajustes adicionales.