f2eb4760ca2bd962198b0ac05701fef6036ec3cd
5 from pylons
import cache
, config
, request
, response
, session
, tmpl_context
as c
, url
6 from pylons
.controllers
.util
import abort
, redirect
7 from pylons
.decorators
.secure
import authenticate_form
8 from routes
import request_config
9 from sqlalchemy
.orm
import joinedload
10 from sqlalchemy
.orm
.exc
import NoResultFound
11 from sqlalchemy
.sql
import func
13 from wtforms
import fields
15 from spline
.model
import meta
16 from spline
.lib
import helpers
as h
17 from spline
.lib
.base
import BaseController
, render
18 import spline
.lib
.markdown
19 from splinext
.forum
import model
as forum_model
21 log
= logging
.getLogger(__name__
)
24 def forum_activity_score(forum
):
25 """Returns a number representing how active a forum is, based on the past
28 The calculation is arbitrary, but 0 is supposed to mean "dead" and 1 is
29 supposed to mean "healthy".
31 cutoff
= datetime
.datetime
.now() - datetime
.timedelta(days
=7)
33 post_count
= meta
.Session
.query(forum_model
.Post
) \
34 .join(forum_model
.Post
.thread
) \
35 .filter(forum_model
.Thread
.forum
== forum
) \
36 .filter(forum_model
.Post
.posted_time
>= cutoff
) \
39 # Avoid domain errors!
43 # The log is to scale 0 posts to 0.0, and 168 posts to 1.0.
44 # The square is really just to take the edge off the log curve; it
45 # accelerates to 1 very quickly, then slows considerably after that.
46 # Squaring helps both of these problems.
47 score
= (math
.log(post_count
) / math
.log(168)) ** 2
49 # TODO more threads and more new threads should boost the score slightly
53 def get_forum_activity():
54 """Returns a hash mapping forum ids to their level of 'activity'."""
55 forums_q
= meta
.Session
.query(forum_model
.Forum
)
58 for forum
in forums_q
:
59 activity
[forum
.id] = forum_activity_score(forum
)
63 def get_forum_volume():
64 """Returns a hash mapping forum ids to the percentage of all posts that
67 # Do a complicated-ass subquery to get a list of forums and postcounts
68 volume_q
= meta
.Session
.query(
69 forum_model
.Forum
.id.label('forum_id'),
70 func
.count(forum_model
.Post
.id).label('post_count'),
72 .outerjoin(forum_model
.Thread
) \
73 .outerjoin(forum_model
.Post
) \
74 .group_by(forum_model
.Forum
.id)
76 # Stick this into a hash, and count the number of total posts
79 for forum_id
, post_count
in volume_q
:
80 post_count
= float(post_count
or 0)
81 volume
[forum_id
] = post_count
82 total_posts
+= post_count
84 # Divide, to get a percentage
86 for forum_id
, post_count
in volume
.iteritems():
87 volume
[forum_id
] /= total_posts
92 class WritePostForm(wtforms
.Form
):
93 content
= fields
.TextAreaField('Content')
95 class WriteThreadForm(WritePostForm
):
96 subject
= fields
.TextField('Subject')
98 class ForumController(BaseController
):
101 c
.forums
= meta
.Session
.query(forum_model
.Forum
) \
102 .order_by(forum_model
.Forum
.id.asc()) \
105 # Get some forum stats. Cache them because they're a bit expensive to
106 # compute. Expire after an hour.
107 # XXX when there are admin controls, they'll need to nuke this cache
108 # when messing with the forum list
109 forum_cache
= cache
.get_cache('spline-forum', expiretime
=3600)
110 c
.forum_activity
= forum_cache
.get_value(
111 key
='forum_activity', createfunc
=get_forum_activity
)
112 c
.forum_volume
= forum_cache
.get_value(
113 key
='forum_volume', createfunc
=get_forum_volume
)
115 c
.max_volume
= max(c
.forum_volume
.itervalues()) or 1
117 # Need to know the last post for each forum, in realtime
119 last_post_subq
= meta
.Session
.query(
120 forum_model
.Forum
.id.label('forum_id'),
121 func
.max(forum_model
.Post
.posted_time
).label('posted_time'),
123 .outerjoin(forum_model
.Thread
) \
124 .outerjoin(forum_model
.Post
) \
125 .group_by(forum_model
.Forum
.id) \
127 last_post_q
= meta
.Session
.query(
129 last_post_subq
.c
.forum_id
,
133 forum_model
.Post
.posted_time
== last_post_subq
.c
.posted_time
,
136 joinedload('thread'),
137 joinedload('author'),
139 for post
, forum_id
in last_post_q
:
140 c
.last_post
[forum_id
] = post
142 return render('/forum/forums.mako')
144 def threads(self
, forum_id
):
145 c
.forum
= meta
.Session
.query(forum_model
.Forum
).get(forum_id
)
149 c
.write_thread_form
= WriteThreadForm()
151 # nb: This will never show post-less threads. Oh well!
152 threads_q
= c
.forum
.threads \
153 .join(forum_model
.Thread
.last_post
) \
154 .order_by(forum_model
.Post
.posted_time
.desc()) \
156 joinedload('last_post'),
157 joinedload('last_post.author'),
159 c
.num_threads
= threads_q
.count()
161 c
.skip
= int(request
.params
.get('skip', 0))
165 c
.threads
= threads_q
.offset(c
.skip
).limit(c
.per_page
)
167 return render('/forum/threads.mako')
169 def posts(self
, forum_id
, thread_id
):
171 c
.thread
= meta
.Session
.query(forum_model
.Thread
) \
172 .filter_by(id=thread_id
, forum_id
=forum_id
).one()
173 except NoResultFound
:
176 c
.write_post_form
= WritePostForm()
178 posts_q
= c
.thread
.posts \
179 .order_by(forum_model
.Post
.position
.asc()) \
180 .options(joinedload('author'))
181 c
.num_posts
= c
.thread
.post_count
183 c
.skip
= int(request
.params
.get('skip', 0))
187 c
.posts
= posts_q
.offset(c
.skip
).limit(c
.per_page
)
189 return render('/forum/posts.mako')
192 def write_thread(self
, forum_id
):
193 """Provides a form for posting a new thread."""
194 if not c
.user
.can('forum:create-thread'):
198 c
.forum
= meta
.Session
.query(forum_model
.Forum
) \
199 .filter_by(id=forum_id
).one()
200 except NoResultFound
:
203 c
.write_thread_form
= WriteThreadForm(request
.params
)
204 return render('/forum/write_thread.mako')
207 def write_thread_commit(self
, forum_id
):
208 """Posts a new thread."""
209 if not c
.user
.can('forum:create-thread'):
213 c
.forum
= meta
.Session
.query(forum_model
.Forum
) \
214 .filter_by(id=forum_id
).one()
215 except NoResultFound
:
218 c
.write_thread_form
= WriteThreadForm(request
.params
)
220 # Reshow the form on failure
221 if not c
.write_thread_form
.validate():
222 return render('/forum/write_thread.mako')
224 # Otherwise, add the post.
225 c
.forum
= meta
.Session
.query(forum_model
.Forum
) \
226 .with_lockmode('update') \
229 thread
= forum_model
.Thread(
230 forum_id
= c
.forum
.id,
231 subject
= c
.write_thread_form
.subject
.data
,
234 source
= c
.write_thread_form
.content
.data
235 post
= forum_model
.Post(
237 author_user_id
= c
.user
.id,
238 raw_content
= source
,
239 content
= spline
.lib
.markdown
.translate(source
),
242 thread
.posts
.append(post
)
243 c
.forum
.threads
.append(thread
)
245 meta
.Session
.commit()
247 # Redirect to the new thread
248 h
.flash("Contribution to the collective knowledge of the species successfully recorded.")
250 url(controller
='forum', action
='posts',
251 forum_id
=forum_id
, thread_id
=thread
.id),
255 def write(self
, forum_id
, thread_id
):
256 """Provides a form for posting to a thread."""
257 if not c
.user
.can('forum:create-post'):
261 c
.thread
= meta
.Session
.query(forum_model
.Thread
) \
262 .filter_by(id=thread_id
, forum_id
=forum_id
).one()
263 except NoResultFound
:
266 c
.write_post_form
= WritePostForm(request
.params
)
267 return render('/forum/write.mako')
270 def write_commit(self
, forum_id
, thread_id
):
271 """Post to a thread."""
272 if not c
.user
.can('forum:create-post'):
276 c
.thread
= meta
.Session
.query(forum_model
.Thread
) \
277 .filter_by(id=thread_id
, forum_id
=forum_id
).one()
278 except NoResultFound
:
281 c
.write_post_form
= WritePostForm(request
.params
)
283 # Reshow the form on failure
284 if not c
.write_post_form
.validate():
285 return render('/forum/write.mako')
287 # Otherwise, add the post.
288 c
.thread
= meta
.Session
.query(forum_model
.Thread
) \
289 .with_lockmode('update') \
292 source
= c
.write_post_form
.content
.data
293 post
= forum_model
.Post(
294 position
= c
.thread
.post_count
+ 1,
295 author_user_id
= c
.user
.id,
296 raw_content
= source
,
297 content
= spline
.lib
.markdown
.translate(source
),
300 c
.thread
.posts
.append(post
)
301 c
.thread
.post_count
+= 1
303 meta
.Session
.commit()
305 # Redirect to the thread
306 # XXX probably to the post instead; anchor? depends on paging scheme
307 h
.flash('Your uniqueness has been added to our own.')
309 url(controller
='forum', action
='posts',
310 forum_id
=forum_id
, thread_id
=thread_id
),