Django: сигнализировать, когда пользователь входит в систему?

83

В моем приложении Django мне нужно запускать несколько периодических фоновых заданий, когда пользователь входит в систему, и прекращать их выполнение, когда пользователь выходит из системы, поэтому я ищу элегантный способ

  1. получать уведомления о входе / выходе пользователя
  2. запросить статус входа пользователя

С моей точки зрения, идеальным решением было бы

  1. сигнал, посланный каждым django.contrib.auth.views.loginи... views.logout
  2. метод django.contrib.auth.models.User.is_logged_in(), аналогичный ... User.is_active()или... User.is_authenticated()

В Django 1.1.1 этого нет, и я не хочу исправлять исходный код и добавлять его (все равно не знаю, как это сделать).

В качестве временного решения я добавил is_logged_inлогическое поле в модель UserProfile, которое очищается по умолчанию, устанавливается при первом переходе пользователя на целевую страницу (определенную LOGIN_REDIRECT_URL = '/') и запрашивается в последующих запросах. Я добавил его в UserProfile, поэтому мне не нужно создавать и настраивать встроенную модель User только для этой цели.

Мне не нравится это решение. Если пользователь явно нажимает кнопку выхода, я могу снять флажок, но в большинстве случаев пользователи просто покидают страницу или закрывают браузер; снятие флажка в этих случаях мне не кажется простым. Кроме того (это скорее придирка к ясности модели данных), is_logged_inэто относится не к UserProfile, а к модели User.

Кто-нибудь может придумать альтернативные подходы?

ssc
источник
4
Пожалуйста, подумайте о выборе нового ответа. В настоящее время принятый вариант - очень плохой выбор в свете сигнала, добавленного в 1.3.
Bryson
1
Вы правы; изменил принятый ответ.
ssc

Ответы:

153

Вы можете использовать такой сигнал (я поместил свой в 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)

См. Django docs: https://docs.djangoproject.com/en/dev/ref/contrib/auth/#module-django.contrib.auth.signals и здесь http://docs.djangoproject.com/en/dev/ темы / сигналы /

ФибиБ
источник
8
Теперь, когда Django 1.3 предлагает эти сигналы, это гораздо лучшее решение, чем упаковка вызова для входа / выхода. Это также означает, что если вы настроите новые способы входа в систему - например, вход в Facebook / Twitter / OpenID - они все равно будут работать.
Jordan Reiter
9
Вместо того, чтобы вставлять это models.pyвместо этого, я предлагаю поместить код signals.pyи автоматически импортировать его в __init__.pyфайл модулей .
Даниэль Соколовски
как использовать этот сигнал для запуска javascript при входе в систему и выходе из системы?
Ашиш Гупта
16

В дополнение к ответу @PhoebeB: вы также можете использовать @receiverтакой декоратор:

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

И если вы поместите его signals.pyв свой каталог приложения, добавьте это в apps.py:

class AppNameConfig(AppConfig):
    ...
    def ready(self):
        import app_name.signals
бершика
источник
13

Один из вариантов может заключаться в том, чтобы обернуть представления входа / выхода Django своими собственными. Например:

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)

Затем вы используете эти представления в своем коде, а не в Django, и вуаля.

Что касается запроса статуса входа в систему, это довольно просто, если у вас есть доступ к объекту запроса; просто проверьте атрибут пользователя запроса, чтобы узнать, является ли он зарегистрированным пользователем или анонимным пользователем, и бинго. Процитируем документацию Django :

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

Если у вас нет доступа к объекту запроса, то определить, вошел ли в систему текущий пользователь, будет сложно.

Редактировать:

К сожалению, вы никогда не получите User.is_logged_in() функциональность - это ограничение протокола HTTP. Однако если вы сделаете несколько предположений, вы сможете приблизиться к тому, чего хотите.

Во-первых, почему вы не можете получить эту функциональность? Ну, вы не можете отличить того, кто закрывает браузер, или кто-то проводит некоторое время на странице, прежде чем загрузить новую. Невозможно узнать через HTTP, когда кто-то действительно покидает сайт или закрывает браузер.

Итак, у вас есть два варианта, которые не идеальны:

  1. Используйте unloadсобытие Javascript, чтобы поймать, когда пользователь покидает страницу. Однако вам придется написать некоторую осторожную логику, чтобы убедиться, что вы не выходите из системы, когда они все еще перемещаются по вашему сайту.
  2. На всякий случай запускайте сигнал выхода из системы всякий раз, когда пользователь входит в систему. Также создайте задание cron, которое выполняется довольно часто, чтобы очистить просроченные сеансы - при удалении истекшего сеанса убедитесь, что у пользователя сеанса (если он не анонимный) больше нет активных сеансов, и в этом случае вы запускаете сигнал выхода.

Эти решения беспорядочные и не идеальные, но, к сожалению, они лучшее, что вы можете сделать.

ШЗ
источник
Это все еще не решает ситуацию «в большинстве случаев пользователи просто покидают страницу или закрывают браузер».
Joel L
Спасибо за ваш ответ, конечно, упаковка входа / выхода избавляет меня от исправления источника, я должен подумать об этом. Однако небольшое исправление: если сигнал отправляется в my_login до вызова входа в систему, пользователь все еще остается анонимным в обработчике сигнала. Лучше (не думаю, что это будет правильно отформатировано): def my_login (request): response = login (request) # запустить сигнал или эквивалентный ответ возврата Кроме того, я уже использую is_authenticated, у меня просто было ощущение, что мне понадобится больше чем это. Однако пока что моя новая часть is_logged_in в модели данных остается неиспользованной.
ssc 03
Хорошие моменты во всем. Думаю, я вообще не is_logged_inочень хорошо разбирался в этом вопросе (извиняюсь, я полагаю, что я не очень хорошо прочитал сообщение), но я обновил ответ, чтобы предложить свою помощь в этой области . К сожалению, это немного невозможная проблема.
ShZ 04
3

быстрое решение этой проблемы: в _ _ init _ _.py вашего приложения поместите следующий код:

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....')
Руи Лима
источник
1

Единственный надежный способ (который также определяет, когда пользователь закрыл браузер) - обновить некоторые last_request поле каждый раз, когда пользователь загружает страницу.

У вас также может быть периодический запрос AJAX, который проверяет сервер каждые x минут, если у пользователя открыта страница.

Затем выполните одно фоновое задание, которое получает список недавних пользователей, создает для них задания и очищает задания для пользователей, которых нет в этом списке.

Джоэл Л
источник
Это лучший способ определить, вошел ли пользователь в систему или нет. По сути, вам придется вести список зарегистрированных пользователей, проверять, какой у них был последний доступ, и выбирать тайм-аут. Если вы установите время ожидания примерно на 10 минут, а затем каждая веб-страница будет вызывать Ajax-запрос каждые 5 минут или около того, пока страница активна, это должно поддерживать актуальность статуса.
Jordan Reiter
1

Вывод из системы, в отличие от того, чтобы они явно нажимали кнопку (чего никто не делает), означает выбор времени простоя, которое приравнивается к «выходу из системы». phpMyAdmin использует по умолчанию 15 минут, некоторые банковские сайты используют всего 5 минут.

Самый простой способ реализовать это - изменить время жизни cookie. Вы можете сделать это для всего сайта, указав settings.SESSION_COOKIE_AGE. В качестве альтернативы вы можете изменить его для каждого пользователя (на основе некоторого произвольного набора критериев), используя HttpResponse.setcookie(). Вы можете централизовать этот код, создав свою собственную версию render_to_response()и задав время жизни для каждого ответа.

Питер Роуэлл
источник
0

Примерная идея - для этого можно использовать промежуточное ПО. Это промежуточное ПО может обрабатывать запросы и запускать сигнал при запросе соответствующего URL-адреса. Он также может обрабатывать ответы и сигнал огня, когда данное действие действительно успешно.

Томаш Зелиньски
источник