annotate gpp/core/whos_online.py @ 504:b5bd3509e6e6

Made some tweaks to the ajax login javascript. Cleared the inputs on failure so someone won't repeatedly try a bad login. Redirect on lockout so the page will refresh. Set focus on the username box after failure for convenience.
author Brian Neal <bgneal@gmail.com>
date Sat, 03 Dec 2011 16:25:15 +0000
parents 3fe60148f75c
children 6f5fff924877
rev   line source
bgneal@423 1 """
bgneal@423 2 This module keeps track of who is online. We maintain records for both
bgneal@423 3 authenticated users ("users") and non-authenticated visitors ("visitors").
bgneal@423 4 """
bgneal@423 5 import logging
bgneal@423 6
bgneal@423 7 from django.conf import settings
bgneal@423 8 import redis
bgneal@423 9
bgneal@423 10 # Users and visitors each have 2 sets that we store in a Redis database:
bgneal@423 11 # a current set and an old set. Whenever a user or visitor is seen, the
bgneal@423 12 # current set is updated. At some interval, the current set is renamed
bgneal@423 13 # to the old set, thus destroying it. At any given time, the union of the
bgneal@423 14 # current and old sets is the "who's online" set.
bgneal@423 15
bgneal@423 16 # Redis connection and database settings
bgneal@423 17
bgneal@423 18 HOST = getattr(settings, 'WHOS_ONLINE_REDIS_HOST', 'localhost')
bgneal@423 19 PORT = getattr(settings, 'WHOS_ONLINE_REDIS_PORT', 6379)
bgneal@423 20 DB = getattr(settings, 'WHOS_ONLINE_REDIS_DB', 0)
bgneal@423 21
bgneal@423 22 # Redis key names:
bgneal@423 23 USER_CURRENT_KEY = "wo_user_current"
bgneal@423 24 USER_OLD_KEY = "wo_user_old"
bgneal@423 25 USER_KEYS = [USER_CURRENT_KEY, USER_OLD_KEY]
bgneal@423 26
bgneal@423 27 VISITOR_CURRENT_KEY = "wo_visitor_current"
bgneal@423 28 VISITOR_OLD_KEY = "wo_visitor_old"
bgneal@423 29 VISITOR_KEYS = [VISITOR_CURRENT_KEY, VISITOR_OLD_KEY]
bgneal@423 30
bgneal@423 31
bgneal@423 32 # Logging: we don't want a Redis malfunction to bring down the site. So we
bgneal@423 33 # catch all Redis exceptions, log them, and press on.
bgneal@423 34 logger = logging.getLogger(__name__)
bgneal@423 35
bgneal@423 36
bgneal@423 37 def _get_connection():
bgneal@423 38 """
bgneal@423 39 Create and return a Redis connection. Returns None on failure.
bgneal@423 40 """
bgneal@423 41 try:
bgneal@423 42 conn = redis.Redis(host=HOST, port=PORT, db=DB)
bgneal@423 43 return conn
bgneal@423 44 except redis.RedisError, e:
bgneal@423 45 logger.error(e)
bgneal@423 46
bgneal@423 47 return None
bgneal@423 48
bgneal@423 49
bgneal@423 50 def report_user(username):
bgneal@423 51 """
bgneal@423 52 Call this function when a user has been seen. The username will be added to
bgneal@423 53 the current set.
bgneal@423 54 """
bgneal@423 55 conn = _get_connection()
bgneal@423 56 if conn:
bgneal@423 57 try:
bgneal@423 58 conn.sadd(USER_CURRENT_KEY, username)
bgneal@423 59 except redis.RedisError, e:
bgneal@423 60 logger.error(e)
bgneal@423 61
bgneal@423 62
bgneal@423 63 def report_visitor(ip):
bgneal@423 64 """
bgneal@423 65 Call this function when a visitor has been seen. The IP address will be
bgneal@423 66 added the current set.
bgneal@423 67 """
bgneal@423 68 conn = _get_connection()
bgneal@423 69 if conn:
bgneal@423 70 try:
bgneal@423 71 conn.sadd(VISITOR_CURRENT_KEY, ip)
bgneal@423 72 except redis.RedisError, e:
bgneal@423 73 logger.error(e)
bgneal@423 74
bgneal@423 75
bgneal@423 76 def get_users_online():
bgneal@423 77 """
bgneal@423 78 Returns a set of user names which is the union of the current and old
bgneal@423 79 sets.
bgneal@423 80 """
bgneal@423 81 conn = _get_connection()
bgneal@423 82 if conn:
bgneal@423 83 try:
bgneal@423 84 # Note that keys that do not exist are considered empty sets
bgneal@423 85 return conn.sunion(USER_KEYS)
bgneal@423 86 except redis.RedisError, e:
bgneal@423 87 logger.error(e)
bgneal@423 88
bgneal@423 89 return set()
bgneal@423 90
bgneal@423 91
bgneal@423 92 def get_visitors_online():
bgneal@423 93 """
bgneal@423 94 Returns a set of visitor IP addresses which is the union of the current
bgneal@423 95 and old sets.
bgneal@423 96 """
bgneal@423 97 conn = _get_connection()
bgneal@423 98 if conn:
bgneal@423 99 try:
bgneal@423 100 # Note that keys that do not exist are considered empty sets
bgneal@423 101 return conn.sunion(VISITOR_KEYS)
bgneal@423 102 except redis.RedisError, e:
bgneal@423 103 logger.error(e)
bgneal@423 104
bgneal@423 105 return set()
bgneal@423 106
bgneal@423 107
bgneal@423 108 def _tick_set(conn, current, old):
bgneal@423 109 """
bgneal@423 110 This function renames the set named "current" to "old".
bgneal@423 111 """
bgneal@423 112 # An exception may be raised if the current key doesn't exist; if that
bgneal@423 113 # happens we have to delete the old set because no one is online.
bgneal@423 114 try:
bgneal@423 115 conn.rename(current, old)
bgneal@423 116 except redis.ResponseError:
bgneal@423 117 try:
bgneal@423 118 del conn[old]
bgneal@423 119 except redis.RedisError, e:
bgneal@423 120 logger.error(e)
bgneal@423 121 except redis.RedisError, e:
bgneal@423 122 logger.error(e)
bgneal@423 123
bgneal@423 124
bgneal@423 125 def tick():
bgneal@423 126 """
bgneal@423 127 Call this function to "age out" the old sets by renaming the current sets
bgneal@423 128 to the old.
bgneal@423 129 """
bgneal@423 130 conn = _get_connection()
bgneal@423 131 if conn:
bgneal@423 132 _tick_set(conn, USER_CURRENT_KEY, USER_OLD_KEY)
bgneal@423 133 _tick_set(conn, VISITOR_CURRENT_KEY, VISITOR_OLD_KEY)