Django: 用户登录时的信号?

在我的 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没有这个功能,我不愿意修补源代码并添加它(无论如何,我不确定如何做到这一点)。

作为一个临时解决方案,我在 UserProfile 模型中添加了一个 is_logged_in布尔字段,默认情况下这个字段是清除的,在用户第一次点击登录页面(由 LOGIN_REDIRECT_URL = '/'定义)时设置,并在随后的请求中被查询。我将它添加到 UserProfile,所以我不必为此目的派生和定制内置的 User 模型。

我不喜欢这个解决方案。如果用户显式地单击注销按钮,我可以清除标志,但大多数情况下,用户只是离开页面或关闭浏览器; 在这些情况下清除标志对我来说似乎并不直接。除此之外(虽然这是数据模型清晰度的吹毛求疵) ,is_logged_in不属于 UserProfile,而属于 User 模型。

有人能想到别的办法吗?

40280 次浏览

One option might be to wrap Django's login/logout views with your own. For example:

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)

You then use these views in your code rather than Django's, and voila.

With regards to querying login status, it's pretty simple if you have access to the request object; simply check request's user attribute to see if they're a registered user or the anonymous user, and bingo. To quote the Django documentation:

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

If you don't have access to the request object, then determining if the current user is logged in is going to be difficult.

Edit:

Unfortunately, you'll never be able to get User.is_logged_in() functionality - it's a limitation of the HTTP protocol. If you make a few assumptions, however, you might be able to get close to what you want.

First, why can't you get that functionality? Well, you can't tell the difference between someone closing the browser, or someone spending a while on a page before fetching a new one. There's no way to tell over HTTP when someone actually leaves the site or closes the browser.

So you have two options here that aren't perfect:

  1. Use Javascript's unload event to catch when a user is leaving a page. You'd have to write some careful logic to make sure you aren't logging out a user when they're still navigating your site, however.
  2. Fire the logout signal whenever a user logs in, just to be sure. Also create a cron job that runs fairly often to flush out expired sessions -- when an expired session is deleted, check that the session's user (if it's not anonymous) has no more active sessions, in which case you fire the logout signal.

These solutions are messy and not ideal, but they're the best you can do, unfortunately.

Rough idea - you could use middleware for this. This middleware could process requests and fire signal when relevant URL is requested. It could also process responses and fire signal when given action actually succeded.

The only reliable way (that also detects when the user has closed the browser) is to update some last_request field every time the user loads a page.

You could also have a periodic AJAX request that pings the server every x minutes if the user has a page open.

Then have a single background job that gets a list of recent users, create jobs for them, and clear the jobs for users not present in that list.

Inferring logout, as opposed to having them explicitly click a button (which nobody does), means picking an amount of idle time that equates to "logged out". phpMyAdmin uses a default of 15 minutes, some banking sites use as little as 5 minutes.

The simplest way of implementing this would be to change the cookie-lifetime. You can do this for your entire site by specifying settings.SESSION_COOKIE_AGE. Alternatively, you could change it on a per-user basis (based on some arbitrary set of criteria) by using HttpResponse.setcookie(). You can centralize this code by creating your own version of render_to_response() and having it set the lifetime on each response.

You can use a signal like this (I put mine in 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)

See django docs: https://docs.djangoproject.com/en/dev/ref/contrib/auth/#module-django.contrib.auth.signals and here http://docs.djangoproject.com/en/dev/topics/signals/

In addition to @PhoebeB answer: you can also use @receiver decorator like this:

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

And if you put it into signals.py in your app dir, then add this to apps.py:

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

a quick solution for this would be, in the _ _ init _ _.py of your app place the following code:

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