blob: 338764ddce6b1d48bec6e3319a3dd2547daa9c54 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import logging
import re
import pymongo
from allura.lib.search import mapped_artifacts_from_index_ids
from tg import expose, validate, redirect
from tg import request
from pylons import tmpl_context as c, app_globals as g
from webob import exc
from formencode import validators
from allura.lib import helpers as h
from allura.lib import utils
from allura import model as M
from allura.lib.security import has_access, require_access
from allura.lib.decorators import require_post
from allura.controllers import DiscussionController, ThreadController, PostController, ModerationController
from allura.lib.widgets import discuss as DW
from allura.lib.widgets.subscriptions import SubscribeForm
from forgediscussion import model as DM
from forgediscussion import widgets as FW
from forgediscussion import tasks
log = logging.getLogger(__name__)
class pass_validator(object):
def validate(self, v, s):
return v
pass_validator = pass_validator()
class ModelConfig(object):
Discussion = DM.Forum
Thread = DM.ForumThread
Post = DM.ForumPost
Attachment = M.DiscussionAttachment
class WidgetConfig(object):
# Forms
subscription_form = DW.SubscriptionForm()
subscribe_form = SubscribeForm()
edit_post = DW.EditPost(show_subject=True)
moderate_thread = FW.ModerateThread()
post_filter = DW.PostFilter()
moderate_posts = DW.ModeratePosts()
# Other widgets
discussion = FW.Forum()
thread = FW.Thread()
post = FW.Post()
thread_header = FW.ThreadHeader()
announcements_table = FW.AnnouncementsTable()
discussion_header = FW.ForumHeader()
class ForumController(DiscussionController):
M = ModelConfig
W = WidgetConfig
def _check_security(self):
require_access(self.discussion, 'read')
def __init__(self, forum_id):
self.ThreadController = ForumThreadController
self.PostController = ForumPostController
self.moderate = ForumModerationController(self)
self.discussion = DM.Forum.query.get(
app_config_id=c.app.config._id,
shortname=forum_id)
if not self.discussion:
raise exc.HTTPNotFound()
super(ForumController, self).__init__()
@expose()
def _lookup(self, id=None, *remainder):
if id and self.discussion:
return ForumController(self.discussion.shortname + '/' + id), remainder
else:
raise exc.HTTPNotFound()
@expose('jinja:forgediscussion:templates/index.html')
@validate(dict(page=validators.Int(if_empty=0, if_invalid=0),
limit=validators.Int(if_empty=None, if_invalid=None)))
def index(self, threads=None, limit=None, page=0, count=0, **kw):
if self.discussion.deleted:
redirect(self.discussion.url() + 'deleted')
limit, page, start = g.handle_paging(limit, page)
if not c.user.is_anonymous():
c.subscribed = M.Mailbox.subscribed(artifact=self.discussion)
c.tool_subscribed = M.Mailbox.subscribed()
threads = DM.ForumThread.query.find(dict(discussion_id=self.discussion._id, num_replies={'$gt': 0})) \
.sort([('flags', pymongo.DESCENDING), ('last_post_date', pymongo.DESCENDING)])
c.discussion = self.W.discussion
c.discussion_header = self.W.discussion_header
c.whole_forum_subscription_form = self.W.subscribe_form
return dict(
discussion=self.discussion,
count=threads.count(),
threads=threads.skip(start).limit(int(limit)).all(),
limit=limit,
page=page)
@expose('jinja:forgediscussion:templates/discussionforums/deleted.html')
def deleted(self):
return dict()
@expose('json:')
@require_post()
@validate(W.subscribe_form)
def subscribe_to_forum(self, subscribe=None, unsubscribe=None, shortname=None, **kw):
if subscribe:
self.discussion.subscribe(type='direct')
# unsubscribe from all individual threads that are part of this forum, so you don't have overlapping subscriptions
forumthread_index_prefix = (DM.ForumThread.__module__ + '.' + DM.ForumThread.__name__).replace('.', '/') + '#'
thread_mboxes = M.Mailbox.query.find(dict(
user_id=c.user._id,
project_id=c.project._id,
app_config_id=c.app.config._id,
artifact_index_id=re.compile('^' + re.escape(forumthread_index_prefix)),
)).all()
# get the ForumThread objects from the subscriptions
thread_index_ids = [mbox.artifact_index_id for mbox in thread_mboxes]
threads_by_id = mapped_artifacts_from_index_ids(thread_index_ids, DM.ForumThread, objectid_id=False)
for mbox in thread_mboxes:
thread_id = mbox.artifact_index_id.split('#')[1]
thread = threads_by_id[thread_id]
# only delete if the ForumThread is part of this forum
if thread.discussion_id == self.discussion._id:
mbox.delete()
elif unsubscribe:
self.discussion.unsubscribe()
return {
'status': 'ok',
'subscribed': M.Mailbox.subscribed(artifact=self.discussion),
'subscribed_to_tool': M.Mailbox.subscribed(),
}
class ForumThreadController(ThreadController):
W = WidgetConfig
@expose('jinja:forgediscussion:templates/discussionforums/thread.html')
@validate(dict(page=validators.Int(if_empty=0, if_invalid=0),
limit=validators.Int(if_empty=25, if_invalid=25)))
def index(self, limit=25, page=0, count=0, **kw):
if self.thread.discussion.deleted and not has_access(c.app, 'configure')():
redirect(self.thread.discussion.url() + 'deleted')
c.thread_subscription_form = self.W.subscribe_form
return super(ForumThreadController, self).index(limit=limit, page=page, count=count, show_moderate=True, **kw)
@h.vardec
@expose()
@require_post()
@validate(pass_validator, index)
def moderate(self, **kw):
require_access(self.thread, 'moderate')
if self.thread.discussion.deleted and not has_access(c.app, 'configure')():
redirect(self.thread.discussion.url() + 'deleted')
args = self.W.moderate_thread.validate(kw, None)
tasks.calc_forum_stats.post(self.thread.discussion.shortname)
if args.pop('delete', None):
url = self.thread.discussion.url()
self.thread.delete()
redirect(url)
forum = args.pop('discussion')
if forum != self.thread.discussion:
tasks.calc_forum_stats.post(forum.shortname)
self.thread.set_forum(forum)
self.thread.flags = args.pop('flags', [])
self.thread.subject = args.pop('subject', self.thread.subject)
redirect(self.thread.url())
@expose('json:')
@require_post()
@validate(W.subscribe_form)
def subscribe(self, subscribe=None, unsubscribe=None, **kw):
if subscribe:
self.thread.subscribe()
elif unsubscribe:
self.thread.unsubscribe()
sub_tool = M.Mailbox.subscribed()
sub_forum = M.Mailbox.subscribed(artifact=self.discussion)
return {
'status': 'ok',
'subscribed': M.Mailbox.subscribed(artifact=self.thread),
'subscribed_to_tool': sub_tool or sub_forum,
'subscribed_to_entire_name': 'forum' if sub_forum else 'discussion tool',
}
class ForumPostController(PostController):
@h.vardec
@expose('jinja:allura:templates/discussion/post.html')
@validate(pass_validator)
@utils.AntiSpam.validate('Spambot protection engaged')
def index(self, **kw):
if self.thread.discussion.deleted and not has_access(c.app, 'configure')():
redirect(self.thread.discussion.url() + 'deleted')
return super(ForumPostController, self).index(**kw)
@expose()
@require_post()
@validate(pass_validator, error_handler=index)
def moderate(self, **kw):
require_access(self.post.thread, 'moderate')
if self.thread.discussion.deleted and not has_access(c.app, 'configure')():
redirect(self.thread.discussion.url() + 'deleted')
tasks.calc_thread_stats.post(self.post.thread._id)
tasks.calc_forum_stats(self.post.discussion.shortname)
super(ForumPostController, self).moderate(**kw)
class ForumModerationController(ModerationController):
PostModel = DM.ForumPost