Ver índice de contenidos del libro

12.4. Utilizando usuarios

Una vez que ya tienes un usuario (normalmente mediante request.user, pero también puede ser por otros métodos, que se describirán en breve) dispondrás de una serie de campos de datos y métodos asociados al mismo. Los objetos de la clase AnonymousUser emulan parte de esta interfaz, pero no toda, por lo que es preferible comprobar el resultado de user.is_authenticated() antes de asumir de buena fe que nos encontramos ante un usuario legítimo.

Campos de los objetos User:

  • username (obligatorio) 30 caracteres como máximo. Sólo acepta caracteres alfanuméricos (letras, dígitos y el carácter subrayado).
  • first_name (opcional) 30 caracteres como máximo.
  • last_name (opcional) 30 caracteres como máximo.
  • email (opcional). Dirección de correo electrónico.
  • password (obligatorio). Un código de comprobación (hash), junto con otros metadatos de la contraseña. Django nunca almacena la contraseña en crudo. Véase la sección "Cambia contraseñas" para más información.
  • is_staff (booleano). Indica que el usuario puede acceder a las secciones de administración.
  • is_active (booleano). Indica que la cuenta puede ser usada para identificarse. Se puede poner a False para deshabilitar a un usuario sin tener que borrarlo de la tabla.
  • is_superuser (booleano). Señala que el usuario tiene todos los permisos, aún cuando no se le hayan asignado explícitamente.
  • last_login. Fecha y hora de la última vez que el usuario se identificó. Se asigna automáticamente a la fecha actual por defecto.
  • date_joined Fecha y hora en que fue creada esta cuenta de usuario. Se asigna automáticamente a la fecha actual en su momento.

Métodos de los objetos User:

  • is_authenticated(): siempre devuelve True para usuarios reales. Es una forma de determinar si el usuario se ha identificado. esto no implica que posea ningún permiso, y tampoco comprueba que la cuenta esté activa. Sólo indica que el usuario se ha identificado con éxito.
  • is_anonymous(): devuelve True sólo para usuarios anónimos, y False para usuarios "reales". En general, es preferible usar el método is_authenticated().
  • get_full_name(): devuelve la concatenación de los campos first_name y last_name, con un espacio en medio.
  • set_password(passwd): cambia la contraseña del usuario a la cadena de texto en claro indicada, realizando internamente las operaciones necesarias para calcular el código de comprobación o hash necesario. Este método no guarda el objeto User.
  • check_password(passwd): devuelve True si la cadena de texto en claro que se le pasa coincide con la contraseña del usuario. Realiza internamente las operaciones necesarias para calcular los códigos de comprobación o hash necesarios.
  • get_group_permissions(): devuelve una lista con los permisos que tiene un usuario, obtenidos a través del grupo o grupos a las que pertenezca.
  • get_all_permissions(): devuelve una lista con los permisos que tiene concedidos un usuario, ya sea a través de los grupos a los que pertenece o bien asignados directamente.
  • has_perm(perm): devuelve True si el usuario tiene el permiso indicado. El valor de perm está en el formato "package.codename". Si el usuario no está activo, siempre devolverá False.
  • has_perms(perm_list): devuelve True si el usuario tiene todos los permisos indicados. Si el usuario no está activo, siempre devolverá False.
  • has_module_perms(app_label): devuelve True si el usuario tiene algún permiso en la etiqueta de aplicación indicada, app_label. Si el usuario no está activo, siempre devolverá False.
  • get_and_delete_messages(): devuelve una lista de mensajes (objetos de la clase Message) de la cola del usuario, y los borra posteriormente.
  • email_user(subj, msg): envía un correo electrónico al usuario. El mensaje aparece como enviado desde la dirección indicada en el valor DEFAULT_FROM_EMAIL. Se le puede pasar un tercer parámetro opcional, from_email, para indicar otra dirección de remite distinta.
  • get_profile(): devuelve un perfil del usuario específico para el sitio. En la sección "Perfiles" se explicará con más detalle este método.

Por último, los objetos de tipo User mantienen dos campos de relaciones múltiples o muchos-a-muchos: Grupos y permisos (groups y permissions). Se puede acceder a estos objetos relacionados de la misma manera en que se usan otros campos múltiples:

# Set a user's groups:
myuser.groups = group_list
 
# Add a user to some groups:
myuser.groups.add(group1, group2,...)
 
# Remove a user from some groups:
myuser.groups.remove(group1, group2,...)
 
# Remove a user from all groups:
myuser.groups.clear()
 
# Permissions work the same way
myuser.permissions = permission_list
myuser.permissions.add(permission1, permission2, ...)
myuser.permissions.remove(permission1, permission2, ...)
myuser.permissions.clear()

12.4.1. Iniciar y cerrar sesión

Django proporciona vistas predefinidas para gestionar la entrada del usuario, (el momento en que se identifica), y la salida, (es decir, cuando cierra la sesión), además de otros trucos ingeniosos. Pero antes de entrar en detalles, veremos como hacer que los usuario puedan iniciar y cerrar la sesión "a mano". Django incluye dos funciones para realizar estas acciones, en el módulo django.contrib.auth: authenticate() y login().

Para autentificar un identificador de usuario y una contraseña, se utiliza la función authenticate(). esta función acepta dos parámetros , username y password, y devuelve un objeto de tipo User si la contraseña es correcta para el identificador de usuario. Si falla la comprobación (ya sea porque sea incorrecta la contraseña o porque sea incorrecta la identificación del usuario), la función devolverá None:

>>> from django.contrib import auth
>>> user = auth.authenticate(username='john', password='secret')
>>> if user is not None:
...     print "Correct!"
... else:
...     print "Oops, that's wrong!"

La llamada a authenticate() sólo verifica las credenciales del usuario. Todavía hay que realizar una llamada a login() para completar el inicio de sesión. La llamada a login() acepta un objeto de la clase HttpRequest y un objeto User y almacena el identificador del usuario en la sesión, usando el entorno de sesiones de Django.

El siguiente ejemplo muestra el uso de ambas funciones, authenticate() y login(), dentro de una vista:

from django.contrib import auth
 
def login(request):
    username = request.POST['username']
    password = request.POST['password']
    user = auth.authenticate(username=username, password=password)
    if user is not None and user.is_active:
        # Correct password, and the user is marked "active"
        auth.login(request, user)
        # Redirect to a success page.
        return HttpResponseRedirect("/account/loggedin/")
    else:
        # Show an error page
        return HttpResponseRedirect("/account/invalid/")

Para cerrar la sesión, se puede llamar a django.contrib.auth.logout() dentro de una vista. Necesita que se le pase como parámetro un objeto de tipo HttpRequest, y no devuelve ningún valor:

from django.contrib import auth
 
def logout(request):
    auth.logout(request)
    # Redirect to a success page.
    return HttpResponseRedirect("/account/loggedout/")

La llamada a logout() no produce ningún error, aun si no hubiera ningún usuario conectado.

En la práctica, no es normalmente necesario escribir tus propias funciones para realizar estas tareas; el sistema de autentificación viene con un conjunto de vistas predefinidas para ello.

El primer paso para utilizar las vistas de autentificación es mapearlas en tu URLconf. Necesitas modificar tu código hasta tener algo parecido a esto:

from django.contrib.auth.views import login, logout
 
urlpatterns = patterns('',
    # existing patterns here...
    (r'^accounts/login/$',  login),
    (r'^accounts/logout/$', logout),
)

/accounts/login/ y /accounts/logout/ son las URL por defecto que usa Django para estas vistas.

Por defecto, la vista de login utiliza la plantilla definida en registration/login.html (puedes cambiar el nombre de la plantilla utilizando un parámetro opcional, template_name). El formulario necesita contener un campo llamado username y otro llamado password. Una plantilla de ejemplo podría ser esta:

{% extends "base.html" %}
 
{% block content %}
 
  {% if form.errors %}
    <p class="error">Sorry, that's not a valid username or password</p>
  {% endif %}
 
  <form action='.' method='post'>
    <label for="username">User name:</label>
    <input type="text" name="username" value="" id="username">
    <label for="password">Password:</label>
    <input type="password" name="password" value="" id="password">
 
    <input type="submit" value="login" />
    <input type="hidden" name="next" value="{{ next|escape }}" />
  <form action='.' method='post'>
 
{% endblock %}

Si el usuario se identifica correctamente, su navegador será redirigido a /accounts/profile/. Puedes indicar una dirección distinta especificando un tercer campo (normalmente oculto) que se llame next, cuyo valor debe ser la URL a redireccionar después de la identificación. También puedes pasar este valor como un parámetro GET a la vista de identificación y se añadirá automáticamente su valor al contexto en una variable llamada next, que puedes incluir ahora en un campo oculto.

La vista de cierre de sesión se comporta de forma un poco diferente. Por defecto utiliza la plantilla definida en registration/logged_out.html (que normalmente contiene un mensaje del tipo "Ha cerrado su sesión"). No obstante, se puede llamar a esta vista con un parámetro extra, llamado next_page, que indicaría la vista a la que se debe redirigir una vez efectuado el cierre de la sesión.

12.4.2. Limitar el acceso a los usuarios identificados

Por supuesto, la razón de haber implementado todo este sistema es permitirnos limitar el acceso a determinadas partes de nuestro sitio.

La forma más simple y directa de limitar este acceso es comprobar el resultado de llamar a la función request.user.is_authenticated() y redirigir a una página de identificación, si procede:

from django.http import HttpResponseRedirect
 
def my_view(request):
    if not request.user.is_authenticated():
        return HttpResponseRedirect('/login/?next=%s' % request.path)
    # ...

O quizás mostrar un mensaje de error:

def my_view(request):
    if not request.user.is_authenticated():
        return render_to_response('myapp/login_error.html')
    # ...

Si se desea abreviar, se puede usar el decorador login_required sobre las vistas que nos interese proteger:

from django.contrib.auth.decorators import login_required
 
@login_required
def my_view(request):
    # ...

Esto es lo que hace el decorador login_required:

  • Si el usuario no está identificado, redirige a la dirección /accounts/login/, incluyendo la url actual como un parámetro con el nombre next, por ejemplo /accounts/login/?next=/polls/3/.
  • Si el usuario está identificado, ejecuta la vista sin ningún cambio. La vista puede asumir sin problemas que el usuario esta identificado correctamente

12.4.3.  Limitar el acceso a usuarios que pasan una prueba

Se puede limitar el acceso basándose en ciertos permisos o en algún otro tipo de prueba, o proporcionar una página de identificación distinta de la vista por defecto, y las dos cosas se hacen de manera similar.

La forma más cruda es ejecutar las pruebas que queremos hacer directamente en el código de la vista. Por ejemplo, para comprobar que el usuario está identificado y que, además, tenga asignado el permiso polls.can_vote (se explicará esto de los permisos con más detalle dentro de poco ) haríamos:

def vote(request):
    if request.user.is_authenticated() and request.user.has_perm('polls.can_vote')):
        # vote here
    else:
        return HttpResponse("You can't vote in this poll.")

De nuevo, Django proporciona una forma abreviada llamada user_passes_test. Requiere que se la pasen unos argumentos y genera un decorador especializado para cada situación en particular:

def user_can_vote(user):
    return user.is_authenticated() and user.has_perm("polls.can_vote")
 
@user_passes_test(user_can_vote, login_url="/login/")
def vote(request):
    # Code here can assume a logged-in user with the correct permission.
    ...

El decorador user_passes_test tiene un parámetro obligatorio: un objeto que se pueda llamar (normalmente una función) y que a su vez acepte como parámetro un objeto del tipo User, y devuelva True si el usuario puede acceder y False en caso contrario. Es importante destacar que user_passes_test no comprueba automáticamente que el usuario esté identificado; esa es una comprobación que se debe hacer explícitamente.

En este ejemplo, hemos usado también un segundo parámetro opcional, login_url, que te permite indicar la url de la página que el usuario debe utilizar para identificarse (/accounts/login/ por defecto).

Comprobar si un usuario posee un determinado permiso es una tarea muy frecuente, así que Django proporciona una forma abreviada para estos casos: El decorador permission_required(). Usando este decorador, el ejemplo anterior se podría codificar así:

from django.contrib.auth.decorators import permission_required
 
@permission_required('polls.can_vote', login_url="/login/")
def vote(request):
    # ...

El decorador permission_required() también acepta el parámetro opcional login_url, de nuevo con el valor /accounts/login/ en caso de omisión.

Advertencia Una de las preguntas más frecuentes en la lista de usuarios de Django trata de cómo limitar el acceso a una vista genérica. Para conseguirlo, tienes que usar un recubrimiento sencillo alrededor de la vista que quieres proteger, y apuntar en tu URLconf al recubrimiento en vez de a la vista genérica:

from dango.contrib.auth.decorators import login_required
from django.views.generic.list_detail import object_detail
 
@login_required
def limited_object_detail(*args, **kwargs):
    return object_detail(*args, **kwargs)

Puedes cambiar el decorador login_required por cualquier otro que quieras usar, como es lógico.

12.4.4. Gestionar usuarios, permisos y grupos

La forma más fácil de gestionar el sistema de autentificación es a través de la interfaz de administración admin. El Capítulo 6 describe como usar esta interfaz para modificar los datos de los usuarios y controlar sus permisos y accesos, y la mayor parte del tiempo esa es la forma más adecuada de gestión.

A veces, no obstante, hace falta un mayor control, y para eso podemos utilizar las llamadas a bajo nivel que describiremos en este capítulo.

12.4.4.1. Crear usuarios

Puedes crear usuarios con el método create_user:

>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user(username='john',
...                                 email='[email protected]',
...                                 password='glass onion')

En este momento, user es una instancia de la clase User, preparada para ser almacenada en la base de datos (create_user() no llama al método save()). Este te permite cambiar algunos de sus atributos antes de guardarlos, si quieres:

>>> user.is_staff = True
>>> user.save()

12.4.4.2. Cambia contraseñas

Puedes cambiar las contraseña de un usuario llamando a set_password():

>>> user = User.objects.get(username='john')
>>> user.set_password('goo goo goo joob')
>>> user.save()

No debes modificar directamente el atributo password, a no ser que tengas muy claro lo que estás haciendo. La contraseña se almacena en la base de datos en forma de código de comprobación (salted hash) y, por tanto, debe ser modificada sólo a través de este método.

Para ser más exactos, el atributo password de un objeto User es una cadena de texto con el siguiente formato:

hashtype$salt$hash

Es decir, el tipo de hash, el grano de sal (salt) y el código hash propiamente dicho, separados entre si por el carácter dolar ($).

El valor de hashtype puede ser sha1 (por defecto) o md5, el algoritmo usado para realizar una transformación hash de un solo sentido sobre la contraseña. El grano de sal es una cadena de texto aleatoria que se utiliza para aumentar la resistencia de esta codificación frente a un ataque por diccionario. Por ejemplo:

sha1$a1976$a36cc8cbf81742a8fb52e221aaeab48ed7f58ab4

Las funciones User.set_password() y User.check_password() manejan todos estos detalles y comprobaciones de forma transparente.

12.4.4.3. El alta del usuario

Podemos usar estas herramientas de bajo nivel para crear vistas que permitan al usuario darse de alta. Prácticamente todos los desarrolladores quieren implementar el alta del usuario a su manera, por lo que Django da la opción de crearte tu propia vista para ello. Afortunadamente, es muy fácil de hacer.

La forma más sencilla es escribir una pequeña vista que pregunte al usuario los datos que necesita y con ellos se cree directamente el usuario. Django proporciona un formulario prefabricado que se puede usar con este fin, como se muestra en el siguiente ejemplo:

from django import oldforms as forms
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from django.contrib.auth.forms import UserCreationForm
 
def register(request):
    form = UserCreationForm()
 
    if request.method == 'POST':
        data = request.POST.copy()
        errors = form.get_validation_errors(data)
        if not errors:
            new_user = form.save(data)
            return HttpResponseRedirect("/books/")
    else:
        data, errors = {}, {}
 
    return render_to_response("registration/register.html", {
        'form' : forms.FormWrapper(form, data, errors)
    })

Este formulario asume que existe una plantilla llamada registration/register.html. esa plantilla podría consistir en algo parecido a esto:

{% extends "base.html" %}
 
{% block title %}Create an account{% endblock %}
 
{% block content %}
  <h1>Create an account</h1>
  <form action="." method="post">
    {% if form.error_dict %}
      <p class="error">Please correct the errors below.</p>
    {% endif %}
 
    {% if form.username.errors %}
      {{ form.username.html_error_list }}
    {% endif %}
    <label for="id_username">Username:</label> {{ form.username }}
 
    {% if form.password1.errors %}
      {{ form.password1.html_error_list }}
    {% endif %}
    <label for="id_password1">Password: {{ form.password1 }}
 
    {% if form.password2.errors %}
      {{ form.password2.html_error_list }}
    {% endif %}
    <label for="id_password2">Password (again): {{ form.password2 }}
 
    <input type="submit" value="Create the account" />
  </label>
{% endblock %}

12.4.5. Usar información de autentificación en plantillas

El usuario actual, así como sus permisos, están disponiblesen el contexto de la plantilla cuando usas RequestContext (véase Capítulo 10).

Nota Técnicamente hablando, estas variables están disponibles en el contexto de la plantilla sólo si usas RequestContext y en la configuración está incluido el valor "django.core.context_processors.auth" en la opción TEMPLATE_CONTEXT_PROCESSORS, que es el valor que viene predefinido cuando se crea un proyecto. Como ya se comentó, véase el Capítulo 10 para más información.

Cuando se usa RequestContext, el usuario actual (ya sea una instancia de User o de AnonymousUser) es accesible en la plantilla con el nombre {{ user }}:

{% if user.is_authenticated %}
  <p>Welcome, {{ user.username }}. Thanks for logging in.</p>
{% else %}
  <p>Welcome, new user. Please log in.</p>
{% endif %}

Los permisos del usuario se almacenan en la variable {{ perms }}. En realidad, es una forma simplificada de acceder a un par de métodos sobre los permisos que veremos en breve.

Hay dos formas de usar este objeto perms. Puedes usar {{ perms.polls }} para comprobar si un usuario tienen algún permiso para una determinada aplicación, o se puede usar una forma más específica, como {{ perms.polls.can_vote }}, para comprobar si el usuario tiene concedido un permiso en concreto.

Por lo tanto, se pueden usar estas comprobaciones en sentencias {% if %}:

{% if perms.polls %}
  <p>You have permission to do something in the polls app.</p>
  {% if perms.polls.can_vote %}
    <p>You can vote!</p>
  {% endif %}
{% else %}
  <p>You don't have permission to do anything in the polls app.</p>
{% endif %}