From a848169319062d37b65fd93bed38bdf948c07322 Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Wed, 30 Jul 2014 02:47:07 -0300 Subject: merge questions feature works --- askbot/conf/words.py | 18 +++ askbot/doc/source/changelog.rst | 1 + askbot/media/images/merge.png | Bin 0 -> 380 bytes askbot/media/js/post.js | 160 +++++++++++++++++++++++ askbot/media/js/utils.js | 44 ++++++- askbot/media/style/style.css | 24 ++++ askbot/media/style/style.less | 33 ++++- askbot/models/__init__.py | 19 +-- askbot/models/post.py | 33 ++++- askbot/models/question.py | 5 +- askbot/templates/question.html | 31 +++-- askbot/templates/question/answer_controls.html | 2 + askbot/templates/question/question_controls.html | 1 + askbot/tests/api_v1_tests.py | 2 +- askbot/urls.py | 5 + askbot/views/api_v1.py | 4 +- askbot/views/commands.py | 17 +++ 17 files changed, 370 insertions(+), 29 deletions(-) create mode 100644 askbot/media/images/merge.png diff --git a/askbot/conf/words.py b/askbot/conf/words.py index 5e12b111..bb99bbce 100644 --- a/askbot/conf/words.py +++ b/askbot/conf/words.py @@ -137,6 +137,24 @@ settings.register( ) ) +settings.register( + values.StringValue( + WORDS, + 'WORDS_MERGE_QUESTIONS', + default=_('Merge duplicate questions'), + description=_('Merge duplicate questions') + ) +) + +settings.register( + values.StringValue( + WORDS, + 'WORDS_ENTER_DUPLICATE_QUESTION_ID', + default=_('Enter duplicate question ID'), + description=_('Enter duplicate question ID') + ) +) + settings.register( values.StringValue( WORDS, diff --git a/askbot/doc/source/changelog.rst b/askbot/doc/source/changelog.rst index 308ee4c6..177d9018 100644 --- a/askbot/doc/source/changelog.rst +++ b/askbot/doc/source/changelog.rst @@ -3,6 +3,7 @@ Changes in Askbot Development master branch (only on github) ------------------------------------------ +* Admins and Moderators can merge questions. * Improved moderation modes: flags, audit, premoderation. Watched user status, IP blocking, mass content removal. * Allow bulk deletion of user content simultaneously with blocking diff --git a/askbot/media/images/merge.png b/askbot/media/images/merge.png new file mode 100644 index 00000000..c3edac7b Binary files /dev/null and b/askbot/media/images/merge.png differ diff --git a/askbot/media/js/post.js b/askbot/media/js/post.js index 2efe0bf8..b2c596f8 100644 --- a/askbot/media/js/post.js +++ b/askbot/media/js/post.js @@ -242,6 +242,157 @@ ThreadUsersDialog.prototype.decorate = function(element) { }); }; +var MergeQuestionsDialog = function() { + ModalDialog.call(this); + this._tags = []; + this._prevQuestionId = undefined; +}; +inherits(MergeQuestionsDialog, ModalDialog); + +MergeQuestionsDialog.prototype.show = function() { + MergeQuestionsDialog.superClass_.show.call(this); + this._idInput.focus(); +}; + +MergeQuestionsDialog.prototype.getStartMergingHandler = function() { + var me = this; + return function() { + $.ajax({ + type: 'POST', + cache: false, + dataType: 'json', + url: askbot['urls']['mergeQuestions'], + data: JSON.stringify({ + from_id: me.getFromId(), + to_id: me.getToId() + }), + success: function(data) { + window.location.reload(); + } + }); + }; +}; + +MergeQuestionsDialog.prototype.setPreview = function(data) { + this._previewTitle.html(data['title']); + this._previewBody.html(data['summary']); + for (var i=0; i textarea { width: 515px; margin-bottom: 0px; diff --git a/askbot/media/style/style.less b/askbot/media/style/style.less index d508420c..285b33dc 100644 --- a/askbot/media/style/style.less +++ b/askbot/media/style/style.less @@ -2405,6 +2405,9 @@ ul#related-tags li { .question-close{ background: url(../images/close.png) no-repeat center left; } + .question-merge { + background: url(../images/merge.png) no-repeat; + } .permant-link{ background: url(../images/link.png) no-repeat 2px 1px; } @@ -4520,8 +4523,34 @@ textarea.tipped-input { .modal-footer { text-align: left; } -.modal p { - font-size: 14px; +.modal { + h3 { + padding: 0; + } + p { + font-size: 14px; + } +} +.modal.merge-questions { + .modal-body { + label { + padding-right: 8px; + } + .body, + .tags { + clear: both; + } + h3 { + line-height: 22px; + margin-top: 12px; + } + .tags { + margin-top: 12px; + } + .body { + margin-top: 16px; + } + } } .modal-body > textarea { width: 515px; diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index 77f3a2fd..72c87294 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -1393,14 +1393,13 @@ def user_mark_tags( return cleaned_tagnames, cleaned_wildcards -def user_merge_duplicate_threads(self, from_thread, to_thread): +def user_merge_duplicate_questions(self, from_q, to_q): """merges content from the ``from_thread`` to the ``to-thread``""" #todo: maybe assertion will depend on which questions are merged self.assert_can_merge_questions() - from_q = from_thread._question_post() - to_q = to_thread._question_post() to_q.merge_post(from_q) - from_q.delete() + from_thread = from_q.thread + to_thread = to_q.thread #set new thread value to all posts posts = from_thread.posts.all() posts.update(thread=to_thread) @@ -1415,13 +1414,15 @@ def user_merge_duplicate_threads(self, from_thread, to_thread): answer_map[author].append(answer) for author in answer_map: - if len(answer_map[author]) > 1: - answers = answer_map[author] - first_answer = answers.pop(0) - for answer in answers: + author_answers = answer_map[author] + if author_answers > 1: + first_answer = author_answers.pop(0) + for answer in author_answers: first_answer.merge_post(answer) + from_thread.spaces.clear() from_thread.delete() + to_thread.invalidate_cached_data() @auto_now_timestamp @@ -3057,7 +3058,7 @@ User.add_to_class('follow_question', user_follow_question) User.add_to_class('unfollow_question', user_unfollow_question) User.add_to_class('is_following_question', user_is_following_question) User.add_to_class('mark_tags', user_mark_tags) -User.add_to_class('merge_duplicate_threads', user_merge_duplicate_threads) +User.add_to_class('merge_duplicate_questions', user_merge_duplicate_questions) User.add_to_class('update_response_counts', user_update_response_counts) User.add_to_class('can_create_tags', user_can_create_tags) User.add_to_class('can_have_strong_url', user_can_have_strong_url) diff --git a/askbot/models/post.py b/askbot/models/post.py index 690e4188..7721b37b 100644 --- a/askbot/models/post.py +++ b/askbot/models/post.py @@ -791,8 +791,36 @@ class Post(models.Model): def merge_post(self, post): """merge with other post""" #take latest revision of current post R1 - #for each revision of other post Ri - #append content of Ri to R1 and use author + rev = self.get_latest_revision() + orig_text = rev.text + for rev in post.revisions.all().order_by('revision'): + #for each revision of other post Ri + #append content of Ri to R1 and use author + new_text = orig_text + '\n\n' + rev.text + author = rev.author + self.apply_edit( + edited_by=rev.author, + text=new_text, + comment=_('merged revision'), + by_email=False, + edit_anonymously=rev.is_anonymous, + suppress_email=True, + ip_addr=rev.ip_addr + ) + if post.is_question() or post.is_answer(): + comments = Post.objects.get_comments().filter(parent=post) + comments.update(parent=self) + + #todo: implement redirects + if post.is_question(): + self.old_question_id = post.id + elif post.is_answer(): + self.old_answer_id = post.id + elif post.is_comment(): + self.old_comment_id = post.id + + self.save() + post.delete() def is_private(self): @@ -1841,6 +1869,7 @@ class Post(models.Model): comment=None, wiki=False, is_private=False, + edit_anonymously=False, by_email=False, suppress_email=False, ip_addr=None, diff --git a/askbot/models/question.py b/askbot/models/question.py index 84a358e5..685c3c9e 100644 --- a/askbot/models/question.py +++ b/askbot/models/question.py @@ -980,11 +980,12 @@ class Thread(models.Model): """true if ``user`` is also a thread moderator""" if user.is_anonymous(): return False - elif askbot_settings.GROUPS_ENABLED: - if user.is_administrator_or_moderator(): + if user.is_administrator_or_moderator(): + if askbot_settings.GROUPS_ENABLED: user_groups = user.get_groups(private=True) thread_groups = self.get_groups_shared_with() return bool(set(user_groups) & set(thread_groups)) + return True return False def requires_response_moderation(self, author): diff --git a/askbot/templates/question.html b/askbot/templates/question.html index 10df6027..1557eb38 100644 --- a/askbot/templates/question.html +++ b/askbot/templates/question.html @@ -13,7 +13,7 @@