Author: andrej
Date: Tue Mar 19 14:48:22 2013
New Revision: 1458321

URL: http://svn.apache.org/r1458321
Log:
Adding compatibility of bhsearch with trac search, remove tabs in bhsearch ui - 
towards #472 (from Anze)

Added:
    
incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/opensearch.xml
Modified:
    
incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch.html
    
incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch_breadcrumbs.html
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/base.py
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/web_ui.py
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/web_ui.py
    incubator/bloodhound/trunk/bloodhound_theme/bhtheme/htdocs/bloodhound.css
    
incubator/bloodhound/trunk/bloodhound_theme/bhtheme/templates/bloodhound_theme.html
    incubator/bloodhound/trunk/bloodhound_theme/bhtheme/theme.py

Modified: 
incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch.html
URL: 
http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch.html?rev=1458321&r1=1458320&r2=1458321&view=diff
==============================================================================
--- 
incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch.html 
(original)
+++ 
incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch.html 
Tue Mar 19 14:48:22 2013
@@ -63,18 +63,6 @@
           <dd>${quickjump.description}</dd>
         </py:if>
 
-        <!--This just a prototype implementation. Should be replaced by proper 
UI mocks-->
-        <div>
-          <!--Render type tabs: All, Ticket, Wiki, etc.-->
-          <ul class="nav nav-tabs" id="search_types">
-            <li py:for="idx, item in enumerate(i for i in types)"
-                class="${classes(first_last(idx, types), active=item.active)}">
-                  <a href="${item.href}">${item.label}</a>
-            </li>
-
-          </ul>
-        </div>
-
         <div py:if="results" class="row">
           <div class="span3 facets">
             <py:if test="facet_counts">

Modified: 
incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch_breadcrumbs.html
URL: 
http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch_breadcrumbs.html?rev=1458321&r1=1458320&r2=1458321&view=diff
==============================================================================
--- 
incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch_breadcrumbs.html
 (original)
+++ 
incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch_breadcrumbs.html
 Tue Mar 19 14:48:22 2013
@@ -23,11 +23,11 @@
       xmlns:py="http://genshi.edgewall.org/";
       xmlns:i18n="http://genshi.edgewall.org/i18n";
       xmlns:xi="http://www.w3.org/2001/XInclude";>
-<a href="${href.bhsearch()}">Search</a>
+<a py:if="active_filter_queries or query" href="${href.bhsearch()}">Search</a>
 <py:for each="active_filter in active_filter_queries">
     &gt; <a href="${active_filter.href}">${active_filter.label}</a>
 </py:for>
 <py:if test="query">
-    &gt; "${query}"
+    &gt; <a href="${active_query.href}">${active_query.label}</a>
 </py:if>
 </html>

Added: 
incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/opensearch.xml
URL: 
http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/opensearch.xml?rev=1458321&view=auto
==============================================================================
--- 
incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/opensearch.xml 
(added)
+++ 
incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/opensearch.xml 
Tue Mar 19 14:48:22 2013
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<OpenSearchDescription xmlns:py="http://genshi.edgewall.org/";
+                       xmlns="http://a9.com/-/spec/opensearch/1.1/";>
+  <ShortName>Search $project.name</ShortName>
+  <InputEncoding>UTF-8</InputEncoding>
+  <Image py:if="chrome.icon.abs_src" width="16" height="16"
+         type="$chrome.icon.mimetype">$chrome.icon.abs_src</Image>
+  <Url type="text/html"
+       template="${abs_href.bhsearch()}?q={searchTerms}&amp;page={startPage}"/>
+</OpenSearchDescription>

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/base.py
URL: 
http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/base.py?rev=1458321&r1=1458320&r2=1458321&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/base.py 
(original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/base.py Tue Mar 
19 14:48:22 2013
@@ -45,9 +45,10 @@ class BaseBloodhoundSearchTest(unittest.
         if create_req:
             self.req = Mock(
                 perm=MockPerm(),
-                chrome={'logo': {}},
+                chrome={'logo': {}, 'links': {}},
                 href=Href("/main"),
                 base_path=BASE_PATH,
+                path_info='/bhsearch',
                 args=arg_list_to_args([]),
             )
 

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/web_ui.py
URL: 
http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/web_ui.py?rev=1458321&r1=1458320&r2=1458321&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/web_ui.py 
(original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/web_ui.py Tue 
Mar 19 14:48:22 2013
@@ -19,15 +19,16 @@
 #  under the License.
 import unittest
 
-from urllib import urlencode, unquote
+from urllib import urlencode, unquote, unquote_plus
 from bhsearch.api import ASC, DESC, SortInstruction
 
 from bhsearch.tests.base import BaseBloodhoundSearchTest
-from bhsearch.web_ui import RequestParameters
+from bhsearch.web_ui import RequestParameters, BloodhoundSearchModule
 from bhsearch.whoosh_backend import WhooshBackend
 
 from trac.test import Mock, MockPerm
 from trac.core import TracError
+from trac.search.web_ui import SearchModule as TracSearchModule
 from trac.util.datefmt import FixedOffset
 from trac.util import format_datetime
 from trac.web import Href, arg_list_to_args, parse_arg_list, RequestDone
@@ -46,15 +47,7 @@ class WebUiTestCaseWithWhoosh(BaseBloodh
         whoosh_backend = WhooshBackend(self.env)
         whoosh_backend.recreate_index()
 
-        self.req = Mock(
-            perm=MockPerm(),
-            chrome={'logo': {}},
-            href=Href("/main"),
-            base_path=BASE_PATH,
-            args=arg_list_to_args([]),
-            redirect=self.redirect
-        )
-
+        self.req.redirect = self.redirect
         self.redirect_url = None
         self.redirect_permanent = None
 
@@ -141,19 +134,7 @@ class WebUiTestCaseWithWhoosh(BaseBloodh
         data = self.process_request()
         #assert
         extra_search_options = dict(data["extra_search_fields"])
-        self.assertEquals("ticket", extra_search_options['type'])
-
-        resource_types = data["types"]
-
-        all = resource_types[0]
-        self._assertResourceType(all, "All", False)
-        self.assertNotIn("type", all["href"])
-
-        ticket = resource_types[1]
-        self._assertResourceType(ticket, "Ticket", True, "type=ticket")
-
-        wiki = resource_types[2]
-        self._assertResourceType(wiki, "Wiki", False, "type=wiki")
+        self.assertEqual("ticket", extra_search_options['type'])
 
     def test_type_parameter_in_links(self):
         self._insert_tickets(12)
@@ -179,51 +160,6 @@ class WebUiTestCaseWithWhoosh(BaseBloodh
         for page in results.shown_pages:
             self.assertIn('type=ticket', page["href"])
 
-    def test_type_grouping(self):
-        self.req.args[RequestParameters.QUERY] = "*:*"
-        data = self.process_request()
-        resource_types =  data["types"]
-
-        all = resource_types[0]
-        self._assertResourceType(all, "All", True)
-        self.assertNotIn("type", all["href"])
-
-        ticket = resource_types[1]
-        self._assertResourceType(ticket, "Ticket", False, "type=ticket")
-
-        wiki = resource_types[2]
-        self._assertResourceType(wiki, "Wiki", False, "type=wiki")
-
-
-    def test_that_there_are_no_page_parameters_for_other_types(self):
-        #arrange
-        self._insert_tickets(12)
-        #act
-        self.req.args[RequestParameters.QUERY] = "*"
-        self.req.args[RequestParameters.PAGELEN] = "4"
-        self.req.args[RequestParameters.PAGE] = "2"
-        data = self.process_request()
-        #assert
-        resource_types =  data["types"]
-
-        all = resource_types[0]
-        self.assertNotIn("page=2", all["href"])
-
-        ticket = resource_types[1]
-        self.assertNotIn("page=", ticket["href"])
-
-    def test_that_there_are_filters_in_type_links(self):
-        #arrange
-#        self._insert_tickets(2)
-        #act
-        self.req.args[RequestParameters.QUERY] = "*"
-        self.req.args[RequestParameters.TYPE] = "ticket"
-        self.req.args[RequestParameters.FILTER_QUERY] = "status:new"
-        data = self.process_request()
-        #assert
-        for type in data["types"]:
-            self.assertNotIn("fq=", type["href"])
-
     def test_that_type_facet_is_in_default_search(self):
         #arrange
         self._insert_tickets(2)
@@ -242,7 +178,7 @@ class WebUiTestCaseWithWhoosh(BaseBloodh
         self.req.args[RequestParameters.QUERY] = "*"
         data = self.process_request()
         #assert
-        facet_counts =  data["facet_counts"]
+        facet_counts = data["facet_counts"]
         status_counts = facet_counts["status"]
         self.assertEquals(1, status_counts["new"]["count"])
         self.assertEquals(1, status_counts["closed"]["count"])
@@ -256,7 +192,7 @@ class WebUiTestCaseWithWhoosh(BaseBloodh
         self.req.args[RequestParameters.QUERY] = "*"
         data = self.process_request()
         #assert
-        facet_counts =  data["facet_counts"]
+        facet_counts = data["facet_counts"]
         status_counts = facet_counts["status"]
         self.assertEquals(1, status_counts["new"]["count"])
         self.assertIn("fq=status%3A%22new%22", status_counts["new"]["href"])
@@ -270,7 +206,7 @@ class WebUiTestCaseWithWhoosh(BaseBloodh
         self.req.args[RequestParameters.QUERY] = "*"
         data = self.process_request()
         #assert
-        facet_counts =  data["facet_counts"]
+        facet_counts = data["facet_counts"]
         status_counts = facet_counts["status"]
         empty_status_count = status_counts[None]
         self.assertEquals(2, empty_status_count["count"])
@@ -280,13 +216,13 @@ class WebUiTestCaseWithWhoosh(BaseBloodh
 
     def test_can_return_empty_facets_result_for_wiki_pages(self):
         #arrange
-        self.insert_wiki("W1","Some text")
+        self.insert_wiki("W1", "Some text")
         #act
         self.req.args[RequestParameters.TYPE] = "wiki"
         self.req.args[RequestParameters.QUERY] = "*"
         data = self.process_request()
         #assert
-        facet_counts =  data["facet_counts"]
+        facet_counts = data["facet_counts"]
         self.assertEquals({}, facet_counts)
 
     def test_can_accept_multiple_filter_query_parameters(self):
@@ -357,14 +293,23 @@ class WebUiTestCaseWithWhoosh(BaseBloodh
         data = self.process_request()
         #assert
         current_filter_queries = data["active_filter_queries"]
-        self.assertEquals(2, len(current_filter_queries))
+        self.assertEquals(3, len(current_filter_queries))
 
-        component_filter =  current_filter_queries[0]
+        type_filter = current_filter_queries[0]
+        self.assertEquals('Ticket', type_filter["label"])
+        self.assertNotIn("type=", type_filter["href"])
+        self.assertNotIn('fq=', unquote(type_filter["href"]))
+
+        component_filter = current_filter_queries[1]
         self.assertEquals('component:"c1"', component_filter["label"])
-        self.assertNotIn("fq=", component_filter["href"])
+        self.assertIn('type=ticket', component_filter["href"])
+        self.assertNotIn('fq=component:"c1"',
+                         unquote(component_filter["href"]))
+        self.assertIn('fq=status:"new"', unquote(component_filter["href"]))
 
-        status_filter =  current_filter_queries[1]
+        status_filter = current_filter_queries[2]
         self.assertEquals('status:"new"', status_filter["label"])
+        self.assertIn('type=ticket', status_filter["href"])
         self.assertIn('fq=component:"c1"', unquote(status_filter["href"]))
         self.assertNotIn('fq=status:"new"', unquote(status_filter["href"]))
 
@@ -587,8 +532,7 @@ class WebUiTestCaseWithWhoosh(BaseBloodh
         data = self.process_request()
         #assert
         extra_search_options = dict(data["extra_search_fields"])
-        self.assertEquals("id, time desc", extra_search_options["sort"])
-        #self.assertNotIn("sort=", active_sort["href"])
+        self.assertEqual("id, time desc", extra_search_options["sort"])
 
     def test_that_document_summary_contains_highlighted_search_terms(self):
         term = "searchterm"
@@ -655,7 +599,6 @@ class WebUiTestCaseWithWhoosh(BaseBloodh
             print id, title
             self.assertIn(id, str(title))
 
-
     def test_that_id_is_highlighted_in_title(self):
         self.insert_ticket("some summary")
         id = "1"
@@ -676,13 +619,159 @@ class WebUiTestCaseWithWhoosh(BaseBloodh
             self.assertLess(len(row['content']), 500)
             self.assertLess(len(row['hilited_content']), 500)
 
+    def test_compatibility_with_legacy_search(self):
+        self.env.config.set('bhsearch', 'enable_redirect', "True")
+        self.req.path_info = '/search'
+
+        self.assertRaises(RequestDone, self.process_request)
+        self.assertIn('/bhsearch', self.redirect_url)
+        self.assertEqual(self.redirect_permanent, True)
+
+        self.req.args['wiki'] = 'on'
+        self.assertRaises(RequestDone, self.process_request)
+        redirect_url = unquote_plus(self.redirect_url)
+        self.assertIn('/bhsearch', redirect_url)
+        self.assertIn('type=wiki', redirect_url)
+        self.assertEqual(self.redirect_permanent, True)
+
+        self.req.args['ticket'] = 'on'
+        self.assertRaises(RequestDone, self.process_request)
+        redirect_url = unquote_plus(self.redirect_url)
+        self.assertIn('fq=type:(ticket OR wiki)', redirect_url)
+        self.assertIn('/bhsearch', self.redirect_url)
+        self.assertEqual(self.redirect_permanent, True)
+
+        self.req.args['milestone'] = 'on'
+        self.assertRaises(RequestDone, self.process_request)
+        redirect_url = unquote_plus(self.redirect_url)
+        self.assertIn('fq=type:(ticket OR wiki OR milestone)', redirect_url)
+        self.assertIn('/bhsearch', self.redirect_url)
+        self.assertEqual(self.redirect_permanent, True)
+
+        self.req.args['changeset'] = 'on'
+        self.assertRaises(RequestDone, self.process_request)
+        redirect_url = unquote_plus(self.redirect_url)
+        self.assertIn('fq=type:(ticket OR wiki OR milestone OR changeset)', 
redirect_url)
+        self.assertIn('/bhsearch', self.redirect_url)
+        self.assertEqual(self.redirect_permanent, True)
+
+    def test_opensearch_integration(self):
+        # pylint: disable=unused-variable
+        self.req.path_info = '/bhsearch/opensearch'
+        bhsearch = BloodhoundSearchModule(self.env)
+
+        url, data, x = bhsearch.process_request(self.req)
+
+        self.assertEqual(url, 'opensearch.xml')
+
+    def test_returns_correct_handler(self):
+        bhsearch = BloodhoundSearchModule(self.env)
+        tracsearch = self.env[TracSearchModule]
+
+        class PathInfoSetter(object):
+            # pylint: disable=incomplete-protocol
+            def __setitem__(other, key, value):
+                if key == "PATH_INFO":
+                    self.req.path_info = value
+        self.req.environ = PathInfoSetter()
+
+        self.env.config.set('bhsearch', 'enable_redirect', "True")
+
+        self.req.path_info = '/search'
+        self.assertIs(bhsearch.pre_process_request(self.req, tracsearch),
+                      bhsearch)
+
+        self.req.path_info = '/bhsearch'
+        self.assertIs(bhsearch.pre_process_request(self.req, tracsearch),
+                      bhsearch)
+
+        self.env.config.set('bhsearch', 'enable_redirect', "False")
+        # With redirect disabled, handler should not be changed.
+        self.req.path_info = '/search'
+        self.assertIs(bhsearch.pre_process_request(self.req, None),
+                      None)
+
+        self.req.path_info = '/bhsearch'
+        self.assertIs(bhsearch.pre_process_request(self.req, None),
+                      None)
+
+    def test_that_correct_search_handle_is_selected_for_quick_search(self):
+        bhsearch = BloodhoundSearchModule(self.env)
+
+        def process_request(path, enable_redirect, is_default):
+            # pylint: disable=unused-variable
+            self.req.path_info = path
+            self.env.config.set('bhsearch', 'enable_redirect',
+                                str(enable_redirect))
+            self.env.config.set('bhsearch', 'is_default', str(is_default))
+            template, data, content_type = \
+                bhsearch.post_process_request(self.req, '', {}, '')
+            return data
+
+        data = process_request('/', enable_redirect=False, is_default=False)
+        self.assertIn('search_handler', data)
+        self.assertEqual(data['search_handler'], self.req.href.search())
+
+        data = process_request('/', enable_redirect=True, is_default=False)
+        self.assertIn('search_handler', data)
+        self.assertEqual(data['search_handler'], self.req.href.bhsearch())
+
+        data = process_request('/', enable_redirect=False, is_default=True)
+        self.assertIn('search_handler', data)
+        self.assertEqual(data['search_handler'], self.req.href.bhsearch())
+
+        data = process_request('/', enable_redirect=True, is_default=True)
+        self.assertIn('search_handler', data)
+        self.assertEqual(data['search_handler'], self.req.href.bhsearch())
+
+        for is_default in [False, True]:
+            data = process_request('/search',
+                                   enable_redirect=False,
+                                   is_default=is_default)
+            self.assertIn('search_handler', data)
+            self.assertEqual(data['search_handler'], self.req.href.search())
+
+        for is_default in [False, True]:
+            data = process_request('/search',
+                                   enable_redirect=True,
+                                   is_default=is_default)
+            self.assertIn('search_handler', data)
+            self.assertEqual(data['search_handler'], self.req.href.bhsearch())
+
+        for enable_redirect in [False, True]:
+            for is_default in [False, True]:
+                data = process_request('/bhsearch',
+                                       enable_redirect=enable_redirect,
+                                       is_default=is_default)
+                self.assertIn('search_handler', data)
+                self.assertEqual(data['search_handler'],
+                                 self.req.href.bhsearch())
+
+    def test_that_active_query_is_set(self):
+        #arrange
+        self.insert_ticket("Ticket 1", component="c1", status="new")
+        self.insert_ticket("Ticket 2", component="c1", status="new")
+        #act
+        self.req.args[RequestParameters.TYPE] = "ticket"
+        self.req.args[RequestParameters.QUERY] = "Ticket"
+        self.req.args[RequestParameters.FILTER_QUERY] = [
+            'component:"c1"',
+            'status:"new"']
+        data = self.process_request()
+        #assert
+        active_query = data["active_query"]
+        self.assertEqual(active_query["label"], '"Ticket"')
+        self.assertEqual(active_query["query"], "Ticket")
+        self.assertNotIn("?q=", unquote(active_query["href"]))
+        self.assertNotIn("&q=", unquote(active_query["href"]))
+        self.assertIn("fq=", unquote(active_query["href"]))
+
     def _find_header(self, headers, name):
         for header in headers:
             if header["name"] == name:
                 return header
         raise Exception("Header not found: %s" % name)
 
-
     def _count_parameter_in_url(self, url, parameter_name, value):
         parameter_to_find = (parameter_name, value)
         parsed_parameters = parse_arg_list(url)
@@ -707,6 +796,7 @@ class WebUiTestCaseWithWhoosh(BaseBloodh
         for i in range(1, n+1):
             self.insert_wiki("test %s" % i)
 
+
 class RequestParametersTest(unittest.TestCase):
     def setUp(self):
         self.req = Mock(

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/web_ui.py
URL: 
http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/web_ui.py?rev=1458321&r1=1458320&r2=1458321&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/web_ui.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/web_ui.py Tue Mar 19 
14:48:22 2013
@@ -31,20 +31,24 @@ from genshi.builder import tag
 from genshi import HTML
 from trac.perm import IPermissionRequestor
 from trac.search import shorten_result
-from trac.config import OrderedExtensionsOption, ListOption, Option
+from trac.config import OrderedExtensionsOption, ListOption, Option, BoolOption
 from trac.util.presentation import Paginator
 from trac.util.datefmt import format_datetime, user_time
-from trac.web import IRequestHandler
+from trac.web import IRequestHandler, IRequestFilter
 from trac.util.translation import _
 from trac.util.html import find_element
 from trac.web.chrome import (INavigationContributor, ITemplateProvider,
-                             add_link, add_stylesheet, web_context)
+                             add_link, add_stylesheet, prevnext_nav,
+                             web_context)
 from bhsearch.api import (BloodhoundSearchApi, ISearchParticipant, SCORE, ASC,
                           DESC, IndexFields, SortInstruction)
 from trac.wiki.formatter import extract_link
 
 SEARCH_PERMISSION = 'SEARCH_VIEW'
 DEFAULT_RESULTS_PER_PAGE = 10
+SEARCH_URL = '/search'
+BHSEARCH_URL = '/bhsearch'
+SEARCH_URLS_RE = re.compile(r'/(?P<prefix>bh)?search(?P<suffix>.*)')
 
 class RequestParameters(object):
     """
@@ -132,8 +136,6 @@ class RequestParameters(object):
 
         return sort if sort else None
 
-
-
     def _remove_possible_duplications(self, parameters_list):
         seen = set()
         return [parameter for parameter in parameters_list
@@ -151,6 +153,7 @@ class RequestParameters(object):
             skip_view=False,
             sort=None,
             skip_sort = False,
+            skip_query = False
     ):
         params = copy.deepcopy(self.params)
 
@@ -183,6 +186,9 @@ class RequestParameters(object):
         elif force_filters is not None:
             params[self.FILTER_QUERY] = force_filters
 
+        if skip_query:
+            self._delete_if_exists(params, self.QUERY)
+
         return self.req.href.bhsearch(**params)
 
     def _create_sort_expression(self, sort):
@@ -204,10 +210,11 @@ class RequestParameters(object):
         if name in params:
             del params[name]
 
+
 class BloodhoundSearchModule(Component):
     """Main search page"""
     implements(INavigationContributor, IPermissionRequestor, IRequestHandler,
-        ITemplateProvider,
+        ITemplateProvider, IRequestFilter
         #           IWikiSyntaxProvider #todo: implement later
     )
 
@@ -245,6 +252,10 @@ class BloodhoundSearchModule(Component):
         default=",".join(default_grid_fields),
         doc="""Default fields for grid view for specific resource""")
 
+    default_search = BoolOption('bhsearch', 'is_default', False)
+
+    redirect_enabled = BoolOption('bhsearch', 'enable_redirect',
+                                  False)
 
     # INavigationContributor methods
     def get_active_navigation_item(self, req):
@@ -263,10 +274,15 @@ class BloodhoundSearchModule(Component):
 
     # IRequestHandler methods
     def match_request(self, req):
-        return re.match(r'/bhsearch?', req.path_info) is not None
+        return re.match('^%s' % BHSEARCH_URL, req.path_info) is not None
 
     def process_request(self, req):
         req.perm.assert_permission(SEARCH_PERMISSION)
+
+        if self._is_opensearch_request(req):
+            return ('opensearch.xml', {},
+                    'application/opensearchdescription+xml')
+
         request_context = RequestContext(
             self.env,
             req,
@@ -276,6 +292,9 @@ class BloodhoundSearchModule(Component):
             self.default_facets
         )
 
+        if request_context.requires_redirect:
+            req.redirect(request_context.parameters.create_href(), True)
+
         # compatibility with legacy search
         req.search_query = request_context.parameters.query
 
@@ -294,6 +313,9 @@ class BloodhoundSearchModule(Component):
         request_context.process_results(query_result)
         return self._return_data(req, request_context.data)
 
+    def _is_opensearch_request(self, req):
+        return req.path_info == BHSEARCH_URL + '/opensearch'
+
     def _return_data(self, req, data):
         add_stylesheet(req, 'common/css/search.css')
         return 'bhsearch.html', data, None
@@ -307,10 +329,29 @@ class BloodhoundSearchModule(Component):
     def get_templates_dirs(self):
         return [pkg_resources.resource_filename(__name__, 'templates')]
 
+    # IRequestFilter methods
+    def pre_process_request(self, req, handler):
+        if SEARCH_URLS_RE.match(req.path_info):
+            if self.redirect_enabled:
+                return self
+        return handler
+
+    def post_process_request(self, req, template, data, content_type):
+        if self.redirect_enabled:
+            data['search_handler'] = req.href.bhsearch()
+        elif req.path_info.startswith(SEARCH_URL):
+            data['search_handler'] = req.href.search()
+        elif self.default_search or req.path_info.startswith(BHSEARCH_URL):
+            data['search_handler'] = req.href.bhsearch()
+        else:
+            data['search_handler'] = req.href.search()
+        return template, data, content_type
+
+
 class RequestContext(object):
     DATA_ACTIVE_FILTER_QUERIES = 'active_filter_queries'
+    DATA_ACTIVE_QUERY = 'active_query'
     DATA_BREADCRUMBS_TEMPLATE = 'resourcepath_template'
-    DATA_TYPES = "types"
     DATA_HEADERS = "headers"
     DATA_ALL_VIEWS = "all_views"
     DATA_VIEW = "view"
@@ -359,13 +400,9 @@ class RequestContext(object):
 
         if self.parameters.sort:
             self.sort = self.parameters.sort
-            self.data[self.DATA_SEARCH_EXTRAS].append(
-                (RequestParameters.SORT, self.parameters.sort_string)
-            )
         else:
             self.sort = self.DEFAULT_SORT
 
-
         self.allowed_participants, self.sorted_participants = \
             self._get_allowed_participants(req)
 
@@ -377,16 +414,20 @@ class RequestContext(object):
             self.active_type = None
             self.active_participant = None
 
-        self._prepare_allowed_types()
-        self._prepare_active_filter_queries()
+        self._prepare_active_type()
+        self._prepare_breadcrumb_items()
+        self._prepare_hidden_search_fields()
         self._prepare_quick_jump()
 
+        # Compatibility with trac search
+        self._process_legacy_type_filters(req, search_participants)
+        if req.path_info.startswith(SEARCH_URL):
+            self.requires_redirect = True
+
         self.fields = self._prepare_fields_and_view()
         self.query_filter = self._prepare_query_filter()
         self.facets = self._prepare_facets()
 
-
-
     def _get_allowed_participants(self, req):
         allowed_participants = {}
         ordered_participants = []
@@ -397,58 +438,67 @@ class RequestContext(object):
                 ordered_participants.append(participant)
         return allowed_participants, ordered_participants
 
-
-    def _prepare_allowed_types(self):
+    def _prepare_active_type(self):
         active_type = self.parameters.type
         if active_type and active_type not in self.allowed_participants:
             raise TracError(_("Unsupported resource type: '%(name)s'",
-                name=active_type))
-        allowed_types = [
-            dict(
-                label=_("All"),
-                active=(active_type is None),
-                href=self.parameters.create_href(
-                    skip_type=True,
-                    skip_page=True,
-                    force_filters=[],
-                ),
-            )
-        ]
-        #we want obtain the same order as in search participants options
-        for participant in self.sorted_participants:
-            allowed_types.append(dict(
-                label=_(participant.get_title()),
-                active=(participant.get_participant_type() == active_type),
-                href=self.parameters.create_href(
-                    type=participant.get_participant_type(),
-                    skip_page=True,
-                    force_filters=[],
-                ),
-            ))
-        self.data[self.DATA_TYPES] = allowed_types
-        self.data[self.DATA_SEARCH_EXTRAS].append(
-            (RequestParameters.TYPE, active_type)
-        )
+                            name=active_type))
 
-    def _prepare_active_filter_queries(self):
+    def _prepare_breadcrumb_items(self):
         current_filters = self.parameters.filter_queries
 
-        def remove_filters_from_list(filer_to_cut_from):
-            return current_filters[:current_filters.index(filer_to_cut_from)]
+        def remove_filter_from_list(filter_to_remove):
+            new_filters = list(current_filters)
+            new_filters.remove(filter_to_remove)
+            return new_filters
+
+        if self.active_type:
+            type_query = self._create_term_expression('type', self.active_type)
+            type_filters = [dict(
+                href=self.parameters.create_href(skip_type=True,
+                                                 force_filters=[]),
+                label=unicode(self.active_type).capitalize(),
+                query=type_query,
+            )]
+        else:
+            type_filters = []
 
         active_filter_queries = [
             dict(
                 href=self.parameters.create_href(
-                    force_filters=remove_filters_from_list(filter_query)
+                    force_filters=remove_filter_from_list(filter_query)
                 ),
                 label=filter_query,
                 query=filter_query,
             ) for filter_query in self.parameters.filter_queries
         ]
-        self.data[self.DATA_ACTIVE_FILTER_QUERIES] = active_filter_queries
-        for filter_query in active_filter_queries:
+        active_query = dict(
+            href=self.parameters.create_href(skip_query=True),
+            label=u'"%s"' % self.parameters.query,
+            query=self.parameters.query
+        )
+
+        self.data[self.DATA_ACTIVE_FILTER_QUERIES] = \
+            type_filters + active_filter_queries
+        self.data[self.DATA_ACTIVE_QUERY] = active_query
+
+    def _prepare_hidden_search_fields(self):
+        if self.active_type:
+            self.data[self.DATA_SEARCH_EXTRAS].append(
+                (RequestParameters.TYPE, self.active_type)
+            )
+
+        if self.parameters.view:
             self.data[self.DATA_SEARCH_EXTRAS].append(
-                (RequestParameters.FILTER_QUERY, filter_query['query'])
+                (RequestParameters.VIEW, self.parameters.view)
+            )
+        if self.parameters.sort:
+            self.data[self.DATA_SEARCH_EXTRAS].append(
+                (RequestParameters.SORT, self.parameters.sort_string)
+            )
+        for filter_query in self.parameters.filter_queries:
+            self.data[self.DATA_SEARCH_EXTRAS].append(
+                (RequestParameters.FILTER_QUERY, filter_query)
             )
 
     def _prepare_quick_jump(self):
@@ -488,7 +538,6 @@ class RequestContext(object):
             else:
                 req.redirect(quickjump_href)
 
-
     def _prepare_fields_and_view(self):
         self._add_views_selector()
         self.view = self._get_view()
@@ -511,10 +560,6 @@ class RequestContext(object):
 
     def _add_views_selector(self):
         active_view = self.parameters.view
-        if active_view:
-            self.data[self.DATA_SEARCH_EXTRAS].append(
-                (RequestParameters.VIEW, active_view)
-            )
 
         all_views = []
         for view, label in self.VIEWS_SUPPORTED.iteritems():
@@ -589,11 +634,31 @@ class RequestContext(object):
     def _prepare_facets(self):
         #TODO: add possibility of specifying facets in query parameters
         if self.active_participant:
-            facets =  self.active_participant.get_default_facets()
+            facets = self.active_participant.get_default_facets()
         else:
-            facets =  self.default_facets
+            facets = self.default_facets
         return facets
 
+    def _process_legacy_type_filters(self, req, search_participants):
+        legacy_type_filters = [sp.get_participant_type()
+                               for sp in search_participants
+                               if sp.get_participant_type() in req.args]
+        if legacy_type_filters:
+            params = self.parameters.params
+            if len(legacy_type_filters) == 1:
+                self.parameters.type = params[RequestParameters.TYPE] = \
+                    legacy_type_filters[0]
+            else:
+                filter_queries = self.parameters.filter_queries
+                if params[RequestParameters.FILTER_QUERY] is not 
filter_queries:
+                    params[RequestParameters.FILTER_QUERY] = filter_queries
+                filter_queries.append(
+                    'type:(%s)' % ' OR '.join(legacy_type_filters)
+                )
+            self.requires_redirect = True
+        else:
+            self.requires_redirect = False
+
     def _process_doc(self, doc):
         ui_doc = dict(doc)
         ui_doc["href"] = self.req.href(doc['type'], doc['id'])
@@ -636,6 +701,7 @@ class RequestContext(object):
             add_link(self.req, 'prev', prev_href, _('Previous Page'))
 
         self.data[self.DATA_RESULTS] = results
+        prevnext_nav(self.req, _('Previous'), _('Next'))
 
     def _prepare_shown_pages(self, results):
         shown_pages = results.get_shown_pages(self.pagelen)

Modified: 
incubator/bloodhound/trunk/bloodhound_theme/bhtheme/htdocs/bloodhound.css
URL: 
http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_theme/bhtheme/htdocs/bloodhound.css?rev=1458321&r1=1458320&r2=1458321&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_theme/bhtheme/htdocs/bloodhound.css 
(original)
+++ incubator/bloodhound/trunk/bloodhound_theme/bhtheme/htdocs/bloodhound.css 
Tue Mar 19 14:48:22 2013
@@ -626,9 +626,9 @@ input[type="submit"].btn.btn-micro {
 }
 
 @media (min-width: 1200px) {
- .search_results {
-  width: 920px;
- }
+ .span9.search_results {
+    width: 920px;
+  }
 }
 
 @media (min-width: 979px) and (max-width: 1199px) {

Modified: 
incubator/bloodhound/trunk/bloodhound_theme/bhtheme/templates/bloodhound_theme.html
URL: 
http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_theme/bhtheme/templates/bloodhound_theme.html?rev=1458321&r1=1458320&r2=1458321&view=diff
==============================================================================
--- 
incubator/bloodhound/trunk/bloodhound_theme/bhtheme/templates/bloodhound_theme.html
 (original)
+++ 
incubator/bloodhound/trunk/bloodhound_theme/bhtheme/templates/bloodhound_theme.html
 Tue Mar 19 14:48:22 2013
@@ -91,10 +91,7 @@
             <div id="searchbox" class="btn-toolbar">
               <div class="btn-group">
                 <form id="mainsearch" class="form-inline"
-                      action="${href.search()   if req.path_info == u'/search' 
else
-                                href.bhsearch() if (req.path_info == 
u'/bhsearch' or
-                                                    is_bhsearch_default) else
-                                href.search()}"
+                      action="${search_handler or href.search()}"
                       method="get">
                   <div class="input-append">
                     <input type="text" class="span3" id="q" name="q"

Modified: incubator/bloodhound/trunk/bloodhound_theme/bhtheme/theme.py
URL: 
http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_theme/bhtheme/theme.py?rev=1458321&r1=1458320&r2=1458321&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_theme/bhtheme/theme.py (original)
+++ incubator/bloodhound/trunk/bloodhound_theme/bhtheme/theme.py Tue Mar 19 
14:48:22 2013
@@ -155,8 +155,6 @@ class BloodhoundTheme(ThemeBase):
     labels_footer_right = Option('labels', 'footer_right', '',
         """Text to use as the right aligned footer""")
 
-    is_bhsearch_default = BoolOption('bhsearch', 'is_default', False)
-
     _wiki_pages = None
     Chrome.default_html_doctype = DocType.HTML5
 
@@ -283,7 +281,6 @@ class BloodhoundTheme(ThemeBase):
         if is_active_theme and data is not None:
             data['responsive_layout'] = self.env.config.getbool(
                     'bloodhound', 'responsive_layout', 'true')
-            data['is_bhsearch_default'] = self.is_bhsearch_default
 
         return template, data, content_type
 


Reply via email to