diff options
author | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2012-05-14 12:37:15 -0400 |
---|---|---|
committer | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2012-05-14 12:37:15 -0400 |
commit | 2225b8828f8b2b3bab55b6ff235a768c2a6a0f8c (patch) | |
tree | a5fa66d61d3ed3bc3347151e79c0a1207131db7a | |
parent | 521a574aa6c029649e18f4592f04990ab69c896c (diff) | |
parent | 81d64e494eeaf184d3dc7e6105b2815486e93053 (diff) | |
download | askbot-2225b8828f8b2b3bab55b6ff235a768c2a6a0f8c.tar.gz askbot-2225b8828f8b2b3bab55b6ff235a768c2a6a0f8c.tar.bz2 askbot-2225b8828f8b2b3bab55b6ff235a768c2a6a0f8c.zip |
Merge branch 'tmp' into user-groups
24 files changed, 279 insertions, 85 deletions
diff --git a/askbot/conf/forum_data_rules.py b/askbot/conf/forum_data_rules.py index 491ebfa8..7d98c9e8 100644 --- a/askbot/conf/forum_data_rules.py +++ b/askbot/conf/forum_data_rules.py @@ -201,6 +201,15 @@ settings.register( ) settings.register( + livesettings.BooleanValue( + FORUM_DATA_RULES, + 'TAG_SEARCH_INPUT_ENABLED', + default = False, + description = _('Enable separate tag search box on main page') + ) +) + +settings.register( livesettings.IntegerValue( FORUM_DATA_RULES, 'MAX_COMMENTS_TO_SHOW', diff --git a/askbot/lamson_handlers.py b/askbot/lamson_handlers.py index 34f93a7b..9062594c 100644 --- a/askbot/lamson_handlers.py +++ b/askbot/lamson_handlers.py @@ -130,7 +130,7 @@ def ASK(message, host = None, addr = None): try: group_tag = Tag.group_tags.get( deleted = False, - name_iexact = addr + name__iexact = addr ) mail.process_emailed_question( from_address, subject, parts, tags = [group_tag.name, ] diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index bfab105a..88c8f7b1 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -1440,6 +1440,7 @@ def user_edit_comment( edited_by = self, by_email = by_email ) + comment_post.thread.invalidate_cached_data() def user_edit_post(self, post = None, diff --git a/askbot/models/post.py b/askbot/models/post.py index c1fc33d6..a0fdc4ff 100644 --- a/askbot/models/post.py +++ b/askbot/models/post.py @@ -518,12 +518,14 @@ class Post(models.Model): def get_absolute_url(self, no_slug = False, question_post=None, thread=None): from askbot.utils.slug import slugify + #todo: the url generation function is pretty bad - + #the trailing slash is entered in three places here + in urls.py if not hasattr(self, '_thread_cache') and thread: self._thread_cache = thread if self.is_answer(): if not question_post: question_post = self.thread._question_post() - return u'%(base)s%(slug)s?answer=%(id)d#post-id-%(id)d' % { + return u'%(base)s%(slug)s/?answer=%(id)d#post-id-%(id)d' % { 'base': urlresolvers.reverse('question', args=[question_post.id]), 'slug': django_urlquote(slugify(self.thread.title)), 'id': self.id @@ -531,9 +533,9 @@ class Post(models.Model): elif self.is_question(): url = urlresolvers.reverse('question', args=[self.id]) if thread: - url += django_urlquote(slugify(thread.title)) + url += django_urlquote(slugify(thread.title)) + '/' elif no_slug is False: - url += django_urlquote(self.slug) + url += django_urlquote(self.slug) + '/' return url elif self.is_comment(): origin_post = self.get_origin_post() @@ -1741,8 +1743,8 @@ class PostRevision(models.Model): return False #if sent by email to group and group does not want moderation - if self.by_email and self.email: - group_name = self.email.split('@')[0] + if self.by_email and self.email_address: + group_name = self.email_address.split('@')[0] try: group = Tag.objects.get(name = group_name, deleted = False) return group.group.profile.moderate_email diff --git a/askbot/models/question.py b/askbot/models/question.py index 58325ccb..a18e719b 100644 --- a/askbot/models/question.py +++ b/askbot/models/question.py @@ -182,8 +182,31 @@ class ThreadManager(models.Manager): qs = qs.filter(posts__post_type='question', posts__author__in=query_users) # TODO: unify with search_state.author ? tags = search_state.unified_tags() - for tag in tags: - qs = qs.filter(tags__name=tag) # Tags or AND-ed here, not OR-ed (i.e. we fetch only threads with all tags) + if len(tags) > 0: + + if askbot_settings.TAG_SEARCH_INPUT_ENABLED: + #todo: this may be gone or disabled per option + #"tag_search_box_enabled" + existing_tags = set( + Tag.objects.filter( + name__in = tags + ).values_list( + 'name', + flat = True + ) + ) + + non_existing_tags = set(tags) - existing_tags + meta_data['non_existing_tags'] = list(non_existing_tags) + tags = existing_tags + else: + meta_data['non_existing_tags'] = list() + + #construct filter for the tag search + for tag in tags: + qs = qs.filter(tags__name=tag) # Tags or AND-ed here, not OR-ed (i.e. we fetch only threads with all tags) + else: + meta_data['non_existing_tags'] = list() if search_state.scope == 'unanswered': qs = qs.filter(closed = False) # Do not show closed questions in unanswered section diff --git a/askbot/search/state_manager.py b/askbot/search/state_manager.py index 8096cbdd..f8154865 100644 --- a/askbot/search/state_manager.py +++ b/askbot/search/state_manager.py @@ -216,9 +216,14 @@ class SearchState(object): ss.page = 1 return ss - def remove_tags(self): + def remove_tags(self, tags = None): ss = self.deepcopy() - ss.tags = [] + if tags: + ss.tags = list( + set(ss.tags) - set(tags) + ) + else: + ss.tags = [] ss.page = 1 return ss diff --git a/askbot/skins/common/media/jquery-openid/openid.css b/askbot/skins/common/media/jquery-openid/openid.css index ec93881d..9a1db85f 100644 --- a/askbot/skins/common/media/jquery-openid/openid.css +++ b/askbot/skins/common/media/jquery-openid/openid.css @@ -1,9 +1,8 @@ -div#login-icons {padding:20px 0 0 0;} +div#login-icons {padding: 0;} ul.login-icons {width: 450px; margin:0;padding:0;text-align:left; list-style-type:none; display:block;} ul.login-icons li {display:inline;} ul.large input {height: 40px; width: 90px;border:1px solid #ccc;margin:0 5px 5px 0;} .openid-signin h1 {padding-bottom: 10px;} -.openid-signin h2 {margin-top:15px;} .openid-signin h2#account-recovery-heading {margin-bottom:2px;} #account-recovery-form p.hint a {color:#1b79bd; text-decoration: none;} #account-recovery-form p.hint a:hover {text-decoration: underline;} diff --git a/askbot/skins/common/media/js/live_search.js b/askbot/skins/common/media/js/live_search.js index 100c3f67..f33862a1 100644 --- a/askbot/skins/common/media/js/live_search.js +++ b/askbot/skins/common/media/js/live_search.js @@ -1,3 +1,55 @@ +var TagWarningBox = function(){ + WrappedElement.call(this); + this._tags = []; +}; +inherits(TagWarningBox, WrappedElement); + +TagWarningBox.prototype.createDom = function(){ + this._element = this.makeElement('div'); + this._element + .css('display', 'block') + .css('margin', '0 0 13px 2px'); + this._element.addClass('non-existing-tags'); + this._warning = this.makeElement('p'); + this._element.append(this._warning); + this._tag_container = this.makeElement('ul'); + this._tag_container.addClass('tags'); + this._element.append(this._tag_container); + this._element.append($('<div class="clearfix"></div>')); + this._element.hide(); +}; + +TagWarningBox.prototype.clear = function(){ + this._tags = []; + if (this._tag_container){ + this._tag_container.empty(); + } + this._warning.hide(); + this._element.hide(); +}; + +TagWarningBox.prototype.addTag = function(tag_name){ + var tag = new Tag(); + tag.setName(tag_name); + tag.setLinkable(false); + tag.setDeletable(false); + var elem = this.getElement(); + this._tag_container.append(tag.getElement()); + this._tag_container.css('display', 'block'); + this._tags.push(tag); + elem.show(); +}; + +TagWarningBox.prototype.showWarning = function(){ + this._warning.html( + ngettext( + 'Sorry, this tag does not exist', + 'Sorry, these tags do not exist', + this._tags.length + ) + ); + this._warning.show(); +}; var liveSearch = function(query_string) { var query = $('input#keywords'); @@ -7,6 +59,70 @@ var liveSearch = function(query_string) { var q_list_sel = 'question-list';//id of question listing div var search_url = askbot['urls']['questions']; var x_button = $('input[name=reset_query]'); + var tag_warning_box = new TagWarningBox(); + + //the tag search input is optional in askbot + $('#ab-tag-search').parent().before( + tag_warning_box.getElement() + ); + + var run_tag_search = function(){ + var search_tags = $('#ab-tag-search').val().split(/\s+/); + if (search_tags.length === 0) { + return; + } + /** @todo: the questions/ might need translation... */ + query_string = '/questions/scope:all/sort:activity-desc/page:1/' + $.each(search_tags, function(idx, tag) { + query_string = QSutils.add_search_tag(query_string, search_tags); + }); + var url = search_url + query_string; + $.ajax({ + url: url, + dataType: 'json', + success: function(data, text_status, xhr){ + render_result(data, text_status, xhr); + $('#ab-tag-search').val(''); + }, + }); + updateHistory(url); + }; + + var activate_tag_search_input = function(){ + //the autocomplete is set up in tag_selector.js + var button = $('#ab-tag-search-add'); + if (button.length === 0){//may be absent + return; + } + var ac = new AutoCompleter({ + url: askbot['urls']['get_tag_list'], + preloadData: true, + minChars: 1, + useCache: true, + matchInside: true, + maxCacheLength: 100, + maxItemsToShow: 20, + onItemSelect: run_tag_search, + delay: 10 + }); + ac.decorate($('#ab-tag-search')); + setupButtonEventHandlers(button, run_tag_search); + //var tag_search_input = $('#ab-tag-search'); + //tag_search_input.keydown( + // makeKeyHandler(13, run_tag_search) + //); + }; + + var render_tag_warning = function(tag_list){ + if ( !tag_list ) { + return; + } + tag_warning_box.clear(); + $.each(tag_list, function(idx, tag_name){ + tag_warning_box.addTag(tag_name); + }); + tag_warning_box.showWarning(); + }; var refresh_x_button = function(){ if(query_val().length > 0){ @@ -69,6 +185,10 @@ var liveSearch = function(query_string) { }, cache: false }); + updateHistory(url); + }; + + var updateHistory = function(url) { var context = { state:1, rand:Math.random() }; History.pushState( context, "Questions", url ); setTimeout(function (){ @@ -195,6 +315,7 @@ var liveSearch = function(query_string) { } render_related_tags(data['related_tags'], data['query_string']); render_relevance_sort_tab(data['query_string']); + render_tag_warning(data['non_existing_tags']); set_active_sort_tab(data['query_data']['sort_order'], data['query_string']); if(data['feed_url']){ // Change RSS URL @@ -214,11 +335,11 @@ var liveSearch = function(query_string) { var askHrefBase = askButton.attr('href').split('?')[0]; askButton.attr('href', askHrefBase + data['query_data']['ask_query_string']); /* INFO: ask_query_string should already be URL-encoded! */ - query.focus(); var old_list = $('#' + q_list_sel); var new_list = $('<div></div>').hide().html(data['questions']); + new_list.find('.timeago').timeago(); old_list.stop(true).after(new_list).fadeOut(200, function() { //show new div with a fadeIn effect old_list.remove(); @@ -260,6 +381,8 @@ var liveSearch = function(query_string) { } }); + activate_tag_search_input(); + $("form#searchForm").submit(function(event) { // if user clicks the button the s(h)e probably wants page reload, // so provide that experience but first update the query string diff --git a/askbot/skins/common/media/js/post.js b/askbot/skins/common/media/js/post.js index f6157848..294c5f41 100644 --- a/askbot/skins/common/media/js/post.js +++ b/askbot/skins/common/media/js/post.js @@ -1215,6 +1215,7 @@ EditCommentForm.prototype.attachTo = function(comment, mode){ this._submit_btn.html(gettext('save comment')); } this.getElement().show(); + this.enableButtons(); this.focus(); putCursorAtEnd(this._textarea); }; @@ -1342,13 +1343,13 @@ EditCommentForm.prototype.createDom = function(){ }; EditCommentForm.prototype.enableButtons = function(){ - this._submit_btn.attr('disabled', ''); - this._cancel_btn.attr('disabled', ''); + this._submit_btn.attr('disabled', false); + this._cancel_btn.attr('disabled', false); }; EditCommentForm.prototype.disableButtons = function(){ - this._submit_btn.attr('disabled', 'disabled'); - this._cancel_btn.attr('disabled', 'disabled'); + this._submit_btn.attr('disabled', true); + this._cancel_btn.attr('disabled', true); }; EditCommentForm.prototype.reset = function(){ @@ -1915,7 +1916,7 @@ var WMD = function(){ WrappedElement.call(this); this._markdown = undefined; this._enabled_buttons = 'bold italic link blockquote code ' + - 'image ol ul heading hr'; + 'image attachment ol ul heading hr'; this._is_previewer_enabled = true; }; inherits(WMD, WrappedElement); diff --git a/askbot/skins/common/media/js/utils.js b/askbot/skins/common/media/js/utils.js index c82913a8..297e3f9a 100644 --- a/askbot/skins/common/media/js/utils.js +++ b/askbot/skins/common/media/js/utils.js @@ -216,7 +216,6 @@ QSutils.add_search_tag = function(query_string, tag){ return this.patch_query_string(query_string, 'tags:' + tag_string); }; - /* **************************************************** */ /* some google closure-like code for the ui elements */ @@ -716,16 +715,6 @@ TwoStateToggle.prototype.isOn = function(){ return this._element.hasClass('on'); }; -TwoStateToggle.prototype.setOn = function(is_on){ - if (is_on){ - this._element.addClass('on'); - this._element.html(this._state_messages['on-state']); - } else { - this._element.removeClass('on'); - this._element.html(this._state_messages['off-state']); - } -}; - TwoStateToggle.prototype.getDefaultHandler = function(){ var me = this; return function(){ @@ -739,7 +728,11 @@ TwoStateToggle.prototype.getDefaultHandler = function(){ data: data, success: function(data) { if (data['success']) { - me.setOn('is_enabled'); + if ( data['is_enabled'] ) { + me.setState('on-state'); + } else { + me.setState('off-state'); + } } else { showMessage(me.getElement(), data['message']); } @@ -748,22 +741,29 @@ TwoStateToggle.prototype.getDefaultHandler = function(){ }; }; +TwoStateToggle.prototype.isCheckBox = function(){ + var element = this._element; + return element.attr('type') === 'checkbox'; +}; + TwoStateToggle.prototype.setState = function(state){ var element = this._element; this._state = state; if (element) { - if ( - element.attr('nodeName') === 'INPUT' && - element.attr('type') === 'checkbox' - ) { + this.resetStyles(); + element.addClass(state); + if (state === 'on-state') { + element.addClass('on'); + } else if (state === 'off-state') { + element.removeClass('on'); + } + if ( this.isCheckBox() ) { if (state === 'on-state') { element.attr('checked', true); } else if (state === 'off-state') { element.attr('checked', false); } } else { - this.resetStyles(); - element.addClass(state); this._element.html(this._state_messages[state]); } } diff --git a/askbot/skins/common/media/js/wmd/wmd.js b/askbot/skins/common/media/js/wmd/wmd.js index 19f32c87..98af264f 100644 --- a/askbot/skins/common/media/js/wmd/wmd.js +++ b/askbot/skins/common/media/js/wmd/wmd.js @@ -2478,7 +2478,7 @@ if(!Attacklab.wmd) mergeEnv(top["wmd_options"]); Attacklab.full = true; - var defaultButtons = "bold italic link blockquote code image ol ul heading hr"; + var defaultButtons = "bold italic link blockquote code image attachment ol ul heading hr"; Attacklab.wmd_env.buttons = Attacklab.wmd_env.buttons || defaultButtons; }; Attacklab.loadEnv(); diff --git a/askbot/skins/common/templates/authopenid/signup_with_password.html b/askbot/skins/common/templates/authopenid/signup_with_password.html index e79263d2..e65cd518 100644 --- a/askbot/skins/common/templates/authopenid/signup_with_password.html +++ b/askbot/skins/common/templates/authopenid/signup_with_password.html @@ -22,12 +22,12 @@ </form> <h2>{% trans %}or create a new user name and password here{% endtrans %}</h2> {% else %} - <h1>{% trans %}Create login name and password{% endtrans %}</h1> - <p class="message">{% trans %}<span class='strong big'>If you prefer, create your forum login name and + <h1 class="section-title">{% trans %}Create login name and password{% endtrans %}</h1> + <!--p class="message">{% trans %}<span class='strong big'>If you prefer, create your forum login name and password here. However</span>, please keep in mind that we also support <strong>OpenID</strong> login method. With <strong>OpenID</strong> you can simply reuse your external login (e.g. Gmail or AOL) without ever sharing -your login details with anyone and having to remember yet another password.{% endtrans %} +your login details with anyone and having to remember yet another password.{% endtrans %}</p--> {%endif%} <form action="{% url user_signup_with_password %}" method="post" accept-charset="utf-8">{% csrf_token %} {{form.login_provider}} diff --git a/askbot/skins/default/media/style/style.less b/askbot/skins/default/media/style/style.less index 0c540698..69cdede0 100644 --- a/askbot/skins/default/media/style/style.less +++ b/askbot/skins/default/media/style/style.less @@ -516,6 +516,8 @@ body.anon { p { margin-bottom: 4px; + color: @info-text; + font-family:@main-font; } p.info-box-follow-up-links { @@ -588,13 +590,22 @@ body.anon { } .inputs{ - #interestingTagInput, #ignoredTagInput, #subscribedTagInput{ + #interestingTagInput, + #ignoredTagInput, + #subscribedTagInput, + #ab-tag-search { width:153px; padding-left:5px; border:#c9c9b5 1px solid; height:25px; } - #interestingTagAdd, #ignoredTagAdd, #subscribedTagAdd { + #ab-tag-search { + width: 135px; + } + #interestingTagAdd, + #ignoredTagAdd, + #subscribedTagAdd, + #ab-tag-search-add { background:url(../images/small-button-blue.png) repeat-x top; border:0; color:@button-label; @@ -605,10 +616,12 @@ body.anon { margin-top:-2px; cursor:pointer; .rounded-corners(4px); - .text-shadow(0px,1px,0px,#E6F6FA); - .box-shadow(1px, 1px, 2px, #808080); - - + .text-shadow(0px,1px,0px,#E6F6FA); + .box-shadow(1px, 1px, 2px, #808080); + } + #ab-tag-search-add { + width: 47px; + margin-left: 3px; } #interestingTagAdd:hover, #ignoredTagAdd:hover, #subscribedTag:hover { background:url(../images/small-button-blue.png) repeat-x bottom; @@ -1286,7 +1299,7 @@ ul#related-tags li { #askFormBar { display:inline-block; - padding: 4px 7px 5px 0px; + padding: 4px 7px 0px 0px; margin-top:0px; p{ @@ -1309,10 +1322,8 @@ ul#related-tags li { .ask-page, .edit-question-page { div#question-list { - float: none; border-bottom:#f0f0ec 1px solid; - float:left; - margin-bottom:10px; + float: none; a{ line-height:30px; } @@ -1359,6 +1370,7 @@ ul#related-tags li { .title-desc { color: @info-text; font-size: 13px; + margin-bottom: 5px; } #fmanswer input.submit, @@ -2214,6 +2226,10 @@ ul#related-tags li { } } +.openid-signin form { + margin-bottom: 5px; +} + #email-input-fs,#local_login_buttons,#password-fs,#openid-fs{ margin-top:10px; #id_email,#id_username,#id_password{ diff --git a/askbot/skins/default/templates/main_page/sidebar.html b/askbot/skins/default/templates/main_page/sidebar.html index fbfbdeb8..7acbe091 100644 --- a/askbot/skins/default/templates/main_page/sidebar.html +++ b/askbot/skins/default/templates/main_page/sidebar.html @@ -10,6 +10,10 @@ {% include "widgets/contributors.html" %} {% endif %} +{% if settings.TAG_SEARCH_INPUT_ENABLED %} + {% include "main_page/tag_search.html" %} +{% endif %} + {% if request.user.is_authenticated() and settings.SIDEBAR_MAIN_SHOW_TAG_SELECTOR %} {% include "widgets/tag_selector.html" %} {% endif %} diff --git a/askbot/skins/default/templates/main_page/tag_search.html b/askbot/skins/default/templates/main_page/tag_search.html new file mode 100644 index 00000000..45f12b2f --- /dev/null +++ b/askbot/skins/default/templates/main_page/tag_search.html @@ -0,0 +1,7 @@ +<div id="tagSearch" class="box"> + <h2>{% trans %}Tag search{% endtrans %}</h2> + <div class="inputs"> + <input id="ab-tag-search" autocomplete="off" type="text"/> + <input id="ab-tag-search-add" type="submit" value="{% trans %}search{% endtrans %}"/> + </div> +</div> diff --git a/askbot/skins/default/templates/users.html b/askbot/skins/default/templates/users.html index bce3f930..b7a16be1 100644 --- a/askbot/skins/default/templates/users.html +++ b/askbot/skins/default/templates/users.html @@ -3,7 +3,7 @@ <!-- users.html --> {% block title %}{% spaceless %}{% trans %}Users{% endtrans %}{% endspaceless %}{% endblock %} {% block before_css %} - {% if group and request.user.is_administrator() %} + {% if group and request.user.is_authenticated() and request.user.is_administrator() %} <link href="{{'/bootstrap/css/bootstrap.css'|media}}" rel="stylesheet" type="text/css" /> {% endif %} {% endblock %} @@ -82,7 +82,7 @@ askbot['urls']['delete_group_logo_url'] = '{% url delete_group_logo %}'; askbot['urls']['join_or_leave_group'] = '{% url join_or_leave_group %}'; </script> - <script type="text/javascript" src='{{"/bootstrap/js/bootstrap.js"|media}}' /> + <script type="text/javascript" src='{{"/bootstrap/js/bootstrap.js"|media}}'></script> <script type='text/javascript' src='{{"/js/editor.js"|media}}'></script> <script type='text/javascript' src='{{"/js/wmd/showdown.js"|media}}'></script> <script type='text/javascript' src='{{"/js/wmd/wmd.js"|media}}'></script> diff --git a/askbot/tests/page_load_tests.py b/askbot/tests/page_load_tests.py index 558ee617..ebfba0c3 100644 --- a/askbot/tests/page_load_tests.py +++ b/askbot/tests/page_load_tests.py @@ -553,19 +553,17 @@ class QuestionPageRedirectTests(AskbotTestCase): url = reverse('question', kwargs={'id': self.q.id}) resp = self.client.get(url) - url = url + self.q.slug - self.assertRedirects(resp, expected_url=url) - - resp = self.client.get(url) - self.assertEqual(200, resp.status_code) - self.assertEqual(self.q, resp.context['question']) + self.assertRedirects( + resp, + expected_url=self.q.get_absolute_url() + ) url = reverse('question', kwargs={'id': 101}) resp = self.client.get(url) - url = reverse('question', kwargs={'id': self.q.id}) + self.q.slug # redirect uses the new question.id ! + url = reverse('question', kwargs={'id': self.q.id}) + self.q.slug + '/'# redirect uses the new question.id ! self.assertRedirects(resp, expected_url=url) - url = reverse('question', kwargs={'id': 101}) + self.q.slug + url = reverse('question', kwargs={'id': 101}) + self.q.slug + '/' resp = self.client.get(url) self.assertEqual(200, resp.status_code) self.assertEqual(self.q, resp.context['question']) @@ -578,7 +576,7 @@ class QuestionPageRedirectTests(AskbotTestCase): url = reverse('question', kwargs={'id': self.q.id}) resp = self.client.get(url, data={'answer': self.a.id}) - url = url + self.q.slug + url = self.q.get_absolute_url() self.assertRedirects(resp, expected_url=url + '?answer=%d' % self.a.id) resp = self.client.get(url, data={'answer': self.a.id}) @@ -586,7 +584,8 @@ class QuestionPageRedirectTests(AskbotTestCase): self.assertEqual(self.q, resp.context['question']) self.assertEqual(self.a, resp.context['show_post']) - url = reverse('question', kwargs={'id': 101}) + self.q.slug + #test redirect from old question + url = reverse('question', kwargs={'id': 101}) + self.q.slug + '/' resp = self.client.get(url, data={'answer': 201}) self.assertRedirects(resp, expected_url=self.a.get_absolute_url()) @@ -597,10 +596,9 @@ class QuestionPageRedirectTests(AskbotTestCase): self.assertEqual(self.a, resp.context['show_post']) self.assertEqual(self.c, resp.context['show_comment']) - url = reverse('question', kwargs={'id': self.q.id}) + url = self.q.get_absolute_url() resp = self.client.get(url, data={'comment': self.c.id}) - url = url + self.q.slug - self.assertRedirects(resp, expected_url=url + '?comment=%d' % self.c.id) + self.assertEqual(200, resp.status_code) resp = self.client.get(url, data={'comment': self.c.id}) self.assertEqual(200, resp.status_code) @@ -608,6 +606,7 @@ class QuestionPageRedirectTests(AskbotTestCase): self.assertEqual(self.a, resp.context['show_post']) self.assertEqual(self.c, resp.context['show_comment']) - url = reverse('question', kwargs={'id': 101}) + self.q.slug - resp = self.client.get(url, data={'comment': 301}) - self.assertRedirects(resp, expected_url=self.c.get_absolute_url()) + url = self.q.get_absolute_url() + #point to a non-existing comment + resp = self.client.get(url, data={'comment': 100301}) + self.assertRedirects(resp, expected_url = self.q.get_absolute_url()) diff --git a/askbot/tests/post_model_tests.py b/askbot/tests/post_model_tests.py index 06bceca1..dd1399c1 100644 --- a/askbot/tests/post_model_tests.py +++ b/askbot/tests/post_model_tests.py @@ -167,17 +167,17 @@ class PostModelTests(AskbotTestCase): th.title = 'lala-x-lala' p = Post(id=3, post_type='question') p._thread_cache = th # cannot assign non-Thread instance directly - self.assertEqual('/question/3/lala-x-lala', p.get_absolute_url(thread=th)) + self.assertEqual('/question/3/lala-x-lala/', p.get_absolute_url(thread=th)) self.assertTrue(p._thread_cache is th) - self.assertEqual('/question/3/lala-x-lala', p.get_absolute_url(thread=th)) + self.assertEqual('/question/3/lala-x-lala/', p.get_absolute_url(thread=th)) def test_cached_get_absolute_url_2(self): p = Post(id=3, post_type='question') th = lambda:1 th.title = 'lala-x-lala' - self.assertEqual('/question/3/lala-x-lala', p.get_absolute_url(thread=th)) + self.assertEqual('/question/3/lala-x-lala/', p.get_absolute_url(thread=th)) self.assertTrue(p._thread_cache is th) - self.assertEqual('/question/3/lala-x-lala', p.get_absolute_url(thread=th)) + self.assertEqual('/question/3/lala-x-lala/', p.get_absolute_url(thread=th)) class ThreadTagModelsTests(AskbotTestCase): @@ -673,4 +673,4 @@ class ThreadRenderCacheUpdateTests(AskbotTestCase): # TODO: (in spare time - those cases should pass without changing anything in code but we should have them eventually for completness) # - Publishing anonymous questions / answers # - Re-posting question as answer and vice versa -# - Management commands (like post_emailed_questions)
\ No newline at end of file +# - Management commands (like post_emailed_questions) diff --git a/askbot/urls.py b/askbot/urls.py index f4768412..799cc346 100644 --- a/askbot/urls.py +++ b/askbot/urls.py @@ -367,6 +367,8 @@ urlpatterns = patterns('', ), ) +#todo - this url below won't work, because it is defined above +#therefore the stackexchange urls feature won't work if getattr(settings, 'ASKBOT_USE_STACKEXCHANGE_URLS', False): urlpatterns += (url( r'^%s(?P<id>\d+)/' % _('questions/'), diff --git a/askbot/utils/decorators.py b/askbot/utils/decorators.py index 940c3654..0042df8f 100644 --- a/askbot/utils/decorators.py +++ b/askbot/utils/decorators.py @@ -87,7 +87,7 @@ def ajax_only(view_func): if data is None: data = {} except Exception, e: - if isinstance(e, Exception): + if hasattr(e, 'messages'): if len(e.messages) > 1: message = u'<ul>' + \ u''.join( diff --git a/askbot/utils/mail.py b/askbot/utils/mail.py index 37245b5a..aa4df320 100644 --- a/askbot/utils/mail.py +++ b/askbot/utils/mail.py @@ -281,7 +281,7 @@ def process_emailed_question(from_address, subject, parts, tags = None): user.post_question( title = title, - tags = tagnames, + tags = tagnames.strip(), body_text = body_text, by_email = True, email_address = from_address diff --git a/askbot/views/commands.py b/askbot/views/commands.py index 2cad834e..6fd493cc 100644 --- a/askbot/views/commands.py +++ b/askbot/views/commands.py @@ -110,7 +110,7 @@ def manage_inbox(request): mail.send_mail( subject_line = _('your post was not accepted'), body_text = unicode(body_text), - recipient_list = [post.author,] + recipient_list = [post.author.email,] ) memo.delete() @@ -589,7 +589,7 @@ def set_tag_filter_strategy(request): """ filter_type = request.POST['filter_type'] filter_value = int(request.POST['filter_value']) - assert(filter_type in 'display', 'email') + assert(filter_type in ('display', 'email')) if filter_type == 'display': assert(filter_value in dict(const.TAG_DISPLAY_FILTER_STRATEGY_CHOICES)) request.user.display_tag_filter_strategy = filter_value diff --git a/askbot/views/readers.py b/askbot/views/readers.py index 613cb3c9..3259cddd 100644 --- a/askbot/views/readers.py +++ b/askbot/views/readers.py @@ -76,6 +76,9 @@ def questions(request, **kwargs): qs, meta_data = models.Thread.objects.run_advanced_search(request_user=request.user, search_state=search_state) + if meta_data['non_existing_tags']: + search_state = search_state.remove_tags(meta_data['non_existing_tags']) + paginator = Paginator(qs, page_size) if paginator.num_pages < search_state.page: search_state.page = 1 @@ -129,10 +132,7 @@ def questions(request, **kwargs): if request.is_ajax(): q_count = paginator.count - if search_state.tags: - question_counter = ungettext('%(q_num)s question, tagged', '%(q_num)s questions, tagged', q_count) - else: - question_counter = ungettext('%(q_num)s question', '%(q_num)s questions', q_count) + question_counter = ungettext('%(q_num)s question', '%(q_num)s questions', q_count) question_counter = question_counter % {'q_num': humanize.intcomma(q_count),} if q_count > page_size: @@ -166,6 +166,7 @@ def questions(request, **kwargs): 'query_string': search_state.query_string(), 'page_size' : page_size, 'questions': questions_html.replace('\n',''), + 'non_existing_tags': meta_data['non_existing_tags'] } ajax_data['related_tags'] = [{ 'name': tag.name, @@ -358,7 +359,7 @@ def question(request, id):#refactor - long subroutine. display question body, an return HttpResponseRedirect(reverse('index')) #redirect if slug in the url is wrong - if request.path.split('/')[-1] != question_post.slug: + if request.path.split('/')[-2] != question_post.slug: logging.debug('no slug match!') question_url = '?'.join(( question_post.get_absolute_url(), @@ -391,7 +392,7 @@ def question(request, id):#refactor - long subroutine. display question body, an 'deleted and is no longer accessible' ) request.user.message_set.create(message = error_message) - return HttpResponseRedirect(reverse('question', kwargs = {'id': id})) + return HttpResponseRedirect(question_post.thread.get_absolute_url()) if str(show_comment.thread._question_post().id) != str(id): return HttpResponseRedirect(show_comment.get_absolute_url()) diff --git a/askbot/views/writers.py b/askbot/views/writers.py index c77c874f..935b7cfa 100644 --- a/askbot/views/writers.py +++ b/askbot/views/writers.py @@ -540,9 +540,10 @@ def __generate_comments_json(obj, user):#non-view generates json data for the po comment_owner = comment.author + tz = ' ' + template_filters.TIMEZONE_STR comment_data = {'id' : comment.id, 'object_id': obj.id, - 'comment_added_at': str(comment.added_at.replace(microsecond = 0)), + 'comment_added_at': str(comment.added_at.replace(microsecond = 0)) + tz, 'html': comment.html, 'user_display_name': comment_owner.username, 'user_url': comment_owner.get_profile_url(), @@ -602,11 +603,12 @@ def edit_comment(request): is_deletable = template_filters.can_delete_comment(comment_post.author, comment_post) is_editable = template_filters.can_edit_comment(comment_post.author, comment_post) + tz = ' ' + template_filters.TIMEZONE_STR return { 'id' : comment_post.id, 'object_id': comment_post.parent.id, - 'comment_added_at': str(comment_post.added_at.replace(microsecond = 0)), + 'comment_added_at': str(comment_post.added_at.replace(microsecond = 0)) + tz, 'html': comment_post.html, 'user_display_name': comment_post.author.username, 'user_url': comment_post.author.get_profile_url(), |