5 from pylons
import cache
, config
, request
, response
, session
, tmpl_context
as c
, url
6 from pylons
.controllers
.util
import abort
, redirect
7 from routes
import request_config
8 from sqlalchemy
.orm
import joinedload
9 from sqlalchemy
.orm
.exc
import NoResultFound
10 from sqlalchemy
.sql
import func
12 from wtforms
import fields
14 from spline
.model
import meta
15 from spline
.lib
import helpers
as h
16 from spline
.lib
.base
import BaseController
, render
17 import spline
.lib
.markdown
18 from splinext
.forum
import model
as forum_model
20 log
= logging
.getLogger(__name__
)
23 def forum_activity_score(forum
):
24 """Returns a number representing how active a forum is, based on the past
27 The calculation is arbitrary, but 0 is supposed to mean "dead" and 1 is
28 supposed to mean "healthy".
30 cutoff
= datetime
.datetime
.now() - datetime
.timedelta(days
=7)
32 post_count
= meta
.Session
.query(forum_model
.Post
) \
33 .join(forum_model
.Post
.thread
) \
34 .filter(forum_model
.Thread
.forum
== forum
) \
35 .filter(forum_model
.Post
.posted_time
>= cutoff
) \
38 # Avoid domain errors!
42 # The log is to scale 0 posts to 0.0, and 168 posts to 1.0.
43 # The square is really just to take the edge off the log curve; it
44 # accelerates to 1 very quickly, then slows considerably after that.
45 # Squaring helps both of these problems.
46 score
= (math
.log(post_count
) / math
.log(168)) ** 2
48 # TODO more threads and more new threads should boost the score slightly
52 def get_forum_activity():
53 """Returns a hash mapping forum ids to their level of 'activity'."""
54 forums_q
= meta
.Session
.query(forum_model
.Forum
)
57 for forum
in forums_q
:
58 activity
[forum
.id] = forum_activity_score(forum
)
62 def get_forum_volume():
63 """Returns a hash mapping forum ids to the percentage of all posts that
66 # Do a complicated-ass subquery to get a list of forums and postcounts
67 volume_q
= meta
.Session
.query(
68 forum_model
.Forum
.id.label('forum_id'),
69 func
.count(forum_model
.Post
.id).label('post_count'),
71 .outerjoin(forum_model
.Thread
) \
72 .outerjoin(forum_model
.Post
) \
73 .group_by(forum_model
.Forum
.id)
75 # Stick this into a hash, and count the number of total posts
78 for forum_id
, post_count
in volume_q
:
79 post_count
= float(post_count
or 0)
80 volume
[forum_id
] = post_count
81 total_posts
+= post_count
83 # Divide, to get a percentage
85 for forum_id
, post_count
in volume
.iteritems():
86 volume
[forum_id
] /= total_posts
91 class WritePostForm(wtforms
.Form
):
92 content
= fields
.TextAreaField('Content')
94 class WriteThreadForm(WritePostForm
):
95 subject
= fields
.TextField('Subject')
97 class ForumController(BaseController
):
100 c
.forums
= meta
.Session
.query(forum_model
.Forum
) \
101 .order_by(forum_model
.Forum
.id.asc()) \
104 # Get some forum stats. Cache them because they're a bit expensive to
105 # compute. Expire after an hour.
106 # XXX when there are admin controls, they'll need to nuke this cache
107 # when messing with the forum list
108 forum_cache
= cache
.get_cache('spline-forum', expiretime
=3600)
109 c
.forum_activity
= forum_cache
.get_value(
110 key
='forum_activity', createfunc
=get_forum_activity
)
111 c
.forum_volume
= forum_cache
.get_value(
112 key
='forum_volume', createfunc
=get_forum_volume
)
114 c
.max_volume
= max(c
.forum_volume
.itervalues()) or 1
116 # Need to know the last post for each forum, in realtime
118 last_post_subq
= meta
.Session
.query(
119 forum_model
.Forum
.id.label('forum_id'),
120 func
.max(forum_model
.Post
.posted_time
).label('posted_time'),
122 .outerjoin(forum_model
.Thread
) \
123 .outerjoin(forum_model
.Post
) \
124 .group_by(forum_model
.Forum
.id) \
126 last_post_q
= meta
.Session
.query(
128 last_post_subq
.c
.forum_id
,
132 forum_model
.Post
.posted_time
== last_post_subq
.c
.posted_time
,
135 joinedload('thread'),
136 joinedload('author'),
138 for post
, forum_id
in last_post_q
:
139 c
.last_post
[forum_id
] = post
141 return render('/forum/forums.mako')
143 def threads(self
, forum_id
):
144 c
.forum
= meta
.Session
.query(forum_model
.Forum
).get(forum_id
)
148 c
.write_thread_form
= WriteThreadForm()
150 c
.threads
= c
.forum
.threads
.options(
151 joinedload('last_post'),
152 joinedload('last_post.author'),
155 return render('/forum/threads.mako')
157 def posts(self
, forum_id
, thread_id
):
159 c
.thread
= meta
.Session
.query(forum_model
.Thread
) \
160 .filter_by(id=thread_id
, forum_id
=forum_id
).one()
161 except NoResultFound
:
164 c
.write_post_form
= WritePostForm()
166 return render('/forum/posts.mako')
169 def write_thread(self
, forum_id
):
170 """Provides a form for posting a new thread."""
171 if not c
.user
.can('forum:create-thread'):
175 c
.forum
= meta
.Session
.query(forum_model
.Forum
) \
176 .filter_by(id=forum_id
).one()
177 except NoResultFound
:
180 c
.write_thread_form
= WriteThreadForm(request
.params
)
182 if request
.method
!= 'POST' or not c
.write_thread_form
.validate():
183 # Failure or initial request; show the form
184 return render('/forum/write_thread.mako')
187 # Otherwise, add the post.
188 c
.forum
= meta
.Session
.query(forum_model
.Forum
) \
189 .with_lockmode('update') \
192 thread
= forum_model
.Thread(
193 forum_id
= c
.forum
.id,
194 subject
= c
.write_thread_form
.subject
.data
,
197 post
= forum_model
.Post(
199 author_user_id
= c
.user
.id,
200 content
= c
.write_thread_form
.content
.data
,
203 thread
.posts
.append(post
)
204 c
.forum
.threads
.append(thread
)
206 meta
.Session
.commit()
208 # Redirect to the new thread
209 h
.flash("Contribution to the collective knowledge of the species successfully recorded.")
211 url(controller
='forum', action
='posts',
212 forum_id
=forum_id
, thread_id
=thread
.id),
216 def write(self
, forum_id
, thread_id
):
217 """Provides a form for posting to a thread."""
218 if not c
.user
.can('forum:create-post'):
222 c
.thread
= meta
.Session
.query(forum_model
.Thread
) \
223 .filter_by(id=thread_id
, forum_id
=forum_id
).one()
224 except NoResultFound
:
227 c
.write_post_form
= WritePostForm(request
.params
)
229 if request
.method
!= 'POST' or not c
.write_post_form
.validate():
230 # Failure or initial request; show the form
231 return render('/forum/write.mako')
234 # Otherwise, add the post.
235 c
.thread
= meta
.Session
.query(forum_model
.Thread
) \
236 .with_lockmode('update') \
239 source
= c
.write_post_form
.content
.data
240 post
= forum_model
.Post(
241 position
= c
.thread
.post_count
+ 1,
242 author_user_id
= c
.user
.id,
243 raw_content
= source
,
244 content
= spline
.lib
.markdown
.translate(source
),
247 c
.thread
.posts
.append(post
)
248 c
.thread
.post_count
+= 1
250 meta
.Session
.commit()
252 # Redirect to the thread
253 # XXX probably to the post instead; anchor? depends on paging scheme
254 h
.flash('Your uniqueness has been added to our own.')
256 url(controller
='forum', action
='posts',
257 forum_id
=forum_id
, thread_id
=thread_id
),