changeset 520:e94570675664

Created stats for users (number of users and list of newest users). Better separated the forums, who's online, and user stats. Use a Celery task to execute the new user stats processing. This addresses #194 and #238.
author Brian Neal <bgneal@gmail.com>
date Sat, 17 Dec 2011 23:19:15 +0000 (2011-12-17)
parents f72ace06658a
children dd14ab08a9c4
files gpp/accounts/stats.py gpp/accounts/tasks.py gpp/accounts/templatetags/__init__.py gpp/accounts/templatetags/accounts_tags.py gpp/core/templatetags/core_tags.py gpp/forums/stats.py gpp/forums/templatetags/forum_tags.py gpp/templates/accounts/user_stats_tag.html gpp/templates/core/max_users_tag.html gpp/templates/core/whos_online_tag.html gpp/templates/forums/forum_stats_tag.html gpp/templates/forums/index.html
diffstat 11 files changed, 177 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gpp/accounts/stats.py	Sat Dec 17 23:19:15 2011 +0000
@@ -0,0 +1,97 @@
+"""
+This module performs user account related statistics.
+
+"""
+import logging
+
+from django.db.models.signals import post_save
+from django.contrib.auth.models import User
+
+from core.services import get_redis_connection
+
+
+# Redis key names
+USER_COUNT_KEY = "accounts:user_count"
+NEW_USERS_KEY = "accounts:new_users"
+
+
+logger = logging.getLogger(__name__)
+
+
+def on_user_save(sender, **kwargs):
+    """
+    This function is our signal handler for when a User object is saved.
+
+    """
+    from accounts.tasks import user_stats_task
+
+    if kwargs['created']:
+        user = kwargs['instance']
+
+        # kick off a task to update user stats
+
+        user_stats_task.delay(user.id)
+
+
+def update_user_stats(user_id):
+    """
+    This function is given a new user id and is responsible for updating various
+    user account statistics.
+
+    """
+    try:
+        user = User.objects.get(pk=user_id)
+    except User.DoesNotExist:
+        logger.warning("update_user_stats: user id %d doesn't exist", user_id)
+        return
+
+    redis = get_redis_connection()
+
+    # update the count of registered users
+
+    count = redis.incr(USER_COUNT_KEY)
+    if count == 1:
+        # it is likely redis got wiped out; update it now
+        count = User.objects.all().count()
+        redis.set(USER_COUNT_KEY, count)
+
+    # update the list of new users
+
+    pipeline = redis.pipeline()
+    pipeline.lpush(NEW_USERS_KEY, user.username)
+    pipeline.ltrim(NEW_USERS_KEY, 0, 9)
+    pipeline.execute()
+
+
+def get_user_count(redis=None):
+    """
+    This function returns the current count of users.
+
+    """
+    if redis is None:
+        redis = get_redis_connection()
+    return redis.get(USER_COUNT_KEY)
+
+
+def get_new_users(redis=None):
+    """
+    This function returns a list of new usernames.
+
+    """
+    if redis is None:
+        redis = get_redis_connection()
+    return redis.lrange(NEW_USERS_KEY, 0, -1)
+
+
+def get_user_stats(redis=None):
+    """
+    This function returns a tuple of the user stats. Element 0 is the user count
+    and element 1 is the list of new users.
+
+    """
+    if redis is None:
+        redis = get_redis_connection()
+    return get_user_count(redis), get_new_users(redis)
+
+
+post_save.connect(on_user_save, sender=User, dispatch_uid='accounts.stats')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gpp/accounts/tasks.py	Sat Dec 17 23:19:15 2011 +0000
@@ -0,0 +1,16 @@
+"""
+Celery tasks for the accounts application.
+
+"""
+from celery.task import task
+
+from accounts.stats import update_user_stats
+
+
+@task
+def user_stats_task(user_id):
+    """
+    Run the update_user_stats() function on a new task.
+
+    """
+    update_user_stats(user_id)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gpp/accounts/templatetags/accounts_tags.py	Sat Dec 17 23:19:15 2011 +0000
@@ -0,0 +1,20 @@
+"""
+Template tags for the accounts applications.
+
+"""
+from django import template
+
+from accounts.stats import get_user_stats
+
+
+register = template.Library()
+
+
+@register.inclusion_tag('accounts/user_stats_tag.html')
+def user_stats():
+    """
+    This tag renders the total number of site users and a list of new users.
+
+    """
+    num_users, new_users = get_user_stats()
+    return {'num_users': num_users, 'new_users': new_users}
--- a/gpp/core/templatetags/core_tags.py	Sat Dec 17 19:29:24 2011 +0000
+++ b/gpp/core/templatetags/core_tags.py	Sat Dec 17 23:19:15 2011 +0000
@@ -13,7 +13,7 @@
 
 import repoze.timeago
 
-from core.whos_online import get_users_online, get_visitors_online
+from core.whos_online import get_users_online, get_visitors_online, get_stats
 from bio.models import UserProfile
 
 
@@ -36,10 +36,21 @@
     return {'STATIC_URL': settings.STATIC_URL}
 
 
+@register.inclusion_tag('core/max_users_tag.html')
+def max_users():
+    """
+    Displays max users online information.
+
+    """
+    return {
+        'stats': get_stats(),
+    }
+
 @register.inclusion_tag('core/whos_online_tag.html')
 def whos_online():
     """
     Displays a list of who is online.
+
     """
     users = get_users_online()
     visitors = get_visitors_online()
--- a/gpp/forums/stats.py	Sat Dec 17 19:29:24 2011 +0000
+++ b/gpp/forums/stats.py	Sat Dec 17 23:19:15 2011 +0000
@@ -3,31 +3,22 @@
 
 """
 from django.core.cache import cache
-from django.contrib.auth.models import User
 
 from forums.models import Post
 
 
-CACHE_KEY = 'forums-stats'
+CACHE_KEY = 'forums-stats-2'
 CACHE_TIMEOUT = 4 * 60 * 60         # seconds
 
 
 def calc_stats():
     """
     This function is responsible for computing the forum statistics.
-    A tuple is returned: (forums post count, active user count, latest username)
+    The forums post count is returned.
 
     """
     post_count = Post.objects.all().count()
-    user_count = User.objects.all().count()
-
-    if user_count > 0:
-        latest_user = User.objects.filter(is_active=True).values_list(
-                'username', flat=True).order_by('-date_joined')[0]
-    else:
-        latest_user = None
-
-    return post_count, user_count, latest_user
+    return post_count
 
 
 def update_stats():
--- a/gpp/forums/templatetags/forum_tags.py	Sat Dec 17 19:29:24 2011 +0000
+++ b/gpp/forums/templatetags/forum_tags.py	Sat Dec 17 23:19:15 2011 +0000
@@ -14,7 +14,6 @@
 from forums.models import Post
 from forums.models import Category
 from forums.stats import retrieve_stats
-from core.whos_online import get_stats as get_core_stats
 
 
 register = template.Library()
@@ -154,13 +153,10 @@
     """
     Displays forum statistics.
     """
-    post_count, user_count, latest_user = retrieve_stats()
+    post_count = retrieve_stats()
 
     return {
-        'stats': get_core_stats(),
         'post_count': post_count,
-        'user_count': user_count,
-        'latest_user': latest_user,
     }
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gpp/templates/accounts/user_stats_tag.html	Sat Dec 17 23:19:15 2011 +0000
@@ -0,0 +1,16 @@
+{% load url from future %}
+{% load bio_tags %}
+{% load humanize %}
+<div id="accounts-stats">
+{% if num_users %}
+Our site has <strong>{{ num_users|intcomma }}</strong> members.
+{% endif %} 
+{% if new_users %}
+Please welcome our newest members:
+<ul class="inline-list">
+{% for user in new_users %}
+   <li>{% profile_link user %}</li>
+{% endfor %}
+</ul>
+{% endif %}
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gpp/templates/core/max_users_tag.html	Sat Dec 17 23:19:15 2011 +0000
@@ -0,0 +1,5 @@
+<div id="max-users">
+{% if stats %}
+The most users ever online was <strong>{{ stats.max_users }}</strong> on {{ stats.max_users_date|date:"P l, N d, Y" }}.<br />
+{% endif %}
+</div>
--- a/gpp/templates/core/whos_online_tag.html	Sat Dec 17 19:29:24 2011 +0000
+++ b/gpp/templates/core/whos_online_tag.html	Sat Dec 17 23:19:15 2011 +0000
@@ -1,4 +1,3 @@
-{% load url from future %}
 {% load bio_tags %}
 <div id="whos-online">
 There {{ total|pluralize:"is,are"}} {{ total }} user{{ total|pluralize }} online: {{ num_users }} registered user{{ num_users|pluralize }} and {{ num_guests }} guest{{ num_guests|pluralize }}.
--- a/gpp/templates/forums/forum_stats_tag.html	Sat Dec 17 19:29:24 2011 +0000
+++ b/gpp/templates/forums/forum_stats_tag.html	Sat Dec 17 23:19:15 2011 +0000
@@ -1,11 +1,5 @@
 {% load url from future %}
-{% load bio_tags %}
 {% load humanize %}
 <div id="forum-stats">
-Our <strong>{{ user_count|intcomma }}</strong> users have posted a total of <strong>{{ post_count|intcomma }}</strong> posts.<br />
-Our newest registered user is {% profile_link latest_user '.' %}
-{% if stats %}
-<br />
-The most users ever online was <strong>{{ stats.max_users }}</strong> on {{ stats.max_users_date|date:"P l, N d, Y" }}.
-{% endif %}
+Our users have posted a total of <strong>{{ post_count|intcomma }}</strong> posts.
 </div>
--- a/gpp/templates/forums/index.html	Sat Dec 17 19:29:24 2011 +0000
+++ b/gpp/templates/forums/index.html	Sat Dec 17 23:19:15 2011 +0000
@@ -1,5 +1,6 @@
 {% extends 'base.html' %}
 {% load url from future %}
+{% load accounts_tags %}
 {% load cache %}
 {% load forum_tags %}
 {% load core_tags %}
@@ -49,8 +50,12 @@
    <input type="submit" value="Mark All Forums Read" />
 </form>
 <br />
+{% user_stats %}
 {% cache 900 forum-stats-block %}
-{% forum_stats %}
+   {% forum_stats %}
+{% endcache %}
+{% cache 900 max-users-block %}
+   {% max_users %}
 {% endcache %}
 {% whos_online %}
 <p>{% current_forum_time user %}</p>