bgneal@509: """ bgneal@509: This module maintains the latest posts datastore. The latest posts are often bgneal@509: needed by RSS feeds, "latest posts" template tags, etc. This module listens for bgneal@509: the post_content_update signal, then bundles the post up and stores it by forum bgneal@509: ID in Redis. We also maintain a combined forums list. This allows quick bgneal@509: retrieval of the latest posts and avoids some slow SQL queries. bgneal@509: bgneal@509: """ bgneal@509: import datetime bgneal@509: import time bgneal@509: bgneal@509: from django.dispatch import receiver bgneal@509: from django.utils import simplejson bgneal@509: bgneal@509: from forums.signals import post_content_update bgneal@509: from forums.models import Forum bgneal@509: from core.services import get_redis_connection bgneal@509: bgneal@509: bgneal@509: # This constant controls how many latest posts per forum we store bgneal@509: MAX_POSTS = 50 bgneal@509: bgneal@509: bgneal@509: @receiver(post_content_update, dispatch_uid='forums.latest_posts') bgneal@509: def on_post_update(sender, **kwargs): bgneal@509: """ bgneal@509: This function is our signal handler, called when a post has been updated. bgneal@509: We only care about newly created posts, and ignore updates. bgneal@509: bgneal@509: We serialize a post to JSON then store in two lists in Redis: bgneal@509: 1. The list for the post's parent forum bgneal@509: 2. The combined forum list bgneal@509: bgneal@509: Note that we only store posts from public forums. bgneal@509: bgneal@509: """ bgneal@509: # ignore non-new posts bgneal@509: if not kwargs['created']: bgneal@509: return bgneal@509: bgneal@509: # ignore posts from non-public forums bgneal@509: public_forums = Forum.objects.public_forum_ids() bgneal@509: if sender.topic.forum.id not in public_forums: bgneal@509: return bgneal@509: bgneal@509: # serialize post attributes bgneal@509: post_content = { bgneal@509: 'id': sender.id, bgneal@509: 'title': sender.topic.name, bgneal@509: 'content': sender.html, bgneal@509: 'author': sender.user.username, bgneal@509: 'pubdate': int(time.mktime(sender.creation_date.timetuple())), bgneal@509: 'forum_name': sender.topic.forum.name, bgneal@509: 'url': sender.get_absolute_url() bgneal@509: } bgneal@509: bgneal@509: s = simplejson.dumps(post_content) bgneal@509: bgneal@509: # store in Redis bgneal@509: bgneal@509: redis = get_redis_connection() bgneal@509: pipeline = redis.pipeline() bgneal@509: bgneal@509: key = 'forums:latest:%d' % sender.topic.forum.id bgneal@509: bgneal@509: pipeline.lpush(key, s) bgneal@509: pipeline.ltrim(key, 0, MAX_POSTS - 1) bgneal@509: bgneal@509: # store in the combined feed; yes this wastes some memory storing it twice, bgneal@509: # but it makes things much easier bgneal@509: bgneal@509: key = 'forums:latest:*' bgneal@509: bgneal@509: pipeline.lpush(key, s) bgneal@509: pipeline.ltrim(key, 0, MAX_POSTS - 1) bgneal@509: bgneal@509: pipeline.execute() bgneal@509: bgneal@509: bgneal@509: def get_latest_posts(num_posts=MAX_POSTS, forum_id=None): bgneal@509: """ bgneal@509: This function retrieves num_posts latest posts for the forum with the given bgneal@509: forum_id. If forum_id is None, the posts are retrieved from the combined bgneal@509: forums datastore. A list of dictionaries is returned. Each dictionary bgneal@509: contains information about a post. bgneal@509: bgneal@509: """ bgneal@509: key = 'forums:latest:%d' % forum_id if forum_id else 'forums:latest:*' bgneal@509: bgneal@509: num_posts = max(0, min(MAX_POSTS, num_posts)) bgneal@509: bgneal@509: if num_posts == 0: bgneal@509: return [] bgneal@509: bgneal@509: redis = get_redis_connection() bgneal@509: raw_posts = redis.lrange(key, 0, num_posts - 1) bgneal@509: bgneal@509: posts = [] bgneal@509: for raw_post in raw_posts: bgneal@509: post = simplejson.loads(raw_post) bgneal@509: bgneal@509: # fix up the pubdate; turn it back into a datetime object bgneal@509: post['pubdate'] = datetime.datetime.fromtimestamp(post['pubdate']) bgneal@509: bgneal@509: posts.append(post) bgneal@509: bgneal@509: return posts