diff options
-rw-r--r-- | askbot/conf/__init__.py | 3 | ||||
-rw-r--r-- | askbot/forms.py | 71 | ||||
-rw-r--r-- | askbot/search/state_manager.py | 224 | ||||
-rw-r--r-- | askbot/skins/common/media/js/live_search.js | 304 | ||||
-rw-r--r-- | askbot/skins/default/templates/macros.html | 29 | ||||
-rw-r--r-- | askbot/skins/default/templates/main_page/tab_bar.html | 3 | ||||
-rw-r--r-- | askbot/tests/search_state_tests.py | 2 | ||||
-rw-r--r-- | askbot/urls.py | 4 | ||||
-rw-r--r-- | askbot/views/readers.py | 35 |
9 files changed, 196 insertions, 479 deletions
diff --git a/askbot/conf/__init__.py b/askbot/conf/__init__.py index 026a6185..9892a2a4 100644 --- a/askbot/conf/__init__.py +++ b/askbot/conf/__init__.py @@ -31,4 +31,5 @@ def should_show_sort_by_relevance(): """True if configuration support sorting questions by search relevance """ - return ('postgresql_psycopg2' in askbot.get_database_engine_name()) + return True + #return ('postgresql_psycopg2' in askbot.get_database_engine_name()) diff --git a/askbot/forms.py b/askbot/forms.py index 82bbcb25..08645fcd 100644 --- a/askbot/forms.py +++ b/askbot/forms.py @@ -496,77 +496,6 @@ class SendMessageForm(forms.Form): ) -class AdvancedSearchForm(forms.Form): - """nothing must be required in this form - it is used by the main questions view for input validation only - """ - scope = forms.ChoiceField(choices=const.POST_SCOPE_LIST, required=False) - sort = forms.ChoiceField(choices=const.POST_SORT_METHODS, required=False) - query = forms.CharField(max_length=256, required=False) - #search field is actually a button, used to detect manual button click - search = forms.CharField(max_length=16, required=False) - reset_tags = forms.BooleanField(required=False) - reset_author = forms.BooleanField(required=False) - reset_query = forms.BooleanField(required=False) - start_over = forms.BooleanField(required=False) - tags = forms.CharField(max_length=256, required=False) - remove_tag = forms.CharField(max_length=256, required=False) - author = forms.IntegerField(required=False) - page_size = forms.ChoiceField(choices=const.PAGE_SIZE_CHOICES, required=False) - page = forms.IntegerField(required=False) - - def clean_tags(self): - if 'tags' in self.cleaned_data: - tags_input = self.cleaned_data['tags'].strip() - split_re = re.compile(const.TAG_SPLIT_REGEX) - tag_strings = split_re.split(tags_input) - tagname_re = re.compile(const.TAG_REGEX, re.UNICODE) - out = set() - for s in tag_strings: - if tagname_re.search(s): - out.add(s) - if len(out) > 0: - self.cleaned_data['tags'] = out - else: - self.cleaned_data['tags'] = None - return self.cleaned_data['tags'] - - def clean_query(self): - if 'query' in self.cleaned_data: - q = self.cleaned_data['query'].strip() - if q == '': - q = None - self.cleaned_data['query'] = q - return self.cleaned_data['query'] - - def clean_page_size(self): - if 'page_size' in self.cleaned_data: - if self.cleaned_data['page_size'] == '': - self.cleaned_data['page_size'] = None - else: - page_size = self.cleaned_data['page_size'] - #by this time it is guaranteed to be castable as int - self.cleaned_data['page_size'] = int(page_size) - return self.cleaned_data['page_size'] - - def clean(self): - #todo rewrite - data = self.cleaned_data - cleanup_dict(data, 'scope', '') - cleanup_dict(data, 'tags', None) - cleanup_dict(data, 'sort', '') - cleanup_dict(data, 'query', None) - cleanup_dict(data, 'search', '') - cleanup_dict(data, 'reset_tags', False) - cleanup_dict(data, 'reset_author', False) - cleanup_dict(data, 'reset_query', False) - cleanup_dict(data, 'remove_tag', '') - cleanup_dict(data, 'start_over', False) - cleanup_dict(data, 'author', None) - cleanup_dict(data, 'page', None) - cleanup_dict(data, 'page_size', None) - return data - class NotARobotForm(forms.Form): recaptcha = RecaptchaField( private_key = askbot_settings.RECAPTCHA_SECRET, diff --git a/askbot/search/state_manager.py b/askbot/search/state_manager.py index adb56138..627d91e6 100644 --- a/askbot/search/state_manager.py +++ b/askbot/search/state_manager.py @@ -1,27 +1,14 @@ -#search state manager object -#that lives in the session and takes care of the state -#persistece during the search session +"""Search state manager object""" import re -import copy + +from django.utils.http import urlquote + import askbot import askbot.conf from askbot import const from askbot.conf import settings as askbot_settings from askbot.utils.functions import strip_plus -import logging - -ACTIVE_COMMANDS = ( - 'sort', 'search', 'query', - 'reset_query', 'reset_author', 'reset_tags', 'remove_tag', - 'tags', 'scope', 'page_size', 'start_over', - 'page' -) -def some_in(what, where): - for element in what: - if element in where: - return True - return False def extract_matching_token(text, regexes): """if text matches any of the regexes, @@ -92,166 +79,79 @@ def parse_query(query): } class SearchState(object): - def __init__(self): - self.scope = const.DEFAULT_POST_SCOPE - self.query = None - self.stripped_query = None - self.query_tags = [] - self.query_users = [] - self.query_title = None - self.search = None - self.tags = None - self.author = None - self.sort = const.DEFAULT_POST_SORT_METHOD - self.page_size = int(askbot_settings.DEFAULT_QUESTIONS_PAGE_SIZE) - self.page = 1 - self.logged_in = False - logging.debug('new search state initialized') + def __init__(self, scope, sort, query, tags, author, page, page_size, user_logged_in): + # INFO: zip(*[('a', 1), ('b', 2)])[0] == ('a', 'b') + + if (scope not in zip(*const.POST_SCOPE_LIST)[0]) or (scope == 'favorite' and not user_logged_in): + self.scope = const.DEFAULT_POST_SCOPE + else: + self.scope = scope + + if query: + self.query = ' '.join(query.split('+')).strip() + if self.query == '': + self.query = None + else: + self.query = None + + if self.query: + #pull out values of [title:xxx], [user:some one] + #[tag: sometag], title:'xxx', title:"xxx", @user, @'some user', + #and #tag - (hash symbol to delineate the tag + query_bits = parse_query(self.query) + self.stripped_query = query_bits['stripped_query'] + self.query_tags = query_bits['query_tags'] + self.query_users = query_bits['query_users'] + self.query_title = query_bits['query_title'] + else: + self.stripped_query = None + self.query_tags = None + self.query_users = None + self.query_title = None + + if (sort not in zip(*const.POST_SORT_METHODS)[0]) or (sort == 'relevance-desc' and (not self.query or not askbot.conf.should_show_sort_by_relevance())): + self.sort = const.DEFAULT_POST_SORT_METHOD + else: + self.sort = sort + + if tags: + # const.TAG_SPLIT_REGEX, const.TAG_REGEX + #' '.join(tags.split('+')) + self.tags = tags.split('+') + else: + self.tags = None + + if author: + self.author = int(author) + else: + self.author = None + + if page: + self.page = int(page) + else: + self.page = 1 + + if not page_size or page_size not in zip(*const.PAGE_SIZE_CHOICES)[0]: + self.page_size = int(askbot_settings.DEFAULT_QUESTIONS_PAGE_SIZE) + else: + self.page_size = int(page_size) def __str__(self): out = 'scope=%s\n' % self.scope out += 'query=%s\n' % self.query - if hasattr(self, 'search'): - manual_search = (self.search == 'search') - out += 'manual_search = %s\n' % str(manual_search) if self.tags: out += 'tags=%s\n' % ','.join(self.tags) out += 'author=%s\n' % self.author out += 'sort=%s\n' % self.sort out += 'page_size=%d\n' % self.page_size out += 'page=%d\n' % self.page - out += 'logged_in=%s\n' % str(self.logged_in) return out - def reset(self): - #re-initialize, but keep login state - is_logged_in = self.logged_in - self.__init__() - self.logged_in = is_logged_in - - def update_value(self, key, store, reset_page=True): - if key in store: - old_value = getattr(self, key) - new_value = store[key] - if new_value != old_value: - setattr(self, key, new_value) - if reset_page == True: - self.reset_page() - - def update_from_user_input(self, input_dict, user_logged_in): - #todo: this function will probably not - #fit the case of multiple parameters entered at the same tiem - if 'start_over' in input_dict: - self.reset() - - reset_page = True - if 'page' in input_dict: - self.page = input_dict['page'] - reset_page = False # This is done to keep page from resetting in other sorting modes - - if 'page_size' in input_dict: - self.update_value('page_size', input_dict) - self.reset_page()#todo may be smarter here - start with ~same q - - if 'scope' in input_dict: - if input_dict['scope'] == 'favorite' and not user_logged_in: - self.scope = const.DEFAULT_POST_SCOPE - else: - self.update_value('scope', input_dict, reset_page=reset_page) - - if 'tags' in input_dict: - if self.tags: - old_tags = self.tags.copy() - self.tags = self.tags.union(input_dict['tags']) - if self.tags != old_tags: - self.reset_page() - else: - self.tags = input_dict['tags'] - - if 'remove_tag' in input_dict and self.tags: - rm_set = set([input_dict['remove_tag']]) - self.tags -= rm_set - return - - #all resets just return - if 'reset_tags' in input_dict: - if self.tags: - self.tags = None - self.reset_page() - return - - #todo: handle case of deleting tags one-by-one - if 'reset_author' in input_dict: - if self.author: - self.author = None - self.reset_page() - return - - if 'reset_query' in input_dict: - self.reset_query() - if input_dict.get('sort', None) == 'relevance-desc': - self.reset_sort() - return - - self.update_value('author', input_dict, reset_page=reset_page) - - if 'query' in input_dict: - query_bits = parse_query(input_dict['query']) - tmp_input_dict = copy.deepcopy(input_dict) - tmp_input_dict.update(query_bits) - self.update_value('query', tmp_input_dict, reset_page=reset_page)#the original query - #pull out values of [title:xxx], [user:some one] - #[tag: sometag], title:'xxx', title:"xxx", @user, @'some user', - #and #tag - (hash symbol to delineate the tag - self.update_value('stripped_query', tmp_input_dict, reset_page=reset_page) - self.update_value('query_tags', tmp_input_dict, reset_page=reset_page) - self.update_value('query_users', tmp_input_dict, reset_page=reset_page) - self.update_value('query_title', tmp_input_dict, reset_page=reset_page) - self.sort = 'relevance-desc' - elif 'search' in input_dict: - #a case of use nulling search query by hand - #this branch corresponds to hitting search button manually - #when the search query is empty - self.reset_query() - return - elif askbot_settings.DECOUPLE_TEXT_QUERY_FROM_SEARCH_STATE: - #no query in the request and the setting instructs to - #not have the text search query sticky - self.reset_query() - - if 'sort' in input_dict: - if input_dict['sort'] == 'relevance-desc' and self.query is None: - self.reset_sort() - else: - self.update_value('sort', input_dict, reset_page=reset_page) - - #todo: plug - mysql has no relevance sort - if not askbot.conf.should_show_sort_by_relevance(): - if self.sort == 'relevance-desc': - self.reset_sort() - - def reset_page(self): - self.page = 1 - - def reset_query(self): - """reset the search query string and - the associated "sort by relevance command" - """ - if self.query: - self.query = None - self.reset_page() - if self.sort == 'relevance-desc': - self.reset_sort() - - def reset_sort(self): - self.sort = const.DEFAULT_POST_SORT_METHOD - - def query_string(self): - out = 'section:%s' % self.scope + out = 'scope:%s' % self.scope out += '/sort:%s' % self.sort if self.query: - out += '/query:%s' % '+'.join(self.query.split(' ')) + out += '/query:%s' % urlquote(self.query) if self.tags: out += '/tags:%s' % '+'.join(self.tags) if self.author: diff --git a/askbot/skins/common/media/js/live_search.js b/askbot/skins/common/media/js/live_search.js index ded6f4cf..8dcfe85f 100644 --- a/askbot/skins/common/media/js/live_search.js +++ b/askbot/skins/common/media/js/live_search.js @@ -1,13 +1,11 @@ -var prevSortMethod = sortMethod; var liveSearch = function(command, query_string) { var query = $('input#keywords'); - var query_val = function () {return $.trim(query.val());} + var query_val = function () {return $.trim(query.val());}; var prev_text = query_val(); var running = false; var q_list_sel = 'question-list';//id of question listing div var search_url = askbot['urls']['questions']; - var current_url = search_url + query_string; var x_button = $('input[name=reset_query]'); var refresh_x_button = function(){ @@ -22,67 +20,65 @@ var liveSearch = function(command, query_string) { } }; - var process_query = function(){ - if (prev_text.length === 0 && showSortByRelevance){ - if (sortMethod === 'activity-desc'){ - prevSortMethod = sortMethod; - sortMethod = 'relevance-desc'; - } - } - if (current_url !== undefined){ - search_url = '/'; //resetting search_url every times - query_string = current_url; - } - else { - search_url = askbot['urls']['questions']; //resetting search_url every times - } - params = query_string.split('/') - for (var i = 0; i < params.length; i++){ - if (params[i] !== ''){ - if (params[i].substring(0, 5) == "sort:"){ //change the sort method - search_url += 'sort:'+sortMethod+'/' - search_url += 'query:'+ encodeURIComponent(cur_text); //cur_text.split(' ').join('+') + '/' //we add the query here - } - else{ - search_url += params[i] + '/'; - } - } - } - send_query(cur_text); - }; - var restart_query = function() { + sortMethod = 'activity-desc'; query.val(''); - (function reset_sort_method(){ - if (sortMethod === 'relevance-desc'){ - sortMethod = prevSortMethod; - if (sortMethod === 'relevance-desc'){ - sortMethod = 'activity-desc'; - } - } else { - sortMethod = 'activity-desc'; - prevSortMethod = 'activity-desc'; - } - })(); refresh_x_button(); - new_url = remove_from_url(search_url, 'query'); - search_url = askbot['urls']['questions'] + 'reset_query:true/'; - reset_query(new_url, sortMethod); - running = true; + send_query(); }; var eval_query = function(){ - cur_text = query_val(); - if (cur_text !== prev_text && running === false){ - if (cur_text.length >= minSearchWordLength){ - process_query(); - running = true; - } else if (cur_text.length === 0){ + cur_query = query_val(); + if (cur_query !== prev_text && running === false){ + if (cur_query.length >= minSearchWordLength){ + send_query(cur_query); + } else if (cur_query.length === 0){ restart_query(); } } }; + var send_query = function(query_text){ + running = true; + if(query_text === undefined) { // handle missing parameter + query_text = query_val(); + } + query_string = patch_query_string( + query_string, + 'query:' + encodeURIComponent(query_text), + query_text === '' // remove if empty + ); + + var url = search_url + query_string; + $.ajax({ + url: url, + dataType: 'json', + success: render_result, + complete: function(){ + running = false; + eval_query(); + }, + cache: false + }); + prev_text = query_text; + var context = { state:1, rand:Math.random() }; + History.pushState( context, "Questions", url ); + }; + + var refresh_main_page = function (){ + $.ajax({ + url: askbot['urls']['questions'], + data: {preserve_state: true}, + dataType: 'json', + success: render_result + }); + + var context = { state:1, rand:Math.random() }; + var title = "Questions"; + var query = askbot['urls']['questions']; + History.pushState( context, title, query ); + }; + /* *********************************** */ var render_related_tags = function(tags, query_string){ @@ -98,7 +94,7 @@ var liveSearch = function(command, query_string) { html_list.push(tag.getElement().outerHTML()); html_list.push('<span class="tag-number">× '); - html_list.push(tags[i]['used_count']) + html_list.push(tags[i]['used_count']); html_list.push('</span>'); html_list.push('<br />'); } @@ -114,7 +110,6 @@ var liveSearch = function(command, query_string) { } else { $('#listSearchTags').show(); $('#search-tips').show(); - var tags_html = ''; $.each(tags, function(idx, tag_name){ var tag = new Tag(); tag.setName(tag_name); @@ -132,94 +127,77 @@ var liveSearch = function(command, query_string) { var create_relevance_tab = function(query_string){ relevance_tab = $('<a></a>'); - href = '/questions/' + replace_in_url(query_string, 'sort:relevance-desc') + href = search_url + patch_query_string(query_string, 'sort:relevance-desc'); relevance_tab.attr('href', href); relevance_tab.attr('id', 'by_relevance'); relevance_tab.html('<span>' + sortButtonData['relevance']['label'] + '</span>'); return relevance_tab; - } + }; + + /* *************************************** */ - var replace_in_url = function (query_string, param){ - values = param.split(':') - type = values[0] - value = values[1] - params = query_string.split('/') - url="" - - for (var i = 0; i < params.length; i++){ - if (params[i] !== ''){ - if (params[i].substring(0, type.length) == type){ - url += param + '/' - } - else{ - url += params[i] + '/' - } + var get_query_string_selector_value = function (query_string, selector) { + var params = query_string.split('/'); + for(var i=0; i<params.length; i++) { + var param_split = params[i].split(':'); + if(param_split[0] === selector) { + return param_split[1]; } } - return url - } + return undefined; + }; - var remove_from_url = function (query_string, type){ - params = query_string.split('/') - url="" - for (var i = 0; i < params.length; i++){ - if (params[i] !== ''){ - if (params[i].substring(0, type.length) !== type){ - url += params[i] + '/' - } + var patch_query_string = function (query_string, patch, remove) { + var patch_split = patch.split(':'); + var mapping = {}; + var params = query_string.split('/'); + var new_query_string = ''; + + if(!remove) { + mapping[patch_split[0]] = patch_split[1]; // prepopulate the patched selector + } + + for (var i = 0; i < params.length; i++) { + var param_split = params[i].split(':'); + if(param_split[0] !== patch_split[0] && param_split[1]) { + mapping[param_split[0]] = param_split[1]; } } - return '/'+url - } - var remove_tag_from_url =function (query_string, tag){ - url = askbot['urls']['questions']; - flag = false - author = '' - if (query_string !== null){ - params = query_string.split('/') - for (var i = 0; i < params.length; i++){ - if (params[i] !== ''){ - if (params[i].substring(0, 5) == "tags:"){ - tags = params[i].substr(5).split('+'); - new_tags = '' - for(var j = 0; j < tags.length; j++){ - if(encodeURIComponent(tags[j]) !== encodeURIComponent(tag)){ - if (new_tags !== ''){ - new_tags += '+' - } - new_tags += encodeURIComponent(tags[j]); - } - } - if(new_tags !== ''){ - url += 'tags:'+new_tags+'/' - } - flag = true - } - else if (params[i].substring(0, 7) == "author:"){ - author = params[i]; - } - else{ - url += params[i] + '/'; - } - } + var add_selector = function(name) { + if(name in mapping) { + new_query_string += name + ':' + mapping[name] + '/'; } - if (author !== '') { - url += author+'/' + }; + + /* The order of selectors should match the Django URL */ + add_selector('scope'); + add_selector('sort'); + add_selector('query'); + add_selector('tags'); + add_selector('author'); + add_selector('page_size'); + add_selector('page'); + + return new_query_string; + }; + + var remove_search_tag = function(tag){ + var tag_string = get_query_string_selector_value(query_string, 'tags'); + if(!tag_string) return; // early exit + + var tags = tag_string.split('+'); + var new_tags = []; + + for(var j = 0; j < tags.length; j++){ + if(tags[j] !== tag) { + new_tags.push(tags[j]); } } - return url - } + query_string = patch_query_string(query_string, 'tags:' + new_tags.join('+')); - var set_section_tabs = function(query_string){ - var tabs = $('#section_tabs > a'); /* TODO: This doesn't point to anything now */ - tabs.each(function(index, element){ - var tab = $(element); - var tab_name = tab.attr('id').replace(/^by_/,''); - href = '/questions/' + replace_in_url(query_string, 'section:'+tab_name) - tab.attr('href', href); - }); + send_query(); }; var set_active_sort_tab = function(sort_method, query_string){ @@ -229,7 +207,7 @@ var liveSearch = function(command, query_string) { var tab = $(element); var tab_name = tab.attr('id').replace(/^by_/,''); if (tab_name in sortButtonData){ - href = '/questions/' + replace_in_url(query_string, 'sort:'+tab_name+'-desc') + href = search_url + patch_query_string(query_string, 'sort:'+tab_name+'-desc'); tab.attr('href', href); tab.attr('title', sortButtonData[tab_name]['desc_tooltip']); tab.html(sortButtonData[tab_name]['label']); @@ -264,21 +242,6 @@ var liveSearch = function(command, query_string) { } }; - var remove_search_tag = function(tag_name, query_string){ - $.ajax({ - url: askbot['urls']['questions']+'remove_tag:'+encodeURIComponent(tag_name)+'/', - dataType: 'json', - success: render_result, - complete: try_again - }); - search_url = remove_tag_from_url(query_string, tag_name) - current_url = search_url; - var context = { state:1, rand:Math.random() }; - var title = "Questions"; - var query = search_url; - History.pushState( context, title, query ); - }; - var render_result = function(data, text_status, xhr){ if (data['questions'].length > 0){ $('#pager').toggle(data['paginator'] !== '').html(data['paginator']); @@ -291,7 +254,6 @@ var liveSearch = function(command, query_string) { render_related_tags(data['related_tags'], data['query_string']); render_relevance_sort_tab(data['query_string']); set_active_sort_tab(sortMethod, data['query_string']); - set_section_tabs(data['query_string']); if(data['feed_url']){ // Change RSS URL $("#ContentLeft a.rss:first").attr("href", data['feed_url']); @@ -308,61 +270,9 @@ var liveSearch = function(command, query_string) { new_list.fadeIn(400); }); } - } - - /* *********************************** */ - - var try_again = function(){ - running = false; - eval_query(); - } - - var send_query = function(query_text){ - $.ajax({ - url: search_url, - dataType: 'json', - success: render_result, - complete: try_again, - cache: false - }); - prev_text = query_text; - var context = { state:1, rand:Math.random() }; - var title = "Questions"; - var query = search_url; - History.pushState( context, title, query ); - } -/* - var reset_query = function(new_url, sort_method){ - $.ajax({ - url: search_url, - //data: {reset_query: true, sort: sort_method}, - dataType: 'json', - success: render_result, - complete: try_again - }); - prev_text = ''; - var context = { state:1, rand:Math.random() }; - var title = "Questions"; - var query = new_url; - History.pushState( context, title, query ); - } -*/ - var refresh_main_page = function (){ - $.ajax({ - url: askbot['urls']['questions'], - data: {preserve_state: true}, - dataType: 'json', - success: render_result - }); - - var context = { state:1, rand:Math.random() }; - var title = "Questions"; - var query = askbot['urls']['questions']; - History.pushState( context, title, query ); }; - /* *************************************** */ - + /* *********************************** */ if(command === 'refresh') { refresh_main_page(); diff --git a/askbot/skins/default/templates/macros.html b/askbot/skins/default/templates/macros.html index 2ffa5d25..55359e0f 100644 --- a/askbot/skins/default/templates/macros.html +++ b/askbot/skins/default/templates/macros.html @@ -361,22 +361,21 @@ for the purposes of the AJAX comment editor #} button_sort_criterium + "asc" or "desc" #} {% set key_name = button_sort_criterium %} - {% set sort = current_sort_method %} - {% if sort == key_name + "-asc" %}{# "worst" first #} - <a id="by_{{key_name}}" - href={% url questions %}{{ query_string|replace_in_url("sort:"+key_name+"-desc") }} - class="rev on" - title="{{desc_tooltip}}"><span>{{label}} ▲</span></a> - {% elif sort == key_name + "-desc" %}{# "best first" #} - <a id="by_{{key_name}}" - href={% url questions %}{{ query_string|replace_in_url("sort:"+key_name+"-asc") }} - class="rev on" - title="{{asc_tooltip}}"><span>{{label}} ▼</span></a> + {% if current_sort_method == key_name + "-asc" %}{# "worst" first #} + <a id="by_{{key_name}}" + href={% url questions %}{{ query_string|replace_in_url("sort:"+key_name+"-desc") }} + class="rev on" + title="{{desc_tooltip}}"><span>{{label}} ▲</span></a> + {% elif current_sort_method == key_name + "-desc" %}{# "best first" #} + <a id="by_{{key_name}}" + href={% url questions %}{{ query_string|replace_in_url("sort:"+key_name+"-asc") }} + class="rev on" + title="{{asc_tooltip}}"><span>{{label}} ▼</span></a> {% else %}{# default, when other button is active #} - <a id="by_{{key_name}}" - href={% url questions %}{{ query_string|replace_in_url("sort:"+key_name+"-desc") }} - class="off" - title="{{desc_tooltip}}"><span>{{label}}</span></a> + <a id="by_{{key_name}}" + href={% url questions %}{{ query_string|replace_in_url("sort:"+key_name+"-desc") }} + class="off" + title="{{desc_tooltip}}"><span>{{label}}</span></a> {% endif %} <script type="text/javascript">{# need to pass on text translations to js #} var sortButtonData = sortButtonData || {}; diff --git a/askbot/skins/default/templates/main_page/tab_bar.html b/askbot/skins/default/templates/main_page/tab_bar.html index 1802640f..80a4b95f 100644 --- a/askbot/skins/default/templates/main_page/tab_bar.html +++ b/askbot/skins/default/templates/main_page/tab_bar.html @@ -19,7 +19,7 @@ {% set relevance_label = gettext('relevance') %} {% if query %} <a id="by_relevance" - {% if sort == "relevance-desc" %} + {% if sortMethod == "relevance-desc" %} href="{% url questions %}{{ query_string|replace_in_url("sort:relevance-desc") }}" class="on" title="{{asc_relevance_tooltip}}"><span>{{relevance_label}} ▼</span> @@ -75,7 +75,6 @@ query_string = query_string, ) }} - </div> </div> diff --git a/askbot/tests/search_state_tests.py b/askbot/tests/search_state_tests.py index 6a6bf167..21c416f8 100644 --- a/askbot/tests/search_state_tests.py +++ b/askbot/tests/search_state_tests.py @@ -59,6 +59,8 @@ class SearchStateTests(TestCase): self.assertEquals(self.state.sort, 'age-asc') self.update({}) self.assertEquals(self.state.sort, DEFAULT_SORT) + + class ParseQueryTests(unittest.TestCase): def test_extract_users(self): text = '@anna haha @"maria fernanda" @\'diego maradona\' hehe [user:karl marx] hoho user:\' george bush \'' diff --git a/askbot/urls.py b/askbot/urls.py index 794660f2..3c080470 100644 --- a/askbot/urls.py +++ b/askbot/urls.py @@ -61,11 +61,11 @@ urlpatterns = patterns('', # BEGIN Questions (main page) urls. All this urls work both normally and through ajax url( + # Note that all parameters, even if optional, are provided to the view. Non-present ones have None value. (r'^%s' % _('questions') + - r'(%s)?' % r'/section:(?P<scope>\w+)' + + r'(%s)?' % r'/scope:(?P<scope>\w+)' + r'(%s)?' % r'/sort:(?P<sort>[\w\-]+)' + r'(%s)?' % r'/query:(?P<query>[^/]+)' + # INFO: question string cannot contain slash (/), which is a section terminator - r'(%s)?' % r'/search:search' + r'(%s)?' % r'/tags:(?P<tags>[\w\d\-\+\#]+)' + r'(%s)?' % r'/author:(?P<author>\d+)' + r'(%s)?' % r'/page_size:(?P<page_size>\d+)' + diff --git a/askbot/views/readers.py b/askbot/views/readers.py index 72d3f06a..aad42b60 100644 --- a/askbot/views/readers.py +++ b/askbot/views/readers.py @@ -11,7 +11,7 @@ import logging import urllib import operator from django.shortcuts import get_object_or_404 -from django.http import HttpResponseRedirect, HttpResponse, Http404 +from django.http import HttpResponseRedirect, HttpResponse, Http404, HttpResponseNotAllowed from django.core.paginator import Paginator, EmptyPage, InvalidPage from django.template import Context from django.utils import simplejson @@ -27,7 +27,7 @@ from django.http import QueryDict import askbot from askbot import exceptions from askbot.utils.diff import textDiff as htmldiff -from askbot.forms import AdvancedSearchForm, AnswerForm, ShowQuestionForm +from askbot.forms import AnswerForm, ShowQuestionForm from askbot import models from askbot import schedules from askbot.models.badges import award_badges_signal @@ -63,38 +63,15 @@ def index(request):#generates front page - shows listing of questions sorted in """ return HttpResponseRedirect(reverse('questions')) -def questions(request, scope=const.DEFAULT_POST_SCOPE, sort=const.DEFAULT_POST_SORT_METHOD, query=None, - search=None, tags=None, author=None, page=None, reset_tags=None, - reset_author=None, reset_query=None, - remove_tag=None, page_size=None): +def questions(request, **kwargs): """ List of Questions, Tagged questions, and Unanswered questions. matching search query or user selection """ - if request.method == 'POST': # TODO: This is 405 condition, not 404. Django 1.2+ has decorator for this: https://docs.djangoproject.com/en/1.2/topics/http/decorators/#django.views.decorators.http.require_GET - raise Http404 - - #make parameters dictionary - params_dict = { - 'scope': scope, - 'sort': sort, - } - for arg_name in ('query', 'tags'): - if locals().get(arg_name, None): - params_dict[arg_name] = ' '.join(locals()[arg_name].split('+')) - for arg_name in ('search', 'author', 'page', 'reset_tags', 'reset_author', 'reset_query', 'start_over', 'remove_tag', 'page_size'): - if locals().get(arg_name, None): - params_dict[arg_name] = locals()[arg_name] - - #update search state - form = AdvancedSearchForm(params_dict) - if form.is_valid(): - user_input = form.cleaned_data - else: - user_input = None + if request.method != 'GET': + return HttpResponseNotAllowed(['GET']) - search_state = SearchState() - search_state.update_from_user_input(input_dict=user_input, user_logged_in=request.user.is_authenticated()) + search_state = SearchState(user_logged_in=request.user.is_authenticated(), **kwargs) ####### |