Django: ¿señal cuando el usuario inicia sesión?

82

En mi aplicación Django, necesito comenzar a ejecutar algunos trabajos en segundo plano periódicos cuando un usuario inicia sesión y dejar de ejecutarlos cuando el usuario cierra sesión, así que estoy buscando una forma elegante de

  1. ser notificado de un inicio de sesión / cierre de sesión de usuario
  2. consultar el estado de inicio de sesión del usuario

Desde mi perspectiva, la solución ideal sería

  1. una señal enviada por todos django.contrib.auth.views.loginy... views.logout
  2. un método django.contrib.auth.models.User.is_logged_in(), análogo ... User.is_active()o... User.is_authenticated()

Django 1.1.1 no tiene eso y soy reacio a parchear la fuente y agregarla (no estoy seguro de cómo hacerlo, de todos modos).

Como solución temporal, agregué un is_logged_incampo booleano al modelo UserProfile que se borra de manera predeterminada, se establece la primera vez que el usuario ingresa a la página de destino (definida por LOGIN_REDIRECT_URL = '/') y se consulta en solicitudes posteriores. Lo agregué a UserProfile, por lo que no tengo que derivar y personalizar el modelo de usuario incorporado solo para ese propósito.

No me gusta esta solución. Si el usuario hace clic explícitamente en el botón de cierre de sesión, puedo borrar la marca, pero la mayoría de las veces, los usuarios simplemente abandonan la página o cierran el navegador; limpiar la bandera en estos casos no me parece sencillo. Además (aunque eso es más bien una nitidez del modelo de datos), is_logged_inno pertenece al UserProfile, sino al modelo User.

¿Alguien puede pensar en enfoques alternativos?

ssc
fuente
4
Considere seleccionar una nueva respuesta. El actualmente aceptado es una muy mala elección a la luz de la señal agregada en 1.3.
Bryson
1
Tienes razón; cambió la respuesta aceptada.
ssc

Respuestas:

151

Puedes usar una señal como esta (puse la mía en models.py)

from django.contrib.auth.signals import user_logged_in


def do_stuff(sender, user, request, **kwargs):
    whatever...

user_logged_in.connect(do_stuff)

Consulte los documentos de django: https://docs.djangoproject.com/en/dev/ref/contrib/auth/#module-django.contrib.auth.signals y aquí http://docs.djangoproject.com/en/dev/ temas / señales /

PhoebeB
fuente
8
Ahora que Django 1.3 ofrece estas señales, es una solución mucho mejor que envolver la llamada a iniciar / cerrar sesión. También significa que si configura nuevas formas de iniciar sesión, por ejemplo, inicios de sesión en Facebook / Twitter / OpenID, seguirán funcionando.
Jordan Reiter
9
En lugar de poner eso en su models.pylugar, sugiero colocar el código signals.pye importarlo automáticamente en el __init__.pyarchivo de los módulos .
Daniel Sokolowski
¿Cómo usar esta señal para ejecutar javascript al iniciar y cerrar sesión?
Ashish Gupta
15

Además de la respuesta de @PhoebeB: también puede usar el @receiverdecorador como este:

from django.contrib.auth.signals import user_logged_in
from django.dispatch import receiver

@receiver(user_logged_in)
def post_login(sender, user, request, **kwargs):
    ...do your stuff..

Y si lo pones signals.pyen el directorio de tu aplicación, agrega esto a apps.py:

class AppNameConfig(AppConfig):
    ...
    def ready(self):
        import app_name.signals
bershika
fuente
13

Una opción podría ser envolver las vistas de inicio / cierre de sesión de Django con las suyas. Por ejemplo:

from django.contrib.auth.views import login, logout

def my_login(request, *args, **kwargs):
    response = login(request, *args, **kwargs)
    #fire a signal, or equivalent
    return response

def my_logout(request, *args, **kwargs):
    #fire a signal, or equivalent
    return logout(request, *args, **kwargs)

Luego usa estas vistas en su código en lugar de Django, y listo.

Con respecto a la consulta del estado de inicio de sesión, es bastante simple si tiene acceso al objeto de solicitud; simplemente verifique el atributo de usuario de la solicitud para ver si es un usuario registrado o el usuario anónimo, y bingo. Para citar la documentación de Django :

if request.user.is_authenticated():
    # Do something for logged-in users.
else:
    # Do something for anonymous users.

Si no tiene acceso al objeto de solicitud, será difícil determinar si el usuario actual está conectado.

Editar:

Desafortunadamente, nunca podrá obtener la User.is_logged_in()funcionalidad; es una limitación del protocolo HTTP. Sin embargo, si hace algunas suposiciones, es posible que pueda acercarse a lo que desea.

Primero, ¿por qué no puede obtener esa funcionalidad? Bueno, no se puede notar la diferencia entre alguien que cierra el navegador o alguien que pasa un tiempo en una página antes de buscar una nueva. No hay forma de saber a través de HTTP cuándo alguien abandona el sitio o cierra el navegador.

Entonces tienes dos opciones aquí que no son perfectas:

  1. Utilice el unloadevento de Javascript para detectar cuándo un usuario abandona una página. Sin embargo, tendría que escribir una lógica cuidadosa para asegurarse de no cerrar la sesión de un usuario cuando todavía está navegando por su sitio.
  2. Active la señal de cierre de sesión cada vez que un usuario inicie sesión, solo para estar seguro. También cree un trabajo cron que se ejecute con bastante frecuencia para eliminar las sesiones caducadas: cuando se elimina una sesión caducada, verifique que el usuario de la sesión (si no es anónimo) no tenga más sesiones activas, en cuyo caso dispara la señal de cierre de sesión.

Estas soluciones son complicadas y no son ideales, pero desafortunadamente son lo mejor que puede hacer.

ShZ
fuente
Esto todavía no aborda la situación de "la mayoría de las veces, los usuarios simplemente abandonan la página o cierran el navegador".
Joel L
Gracias por su respuesta, por supuesto, envolver el inicio de sesión / cierre de sesión me evita parchear la fuente, debería haberlo pensado yo mismo. Sin embargo, una pequeña corrección: si la señal se envía en my_login antes de que se llame al inicio de sesión, el usuario sigue siendo anónimo en el controlador de señales. Mejor (no creo que esto se formateará correctamente): def my_login (solicitud): respuesta = inicio de sesión (solicitud) # disparar una señal, o una respuesta de retorno equivalente Además, ya estoy usando is_authenticated, solo tenía la sensación de que necesitaría más que eso. Sin embargo, hasta ahora, mi nueva pieza is_logged_in en el modelo de datos se encuentra allí sin usar.
ssc
Buenos puntos por todos lados. Supongo que realmente no abordé las is_logged_incosas muy bien (disculpas, supongo que no hice un gran trabajo al leer la publicación), pero actualicé la respuesta para ofrecer la ayuda que puedo en esa área. . Es un problema un poco imposible, desafortunadamente.
ShZ
3

una solución rápida para esto sería, en el _ _ init _ _.py de su aplicación coloque el siguiente código:

from django.contrib.auth.signals import user_logged_in
from django.dispatch import receiver


@receiver(user_logged_in)
def on_login(sender, user, request, **kwargs):
    print('User just logged in....')
Rui Lima
fuente
1

La única forma confiable (que también detecta cuando el usuario ha cerrado el navegador) es actualizar algún last_requestcampo cada vez que el usuario carga una página.

También podría tener una solicitud AJAX periódica que haga ping al servidor cada x minutos si el usuario tiene una página abierta.

Luego, tenga un único trabajo en segundo plano que obtenga una lista de usuarios recientes, cree trabajos para ellos y borre los trabajos para los usuarios que no están presentes en esa lista.

Joel L
fuente
Esta es la mejor manera de medir si un usuario está conectado o no. Básicamente, tendrá que mantener una lista de usuarios registrados y verificar cuál fue su último acceso y decidir un tiempo de espera. Si configura el tiempo de espera en algo así como 10 minutos, y luego también hace que cada página web llame a una solicitud Ajax cada 5 minutos aproximadamente mientras una página está activa, eso debería mantener el estado actualizado.
Jordan Reiter
1

Inferir el cierre de sesión, en lugar de hacer que hagan clic explícitamente en un botón (lo que nadie hace), significa elegir una cantidad de tiempo de inactividad que equivale a "cerrar sesión". phpMyAdmin usa un valor predeterminado de 15 minutos, algunos sitios bancarios usan tan solo 5 minutos.

La forma más sencilla de implementar esto sería cambiar la duración de la cookie. Puede hacer esto para todo su sitio especificando settings.SESSION_COOKIE_AGE. Alternativamente, puede cambiarlo por usuario (basado en un conjunto arbitrario de criterios) usando HttpResponse.setcookie(). Puede centralizar este código creando su propia versión render_to_response()y estableciendo la duración de cada respuesta.

Peter Rowell
fuente
0

Idea aproximada: podría usar middleware para esto. Este middleware podría procesar solicitudes y disparar señales cuando se solicita una URL relevante. También podría procesar respuestas y señales de disparo cuando una determinada acción realmente tuvo éxito.

Tomasz Zieliński
fuente