Django: Signal, wenn sich der Benutzer anmeldet?

82

In meiner Django-App muss ich einige regelmäßige Hintergrundjobs ausführen, wenn sich ein Benutzer anmeldet, und sie nicht mehr ausführen, wenn sich der Benutzer abmeldet. Daher suche ich nach einer eleganten Möglichkeit, dies zu tun

  1. Benachrichtigung über ein Benutzer-Login / Logout
  2. Benutzeranmeldestatus abfragen

Aus meiner Sicht wäre die ideale Lösung

  1. ein von jedem django.contrib.auth.views.loginund gesendetes Signal... views.logout
  2. eine Methode django.contrib.auth.models.User.is_logged_in(), analog zu ... User.is_active()oder... User.is_authenticated()

Django 1.1.1 hat das nicht und ich zögere es, die Quelle zu patchen und hinzuzufügen (ich bin mir sowieso nicht sicher, wie ich das machen soll).

Als vorübergehende Lösung habe ich is_logged_indem UserProfile-Modell ein boolesches Feld hinzugefügt , das standardmäßig gelöscht wird, beim ersten Aufrufen der Zielseite durch den Benutzer festgelegt wird (definiert durch LOGIN_REDIRECT_URL = '/') und in nachfolgenden Anforderungen abgefragt wird. Ich habe es zu UserProfile hinzugefügt, damit ich das integrierte Benutzermodell nicht nur für diesen Zweck ableiten und anpassen muss.

Ich mag diese Lösung nicht. Wenn der Benutzer explizit auf die Schaltfläche zum Abmelden klickt, kann ich das Flag löschen. In den meisten Fällen verlassen Benutzer jedoch nur die Seite oder schließen den Browser. Das Löschen der Flagge in diesen Fällen scheint mir nicht einfach zu sein. Außerdem (das ist aber eher Datenmodell-Klarheit, Nitpicking) is_logged_ingehört nicht in das UserProfile, sondern in das User-Modell.

Kann sich jemand alternative Ansätze vorstellen?

ssc
quelle
4
Bitte erwägen Sie die Auswahl einer neuen Antwort. Die derzeit akzeptierte ist angesichts des in 1.3 hinzugefügten Signals eine sehr schlechte Wahl.
Bryson
1
Du hast recht; änderte die akzeptierte Antwort.
SSC

Antworten:

151

Sie können ein solches Signal verwenden (ich habe mein Signal in models.py eingefügt)

from django.contrib.auth.signals import user_logged_in


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

user_logged_in.connect(do_stuff)

Siehe Django-Dokumente: https://docs.djangoproject.com/de/dev/ref/contrib/auth/#module-django.contrib.auth.signals und hier http://docs.djangoproject.com/en/dev/ Themen / Signale /

PhoebeB
quelle
8
Jetzt, da Django 1.3 diese Signale bietet, ist es eine viel bessere Lösung, als den Aufruf zum Anmelden / Abmelden zu beenden. Dies bedeutet auch, dass neue Anmeldemethoden, z. B. Facebook- / Twitter- / OpenID-Anmeldungen, weiterhin funktionieren.
Jordan Reiter
9
Anstatt dies models.pystattdessen einzugeben, schlage ich vor, den Code signals.pyeinzufügen und ihn automatisch in die __init__.pyDatei des Moduls zu importieren .
Daniel Sokolowski
Wie verwende ich dieses Signal, um beim An- und Abmelden Javascript auszuführen?
Ashish Gupta
14

Zusätzlich zu @PhoebeB Antwort: Sie können auch @receiverDekorateur wie folgt verwenden :

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..`

Und wenn Sie es in signals.pyIhr App-Verzeichnis einfügen, fügen Sie dies hinzu zu app.py:

def ready(self):
    import app_name.signals`
Bershika
quelle
13

Eine Möglichkeit könnte darin bestehen, die Anmelde- / Abmeldeansichten von Django mit Ihren eigenen zu versehen. Zum Beispiel:

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)

Sie verwenden diese Ansichten dann in Ihrem Code anstelle von Djangos und voila.

In Bezug auf die Abfrage des Anmeldestatus ist es ziemlich einfach, wenn Sie Zugriff auf das Anforderungsobjekt haben. Überprüfen Sie einfach das Benutzerattribut der Anfrage, um festzustellen, ob es sich um einen registrierten Benutzer oder einen anonymen Benutzer handelt, und Bingo. So zitieren Sie die Django-Dokumentation :

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

Wenn Sie keinen Zugriff auf das Anforderungsobjekt haben, ist es schwierig festzustellen, ob der aktuelle Benutzer angemeldet ist.

Bearbeiten:

Leider werden Sie nie in der Lage sein, User.is_logged_in()Funktionen zu erhalten - dies ist eine Einschränkung des HTTP-Protokolls. Wenn Sie jedoch einige Annahmen treffen, können Sie sich möglicherweise dem nähern, was Sie wollen.

Erstens, warum können Sie diese Funktionalität nicht erhalten? Nun, Sie können den Unterschied zwischen jemandem, der den Browser schließt, oder jemandem, der eine Weile auf einer Seite verbringt, bevor er eine neue abruft, nicht erkennen. Über HTTP kann nicht festgestellt werden, wann jemand die Site tatsächlich verlässt oder den Browser schließt.

Sie haben hier also zwei Möglichkeiten, die nicht perfekt sind:

  1. Verwenden Sie das unloadEreignis von Javascript, um zu erfassen, wann ein Benutzer eine Seite verlässt. Sie müssten jedoch eine sorgfältige Logik schreiben, um sicherzustellen, dass Sie einen Benutzer nicht abmelden, wenn er noch auf Ihrer Website navigiert .
  2. Feuern Sie das Abmeldesignal ab, wenn sich ein Benutzer anmeldet, nur um sicherzugehen. Erstellen Sie auch einen Cron-Job, der ziemlich häufig ausgeführt wird, um abgelaufene Sitzungen zu löschen. Wenn eine abgelaufene Sitzung gelöscht wird, überprüfen Sie, ob der Benutzer der Sitzung (falls nicht anonym) keine aktiven Sitzungen mehr hat. In diesem Fall lösen Sie das Abmeldesignal aus.

Diese Lösungen sind chaotisch und nicht ideal, aber leider das Beste, was Sie tun können.

ShZ
quelle
Dies betrifft immer noch nicht die Situation "Meistens verlassen Benutzer einfach die Seite oder schließen den Browser".
Joel L
Vielen Dank für Ihre Antwort. Das Einpacken von Login / Logout erspart mir natürlich das Patchen der Quelle. Eine kleine Korrektur: Wenn das Signal in my_login gesendet wird, bevor die Anmeldung aufgerufen wird, ist der Benutzer im Signalhandler immer noch anonym. Besser (glaube nicht, dass dies richtig formatiert wird): def my_login (Anfrage): response = login (Anfrage) # ein Signal oder eine gleichwertige Rückantwort auslösen. Außerdem verwende ich bereits is_authenticated. Ich hatte nur das Gefühl, dass ich es brauchen würde mehr als das. Bisher befindet sich mein neues Stück is_logged_in im Datenmodell jedoch unbenutzt.
SSC
Rundum gute Punkte. Ich glaube, ich habe das is_logged_inZeug überhaupt nicht wirklich gut angesprochen (Entschuldigung, ich glaube, ich habe den Beitrag nicht besonders gut gelesen), aber ich habe die Antwort aktualisiert, um anzubieten, was ich in diesem Bereich helfen kann . Das ist leider ein unmögliches Problem.
ShZ
3

Eine schnelle Lösung hierfür wäre, im _ _ init _ _.py Ihrer App den folgenden Code zu platzieren:

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
quelle
1

Die einzige zuverlässige Möglichkeit (die auch erkennt, wenn der Benutzer den Browser geschlossen hat) besteht darin last_request, jedes Mal, wenn der Benutzer eine Seite lädt, ein Feld zu aktualisieren .

Sie können auch eine regelmäßige AJAX-Anforderung erhalten, die den Server alle x Minuten anpingt, wenn der Benutzer eine Seite geöffnet hat.

Verfügen Sie dann über einen einzelnen Hintergrundjob, der eine Liste der zuletzt verwendeten Benutzer abruft, Jobs für sie erstellt und die Jobs für Benutzer löscht, die in dieser Liste nicht vorhanden sind.

Joel L.
quelle
Dies ist der beste Weg, um zu messen, ob ein Benutzer angemeldet ist oder nicht. Grundsätzlich müssen Sie eine Liste der angemeldeten Benutzer führen, den letzten Zugriff überprüfen und eine Zeitüberschreitung festlegen. Wenn Sie das Zeitlimit auf etwa 10 Minuten einstellen und dann jede Webseite etwa alle 5 Minuten eine Ajax-Anfrage aufrufen lässt, während eine Seite aktiv ist, sollte dies den Status auf dem neuesten Stand halten.
Jordan Reiter
1

Wenn Sie auf das Abmelden schließen, anstatt explizit auf eine Schaltfläche zu klicken (was niemand tut), müssen Sie eine Leerlaufzeit auswählen, die "Abgemeldet" entspricht. phpMyAdmin verwendet einen Standardwert von 15 Minuten, einige Bankseiten benötigen nur 5 Minuten.

Die einfachste Möglichkeit, dies zu implementieren, besteht darin, die Lebensdauer der Cookies zu ändern. Sie können dies für Ihre gesamte Site tun, indem Sie angeben settings.SESSION_COOKIE_AGE. Alternativ können Sie es auf Benutzerbasis (basierend auf einem beliebigen Satz von Kriterien) mithilfe von ändern HttpResponse.setcookie(). Sie können diesen Code zentralisieren, indem Sie eine eigene Version von erstellen render_to_response()und die Lebensdauer für jede Antwort festlegen.

Peter Rowell
quelle
0

Grobe Idee - Sie könnten dafür Middleware verwenden. Diese Middleware kann Anforderungen verarbeiten und ein Signal auslösen, wenn eine relevante URL angefordert wird. Es könnte auch Reaktionen und Feuersignale verarbeiten, wenn eine bestimmte Aktion tatsächlich erfolgreich war.

Tomasz Zieliński
quelle