From de2bfcb341acab31a8922d407b1efe756d09bbee Mon Sep 17 00:00:00 2001 From: Tomasz Zielinski Date: Thu, 12 Jan 2012 13:34:22 +0100 Subject: Further SearchState && JS cleanup - search kind of works now but still needs a lot of attention --- askbot/conf/__init__.py | 3 +- askbot/forms.py | 71 ----- askbot/search/state_manager.py | 224 +++++---------- askbot/skins/common/media/js/live_search.js | 304 ++++++++------------- askbot/skins/default/templates/macros.html | 29 +- .../skins/default/templates/main_page/tab_bar.html | 3 +- askbot/tests/search_state_tests.py | 2 + askbot/urls.py | 4 +- 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('× '); - html_list.push(tags[i]['used_count']) + html_list.push(tags[i]['used_count']); html_list.push(''); html_list.push('
'); } @@ -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 = $(''); - 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('' + sortButtonData['relevance']['label'] + ''); 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 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 #} - {{label}} ▲ - {% elif sort == key_name + "-desc" %}{# "best first" #} - {{label}} ▼ + {% if current_sort_method == key_name + "-asc" %}{# "worst" first #} + {{label}} ▲ + {% elif current_sort_method == key_name + "-desc" %}{# "best first" #} + {{label}} ▼ {% else %}{# default, when other button is active #} - {{label}} + {{label}} {% endif %}