summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEvgeny Fadeev <evgeny.fadeev@gmail.com>2014-07-30 02:47:07 -0300
committerEvgeny Fadeev <evgeny.fadeev@gmail.com>2014-07-30 02:47:07 -0300
commita848169319062d37b65fd93bed38bdf948c07322 (patch)
tree6255f2916c3d5324f76a9af28314aff9caa075bf
parent383fbc831da08cd1c21ed30420d55e0fb4e81b35 (diff)
downloadaskbot-a848169319062d37b65fd93bed38bdf948c07322.tar.gz
askbot-a848169319062d37b65fd93bed38bdf948c07322.tar.bz2
askbot-a848169319062d37b65fd93bed38bdf948c07322.zip
merge questions feature works
-rw-r--r--askbot/conf/words.py18
-rw-r--r--askbot/doc/source/changelog.rst1
-rw-r--r--askbot/media/images/merge.pngbin0 -> 380 bytes
-rw-r--r--askbot/media/js/post.js160
-rw-r--r--askbot/media/js/utils.js44
-rw-r--r--askbot/media/style/style.css24
-rw-r--r--askbot/media/style/style.less33
-rw-r--r--askbot/models/__init__.py19
-rw-r--r--askbot/models/post.py33
-rw-r--r--askbot/models/question.py5
-rw-r--r--askbot/templates/question.html31
-rw-r--r--askbot/templates/question/answer_controls.html2
-rw-r--r--askbot/templates/question/question_controls.html1
-rw-r--r--askbot/tests/api_v1_tests.py2
-rw-r--r--askbot/urls.py5
-rw-r--r--askbot/views/api_v1.py4
-rw-r--r--askbot/views/commands.py17
17 files changed, 370 insertions, 29 deletions
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
@@ -140,6 +140,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,
'WORDS_ASKED',
default=_('asked'),
description=_('asked'),
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
--- /dev/null
+++ b/askbot/media/images/merge.png
Binary files 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<this._tags.length; i++) {
+ this._tags[i].dispose();
+ }
+ for (i=0; i<data['tags'].length; i++) {
+ var tag = new Tag();
+ tag.setLinkable(false);
+ tag.setName(data['tags'][i]);
+ this._previewTags.append(tag.getElement());
+ this._tags.push(tag);
+ }
+ this._preview.fadeIn();
+};
+
+MergeQuestionsDialog.prototype.clearPreview = function() {
+ for (var i=0; i<this._tags.length; i++) {
+ this._tags[i].dispose();
+ }
+ this._previewTitle.html('');
+ this._previewBody.html('');
+ this._previewTags.html('');
+ this._preview.hide();
+};
+
+MergeQuestionsDialog.prototype.getFromId = function() {
+ return this._fromId;
+};
+
+MergeQuestionsDialog.prototype.getToId = function() {
+ return this._idInput.val();
+};
+
+MergeQuestionsDialog.prototype.getPrevToId = function() {
+ return this._prevQuestionId;
+};
+
+MergeQuestionsDialog.prototype.setPrevToId = function(toId) {
+ this._prevQuestionId = toId;
+};
+
+MergeQuestionsDialog.prototype.getLoadPreviewHandler = function() {
+ var me = this;
+ return function() {
+ var prevId = me.getPrevToId();
+ var curId = me.getToId();
+ if (curId && curId != prevId) {
+ $.ajax({
+ type: 'GET',
+ cache: false,
+ dataType: 'json',
+ url: askbot['urls']['apiV1Questions'] + curId + '/',
+ success: function(data) {
+ me.setPreview(data);
+ me.setPrevToId(curId);
+ me.setAcceptButtonText(gettext('Merge'));
+ },
+ error: function() {
+ me.clearPreview();
+ me.setAcceptButtonText(gettext('Load preview'));
+ }
+ });
+ }
+ };
+};
+
+MergeQuestionsDialog.prototype.createDom = function() {
+ //make content
+ var content = this.makeElement('div');
+ var label = this.makeElement('label');
+ label.attr('for', 'question_id');
+ label.html(gettext(askbot['messages']['enterDuplicateQuestionId']));
+ content.append(label);
+ var input = this.makeElement('input');
+ input.attr('type', 'text');
+ input.attr('name', 'question_id');
+ content.append(input);
+ this._idInput = input;
+
+ var preview = this.makeElement('div');
+ content.append(preview);
+ this._preview = preview;
+ preview.hide();
+
+ var title = this.makeElement('h3');
+ preview.append(title);
+ this._previewTitle = title;
+
+ var tags = this.makeElement('div');
+ tags.addClass('tags');
+ this._preview.append(tags);
+ this._previewTags = tags;
+
+ var clr = this.makeElement('div');
+ clr.addClass('clearfix');
+ this._preview.append(clr);
+
+ var body = this.makeElement('div');
+ body.addClass('body');
+ this._preview.append(body);
+ this._previewBody = body;
+
+ var previewHandler = this.getLoadPreviewHandler();
+ var enterHandler = makeKeyHandler(13, previewHandler);
+ input.keydown(enterHandler);
+ input.blur(previewHandler);
+
+ this.setContent(content);
+
+ this.setClass('merge-questions');
+ this.setRejectButtonText(gettext('Cancel'));
+ this.setAcceptButtonText(gettext('Load preview'));
+ this.setHeadingText(askbot['messages']['mergeQuestions']);
+ this.setAcceptHandler(this.getStartMergingHandler());
+
+ MergeQuestionsDialog.superClass_.createDom.call(this);
+
+ this._fromId = $('.post.question').data('postId');
+};
/**
* @constructor
@@ -4775,6 +4926,15 @@ $(document).ready(function() {
var askButton = new AskButton();
askButton.decorate($("#askButton"));
+
+ if (askbot['data']['userIsThreadModerator']) {
+ var mergeQuestions = new MergeQuestionsDialog();
+ $(document).append(mergeQuestions.getElement());
+ var mergeBtn = $('.question-merge');
+ setupButtonEventHandlers(mergeBtn, function() {
+ mergeQuestions.show();
+ });
+ }
});
/* google prettify.js from google code */
diff --git a/askbot/media/js/utils.js b/askbot/media/js/utils.js
index 7d877b4b..f1c3e17e 100644
--- a/askbot/media/js/utils.js
+++ b/askbot/media/js/utils.js
@@ -1452,6 +1452,7 @@ DeleteIcon.prototype.setContent = function(content){
var ModalDialog = function() {
WrappedElement.call(this);
this._accept_button_text = gettext('Ok');
+ this._acceptBtnEnabled = true;
this._reject_button_text = gettext('Cancel');
this._heading_text = 'Add heading by setHeadingText()';
this._initial_content = undefined;
@@ -1472,6 +1473,13 @@ ModalDialog.prototype.hide = function() {
this._element.modal('hide');
};
+ModalDialog.prototype.setClass = function(cls) {
+ this._cssClass = cls;
+ if (this._element) {
+ this._element.addClass(cls);
+ };
+};
+
ModalDialog.prototype.setContent = function(content) {
this._initial_content = content;
if (this._content_element) {
@@ -1489,12 +1497,23 @@ ModalDialog.prototype.setHeadingText = function(text) {
ModalDialog.prototype.setAcceptButtonText = function(text) {
this._accept_button_text = text;
+ if (this._acceptBtn) {
+ this._acceptBtn.html(text);
+ }
};
ModalDialog.prototype.setRejectButtonText = function(text) {
this._reject_button_text = text;
};
+ModalDialog.prototype.hideRejectButton = function() {
+ this._rejectBtn.hide();
+};
+
+ModalDialog.prototype.hideAcceptButton = function() {
+ this._acceptButton.hide();
+};
+
ModalDialog.prototype.setAcceptHandler = function(handler) {
this._accept_handler = handler;
};
@@ -1516,10 +1535,28 @@ ModalDialog.prototype.setMessage = function(text, message_type) {
this.prependContent(box.getElement());
};
+ModalDialog.prototype.disableAcceptButton = function() {
+ this._acceptBtnEnabled = false;
+ if (this._acceptBtn) {
+ this._acceptBtn.prop('disabled', true);
+ }
+};
+
+ModalDialog.prototype.enableAcceptButton = function() {
+ this._acceptBtnEnabled = true;
+ if (this._acceptBtn) {
+ this._acceptBtn.prop('disabled', false);
+ }
+};
+
ModalDialog.prototype.createDom = function() {
this._element = this.makeElement('div')
var element = this._element;
+ if (this._cssClass) {
+ element.addClass(this._cssClass);
+ }
+
element.addClass('modal');
if (this._className) {
element.addClass(this._className);
@@ -1540,8 +1577,6 @@ ModalDialog.prototype.createDom = function() {
header.append(title);
}
-
-
//2) create content
var body = this.makeElement('div')
body.addClass('modal-body');
@@ -1557,15 +1592,20 @@ ModalDialog.prototype.createDom = function() {
element.append(footer);
var accept_btn = this.makeElement('button');
+ if (this._acceptBtnEnabled === false) {
+ accept_btn.prop('disabled', true);
+ }
accept_btn.addClass('submit');
accept_btn.html(this._accept_button_text);
footer.append(accept_btn);
+ this._acceptBtn = accept_btn;
if (this._reject_button_text) {
var reject_btn = this.makeElement('button');
reject_btn.addClass('submit cancel');
reject_btn.html(this._reject_button_text);
footer.append(reject_btn);
+ this._rejectBtn = reject_btn;
}
//4) attach event handlers to the buttons
diff --git a/askbot/media/style/style.css b/askbot/media/style/style.css
index b9bb6643..8d46fb20 100644
--- a/askbot/media/style/style.css
+++ b/askbot/media/style/style.css
@@ -2291,6 +2291,10 @@ ul#related-tags li {
.question-page .answer-controls .question-close {
background: url(../images/close.png) no-repeat center left;
}
+.question-page .post-controls .question-merge,
+.question-page .answer-controls .question-merge {
+ background: url(../images/merge.png) no-repeat;
+}
.question-page .post-controls .permant-link,
.question-page .answer-controls .permant-link {
background: url(../images/link.png) no-repeat 2px 1px;
@@ -4305,9 +4309,29 @@ textarea.tipped-input {
.modal-footer {
text-align: left;
}
+.modal h3 {
+ padding: 0;
+}
.modal p {
font-size: 14px;
}
+.modal.merge-questions .modal-body label {
+ padding-right: 8px;
+}
+.modal.merge-questions .modal-body .body,
+.modal.merge-questions .modal-body .tags {
+ clear: both;
+}
+.modal.merge-questions .modal-body h3 {
+ line-height: 22px;
+ margin-top: 12px;
+}
+.modal.merge-questions .modal-body .tags {
+ margin-top: 12px;
+}
+.modal.merge-questions .modal-body .body {
+ margin-top: 16px;
+}
.modal-body > 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 @@
<script type="text/javascript">
/*<![CDATA[*/
//below is pure cross-browser javascript, no jQuery
- askbot['data']['userIsThreadModerator'] = {% if user_is_thread_moderator %}true{% else %}false{% endif %};
+ askbot['data']['userIsThreadModerator'] = {{ user_is_thread_moderator|as_js_bool }};
askbot['data']['oldestAnswerId'] = {% if oldest_answer_id %}{{ oldest_answer_id }}{% else %}-1{% endif %};
{% if settings.READ_ONLY_MODE_ENABLED %}
askbot['settings']['readOnlyModeEnabled'] = true;
@@ -173,7 +173,9 @@
//hide publish/unpublish answer links
var answerId = 'post-' + postId + '-publish';
var pubBtn = document.getElementById(answerId);
- pubBtn.parentNode.removeChild(pubBtn);
+ if (pubBtn) {
+ removeNode(pubBtn);
+ }
}
}
@@ -208,6 +210,13 @@
return;
}
+ var deleteBtn = document.getElementById('post-' + post_id + '-delete');
+ var controls = deleteBtn.parentNode;
+ var mergeBtn = findChildrenByClassName(controls, 'question-merge');
+ if (mergeBtn.length === 1 && data['userIsThreadModerator'] === false) {
+ removeNode(mergeBtn[0]);
+ }
+
if (data['userIsAdminOrMod']){
return;//all remaining functions stay on
}
@@ -215,12 +224,9 @@
//todo: remove edit button from older comments
return;
}
- var deleteBtn = document.getElementById('post-' + post_id + '-delete');
- var controls = deleteBtn.parentNode;
if (//maybe remove "delete" button
(data['userReputation'] <
- {{settings.MIN_REP_TO_DELETE_OTHERS_COMMENTS}}) ||
- data['userIsReadOnly']
+ {{settings.MIN_REP_TO_DELETE_OTHERS_COMMENTS}}) || data['userIsReadOnly']
) {
removeNode(deleteBtn);
}
@@ -240,6 +246,7 @@
) {
removeNode(closeBtn[0]);
}
+
var repLow = (data['userReputation'] < {{settings.MIN_REP_TO_EDIT_OTHERS_POSTS}});
if (//maybe remove "edit" button
repLow || data['userIsReadOnly']//only authors edit comments
@@ -361,15 +368,19 @@
askbot['urls']['get_html_template'] = '{% url get_html_template %}';
askbot['urls']['getGroupsList'] = '{% url get_groups_list %}';
askbot['urls']['publishAnswer'] = '{% url publish_answer %}';
- askbot['data']['userIsThreadModerator'] = {{ user_is_thread_moderator|as_js_bool }};
+ askbot['urls']['apiV1Questions'] = '{% url api_v1_questions %}';
+ askbot['urls']['mergeQuestions'] = '{% url merge_questions %}';
+ {# already added on top askbot['data']['userIsThreadModerator'] = {{ user_is_thread_moderator|as_js_bool }}; #}
askbot['data']['questionAuthorId'] = {{ question.author_id }};
askbot['data']['threadIsClosed'] = {{ thread.closed|as_js_bool }};
askbot['data']['answersSortTab'] = '{{ tab_id }}';
askbot['data']['questionId'] = {{ question.id }};
askbot['data']['threadSlug'] = '{{ thread.title|slugify }}';
- askbot['messages']['addComment'] = "{% trans %}add a comment{% endtrans %}";
- askbot['messages']['userNamePrompt'] = "{% trans %}User name:{% endtrans %}";
- askbot['messages']['userEmailPrompt'] = "{% trans %}Email address:{% endtrans %}";
+ askbot['messages']['addComment'] = '{% trans %}add a comment{% endtrans %}';
+ askbot['messages']['userNamePrompt'] = '{% trans %}User name:{% endtrans %}';
+ askbot['messages']['userEmailPrompt'] = '{% trans %}Email address:{% endtrans %}';
+ askbot['messages']['mergeQuestions'] = '{{ settings.WORDS_MERGE_QUESTIONS }}';
+ askbot['messages']['enterDuplicateQuestionId'] = '{{ settings.WORDS_ENTER_DUPLICATE_QUESTION_ID }}';
{% if settings.READ_ONLY_MODE_ENABLED %}
askbot['messages']['readOnlyMessage'] = "{{ settings.READ_ONLY_MESSAGE }}";
{% endif %}
diff --git a/askbot/templates/question/answer_controls.html b/askbot/templates/question/answer_controls.html
index 1c66f147..022287a1 100644
--- a/askbot/templates/question/answer_controls.html
+++ b/askbot/templates/question/answer_controls.html
@@ -32,6 +32,7 @@
<a class="question-delete"
>{% if answer.deleted %}{% trans %}undelete{% endtrans %}{% else %}{% trans %}delete{% endtrans %}{% endif %}</a>
</span>
+{% if thread.is_moderated() %}
<span
id="post-{{answer.id}}-publish"
class="action-link"
@@ -48,6 +49,7 @@
>{% trans %}publish{% endtrans %}</a>
{% endif %}
</span>
+{% endif %}
<span class="action-link">
<a class="permant-link"
href="{{ answer.get_absolute_url(question_post=question) }}"
diff --git a/askbot/templates/question/question_controls.html b/askbot/templates/question/question_controls.html
index ddb40e1d..e38157c2 100644
--- a/askbot/templates/question/question_controls.html
+++ b/askbot/templates/question/question_controls.html
@@ -30,6 +30,7 @@
{% else %}
<a class="question-close" href="{% url close question.id %}">{% trans %}close{% endtrans %}</a>
{% endif %}
+<a class="question-merge">{% trans %}merge{% endtrans %}</a>
<a
id="post-{{question.id}}-delete"
class="question-delete"
diff --git a/askbot/tests/api_v1_tests.py b/askbot/tests/api_v1_tests.py
index a90581cd..659ef9e1 100644
--- a/askbot/tests/api_v1_tests.py
+++ b/askbot/tests/api_v1_tests.py
@@ -39,7 +39,7 @@ class ApiV1Tests(AskbotTestCase):
expected_keys = set([
'id', 'view_count', 'title', 'answer_count',
'last_activity_by', 'last_activity_at', 'author',
- 'url', 'tags', 'added_at', 'score'
+ 'url', 'tags', 'added_at', 'score', 'summary'
])
self.assertEqual(expected_keys, set(response_data['questions'][0].keys()))
diff --git a/askbot/urls.py b/askbot/urls.py
index 93fb879c..d06bff72 100644
--- a/askbot/urls.py
+++ b/askbot/urls.py
@@ -275,6 +275,11 @@ urlpatterns = patterns('',
views.writers.answer,
name='answer'
),
+ service_url(
+ r'^merge-questions/',
+ views.commands.merge_questions,
+ name='merge_questions'
+ ),
service_url(#ajax only
r'^vote$',
views.commands.vote,
diff --git a/askbot/views/api_v1.py b/askbot/views/api_v1.py
index c8cab137..76062036 100644
--- a/askbot/views/api_v1.py
+++ b/askbot/views/api_v1.py
@@ -27,14 +27,16 @@ def get_user_data(user):
def get_question_data(thread):
"""returns data dictionary for a given thread"""
+ question_post = thread._question_post()
datum = {
'added_at': thread.added_at.strftime('%s'),
- 'id': thread._question_post().id,
+ 'id': question_post.id,
'answer_count': thread.answer_count,
'view_count': thread.view_count,
'score': thread.score,
'last_activity_at': thread.last_activity_at.strftime('%s'),
'title': thread.title,
+ 'summary': question_post.summary,
'tags': thread.tagnames.strip().split(),
'url': site_url(thread.get_absolute_url()),
}
diff --git a/askbot/views/commands.py b/askbot/views/commands.py
index d9866db9..86fa2abd 100644
--- a/askbot/views/commands.py
+++ b/askbot/views/commands.py
@@ -1566,3 +1566,20 @@ def publish_answer(request):
#todo: notify enquirer by email about the post
request.user.message_set.create(message=message)
return {'redirect_url': answer.get_absolute_url()}
+
+@csrf.csrf_exempt
+@decorators.ajax_only
+@decorators.post_only
+def merge_questions(request):
+ post_data = simplejson.loads(request.raw_post_data)
+ if request.user.is_anonymous():
+ denied_msg = _('Sorry, only thread moderators can use this function')
+ raise exceptions.PermissionDenied(denied_msg)
+
+ form_class = forms.GetDataForPostForm
+ from_form = form_class({'post_id': post_data['from_id']})
+ to_form = form_class({'post_id': post_data['to_id']})
+ if from_form.is_valid() and to_form.is_valid():
+ from_question = get_object_or_404(models.Post, id=from_form.cleaned_data['post_id'])
+ to_question = get_object_or_404(models.Post, id=to_form.cleaned_data['post_id'])
+ request.user.merge_duplicate_questions(from_question, to_question)