0d1207f4675126cf147aea547743daafa5ee6311
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 # nb: This will never show post-less threads. Oh well!
151 threads_q
= c
.forum
.threads \
152 .join(forum_model
.Thread
.last_post
) \
153 .order_by(forum_model
.Post
.posted_time
.desc()) \
154 .options(joinedload('last_post.author'))
155 c
.num_threads
= threads_q
.count()
157 c
.skip
= int(request
.params
.get('skip', 0))
161 c
.threads
= threads_q
.offset(c
.skip
).limit(c
.per_page
)
163 return render('/forum/threads.mako')
165 def posts(self
, forum_id
, thread_id
):
167 c
.thread
= meta
.Session
.query(forum_model
.Thread
) \
168 .filter_by(id=thread_id
, forum_id
=forum_id
).one()
169 except NoResultFound
:
172 c
.write_post_form
= WritePostForm()
174 posts_q
= c
.thread
.posts \
175 .order_by(forum_model
.Post
.position
.asc()) \
176 .options(joinedload('author'))
177 c
.num_posts
= c
.thread
.post_count
179 c
.skip
= int(request
.params
.get('skip', 0))
183 c
.posts
= posts_q
.offset(c
.skip
).limit(c
.per_page
)
185 return render('/forum/posts.mako')
188 def write_thread(self
, forum_id
):
189 """Provides a form for posting a new thread."""
190 if not c
.user
.can('forum:create-thread'):
194 c
.forum
= meta
.Session
.query(forum_model
.Forum
) \
195 .filter_by(id=forum_id
).one()
196 except NoResultFound
:
199 c
.write_thread_form
= WriteThreadForm(request
.params
)
201 if request
.method
!= 'POST' or not c
.write_thread_form
.validate():
202 # Failure or initial request; show the form
203 return render('/forum/write_thread.mako')
206 # Otherwise, add the post.
207 c
.forum
= meta
.Session
.query(forum_model
.Forum
) \
208 .with_lockmode('update') \
211 thread
= forum_model
.Thread(
212 forum_id
= c
.forum
.id,
213 subject
= c
.write_thread_form
.subject
.data
,
216 source
= c
.write_thread_form
.content
.data
217 post
= forum_model
.Post(
219 author_user_id
= c
.user
.id,
220 raw_content
= source
,
221 content
= spline
.lib
.markdown
.translate(source
),
224 thread
.posts
.append(post
)
225 c
.forum
.threads
.append(thread
)
227 meta
.Session
.commit()
229 # Redirect to the new thread
230 h
.flash("Contribution to the collective knowledge of the species successfully recorded.")
232 url(controller
='forum', action
='posts',
233 forum_id
=forum_id
, thread_id
=thread
.id),
237 def write(self
, forum_id
, thread_id
):
238 """Provides a form for posting to a thread."""
239 if not c
.user
.can('forum:create-post'):
243 c
.thread
= meta
.Session
.query(forum_model
.Thread
) \
244 .filter_by(id=thread_id
, forum_id
=forum_id
).one()
245 except NoResultFound
:
248 c
.write_post_form
= WritePostForm(request
.params
)
250 if request
.method
!= 'POST' or not c
.write_post_form
.validate():
251 # Failure or initial request; show the form
252 return render('/forum/write.mako')
255 # Otherwise, add the post.
256 c
.thread
= meta
.Session
.query(forum_model
.Thread
) \
257 .with_lockmode('update') \
260 source
= c
.write_post_form
.content
.data
261 post
= forum_model
.Post(
262 position
= c
.thread
.post_count
+ 1,
263 author_user_id
= c
.user
.id,
264 raw_content
= source
,
265 content
= spline
.lib
.markdown
.translate(source
),
268 c
.thread
.posts
.append(post
)
269 c
.thread
.post_count
+= 1
271 meta
.Session
.commit()
273 # Redirect to the thread
274 # XXX probably to the post instead; anchor? depends on paging scheme
275 h
.flash('Your uniqueness has been added to our own.')
277 url(controller
='forum', action
='posts',
278 forum_id
=forum_id
, thread_id
=thread_id
),