From 5a6370fccd50663bdf570a18d59bffb875ea487e Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Wed, 9 Jul 2014 08:51:30 -0300 Subject: enabled "decline with reason" moderation and improved moderation feature --- askbot/media/js/user.js | 320 +++------------------ askbot/media/js/utils.js | 6 +- askbot/media/style/style.css | 4 + askbot/media/style/style.less | 7 + askbot/models/post.py | 45 +-- .../moderation/manage_reject_reasons_dialog.html | 43 +++ askbot/templates/moderation/queue.html | 6 +- .../templates/moderation/reject_post_dialog.html | 109 ------- askbot/views/moderation.py | 31 +- askbot/views/users.py | 5 +- 10 files changed, 159 insertions(+), 417 deletions(-) create mode 100644 askbot/templates/moderation/manage_reject_reasons_dialog.html delete mode 100644 askbot/templates/moderation/reject_post_dialog.html diff --git a/askbot/media/js/user.js b/askbot/media/js/user.js index 90ca2a7b..b2ff5658 100644 --- a/askbot/media/js/user.js +++ b/askbot/media/js/user.js @@ -1,131 +1,3 @@ -//todo: refactor this into "Inbox" object or more specialized -/* -var setup_inbox = function(){ - - var getSelected = function(){ - - var id_list = new Array(); - var elements = $('#responses input:checked').parent(); - - elements.each(function(index, element){ - var id = $(element).attr('id').replace(/^re_/,''); - id_list.push(id); - }); - - if (id_list.length === 0){ - alert(gettext('Please select at least one item')); - } - - return {id_list: id_list, elements: elements}; - }; - - var submit = function(id_list, elements, action_type){ - if (action_type == 'delete' || action_type == 'mark_new' || action_type == 'mark_seen' || action_type == 'remove_flag' || action_type == 'delete_post'){ - $.ajax({ - type: 'POST', - cache: false, - dataType: 'json', - data: JSON.stringify({memo_list: id_list, action_type: action_type}), - url: askbot['urls']['manageInbox'], - success: function(response_data){ - if (response_data['success'] == true){ - if (action_type == 'delete' || action_type == 'remove_flag' || action_type == 'delete_post'){ - elements.remove(); - } - else if (action_type == 'mark_new'){ - elements.addClass('highlight'); - elements.addClass('new'); - elements.removeClass('seen'); - } - else if (action_type == 'mark_seen'){ - elements.removeClass('highlight'); - elements.addClass('seen'); - elements.removeClass('new'); - } - } - else { - showMessage($('#responses'), response_data['message']); - } - } - }); - } - }; - - var startAction = function(action_type){ - var data = getSelected(); - if (data['id_list'].length === 0){ - return; - } - if (action_type == 'delete'){ - msg = ngettext('Delete this notification?', - 'Delete these notifications?', data['id_list'].length); - if (confirm(msg) === false){ - return; - } - } - if (action_type == 'close'){ - msg = ngettext('Close this entry?', - 'Close these entries?', data['id_list'].length); - if (confirm(msg) === false){ - return; - } - } - if (action_type == 'remove_flag'){ - msg = ngettext( - 'Remove all flags and approve this entry?', - 'Remove all flags and approve these entries?', - data['id_list'].length - ); - if (confirm(msg) === false){ - return; - } - } - submit(data['id_list'], data['elements'], action_type); - }; - setupButtonEventHandlers($('#re_mark_seen'), function(){startAction('mark_seen')}); - setupButtonEventHandlers($('#re_mark_new'), function(){startAction('mark_new')}); - setupButtonEventHandlers($('#re_dismiss'), function(){startAction('delete')}); - setupButtonEventHandlers($('#re_remove_flag'), function(){startAction('remove_flag')}); - //setupButtonEventHandlers($('#re_close'), function(){startAction('close')}); - setupButtonEventHandlers( - $('#sel_all'), - function(){ - setCheckBoxesIn('#responses .new', true); - setCheckBoxesIn('#responses .seen', true); - } - ); - setupButtonEventHandlers( - $('#sel_seen'), - function(){ - setCheckBoxesIn('#responses .seen', true); - } - ); - setupButtonEventHandlers( - $('#sel_new'), - function(){ - setCheckBoxesIn('#responses .new', true); - } - ); - setupButtonEventHandlers( - $('#sel_none'), - function(){ - setCheckBoxesIn('#responses .new', false); - setCheckBoxesIn('#responses .seen', false); - } - ); - - if ($('body').hasClass('inbox-flags')) { - var responses = $('.response-parent'); - responses.each(function(idx, response) { - var control = new PostModerationControls(); - control.setParent($(response)); - control.setReasonsDialog(rejectPostDialog); - rejectPostDialog.addPostModerationControl(control); - $(response).append(control.getElement()); - }); - } -}; -*/ var setup_inbox = function(){ var page = $('.inbox-flags'); if (page.length) { @@ -152,63 +24,6 @@ var setup_badge_details_toggle = function(){ }); }; -/** -* response notifications -*/ -var Inbox = function() { - WrappedElement.call(this); -}; -inherits(Inbox, WrappedElement); - -Inbox.prototype.getMemoId = function() { - return this._parent_element.data('responseId'); -}; - -Inbox.prototype.getMemoElement = function() { - var reId = this.getMemoId(); - return $('#re_' + reId); -}; - -Inbox.prototype.removeMemo = function() { - this.getMemoElement().remove(); -}; - -Inbox.prototype.markMemo = function() { - var memo = this.getMemoElement(); - var checkbox = memo.find('input[type="checkbox"]'); - checkbox.attr('checked', true); -}; - -Inbox.prototype.moderatePost = function(reasonId, actionType){ - var me = this; - var data = { - reject_reason_id: reasonId, - memo_list: [me.getMemoId()], - action_type: actionType - }; - $.ajax({ - type: 'POST', - dataType: 'json', - cache: false, - data: JSON.stringify(data), - url: askbot['urls']['manageInbox'], - success: function(data){ - if (data['success']){ - me.removeMemo(); - me.dispose(); - if (actionType === 'delete') { - notify.show(gettext('Post deleted')); - } else if (actionType === 'remove_flag') { - notify.show(gettext('Post approved')); - } - } else { - notify.show(data['message']); - } - } - }); -}; - - /** * the dropdown menu with selection of reasons * to reject posts and a button that starts menu to @@ -233,11 +48,17 @@ DeclineAndExplainMenu.prototype.addReason = function(id, title) { li.append(button); button.html(title); button.data('reasonId', id); + button.attr('data-reason-id', id); this._addReasonBtn.parent().before(li); this.setupDeclinePostHandler(button); }; +DeclineAndExplainMenu.prototype.removeReason = function(id) { + var btn = this._element.find('a[data-reason-id="' + id + '"]'); + btn.parent().remove(); +}; + DeclineAndExplainMenu.prototype.setControls = function(controls) { this._controls = controls; }; @@ -266,6 +87,8 @@ DeclineAndExplainMenu.prototype.decorate = function(element) { manageReasonsDialog.decorate($('#manage-reject-reasons-modal')); this._manageReasonsDialog = manageReasonsDialog; manageReasonsDialog.setMenu(this); + + setupButtonEventHandlers(addReasonBtn, function() { manageReasonsDialog.show(); }); }; /** @@ -350,7 +173,7 @@ PostModerationControls.prototype.getModHandler = function(action, items, optReas url: askbot['urls']['moderatePostEdits'], success: function(response_data){ if (response_data['success'] == true){ - me.removeEntries(selectedEditIds); + me.removeEntries(response_data['memo_ids']); } if (response_data['message']) { me.showMessage(response_data['message']); @@ -411,7 +234,7 @@ var ManageRejectReasonsDialog = function(){ WrappedElement.call(this); this._selected_edit_ids = null; this._selected_reason_id = null; - this._state = null;//'select', 'preview', 'add-new' + this._state = null;//'select', 'add-new' this._postModerationControls = []; this._selectedEditDataReader = undefined; }; @@ -421,6 +244,10 @@ ManageRejectReasonsDialog.prototype.setMenu = function(menu) { this._reasonsMenu = menu; }; +ManageRejectReasonsDialog.prototype.getMenu = function() { + return this._reasonsMenu; +}; + ManageRejectReasonsDialog.prototype.setSelectedEditDataReader = function(func) { this._selectedEditDataReader = func; }; @@ -445,11 +272,8 @@ ManageRejectReasonsDialog.prototype.setState = function(state){ if (this._element){ this._selector.hide(); this._adder.hide(); - this._previewer.hide(); if (state === 'select'){ this._selector.show(); - } else if (state === 'preview'){ - this._previewer.show(); } else if (state === 'add-new'){ this._adder.show(); } @@ -573,8 +397,10 @@ ManageRejectReasonsDialog.prototype.startSavingReason = function(callback){ title: title_input.getVal(), details: details_input.getVal() }; + var reasonIsNew = true; if (this._selected_reason_id){ data['reason_id'] = this._selected_reason_id; + reasonIsNew = false; } var me = this; @@ -589,6 +415,9 @@ ManageRejectReasonsDialog.prototype.startSavingReason = function(callback){ if (data['success']){ //show current reason data and focus on it me.addSelectableReason(data); + if (reasonIsNew) { + me.getMenu().addReason(data['reason_id'], data['title']); + } if (callback){ callback(data); } else { @@ -601,47 +430,13 @@ ManageRejectReasonsDialog.prototype.startSavingReason = function(callback){ }); }; -ManageRejectReasonsDialog.prototype.rejectPost = function(reason_id){ - var me = this; - var memos = this._selected_edit_data['elements']; - var memo_ids = this._selected_edit_data['id_list']; - var data = { - reject_reason_id: reason_id, - memo_list: memo_ids, - action_type: 'delete_post' - }; - $.ajax({ - type: 'POST', - dataType: 'json', - cache: false, - data: JSON.stringify(data), - url: askbot['urls']['manageInbox'], - success: function(data){ - if (data['success']){ - $.each(memos, function(idx, memo) { - $(memo).next('.post-moderation-controls').remove(); - $(memo).remove(); - }); - me.hide(); - } else { - //only fatal errors here - me.setErrors(data['message']); - } - } - }); -}; - -ManageRejectReasonsDialog.prototype.setPreviewerData = function(data){ - this._selected_reason_id = data['id']; - this._element.find('.selected-reason-title').html(data['title']); - this._element.find('.selected-reason-details').html(data['details']); -}; - ManageRejectReasonsDialog.prototype.startEditingReason = function(){ - var title = this._element.find('.selected-reason-title').html(); - var details = this._element.find('.selected-reason-details').html(); + var data = this._select_box.getSelectedItemData(); + var title = $(data['title']).text(); + var details = data['details']; this._title_input.setVal(title); this._details_input.setVal(details); + this._selected_reason_id = data['id']; this.setState('add-new'); }; @@ -668,6 +463,8 @@ ManageRejectReasonsDialog.prototype.startDeletingReason = function(){ success: function(data){ if (data['success']){ select_box.removeItem(reason_id); + me.hideEditButtons(); + me.getMenu().removeReason(reason_id); } else { me.setSelectorErrors(data['message']); } @@ -680,12 +477,21 @@ ManageRejectReasonsDialog.prototype.startDeletingReason = function(){ } }; +ManageRejectReasonsDialog.prototype.hideEditButtons = function() { + this._editButton.hide(); + this._deleteButton.hide(); +}; + +ManageRejectReasonsDialog.prototype.showEditButtons = function() { + this._editButton.show(); + this._deleteButton.show(); +}; + ManageRejectReasonsDialog.prototype.decorate = function(element){ this._element = element; //set default state according to the # of available reasons this._selector = $(element).find('#reject-edit-modal-select'); this._adder = $(element).find('#reject-edit-modal-add-new'); - this._previewer = $(element).find('#reject-edit-modal-preview'); if (this._selector.find('li').length > 0){ this.setState('select'); this.resetInputs(); @@ -694,10 +500,9 @@ ManageRejectReasonsDialog.prototype.decorate = function(element){ this.resetInputs(); } - $(this._element).find('.dropdown-toggle').dropdown(); - var select_box = new SelectBox(); select_box.decorate($(this._selector.find('.select-box'))); + select_box.setSelectHandler(function() { me.showEditButtons() }); this._select_box = select_box; //setup tipped-inputs @@ -706,8 +511,7 @@ ManageRejectReasonsDialog.prototype.decorate = function(element){ title_input.decorate($(reject_title_input)); this._title_input = title_input; - var reject_details_input = $(this._element) - .find('textarea.reject-reason-details'); + var reject_details_input = $(this._element).find('textarea.reject-reason-details'); var details_input = new TippedInput(); details_input.decorate($(reject_details_input)); @@ -722,6 +526,7 @@ ManageRejectReasonsDialog.prototype.decorate = function(element){ me.resetInputs(); me.resetSelectedReasonId(); me.setState('select'); + me.hideEditButtons(); } ); @@ -730,65 +535,26 @@ ManageRejectReasonsDialog.prototype.decorate = function(element){ function(){ me.startSavingReason() } ); - setupButtonEventHandlers( - $(this._element).find('.save-reason-and-reject'), - function(){ - me.startSavingReason( - function(data){ - me.rejectPost(data['reason_id']); - } - ); - } - ); - - setupButtonEventHandlers( - $(this._element).find('.reject'), - function(){ - me.rejectPost(me.getSelectedReasonId()); - } - ); - - setupButtonEventHandlers( - element.find('.select-other-reason'), - function(){ - me.resetInputs(); - me.setState('select'); - } - ) - setupButtonEventHandlers( element.find('.add-new-reason'), function(){ me.resetSelectedReasonId(); me.resetInputs(); - me.setState('add-new') - } - ); - - setupButtonEventHandlers( - element.find('.select-this-reason'), - function(){ - var data = select_box.getSelectedItemData(); - if (data['id']){ - me.setState('preview'); - me.setPreviewerData(data); - } else { - me.setSelectorErrors( - gettext('A reason must be selected to reject post.') - ) - } + me.setState('add-new') ; } ); + this._editButton = element.find('.edit-this-reason'); setupButtonEventHandlers( - element.find('.edit-reason'), + this._editButton, function(){ me.startEditingReason(); } ); + this._deleteButton = element.find('.delete-this-reason'); setupButtonEventHandlers( - element.find('.delete-this-reason'), + this._deleteButton, function(){ me.startDeletingReason(); } diff --git a/askbot/media/js/utils.js b/askbot/media/js/utils.js index a3eb2028..7d877b4b 100644 --- a/askbot/media/js/utils.js +++ b/askbot/media/js/utils.js @@ -2487,11 +2487,15 @@ SelectBox.prototype.setSelectHandler = function(handler) { this._select_handler = handler; }; +SelectBox.prototype.getSelectHandlerInternal = function() { + return this._select_handler; +}; + SelectBox.prototype.getSelectHandler = function(item) { var me = this; - var handler = this._select_handler; return function(){ me.selectItem(item); + var handler = me.getSelectHandlerInternal(); handler(item.getData()); }; }; diff --git a/askbot/media/style/style.css b/askbot/media/style/style.css index 7d1cd135..a22c5e48 100644 --- a/askbot/media/style/style.css +++ b/askbot/media/style/style.css @@ -4286,6 +4286,10 @@ textarea.tipped-input { width: 515px; margin-bottom: 0px; } +.modal-body > input[type="text"] { + width: 515px; + font-style: normal; +} .tag-subscriptions { border-spacing: 10px; border-collapse: separate; diff --git a/askbot/media/style/style.less b/askbot/media/style/style.less index 55f237ae..e0ea5942 100644 --- a/askbot/media/style/style.less +++ b/askbot/media/style/style.less @@ -4502,6 +4502,13 @@ textarea.tipped-input { width: 515px; margin-bottom: 0px; } +.modal-body > input[type="text"] { + width: 515px; + font-style: normal; +} +.alert .close { + right: -38px; +} .tag-subscriptions { border-spacing: 10px; diff --git a/askbot/models/post.py b/askbot/models/post.py index 94b96370..31396d67 100644 --- a/askbot/models/post.py +++ b/askbot/models/post.py @@ -2221,28 +2221,29 @@ class PostRevision(models.Model): self.post.thread.save() #give message to the poster - if self.by_email: - #todo: move this to the askbot.mail module - from askbot.mail import send_mail - email_context = { - 'site': askbot_settings.APP_SHORT_NAME - } - body_text = _( - 'Thank you for your post to %(site)s. ' - 'It will be published after the moderators review.' - ) % email_context - send_mail( - subject_line = _('your post to %(site)s') % email_context, - body_text = body_text, - recipient_list = [self.author.email,], - ) + if askbot_settings.CONTENT_MODERATION_MODE == 'premoderation': + if self.by_email: + #todo: move this to the askbot.mail module + from askbot.mail import send_mail + email_context = { + 'site': askbot_settings.APP_SHORT_NAME + } + body_text = _( + 'Thank you for your post to %(site)s. ' + 'It will be published after the moderators review.' + ) % email_context + send_mail( + subject_line = _('your post to %(site)s') % email_context, + body_text = body_text, + recipient_list = [self.author.email,], + ) - else: - message = _( - 'Your post was placed on the moderation queue ' - 'and will be published after the moderator approval.' - ) - self.author.message_set.create(message = message) + else: + message = _( + 'Your post was placed on the moderation queue ' + 'and will be published after the moderator approval.' + ) + self.author.message_set.create(message = message) activity_type = const.TYPE_ACTIVITY_MODERATED_NEW_POST else: @@ -2333,7 +2334,7 @@ class PostRevision(models.Model): 'title': self.title, 'html': sanitized_html } - elif self.post.is_answer(): + else: return sanitized_html def get_snippet(self, max_length = 120): diff --git a/askbot/templates/moderation/manage_reject_reasons_dialog.html b/askbot/templates/moderation/manage_reject_reasons_dialog.html new file mode 100644 index 00000000..f9ef0e31 --- /dev/null +++ b/askbot/templates/moderation/manage_reject_reasons_dialog.html @@ -0,0 +1,43 @@ + diff --git a/askbot/templates/moderation/queue.html b/askbot/templates/moderation/queue.html index e204b86d..a0789a6d 100644 --- a/askbot/templates/moderation/queue.html +++ b/askbot/templates/moderation/queue.html @@ -12,7 +12,7 @@ {% trans %}approve posts{% endtrans %} {% trans %}approve posts and users{% endtrans %} - + {% trans %}delete posts and block users{% endtrans %} {% if settings.IP_MODERATION_ENABLED %} {% trans %}delete posts, block users and IPs{% endtrans %} @@ -35,7 +35,7 @@

Attention: approval of users removes them from the queue and approves ALL of their posts, blocking of the users DELETES ALL OF THEIR POSTS. There is no easy undo at the moment.

- {% include "moderation/reject_post_dialog.html" %} + {% include "moderation/manage_reject_reasons_dialog.html" %}
{% for message in messages %}{# messages are grouped by question, using the "nested_messages" #} diff --git a/askbot/templates/moderation/reject_post_dialog.html b/askbot/templates/moderation/reject_post_dialog.html deleted file mode 100644 index 24c75769..00000000 --- a/askbot/templates/moderation/reject_post_dialog.html +++ /dev/null @@ -1,109 +0,0 @@ - diff --git a/askbot/views/moderation.py b/askbot/views/moderation.py index 94561348..8166cb12 100644 --- a/askbot/views/moderation.py +++ b/askbot/views/moderation.py @@ -58,7 +58,10 @@ def moderate_post_edits(request): memo_set = models.ActivityAuditStatus.objects.filter( id__in=post_data['edit_ids'] ).select_related('activity') - result = {'message': ''} + result = { + 'message': '', + 'memo_ids': set() + } if post_data['action'] == 'decline-with-reason': num_posts = 0 @@ -125,6 +128,8 @@ def moderate_post_edits(request): activity_type__in=mod_activity_types ) num_posts = items.count() + memo_set = models.ActivityAuditStatus.objects.filter(user=request.user, activity__in=items) + result['memo_ids'].update(memo_set.values_list('id', flat=True)) items.delete() if num_posts > 0: @@ -135,8 +140,23 @@ def moderate_post_edits(request): editors = get_editors(memo_set, exclude=request.user) num_posts = 0 for editor in editors: + #block user editor.set_status('b') + #delete all content by the user num_posts += request.user.delete_all_content_authored_by_user(editor) + #delete all moderation queue items + mod_activity_types = ( + const.TYPE_ACTIVITY_MARK_OFFENSIVE, + const.TYPE_ACTIVITY_MODERATED_NEW_POST, + const.TYPE_ACTIVITY_MODERATED_POST_EDIT + ) + items = models.Activity.objects.filter( + activity_type__in=mod_activity_types, + user=editor + ) + memo_set = models.ActivityAuditStatus.objects.filter(user=request.user, activity__in=items) + result['memo_ids'].update(memo_set.values_list('id', flat=True)) + items.delete() if num_posts: posts_message = ungettext('%d post deleted', '%d posts deleted', num_posts) % num_posts @@ -148,7 +168,8 @@ def moderate_post_edits(request): result['message'] = concat_messages(result['message'], users_message) moderate_ips = getattr(django_settings, 'ASKBOT_IP_MODERATION_ENABLED', False) - if moderate_ips and 'ips' in post_data and post_data['action'] == 'block': + if moderate_ips and 'ips' in post_data['items'] and post_data['action'] == 'block': + ips = set() for memo in memo_set: obj = memo.activity.content_object if isinstance(obj, models.PostRevision): @@ -157,7 +178,9 @@ def moderate_post_edits(request): #to make sure to not block the admin and #in case REMOTE_ADDR is a proxy server - not #block access to the site - ips.remove(request.META['REMOTE_ADDR']) + my_ip = request.META['REMOTE_ADDR'] + if my_ip in ips: + ips.remove(request.META['REMOTE_ADDR']) from stopforumspam.models import Cache already_blocked = Cache.objects.filter(ip__in=ips) @@ -173,6 +196,8 @@ def moderate_post_edits(request): ips_message = ungettext('%d ip blocked', '%d ips blocked', num_ips) % num_ips result['message'] = concat_messages(result['message'], ips_message) + result['memo_ids'].update(set(memo_set.values_list('id', flat=True))) + result['memo_ids'] = list(result['memo_ids']) memo_set.delete() request.user.update_response_counts() if result['message']: diff --git a/askbot/views/users.py b/askbot/views/users.py index 504e654b..7c3c87c3 100644 --- a/askbot/views/users.py +++ b/askbot/views/users.py @@ -787,7 +787,8 @@ def user_responses(request, user, context): #3) "package" data for the output response_list = list() for memo in memo_set: - if memo.activity.content_object is None: + obj = memo.activity.content_object + if obj is None: continue#a temp plug due to bug in the comment deletion response = { 'id': memo.id, @@ -800,7 +801,7 @@ def user_responses(request, user, context): 'message_type': memo.activity.get_activity_type_display(), 'question_id': memo.activity.question.id, 'followup_messages': list(), - 'content': memo.activity.content_object.html, + 'content': obj.html or obj.text, } response_list.append(response) -- cgit v1.2.3-1-g7c22