class ThreadsController(BaseController): __metaclass__=h.ProxiedAttrMeta M=h.attrproxy('_discussion_controller', 'M') W=h.attrproxy('_discussion_controller', 'W') ThreadController=h.attrproxy('_discussion_controller', 'ThreadController') PostController=h.attrproxy('_discussion_controller', 'PostController') AttachmentController=h.attrproxy('_discussion_controller', 'AttachmentController') def __init__(self, discussion_controller): self._discussion_controller = discussion_controller @expose() def _lookup(self, id=None, *remainder): if id: id=unquote(id) return self.ThreadController(self._discussion_controller, id), remainder else: raise exc.HTTPNotFound()
class ModerationController(BaseController): __metaclass__ = h.ProxiedAttrMeta PostModel = M.Post M = h.attrproxy('_discussion_controller', 'M') W = h.attrproxy('_discussion_controller', 'W') ThreadController = h.attrproxy('_discussion_controller', 'ThreadController') PostController = h.attrproxy('_discussion_controller', 'PostController') AttachmentController = h.attrproxy('_discussion_controller', 'AttachmentController') def _check_security(self): require_access(self.discussion, 'moderate') def __init__(self, discussion_controller): self._discussion_controller = discussion_controller @LazyProperty def discussion(self): return self._discussion_controller.discussion @h.vardec @expose('jinja:allura:templates/discussion/moderate.html') @validate(pass_validator) def index(self, **kw): kw = WidgetConfig.post_filter.validate(kw, None) page = kw.pop('page', 0) limit = kw.pop('limit', 50) status = kw.pop('status', 'pending') flag = kw.pop('flag', None) c.post_filter = WidgetConfig.post_filter c.moderate_posts = WidgetConfig.moderate_posts query = dict(discussion_id=self.discussion._id) if status != '-': query['status'] = status if flag: query['flags'] = {'$gte': int(flag)} q = self.PostModel.query.find(query) count = q.count() if not page: page = 0 page = int(page) limit = int(limit) q = q.skip(page) q = q.limit(limit) pgnum = (page // limit) + 1 pages = (count // limit) + 1 return dict(discussion=self.discussion, posts=q, page=page, limit=limit, status=status, flag=flag, pgnum=pgnum, pages=pages) @h.vardec @expose() @require_post() def save_moderation(self, post=[], delete=None, spam=None, approve=None, **kw): for p in post: if 'checked' in p: posted = self.PostModel.query.get(full_slug=p['full_slug']) if posted: if delete: posted.delete() # If we just deleted the last post in the # thread, delete the thread. if posted.thread and posted.thread.num_replies == 0: posted.thread.delete() elif spam and posted.status != 'spam': posted.spam() elif approve and posted.status != 'ok': posted.status = 'ok' posted.thread.num_replies += 1 redirect(request.referer)
class PostController(BaseController): __metaclass__ = h.ProxiedAttrMeta M = h.attrproxy('_discussion_controller', 'M') W = h.attrproxy('_discussion_controller', 'W') ThreadController = h.attrproxy('_discussion_controller', 'ThreadController') PostController = h.attrproxy('_discussion_controller', 'PostController') AttachmentController = h.attrproxy('_discussion_controller', 'AttachmentController') def _check_security(self): require_access(self.post, 'read') def __init__(self, discussion_controller, thread, slug): self._discussion_controller = discussion_controller self.thread = thread self._post_slug = slug self.attachment = DiscussionAttachmentsController(self.post) @LazyProperty def post(self): result = self.M.Post.query.find(dict(slug=self._post_slug)).all() for p in result: if p.thread_id == self.thread._id: return p if result: redirect(result[0].url()) else: redirect('..') @h.vardec @expose('jinja:allura:templates/discussion/post.html') @validate(pass_validator) @utils.AntiSpam.validate('Spambot protection engaged') def index(self, version=None, **kw): c.post = self.W.post if request.method == 'POST': require_access(self.post, 'moderate') post_fields = self.W.edit_post.to_python(kw, None) file_info = post_fields.pop('file_info', None) if hasattr(file_info, 'file'): self.post.attach(file_info.filename, file_info.file, content_type=file_info.type, post_id=self.post._id, thread_id=self.post.thread_id, discussion_id=self.post.discussion_id) for k, v in post_fields.iteritems(): try: setattr(self.post, k, v) except AttributeError: continue self.post.edit_count = self.post.edit_count + 1 self.post.last_edit_date = datetime.utcnow() self.post.last_edit_by_id = c.user._id redirect(request.referer) elif request.method == 'GET': if version is not None: HC = self.post.__mongometa__.history_class ss = HC.query.find({ 'artifact_id': self.post._id, 'version': int(version) }).first() if not ss: raise exc.HTTPNotFound post = Object(ss.data, acl=self.post.acl, author=self.post.author, url=self.post.url, thread=self.post.thread, reply_subject=self.post.reply_subject, attachments=self.post.attachments, related_artifacts=self.post.related_artifacts) else: post = self.post return dict(discussion=self.post.discussion, post=post) @h.vardec @expose() @require_post() @validate(pass_validator, error_handler=index) @utils.AntiSpam.validate('Spambot protection engaged') @require_post(redir='.') def reply(self, **kw): require_access(self.thread, 'post') kw = self.W.edit_post.to_python(kw, None) self.thread.post(parent_id=self.post._id, **kw) self.thread.num_replies += 1 redirect(request.referer) @h.vardec @expose() @require_post() @validate(pass_validator, error_handler=index) def moderate(self, **kw): require_access(self.post.thread, 'moderate') if kw.pop('delete', None): self.post.delete() self.thread.update_stats() elif kw.pop('spam', None): self.post.status = 'spam' self.thread.update_stats() redirect(request.referer) @h.vardec @expose() @require_post() @validate(pass_validator, error_handler=index) def flag(self, **kw): self.W.flag_post.to_python(kw, None) if c.user._id not in self.post.flagged_by: self.post.flagged_by.append(c.user._id) self.post.flags += 1 redirect(request.referer) @h.vardec @expose() @require_post() def attach(self, file_info=None): require_access(self.post, 'moderate') if hasattr(file_info, 'file'): mime_type = file_info.type # If mime type was not passed or bogus, guess it if not mime_type or '/' not in mime_type: mime_type = utils.guess_mime_type(file_info.filename) self.post.attach(file_info.filename, file_info.file, content_type=mime_type, post_id=self.post._id, thread_id=self.post.thread_id, discussion_id=self.post.discussion_id) redirect(request.referer) @expose() def _lookup(self, id, *remainder): id = unquote(id) return self.PostController(self._discussion_controller, self.thread, self._post_slug + '/' + id), remainder
class ThreadController(BaseController): __metaclass__ = h.ProxiedAttrMeta M = h.attrproxy('_discussion_controller', 'M') W = h.attrproxy('_discussion_controller', 'W') ThreadController = h.attrproxy('_discussion_controller', 'ThreadController') PostController = h.attrproxy('_discussion_controller', 'PostController') AttachmentController = h.attrproxy('_discussion_controller', 'AttachmentController') def _check_security(self): require_access(self.thread, 'read') def __init__(self, discussion_controller, thread_id): self._discussion_controller = discussion_controller self.discussion = discussion_controller.discussion self.thread = self.M.Thread.query.get(_id=thread_id) if not self.thread: raise exc.HTTPNotFound @expose() def _lookup(self, id, *remainder): id = unquote(id) return self.PostController(self._discussion_controller, self.thread, id), remainder @expose('jinja:allura:templates/discussion/thread.html') def index(self, limit=None, page=0, count=0, **kw): c.thread = self.W.thread c.thread_header = self.W.thread_header limit, page, start = g.handle_paging(limit, page) self.thread.num_views += 1 M.session.artifact_orm_session._get( ).skip_mod_date = True # the update to num_views shouldn't affect it count = self.thread.query_posts(page=page, limit=int(limit)).count() return dict(discussion=self.thread.discussion, thread=self.thread, page=int(page), count=int(count), limit=int(limit), show_moderate=kw.get('show_moderate')) @h.vardec @expose() @require_post() @validate(pass_validator, error_handler=index) @utils.AntiSpam.validate('Spambot protection engaged') def post(self, **kw): require_access(self.thread, 'post') kw = self.W.edit_post.to_python(kw, None) if not kw['text']: flash('Your post was not saved. You must provide content.', 'error') redirect(request.referer) file_info = kw.get('file_info', None) p = self.thread.add_post(**kw) if hasattr(file_info, 'file'): p.attach(file_info.filename, file_info.file, content_type=file_info.type, post_id=p._id, thread_id=p.thread_id, discussion_id=p.discussion_id) if self.thread.artifact: self.thread.artifact.mod_date = datetime.utcnow() flash('Message posted') redirect(request.referer) @expose() @require_post() def tag(self, labels, **kw): require_access(self.thread, 'post') self.thread.labels = labels.split(',') redirect(request.referer) @expose() def flag_as_spam(self, **kw): require_access(self.thread, 'moderate') self.thread.spam() flash('Thread flagged as spam.') redirect(self.discussion.url()) @without_trailing_slash @expose() @validate( dict(since=DateTimeConverter(if_empty=None), until=DateTimeConverter(if_empty=None), page=validators.Int(if_empty=None), limit=validators.Int(if_empty=None))) def feed(self, since=None, until=None, page=None, limit=None): if request.environ['PATH_INFO'].endswith('.atom'): feed_type = 'atom' else: feed_type = 'rss' title = 'Recent posts to %s' % (self.thread.subject or '(no subject)') feed = M.Feed.feed(dict(ref_id=self.thread.index_id()), feed_type, title, self.thread.url(), title, since, until, page, limit) response.headers['Content-Type'] = '' response.content_type = 'application/xml' return feed.writeString('utf-8')
class ModerationController(BaseController): __metaclass__ = h.ProxiedAttrMeta PostModel = M.Post M = h.attrproxy('_discussion_controller', 'M') W = h.attrproxy('_discussion_controller', 'W') ThreadController = h.attrproxy('_discussion_controller', 'ThreadController') PostController = h.attrproxy('_discussion_controller', 'PostController') AttachmentController = h.attrproxy('_discussion_controller', 'AttachmentController') def _check_security(self): require_access(self.discussion, 'moderate') def __init__(self, discussion_controller): self._discussion_controller = discussion_controller @LazyProperty def discussion(self): return self._discussion_controller.discussion @h.vardec @expose('jinja:allura:templates/discussion/moderate.html') @validate(pass_validator) def index(self, **kw): kw = WidgetConfig.post_filter.validate(kw, None) page = kw.pop('page', 0) limit = kw.pop('limit', 50) status = kw.pop('status', 'pending') username = kw.pop('username', None) flag = kw.pop('flag', None) c.post_filter = WidgetConfig.post_filter c.moderate_posts = WidgetConfig.moderate_posts query = dict(discussion_id=self.discussion._id, deleted=False) if status != '-': query['status'] = status if flag: query['flags'] = {'$gte': int(flag)} if username: filtered_user = User.by_username(username) query['author_id'] = filtered_user._id if filtered_user else None q = self.PostModel.query.find(query) count = q.count() limit, page, start = g.handle_paging(limit, page or 0, default=50) q = q.skip(start) q = q.limit(limit) pgnum = (page // limit) + 1 pages = (count // limit) + 1 return dict(discussion=self.discussion, posts=q, page=page, limit=limit, status=status, flag=flag, username=username, pgnum=pgnum, pages=pages) @h.vardec @expose() @require_post() def save_moderation(self, post=[], delete=None, spam=None, approve=None, **kw): for p in post: if 'checked' in p: posted = self.PostModel.query.get( _id=p['_id'], # make sure nobody hacks the HTML form to moderate other # posts discussion_id=self.discussion._id, ) if posted: if delete: posted.delete() # If we just deleted the last post in the # thread, delete the thread. if posted.thread and posted.thread.num_replies == 0: posted.thread.delete() elif spam and posted.status != 'spam': posted.spam() elif approve and posted.status != 'ok': posted.approve() g.spam_checker.submit_ham(posted.text, artifact=posted, user=posted.author()) posted.thread.post_to_feed(posted) redirect(request.referer)
class PostController(BaseController): __metaclass__ = h.ProxiedAttrMeta M = h.attrproxy('_discussion_controller', 'M') W = h.attrproxy('_discussion_controller', 'W') ThreadController = h.attrproxy('_discussion_controller', 'ThreadController') PostController = h.attrproxy('_discussion_controller', 'PostController') AttachmentController = h.attrproxy('_discussion_controller', 'AttachmentController') def _check_security(self): require_access(self.post, 'read') def __init__(self, discussion_controller, thread, slug): self._discussion_controller = discussion_controller self.thread = thread self._post_slug = slug self.attachment = DiscussionAttachmentsController(self.post) @LazyProperty def post(self): post = self.M.Post.query.get(slug=self._post_slug, thread_id=self.thread._id) if post: return post else: redirect('..') @h.vardec @expose('jinja:allura:templates/discussion/post.html') @validate(pass_validator) @utils.AntiSpam.validate('Spambot protection engaged') def index(self, version=None, **kw): c.post = self.W.post if request.method == 'POST': require_access(self.post, 'moderate') post_fields = self.W.edit_post.to_python(kw, None) file_info = post_fields.pop('file_info', None) self.post.add_multiple_attachments(file_info) for k, v in post_fields.iteritems(): try: setattr(self.post, k, v) except AttributeError: continue self.post.edit_count = self.post.edit_count + 1 self.post.last_edit_date = datetime.utcnow() self.post.last_edit_by_id = c.user._id self.post.commit() g.director.create_activity( c.user, 'modified', self.post, target=self.post.thread.artifact or self.post.thread, related_nodes=[self.post.app_config.project], tags=['comment']) redirect(request.referer) elif request.method == 'GET': if self.post.deleted: raise exc.HTTPNotFound if version is not None: HC = self.post.__mongometa__.history_class ss = HC.query.find({ 'artifact_id': self.post._id, 'version': int(version) }).first() if not ss: raise exc.HTTPNotFound post = Object( ss.data, acl=self.post.acl, author=self.post.author, url=self.post.url, thread=self.post.thread, reply_subject=self.post.reply_subject, attachments=self.post.attachments, related_artifacts=self.post.related_artifacts, parent_security_context=lambda: None, ) else: post = self.post return dict(discussion=self.post.discussion, post=post) def error_handler(self, *args, **kwargs): redirect(request.referer) @h.vardec @expose() @require_post() @validate(pass_validator, error_handler=error_handler) @utils.AntiSpam.validate('Spambot protection engaged') @require_post(redir='.') def reply(self, file_info=None, **kw): require_access(self.thread, 'post') kw = self.W.edit_post.to_python(kw, None) p = self.thread.add_post(parent_id=self.post._id, **kw) p.add_multiple_attachments(file_info) redirect(request.referer) @h.vardec @expose() @require_post() @validate(pass_validator, error_handler=error_handler) def moderate(self, **kw): require_access(self.post.thread, 'moderate') if kw.pop('delete', None): self.post.delete() elif kw.pop('spam', None): self.post.spam() elif kw.pop('undo', None): prev_status = kw.pop('prev_status', None) self.post.undo(prev_status) elif kw.pop('approve', None): if self.post.status != 'ok': self.post.approve() g.spam_checker.submit_ham(self.post.text, artifact=self.post, user=self.post.author()) self.post.thread.post_to_feed(self.post) return dict(result='success') @h.vardec @expose() @require_post() def attach(self, file_info=None): require_access(self.post, 'moderate') self.post.add_multiple_attachments(file_info) redirect(request.referer) @expose() def _lookup(self, id, *remainder): id = unquote(id) return self.PostController(self._discussion_controller, self.thread, self._post_slug + '/' + id), remainder
class ThreadController(BaseController, FeedController): __metaclass__ = h.ProxiedAttrMeta M = h.attrproxy('_discussion_controller', 'M') W = h.attrproxy('_discussion_controller', 'W') ThreadController = h.attrproxy('_discussion_controller', 'ThreadController') PostController = h.attrproxy('_discussion_controller', 'PostController') AttachmentController = h.attrproxy('_discussion_controller', 'AttachmentController') def _check_security(self): require_access(self.thread, 'read') if self.thread.ref: require_access(self.thread.ref.artifact, 'read') def __init__(self, discussion_controller, thread_id): self._discussion_controller = discussion_controller self.discussion = discussion_controller.discussion self.thread = self.M.Thread.query.get(_id=thread_id) if not self.thread: raise exc.HTTPNotFound @expose() def _lookup(self, id, *remainder): id = unquote(id) return self.PostController(self._discussion_controller, self.thread, id), remainder @with_trailing_slash @expose('jinja:allura:templates/discussion/thread.html') def index(self, limit=None, page=0, count=0, **kw): c.thread = self.W.thread c.thread_header = self.W.thread_header limit, page, start = g.handle_paging(limit, page) self.thread.num_views += 1 # the update to num_views shouldn't affect it M.session.artifact_orm_session._get().skip_mod_date = True M.session.artifact_orm_session._get().skip_last_updated = True count = self.thread.query_posts(page=page, limit=int(limit)).count() return dict(discussion=self.thread.discussion, thread=self.thread, page=int(page), count=int(count), limit=int(limit), show_moderate=kw.get('show_moderate')) def error_handler(self, *args, **kwargs): redirect(request.referer) @h.vardec @expose() @require_post() @validate(pass_validator, error_handler=error_handler) @utils.AntiSpam.validate('Spambot protection engaged') def post(self, **kw): require_access(self.thread, 'post') if self.thread.ref: require_access(self.thread.ref.artifact, 'post') kw = self.W.edit_post.to_python(kw, None) if not kw['text']: flash('Your post was not saved. You must provide content.', 'error') redirect(request.referer) file_info = kw.get('file_info', None) p = self.thread.add_post(**kw) p.add_multiple_attachments(file_info) if self.thread.artifact: self.thread.artifact.mod_date = datetime.utcnow() flash('Message posted') redirect(request.referer) @expose() @require_post() def tag(self, labels, **kw): require_access(self.thread, 'post') if self.thread.ref: require_access(self.thread.ref.artifact, 'post') self.thread.labels = labels.split(',') redirect(request.referer) @expose() def flag_as_spam(self, **kw): require_access(self.thread, 'moderate') self.thread.spam() flash('Thread flagged as spam.') redirect(self.discussion.url()) def get_feed(self, project, app, user): """Return a :class:`allura.controllers.feed.FeedArgs` object describing the xml feed for this controller. Overrides :meth:`allura.controllers.feed.FeedController.get_feed`. """ return FeedArgs( dict(ref_id=self.thread.index_id()), 'Recent posts to %s' % (self.thread.subject or '(no subject)'), self.thread.url())
class ModerationController( six.with_metaclass(h.ProxiedAttrMeta, BaseController)): PostModel = M.Post M = h.attrproxy('_discussion_controller', 'M') W = h.attrproxy('_discussion_controller', 'W') ThreadController = h.attrproxy('_discussion_controller', 'ThreadController') PostController = h.attrproxy('_discussion_controller', 'PostController') AttachmentController = h.attrproxy('_discussion_controller', 'AttachmentController') def _check_security(self): require_access(self.discussion, 'moderate') def __init__(self, discussion_controller): self._discussion_controller = discussion_controller @LazyProperty def discussion(self): return self._discussion_controller.discussion @h.vardec @expose('jinja:allura:templates/discussion/moderate.html') @validate(pass_validator) def index(self, **kw): kw = WidgetConfig.post_filter.validate(kw, None) page = kw.pop('page', 0) limit = kw.pop('limit', 50) status = kw.pop('status', 'pending') username = kw.pop('username', None) flag = kw.pop('flag', None) c.post_filter = WidgetConfig.post_filter c.moderate_posts = WidgetConfig.moderate_posts c.page_list = WidgetConfig.page_list query = dict(discussion_id=self.discussion._id, deleted=False) if status != '-': query['status'] = status if flag: query['flags'] = {'$gte': int(flag)} if username: filtered_user = User.by_username(username) query['author_id'] = filtered_user._id if filtered_user else None q = self.PostModel.query.find(query).sort('timestamp', -1) count = q.count() limit, page, start = g.handle_paging(limit, page or 0, default=50) q = q.skip(start) q = q.limit(limit) pgnum = (page // limit) + 1 pages = (count // limit) + 1 return dict(discussion=self.discussion, posts=q, page=page, limit=limit, status=status, flag=flag, username=username, pgnum=pgnum, pages=pages, count=count) @h.vardec @expose() @require_post() def save_moderation(self, post=[], delete=None, spam=None, approve=None, **kw): count = 0 for p in post: posted = None if isinstance(p, dict): # regular form submit if 'checked' in p: posted = self.PostModel.query.get( _id=p['_id'], # make sure nobody hacks the HTML form to moderate other # posts discussion_id=self.discussion._id, ) elif isinstance(p, self.PostModel): # called from save_moderation_bulk_user with models already posted = p else: raise TypeError( 'post list should be form fields, or Post models') if posted: if delete: posted.delete() # If we just deleted the last post in the # thread, delete the thread. if posted.thread and posted.thread.num_replies == 0: count += 1 posted.thread.delete() elif spam and posted.status != 'spam': count += 1 posted.spam() elif approve and posted.status != 'ok': count += 1 posted.approve() g.spam_checker.submit_ham(posted.text, artifact=posted, user=posted.author()) posted.thread.post_to_feed(posted) flash('{} {}'.format( h.text.plural(count, 'post', 'posts'), 'deleted' if delete else 'marked as spam' if spam else 'approved')) redirect(request.referer or '/') @expose() @require_post() def save_moderation_bulk_user(self, username, **kw): # this is used by post.js as a quick way to deal with all a user's posts user = User.by_username(username) posts = self.PostModel.query.find({ 'author_id': user._id, 'deleted': False, # this is what the main moderation forms does (e.g. single discussion within a forum app) # 'discussion_id': self.discussion._id # but instead want to do all discussions within this app 'app_config_id': c.app.config._id }) return self.save_moderation(posts, **kw)
class PostController(six.with_metaclass(h.ProxiedAttrMeta, BaseController)): M = h.attrproxy('_discussion_controller', 'M') W = h.attrproxy('_discussion_controller', 'W') ThreadController = h.attrproxy('_discussion_controller', 'ThreadController') PostController = h.attrproxy('_discussion_controller', 'PostController') AttachmentController = h.attrproxy('_discussion_controller', 'AttachmentController') def _check_security(self): require_access(self.post, 'read') def __init__(self, discussion_controller, thread, slug): self._discussion_controller = discussion_controller self.thread = thread self._post_slug = slug self.attachment = DiscussionAttachmentsController(self.post) @LazyProperty def post(self): post = self.M.Post.query.get(slug=self._post_slug, thread_id=self.thread._id) if post: return post else: redirect('..') @h.vardec @expose('jinja:allura:templates/discussion/post.html') @validate(pass_validator) @utils.AntiSpam.validate('Spambot protection engaged') def index(self, version=None, **kw): c.post = self.W.post if request.method == 'POST': old_text = self.post.text require_access(self.post, 'moderate') post_fields = self.W.edit_post.to_python( kw, None ) # could raise Invalid, but doesn't seem like it ever does file_info = post_fields.pop('file_info', None) self.post.add_multiple_attachments(file_info) for k, v in six.iteritems(post_fields): try: setattr(self.post, k, v) except AttributeError: continue self.post.edit_count = self.post.edit_count + 1 self.post.last_edit_date = datetime.utcnow() self.post.last_edit_by_id = c.user._id self.thread.is_spam( self.post) # run spam checker, nothing to do with result yet self.post.commit() notification_tasks.send_usermentions_notification.post( self.post.index_id(), kw['text'], old_text) g.director.create_activity( c.user, 'modified', self.post, target=self.post.thread.artifact or self.post.thread, related_nodes=[self.post.app_config.project], tags=['comment']) redirect(request.referer or '/') elif request.method == 'GET': if self.post.deleted: raise exc.HTTPNotFound if version is not None: HC = self.post.__mongometa__.history_class ss = HC.query.find({ 'artifact_id': self.post._id, 'version': int(version) }).first() if not ss: raise exc.HTTPNotFound class VersionedSnapshotTempObject(Object): pass post = VersionedSnapshotTempObject( ss.data, acl=self.post.acl, author=self.post.author, url=self.post.url, thread=self.post.thread, reply_subject=self.post.reply_subject, attachments=self.post.attachments, related_artifacts=self.post.related_artifacts, parent_security_context=lambda: None, last_edit_by=lambda: self.post.last_edit_by()) else: post = self.post return dict(discussion=self.post.discussion, post=post) @without_trailing_slash @expose('json:') @require_post() def update_markdown(self, text=None, **kw): if has_access(self.post, 'moderate'): self.post.text = text self.post.edit_count = self.post.edit_count + 1 self.post.last_edit_date = datetime.utcnow() self.post.last_edit_by_id = c.user._id self.post.commit() g.director.create_activity( c.user, 'modified', self.post, target=self.post.thread.artifact or self.post.thread, related_nodes=[self.post.app_config.project], tags=['comment']) return {'status': 'success'} else: return {'status': 'no_permission'} @expose() @without_trailing_slash def get_markdown(self): return self.post.text @expose('json:') @without_trailing_slash @require_post() def post_reaction(self, r, **kw): if c.user.is_anonymous(): return {'error': 'no_permission'} status = 'ok' if r in utils.get_reaction_emoji_list(): self.post.post_reaction(r, c.user) else: status = 'error' return dict(status=status, counts=self.post.react_counts) def error_handler(self, *args, **kwargs): redirect(request.referer or '/') @memorable_forget() @h.vardec @expose() @require_post() @validate(pass_validator, error_handler=error_handler) @utils.AntiSpam.validate('Spambot protection engaged') @require_post(redir='.') def reply(self, **kw): handle_post_or_reply(thread=self.thread, parent_post_id=self.post._id, edit_widget=self.W.edit_post, rate_limit=self.rate_limit, kw=kw) @h.vardec @expose('json') @require_post() @validate(pass_validator, error_handler=error_handler) def moderate(self, **kw): require_access(self.post.thread, 'moderate') if kw.pop('delete', None): self.post.delete() elif kw.pop('spam', None): self.post.spam() elif kw.pop('undo', None): prev_status = kw.pop('prev_status', None) if self.post.status == 'spam' and prev_status == 'ok': g.spam_checker.submit_ham(self.post.text, artifact=self.post, user=self.post.author()) self.post.undo(prev_status) elif kw.pop('approve', None): if self.post.status != 'ok': self.post.approve() g.spam_checker.submit_ham(self.post.text, artifact=self.post, user=self.post.author()) self.post.thread.post_to_feed(self.post) return dict(result='success') @h.vardec @expose() @require_post() def attach(self, file_info=None): require_access(self.post, 'moderate') self.post.add_multiple_attachments(file_info) redirect(request.referer or '/') @expose() def _lookup(self, id, *remainder): id = unquote(id) return self.PostController(self._discussion_controller, self.thread, self._post_slug + '/' + id), remainder