Django: sygnał, kiedy użytkownik się loguje?

83

W mojej aplikacji Django muszę zacząć uruchamiać kilka okresowych zadań w tle, gdy użytkownik się loguje, i przestać je uruchamiać, gdy użytkownik się wyloguje, więc szukam eleganckiego sposobu

  1. otrzymywać powiadomienia o logowaniu / wylogowaniu użytkownika
  2. zapytaj o stan logowania użytkownika

Z mojej perspektywy idealnym rozwiązaniem byłoby

  1. sygnał wysłany przez każdy django.contrib.auth.views.logini... views.logout
  2. metoda django.contrib.auth.models.User.is_logged_in()analogiczna do ... User.is_active()lub... User.is_authenticated()

Django 1.1.1 tego nie ma i niechętnie poprawiam źródła i dodam je (zresztą nie wiem jak to zrobić).

Jako rozwiązanie tymczasowe dodałem is_logged_indo modelu UserProfile pole boolowskie, które jest domyślnie wyczyszczone, jest ustawiane przy pierwszym wejściu użytkownika na stronę docelową (zdefiniowane przez LOGIN_REDIRECT_URL = '/') i jest odpytywane w kolejnych żądaniach. Dodałem go do UserProfile, więc nie muszę wyprowadzać i dostosowywać wbudowanego modelu użytkownika tylko do tego celu.

Nie podoba mi się to rozwiązanie. Jeśli użytkownik wyraźnie kliknie przycisk wylogowania, mogę wyczyścić flagę, ale w większości przypadków użytkownicy po prostu opuszczają stronę lub zamykają przeglądarkę; Usunięcie flagi w takich przypadkach nie wydaje mi się proste. Poza tym (jest to raczej szukanie jasności modelu danych), is_logged_innie należy do UserProfile, ale do modelu User.

Czy ktoś może pomyśleć o alternatywnych podejściach?

ssc
źródło
4
Rozważ wybranie nowej odpowiedzi. Obecnie akceptowany jest bardzo złym wyborem w świetle sygnału dodanego w 1.3.
Bryson
1
Masz rację; zmienił zaakceptowaną odpowiedź.
ssc

Odpowiedzi:

153

Możesz użyć takiego sygnału (umieściłem mój w 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)

Zobacz dokumentację django: https://docs.djangoproject.com/en/dev/ref/contrib/auth/#module-django.contrib.auth.signals i tutaj http://docs.djangoproject.com/en/dev/ tematy / sygnały /

PhoebeB
źródło
8
Teraz, gdy Django 1.3 oferuje te sygnały, jest to znacznie lepsze rozwiązanie niż zawijanie wywołania logowania / wylogowania. Oznacza to również, że jeśli skonfigurujesz nowe sposoby logowania - na przykład loginy Facebook / Twitter / OpenID - będą one nadal działać.
Jordan Reiter
9
Zamiast tego models.pyzamiast tego sugeruję umieszczenie kodu signals.pyi automatyczne zaimportowanie go do __init__.pypliku modułów .
Daniel Sokolowski
jak używać tego sygnału do uruchamiania javascript podczas logowania i wylogowywania?
Ashish Gupta
16

Oprócz odpowiedzi @PhoebeB: możesz również użyć @receiverdekoratora w ten sposób:

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

A jeśli umieścisz to signals.pyw katalogu swojej aplikacji, dodaj to do apps.py:

class AppNameConfig(AppConfig):
    ...
    def ready(self):
        import app_name.signals
bershika
źródło
13

Jedną z opcji może być zawinięcie widoków logowania / wylogowania Django własnymi. Na przykład:

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)

Następnie używasz tych widoków w swoim kodzie, a nie w Django, i voila.

Jeśli chodzi o sprawdzanie statusu logowania, jest to całkiem proste, jeśli masz dostęp do obiektu żądania; po prostu sprawdź atrybut użytkownika żądania, aby zobaczyć, czy jest to zarejestrowany użytkownik, czy anonimowy użytkownik i bingo. Cytując dokumentację Django :

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

Jeśli nie masz dostępu do obiektu żądania, określenie, czy bieżący użytkownik jest zalogowany, będzie trudne.

Edytować:

Niestety, nigdy nie będziesz w stanie uzyskać User.is_logged_in()funkcjonalności - jest to ograniczenie protokołu HTTP. Jeśli jednak przyjmiesz kilka założeń, możesz być w stanie zbliżyć się do tego, czego chcesz.

Po pierwsze, dlaczego nie możesz uzyskać tej funkcji? Cóż, nie możesz odróżnić kogoś, kto zamknął przeglądarkę, a kimś, kto spędził trochę czasu na stronie przed pobraniem nowej. Nie ma sposobu, aby stwierdzić przez HTTP, kiedy ktoś faktycznie opuści witrynę lub zamknie przeglądarkę.

Masz więc dwie opcje, które nie są idealne:

  1. Użyj unloadzdarzenia JavaScript, aby wychwycić, kiedy użytkownik opuszcza stronę. Musisz jednak napisać ostrożną logikę, aby upewnić się, że nie wylogowujesz użytkownika, gdy nadal nawiguje on po Twojej witrynie.
  2. Uruchom sygnał wylogowania za każdym razem, gdy użytkownik się zaloguje, dla pewności. Utwórz również zadanie cron, które działa dość często, aby opróżnić wygasłe sesje - po usunięciu wygasłej sesji sprawdź, czy użytkownik sesji (jeśli nie jest anonimowy) nie ma więcej aktywnych sesji, w takim przypadku uruchomisz sygnał wylogowania.

Te rozwiązania są niechlujne i nie są idealne, ale niestety są najlepsze, co możesz zrobić.

ShZ
źródło
To nadal nie rozwiązuje problemu „w większości przypadków użytkownicy po prostu opuszczają stronę lub zamykają przeglądarkę”.
Joel L
Dziękuję za odpowiedź, oczywiście zawijanie login / wylogowanie uchroni mnie przed łataniem źródła, sam powinienem o tym pomyśleć. Jednak mała poprawka: jeśli sygnał jest wysyłany w my_login przed wywołaniem logowania, użytkownik jest nadal anonimowy w obsłudze sygnału. Lepiej (nie myśl, że to będzie poprawnie sformatowane): def my_login (request): response = login (request) # wystrzel sygnał lub równoważną odpowiedź zwrotną Ponadto używam już is_authenticated, po prostu miałem wrażenie, że będę potrzebować więcej niż to. Jak dotąd jednak mój nowy fragment is_logged_in w modelu danych jest tam nieużywany.
ssc
Dobre punkty dookoła. Wydaje mi się, że nie odniosłem się do is_logged_inrzeczy zbyt dobrze (przepraszam, myślę, że nie spisałem się dobrze czytając post), ale zaktualizowałem odpowiedź, aby zaoferować, jaką mogę pomóc w tej dziedzinie . Niestety jest to trochę niemożliwy problem.
ShZ
3

szybkim rozwiązaniem byłoby umieszczenie w _ _ init _ _.py swojej aplikacji następującego kodu:

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
źródło
1

Jedynym niezawodnym sposobem (który również wykrywa, kiedy użytkownik zamknął przeglądarkę) jest aktualizacja jakiegoś last_requestpola za każdym razem, gdy użytkownik ładuje stronę.

Możesz również mieć okresowe żądanie AJAX, które pinguje serwer co x minut, jeśli użytkownik ma otwartą stronę.

Następnie wykonaj jedno zadanie w tle, które pobierze listę ostatnich użytkowników, utwórz dla nich zadania i wyczyść zadania dla użytkowników, których nie ma na tej liście.

Joel L.
źródło
To najlepszy sposób na sprawdzenie, czy użytkownik jest zalogowany, czy nie. Zasadniczo będziesz musiał zachować listę zalogowanych użytkowników i sprawdzić, jaki był ich ostatni dostęp, i zdecydować o limicie czasu. Jeśli ustawisz limit czasu na około 10 minut, a następnie każda strona internetowa wywoła żądanie Ajax co około 5 minut, gdy strona jest aktywna, powinno to zapewnić aktualność stanu.
Jordan Reiter
1

Wnioskowanie o wylogowaniu, w przeciwieństwie do wyraźnego kliknięcia przycisku (czego nikt nie robi), oznacza wybranie ilości czasu bezczynności, która jest równoznaczna z „wylogowaniem”. phpMyAdmin używa domyślnie 15 minut, niektóre strony bankowe zajmują tylko 5 minut.

Najprostszym sposobem wdrożenia tego byłoby zmiana czasu życia pliku cookie. Możesz to zrobić dla całej witryny, określając settings.SESSION_COOKIE_AGE. Alternatywnie możesz zmienić to dla każdego użytkownika (na podstawie dowolnego zestawu kryteriów), używając HttpResponse.setcookie(). Możesz scentralizować ten kod, tworząc własną wersję render_to_response()i ustawiając okres istnienia dla każdej odpowiedzi.

Peter Rowell
źródło
0

Zgrubny pomysł - możesz do tego użyć oprogramowania pośredniczącego. To oprogramowanie pośredniczące może przetwarzać żądania i uruchamiać sygnał, gdy żądany jest odpowiedni adres URL. Może również przetwarzać odpowiedzi i sygnał pożaru, gdy dane działanie faktycznie się powiedzie.

Tomasz Zieliński
źródło