Módulo 4 · Lección 34 de 45
Modelo de usuario personalizado
La decisión que hay que tomar en el primer día del proyecto, el error exacto que sale si te acuerdas tarde, y las tres formas de hacerlo.
El modelo User que has estado usando trae unos supuestos que conviene mirar de cerca, porque cambiarlos después es caro. Medidos:
username CharField max_length=150 unique=True blank=False
email EmailField max_length=254 unique=False blank=True
first_name CharField max_length=150 blank=True
last_name CharField max_length=150 blank=TrueEl correo no es único y puede estar vacío. Comprobado:
dos usuarios con el mismo correo: 5, 6Para casi cualquier sitio actual eso está mal, porque la gente entra con su correo y espera recuperar la contraseña con él. Y a eso se suman un nombre de usuario obligatorio que nadie quiere inventarse y un nombre partido en dos que no funciona en medio mundo.
Todo se arregla con un modelo propio. El problema es cuándo.
Primero, la advertencia#
Un proyecto ya migrado con auth.User, al que se le cambia el modelo después:
django.db.migrations.exceptions.InconsistentMigrationHistory:
Migration admin.0001_initial is applied before its dependency
cuentas.0001_initial on database 'default'.Eso es lo que sale, y no hay ningún ajuste que lo arregle. El motivo es que medio Django apunta al modelo de usuario: el panel, las sesiones, los permisos, los grupos y el historial. Cambiarlo significa reescribir todas esas claves ajenas en tablas que ya tienen datos.
Las salidas son dos, y ninguna es agradable. En desarrollo, borrar la base de datos y las migraciones y empezar de nuevo, que es lo que se hace en la práctica. En producción, una secuencia de migraciones manuales que la documentación de Django describe con la palabra "difícil".
Las tres estrategias#
| Estrategia | Cuándo | Coste |
|---|---|---|
Modelo aparte con OneToOne | El proyecto ya está en producción con auth.User | Una consulta más y un objeto que puede no existir |
AbstractUser | Casi siempre | Ninguno si se hace al principio |
AbstractBaseUser | Autenticación de verdad distinta | Hay que escribir bastante |
El perfil aparte#
class Perfil(models.Model):
usuario = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
telefono = models.CharField(max_length=20, blank=True)Es lo que se hacía antes de que existieran las otras dos y es la única opción cuando ya es tarde. Funciona y tiene dos pegas que la lección 14 ya midió: cada acceso al perfil es otra consulta si no se pide con select_related, y la vuelta de un OneToOne lanza una excepción en vez de devolver None cuando no existe, lo que obliga a crear el perfil en el registro y a acordarse de los usuarios antiguos.
AbstractUser#
La que se usa el noventa por ciento de las veces. Hereda todo lo que tenía User y deja añadir y redefinir:
from django.contrib.auth.models import AbstractUser
from django.db import models
class Usuario(AbstractUser):
email = models.EmailField('correo', unique=True)
telefono = models.CharField('teléfono', max_length=20, blank=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
def __str__(self):
return self.emailAUTH_USER_MODEL = 'cuentas.Usuario'Esa cadena es aplicacion.Modelo, no la ruta de importación.
Los dos atributos de en medio son los que cambian cómo se entra:
USERNAME_FIELD es el campo que identifica. Al ponerlo en email, se entra con el correo. Tiene que ser único, y por eso la línea de arriba lo redefine.
REQUIRED_FIELDS es lo que pide createsuperuser además del identificador y la contraseña. No se pone ahí ni USERNAME_FIELD ni la contraseña.
Medido:
USERNAME_FIELD: email | REQUIRED_FIELDS: ['username']
autenticar con el correo: jefe@ejemplo.es
autenticar con el nombre: NoneEl nombre de usuario ya no sirve para entrar, aunque siga existiendo. Si no lo quieres, se quita con username = None y REQUIRED_FIELDS = [], y entonces hace falta un gestor propio porque create_user espera un nombre.
AbstractBaseUser#
Solo trae la contraseña y la marca de última entrada. Todo lo demás —los permisos, las banderas, el gestor— hay que escribirlo o heredarlo de PermissionsMixin.
Es lo que hace falta cuando la autenticación no se parece a un usuario con contraseña: entrar solo con el teléfono y un código, identificarse contra un sistema externo, o no tener contraseña en absoluto.
Si dudas entre esta y AbstractUser, es AbstractUser.
El panel#
El modelo propio hay que registrarlo, y no con un ModelAdmin normal:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import Usuario
@admin.register(Usuario)
class UsuarioAdmin(UserAdmin):
list_display = ['email', 'username', 'is_staff']
ordering = ['email']
fieldsets = UserAdmin.fieldsets + ((None, {'fields': ['telefono']}),)login en el admin: True
/admin/cuentas/usuario/ -> 200
/admin/cuentas/usuario/1/change/ -> 200Heredar de UserAdmin y no de ModelAdmin es lo que trae el formulario de contraseña que cifra en lugar de guardar el texto, el enlace para cambiarla y la sección de permisos.
Y fíjate en el fieldsets = UserAdmin.fieldsets + (...): hay que sumar a los del padre, no sustituirlos, o los campos heredados desaparecen del formulario. Es el mismo razonamiento del class Meta(UserCreationForm.Meta) de la lección 32.
El ordering tampoco es decorativo: el de UserAdmin ordena por username, y si has quitado ese campo, el panel falla.