comparison gpp/forums/latest.py @ 509:248dd8dd67f8

For #237, use Redis as the source of posts for the RSS feeds to hopefully eliminate some slow queries.
author Brian Neal <bgneal@gmail.com>
date Wed, 07 Dec 2011 01:08:54 +0000
parents
children 82b97697312e
comparison
equal deleted inserted replaced
508:6f5fff924877 509:248dd8dd67f8
1 """
2 This module maintains the latest posts datastore. The latest posts are often
3 needed by RSS feeds, "latest posts" template tags, etc. This module listens for
4 the post_content_update signal, then bundles the post up and stores it by forum
5 ID in Redis. We also maintain a combined forums list. This allows quick
6 retrieval of the latest posts and avoids some slow SQL queries.
7
8 """
9 import datetime
10 import time
11
12 from django.dispatch import receiver
13 from django.utils import simplejson
14
15 from forums.signals import post_content_update
16 from forums.models import Forum
17 from core.services import get_redis_connection
18
19
20 # This constant controls how many latest posts per forum we store
21 MAX_POSTS = 50
22
23
24 @receiver(post_content_update, dispatch_uid='forums.latest_posts')
25 def on_post_update(sender, **kwargs):
26 """
27 This function is our signal handler, called when a post has been updated.
28 We only care about newly created posts, and ignore updates.
29
30 We serialize a post to JSON then store in two lists in Redis:
31 1. The list for the post's parent forum
32 2. The combined forum list
33
34 Note that we only store posts from public forums.
35
36 """
37 # ignore non-new posts
38 if not kwargs['created']:
39 return
40
41 # ignore posts from non-public forums
42 public_forums = Forum.objects.public_forum_ids()
43 if sender.topic.forum.id not in public_forums:
44 return
45
46 # serialize post attributes
47 post_content = {
48 'id': sender.id,
49 'title': sender.topic.name,
50 'content': sender.html,
51 'author': sender.user.username,
52 'pubdate': int(time.mktime(sender.creation_date.timetuple())),
53 'forum_name': sender.topic.forum.name,
54 'url': sender.get_absolute_url()
55 }
56
57 s = simplejson.dumps(post_content)
58
59 # store in Redis
60
61 redis = get_redis_connection()
62 pipeline = redis.pipeline()
63
64 key = 'forums:latest:%d' % sender.topic.forum.id
65
66 pipeline.lpush(key, s)
67 pipeline.ltrim(key, 0, MAX_POSTS - 1)
68
69 # store in the combined feed; yes this wastes some memory storing it twice,
70 # but it makes things much easier
71
72 key = 'forums:latest:*'
73
74 pipeline.lpush(key, s)
75 pipeline.ltrim(key, 0, MAX_POSTS - 1)
76
77 pipeline.execute()
78
79
80 def get_latest_posts(num_posts=MAX_POSTS, forum_id=None):
81 """
82 This function retrieves num_posts latest posts for the forum with the given
83 forum_id. If forum_id is None, the posts are retrieved from the combined
84 forums datastore. A list of dictionaries is returned. Each dictionary
85 contains information about a post.
86
87 """
88 key = 'forums:latest:%d' % forum_id if forum_id else 'forums:latest:*'
89
90 num_posts = max(0, min(MAX_POSTS, num_posts))
91
92 if num_posts == 0:
93 return []
94
95 redis = get_redis_connection()
96 raw_posts = redis.lrange(key, 0, num_posts - 1)
97
98 posts = []
99 for raw_post in raw_posts:
100 post = simplejson.loads(raw_post)
101
102 # fix up the pubdate; turn it back into a datetime object
103 post['pubdate'] = datetime.datetime.fromtimestamp(post['pubdate'])
104
105 posts.append(post)
106
107 return posts