[#8280] add mechanism to block user and spam all their posts
diff --git a/Allura/allura/controllers/discuss.py b/Allura/allura/controllers/discuss.py
index 7209242..52a1b3c 100644
--- a/Allura/allura/controllers/discuss.py
+++ b/Allura/allura/controllers/discuss.py
@@ -337,7 +337,11 @@
{'artifact_id': self.post._id, 'version': int(version)}).first()
if not ss:
raise exc.HTTPNotFound
- post = Object(
+
+ class VersionedSnapshotTempObject(Object):
+ pass
+
+ post = VersionedSnapshotTempObject(
ss.data,
acl=self.post.acl,
author=self.post.author,
@@ -518,29 +522,59 @@
@expose()
@require_post()
def save_moderation(self, post=[], delete=None, spam=None, approve=None, **kw):
+ count = 0
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)
+ 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(u'{} {}'.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 PostRestController(PostController):
diff --git a/Allura/allura/lib/widgets/resources/js/post.js b/Allura/allura/lib/widgets/resources/js/post.js
index 0a47a48..a4811dd 100644
--- a/Allura/allura/lib/widgets/resources/js/post.js
+++ b/Allura/allura/lib/widgets/resources/js/post.js
@@ -55,6 +55,35 @@
});
});
+ $('.spam-all-block', post).click(function(e) {
+ e.preventDefault();
+ var $this = $(this);
+ var cval = $.cookie('_session_id');
+ $.ajax({
+ type: 'POST',
+ url: $this.attr('data-admin-url') + '/block_user',
+ data: {
+ username: $this.attr('data-user'),
+ perm: 'post',
+ '_session_id': cval
+ },
+ success: function (data, textStatus, jqxhr) {
+ if (data.error) {
+ flash(data.error, 'error');
+ } else if (data.username) {
+ flash('User blocked', 'success');
+ // full page form submit
+ $('<form method="POST" action="' + $this.data('discussion-url')+'moderate/save_moderation_bulk_user?username=' + $this.attr('data-user') + '&spam=1">' +
+ '<input name="_session_id" type="hidden" value="'+cval+'"></form>')
+ .appendTo('body')
+ .submit();
+ } else {
+ flash('Error. Make sure you are logged in still.', 'error');
+ }
+ }
+ });
+ });
+
function spam_block_display($post, display_type) {
var spam_block = $post.find('.info.grid-15.spam-present');
var row = $post.find('.comment-row').eq(0);
diff --git a/Allura/allura/templates/widgets/post_widget.html b/Allura/allura/templates/widgets/post_widget.html
index bd03f92..0d1e6ee 100644
--- a/Allura/allura/templates/widgets/post_widget.html
+++ b/Allura/allura/templates/widgets/post_widget.html
@@ -36,9 +36,14 @@
<a href="" class="moderate_post little_link"><span>Undo</span></a>
{{lib.csrf_token()}}
</form>
+ {% if value.discussion and value.app_config %}{# avoid errors, when viewing old versions of comments (obscure, but has test coverage) #}
<br>
- <span class="spam-text">You can see all pending comments posted by this user </span>
- <a href="{{value.thread.discussion.url()}}moderate?username={{value.author().username}}&status=pending">here</a>
+ View and moderate
+ <a href="{{value.thread.discussion.url()}}moderate?username={{value.author().username}}&status=-">all "{{ value.discussion.name }}" comments posted by this user</a>
+ <br><br>
+ <a href="#" class="spam-all-block" data-user="{{ value.author().username }}" data-admin-url="{{ c.app.admin_url }}" data-discussion-url="{{value.thread.discussion.url()}}">
+ Mark all as spam, and block user from posting to "{{ value.app_config.options.mount_label }}"</a>
+ {% endif %}
</div>
{% endif %}
<div class="comment-row">
diff --git a/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py b/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py
index 58c98ef..e6d0353 100644
--- a/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py
+++ b/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py
@@ -63,9 +63,10 @@
assert_equal(self.get_post(), None)
def moderate_post(self, **kwargs):
- self.controller.save_moderation(
- post=[dict(checked=True, _id=self.get_post()._id)],
- **kwargs)
+ with patch('allura.controllers.discuss.flash'):
+ self.controller.save_moderation(
+ post=[dict(checked=True, _id=self.get_post()._id)],
+ **kwargs)
ThreadLocalORMSession.flush_all()
def get_post(self):
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
index 44cc42f..103d79f 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
@@ -446,6 +446,30 @@
input_field = r.html.fieldset.find('input', {'value': username})
assert input_field is None
+ def test_save_moderation_bulk_user(self):
+ # create posts
+ for i in range(5):
+ r = self.app.get('/discussion/create_topic/')
+ f = r.html.find(
+ 'form', {'action': '/p/test/discussion/save_new_topic'})
+ params = dict()
+ inputs = f.findAll('input')
+ for field in inputs:
+ if field.has_key('name'): # nopep8 - beautifulsoup3 actually uses has_key
+ params[field['name']] = field.get('value') or ''
+ params[f.find('textarea')['name']] = 'Post text'
+ params[f.find('select')['name']] = 'testforum'
+ params[f.find('input', {'style': 'width: 90%'})['name']] = "this is my post"
+ r = self.app.post('/discussion/save_new_topic', params=params)
+
+ assert_equal(5, FM.ForumPost.query.find({'status': 'ok'}).count())
+
+ r = self.app.post('/discussion/testforum/moderate/save_moderation_bulk_user', params={
+ 'username': 'test-admin',
+ 'spam': '1'})
+ assert_in(u'5 posts marked as spam', self.webflash(r))
+ assert_equal(5, FM.ForumPost.query.find({'status': 'spam'}).count())
+
def test_posting(self):
r = self.app.get('/discussion/create_topic/')
f = r.html.find('form', {'action': '/p/test/discussion/save_new_topic'})