From 53a1b80b0d77912434513cd878f71c2520fbc0a4 Mon Sep 17 00:00:00 2001 From: Eevee Date: Wed, 8 Sep 2010 22:47:16 -0700 Subject: [PATCH] Populate those empty "activity" and "volume" columns. Show last posts. #315 --- splinext/forum/controllers/forum.py | 120 +++++++++++++++++++++++++++- splinext/forum/model/__init__.py | 1 + splinext/forum/templates/css/forum.mako | 11 ++- splinext/forum/templates/forum/forums.mako | 53 +++++++++++- splinext/forum/templates/forum/threads.mako | 11 +++ 5 files changed, 190 insertions(+), 6 deletions(-) diff --git a/splinext/forum/controllers/forum.py b/splinext/forum/controllers/forum.py index b15302c..3628ff0 100644 --- a/splinext/forum/controllers/forum.py +++ b/splinext/forum/controllers/forum.py @@ -1,9 +1,13 @@ +import datetime import logging +import math -from pylons import config, request, response, session, tmpl_context as c, url +from pylons import cache, config, request, response, session, tmpl_context as c, url from pylons.controllers.util import abort, redirect from routes import request_config +from sqlalchemy.orm import joinedload from sqlalchemy.orm.exc import NoResultFound +from sqlalchemy.sql import func import wtforms from wtforms import fields @@ -16,6 +20,74 @@ from splinext.forum import model as forum_model log = logging.getLogger(__name__) +def forum_activity_score(forum): + """Returns a number representing how active a forum is, based on the past + week. + + The calculation is arbitrary, but 0 is supposed to mean "dead" and 1 is + supposed to mean "healthy". + """ + cutoff = datetime.datetime.now() - datetime.timedelta(days=7) + + post_count = meta.Session.query(forum_model.Post) \ + .join(forum_model.Post.thread) \ + .filter(forum_model.Thread.forum == forum) \ + .filter(forum_model.Post.posted_time >= cutoff) \ + .count() + + # Avoid domain errors! + if not post_count: + return 0.0 + + # The log is to scale 0 posts to 0.0, and 168 posts to 1.0. + # The square is really just to take the edge off the log curve; it + # accelerates to 1 very quickly, then slows considerably after that. + # Squaring helps both of these problems. + score = (math.log(post_count) / math.log(168)) ** 2 + + # TODO more threads and more new threads should boost the score slightly + + return score + +def get_forum_activity(): + """Returns a hash mapping forum ids to their level of 'activity'.""" + forums_q = meta.Session.query(forum_model.Forum) + + activity = {} + for forum in forums_q: + activity[forum.id] = forum_activity_score(forum) + + return activity + +def get_forum_volume(): + """Returns a hash mapping forum ids to the percentage of all posts that + reside in that forum. + """ + # Do a complicated-ass subquery to get a list of forums and postcounts + volume_q = meta.Session.query( + forum_model.Forum.id.label('forum_id'), + func.count(forum_model.Post.id).label('post_count'), + ) \ + .outerjoin(forum_model.Thread) \ + .outerjoin(forum_model.Post) \ + .group_by(forum_model.Forum.id) + + # Stick this into a hash, and count the number of total posts + total_posts = 0 + volume = {} + for forum_id, post_count in volume_q: + post_count = float(post_count or 0) + volume[forum_id] = post_count + total_posts += post_count + + # Divide, to get a percentage + if total_posts: + for forum_id, post_count in volume.iteritems(): + volume[forum_id] /= total_posts + + return volume + + class WritePostForm(wtforms.Form): content = fields.TextAreaField('Content') @@ -26,7 +98,46 @@ class ForumController(BaseController): def forums(self): c.forums = meta.Session.query(forum_model.Forum) \ - .order_by(forum_model.Forum.id.asc()) + .order_by(forum_model.Forum.id.asc()) \ + .all() + + # Get some forum stats. Cache them because they're a bit expensive to + # compute. Expire after an hour. + # XXX when there are admin controls, they'll need to nuke this cache + # when messing with the forum list + forum_cache = cache.get_cache('spline-forum', expiretime=3600) + c.forum_activity = forum_cache.get_value( + key='forum_activity', createfunc=get_forum_activity) + c.forum_volume = forum_cache.get_value( + key='forum_volume', createfunc=get_forum_volume) + + c.max_volume = max(c.forum_volume.itervalues()) or 1 + + # Need to know the last post for each forum, in realtime + c.last_post = {} + last_post_subq = meta.Session.query( + forum_model.Forum.id.label('forum_id'), + func.max(forum_model.Post.posted_time).label('posted_time'), + ) \ + .outerjoin(forum_model.Thread) \ + .outerjoin(forum_model.Post) \ + .group_by(forum_model.Forum.id) \ + .subquery() + last_post_q = meta.Session.query( + forum_model.Post, + last_post_subq.c.forum_id, + ) \ + .join(( + last_post_subq, + forum_model.Post.posted_time == last_post_subq.c.posted_time, + )) \ + .options( + joinedload('thread'), + joinedload('author'), + ) + for post, forum_id in last_post_q: + c.last_post[forum_id] = post + return render('/forum/forums.mako') def threads(self, forum_id): @@ -36,7 +147,10 @@ class ForumController(BaseController): c.write_thread_form = WriteThreadForm() - c.threads = c.forum.threads + c.threads = c.forum.threads.options( + joinedload('last_post'), + joinedload('last_post.author'), + ) return render('/forum/threads.mako') diff --git a/splinext/forum/model/__init__.py b/splinext/forum/model/__init__.py index 80ca57e..6dc6bfd 100644 --- a/splinext/forum/model/__init__.py +++ b/splinext/forum/model/__init__.py @@ -84,6 +84,7 @@ Forum.threads = relation(Thread, order_by=Thread.id.desc(), lazy='dynamic', back Thread.posts = relation(Post, order_by=Post.position.asc(), lazy='dynamic', backref='thread') Thread.first_post = relation(Post, primaryjoin=and_(Post.thread_id == Thread.id, Post.position == 1), uselist=False) +# XXX THIS WILL NEED TO CHANGE when posts can be deleted! Or change what 'position' means Thread.last_post = relation(Post, primaryjoin=and_(Post.thread_id == Thread.id, Post.position == Thread.post_count), uselist=False) Post.author = relation(users_model.User, backref='posts') diff --git a/splinext/forum/templates/css/forum.mako b/splinext/forum/templates/css/forum.mako index dd35bfd..6400dd9 100644 --- a/splinext/forum/templates/css/forum.mako +++ b/splinext/forum/templates/css/forum.mako @@ -9,7 +9,16 @@ table.forum-list .header-row th { vertical-align: middle; } table.forum-list .name { text-align: left; } table.forum-list td.name a { display: block; font-size: 1.5em; padding: 0.33em; } table.forum-list td.name .forum-description { padding: 0.33em 0.5em; color: #404040; } -table.forum-list .stats { width: 10em; text-align: center; } +table.forum-list .last-post { width: 20em; } +table.forum-list td.last-post { line-height: 1.33; text-align: left; vertical-align: top; } +table.forum-list .stats { width: 8em; text-align: center; } +table.forum-list td.stats { line-height: 1.33; vertical-align: top; } +table.forum-list td.stats.verylow { font-weight: bold; color: #aaaaaa; } +table.forum-list td.stats.low { font-weight: bold; color: #aa5555; } +table.forum-list td.stats.okay { font-weight: bold; color: #aa9555; } +table.forum-list td.stats.high { font-weight: bold; color: #78aa55; } +table.forum-list td.stats.veryhigh { font-weight: bold; color: #559eaa; } +table.forum-list td.stats.whoanelly { font-weight: bold; color: #6855aa; } .forum-post-container { } .forum-post { position: relative; margin: 1em 0; background: #fcfcfc; -moz-border-radius: 1em; -webkit-border-radius: 1em; } diff --git a/splinext/forum/templates/forum/forums.mako b/splinext/forum/templates/forum/forums.mako index 00310d0..2cf6451 100644 --- a/splinext/forum/templates/forum/forums.mako +++ b/splinext/forum/templates/forum/forums.mako @@ -1,5 +1,6 @@ <%inherit file="/base.mako" /> <%namespace name="forumlib" file="/forum/lib.mako" /> +<%namespace name="userlib" file="/users/lib.mako" /> <%def name="title()">Forums @@ -11,6 +12,7 @@ Forum + Last post Volume Activity @@ -27,8 +29,55 @@ % endif - xxx - xxx + + + <% last_post = c.last_post.get(forum.id, None) %> \ + % if last_post: + ## XXX should do direct post link + ${last_post.posted_time} +
in ${last_post.thread.subject} +
by ${userlib.color_bar(last_post.author)} ${last_post.author.name} + % else: + — + % endif + + + <% relative_volume = c.forum_volume[forum.id] / c.max_volume %>\ + + ${"{0:3.1f}".format(c.forum_volume[forum.id] * 100)}% + + <% activity = c.forum_activity[forum.id] %>\ + + ${"{0:0.3f}".format(activity)} + % endfor diff --git a/splinext/forum/templates/forum/threads.mako b/splinext/forum/templates/forum/threads.mako index 7df962b..cf510e7 100644 --- a/splinext/forum/templates/forum/threads.mako +++ b/splinext/forum/templates/forum/threads.mako @@ -1,5 +1,6 @@ <%inherit file="/base.mako" /> <%namespace name="forumlib" file="/forum/lib.mako" /> +<%namespace name="userlib" file="/users/lib.mako" /> <%def name="title()">${c.forum.name} - Forums @@ -20,6 +21,7 @@ ${forumlib.hierarchy(c.forum)} Thread + Last post Posts @@ -27,6 +29,15 @@ ${forumlib.hierarchy(c.forum)} % for thread in c.threads: ${thread.subject} + + % if thread.last_post: + ## XXX should do direct post link + ${thread.last_post.posted_time} +
by ${userlib.color_bar(thread.last_post.author)} ${thread.last_post.author.name} + % else: + — + % endif + ${thread.post_count} % endfor -- 2.7.4