ForgeDiscussion/forgediscussion/controllers/forum.py (180 lines of code) (raw):

# 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 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.controllers import discuss as controllers_discuss 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: def validate(self, v, s): return v pass_validator = pass_validator() class ModelConfig(controllers_discuss.ModelConfig): Discussion = DM.Forum Thread = DM.ForumThread Post = DM.ForumPost Attachment = M.DiscussionAttachment class WidgetConfig(controllers_discuss.WidgetConfig): # 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().__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: raise exc.HTTPNotFound() 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('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={'$regex': '^' + 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'): raise exc.HTTPNotFound() c.thread_subscription_form = self.W.subscribe_form return super().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'): raise exc.HTTPNotFound() 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'): raise exc.HTTPNotFound() return super().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'): raise exc.HTTPNotFound() tasks.calc_thread_stats.post(self.post.thread._id) tasks.calc_forum_stats(self.post.discussion.shortname) super().moderate(**kw) class ForumModerationController(ModerationController): PostModel = DM.ForumPost