Django:用户登录时发出信号?


82

在我的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模型。

谁能想到替代方法?


4
请考虑选择一个新答案。考虑到1.3中添加的信号,当前接受的选择是非常差的选择。
布赖森

1
你是对的; 更改了接受的答案。
ssc

Answers:


151

您可以使用这样的信号(我将我的信号放在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提供了这些信号,它比包装对login / logout的调用好得多。这也意味着,如果您设置了新的登录方式(例如Facebook / Twitter / OpenID登录),它们仍然可以使用。
Jordan Reiter

9
相反,models.py我建议将代码放入signals.py并自动将其导入模块的__init__.py文件中。
Daniel Sokolowski

如何使用此信号在登录和注销时运行javascript?
Ashish Gupta

15

除了@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和voila。

关于查询登录状态,如果您有权访问请求对象,这非常简单。只需检查请求的用户属性,看看他们是注册用户还是匿名用户,以及宾果游戏。引用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. 使用Javascriptunload事件捕获用户离开页面的时间。但是,您必须编写一些谨慎的逻辑以确保当用户仍在浏览您的网站时不会注销该用户。
  2. 为了确保用户登录,请在用户登录时触发注销信号。还创建一个cron作业,该作业经常运行以清除过期的会话-删除过期的会话后,请检查会话的用户(如果不是匿名用户)没有更多活动的会话,在这种情况下,您将发出注销信号。

这些解决方案很杂乱,并不理想,但是不幸的是,它们是您可以做的最好的事情。


这仍然无法解决“大多数情况下,用户只是离开页面或关闭浏览器”的情况。
乔尔L

感谢您的回答,当然,包装登录/注销可以使我免于修补源,但我自己应该有。不过,需要进行一些修正:如果在调用登录之前在my_login中发送了信号,则用户在信号处理程序中仍然是匿名的。更好(不要认为这将被正确格式化):def my_login(request):response = login(request)#触发信号或等效的返回响应另外,我已经在使用is_authenticated,我只是有一种感觉比那更多的。不过,到目前为止,我在数据模型中的新is_logged_in部分尚未使用。
ssc 2010年

到处都是好点。我想我根本没有很好地解决is_logged_in这些问题(很抱歉,我想我在阅读这篇文章时做得不好),但是我更新了答案以提供在该领域可以提供的帮助。不幸的是,这是一个不可能解决的问题。
ShZ 2010年

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分钟对服务器进行ping操作。

然后,有一个后台作业可获取最近用户的列表,为他们创建作业,并为该列表中不存在的用户清除作业。


这是衡量用户是否已登录的最佳方法。基本上,您必须保留已登录用户的花名册,并检查他们的最近访问权限并决定是否超时。如果将超时设置为10分钟左右,然后又在每个页面处于活动状态时让每个网页每5分钟左右调用一次Ajax请求,则该状态应保持最新。
Jordan Reiter

1

推断注销,而不是让他们明确单击一个按钮(没有人这样做),意味着选择与“注销”相当的空闲时间。phpMyAdmin默认使用15分钟,某些银行网站仅使用5分钟。

实现此目的的最简单方法是更改​​Cookie的生存时间。您可以通过指定对整个站点执行此操作settings.SESSION_COOKIE_AGE。或者,您可以使用来针对每个用户(基于任意一组标准)对其进行更改HttpResponse.setcookie()。您可以通过创建自己的版本render_to_response()并在每个响应中设置生存期来集中该代码。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.